Files
cozsweet-frontend-nextjs/src/app/_hooks/use-payment-route-flow.ts
T
Codex 59e4eac736
Docker Image / Build and Push Docker Image (push) Successful in 2m10s
feat(payment): expand Stripe methods and checkout handoff
2026-07-28 16:48:11 +08:00

111 lines
3.2 KiB
TypeScript

"use client";
import { type Dispatch, useEffect, useRef } from "react";
import type { PayChannel } from "@/data/schemas/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;
characterId?: string;
initialCategory?: string | null;
initialPlanId?: string | null;
initialAutoRenew?: boolean | null;
commercialOfferId?: string | null;
resumeOrderId?: string | null;
chatActionId?: string | null;
}
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,
characterId,
initialCategory = null,
initialPlanId = null,
initialAutoRenew = null,
commercialOfferId = null,
resumeOrderId = null,
chatActionId = null,
}: UsePaymentRouteFlowInput): PaymentRouteFlow {
const payment = usePaymentState();
const paymentDispatch = usePaymentDispatch();
const initializedCatalogKeyRef = useRef<string | null>(null);
const resumedOrderIdRef = useRef<string | null>(null);
const catalogKey = [
catalog,
characterId ?? "",
initialCategory ?? "",
initialPlanId ?? "",
initialAutoRenew === null ? "" : String(initialAutoRenew),
commercialOfferId ?? "",
chatActionId ?? "",
].join(":");
usePendingPaymentOrderLifecycle({
payment,
paymentDispatch,
paymentType,
shouldResumePendingOrder,
});
useEffect(() => {
if (initializedCatalogKeyRef.current === catalogKey) return;
initializedCatalogKeyRef.current = catalogKey;
paymentDispatch({
type: "PaymentInit",
catalog,
payChannel: initialPayChannel,
...(characterId ? { characterId } : {}),
...(initialCategory ? { category: initialCategory } : {}),
...(initialPlanId ? { planId: initialPlanId } : {}),
...(initialAutoRenew === null
? {}
: { autoRenew: initialAutoRenew }),
...(commercialOfferId ? { commercialOfferId } : {}),
...(chatActionId ? { chatActionId } : {}),
});
}, [
catalog,
catalogKey,
characterId,
commercialOfferId,
chatActionId,
initialCategory,
initialPlanId,
initialAutoRenew,
initialPayChannel,
paymentDispatch,
]);
useEffect(() => {
if (!resumeOrderId || resumedOrderIdRef.current === resumeOrderId) return;
resumedOrderIdRef.current = resumeOrderId;
paymentDispatch({ type: "PaymentReturned", orderId: resumeOrderId });
}, [paymentDispatch, resumeOrderId]);
return { payment, paymentDispatch };
}