diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 092521eca..171e4db36 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - test/cicd env: DATABASE_URL: ${{ secrets.SUPABASE_URL }} UNIT_TEST_AUTUMN_SECRET_KEY: ${{ secrets.AUTUMN_KEY }} @@ -18,6 +17,7 @@ env: STRIPE_WEBHOOK_URL: ${{ secrets.STRIPE_WEBHOOK_URL }} BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }} HYPERBROWSER_API_KEY: ${{ secrets.HYPERBROWSER_API_KEY }} + STRIPE_TEST_KEY: ${{ secrets.STRIPE_TEST_KEY }} jobs: test: @@ -28,13 +28,12 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up bun - - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@v2 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '22' - cache: 'bun' - name: Install dependencies # run: bun install --no-frozen-lockfile @@ -102,8 +101,9 @@ jobs: echo "TESTS_ORG_ID=${{ env.TESTS_ORG_ID }}" >> .env echo "LOCALTUNNEL_RESERVED_KEY=${{ env.LOCALTUNNEL_RESERVED_KEY }}" >> .env echo "BETTER_AUTH_SECRET=${{ env.BETTER_AUTH_SECRET }}" >> .env - echo "STRIPE_WEBHOOK_URL=${{ env.STRIPE_WEBHOOK_URL }}" >> .env echo "HYPERBROWSER_API_KEY=${{ env.HYPERBROWSER_API_KEY }}" >> .env + echo "STRIPE_TEST_KEY=${{ env.STRIPE_TEST_KEY }}" >> .env + echo "STRIPE_WEBHOOK_URL=${{ env.STRIPE_WEBHOOK_URL }}" >> .env cat .env - name: Build shared @@ -112,7 +112,7 @@ jobs: bun run build - name: Startup server - run: sudo docker compose -f docker/docker-compose.ci.yml up --detach + run: sudo docker compose -f docker-compose.ci.yml up --detach - name: Wait for server to be ready run: | @@ -139,20 +139,23 @@ jobs: chmod +x ./shell/g3.sh ./shell/g3.sh - - name: Run G4 tests - run: | - cd server/ - chmod +x ./shell/g4.sh - ./shell/g4.sh + # - name: Run G4 tests + # run: | + # cd server/ + # chmod +x ./shell/g4.sh + # ./shell/g4.sh - - name: Message - run: - echo "We're all setup!" + # - name: Message + # run: + # echo "We're all setup!" # - name: Check logs # if: always() - # run: sudo docker compose -f docker-compose.unix.yml logs server + # run: sudo docker compose -f docker-compose.ci.yml logs server + - name: Check logs + if: always() + run: sudo docker compose -f docker-compose.ci.yml logs localtunnel - name: Close Docker Containers if: always() - run: sudo docker compose -f docker/docker-compose.unix.yml down \ No newline at end of file + run: sudo docker compose -f docker-compose.ci.yml down \ No newline at end of file diff --git a/docker/docker-compose.ci.yml b/docker-compose.ci.yml similarity index 91% rename from docker/docker-compose.ci.yml rename to docker-compose.ci.yml index 4d4460b0e..47f5725ae 100644 --- a/docker/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -18,7 +18,7 @@ services: shared: build: - dockerfile: ./dev.dockerfile + dockerfile: docker/dev.dockerfile context: . target: shared volumes: @@ -33,7 +33,7 @@ services: # Vite frontend vite: build: - dockerfile: ./dev.dockerfile + dockerfile: docker/dev.dockerfile context: . target: vite ports: @@ -52,7 +52,7 @@ services: # Main Express server server: build: - dockerfile: ./dev.dockerfile + dockerfile: docker/dev.dockerfile context: . target: server ports: @@ -82,7 +82,7 @@ services: # BullMQ Workers workers: build: - dockerfile: ./dev.dockerfile + dockerfile: docker/dev.dockerfile context: . target: workers volumes: @@ -102,11 +102,13 @@ services: # Run localtunnel localtunnel: build: - dockerfile: ./dev.dockerfile + dockerfile: docker/dev.dockerfile context: . target: localtunnel volumes: - ./server:/app/server + environment: + - LOCALTUNNEL_RESERVED_KEY=${LOCALTUNNEL_RESERVED_KEY} depends_on: - server restart: unless-stopped diff --git a/server/shell/g1.sh b/server/shell/g1.sh index df4aa27bc..59f694d60 100755 --- a/server/shell/g1.sh +++ b/server/shell/g1.sh @@ -10,4 +10,6 @@ MOCHA_PARALLEL=true $MOCHA_SETUP \ 'tests/attach/downgrade/*.ts' \ 'tests/attach/free/*.ts' +$MOCHA_CMD 'tests/attach/entities/*.ts' + # 'tests/attach/basic/basic2.ts' \ \ No newline at end of file diff --git a/server/shell/g2.sh b/server/shell/g2.sh index 5ae2b94a7..72c61806a 100755 --- a/server/shell/g2.sh +++ b/server/shell/g2.sh @@ -12,10 +12,10 @@ $MOCHA_CMD \ 'tests/attach/migrations/*.ts' \ 'tests/attach/newVersion/*.ts' \ 'tests/attach/upgradeOld/*.ts' \ -'tests/attach/entities/*.ts' \ 'tests/attach/others/*.ts' \ 'tests/attach/updateEnts/*.ts' \ 'tests/advanced/check/*.ts' \ 'tests/attach/others/*.ts' +# 'tests/attach/entities/*.ts' \ $MOCHA_CMD 'tests/attach/prepaid/*.ts' \ No newline at end of file diff --git a/server/src/external/autumn/autumnCli.ts b/server/src/external/autumn/autumnCli.ts index 44ef1de5b..f462d89f8 100644 --- a/server/src/external/autumn/autumnCli.ts +++ b/server/src/external/autumn/autumnCli.ts @@ -398,6 +398,23 @@ export class AutumnInt { }, }; + stripe = { + connect: async (params: { + testApiKey: string; + liveApiKey: string; + successUrl: string; + defaultCurrency: string; + }) => { + const data = await this.post(`/org/stripe`, params); + return data; + }, + + delete: async () => { + const data = await this.delete(`/org/stripe`); + return data; + }, + }; + track = async (params: TrackParams) => { const data = await this.post(`/track`, params); return data; diff --git a/server/src/internal/api/apiRouter.ts b/server/src/internal/api/apiRouter.ts index f36ac1a54..487ffcd62 100644 --- a/server/src/internal/api/apiRouter.ts +++ b/server/src/internal/api/apiRouter.ts @@ -23,6 +23,8 @@ import expireRouter from "../customers/expire/expireRouter.js"; import { handleSetupPayment } from "../customers/attach/handleSetupPayment.js"; import { internalFeatureRouter } from "../features/internalFeatureRouter.js"; import { analyticsRouter } from "../analytics/analyticsRouter.js"; +import { handleConnectStripe } from "../orgs/handlers/handleConnectStripe.js"; +import { handleDeleteStripe } from "../orgs/handlers/handleDeleteStripe.js"; const apiRouter: Router = Router(); @@ -59,5 +61,7 @@ apiRouter.post("/setup_payment", handleSetupPayment); // Analytics apiRouter.use("/query", analyticsRouter); +apiRouter.post("/org/stripe", handleConnectStripe); +apiRouter.delete("/org/stripe", handleDeleteStripe); export { apiRouter }; diff --git a/server/src/internal/orgs/handlers/handleConnectStripe.ts b/server/src/internal/orgs/handlers/handleConnectStripe.ts new file mode 100644 index 000000000..9861aba96 --- /dev/null +++ b/server/src/internal/orgs/handlers/handleConnectStripe.ts @@ -0,0 +1,112 @@ +import { routeHandler } from "@/utils/routerUtils.js"; +import express, { Router } from "express"; +import Stripe from "stripe"; + +import RecaseError, { handleRequestError } from "@/utils/errorUtils.js"; +import { encryptData } from "@/utils/encryptUtils.js"; + +import { ErrCode } from "@/errors/errCodes.js"; + +import { + checkKeyValid, + createWebhookEndpoint, +} from "@/external/stripe/stripeOnboardingUtils.js"; + +import { OrgService } from "../OrgService.js"; +import { AppEnv } from "@autumn/shared"; +import { nullish } from "@/utils/genUtils.js"; +import { clearOrgCache } from "../orgUtils/clearOrgCache.js"; + +export const handleConnectStripe = async (req: any, res: any) => + routeHandler({ + req, + res, + action: "connect stripe", + handler: async (req: any, res: any) => { + let { testApiKey, liveApiKey, successUrl, defaultCurrency } = req.body; + let { db, orgId, logtail: logger } = req; + if (!testApiKey || !liveApiKey || !successUrl) { + throw new RecaseError({ + message: "Missing required fields", + code: ErrCode.StripeKeyInvalid, + statusCode: 400, + }); + } + + // 1. Check if API keys are valid + try { + await clearOrgCache({ + db, + orgId, + logger, + }); + + await checkKeyValid(testApiKey); + await checkKeyValid(liveApiKey); + + // Get default currency from Stripe + let stripe = new Stripe(testApiKey); + let account = await stripe.accounts.retrieve(); + if (nullish(defaultCurrency) && nullish(account.default_currency)) { + throw new RecaseError({ + message: "Default currency not set", + code: ErrCode.StripeKeyInvalid, + statusCode: 500, + }); + } else if (nullish(defaultCurrency)) { + defaultCurrency = account.default_currency; + } + } catch (error: any) { + console.error("Error checking stripe keys", error); + throw new RecaseError({ + message: error.message || "Invalid Stripe API keys", + code: ErrCode.StripeKeyInvalid, + statusCode: 500, + data: error, + }); + } + // 2. Create webhook endpoint + let testWebhook: Stripe.WebhookEndpoint; + let liveWebhook: Stripe.WebhookEndpoint; + try { + testWebhook = await createWebhookEndpoint( + testApiKey, + AppEnv.Sandbox, + req.orgId + ); + liveWebhook = await createWebhookEndpoint( + liveApiKey, + AppEnv.Live, + req.orgId + ); + } catch (error) { + throw new RecaseError({ + message: "Error creating stripe webhook", + code: ErrCode.StripeKeyInvalid, + statusCode: 500, + data: error, + }); + } + + // 1. Update org in Supabase + await OrgService.update({ + db, + orgId: req.orgId, + updates: { + stripe_connected: true, + default_currency: defaultCurrency, + stripe_config: { + test_api_key: encryptData(testApiKey), + live_api_key: encryptData(liveApiKey), + test_webhook_secret: encryptData(testWebhook.secret as string), + live_webhook_secret: encryptData(liveWebhook.secret as string), + success_url: successUrl, + }, + }, + }); + + res.status(200).json({ + message: "Stripe connected", + }); + }, + }); diff --git a/server/src/internal/orgs/handlers/handleDeleteStripe.ts b/server/src/internal/orgs/handlers/handleDeleteStripe.ts new file mode 100644 index 000000000..00b8e5ea6 --- /dev/null +++ b/server/src/internal/orgs/handlers/handleDeleteStripe.ts @@ -0,0 +1,59 @@ +import { routeHandler } from "@/utils/routerUtils.js"; +import { OrgService } from "../OrgService.js"; +import { createStripeCli } from "@/external/stripe/utils.js"; +import { clearOrgCache } from "../orgUtils/clearOrgCache.js"; +import { AppEnv } from "@autumn/shared"; + +export const handleDeleteStripe = async (req: any, res: any) => + routeHandler({ + req, + res, + action: "delete stripe", + handler: async (req: any, res: any) => { + const org = await OrgService.getFromReq(req); + + let { db, orgId, logtail: logger } = req; + await clearOrgCache({ + db, + orgId, + logger, + }); + + // 2. Delete webhook endpoint + try { + const testStripeCli = createStripeCli({ org, env: AppEnv.Sandbox }); + const liveStripeCli = createStripeCli({ org, env: AppEnv.Live }); + + const testWebhooks = await testStripeCli.webhookEndpoints.list(); + for (const webhook of testWebhooks.data) { + if (webhook.url.includes(org.id)) { + await testStripeCli.webhookEndpoints.del(webhook.id); + } + } + + const liveWebhooks = await liveStripeCli.webhookEndpoints.list(); + for (const webhook of liveWebhooks.data) { + if (webhook.url.includes(org.id)) { + await liveStripeCli.webhookEndpoints.del(webhook.id); + } + } + } catch (error: any) { + console.error("Error deleting stripe webhook(s)"); + console.error(error.message); + } + + await OrgService.update({ + db, + orgId: req.orgId, + updates: { + stripe_connected: false, + stripe_config: null, + default_currency: undefined, + }, + }); + + res.status(200).json({ + message: "Stripe disconnected", + }); + }, + }); diff --git a/server/tests/00_setup.ts b/server/tests/00_setup.ts index e84ca5cfe..acebf86d9 100644 --- a/server/tests/00_setup.ts +++ b/server/tests/00_setup.ts @@ -29,7 +29,6 @@ describe("Initialize org for tests", () => { this.timeout(1000000000); this.org = await clearOrg({ orgSlug: ORG_SLUG, env: DEFAULT_ENV }); this.env = DEFAULT_ENV; - // this.sb = createSupabaseClient(); let { db, client } = initDrizzle(); this.db = db; diff --git a/server/tests/attach/newVersion/newVersion1.ts b/server/tests/attach/newVersion/newVersion1.ts index 507d411c7..130afc7c4 100644 --- a/server/tests/attach/newVersion/newVersion1.ts +++ b/server/tests/attach/newVersion/newVersion1.ts @@ -174,6 +174,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing attach with new version`)}` stripeCli, testClockId, advanceTo: addMonths(curUnix, 1).getTime(), + waitForSeconds: 30, }); await advanceTestClock({ @@ -187,7 +188,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing attach with new version`)}` const invoice = customer.invoices[0]; expect(invoice.total).to.equal( invoiceTotal, - "invoice total after 1 cycle should be correct", + "invoice total after 1 cycle should be correct" ); }); }); diff --git a/server/tests/attach/others/others4.ts b/server/tests/attach/others/others4.ts index fa90a8341..29a18002a 100644 --- a/server/tests/attach/others/others4.ts +++ b/server/tests/attach/others/others4.ts @@ -1,161 +1,161 @@ -import { assert } from "chai"; -import { features, products } from "tests/global.js"; -import { initCustomer } from "@/utils/scriptUtils/initCustomer.js"; -import { getPublicAxiosInstance } from "tests/utils/setup.js"; -import { completeCheckoutForm } from "tests/utils/stripeUtils.js"; -import { timeout } from "tests/utils/genUtils.js"; -import { ErrCode } from "@autumn/shared"; -import { compareMainProduct } from "tests/utils/compare.js"; -import { AutumnCli } from "tests/cli/AutumnCli.js"; -import chalk from "chalk"; -import { setupBefore } from "tests/before.js"; +// import { assert } from "chai"; +// import { features, products } from "tests/global.js"; +// import { initCustomer } from "@/utils/scriptUtils/initCustomer.js"; +// import { getPublicAxiosInstance } from "tests/utils/setup.js"; +// import { completeCheckoutForm } from "tests/utils/stripeUtils.js"; +// import { timeout } from "tests/utils/genUtils.js"; +// import { ErrCode } from "@autumn/shared"; +// import { compareMainProduct } from "tests/utils/compare.js"; +// import { AutumnCli } from "tests/cli/AutumnCli.js"; +// import chalk from "chalk"; +// import { setupBefore } from "tests/before.js"; -const testCase = "others4"; -describe(`${chalk.yellowBright("others4: Testing publishable key")}`, () => { - // 1. Initialize customer with card - let customerId = testCase; - const bearerPublicAxios = getPublicAxiosInstance({ - withBearer: true, - }); +// const testCase = "others4"; +// describe(`${chalk.yellowBright("others4: Testing publishable key")}`, () => { +// // 1. Initialize customer with card +// let customerId = testCase; +// const bearerPublicAxios = getPublicAxiosInstance({ +// withBearer: true, +// }); - before(async function () { - await setupBefore(this); - await initCustomer({ - customerId, - db: this.db, - org: this.org, - env: this.env, - autumn: this.autumnJs, - }); - }); +// before(async function () { +// await setupBefore(this); +// await initCustomer({ +// customerId, +// db: this.db, +// org: this.org, +// env: this.env, +// autumn: this.autumnJs, +// }); +// }); - it("should return a 401 if the pkey is invalid", async function () { - this.timeout(30000); - const axiosInstance = getPublicAxiosInstance({ - withBearer: true, - pkey: "am_pk_test_invalid", - }); +// it("should return a 401 if the pkey is invalid", async function () { +// this.timeout(30000); +// const axiosInstance = getPublicAxiosInstance({ +// withBearer: true, +// pkey: "am_pk_test_invalid", +// }); - try { - const { data } = await axiosInstance.post("/v1/attach", { - customer_id: customerId, - product_id: products.pro.id, - }); +// try { +// const { data } = await axiosInstance.post("/v1/attach", { +// customer_id: customerId, +// product_id: products.pro.id, +// }); - throw new Error("Should not be able to attach"); - } catch (error: any) { - assert.equal(error.response.status, 401); - } - }); +// throw new Error("Should not be able to attach"); +// } catch (error: any) { +// assert.equal(error.response.status, 401); +// } +// }); - it("should return checkout URL for both bearer key", async function () { - this.timeout(30000); - const axiosInstanceBearer = getPublicAxiosInstance({ - withBearer: true, - }); +// it("should return checkout URL for both bearer key", async function () { +// this.timeout(30000); +// const axiosInstanceBearer = getPublicAxiosInstance({ +// withBearer: true, +// }); - // 1. Should be able to upgrade to pro - const { data } = await axiosInstanceBearer.post("/v1/attach", { - customer_id: customerId, - product_id: products.pro.id, - }); +// // 1. Should be able to upgrade to pro +// const { data } = await axiosInstanceBearer.post("/v1/attach", { +// customer_id: customerId, +// product_id: products.pro.id, +// }); - assert.exists(data.checkout_url); +// assert.exists(data.checkout_url); - await completeCheckoutForm(data.checkout_url); +// await completeCheckoutForm(data.checkout_url); - await timeout(5000); - }); +// await timeout(5000); +// }); - it("should have customer with product", async function () { - this.timeout(30000); - const res = await AutumnCli.getCustomer(customerId); - compareMainProduct({ - sent: products.pro, - cusRes: res, - }); - }); +// it("should have customer with product", async function () { +// this.timeout(30000); +// const res = await AutumnCli.getCustomer(customerId); +// compareMainProduct({ +// sent: products.pro, +// cusRes: res, +// }); +// }); - it("should return error if try to upgrade or downgrade without pkey", async function () { - this.timeout(30000); - const axiosInstance = getPublicAxiosInstance({ - withBearer: true, - }); +// it("should return error if try to upgrade or downgrade without pkey", async function () { +// this.timeout(30000); +// const axiosInstance = getPublicAxiosInstance({ +// withBearer: true, +// }); - try { - await axiosInstance.post("/v1/attach", { - customer_id: customerId, - product_id: products.premium.id, - }); +// try { +// await axiosInstance.post("/v1/attach", { +// customer_id: customerId, +// product_id: products.premium.id, +// }); - throw new Error("Should not be able to attach"); - } catch (error: any) { - assert.equal(error.response.status, 400); - assert.equal(error.response.data.code, ErrCode.InvalidRequest); - } - }); +// throw new Error("Should not be able to attach"); +// } catch (error: any) { +// assert.equal(error.response.status, 400); +// assert.equal(error.response.data.code, ErrCode.InvalidRequest); +// } +// }); - it("should return error if try to downgrade to free", async function () { - try { - await bearerPublicAxios.post("/v1/attach", { - customer_id: customerId, - product_id: products.free.id, - }); +// it("should return error if try to downgrade to free", async function () { +// try { +// await bearerPublicAxios.post("/v1/attach", { +// customer_id: customerId, +// product_id: products.free.id, +// }); - throw new Error("Should not be able to attach"); - } catch (error: any) { - assert.equal(error.response.status, 400); - assert.equal(error.response.data.code, ErrCode.InvalidRequest); - } - }); +// throw new Error("Should not be able to attach"); +// } catch (error: any) { +// assert.equal(error.response.status, 400); +// assert.equal(error.response.data.code, ErrCode.InvalidRequest); +// } +// }); - // Next, check entitled for pro - it("should return correct metered1 amount for pro", async function () { - const { data } = await bearerPublicAxios.post("/v1/entitled", { - customer_id: customerId, - feature_id: features.metered1.id, - }); +// // Next, check entitled for pro +// it("should return correct metered1 amount for pro", async function () { +// const { data } = await bearerPublicAxios.post("/v1/entitled", { +// customer_id: customerId, +// feature_id: features.metered1.id, +// }); - assert.equal(data.allowed, true); - const metered1Balance = data.balances.find( - (b: any) => b.feature_id === features.metered1.id, - ); - assert.equal( - metered1Balance.balance, - products.pro.entitlements.metered1.allowance, - ); - }); +// assert.equal(data.allowed, true); +// const metered1Balance = data.balances.find( +// (b: any) => b.feature_id === features.metered1.id, +// ); +// assert.equal( +// metered1Balance.balance, +// products.pro.entitlements.metered1.allowance, +// ); +// }); - it("should return same balance for entitled with bearer and x-publishable-key", async function () { - const { data } = await bearerPublicAxios.post("/v1/entitled", { - customer_id: customerId, - feature_id: features.metered1.id, - }); +// it("should return same balance for entitled with bearer and x-publishable-key", async function () { +// const { data } = await bearerPublicAxios.post("/v1/entitled", { +// customer_id: customerId, +// feature_id: features.metered1.id, +// }); - assert.equal(data.allowed, true); - const metered1Balance = data.balances.find( - (b: any) => b.feature_id === features.metered1.id, - ); - assert.equal( - metered1Balance.balance, - products.pro.entitlements.metered1.allowance, - ); - }); +// assert.equal(data.allowed, true); +// const metered1Balance = data.balances.find( +// (b: any) => b.feature_id === features.metered1.id, +// ); +// assert.equal( +// metered1Balance.balance, +// products.pro.entitlements.metered1.allowance, +// ); +// }); - it("should return error when try to send event", async function () { - try { - await bearerPublicAxios.post("/v1/events", { - customer_id: customerId, - event_name: features.metered1.id, - properties: { - value: 10, - }, - }); +// it("should return error when try to send event", async function () { +// try { +// await bearerPublicAxios.post("/v1/events", { +// customer_id: customerId, +// event_name: features.metered1.id, +// properties: { +// value: 10, +// }, +// }); - throw new Error("Should not be able to send event"); - } catch (error: any) { - assert.equal(error.response.status, 401); - assert.equal(error.response.data.code, ErrCode.EndpointNotPublic); - } - }); -}); +// throw new Error("Should not be able to send event"); +// } catch (error: any) { +// assert.equal(error.response.status, 401); +// assert.equal(error.response.data.code, ErrCode.EndpointNotPublic); +// } +// }); +// }); diff --git a/server/tests/attach/prepaid/prepaid2.ts b/server/tests/attach/prepaid/prepaid2.ts index d76ad375a..340e4823c 100644 --- a/server/tests/attach/prepaid/prepaid2.ts +++ b/server/tests/attach/prepaid/prepaid2.ts @@ -120,7 +120,7 @@ describe(`${chalk.yellowBright(`attach/${testCase}: upgrade quantity, prorate im stripeCli, testClockId, advanceTo: addWeeks(new Date(), 2).getTime(), - waitForSeconds: 10, + waitForSeconds: 30, }); await attachAndExpectCorrect({ diff --git a/server/tests/attach/prepaid/prepaid4.ts b/server/tests/attach/prepaid/prepaid4.ts index cfc6a8e89..91b2853e0 100644 --- a/server/tests/attach/prepaid/prepaid4.ts +++ b/server/tests/attach/prepaid/prepaid4.ts @@ -125,7 +125,7 @@ describe(`${chalk.yellowBright(`attach/${testCase}: Testing prepaid reset`)}`, ( const customer = await autumn.customers.get(customerId); const newBalance = options[0].quantity - usage; expect(customer.features[TestFeature.Messages].balance).to.equal( - newBalance, + newBalance ); }); @@ -135,14 +135,14 @@ describe(`${chalk.yellowBright(`attach/${testCase}: Testing prepaid reset`)}`, ( testClockId, advanceTo: addHours( addMonths(new Date(), 1), - hoursToFinalizeInvoice, + hoursToFinalizeInvoice ).getTime(), - waitForSeconds: 25, + waitForSeconds: 30, }); const customer = await autumn.customers.get(customerId); expect(customer.features[TestFeature.Messages].balance).to.equal( - options[0].quantity, + options[0].quantity ); }); }); diff --git a/server/tests/utils/setup.ts b/server/tests/utils/setup.ts index c53e93931..ca1b7fb6d 100644 --- a/server/tests/utils/setup.ts +++ b/server/tests/utils/setup.ts @@ -76,6 +76,27 @@ export const clearOrg = async ({ process.exit(1); } + const autumn = new AutumnInt(); + + if (process.env.STRIPE_TEST_KEY) { + console.log(`Reconnecting stripe...`); + + try { + await autumn.stripe.delete(); + } catch (error) {} + try { + await autumn.stripe.connect({ + testApiKey: process.env.STRIPE_TEST_KEY!, + liveApiKey: process.env.STRIPE_TEST_KEY!, + successUrl: "https://useautumn.com", + defaultCurrency: "usd", + }); + } catch (error: any) { + console.error("Error reconnecting stripe", error.message); + process.exit(1); + } + } + const { db, client } = initDrizzle(); const org = await OrgService.getBySlug({ db, slug: orgSlug });