fix: prepaid complex schedules

This commit is contained in:
johnyeo
2026-05-30 11:47:47 +01:00
parent 6af73b7bbe
commit b520562679
13 changed files with 2029 additions and 49 deletions

View File

@@ -14,6 +14,22 @@ mock.module("@server/external/connect/createStripeCli", () => ({
subscriptionSchedules: {
create: async (params: unknown) => {
mockState.createCalls.push(params);
if ((params as { from_subscription?: string }).from_subscription) {
return {
id: "sched_created",
current_phase: { start_date: 1000, end_date: 2000 },
phases: [
{
items: [
{
price: { id: "price_current_inline" },
quantity: 1,
},
],
},
],
};
}
return { id: "sched_created" };
},
release: async (scheduleId: string) => {
@@ -90,6 +106,170 @@ describe("executeStripeSubscriptionScheduleAction", () => {
expect(mockState.createCalls).toEqual([]);
expect(result?.id).toBe("sched_standalone");
});
test("reuses current inline price ids in future phases", async () => {
const inlinePrice = {
product: "stripe_prod_inline",
currency: "usd",
recurring: { interval: "month" as const, interval_count: 1 },
unit_amount_decimal: "1000",
};
const params = {
phases: [
{
start_date: 1000,
items: [{ price_data: inlinePrice, quantity: 1 }],
},
{
start_date: 2000,
items: [
{
price_data: inlinePrice,
quantity: 1,
metadata: {
autumn_customer_price_id: "cus_price_inline",
},
},
],
},
],
end_behavior: "release",
} satisfies Stripe.SubscriptionScheduleUpdateParams;
const stripeSubscription = {
id: "sub_123",
object: "subscription",
items: {
object: "list",
data: [
{
id: "si_inline",
metadata: {
autumn_customer_price_id: "cus_price_inline",
},
price: {
id: "price_current_inline",
object: "price",
product: "stripe_prod_inline",
currency: "usd",
recurring: { interval: "month", interval_count: 1 },
unit_amount_decimal: "1000",
},
},
],
},
} as unknown as Stripe.Subscription;
const billingContext = {
stripeCustomer: { id: "cus_123" },
} as unknown as BillingContext;
await executeStripeSubscriptionScheduleAction({
ctx,
billingContext,
subscriptionScheduleAction: {
type: "create",
params,
},
stripeSubscription,
});
const updateParams = mockState.updateCalls[0] as {
params: Stripe.SubscriptionScheduleUpdateParams;
};
const futureItem = updateParams.params.phases?.[1]?.items?.[0];
expect(futureItem?.price).toBe("price_current_inline");
expect(futureItem?.price_data).toBeUndefined();
expect(futureItem?.metadata?.autumn_customer_price_id).toBe(
"cus_price_inline",
);
});
test("can reuse the same current inline price across separate future phases", async () => {
const inlinePrice = {
product: "stripe_prod_inline",
currency: "usd",
recurring: { interval: "month" as const, interval_count: 1 },
unit_amount_decimal: "1000",
};
const params = {
phases: [
{
start_date: 1000,
items: [{ price_data: inlinePrice, quantity: 1 }],
},
{
start_date: 2000,
end_date: 3000,
items: [
{
price_data: inlinePrice,
quantity: 1,
metadata: {
autumn_customer_price_id: "cus_price_inline",
},
},
],
},
{
start_date: 3000,
items: [
{
price_data: inlinePrice,
quantity: 1,
metadata: {
autumn_customer_price_id: "cus_price_inline",
},
},
],
},
],
end_behavior: "release",
} satisfies Stripe.SubscriptionScheduleUpdateParams;
const stripeSubscription = {
id: "sub_123",
object: "subscription",
items: {
object: "list",
data: [
{
id: "si_inline",
metadata: {
autumn_customer_price_id: "cus_price_inline",
},
price: {
id: "price_current_inline",
object: "price",
product: "stripe_prod_inline",
currency: "usd",
recurring: { interval: "month", interval_count: 1 },
unit_amount_decimal: "1000",
},
},
],
},
} as unknown as Stripe.Subscription;
await executeStripeSubscriptionScheduleAction({
ctx,
billingContext: { stripeCustomer: { id: "cus_123" } } as BillingContext,
subscriptionScheduleAction: {
type: "create",
params,
},
stripeSubscription,
});
const updateParams = mockState.updateCalls[0] as {
params: Stripe.SubscriptionScheduleUpdateParams;
};
expect(updateParams.params.phases?.[1]?.items?.[0]?.price).toBe(
"price_current_inline",
);
expect(updateParams.params.phases?.[2]?.items?.[0]?.price).toBe(
"price_current_inline",
);
});
});
afterAll(() => {