// server/supabase/functions/payple-checkout/index.ts // Payple 빌링키 결제 처리 — 웹 결제 페이지에서 카드 등록 후 호출. // 1) 클라이언트가 Payple JS SDK로 카드 등록 → PCD_PAYER_ID(빌링키) 획득 // 2) 이 함수에 payer_id + tier 전달 → 파트너 인증 → 빌링 결제 → DB 업데이트 // verify_jwt = false (requireUser로 직접 인증) import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts' import { requireUser, authErrorResponse, type AuthError } from '../_shared/auth.ts' import { createServiceRoleClient } from '../_shared/quota.ts' import { getPaypleConfig, paypleAuth, paypleBilling, generateOrderId, calcSubscriptionPeriod, TIER_PRICE, TIER_GOODS_NAME, } from '../_shared/payple.ts' interface CheckoutRequest { payer_id: string // PCD_PAYER_ID (빌링키) tier: 'pro' | 'pro_plus' pcd_pay_cardname?: string // 카드사명 (표시용) pcd_pay_cardnum?: string // 카드번호 마스킹 (표시용) } function jsonResponse(body: Record, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } // @ts-expect-error — Deno 런타임 전역 Deno.serve(async (req: Request) => { const preflight = handleCorsPreflightRequest(req) if (preflight) return preflight if (req.method !== 'POST') { return jsonResponse({ error: 'Method not allowed' }, 405) } try { const user = await requireUser(req) const body = (await req.json()) as CheckoutRequest if (!body.payer_id || !body.tier) { return jsonResponse({ error: 'payer_id and tier are required' }, 400) } if (body.tier !== 'pro' && body.tier !== 'pro_plus') { return jsonResponse({ error: 'Invalid tier. Must be pro or pro_plus' }, 400) } const price = TIER_PRICE[body.tier] const goodsName = TIER_GOODS_NAME[body.tier] if (!price || !goodsName) { return jsonResponse({ error: 'Unknown tier' }, 400) } // 1. Payple 파트너 인증 (simple flag — 빌링 결제용) const config = getPaypleConfig() const auth = await paypleAuth(config, { simpleFlag: true }) // 2. 빌링키로 첫 결제 실행 const orderId = generateOrderId(user.id) const billingResult = await paypleBilling(config, auth, { payerId: body.payer_id, amount: price, orderId, goodsName, }) // 3. 결제 성공 → DB 업데이트 const { start, end } = calcSubscriptionPeriod() const serviceClient = createServiceRoleClient() await serviceClient .from('subscriptions') .update({ tier: body.tier, status: 'active', payment_provider: 'payple', payple_payer_id: body.payer_id, payple_pay_oid: billingResult.PCD_PAY_OID || orderId, current_period_start: start, current_period_end: end, cancel_at: null, updated_at: new Date().toISOString(), }) .eq('user_id', user.id) // profiles.tier도 동기화 await serviceClient .from('profiles') .update({ tier: body.tier, updated_at: new Date().toISOString() }) .eq('id', user.id) return jsonResponse({ success: true, tier: body.tier, order_id: billingResult.PCD_PAY_OID || orderId, amount: price, }) } catch (err) { if (err && typeof err === 'object' && 'status' in err && 'message' in err) { return authErrorResponse(err as AuthError, corsHeaders) } const message = err instanceof Error ? err.message : 'Unknown error' return jsonResponse({ error: message }, 500) } })