'use client' // apps/web/src/components/billing/payple-manage-button.tsx // Payple 구독 관리 — payple-manage Edge Function 호출 (구독 취소) import { useEffect, useState } from 'react' import { Button, CircularProgress, Alert, Box, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material' import SettingsIcon from '@mui/icons-material/Settings' import { getSupabaseBrowserClient } from '@/lib/supabase-browser' export function PaypleManageButton(): React.ReactElement { const [busy, setBusy] = useState(false) const [error, setError] = useState(null) const [confirmOpen, setConfirmOpen] = useState(false) const [success, setSuccess] = useState(false) const [ready, setReady] = useState(false) useEffect(() => setReady(true), []) async function handleCancel(): Promise { setConfirmOpen(false) setError(null) setBusy(true) try { const supabase = getSupabaseBrowserClient() const { data: { session }, error: sessionError } = await supabase.auth.getSession() if (sessionError || !session) { setError('로그인이 필요합니다') return } const baseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.trim() if (!baseUrl) throw new Error('결제 서버 설정이 완료되지 않았습니다') const response = await fetch( new URL('/functions/v1/payple-manage', baseUrl).toString(), { method: 'POST', headers: { Authorization: `Bearer ${session.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'cancel' }), } ) const data = await response.json().catch(() => null) as { success?: unknown; cancel_at?: unknown } | null if (!response.ok) { throw new Error(response.status === 409 ? '구독 취소 확인이 필요합니다. 다시 요청하지 말고 고객센터에 문의해 주세요.' : '구독 취소를 완료하지 못했습니다.') } if ( data?.success !== true || typeof data.cancel_at !== 'string' || !Number.isFinite(Date.parse(data.cancel_at)) ) { throw new Error('구독 취소 결과를 확인할 수 없습니다.') } setSuccess(true) window.setTimeout(() => window.location.reload(), 2000) } catch (e) { setError(e instanceof Error ? e.message : '알 수 없는 오류') } finally { setBusy(false) } } if (success) { return ( 구독이 취소되었습니다. 현재 결제 기간이 끝날 때까지 이용 가능합니다. ) } return ( setConfirmOpen(false)}> 구독 취소 정말 구독을 취소하시겠습니까? 현재 결제 기간이 끝날 때까지는 계속 이용할 수 있습니다. {error && ( {error} )} ) }