refactor(stores): standardize state machine structure
This commit is contained in:
@@ -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",
|
||||
});
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user