From 9095b4d9b1ead333e43087ca28696d0a6e75c50e Mon Sep 17 00:00:00 2001 From: callmeyan Date: Sat, 22 Jun 2024 20:56:29 +0800 Subject: [PATCH] :sparkles: update flywire --- src/assets/index.less | 3 +- src/components/icons/index.tsx | 12 +- src/i18n/translations/en.json | 2 + src/i18n/translations/sc.json | 2 + src/i18n/translations/tc.json | 2 + src/pages/bill/query.tsx | 13 ++- src/pages/pay/component/start-fly-wire.tsx | 55 ++++------ src/pages/pay/index.tsx | 3 +- src/pages/pay/pay.module.less | 9 +- src/pages/pay/result.tsx | 121 +++++++++++++++------ src/service/api/bill.ts | 8 +- src/service/generate-pdf.ts | 2 +- 12 files changed, 152 insertions(+), 80 deletions(-) diff --git a/src/assets/index.less b/src/assets/index.less index 299d6d7..3d37678 100644 --- a/src/assets/index.less +++ b/src/assets/index.less @@ -173,7 +173,8 @@ body #root .dashboard-layout{ .dashboard-menu-container { padding: var(--dashboard-layout-padding, 15px); - + position: sticky; + top:var(--dashboard-header-height, 50px); .nav-item { padding: 15px; display: flex; diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index 2f4004d..be49cc9 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -130,4 +130,14 @@ export const IconBillType = ({style}: IconProps) => { ) -} \ No newline at end of file +} + +export const IconLoading = ({size}:{size?:string|number})=>( + + + + + +) \ No newline at end of file diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json index 26e35c8..66e4185 100644 --- a/src/i18n/translations/en.json +++ b/src/i18n/translations/en.json @@ -87,6 +87,8 @@ "bill_error": "Bills to be paid do not exist or are overdue", "charge": "Charge", "confirm_pay": "CONFIRM PAYMENT", + "query_pay_status": "Check payment status...", + "returnBack": "Return Payment System", "text_canceled": "Payment has been cancelled", "text_failed": "Payment Failure", "text_success": "Success", diff --git a/src/i18n/translations/sc.json b/src/i18n/translations/sc.json index a079b0a..5f1d715 100644 --- a/src/i18n/translations/sc.json +++ b/src/i18n/translations/sc.json @@ -87,6 +87,8 @@ "bill_error": "待支付账单不存在或已过期", "charge": "手续费", "confirm_pay": "确认支付", + "query_pay_status": "查询支付状态...", + "returnBack": "返回Payment System", "text_canceled": "支付已取消", "text_failed": "支付失败", "text_success": "支付成功", diff --git a/src/i18n/translations/tc.json b/src/i18n/translations/tc.json index f1db67b..9b3389b 100644 --- a/src/i18n/translations/tc.json +++ b/src/i18n/translations/tc.json @@ -87,6 +87,8 @@ "bill_error": "待支付帳單不存在或已過期", "charge": "手續費", "confirm_pay": "確認付款", + "query_pay_status": "查詢支付狀態...", + "returnBack": "返回Payment System", "text_canceled": "付款已取消", "text_failed": "付款失敗", "text_success": "付款成功", diff --git a/src/pages/bill/query.tsx b/src/pages/bill/query.tsx index 2f5f14b..65ba4e6 100644 --- a/src/pages/bill/query.tsx +++ b/src/pages/bill/query.tsx @@ -12,6 +12,13 @@ import {BillStatus} from "@/service/types.ts"; import {useDownloadReceiptPDF} from "@/service/generate-pdf.ts"; import useAuth from "@/hooks/useAuth.ts"; + +const DownloadButton = ({bill,text}: { bill: BillModel;text:string }) => { + const {loading: downloading, downloadPDF} = useDownloadReceiptPDF() + return () +} const BillQuery = () => { const {user} = useAuth(); const [showBill, setShowBill] = useState() @@ -31,7 +38,6 @@ const BillQuery = () => { }); } }) - const {loading: downloading, downloadPDF} = useDownloadReceiptPDF() const {t} = useTranslation() const onConfirmCancel = (bill: BillModel) => { @@ -56,10 +62,7 @@ const BillQuery = () => { {AppMode == 'development' && 支付} } { - bill.status == BillStatus.PAID - && + bill.status == BillStatus.PAID && } ) } diff --git a/src/pages/pay/component/start-fly-wire.tsx b/src/pages/pay/component/start-fly-wire.tsx index 5d87060..76c9b24 100644 --- a/src/pages/pay/component/start-fly-wire.tsx +++ b/src/pages/pay/component/start-fly-wire.tsx @@ -1,43 +1,32 @@ import styles from "@/pages/pay/pay.module.less"; -import React, {useMemo} from "react"; +import React, {useEffect, useState} from "react"; import {getAppUrl} from "@/hooks/useAppUrl.ts"; import {useTranslation} from "react-i18next"; +import {IconLoading} from "@/components/icons"; +import {getFlywirePayUrl} from "@/service/api/bill.ts"; -// 支付费用配置 -const PayFeeTypeConfig: { - [key: string]: string -} = { - 'CAUTION FEE': 'items[caution_fee]', // caution_fee - 'STUDENT UNION FEE': 'items[student_union_fee]', // student_union_fee - 'TUITION FEE': 'items[tuition_fee]', // tuition_fee - 'CREDIT POINT': 'items[credit_point]', // credit_point - 'APPLICATION FEE': 'items[application_fee]', // application_fee +type StartFlyWireProps = { + open?: boolean; + bill: BillModel } -function getPaymentFees(bill: BillModel) { - // 根据bill details的账单类型设置支付费用 - return bill.details.map(it => { - return (PayFeeTypeConfig[it.bill_type] ?? 'items[other_fee]') + `=${Number(it.amount) * 100}` - }) -} - -export const StartFlyWire: React.FC<{ bill: BillModel }> = ({bill}) => { - const callbackUrl = getAppUrl() + "/pay/success?from=flywire"; +export const StartFlyWire: React.FC = ({bill, open}) => { + const callbackUrl = getAppUrl() + "/pay/success?from=FLYWIRE&bill=" + bill.id; const {t} = useTranslation() - - const redirectUrl = useMemo(() => { - const fees = getPaymentFees(bill) // 支付费用 - // flywire 支付跳转连接 - return `${AppConfig.FIY_WIRE_GATEWAY}?provider=HAI&payment_destination=chuhaicollege` - + `&${fees.join('&')}` - + `&student_last_name=${encodeURIComponent(bill.student_english_name)}` - + `&student_id=${encodeURIComponent(bill.student_number || bill.application_number || '')}` - + `&student_email=${encodeURIComponent(bill.student_email)}` - + `&callback_url=${encodeURIComponent(callbackUrl)}` - + `&callback_id=${encodeURIComponent(bill.merchant_ref || '')}` + const [loading, setLoading] = useState(false) + const [redirectUrl, setRedirectUrl] = useState() + useEffect(() => { + if (bill) { + setLoading(true) + getFlywirePayUrl(bill.id, callbackUrl, t(`pay.returnBack`)).then(res => { + setRedirectUrl(res.url) + }).catch(console.error).finally(() => setLoading(false)) + } }, [bill]) - return ( - {t('pay.confirm_pay')} - ) + return (<> + {open ? (!loading ? + {t('pay.confirm_pay')} + : ):<>} + ) } \ No newline at end of file diff --git a/src/pages/pay/index.tsx b/src/pages/pay/index.tsx index 6a9e992..276daab 100644 --- a/src/pages/pay/index.tsx +++ b/src/pages/pay/index.tsx @@ -93,7 +93,8 @@ const PayIndex = () => {
Your Email: {bill.student_email}
- { payChannel == 'asia_pay' ? : } + { payChannel == 'asia_pay' && } +
: <>} diff --git a/src/pages/pay/pay.module.less b/src/pages/pay/pay.module.less index 2e46873..3300a02 100644 --- a/src/pages/pay/pay.module.less +++ b/src/pages/pay/pay.module.less @@ -9,7 +9,14 @@ width: 600px; transition: width 0.1s; } - +.payLoading{ + padding: 50px 0; + text-align: center; +} +.loadingText{ + font-size: 32px; + margin-top: 20px; +} .payAmount { color: #fff; background-color: #50ADA7; diff --git a/src/pages/pay/result.tsx b/src/pages/pay/result.tsx index 15d301f..522ea0d 100644 --- a/src/pages/pay/result.tsx +++ b/src/pages/pay/result.tsx @@ -9,11 +9,14 @@ import {useDownloadReceiptPDF} from "@/service/generate-pdf.ts"; import {useEffect} from "react"; import {useSetState} from "ahooks"; import {FailIcon} from "@/assets/images/pay/fail.tsx"; -import {updateBillPaymentSuccess} from "@/service/api/bill.ts"; - -type PayResult = 'fail' | 'success' | 'cancel' | 'error' | string; +import {getBillDetail, updateBillPaymentSuccess} from "@/service/api/bill.ts"; +import {BillStatus} from "@/service/types.ts"; +import {IconLoading} from "@/components/icons"; +type PayResult = 'fail' | 'success' | 'cancel' | 'error' | 'loading' | string; +const QUERY_MAX_COUNT = 50, + QUERY_DELAY = 6000; const PayIndex = () => { const {t} = useTranslation() @@ -24,14 +27,48 @@ const PayIndex = () => { id?: string; bill?: BillModel status?: string | null; - }>({}); + loading?: boolean; + queryCount: number; + }>({ + result: 'loading', + queryCount: 0 + }); + const param = useParams<{ result: PayResult }>(); const [search] = useSearchParams(); // 参数有: from: asia_spay || flywire - const updateBillSuccess = (billId: number, type: string, ref: string) =>{ + const updateBillSuccess = (billId: number, type: string, ref: string) => { + setState({result: 'loading'}) updateBillPaymentSuccess(billId, ref, type).then(bill => { - setState({bill}) - }).catch(()=>{ - setState({result:'fail'}) + setState({ + result: 'success', + bill: bill + }) + }).catch(() => { + setState({result: 'fail'}) + }) + } + const checkBillSuccess = (billId: number) => { + setState({ + result: 'loading', + queryCount: state.queryCount + 1 + }) + + getBillDetail(Number(billId)).then((bill) => { + // 判断bill状态 + if (bill.status != BillStatus.PAID) { + if(state.queryCount > QUERY_MAX_COUNT) { + setState({result: 'fail'}) + }else{ + // 重试 + setTimeout(()=>checkBillSuccess(billId), QUERY_DELAY); + } + return; + } + setState({ + result: 'success', + bill: bill + }) + }).catch(() => { }) } useEffect(() => { @@ -47,8 +84,13 @@ const PayIndex = () => { return; } if (result == 'success') { - updateBillSuccess(Number(bill), from , (from == 'ASIAPAY' ? search.get('Ref')! : search.get('callback_id')!)); + if (from == 'ASIAPAY') { + updateBillSuccess(Number(bill), from, search.get('Ref')!); + } else if (from == 'FLYWIRE') { + checkBillSuccess(Number(bill)); + } } + return; } setState({result, status}) }, []); @@ -56,33 +98,42 @@ const PayIndex = () => { return (
{state.result == 'success' ? <> -
-
- +
+
+ +
+

+ {t('pay.text_success')} +

-

- {t('pay.text_success')} -

-
- {state.bill &&
-
{t('bill.bill_number')}: {state.bill.student_number || state.bill.application_number}
-
- -
-
} - :
-
- -
-

- {state.result == 'fail' ? t('pay.text_failed') : ( - state.result == 'cancel' ? t('pay.text_canceled') : (t('pay.bill_error')) - )} -

-
} + {state.bill &&
+
{t('bill.bill_number')}: {state.bill.student_number || state.bill.application_number}
+
+ +
+
} + + : ( + state.result == 'loading' ?
+ +
{t('pay.query_pay_status')}
+
+ : (
+
+ +
+

+ {state.result == 'fail' ? t('pay.text_failed') : ( + state.result == 'cancel' ? t('pay.text_canceled') : (t('pay.bill_error')) + )} +

+
) + ) + }
); } export default PayIndex; \ No newline at end of file diff --git a/src/service/api/bill.ts b/src/service/api/bill.ts index de3457f..493ca76 100644 --- a/src/service/api/bill.ts +++ b/src/service/api/bill.ts @@ -42,6 +42,10 @@ export function getAsiaPayData(id: number) { return get(`/bills/${id}/asiapay`) } +export function getFlywirePayUrl(id: number, return_cta: string, return_cta_name: string) { + return post<{ url: string }>(`/bills/${id}/flywire`, {return_cta, return_cta_name}) +} + // 作废订单 export function cancelBill(id: number) { return put(`/bills/${id}/cancel`) @@ -51,6 +55,6 @@ export function confirmBills(bill_ids: number[]) { return post(`/bills/apply`, {bill_ids}) } -export function updateBillPaymentSuccess(bill_id: number, merchant_ref: string,payment_channel:string) { - return post(`/bills/finish`, {merchant_ref,payment_channel,bill_id}) +export function updateBillPaymentSuccess(bill_id: number, merchant_ref: string, payment_channel: string) { + return post(`/bills/finish`, {merchant_ref, payment_channel, bill_id}) } diff --git a/src/service/generate-pdf.ts b/src/service/generate-pdf.ts index 7cc5c3f..b6d8626 100644 --- a/src/service/generate-pdf.ts +++ b/src/service/generate-pdf.ts @@ -35,7 +35,7 @@ export function GeneratePdf(bill: BillModel) { title: 'Programme:', content: bill.programme_english_name }, 56) - drawItem(doc, {title: 'Mode of Study:', content: bill.attendance_mode}, 70) + drawItem(doc, {title: 'Mode of Study:', content: bill.attendance_mode}, bill.programme_english_name.length > 70?70:64) // draw table autoTable(doc, { startY: 80,