refactor(payment): centralize route lifecycle

This commit is contained in:
2026-07-15 18:47:01 +08:00
parent 7dce1b5d4c
commit 590dee417b
9 changed files with 203 additions and 144 deletions
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { shouldInspectPendingPaymentOrder } from "../use-payment-order-lifecycle";
import { shouldInspectPendingPaymentOrder } from "../use-pending-payment-order-lifecycle";
describe("shouldInspectPendingPaymentOrder", () => {
it("allows pending order inspection when payment plans are ready", () => {
+83
View File
@@ -0,0 +1,83 @@
"use client";
import { type Dispatch, useEffect, useRef } from "react";
import type { PayChannel } from "@/data/dto/payment";
import type { PendingPaymentSubscriptionType } from "@/lib/payment/pending_payment_order";
import {
usePaymentDispatch,
usePaymentState,
type PaymentContextState,
} from "@/stores/payment/payment-context";
import type { PaymentEvent } from "@/stores/payment/payment-events";
import type { PaymentPlanCatalog } from "@/stores/payment/payment-state";
import { usePendingPaymentOrderLifecycle } from "./use-pending-payment-order-lifecycle";
export interface UsePaymentRouteFlowInput {
catalog?: PaymentPlanCatalog;
initialPayChannel: PayChannel;
paymentType: PendingPaymentSubscriptionType;
shouldResumePendingOrder: boolean;
}
export interface PaymentRouteFlow {
payment: PaymentContextState;
paymentDispatch: Dispatch<PaymentEvent>;
}
/**
* Owns the lifecycle shared by payment entry routes:
* initialize the requested catalog and restore or clean up redirect orders.
*/
export function usePaymentRouteFlow({
catalog = "default",
initialPayChannel,
paymentType,
shouldResumePendingOrder,
}: UsePaymentRouteFlowInput): PaymentRouteFlow {
const payment = usePaymentState();
const paymentDispatch = usePaymentDispatch();
const initialPayChannelAppliedRef = useRef(false);
usePendingPaymentOrderLifecycle({
payment,
paymentDispatch,
paymentType,
shouldResumePendingOrder,
});
useEffect(() => {
if (payment.status === "idle") {
initialPayChannelAppliedRef.current = true;
paymentDispatch({
type: "PaymentInit",
catalog,
payChannel: initialPayChannel,
});
return;
}
if (
initialPayChannelAppliedRef.current ||
payment.status !== "ready"
) {
return;
}
initialPayChannelAppliedRef.current = true;
if (payment.payChannel === initialPayChannel) return;
paymentDispatch({
type: "PaymentPayChannelChanged",
payChannel: initialPayChannel,
});
}, [
catalog,
initialPayChannel,
payment.payChannel,
payment.status,
paymentDispatch,
]);
return { payment, paymentDispatch };
}
@@ -7,11 +7,7 @@ import {
getPendingPaymentOrderForType,
type PendingPaymentSubscriptionType,
} from "@/lib/payment/pending_payment_order";
import type {
PaymentContextState,
} from "@/stores/payment/payment-context";
import type { PaymentEvent } from "@/stores/payment/payment-events";
import { useUserDispatch } from "@/stores/user/user-context";
export interface PaymentOrderLifecycleState {
currentOrderId: string | null;
@@ -25,12 +21,11 @@ export interface ShouldInspectPendingPaymentOrderInput
shouldResumePendingOrder: boolean;
}
export interface UsePaymentOrderLifecycleInput {
payment: PaymentContextState;
export interface UsePendingPaymentOrderLifecycleInput {
payment: PaymentOrderLifecycleState;
paymentDispatch: Dispatch<PaymentEvent>;
paymentType: PendingPaymentSubscriptionType;
shouldResumePendingOrder: boolean;
refreshUserOnPaid?: boolean;
}
export function shouldInspectPendingPaymentOrder({
@@ -46,30 +41,14 @@ export function shouldInspectPendingPaymentOrder({
);
}
export function usePaymentOrderLifecycle({
export function usePendingPaymentOrderLifecycle({
payment,
paymentDispatch,
paymentType,
refreshUserOnPaid = true,
shouldResumePendingOrder,
}: UsePaymentOrderLifecycleInput): void {
const userDispatch = useUserDispatch();
const refreshedPaidOrderRef = useRef<string | null>(null);
}: UsePendingPaymentOrderLifecycleInput): void {
const resumedPendingOrderRef = useRef<string | null>(null);
useEffect(() => {
if (!refreshUserOnPaid) return;
if (!payment.isPaid || !payment.currentOrderId) return;
if (refreshedPaidOrderRef.current === payment.currentOrderId) return;
refreshedPaidOrderRef.current = payment.currentOrderId;
userDispatch({ type: "UserFetch" });
}, [
payment.currentOrderId,
payment.isPaid,
refreshUserOnPaid,
userDispatch,
]);
useEffect(() => {
if (
!shouldInspectPendingPaymentOrder({