From 47d14b0bfc109305ebe1f822e7dc2bfb5ffb0d91 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Wed, 18 Jun 2025 17:24:40 +0100 Subject: [PATCH] fix: expiring product in cancel handler instead of waiting for stripe webhook --- server/src/external/autumn/autumnCli.ts | 2 +- .../handlers/handleCusProductExpired.ts | 31 +++-- server/tests/attach/upgrade/upgrade7.ts | 113 ++++++++++++++++++ 3 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 server/tests/attach/upgrade/upgrade7.ts diff --git a/server/src/external/autumn/autumnCli.ts b/server/src/external/autumn/autumnCli.ts index c4e6e78c1..e633f8dca 100644 --- a/server/src/external/autumn/autumnCli.ts +++ b/server/src/external/autumn/autumnCli.ts @@ -418,7 +418,7 @@ export class AutumnInt { return data; }; - cancel = async (params: CancelParams & { expire_immediately?: boolean }) => { + cancel = async (params: CancelParams) => { const data = await this.post(`/cancel`, params); return data; }; diff --git a/server/src/internal/customers/handlers/handleCusProductExpired.ts b/server/src/internal/customers/handlers/handleCusProductExpired.ts index a611dc3b2..936064a70 100644 --- a/server/src/internal/customers/handlers/handleCusProductExpired.ts +++ b/server/src/internal/customers/handlers/handleCusProductExpired.ts @@ -183,7 +183,18 @@ export const expireCusProduct = async ({ // For regular products // 1. Cancel stripe subscriptions - const cancelled = await cancelCusProductSubscriptions({ + + logger.info(`Expiring current product: ${cusProduct.product.name}`); + await expireAndActivate({ + db, + env, + cusProduct, + org, + logger, + }); + + logger.info(`Cancelling stripe subscriptions`); + await cancelCusProductSubscriptions({ cusProduct, org, env, @@ -191,15 +202,15 @@ export const expireCusProduct = async ({ prorate, }); - if (!cancelled) { - await expireAndActivate({ - db, - env, - cusProduct, - org, - logger, - }); - } // else will be handled by webhook + // if (!cancelled) { + // await expireAndActivate({ + // db, + // env, + // cusProduct, + // org, + // logger, + // }); + // } // else will be handled by webhook return; }; diff --git a/server/tests/attach/upgrade/upgrade7.ts b/server/tests/attach/upgrade/upgrade7.ts new file mode 100644 index 000000000..62edad716 --- /dev/null +++ b/server/tests/attach/upgrade/upgrade7.ts @@ -0,0 +1,113 @@ +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { initCustomer } from "@/utils/scriptUtils/initCustomer.js"; +import { APIVersion, AppEnv, Organization } from "@autumn/shared"; +import chalk from "chalk"; +import Stripe from "stripe"; +import { DrizzleCli } from "@/db/initDrizzle.js"; +import { setupBefore } from "tests/before.js"; +import { createProducts } from "tests/utils/productUtils.js"; +import { addPrefixToProducts, runAttachTest } from "../utils.js"; +import { + constructArrearItem, + constructArrearProratedItem, +} from "@/utils/scriptUtils/constructItem.js"; +import { TestFeature } from "tests/setup/v2Features.js"; +import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; + +import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js"; +import { attachFailedPaymentMethod } from "@/external/stripe/stripeCusUtils.js"; +import { CusService } from "@/internal/customers/CusService.js"; +import { timeout } from "@/utils/genUtils.js"; +import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js"; +import { expectSubItemsCorrect } from "tests/utils/expectUtils/expectSubUtils.js"; +import { expectFeaturesCorrect } from "tests/utils/expectUtils/expectFeaturesCorrect.js"; + +const testCase = "upgrade7"; + +export let pro = constructProduct({ + items: [], + type: "pro", +}); + +export let premium = constructProduct({ + items: [], + type: "premium", +}); + +describe(`${chalk.yellowBright(`${testCase}: Testing upgrade via cancel + attach`)}`, () => { + let customerId = testCase; + let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 }); + let testClockId: string; + let db: DrizzleCli, org: Organization, env: AppEnv; + let stripeCli: Stripe; + + before(async function () { + await setupBefore(this); + const { autumnJs } = this; + db = this.db; + org = this.org; + env = this.env; + + stripeCli = this.stripeCli; + + const { testClockId: testClockId1 } = await initCustomer({ + autumn: autumnJs, + customerId, + db, + org, + env, + attachPm: "success", + }); + + addPrefixToProducts({ + products: [pro, premium], + prefix: testCase, + }); + + await createProducts({ + autumn, + products: [pro, premium], + db, + orgId: org.id, + env, + }); + + testClockId = testClockId1!; + }); + + it("should attach pro product", async function () { + await runAttachTest({ + autumn, + customerId, + product: pro, + stripeCli, + db, + org, + env, + }); + }); + + it("should cancel than attach premium product", async function () { + await autumn.cancel({ + customer_id: customerId, + product_id: pro.id, + cancel_immediately: true, + }); + + await autumn.attach({ + customer_id: customerId, + product_id: premium.id, + force_checkout: true, + }); + + // await runAttachTest({ + // autumn, + // customerId, + // product: premium, + // stripeCli, + // db, + // org, + // env, + // }); + }); +});