fix(payment): resume ezpay orders only after return

This commit is contained in:
2026-06-22 18:23:59 +08:00
parent 27021f9328
commit 2cfbaba058
4 changed files with 64 additions and 9 deletions
+22 -2
View File
@@ -1,12 +1,32 @@
import { Suspense } from "react";
import { MobileShell } from "@/app/_components/core";
import { SubscriptionPageClient } from "./subscription-page-client";
import { SubscriptionScreen } from "./subscription-screen";
export default function SubscriptionPage() {
return (
<Suspense fallback={<SubscriptionScreen subscriptionType="vip" />}>
<Suspense fallback={<SubscriptionFallback />}>
<SubscriptionPageClient />
</Suspense>
);
}
function SubscriptionFallback() {
return (
<MobileShell>
<div
style={{
minHeight: "60vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "2rem",
color: "#666",
}}
>
Loading subscription...
</div>
</MobileShell>
);
}
+5 -2
View File
@@ -10,8 +10,11 @@ import { ROUTES } from "@/router/routes";
type ReturnStatus = "loading" | "missing";
function buildSubscriptionUrl(subscriptionType: "vip" | "voice"): string {
if (subscriptionType === "voice") return `${ROUTES.subscription}?type=voice`;
return `${ROUTES.subscription}?type=vip`;
const params = new URLSearchParams({
type: subscriptionType,
paymentReturn: "1",
});
return `${ROUTES.subscription}?${params.toString()}`;
}
export default function SubscriptionReturnPage() {
@@ -14,6 +14,12 @@ function toSubscriptionType(value: string | null): SubscriptionType {
export function SubscriptionPageClient() {
const searchParams = useSearchParams();
const subscriptionType = toSubscriptionType(searchParams.get("type"));
const shouldResumePendingOrder = searchParams.get("paymentReturn") === "1";
return <SubscriptionScreen subscriptionType={subscriptionType} />;
return (
<SubscriptionScreen
subscriptionType={subscriptionType}
shouldResumePendingOrder={shouldResumePendingOrder}
/>
);
}
+30 -4
View File
@@ -113,10 +113,12 @@ function toPlanView(
export interface SubscriptionScreenProps {
subscriptionType?: SubscriptionType;
shouldResumePendingOrder?: boolean;
}
export function SubscriptionScreen({
subscriptionType = "vip",
shouldResumePendingOrder = false,
}: SubscriptionScreenProps) {
const user = useUserState();
const userDispatch = useUserDispatch();
@@ -139,15 +141,31 @@ export function SubscriptionScreen({
}, [payment.currentOrderId, payment.isPaid, userDispatch]);
useEffect(() => {
if (payment.status !== "ready") return;
const canInspectPendingOrder =
payment.status === "ready" ||
(!shouldResumePendingOrder &&
(payment.isPollingOrder || payment.isPaid || payment.status === "failed"));
if (!canInspectPendingOrder) return;
let cancelled = false;
const resumePendingOrder = async () => {
const handlePendingOrder = async () => {
const result = await PendingPaymentOrderStorage.getPendingOrderForType(
subscriptionType,
);
if (cancelled || !result.success || result.data === null) return;
if (!shouldResumePendingOrder) {
await PendingPaymentOrderStorage.clearPendingOrder();
if (
payment.currentOrderId === result.data.orderId &&
(payment.isPollingOrder || payment.isPaid || payment.status === "failed")
) {
paymentDispatch({ type: "PaymentReset" });
}
return;
}
if (payment.currentOrderId === result.data.orderId) return;
if (resumedPendingOrderRef.current === result.data.orderId) return;
@@ -158,11 +176,19 @@ export function SubscriptionScreen({
});
};
void resumePendingOrder();
void handlePendingOrder();
return () => {
cancelled = true;
};
}, [payment.currentOrderId, payment.status, paymentDispatch, subscriptionType]);
}, [
payment.currentOrderId,
payment.isPaid,
payment.isPollingOrder,
payment.status,
paymentDispatch,
shouldResumePendingOrder,
subscriptionType,
]);
useEffect(() => {
if (!payment.currentOrderId) return;