page.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. 'use client';
  2. import '../style.scss';
  3. import { useEffect, useRef, useState } from 'react';
  4. import { useSearchParams } from 'next/navigation';
  5. import { fetchApi, getDateTime } from '@/lib/utils/client';
  6. import { DanalConfirmRequest } from '@/types/request/payment/charge';
  7. import { DanalConfirmResponse } from '@/types/response/payment/charge';
  8. const METHOD_LABELS: Record<string, string> = {
  9. CARD: '신용카드',
  10. KAKAOPAY: '카카오페이',
  11. NAVERPAY: '네이버페이',
  12. TRANSFER: '계좌이체',
  13. MOBILE: '휴대폰',
  14. VIRTUAL_ACCOUNT: '가상계좌'
  15. };
  16. export default function ChargeSuccessPage()
  17. {
  18. const searchParams = useSearchParams();
  19. const [status, setStatus] = useState<'loading'|'success'|'error'>('loading');
  20. const [message, setMessage] = useState('결제 승인 처리 중...');
  21. const [pointAmount, setPointAmount] = useState<number>(0);
  22. const [paidAt, setPaidAt] = useState<string|null>(null);
  23. const calledRef = useRef(false);
  24. useEffect(() => {
  25. if (calledRef.current) {
  26. return;
  27. }
  28. calledRef.current = true;
  29. confirmPayment();
  30. }, []);
  31. const confirmPayment = async () => {
  32. const orderID = searchParams.get('orderId');
  33. const transactionID = searchParams.get('transactionId');
  34. const method = searchParams.get('method');
  35. if (!orderID || !transactionID || !method) {
  36. setStatus('error');
  37. setMessage('결제 정보가 올바르지 않습니다.');
  38. return;
  39. }
  40. try {
  41. const res = await fetchApi<DanalConfirmResponse>('/api/payment/confirm', {
  42. method: 'POST',
  43. body: { orderID, transactionID, method } as DanalConfirmRequest
  44. });
  45. if (res.success && res.data) {
  46. setPointAmount(res.data.pointAmount);
  47. setPaidAt(res.data.paidAt);
  48. setStatus('success');
  49. setMessage('포인트 충전 완료');
  50. if (window.opener) {
  51. window.opener.postMessage({ type: 'CHARGE_COMPLETE' }, '*');
  52. }
  53. } else {
  54. setStatus('error');
  55. setMessage(res.message || '결제 승인에 실패했습니다.');
  56. }
  57. } catch {
  58. setStatus('error');
  59. setMessage('결제 승인 중 오류가 발생했습니다.');
  60. }
  61. };
  62. const method = searchParams.get('method') ?? '';
  63. const methodLabel = METHOD_LABELS[method] ?? method;
  64. const handleClose = () => {
  65. if (window.opener) {
  66. window.close();
  67. } else {
  68. window.location.href = '/';
  69. }
  70. };
  71. return (
  72. <div className="charge-page">
  73. <div className="charge-page__card">
  74. <div className="charge-page__result">
  75. {status === 'loading' && <div className="charge-page__spinner" />}
  76. {status === 'success' && <div className="charge-page__result-icon charge-page__result-icon--success">✓</div>}
  77. {status === 'error' && <div className="charge-page__result-icon charge-page__result-icon--error">✕</div>}
  78. <p className="charge-page__result-message">{message}</p>
  79. {status === 'success' && (
  80. <div className="charge-page__receipt">
  81. <div className="charge-page__receipt-row">
  82. <span>결제 금액</span>
  83. <span>{pointAmount.toLocaleString()} 원</span>
  84. </div>
  85. <div className="charge-page__receipt-row">
  86. <span>결제 수단</span>
  87. <span>{methodLabel}</span>
  88. </div>
  89. <div className="charge-page__receipt-row">
  90. <span>결제 일시</span>
  91. <span>{getDateTime(paidAt)}</span>
  92. </div>
  93. </div>
  94. )}
  95. {status !== 'loading' && (
  96. <button type="button" className="charge-page__submit" onClick={handleClose}>
  97. 닫기
  98. </button>
  99. )}
  100. </div>
  101. </div>
  102. </div>
  103. );
  104. }