feat(characters): use local character catalog

This commit is contained in:
2026-07-17 16:03:18 +08:00
parent a210a98d98
commit b3ebd5cf3b
96 changed files with 1023 additions and 522 deletions
@@ -20,6 +20,7 @@ export interface CreateOrderInput {
planId: string;
payChannel: PayChannel;
autoRenew: boolean;
recipientCharacterId?: string;
}
export type CreateOrderSpy = (input: CreateOrderInput) => void;
@@ -79,6 +79,27 @@ describe("payment order flow", () => {
actor.stop();
});
it("passes the selected tip recipient to order creation", async () => {
const createOrderSpy = vi.fn<CreateOrderSpy>();
const actor = createActor(
createTestPaymentMachine({ createOrderSpy }),
).start();
await initialize(actor);
actor.send({
type: "PaymentCreateOrderSubmitted",
recipientCharacterId: "character_maya",
});
await waitFor(actor, (snapshot) => snapshot.matches("paid"));
expect(createOrderSpy).toHaveBeenCalledWith({
planId: "vip_monthly",
payChannel: "stripe",
autoRenew: true,
recipientCharacterId: "character_maya",
});
actor.stop();
});
it("tracks order creation failures", async () => {
const createOrderFailed = vi
.spyOn(behaviorAnalytics, "createOrderFailed")
+7 -1
View File
@@ -10,12 +10,18 @@ import { Result } from "@/utils/result";
export const createPaymentOrderActor = fromPromise<
CreatePaymentOrderResponse,
{ planId: string; payChannel: PayChannel; autoRenew: boolean }
{
planId: string;
payChannel: PayChannel;
autoRenew: boolean;
recipientCharacterId?: string;
}
>(async ({ input }) => {
const result = await getPaymentRepository().createOrder(
input.planId,
input.payChannel,
input.autoRenew,
input.recipientCharacterId,
);
if (Result.isErr(result)) throw result.error;
return result.data;
+5 -1
View File
@@ -159,10 +159,14 @@ const creatingOrderState = paymentMachineSetup.createStateConfig({
entry: "trackCreateOrderStart",
invoke: {
src: "createOrder",
input: ({ context }) => ({
input: ({ context, event }) => ({
planId: context.selectedPlanId,
payChannel: context.payChannel,
autoRenew: context.autoRenew,
...(event.type === "PaymentCreateOrderSubmitted" &&
event.recipientCharacterId
? { recipientCharacterId: event.recipientCharacterId }
: {}),
}),
onDone: {
target: "pollingOrder",
+4 -1
View File
@@ -14,7 +14,10 @@ export type PaymentEvent =
| { type: "PaymentPayChannelChanged"; payChannel: PayChannel }
| { type: "PaymentAutoRenewChanged"; autoRenew: boolean }
| { type: "PaymentAgreementChanged"; agreed: boolean }
| { type: "PaymentCreateOrderSubmitted" }
| {
type: "PaymentCreateOrderSubmitted";
recipientCharacterId?: string;
}
| { type: "PaymentReturned"; orderId: string; createdAt?: number }
| { type: "PaymentLaunchFailed"; errorMessage: string }
| { type: "PaymentFirstRechargeConsumed" }