Files
nice-app-web/src/pages/additional-service/key-in-payment/requeset-page.tsx
2025-10-21 18:16:27 +09:00

401 lines
20 KiB
TypeScript

import { ChangeEvent, useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useLocation } from 'react-router';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { HeaderType } from '@/entities/common/model/types';
import { useExtensionKeyinApplyMutation } from '@/entities/additional-service/api/use-extension-keyin-apply-mutation';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
import { overlay } from 'overlay-kit';
import { Dialog } from '@/shared/ui/dialogs/dialog';
import { useStore } from '@/shared/model/store';
export const KeyInPaymentRequestPage = () => {
const { navigate } = useNavigate();
const location = useLocation();
const userMid = useStore.getState().UserStore.mid;
const [mid, setMid] = useState<string>(userMid || '');
const [goodsName, setGoodsName] = useState<string>('');
const [amount, setAmount] = useState<number>(0);
const [buyerName, setBuyerName] = useState<string>('');
const [email, setEmail] = useState<string>('');
const [phoneNumber, setPhoneNumber] = useState<string>('');
const [cardNo1, setCardNo1] = useState<string>('');
const [cardNo2, setCardNo2] = useState<string>('');
const [cardNo3, setCardNo3] = useState<string>('');
const [cardNo4, setCardNo4] = useState<string>('');
const [cardExpirationMonth, setCardExpirationMonth] = useState<string>('');
const [cardExpirationYear, setCardExpirationYear] = useState<string>('');
const [instmntMonth, setInstmntMonth] = useState<string>('00');
const [moid, setMoid] = useState<string>('');
const { mutateAsync: keyInApply } = useExtensionKeyinApplyMutation();
useSetHeaderTitle('KEY-IN 결제');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.additionalService.keyInPayment.list);
});
const resetForm = () => {
setGoodsName('');
setAmount(0);
setBuyerName('');
setEmail('');
setPhoneNumber('');
setCardNo1('');
setCardNo2('');
setCardNo3('');
setCardNo4('');
setCardExpirationMonth('');
setCardExpirationYear('');
setInstmntMonth('00');
setMoid('');
};
const callKeyInPaymentRequest = () => {
const cardNo = `${cardNo1}${cardNo2}${cardNo3}${cardNo4}`;
const cardExpirationDate = `${cardExpirationMonth}${cardExpirationYear}`;
let keyInApplyParams = {
mid: mid,
goodsName: goodsName,
amount: amount,
buyerName: buyerName,
email: email,
phoneNumber: phoneNumber,
cardNo: cardNo,
cardExpirationDate: cardExpirationDate,
instmntMonth: instmntMonth,
moid: moid,
};
keyInApply(keyInApplyParams).then((rs) => {
console.log('결제 응답:', rs);
if (rs.status) {
// 성공: 화면 유지 & 입력 내용 초기화
showSuccessDialog();
resetForm();
} else {
// 실패: 화면 유지 & 입력 내용 유지
showErrorDialog('결제에 실패했습니다. 입력 내용을 확인해주세요.');
}
}).catch((error) => {
console.error('결제 실패:', error);
showErrorDialog(error?.message || '결제 요청 중 오류가 발생했습니다');
});
};
const showSuccessDialog = () => {
overlay.open(({ isOpen, close, unmount }) => {
return (
<Dialog
afterLeave={unmount}
open={isOpen}
onClose={close}
onConfirmClick={close}
message="결제가 성공적으로 처리되었습니다"
buttonLabel={['확인']}
/>
);
});
};
const showErrorDialog = (errorMessage: string) => {
overlay.open(({ isOpen, close, unmount }) => {
return (
<Dialog
afterLeave={unmount}
open={isOpen}
onClose={close}
onConfirmClick={close}
message={errorMessage}
buttonLabel={['확인']}
/>
);
});
};
const isValidPhoneNumber = (phone: string) => {
const phoneRegex = /^01[0|1|6|7|8|9][0-9]{7,8}$/;
return phoneRegex.test(phone);
};
const isValidEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const isValidCardNumber = () => {
return cardNo1.length === 4 && cardNo2.length === 4 &&
cardNo3.length === 4 && cardNo4.length === 4;
};
const isValidCardExpiration = () => {
if (cardExpirationMonth.length !== 2 || cardExpirationYear.length !== 2) {
return false;
}
const month = parseInt(cardExpirationMonth);
return month >= 1 && month <= 12;
};
const isFormValid = () => {
return (
mid.trim() !== '' &&
goodsName.trim() !== '' &&
amount > 0 &&
buyerName.trim() !== '' &&
isValidEmail(email) &&
isValidPhoneNumber(phoneNumber) &&
isValidCardNumber() &&
isValidCardExpiration()
);
};
const onClickToRequest = () => {
callKeyInPaymentRequest();
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="billing-form gap-16">
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value={mid}
readOnly={true}
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value={goodsName}
onChange={(e: ChangeEvent<HTMLInputElement>) => setGoodsName(e.target.value)}
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value={amount === 0 ? '' : amount.toString()}
placeholder="금액을 입력하세요"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, ''); // 숫자만 남김
setAmount(onlyNumbers === '' ? 0 : parseInt(onlyNumbers, 10));
}}
inputMode="numeric" // 모바일 키보드 숫자 전용
pattern="[0-9]*" // 브라우저 기본 숫자만 유효하도록
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value={buyerName}
onChange={(e: ChangeEvent<HTMLInputElement>) => setBuyerName(e.target.value)}
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="email"
value={email}
placeholder='test@nicepay.co.kr'
onChange={(e: ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)}
className={email && !isValidEmail(email) ? 'error' : ''}
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="tel"
value={phoneNumber}
placeholder='01012345678'
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
setPhoneNumber(onlyNumbers);
}}
className={phoneNumber && !isValidPhoneNumber(phoneNumber) ? 'error' : ''}
inputMode="numeric"
pattern="[0-9]*"
maxLength={11}
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value={cardNo1}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumbers.length <= 4) setCardNo1(onlyNumbers);
}}
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="1234"
/>
</div>
<div className="billing-field">
<input
type="text"
value={cardNo2}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumbers.length <= 4) setCardNo2(onlyNumbers);
}}
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="5678"
/>
</div>
<div className="billing-field">
<input
type="text"
value={cardNo3}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumbers.length <= 4) setCardNo3(onlyNumbers);
}}
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="9012"
/>
</div>
<div className="billing-field">
<input
type="text"
value={cardNo4}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumbers.length <= 4) setCardNo4(onlyNumbers);
}}
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="3456"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label">(/)<span>*</span></div>
<div className="billing-field" style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<input
type="text"
value={cardExpirationMonth}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumbers.length <= 2) setCardExpirationMonth(onlyNumbers);
}}
inputMode="numeric"
pattern="[0-9]*"
maxLength={2}
placeholder='MM'
style={{ flex: 1 }}
/>
<span>/</span>
<input
type="text"
value={cardExpirationYear}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const onlyNumbers = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumbers.length <= 2) setCardExpirationYear(onlyNumbers);
}}
inputMode="numeric"
pattern="[0-9]*"
maxLength={2}
placeholder='YY'
style={{ flex: 1 }}
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<select
disabled={amount < 50000}
value={instmntMonth}
onChange={(e: ChangeEvent<HTMLSelectElement>) => setInstmntMonth(e.target.value)}
>
<option value="00"> ()</option>
{amount >= 50000 && (
<>
<option value="02">2</option>
<option value="03">3</option>
<option value="04">4</option>
<option value="05">5</option>
<option value="06">6</option>
<option value="07">7</option>
<option value="08">8</option>
<option value="09">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</>
)}
</select>
</div>
</div>
<div className="billing-row">
<div className="billing-label"></div>
<div className="billing-field">
<input
type="text"
value={moid}
onChange={(e: ChangeEvent<HTMLInputElement>) => setMoid(e.target.value)}
/>
</div>
</div>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={() => onClickToRequest()}
disabled={!isFormValid()}
> </button>
</div>
</div>
</div>
</main>
</>
)
}