'use client'; import { useState, useEffect } from 'react'; import { ensureProfile } from '@/lib/ensure-profile'; declare global { interface Window { IMP: any; } } interface TokenPackage { id: string; name: string; price: number; tokens: number; pricePerToken: number; badge?: string; highlight?: boolean; } interface TokenPurchaseModalProps { isOpen: boolean; onClose: () => void; onPurchaseComplete: () => void; user: any; supabase: any; } export default function TokenPurchaseModal({ isOpen, onClose, onPurchaseComplete, user, supabase }: TokenPurchaseModalProps) { const [isFirstPurchase, setIsFirstPurchase] = useState(false); const [loading, setLoading] = useState(true); const [purchasing, setPurchasing] = useState(false); const [credits, setCredits] = useState(0); useEffect(() => { if (!isOpen || !user) return; const checkFirstPurchase = async () => { setLoading(true); // 프로필 확인 (없으면 자동 생성) const currentCredits = await ensureProfile(supabase, user); setCredits(currentCredits); // 결제 내역 확인 const { data: payments, error } = await supabase .from('payments') .select('id') .eq('user_id', user.id) .eq('status', 'paid') .limit(1); if (!error) { setIsFirstPurchase(!payments || payments.length === 0); } setLoading(false); }; checkFirstPurchase(); }, [isOpen, user]); const getPackages = (): TokenPackage[] => { const packages: TokenPackage[] = []; if (isFirstPurchase) { packages.push({ id: 'first_990', name: '첫 결제 특별 혜택', price: 990, tokens: 3, pricePerToken: 330, badge: '첫 결제 한정', highlight: true, }); } packages.push( { id: 'basic_990', name: '기본 패키지', price: 990, tokens: 1, pricePerToken: 990, }, { id: 'standard_2500', name: '스탠다드 패키지', price: 2500, tokens: 3, pricePerToken: 833, badge: '인기', }, { id: 'premium_9000', name: '프리미엄 패키지', price: 9000, tokens: 10, pricePerToken: 900, badge: '최고 가성비', }, ); return packages; }; const handlePurchase = async (pkg: TokenPackage) => { if (!window.IMP) { alert('결제 모듈을 불러오는 중입니다. 잠시 후 다시 시도해주세요.'); return; } setPurchasing(true); const { IMP } = window; IMP.init(process.env.NEXT_PUBLIC_PORTONE_IMP_CODE); const merchantUid = `token_${pkg.id}_${new Date().getTime()}`; const data = { pg: 'kakaopay', pay_method: 'card', merchant_uid: merchantUid, name: `사주보기 토큰 ${pkg.tokens}개 (${pkg.name})`, amount: pkg.price, buyer_email: user.email, }; IMP.request_pay(data, async (rsp: any) => { if (rsp.success) { try { // 1. 결제 기록 저장 (token_amount 컬럼 없을 수 있으므로 fallback) const paymentData: any = { user_id: user.id, imp_uid: rsp.imp_uid, merchant_uid: rsp.merchant_uid, amount: pkg.price, status: 'paid', }; const { error: paymentError } = await supabase .from('payments') .insert(paymentData); if (paymentError) { console.warn('결제 기록 저장 경고:', paymentError.message); // merchant_uid 중복이면 이미 처리된 결제이므로 무시 if (!paymentError.message.includes('duplicate') && !paymentError.message.includes('unique')) { console.error('결제 기록 저장 실패:', paymentError); } } // 2. 크레딧 업데이트 - RPC 함수 시도 (atomic increment) let newCredits = credits + pkg.tokens; const { data: rpcResult, error: rpcError } = await supabase .rpc('add_credits', { user_id_input: user.id, amount_input: pkg.tokens }); if (rpcError) { console.warn('RPC add_credits 실패, 직접 업데이트 시도:', rpcError.message); // RPC 함수가 아직 없는 경우 직접 업데이트 // 현재 최신 credits를 다시 조회 후 업데이트 const { data: freshProfile } = await supabase .from('profiles') .select('credits') .eq('id', user.id) .single(); const currentCredits = freshProfile?.credits || 0; newCredits = currentCredits + pkg.tokens; const { error: updateError } = await supabase .from('profiles') .update({ credits: newCredits }) .eq('id', user.id); if (updateError) { alert(`토큰 충전에 실패했습니다: ${updateError.message}\n고객센터에 문의해주세요.`); setPurchasing(false); return; } } else { // RPC 성공 시 반환값이 최신 크레딧 newCredits = rpcResult ?? newCredits; } setCredits(newCredits); alert(`결제가 완료되었습니다! 토큰 ${pkg.tokens}개가 충전되었어요.`); setPurchasing(false); onPurchaseComplete(); } catch (err) { console.error('충전 처리 중 오류:', err); alert('결제는 완료되었으나 토큰 충전 중 오류가 발생했습니다. 페이지를 새로고침해주세요.'); setPurchasing(false); } } else { alert(`결제에 실패하였습니다: ${rsp.error_msg}`); setPurchasing(false); } }); }; if (!isOpen) return null; return (
{/* Header */}

토큰 충전

보유 토큰: {credits}개

{/* Content */}
{loading ? (
) : (
{/* 토큰 설명 */}

토큰 1개로 사주 분석, 궁합 상세해석, 토정비결 상세해석 중 하나를 이용할 수 있어요.

{getPackages().map((pkg) => ( ))}
)}
{/* Footer */}

결제 후 즉시 토큰이 충전됩니다. 충전된 토큰은 환불이 불가합니다.

); }