generated from pricelees/issue-pr-template
<!-- 제목 양식 --> <!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) --> ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #44 ## ✨ 작업 내용 <!-- 어떤 작업을 했는지 알려주세요! --> - 매장 기능 도입 및 기존 기능에 적용 - 관리자 타입(본사, 매장, 전체) 분리 및 API별 적용 ## 🧪 테스트 <!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! --> - 신규 기능 및 매장 기능 도입으로 수정된 기존 API 모두 통합 테스트 완료 ## 📚 참고 자료 및 기타 <!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! --> - 아직 미결제 예약 스케쥴링 작업 등 추가적인 작업이 필요하긴 하지만, 이 작업들은 배포 후 추가로 진행할 예정 - 다음 작업은 배포 + 초기 데이터 삽입 Reviewed-on: #45 Co-authored-by: pricelees <priceelees@gmail.com> Co-committed-by: pricelees <priceelees@gmail.com>
139 lines
5.2 KiB
TypeScript
139 lines
5.2 KiB
TypeScript
import { isLoginRequiredError } from '@_api/apiClient';
|
|
import { confirmPayment } from '@_api/payment/paymentAPI';
|
|
import { type PaymentConfirmRequest, PaymentType } from '@_api/payment/PaymentTypes';
|
|
import { confirmReservation } from '@_api/reservation/reservationAPI';
|
|
import '@_css/reservation-v2-1.css';
|
|
import React, { useEffect, useRef } from 'react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
import { formatDate } from 'src/util/DateTimeFormatter';
|
|
|
|
declare global {
|
|
interface Window {
|
|
PaymentWidget: any;
|
|
}
|
|
}
|
|
|
|
const ReservationStep2Page: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const paymentWidgetRef = useRef<any>(null);
|
|
const paymentMethodsRef = useRef<any>(null);
|
|
|
|
const { reservationId, storeName, themeName, themePrice, totalPrice, date, time, participantCount } = location.state || {};
|
|
|
|
const handleError = (err: any) => {
|
|
if (isLoginRequiredError(err)) {
|
|
alert('로그인이 필요해요.');
|
|
navigate('/login', { state: { from: location } });
|
|
} else {
|
|
const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.';
|
|
alert(message);
|
|
console.error(err);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!reservationId) {
|
|
alert('잘못된 접근입니다.');
|
|
navigate('/reservation');
|
|
return;
|
|
}
|
|
|
|
const script = document.createElement('script');
|
|
script.src = 'https://js.tosspayments.com/v1/payment-widget';
|
|
script.async = true;
|
|
document.head.appendChild(script);
|
|
|
|
script.onload = () => {
|
|
const widgetClientKey = "test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm";
|
|
const paymentWidget = window.PaymentWidget(widgetClientKey, window.PaymentWidget.ANONYMOUS);
|
|
paymentWidgetRef.current = paymentWidget;
|
|
|
|
const paymentMethods = paymentWidget.renderPaymentMethods(
|
|
"#payment-method",
|
|
{ value: totalPrice, currency: "KRW" },
|
|
{ variantKey: "DEFAULT" }
|
|
);
|
|
paymentMethodsRef.current = paymentMethods;
|
|
};
|
|
}, [reservationId, totalPrice, navigate]);
|
|
|
|
const handlePayment = () => {
|
|
if (!paymentWidgetRef.current || !reservationId) {
|
|
alert('결제 위젯이 로드되지 않았거나 예약 정보가 없습니다.');
|
|
return;
|
|
}
|
|
|
|
const generateRandomString = () =>
|
|
crypto.randomUUID().replace(/-/g, '');
|
|
|
|
|
|
paymentWidgetRef.current.requestPayment({
|
|
orderId: generateRandomString(),
|
|
orderName: `${themeName} 예약 결제`,
|
|
amount: totalPrice,
|
|
}).then((data: any) => {
|
|
const paymentData: PaymentConfirmRequest = {
|
|
paymentKey: data.paymentKey,
|
|
orderId: data.orderId,
|
|
amount: totalPrice,
|
|
paymentType: data.paymentType || PaymentType.NORMAL,
|
|
};
|
|
|
|
confirmPayment(reservationId, paymentData)
|
|
.then(() => {
|
|
return confirmReservation(reservationId);
|
|
})
|
|
.then(() => {
|
|
alert('결제가 완료되었어요!');
|
|
navigate('/reservation/success', {
|
|
state: {
|
|
storeName: storeName,
|
|
themeName: themeName,
|
|
date: date,
|
|
time: time,
|
|
participantCount: participantCount,
|
|
totalPrice: totalPrice,
|
|
}
|
|
});
|
|
})
|
|
.catch(handleError);
|
|
}).catch((error: any) => {
|
|
console.error("Payment request error:", error);
|
|
alert("결제 요청 중 오류가 발생했습니다.");
|
|
});
|
|
};
|
|
|
|
if (!reservationId) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="reservation-v21-container">
|
|
<h2 className="page-title">결제하기</h2>
|
|
<div className="step-section">
|
|
<h3>결제 정보 확인</h3>
|
|
<p><strong>날짜:</strong> {formatDate(date)}</p>
|
|
<p><strong>시간:</strong> {time}</p>
|
|
<p><strong>테마:</strong> {themeName}</p>
|
|
<p><strong>매장:</strong> {storeName}</p>
|
|
<p><strong>인원:</strong> {participantCount}명</p>
|
|
<p><strong>1인당 금액:</strong> {themePrice.toLocaleString()}원</p>
|
|
<p><strong>총 금액:</strong> {totalPrice.toLocaleString()}원</p>
|
|
</div>
|
|
<div className="step-section">
|
|
<h3>결제 수단</h3>
|
|
<div id="payment-method" className="w-100"></div>
|
|
<div id="agreement" className="w-100"></div>
|
|
</div>
|
|
<div className="next-step-button-container">
|
|
<button onClick={handlePayment} className="next-step-button">
|
|
{totalPrice.toLocaleString()}원 결제하기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ReservationStep2Page;
|