|
|
@@ -3,21 +3,25 @@
|
|
|
import { useSearchParams } from "next/navigation";
|
|
|
import { useTranslations } from "next-intl";
|
|
|
import { useRouter } from "@/i18n/navigation";
|
|
|
-import { useEffect, useMemo, useState } from "react";
|
|
|
+import { useEffect, useId, useMemo, useState } from "react";
|
|
|
import { getCourseBySlug } from "@/data/courses";
|
|
|
import { useAuth } from "@/providers/auth-provider";
|
|
|
import {
|
|
|
fetchBankChannelOptions,
|
|
|
fetchRemittanceChannels,
|
|
|
isTelegraphicStylePayRequestUrl,
|
|
|
+ payAmountWithChannelRate,
|
|
|
+ resolvePayRateMultiplier,
|
|
|
submitXfgPayOrder,
|
|
|
type BankChannelOption,
|
|
|
type RemittanceChannel,
|
|
|
} from "@/lib/checkout-api";
|
|
|
import { InlineLoading } from "@/components/ui/loading-state";
|
|
|
import { ModalShell } from "@/components/ui/modal-shell";
|
|
|
+import { resolveRemittanceChannelIconUrl } from "@/lib/remittance-icon-url";
|
|
|
|
|
|
export function CheckoutForm() {
|
|
|
+ const checkoutFormId = useId();
|
|
|
const t = useTranslations("checkout");
|
|
|
const router = useRouter();
|
|
|
const searchParams = useSearchParams();
|
|
|
@@ -104,14 +108,30 @@ export function CheckoutForm() {
|
|
|
.sort((a, b) => a.order - b.order);
|
|
|
}, [channels]);
|
|
|
|
|
|
+ const selectedChannel = channels.find((c) => c.id === selectedChannelId);
|
|
|
+ const selectedBank = bankOptions.find((b) => b.value === selectedBankCode);
|
|
|
+ const shouldShowBankSelector = selectedChannel?.bankValid === 1;
|
|
|
+
|
|
|
+ const payRateMultiplier = useMemo(
|
|
|
+ () =>
|
|
|
+ resolvePayRateMultiplier({
|
|
|
+ bankSelectorVisible: shouldShowBankSelector,
|
|
|
+ selectedBankCode,
|
|
|
+ bankOptions,
|
|
|
+ channelRate: selectedChannel?.rate ?? null,
|
|
|
+ }),
|
|
|
+ [shouldShowBankSelector, selectedBankCode, bankOptions, selectedChannel?.rate],
|
|
|
+ );
|
|
|
+
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
|
if (!user) return;
|
|
|
const depositAmountNum = Number(depositAmount);
|
|
|
+ const pricedByRate = payAmountWithChannelRate(displayCoursePrice, payRateMultiplier);
|
|
|
const finalAmount =
|
|
|
Number.isFinite(depositAmountNum) && depositAmountNum > 0
|
|
|
? depositAmountNum
|
|
|
- : displayCoursePrice;
|
|
|
+ : pricedByRate;
|
|
|
if (!goodsId) {
|
|
|
setMsg("商品ID缺失,无法提交订单。");
|
|
|
return;
|
|
|
@@ -172,10 +192,6 @@ export function CheckoutForm() {
|
|
|
setDepositAmount("");
|
|
|
};
|
|
|
|
|
|
- const selectedChannel = channels.find((c) => c.id === selectedChannelId);
|
|
|
- const selectedBank = bankOptions.find((b) => b.value === selectedBankCode);
|
|
|
- const shouldShowBankSelector = selectedChannel?.bankValid === 1;
|
|
|
-
|
|
|
const toggleGroup = (groupName: string) => {
|
|
|
setExpandedGroups((prev) => ({
|
|
|
...prev,
|
|
|
@@ -214,14 +230,15 @@ export function CheckoutForm() {
|
|
|
}, [isPaymentModalOpen, selectedChannelId, selectedChannel?.code, shouldShowBankSelector]);
|
|
|
|
|
|
useEffect(() => {
|
|
|
- if (!isPaymentModalOpen) return;
|
|
|
- if (depositAmount) return;
|
|
|
+ if (!isPaymentModalOpen || !selectedChannelId) return;
|
|
|
if (!(displayCoursePrice > 0)) return;
|
|
|
- setDepositAmount(String(displayCoursePrice));
|
|
|
- }, [isPaymentModalOpen, displayCoursePrice, depositAmount]);
|
|
|
+ const amount = payAmountWithChannelRate(displayCoursePrice, payRateMultiplier);
|
|
|
+ if (!(amount > 0)) return;
|
|
|
+ setDepositAmount(String(amount));
|
|
|
+ }, [isPaymentModalOpen, selectedChannelId, displayCoursePrice, payRateMultiplier]);
|
|
|
|
|
|
return (
|
|
|
- <form onSubmit={handleSubmit} className="space-y-8">
|
|
|
+ <form id={checkoutFormId} onSubmit={handleSubmit} className="space-y-8">
|
|
|
<div className="rounded-2xl border border-[var(--border)] bg-[var(--card)] p-6">
|
|
|
{channelsLoading ? <InlineLoading text="通道加载中..." className="mt-4" /> : null}
|
|
|
{channelsError ? (
|
|
|
@@ -270,7 +287,7 @@ export function CheckoutForm() {
|
|
|
{channel.icon ? (
|
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
|
<img
|
|
|
- src={channel.icon}
|
|
|
+ src={resolveRemittanceChannelIconUrl(channel.icon)}
|
|
|
alt={channel.name}
|
|
|
className="h-7 w-auto rounded object-contain"
|
|
|
/>
|
|
|
@@ -477,6 +494,7 @@ export function CheckoutForm() {
|
|
|
<div className="mt-6">
|
|
|
<button
|
|
|
type="submit"
|
|
|
+ form={checkoutFormId}
|
|
|
disabled={
|
|
|
!user ||
|
|
|
!hasCourseInfo ||
|