diff --git a/server/src/internal/billing/v2/actions/attach/errors/handleStripeCheckoutErrors.ts b/server/src/internal/billing/v2/actions/attach/errors/handleStripeCheckoutErrors.ts index a2157525d..28b47b6c8 100644 --- a/server/src/internal/billing/v2/actions/attach/errors/handleStripeCheckoutErrors.ts +++ b/server/src/internal/billing/v2/actions/attach/errors/handleStripeCheckoutErrors.ts @@ -53,6 +53,8 @@ export const handleStripeCheckoutErrors = ({ autumnBillingPlan, }); + console.log("recurringIntervals", recurringIntervals); + // If we have more than one unique recurring interval, throw an error if (recurringIntervals.size > 1) { throw new RecaseError({ diff --git a/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-multi-interval.test.ts b/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-multi-interval.test.ts index 2aca0a77e..072812d22 100644 --- a/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-multi-interval.test.ts +++ b/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-multi-interval.test.ts @@ -336,3 +336,48 @@ test.concurrent(`${chalk.yellowBright("stripe-checkout: annual with allocated us latestInvoiceProductId: proAnnual.id, }); }); + +test.concurrent(`${chalk.yellowBright("setup-payment: monthly plan + one-time payment - invoices paid after trial")}`, async () => { + const premium = products.base({ + id: "premium", + items: [ + items.annualPrice({ price: 50 }), + items.oneOffMessages({ price: 100, billingUnits: 100, includedUsage: 0 }), + ], + }); + + const { customerId, autumnV1 } = await initScenario({ + customerId: "setup-payment-monthly-oneoff", + setup: [s.customer({}), s.products({ list: [premium] })], + actions: [], + }); + + // Attach product with quantity option for the one-off prepaid feature + const res = await autumnV1.billing.attach({ + customer_id: customerId, + product_id: premium.id, + redirect_mode: "if_required", + options: [ + { + feature_id: TestFeature.Messages, + quantity: 100, + }, + ], + }); + + await completeStripeCheckoutForm({ url: res.payment_url! }); + + const customer = await autumnV1.customers.get(customerId); + + await expectProductActive({ + customer, + productId: premium.id, + }); + + await expectCustomerFeatureCorrect({ + customer, + featureId: TestFeature.Messages, + balance: 100, + usage: 0, + }); +}); diff --git a/server/tests/integration/billing/stripe-webhooks/checkout-session-completed/setup-payment-after-trial.test.ts b/server/tests/integration/billing/stripe-webhooks/checkout-session-completed/setup-payment-after-trial.test.ts index cd1d28b99..f140095fa 100644 --- a/server/tests/integration/billing/stripe-webhooks/checkout-session-completed/setup-payment-after-trial.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/checkout-session-completed/setup-payment-after-trial.test.ts @@ -1,10 +1,10 @@ import { expect, test } from "bun:test"; +import { FreeTrialDuration } from "@autumn/shared"; import { completeSetupPaymentForm } from "@tests/utils/browserPool"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import { advanceTestClock } from "@tests/utils/stripeUtils.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; -import { FreeTrialDuration } from "@autumn/shared"; import chalk from "chalk"; /** diff --git a/vite/src/components/forms/attach-v2/hooks/useAttachRequestBody.ts b/vite/src/components/forms/attach-v2/hooks/useAttachRequestBody.ts index 541940a29..f4215abbc 100644 --- a/vite/src/components/forms/attach-v2/hooks/useAttachRequestBody.ts +++ b/vite/src/components/forms/attach-v2/hooks/useAttachRequestBody.ts @@ -6,7 +6,7 @@ import { type FreeTrialDuration, type PlanTiming, type ProductItem, - ProductItemInterval, + type ProductItemInterval, type ProductV2, UsageModel, } from "@autumn/shared"; @@ -117,8 +117,7 @@ export function buildAttachRequestBody({ if (items !== null) { body.items = items.map((item) => ({ ...item, - interval: (item.interval || - ProductItemInterval.Month) as ProductItemInterval, + interval: (item.interval ?? null) as ProductItemInterval | null, })); }