'use client'; import './style.scss'; import { useState, useEffect, useRef, useCallback } from 'react'; import { loadTossPayments } from '@tosspayments/tosspayments-sdk'; import type { TossPaymentsWidgets } from '@tosspayments/tosspayments-sdk'; import { fetchApi } from '@/lib/utils/client'; import useAuth from '@/hooks/useAuth'; import { useConfigContext } from '@/contexts/configProvider'; import { TossCreateOrderRequest } from '@/types/request/payment/charge'; import { TossCreateOrderResponse } from '@/types/response/payment/charge'; const QUICK_AMOUNTS = [10000, 50000, 100000, 1000000]; export default function ChargePage() { // cashInput = 사용자가 충전하려는 캐시 (VAT 제외). VAT 10%는 서버에서 별도 가산. const [cashInput, setCashInput] = useState(0); const [order, setOrder] = useState(null); const [loading, setLoading] = useState(false); const [paying, setPaying] = useState(false); const [isPopup, setIsPopup] = useState(false); const [error, setError] = useState(null); const { member, loginCheck } = useAuth(); const config = useConfigContext(); const isTossTestMode = config?.toss?.tossPayMode === 'test'; const widgetsRef = useRef(null); useEffect(() => { setIsPopup(!!window.opener); }, []); // 주문 생성 후 토스 위젯 초기화 — clientKey/customerKey 로 widgets 생성, 금액 설정, UI 렌더링 const initWidgets = useCallback(async (created: TossCreateOrderResponse) => { const tossPayments = await loadTossPayments(created.clientKey); const widgets = tossPayments.widgets({ customerKey: created.customerKey }); widgetsRef.current = widgets; await widgets.setAmount({ value: created.amount, currency: 'KRW' }); await Promise.all([ widgets.renderPaymentMethods({ selector: '#toss-payment-methods' }), widgets.renderAgreement({ selector: '#toss-agreement' }) ]); }, []); if (!member || !loginCheck()) { return null; } const handleAmountChange = (value: string) => { const num = parseInt(value.replace(/[^0-9]/g, ''), 10); setCashInput(isNaN(num) ? 0 : num); }; const addAmount = (add: number) => { setCashInput(prev => prev + add); }; // VAT 10% 별도 가산 (표시용). 최종 결제 금액은 서버 주문(order.amount)이 확정한다. const vatAmount = Math.round(cashInput * 0.1); const totalAmount = cashInput + vatAmount; // 1단계: 서버에 주문 생성 → 토스 위젯 초기화 const handleCreateOrder = async () => { if (cashInput < 1000) { alert('최소 충전 금액은 1,000원입니다.'); return; } setLoading(true); setError(null); try { const res = await fetchApi('/api/payments/orders', { method: 'POST', body: { amount: cashInput } as TossCreateOrderRequest }); if (!res.success || !res.data) { setError(res.message || '주문 생성에 실패했습니다.'); setLoading(false); return; } const created = res.data; setOrder(created); await initWidgets(created); } catch (e) { console.error('주문 생성/위젯 초기화 실패:', e); setError('결제 준비 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); } finally { setLoading(false); } }; // 2단계: 결제 요청 (redirect 방식) — successUrl/failUrl 로 이동 const handleRequestPayment = async () => { const widgets = widgetsRef.current; if (!widgets || !order) { return; } setPaying(true); setError(null); try { const origin = window.location.origin; await widgets.requestPayment({ orderId: order.orderID, orderName: order.orderName, successUrl: `${origin}/charge/success`, failUrl: `${origin}/charge/fail`, customerEmail: member.email ?? undefined, customerName: member.name ?? undefined }); } catch (e) { // 사용자가 결제창을 닫거나 요청이 실패한 경우 — 페이지에 머물러 재시도 가능 console.error('결제 요청 실패:', e); const message = e instanceof Error ? e.message : '결제 요청에 실패했습니다.'; setError(message); setPaying(false); } }; // 금액 재선택 — 위젯을 닫고 1단계로 복귀 (금액이 바뀌면 새 주문이 필요) const handleReset = () => { widgetsRef.current = null; setOrder(null); setError(null); }; return (
{isTossTestMode && (
현재 토스 충전은 테스트 환경입니다.
)}

캐시 충전

{isPopup && ( )}
{!order && ( <> 0 ? cashInput.toLocaleString() : ''} onChange={e => handleAmountChange(e.target.value)} placeholder="0" inputMode="numeric" autoFocus />
{QUICK_AMOUNTS.map(v => ( ))}
충전 캐시 {cashInput.toLocaleString()} 원
부가세 (10%) {vatAmount.toLocaleString()} 원
총 결제 금액 {totalAmount.toLocaleString()} 원
{error &&

{error}

} )} {order && ( <>
충전 캐시 {order.pointAmount.toLocaleString()} 원
부가세 (10%) {order.vatAmount.toLocaleString()} 원
총 결제 금액 {order.amount.toLocaleString()} 원
{error &&

{error}

} )}
); }