// server/supabase/functions/stripe-portal/index.ts // Stripe Customer Portal 세션 생성 — 로그인한 사용자가 자신의 구독을 관리할 수 있도록. // 응답: { url } → 클라이언트가 redirect. import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts' import { requireUser, authErrorResponse, type AuthError } from '../_shared/auth.ts' import { createServiceRoleClient } from '../_shared/quota.ts' interface PortalRequest { return_url: string } // @ts-expect-error — Deno 런타임 전역 Deno.serve(async (req: Request) => { const preflight = handleCorsPreflightRequest(req) if (preflight) return preflight if (req.method !== 'POST') { return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }) } try { const user = await requireUser(req) const body = (await req.json()) as PortalRequest // @ts-expect-error — Deno.env const stripeKey = Deno.env.get('STRIPE_SECRET_KEY') ?? '' if (!stripeKey) { return new Response( JSON.stringify({ error: 'stripe_not_configured' }), { status: 503, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ) } // 기존 customer_id 조회 const serviceClient = createServiceRoleClient() const { data: sub } = await serviceClient .from('subscriptions') .select('stripe_customer_id') .eq('user_id', user.id) .maybeSingle() const customerId = (sub?.stripe_customer_id as string | null | undefined) ?? null if (!customerId) { return new Response( JSON.stringify({ error: 'no_customer', message: '활성 구독이 없습니다. 먼저 업그레이드하세요.' }), { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ) } // Portal session 생성 const portalResp = await fetch('https://api.stripe.com/v1/billing_portal/sessions', { method: 'POST', headers: { Authorization: `Bearer ${stripeKey}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ customer: customerId, return_url: body.return_url }) }) if (!portalResp.ok) { const errText = await portalResp.text() throw new Error(`Portal 세션 생성 실패: ${errText}`) } const data = (await portalResp.json()) as { url: string } return new Response(JSON.stringify({ url: data.url }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }) } 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 new Response(JSON.stringify({ error: message }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }) } })