빌키 에스크로 추가 api 연ㄱㅕㄹ

This commit is contained in:
focp212@naver.com
2025-09-11 18:04:29 +09:00
parent 48a3bd1ed4
commit 84288188dc
24 changed files with 542 additions and 180 deletions

View File

@@ -0,0 +1,11 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="6" fill="#F5F5F5"/>
<g clip-path="url(#clip0_2269_6765)">
<rect x="9" y="17" width="18" height="2" rx="1" fill="#999999"/>
</g>
<defs>
<clipPath id="clip0_2269_6765">
<rect x="8" y="8" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@@ -0,0 +1,11 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2269_6819)">
<rect x="1" y="9" width="18" height="2" rx="1" fill="#999999"/>
<rect x="11" y="1" width="18" height="2" rx="1" transform="rotate(90 11 1)" fill="#999999"/>
</g>
<defs>
<clipPath id="clip0_2269_6819">
<rect width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 414 B

View File

@@ -0,0 +1,13 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="80" height="80" rx="40" fill="#F4F8FF"/>
<path d="M62.9999 26V21.8988C62.9999 19.7455 61.2543 18 59.1011 18H54.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.9999 26V21.8988C16.9999 19.7455 18.7454 18 20.8987 18H24.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.9999 54V58.1012C62.9999 60.2545 61.2543 62 59.1011 62H54.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.9999 54V58.1012C16.9999 60.2545 18.7454 62 20.8987 62H24.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="22.9999" y="23" width="34" height="34" rx="4.36364" fill="#86CBFF"/>
<path d="M35.1083 45.9883C36.8146 47.6106 40.7305 49.2609 45.1779 45.9883" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="31.4999" y="33.5" width="1" height="2.5" rx="0.5" stroke="white"/>
<rect x="48.4999" y="33.5" width="1" height="3" rx="0.5" stroke="white"/>
<path d="M40.2273 34.5762V40.7018C40.2273 41.6249 39.8098 42.3507 38.3182 42.5164" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="21.4544" y1="40.0009" x2="58.5453" y2="40.0009" stroke="#F05D92" stroke-width="2.18182" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,29 @@
import axios from 'axios';
import { API_URL } from '@/shared/api/urls';
import { resultify } from '@/shared/lib/resultify';
import { CBDCAxiosError } from '@/shared/@types/error';
import {
BillingChargeParams,
BillingChargeResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const billingCharge = (params: BillingChargeParams) => {
return resultify(
axios.post<BillingChargeResponse>(API_URL.billingDetail(), params),
);
};
export const useBillingChargeMutation = (options?: UseMutationOptions<BillingChargeResponse, CBDCAxiosError, BillingChargeParams>) => {
const mutation = useMutation<BillingChargeResponse, CBDCAxiosError, BillingChargeParams>({
...options,
mutationFn: (params: BillingChargeParams) => billingCharge(params),
});
return {
...mutation,
};
};

View File

@@ -0,0 +1,29 @@
import axios from 'axios';
import { API_URL } from '@/shared/api/urls';
import { resultify } from '@/shared/lib/resultify';
import { CBDCAxiosError } from '@/shared/@types/error';
import {
EscrowMailResendParams,
EscrowMailResendResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const escrowMailResend = (params: EscrowMailResendParams) => {
return resultify(
axios.post<EscrowMailResendResponse>(API_URL.escrowMailResend(), params),
);
};
export const useEscrowMailResendMutation = (options?: UseMutationOptions<EscrowMailResendResponse, CBDCAxiosError, EscrowMailResendParams>) => {
const mutation = useMutation<EscrowMailResendResponse, CBDCAxiosError, EscrowMailResendParams>({
...options,
mutationFn: (params: EscrowMailResendParams) => escrowMailResend(params),
});
return {
...mutation,
};
};

View File

@@ -451,3 +451,22 @@ export interface CashReceiptManualIssueResponse {
issueDateTime: string,
issueResult: SuccessResult
};
export interface BillingChargeParams {
billKey: string;
productName: string;
productAmount: number | string;
orderNumber: string;
buyerName: string;
paymentRequestDate: string;
installmentMonth: string;
};
export interface BillingChargeResponse {
};
export interface EscrowMailResendParams {
orderNumber?: string;
tid?: string;
};
export interface EscrowMailResendResponse {
};

View File

@@ -25,7 +25,7 @@ export const BillingList = ({
};
const onClickToNavigate = () => {
navigate(PATHS.transaction.billing.paymentRequest);
navigate(PATHS.transaction.billing.charge);
};
return (

View File

@@ -1,17 +1,17 @@
import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion';
export interface BottomSheetCashReceitPurposeUpdateProps {
export interface CashReceitPurposeUpdateBottomSheetProps {
setBottomSheetOn: (bottomSheetOn: boolean) => void;
bottomSheetOn: boolean;
callPurposeUpdate: () => void;
};
export const BottomSheetCashReceitPurposeUpdate = ({
export const CashReceitPurposeUpdateBottomSheet = ({
setBottomSheetOn,
bottomSheetOn,
callPurposeUpdate
}: BottomSheetCashReceitPurposeUpdateProps) => {
}: CashReceitPurposeUpdateBottomSheetProps) => {
const onClickToClose = () => {
setBottomSheetOn(false);

View File

@@ -1,11 +1,42 @@
import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion';
export const BottomSheetEmail = () => {
export interface EscrowMailResendBottomSheetProps {
setBottomSheetOn: (bottomSheetOn: boolean) => void;
bottomSheetOn: boolean;
callMailResend: () => void;
};
export const EscrowMailResendBottomSheet = ({
setBottomSheetOn,
bottomSheetOn,
callMailResend
}: EscrowMailResendBottomSheetProps) => {
const onClickToClose = () => {
setBottomSheetOn(false);
};
const onClickToMailResend = () => {
callMailResend();
};
const variants = {
hidden: { y: '100%' },
visible: { y: '0%' },
};
return (
<>
<div className="bg-dim"></div>
<div className="bottomsheet">
{ (bottomSheetOn) &&
<div className="bg-dim"></div>
}
<motion.div
className="bottomsheet"
initial="hidden"
animate={ (bottomSheetOn)? 'visible': 'hidden' }
variants={ variants }
transition={{ duration: 0.5 }}
>
<div className="bottomsheet-header">
<div className="bottomsheet-title">
<h2> </h2>
@@ -16,6 +47,7 @@ export const BottomSheetEmail = () => {
<img
src={ IMAGE_ROOT + '/ico_close.svg' }
alt="닫기"
onClick={ () => onClickToClose() }
/>
</button>
</div>
@@ -27,7 +59,7 @@ export const BottomSheetEmail = () => {
<div className="mail-icon">
<div className="mail-icon-bg"></div>
<img
src="../images/ico_email.svg"
src={ IMAGE_ROOT +'/ico_email.svg' }
alt="메일"
/>
</div>
@@ -54,10 +86,10 @@ export const BottomSheetEmail = () => {
<button
className="btn-50 btn-blue flex-1"
type="button"
disabled
onClick={ () => onClickToMailResend() }
></button>
</div>
</div>
</motion.div>
</>
);
};

View File

@@ -1,7 +1,9 @@
import { ChangeEvent, useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types';
import { useBillingChargeMutation } from '@/entities/transaction/api/use-billing-charge-mutation';
import {
useSetOnBack,
useSetHeaderTitle,
@@ -9,9 +11,17 @@ import {
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const BillingPaymentRequestPage = () => {
export const BillingChargePage = () => {
const { navigate } = useNavigate();
const [billKey, setBillKey] = useState<string>('BIKYvattest01m');
const [productName, setProductName] = useState<string>('테스트상품123');
const [productAmount, setProductAmount] = useState<number | string>(1000000);
const [orderNumber, setOrderNumber] = useState<string>('P146733723');
const [buyerName, setBuyerName] = useState<string>('김테스트');
const [paymentRequestDate, setPaymentRequestDate] = useState<string>('2025-06-08');
const [installmentMonth, setInstallmentMonth] = useState<string>('00');
useSetHeaderTitle('빌링 결제 신청');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
@@ -19,6 +29,52 @@ export const BillingPaymentRequestPage = () => {
});
useSetFooterMode(false);
const { mutateAsync: billingCharge } = useBillingChargeMutation();
const onClickToBillingCharge = () => {
let params = {
billKey: billKey,
productName: productName,
productAmount: productAmount,
orderNumber: orderNumber,
buyerName: buyerName,
paymentRequestDate: paymentRequestDate,
installmentMonth: installmentMonth
};
billingCharge(params).then((rs) => {
console.log(rs);
alert('성공')
navigate(PATHS.transaction.billing.list);
});
};
const makeInstallmentMonthSelect = () => {
let rs = [];
rs.push(
<option
key={ `key-installment` }
value=''
></option>
);
rs.push(
<option
key={ `key-installment-0` }
value='00'
></option>
);
for(let i=2;i<=24;i++){
let val = (i < 10)? '0'+i: ''+i;
rs.push(
<option
key={ `key-installment-${i}`}
value={ val }
>{i}</option>
);
};
return rs;
};
return (
<>
<main>
@@ -26,15 +82,14 @@ export const BillingPaymentRequestPage = () => {
<div className="tab-pane sub active">
<div className="option-list">
<div className="billing-title"> </div>
<div className="billing-form">
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="BIKYvattest01m"
readOnly={ true }
value={ billKey }
/>
</div>
</div>
@@ -43,7 +98,8 @@ export const BillingPaymentRequestPage = () => {
<div className="billing-field">
<input
type="text"
value="테스트상품123"
value={ productName }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setProductName(e.target.value) }
/>
</div>
</div>
@@ -52,7 +108,8 @@ export const BillingPaymentRequestPage = () => {
<div className="billing-field">
<input
type="text"
value="1,000,000"
value={ productAmount }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setProductAmount(e.target.value) }
/>
</div>
</div>
@@ -61,7 +118,8 @@ export const BillingPaymentRequestPage = () => {
<div className="billing-field">
<input
type="text"
value="P146733723"
value={ orderNumber }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setOrderNumber(e.target.value) }
/>
</div>
</div>
@@ -70,7 +128,8 @@ export const BillingPaymentRequestPage = () => {
<div className="billing-field">
<input
type="text"
value="김테스트"
value={ buyerName }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setBuyerName(e.target.value) }
/>
</div>
</div>
@@ -81,8 +140,8 @@ export const BillingPaymentRequestPage = () => {
<div className="input-wrapper date wid-100">
<input
type="text"
value="2025/03/21"
placeholder=""
value={ paymentRequestDate }
/>
<button
className="date-btn"
@@ -101,19 +160,21 @@ export const BillingPaymentRequestPage = () => {
<div className="billing-row">
<div className="billing-label"> </div>
<div className="billing-field">
<select>
<option value=""></option>
<option value="0"></option>
<option value="2">2</option>
<option value="3">3</option>
<option value="6">6</option>
<select
value={ installmentMonth }
onChange={ (e: ChangeEvent<HTMLSelectElement>) => setBuyerName(e.target.value) }
>
{ makeInstallmentMonthSelect() }
</select>
</div>
</div>
</div>
</div>
<div className="apply-row">
<button className="btn-50 btn-blue flex-1"> </button>
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToBillingCharge() }
> </button>
</div>
</div>
</div>

View File

@@ -21,7 +21,7 @@ import {
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
import { BottomSheetCashReceitPurposeUpdate } from '@/entities/transaction/ui/bottom-sheet-cash-receit-purpose-update';
import { CashReceitPurposeUpdateBottomSheet } from '@/entities/transaction/ui/cash-receit-purpose-update-bottom-sheet';
import { useCashReceiptPurposeUpdateMutation } from '@/entities/transaction/api/use-cash-receipt-purpose-update';
export const CashReceiptDetailPage = () => {
@@ -116,11 +116,11 @@ export const CashReceiptDetailPage = () => {
</div>
</div>
</main>
<BottomSheetCashReceitPurposeUpdate
<CashReceitPurposeUpdateBottomSheet
setBottomSheetOn={ setBottomSheetOn }
bottomSheetOn={ bottomSheetOn }
callPurposeUpdate={ callPurposeUpdate }
></BottomSheetCashReceitPurposeUpdate>
></CashReceitPurposeUpdateBottomSheet>
</>
);
};

View File

@@ -28,6 +28,8 @@ import {
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
import { EscrowMailResendBottomSheet } from '@/entities/transaction/ui/escrow-mail-resend-bottom-sheet';
import { useEscrowMailResendMutation } from '@/entities/transaction/api/use-escrow-mail-resend-mutation';
export const EscrowDetailPage = () => {
const { navigate } = useNavigate();
@@ -45,7 +47,10 @@ export const EscrowDetailPage = () => {
const [showPaymentInfo, setShowPaymentInfo] = useState<boolean>(false);
const [showTransactionInfo, setShowTransactionInfo] = useState<boolean>(false);
const [showSettlementInfo, setShowSettlementInfo] = useState<boolean>(false);
const [bottomSheetOn, setBottomSheetOn] = useState<boolean>(false);
const [orderNumber, setOrderNumber] = useState<string>();
const [tid, setTid] = useState<string>();
useSetHeaderTitle('에스크로 상세');
useSetHeaderType(HeaderType.RightClose);
@@ -54,23 +59,41 @@ export const EscrowDetailPage = () => {
});
useSetFooterMode(false);
const { mutateAsync: escroDetail } = useEscrowDetailMutation();
const { mutateAsync: escrowDetail } = useEscrowDetailMutation();
const { mutateAsync: escrowMailResend } = useEscrowMailResendMutation()
const callDetail = () => {
let escroDetailParams: EscrowDetailParams = {
issueNumber: location?.state.issueNumber,
};
escroDetail(escroDetailParams).then((rs: DetailResponse) => {
escrowDetail(escroDetailParams).then((rs: DetailResponse) => {
setImportantInfo(rs.importantInfo);
setEscrowInfo(rs.escrowInfo);
setPaymentInfo(rs.paymentInfo);
setTransactionInfo(rs.transactionInfo);
setSettlementInfo(rs.settlementInfo);
setOrderNumber(rs.importantInfo?.ordNo);
setTid(rs.importantInfo?.tid);
});
};
useEffect(() => {
callDetail();
}, []);
const onClickToShowMailResend = () => {
setBottomSheetOn(true);
};
const callMailResend = () => {
let params = {
orderNumber: orderNumber,
tid: tid,
};
escrowMailResend(params).then((rs: any) => {
console.log(rs);
});
};
const onClickToShowInfo = (infoWrapKey: InfoWrapKeys) => {
if(infoWrapKey === InfoWrapKeys.Amount){
@@ -136,9 +159,20 @@ export const EscrowDetailPage = () => {
<div className="txn-divider"></div>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToShowMailResend() }
> </button>
</div>
</div>
</div>
</main>
<EscrowMailResendBottomSheet
setBottomSheetOn={ setBottomSheetOn }
bottomSheetOn={ bottomSheetOn }
callMailResend={ callMailResend }
></EscrowMailResendBottomSheet>
</>
);
};

View File

@@ -11,7 +11,7 @@ import { EscrowListPage } from './escrow/list-page';
import { EscrowDetailPage } from './escrow/detail-page';
import { BillingListPage } from './billing/list-page';
import { BillingDetailPage } from './billing/detail-page';
import { BillingPaymentRequestPage } from './billing/payment-request-page';
import { BillingChargePage } from './billing/charge-page';
export const TransactionPages = () => {
@@ -35,7 +35,7 @@ export const TransactionPages = () => {
<Route path={ROUTE_NAMES.transaction.billing.base}>
<Route path={ROUTE_NAMES.transaction.billing.list} element={<BillingListPage />} />
<Route path={ROUTE_NAMES.transaction.billing.detail} element={<BillingDetailPage />} />
<Route path={ROUTE_NAMES.transaction.billing.paymentRequest} element={<BillingPaymentRequestPage />} />
<Route path={ROUTE_NAMES.transaction.billing.charge} element={<BillingChargePage />} />
</Route>
</SentryRoutes>
</>

View File

@@ -154,7 +154,7 @@ export const API_URL = {
// POST: 에스크로 목록 상세 조회
return `${API_BASE_URL}/api/v1/escrows/detail`;
},
escroMailResend: () => {
escrowMailResend: () => {
// POST: 에스크로 메일 재발송
return `${API_BASE_URL}/api/v1/escrows/mail/resend`;
},

View File

@@ -65,9 +65,9 @@ export const PATHS: RouteNamesType = {
`${ROUTE_NAMES.transaction.base}${ROUTE_NAMES.transaction.billing.base}`,
ROUTE_NAMES.transaction.billing.detail,
),
paymentRequest: generatePath(
charge: generatePath(
`${ROUTE_NAMES.transaction.base}${ROUTE_NAMES.transaction.billing.base}`,
ROUTE_NAMES.transaction.billing.paymentRequest,
ROUTE_NAMES.transaction.billing.charge,
),
}
},

View File

@@ -23,7 +23,7 @@ export const ROUTE_NAMES = {
base: '/billing/*',
list: 'list',
detail: 'detail',
paymentRequest: 'payment-request',
charge: 'charge',
}
},
settlement: {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="6" fill="#F5F5F5"/>
<g clip-path="url(#clip0_2269_6765)">
<rect x="9" y="17" width="18" height="2" rx="1" fill="#999999"/>
</g>
<defs>
<clipPath id="clip0_2269_6765">
<rect x="8" y="8" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@@ -0,0 +1,11 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2269_6819)">
<rect x="1" y="9" width="18" height="2" rx="1" fill="#999999"/>
<rect x="11" y="1" width="18" height="2" rx="1" transform="rotate(90 11 1)" fill="#999999"/>
</g>
<defs>
<clipPath id="clip0_2269_6819">
<rect width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 414 B

View File

@@ -0,0 +1,13 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="80" height="80" rx="40" fill="#F4F8FF"/>
<path d="M62.9999 26V21.8988C62.9999 19.7455 61.2543 18 59.1011 18H54.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.9999 26V21.8988C16.9999 19.7455 18.7454 18 20.8987 18H24.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.9999 54V58.1012C62.9999 60.2545 61.2543 62 59.1011 62H54.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.9999 54V58.1012C16.9999 60.2545 18.7454 62 20.8987 62H24.9999" stroke="#A9B3DE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="22.9999" y="23" width="34" height="34" rx="4.36364" fill="#86CBFF"/>
<path d="M35.1083 45.9883C36.8146 47.6106 40.7305 49.2609 45.1779 45.9883" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="31.4999" y="33.5" width="1" height="2.5" rx="0.5" stroke="white"/>
<rect x="48.4999" y="33.5" width="1" height="3" rx="0.5" stroke="white"/>
<path d="M40.2273 34.5762V40.7018C40.2273 41.6249 39.8098 42.3507 38.3182 42.5164" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="21.4544" y1="40.0009" x2="58.5453" y2="40.0009" stroke="#F05D92" stroke-width="2.18182" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB