feat(payment): implement coffee tip plans and update payment flow
This commit is contained in:
@@ -12,6 +12,11 @@ import {
|
||||
MAX_ORDER_POLLING_MS,
|
||||
PAYMENT_TIMEOUT_ERROR_MESSAGE,
|
||||
} from "@/stores/payment/payment-machine.helpers";
|
||||
import type { PaymentPlanCatalog } from "@/stores/payment/payment-state";
|
||||
|
||||
interface PaymentPlansActorInput {
|
||||
catalog: PaymentPlanCatalog;
|
||||
}
|
||||
|
||||
interface CreateOrderInput {
|
||||
planId: string;
|
||||
@@ -73,6 +78,19 @@ const quarterlyPlan = {
|
||||
currency: "usd",
|
||||
};
|
||||
|
||||
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",
|
||||
};
|
||||
|
||||
function createTestPaymentMachine(
|
||||
overrides: Partial<{
|
||||
createOrderSpy: CreateOrderSpy;
|
||||
@@ -87,10 +105,16 @@ function createTestPaymentMachine(
|
||||
|
||||
return paymentMachine.provide({
|
||||
actors: {
|
||||
loadCachedPlans: fromPromise<PaymentPlansResponse | null>(
|
||||
loadCachedPlans: fromPromise<
|
||||
PaymentPlansResponse | null,
|
||||
PaymentPlansActorInput
|
||||
>(
|
||||
async () => null,
|
||||
),
|
||||
refreshPlans: fromPromise(async () =>
|
||||
refreshPlans: fromPromise<
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansActorInput
|
||||
>(async () =>
|
||||
PaymentPlansResponse.from({
|
||||
plans: [monthlyPlan, lifetimePlan],
|
||||
}),
|
||||
@@ -145,14 +169,85 @@ describe("paymentMachine", () => {
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("loads the tip catalog and disables auto renew", async () => {
|
||||
const loadCachedCatalog = vi.fn();
|
||||
const refreshCatalog = vi.fn();
|
||||
const createOrderSpy = vi.fn<CreateOrderSpy>();
|
||||
const actor = createActor(
|
||||
paymentMachine.provide({
|
||||
actors: {
|
||||
loadCachedPlans: fromPromise<
|
||||
PaymentPlansResponse | null,
|
||||
PaymentPlansActorInput
|
||||
>(async ({ input }) => {
|
||||
loadCachedCatalog(input.catalog);
|
||||
return null;
|
||||
}),
|
||||
refreshPlans: fromPromise<
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansActorInput
|
||||
>(async ({ input }) => {
|
||||
refreshCatalog(input.catalog);
|
||||
return PaymentPlansResponse.from({ plans: [tipPlan] });
|
||||
}),
|
||||
createOrder: fromPromise<
|
||||
CreatePaymentOrderResponse,
|
||||
CreateOrderInput
|
||||
>(async ({ input }) => {
|
||||
createOrderSpy(input);
|
||||
return CreatePaymentOrderResponse.from({
|
||||
orderId: "pay_tip_001",
|
||||
payParams: { url: "https://checkout.example/tip" },
|
||||
});
|
||||
}),
|
||||
pollOrderStatus: fromPromise(async () =>
|
||||
PaymentOrderStatusResponse.from({
|
||||
orderId: "pay_tip_001",
|
||||
status: "paid",
|
||||
orderType: "tip",
|
||||
planId: tipPlan.planId,
|
||||
}),
|
||||
),
|
||||
},
|
||||
}),
|
||||
).start();
|
||||
|
||||
actor.send({ type: "PaymentInit", catalog: "tip" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("ready"));
|
||||
|
||||
expect(loadCachedCatalog).toHaveBeenCalledWith("tip");
|
||||
expect(refreshCatalog).toHaveBeenCalledWith("tip");
|
||||
expect(actor.getSnapshot().context).toMatchObject({
|
||||
planCatalog: "tip",
|
||||
selectedPlanId: "tip_coffee_usd_4_99",
|
||||
autoRenew: false,
|
||||
});
|
||||
|
||||
actor.send({ type: "PaymentCreateOrderSubmitted" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("paid"));
|
||||
expect(createOrderSpy).toHaveBeenCalledWith({
|
||||
planId: "tip_coffee_usd_4_99",
|
||||
payChannel: "stripe",
|
||||
autoRenew: false,
|
||||
});
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("keeps first recharge flag and plan metadata from the plans response", async () => {
|
||||
const actor = createActor(
|
||||
paymentMachine.provide({
|
||||
actors: {
|
||||
loadCachedPlans: fromPromise<PaymentPlansResponse | null>(
|
||||
loadCachedPlans: fromPromise<
|
||||
PaymentPlansResponse | null,
|
||||
PaymentPlansActorInput
|
||||
>(
|
||||
async () => null,
|
||||
),
|
||||
refreshPlans: fromPromise(async () =>
|
||||
refreshPlans: fromPromise<
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansActorInput
|
||||
>(async () =>
|
||||
PaymentPlansResponse.from({
|
||||
isFirstRecharge: true,
|
||||
firstRechargeOffer: {
|
||||
@@ -194,10 +289,16 @@ describe("paymentMachine", () => {
|
||||
const actor = createActor(
|
||||
paymentMachine.provide({
|
||||
actors: {
|
||||
loadCachedPlans: fromPromise<PaymentPlansResponse | null>(
|
||||
loadCachedPlans: fromPromise<
|
||||
PaymentPlansResponse | null,
|
||||
PaymentPlansActorInput
|
||||
>(
|
||||
async () => null,
|
||||
),
|
||||
refreshPlans: fromPromise(async () =>
|
||||
refreshPlans: fromPromise<
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansActorInput
|
||||
>(async () =>
|
||||
PaymentPlansResponse.from({
|
||||
isFirstRecharge: true,
|
||||
firstRechargeOffer: {
|
||||
@@ -243,10 +344,16 @@ describe("paymentMachine", () => {
|
||||
const actor = createActor(
|
||||
paymentMachine.provide({
|
||||
actors: {
|
||||
loadCachedPlans: fromPromise<PaymentPlansResponse | null>(
|
||||
loadCachedPlans: fromPromise<
|
||||
PaymentPlansResponse | null,
|
||||
PaymentPlansActorInput
|
||||
>(
|
||||
async () => null,
|
||||
),
|
||||
refreshPlans: fromPromise(async () =>
|
||||
refreshPlans: fromPromise<
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansActorInput
|
||||
>(async () =>
|
||||
PaymentPlansResponse.from({
|
||||
isFirstRecharge: false,
|
||||
plans: [
|
||||
@@ -285,13 +392,19 @@ describe("paymentMachine", () => {
|
||||
const actor = createActor(
|
||||
paymentMachine.provide({
|
||||
actors: {
|
||||
loadCachedPlans: fromPromise<PaymentPlansResponse | null>(
|
||||
loadCachedPlans: fromPromise<
|
||||
PaymentPlansResponse | null,
|
||||
PaymentPlansActorInput
|
||||
>(
|
||||
async () =>
|
||||
PaymentPlansResponse.from({
|
||||
plans: [quarterlyPlan],
|
||||
}),
|
||||
),
|
||||
refreshPlans: fromPromise<PaymentPlansResponse>(
|
||||
refreshPlans: fromPromise<
|
||||
PaymentPlansResponse,
|
||||
PaymentPlansActorInput
|
||||
>(
|
||||
async () => refreshPlansPromise,
|
||||
),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user