refactor(stores): standardize state machine structure

This commit is contained in:
2026-07-15 11:19:08 +08:00
parent ed3e800245
commit 20d07023e4
68 changed files with 3492 additions and 3193 deletions
@@ -0,0 +1,158 @@
import { vi } from "vitest";
import { fromPromise } from "xstate";
import {
CreatePaymentOrderResponse,
type PayChannel,
PaymentOrderStatusResponse,
PaymentPlansResponse,
} from "@/data/dto/payment";
import { paymentMachine } from "@/stores/payment/payment-machine";
import type { PaymentPlanCatalog } from "@/stores/payment/payment-state";
export interface PaymentPlansActorInput {
catalog: PaymentPlanCatalog;
}
export interface CreateOrderInput {
planId: string;
payChannel: PayChannel;
autoRenew: boolean;
}
export type CreateOrderSpy = (input: CreateOrderInput) => void;
export const monthlyPlan = {
planId: "vip_monthly",
planName: "VIP Monthly",
orderType: "vip_monthly",
vipDays: 30,
dolAmount: null,
creditBalance: 3000,
amountCents: 999,
originalAmountCents: null,
dailyPriceCents: 33,
currency: "usd",
};
export const lifetimePlan = {
planId: "vip_lifetime",
planName: "VIP Lifetime",
orderType: "vip_lifetime",
vipDays: 3650,
dolAmount: null,
creditBalance: 0,
amountCents: 4999,
originalAmountCents: null,
dailyPriceCents: null,
currency: "usd",
};
export const creditPlan = {
planId: "dol_1000",
planName: "1000 Credits",
orderType: "dol",
vipDays: null,
dolAmount: 1000,
creditBalance: 1000,
amountCents: 990,
originalAmountCents: null,
dailyPriceCents: null,
currency: "usd",
};
export const quarterlyPlan = {
planId: "vip_quarterly",
planName: "VIP Quarterly",
orderType: "vip_quarterly",
vipDays: 90,
dolAmount: null,
creditBalance: 9000,
amountCents: 2499,
originalAmountCents: null,
dailyPriceCents: 28,
currency: "usd",
};
export const tipPlan = {
planId: "tip_coffee_usd_4_99",
planName: "Small Coffee",
orderType: "tip",
vipDays: null,
dolAmount: null,
creditBalance: 0,
amountCents: 499,
originalAmountCents: null,
dailyPriceCents: null,
currency: "USD",
};
export function createTestPaymentMachine(
overrides: Partial<{
cachedPlans: PaymentPlansResponse | null;
refreshedPlans: PaymentPlansResponse;
refreshPlans: () => Promise<PaymentPlansResponse>;
onLoadCatalog: (catalog: PaymentPlanCatalog) => void;
onRefreshCatalog: (catalog: PaymentPlanCatalog) => void;
createOrderSpy: CreateOrderSpy;
createOrderError: Error;
orderStatus: "pending" | "paid" | "failed";
orderStatuses: ("pending" | "paid" | "failed")[];
}> = {},
) {
const createOrderSpy = overrides.createOrderSpy ?? vi.fn<CreateOrderSpy>();
const orderStatus = overrides.orderStatus ?? "paid";
const orderStatuses = overrides.orderStatuses ?? [];
let pollCount = 0;
return paymentMachine.provide({
actors: {
loadCachedPlans: fromPromise<
PaymentPlansResponse | null,
PaymentPlansActorInput
>(async ({ input }) => {
overrides.onLoadCatalog?.(input.catalog);
return overrides.cachedPlans ?? null;
}),
refreshPlans: fromPromise<
PaymentPlansResponse,
PaymentPlansActorInput
>(async ({ input }) => {
overrides.onRefreshCatalog?.(input.catalog);
if (overrides.refreshPlans) return overrides.refreshPlans();
return (
overrides.refreshedPlans ??
PaymentPlansResponse.from({
plans: [monthlyPlan, lifetimePlan, creditPlan],
})
);
}),
createOrder: fromPromise<
CreatePaymentOrderResponse,
CreateOrderInput
>(async ({ input }) => {
createOrderSpy(input);
if (overrides.createOrderError) throw overrides.createOrderError;
return CreatePaymentOrderResponse.from({
orderId: "pay_test_001",
payParams: {
provider: "stripe",
clientSecret: "pi_test_secret_test",
},
});
}),
pollOrderStatus: fromPromise(async () => {
const status =
orderStatuses[Math.min(pollCount, orderStatuses.length - 1)] ??
orderStatus;
pollCount += 1;
return PaymentOrderStatusResponse.from({
orderId: "pay_test_001",
status,
orderType: "vip_monthly",
planId: "vip_monthly",
});
}),
},
});
}