'use client'; import { useState, useEffect, useMemo } from 'react'; import { fetchApi } from '@/lib/utils/client'; import useAuth from '@/hooks/useAuth'; import { useConfigContext } from '@/contexts/configProvider'; import { DanalCreateOrderRequest, PaymentMethodType } from '@/types/request/payment/charge'; import { DanalCreateOrderResponse } from '@/types/response/payment/charge'; import type { PaymentForm } from '@/types/config'; import CreditCardIcon from '@/public/icons/payment/credit-card.svg'; import KakaoIcon from '@/public/icons/payment/kakao.svg'; import NaverIcon from '@/public/icons/payment/naver.svg'; import BankIcon from '@/public/icons/payment/bank.svg'; import PhoneIcon from '@/public/icons/payment/phone.svg'; import { Landmark as VBankIcon } from 'lucide-react'; type PaymentMethodOption = { value: PaymentMethodType; label: string; danalMethod: string; danalKey: string; Icon: React.ComponentType>; enabledKey: keyof PaymentForm; }; const PAYMENT_METHODS: PaymentMethodOption[] = [ { value: 'Card', label: '신용카드', danalMethod: 'CARD', danalKey: 'card', Icon: CreditCardIcon, enabledKey: 'isCardEnabled' }, { value: 'KakaoPay', label: '카카오페이', danalMethod: 'KAKAOPAY', danalKey: 'kakaoPay', Icon: KakaoIcon, enabledKey: 'isKakaoPayEnabled' }, { value: 'NaverPay', label: '네이버페이', danalMethod: 'NAVERPAY', danalKey: 'naverPay', Icon: NaverIcon, enabledKey: 'isNaverPayEnabled' }, { value: 'Transfer', label: '계좌이체', danalMethod: 'TRANSFER', danalKey: 'transfer', Icon: BankIcon, enabledKey: 'isTransferEnabled' }, { value: 'Mobile', label: '휴대폰', danalMethod: 'MOBILE', danalKey: 'mobile', Icon: PhoneIcon, enabledKey: 'isMobileEnabled' }, { value: 'VirtualAccount', label: '가상계좌', danalMethod: 'VIRTUAL_ACCOUNT', danalKey: 'virtualAccount', Icon: VBankIcon, enabledKey: 'isVirtualAccountEnabled' }, ]; // 결제수단별 다날 SDK methods 설정 const METHOD_CONFIGS: Record = { card: {}, kakaoPay: {}, naverPay: {}, payco: {}, transfer: {}, mobile: { itemCode: '1270000000', itemType: '1' }, virtualAccount: { notiUrl: `${process.env.NEXT_PUBLIC_API_URL}/api/payment/noti/vaccount` } }; const QUICK_AMOUNTS = [10000, 50000, 100000, 1000000]; export default function ChargePage() { // pointInput = 사용자가 충전하려는 포인트 (VAT 제외). VAT 10%는 별도 가산. const [pointInput, setPointInput] = useState(0); const [paymentMethod, setPaymentMethod] = useState('Card'); const [loading, setLoading] = useState(false); const [isPopup, setIsPopup] = useState(false); const { member, loginCheck } = useAuth(); const config = useConfigContext(); const isDanalTestMode = config?.external?.danalPayMode === 'test'; // 결제 수단별 활성 여부 매핑 const methodEnabled = useMemo>(() => { const payment = config?.payment; const map = {} as Record; for (const m of PAYMENT_METHODS) { map[m.value] = payment ? Boolean(payment[m.enabledKey]) : false; } return map; }, [config?.payment]); useEffect(() => { setIsPopup(!!window.opener); }, []); // 초기 선택된 결제 수단이 비활성이면 첫 번째 활성 수단으로 자동 전환 useEffect(() => { if (!methodEnabled[paymentMethod]) { const firstEnabled = PAYMENT_METHODS.find(m => methodEnabled[m.value]); if (firstEnabled) { setPaymentMethod(firstEnabled.value); } } }, [methodEnabled, paymentMethod]); if (!member || !loginCheck()) { return null; } const handleAmountChange = (value: string) => { const num = parseInt(value.replace(/[^0-9]/g, ''), 10); setPointInput(isNaN(num) ? 0 : num); }; const addAmount = (add: number) => { setPointInput(prev => prev + add); }; // VAT 10% 별도 가산. 최종 결제 금액 = pointInput + vat. const vatAmount = Math.round(pointInput * 0.1); const totalAmount = pointInput + vatAmount; const handleSubmit = async () => { if (pointInput < 1000) { alert('최소 충전 금액은 1,000원입니다.'); return; } setLoading(true); try { const res = await fetchApi('/api/payment/order', { method: 'POST', body: { amount: pointInput, paymentMethod } as DanalCreateOrderRequest }); if (!res.data) { alert(res.message || '주문 생성에 실패했습니다.'); return setLoading(false); } const order = res.data; const selected = PAYMENT_METHODS.find(m => m.value === paymentMethod)!; const { loadDanalPaymentsSDK } = await import('@danalpay/javascript-sdk'); const danalPayments = await loadDanalPaymentsSDK({ clientKey: order.clientKey }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const paymentParams: any = { paymentsMethod: selected.danalMethod, methods: { [selected.danalKey]: METHOD_CONFIGS[selected.danalKey] }, orderName: `개미투자 ${pointInput.toLocaleString()}(P) 충전`, amount: order.amount, merchantId: order.merchantID, orderId: order.orderID, userId: member.id, userEmail: member.email, userName: member.name, successUrl: order.successUrl, failUrl: order.failUrl }; danalPayments.requestPayment(paymentParams); } catch (e) { console.error('결제 요청 실패:', e); alert('결제 요청에 실패했습니다.'); setLoading(false); } }; return (
{isDanalTestMode && (
현재 다날 충전은 테스트 환경입니다.
)}

포인트 충전

{isPopup && ( )}
0 ? pointInput.toLocaleString() : ''} onChange={e => handleAmountChange(e.target.value)} placeholder="0" inputMode="numeric" autoFocus />
{QUICK_AMOUNTS.map(v => ( ))}
{PAYMENT_METHODS.map(m => { const enabled = methodEnabled[m.value]; const isActive = enabled && paymentMethod === m.value; const cls = [ 'charge-page__method-btn', isActive ? 'charge-page__method-btn--active' : '', !enabled ? 'charge-page__method-btn--disabled' : '' ].filter(Boolean).join(' '); return ( ); })}
충전 (P) {pointInput.toLocaleString()} P
부가세 (10%) {vatAmount.toLocaleString()} 원
총 결제 금액 {totalAmount.toLocaleString()} 원
); }