/* ============================================================================ * 서명전에 — iOS Billing sheet (앱인토스 IAP 인앱결제) * * 백엔드 상품 3종(app/toss_payments.py)과 완벽히 동기화: * - single_text : ₩990 · 텍스트 계약서 1회 * - single_ocr : ₩1,990 · 사진(OCR) 계약서 1회 (2크레딧 차감) * - pack : ₩5,900 · 계약서 6회권 * * 결제 버튼을 누르면 상위 `onPurchase(plan)` 콜백이 (Task #197, 2026-04-24): * 1) TossAdapter.iap.purchase({ productId }) * → Toss SDK 가 결제 UI 표시 → { orderId, receipt, productId } 반환 * 2) POST /payments/iap/confirm { product_id, order_id, receipt, amount } * → 백엔드가 mTLS 로 영수증 검증 후 크레딧 지급 * 을 돌리고 { ok, message, creditsAdded, idempotent } 를 돌려준다. * * 콘솔 등록 전엔 useAuth().iapProductId* 가 null 이라 onPurchase 가 * "결제 준비 중" 메시지를 돌려준다 — 그 메시지를 그대로 시트 배너에 띄운다. * ========================================================================== */ function BillingSheet({ onPurchase, onClose }) { // IAP 상품 ID 매핑 — 콘솔 등록 전엔 null (결제 버튼 비활성화 목적). const auth = (typeof useAuth === "function") ? useAuth() : null; const iapReady = !!( auth?.iapProductIdSingleText || auth?.iapProductIdSingleOcr || auth?.iapProductIdPack ); const [sel, setSel] = useState("pack"); const [busy, setBusy] = useState(false); // status: null | { kind: 'ok'|'err', message: string } const [status, setStatus] = useState(null); const plans = [ { kind: "single_text", title: "텍스트 1회", sub: "카톡·문자로 받은 본문 분석", price: 990, credits: 1, }, { kind: "single_ocr", title: "사진 1회", sub: "사진·PDF 계약서 (OCR)", price: 1990, credits: 2, }, { kind: "pack", title: "6회권", sub: "건당 ₩983 · 가장 아끼는 선택", price: 5900, credits: 6, highlight: true, }, ]; const current = plans.find((p) => p.kind === sel) || plans[0]; async function handlePurchase() { if (busy) return; setStatus(null); setBusy(true); try { const res = await onPurchase?.(current); if (res && res.ok) { setStatus({ kind: "ok", message: `${res.creditsAdded || current.credits}회 충전 완료!` }); // 성공시 짧게 보여주고 시트 닫기 setTimeout(() => { onClose?.(); }, 900); } else { setStatus({ kind: "err", message: (res && res.message) || "결제가 완료되지 않았어요. 잠시 후 다시 시도해 주세요.", }); } } catch (err) { setStatus({ kind: "err", message: err?.message || "결제 중 오류가 발생했어요." }); } finally { setBusy(false); } } return (