fix: mixed schedules
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
||||
type UpdateSubscriptionV1Params,
|
||||
} from "@autumn/shared";
|
||||
import { all } from "better-all";
|
||||
import type Stripe from "stripe";
|
||||
import { stripeSubscriptionToScheduleId } from "@/external/stripe/subscriptions/utils/convertStripeSubscription";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||
import { fetchStripeCustomerForBilling } from "./fetchStripeCustomerForBilling";
|
||||
import { fetchStripeDiscountsForBilling } from "./fetchStripeDiscountsForBilling";
|
||||
@@ -17,6 +19,13 @@ import { fetchStripeSubscriptionForBilling } from "./fetchStripeSubscriptionForB
|
||||
import { fetchStripeSubscriptionScheduleForBilling } from "./fetchStripeSubscriptionScheduleForBilling";
|
||||
import { fetchStripeTaxRateForBilling } from "./fetchStripeTaxRateForBilling";
|
||||
|
||||
const getScheduleSubscriptionId = (
|
||||
stripeSubscriptionSchedule: Stripe.SubscriptionSchedule | undefined,
|
||||
) => {
|
||||
const subscription = stripeSubscriptionSchedule?.subscription;
|
||||
return typeof subscription === "string" ? subscription : subscription?.id;
|
||||
};
|
||||
|
||||
export const setupStripeBillingContext = async ({
|
||||
ctx,
|
||||
fullCustomer,
|
||||
@@ -101,10 +110,9 @@ export const setupStripeBillingContext = async ({
|
||||
? fetchStripeSubscriptionScheduleForBilling({
|
||||
ctx,
|
||||
fullCus: fullCustomer,
|
||||
subscriptionScheduleId:
|
||||
typeof localStripeSubscription?.schedule === "string"
|
||||
? localStripeSubscription.schedule
|
||||
: undefined,
|
||||
subscriptionScheduleId: stripeSubscriptionToScheduleId({
|
||||
stripeSubscription: localStripeSubscription,
|
||||
}),
|
||||
products: [],
|
||||
targetCusProductId: targetCustomerProduct?.id,
|
||||
})
|
||||
@@ -130,9 +138,17 @@ export const setupStripeBillingContext = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const stripeSubscriptionScheduleForContext =
|
||||
stripeSubscription && stripeSubscriptionSchedule
|
||||
? getScheduleSubscriptionId(stripeSubscriptionSchedule) ===
|
||||
stripeSubscription.id
|
||||
? stripeSubscriptionSchedule
|
||||
: undefined
|
||||
: stripeSubscriptionSchedule;
|
||||
|
||||
return {
|
||||
stripeSubscription,
|
||||
stripeSubscriptionSchedule,
|
||||
stripeSubscriptionSchedule: stripeSubscriptionScheduleForContext,
|
||||
stripeCustomer,
|
||||
stripeDiscounts,
|
||||
stripeTaxRate,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { getSubscriptionId } from "@tests/integration/billing/utils/stripe/getSubscriptionId";
|
||||
import { items } from "@tests/utils/fixtures/items";
|
||||
import { products } from "@tests/utils/fixtures/products";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario";
|
||||
import chalk from "chalk";
|
||||
import { isStripeSubscriptionCanceling } from "@/external/stripe/subscriptions/utils/classifyStripeSubscriptionUtils";
|
||||
import { CusService } from "@/internal/customers/CusService";
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("cancel EOC: ignores schedules from other subscriptions")}`, async () => {
|
||||
const customerId = "cancel-eoc-mismatched-schedule";
|
||||
|
||||
const pro = products.pro({
|
||||
id: "pro",
|
||||
items: [items.monthlyMessages({ includedUsage: 100 })],
|
||||
});
|
||||
const monthlyAddon = products.base({
|
||||
id: "monthly-addon",
|
||||
isAddOn: true,
|
||||
items: [items.monthlyPrice({ price: 7 })],
|
||||
});
|
||||
const annualAddon = products.base({
|
||||
id: "annual-addon",
|
||||
isAddOn: true,
|
||||
items: [items.annualPrice({ price: 70 })],
|
||||
});
|
||||
|
||||
const { autumnV1, ctx } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success" }),
|
||||
s.products({ list: [pro, monthlyAddon, annualAddon] }),
|
||||
],
|
||||
actions: [s.billing.attach({ productId: pro.id })],
|
||||
});
|
||||
|
||||
await autumnV1.billing.multiAttach({
|
||||
customer_id: customerId,
|
||||
plans: [{ plan_id: monthlyAddon.id }, { plan_id: annualAddon.id }],
|
||||
new_billing_subscription: true,
|
||||
});
|
||||
|
||||
const proSubId = await getSubscriptionId({
|
||||
ctx,
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
});
|
||||
const monthlyAddonSubId = await getSubscriptionId({
|
||||
ctx,
|
||||
customerId,
|
||||
productId: monthlyAddon.id,
|
||||
});
|
||||
const annualAddonSubId = await getSubscriptionId({
|
||||
ctx,
|
||||
customerId,
|
||||
productId: annualAddon.id,
|
||||
});
|
||||
|
||||
expect(proSubId).not.toBe(monthlyAddonSubId);
|
||||
expect(monthlyAddonSubId).toBe(annualAddonSubId);
|
||||
|
||||
await autumnV1.subscriptions.update({
|
||||
customer_id: customerId,
|
||||
product_id: monthlyAddon.id,
|
||||
cancel_action: "cancel_end_of_cycle",
|
||||
});
|
||||
|
||||
const addonSubWithSchedule =
|
||||
await ctx.stripeCli.subscriptions.retrieve(monthlyAddonSubId);
|
||||
expect(addonSubWithSchedule.schedule).not.toBeNull();
|
||||
const addonScheduleId = addonSubWithSchedule.schedule as string;
|
||||
|
||||
await autumnV1.subscriptions.update({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
cancel_action: "cancel_end_of_cycle",
|
||||
});
|
||||
|
||||
const proSubAfterCancel = await ctx.stripeCli.subscriptions.retrieve(proSubId);
|
||||
const addonSubAfterProCancel =
|
||||
await ctx.stripeCli.subscriptions.retrieve(monthlyAddonSubId);
|
||||
|
||||
expect(isStripeSubscriptionCanceling(proSubAfterCancel)).toBe(true);
|
||||
expect(proSubAfterCancel.schedule).toBeNull();
|
||||
expect(addonSubAfterProCancel.schedule).toBe(addonScheduleId);
|
||||
|
||||
const fullCustomer = await CusService.getFull({ ctx, idOrInternalId: customerId });
|
||||
const proCustomerProduct = fullCustomer.customer_products.find(
|
||||
(cp) => cp.product.id === pro.id,
|
||||
);
|
||||
const monthlyAddonCustomerProduct = fullCustomer.customer_products.find(
|
||||
(cp) => cp.product.id === monthlyAddon.id,
|
||||
);
|
||||
|
||||
expect(proCustomerProduct?.scheduled_ids ?? []).toEqual([]);
|
||||
expect(monthlyAddonCustomerProduct?.scheduled_ids).toEqual([addonScheduleId]);
|
||||
});
|
||||
Reference in New Issue
Block a user