page.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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 { TossConfirmRequest } from '@/types/request/payment/charge';
  7. import { TossConfirmResponse } from '@/types/response/payment/charge';
  8. type ViewStatus = 'loading'|'success'|'waiting'|'error';
  9. export default function ChargeSuccessPage()
  10. {
  11. const searchParams = useSearchParams();
  12. const [status, setStatus] = useState<ViewStatus>('loading');
  13. const [message, setMessage] = useState('결제 승인 처리 중...');
  14. const [pointAmount, setPointAmount] = useState<number>(0);
  15. const [paidAt, setPaidAt] = useState<string|null>(null);
  16. const calledRef = useRef(false);
  17. useEffect(() => {
  18. if (calledRef.current) {
  19. return;
  20. }
  21. calledRef.current = true;
  22. confirmPayment();
  23. }, []);
  24. const confirmPayment = async () => {
  25. const paymentKey = searchParams.get('paymentKey');
  26. const orderID = searchParams.get('orderId');
  27. const amountStr = searchParams.get('amount');
  28. const amount = amountStr ? parseInt(amountStr, 10) : NaN;
  29. if (!paymentKey || !orderID || isNaN(amount)) {
  30. setStatus('error');
  31. setMessage('결제 정보가 올바르지 않습니다.');
  32. return;
  33. }
  34. try {
  35. const res = await fetchApi<TossConfirmResponse>('/api/payments/confirm', {
  36. method: 'POST',
  37. body: { paymentKey, orderID, amount } as TossConfirmRequest,
  38. silent: true
  39. });
  40. const data = res.data;
  41. if (res.success && data?.success) {
  42. // 가상계좌 입금 대기 — 이 시점엔 적립 전. 입금 완료 후 지갑에 반영된다.
  43. if (data.status === 'WaitingDeposit') {
  44. setStatus('waiting');
  45. setMessage(data.message || '가상계좌가 발급되었습니다. 입금 확인 후 충전됩니다.');
  46. return;
  47. }
  48. setPointAmount(data.pointAmount);
  49. setPaidAt(data.paidAt);
  50. setStatus('success');
  51. setMessage(data.message || '캐시 충전 완료');
  52. if (window.opener) {
  53. window.opener.postMessage({ type: 'CHARGE_COMPLETE' }, '*');
  54. }
  55. } else {
  56. setStatus('error');
  57. setMessage(data?.message || res.message || '결제 승인에 실패했습니다.');
  58. }
  59. } catch {
  60. setStatus('error');
  61. setMessage('결제 승인 중 오류가 발생했습니다.');
  62. }
  63. };
  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 === 'waiting' && <div className="charge-page__result-icon charge-page__result-icon--waiting">…</div>}
  78. {status === 'error' && <div className="charge-page__result-icon charge-page__result-icon--error">✕</div>}
  79. <p className="charge-page__result-message">{message}</p>
  80. {status === 'success' && (
  81. <div className="charge-page__receipt">
  82. <div className="charge-page__receipt-row">
  83. <span>충전 캐시</span>
  84. <span>{pointAmount.toLocaleString()} 원</span>
  85. </div>
  86. <div className="charge-page__receipt-row">
  87. <span>결제 일시</span>
  88. <span>{getDateTime(paidAt)}</span>
  89. </div>
  90. </div>
  91. )}
  92. {status !== 'loading' && (
  93. <button type="button" className="charge-page__submit" onClick={handleClose}>
  94. 닫기
  95. </button>
  96. )}
  97. </div>
  98. </div>
  99. </div>
  100. );
  101. }