From 6a562422dfc689d46301d608c5dddc9a28e27ad3 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Feb 2026 19:40:10 +0000 Subject: [PATCH] fix: type errors --- .../createStripePrice/createStripeInArrear.ts | 14 +- .../createStripePrice/createStripePrepaid.ts | 17 +- .../src/external/stripe/stripePriceUtils.ts | 2 +- server/src/utils/scriptUtils/constructItem.ts | 5 +- .../immediate-switch-prepaid-volume.test.ts | 4 +- .../new-plan/attach-prepaid-volume.test.ts | 95 +++++++ .../update-paid-billing-method.test.ts | 2 +- .../update-paid-tier-behavior.test.ts | 235 ++++++++++++++++++ server/tests/utils/fixtures/items.ts | 26 ++ .../lineItemUtils/volumeTiersToLineAmount.ts | 2 +- .../priceToStripeCreatePriceParams.ts | 6 +- .../convertPrice/priceToStripeTiersMode.ts | 7 + shared/utils/productUtils/priceUtils/index.ts | 3 + .../productItemToPlanItemParamsV1.ts | 1 + 14 files changed, 395 insertions(+), 24 deletions(-) create mode 100644 server/tests/integration/billing/update-subscription/custom-plan/update-paid-tier-behavior.test.ts create mode 100644 shared/utils/productUtils/priceUtils/convertPrice/priceToStripeTiersMode.ts diff --git a/server/src/external/stripe/createStripePrice/createStripeInArrear.ts b/server/src/external/stripe/createStripePrice/createStripeInArrear.ts index 64bcac016..315f86f96 100644 --- a/server/src/external/stripe/createStripePrice/createStripeInArrear.ts +++ b/server/src/external/stripe/createStripePrice/createStripeInArrear.ts @@ -271,12 +271,14 @@ export const createStripeInArrearPrice = async ({ ...productData, ...priceAmountData, currency: org.default_currency || "usd", - recurring: { - interval: recurringData.interval, - interval_count: recurringData.interval_count, - meter: meter.id, - usage_type: "metered", - }, + recurring: recurringData?.interval + ? { + interval: recurringData.interval, + interval_count: recurringData.interval_count, + meter: meter.id, + usage_type: "metered", + } + : undefined, nickname: `Autumn Price (${relatedEnt.feature.name})`, }); diff --git a/server/src/external/stripe/createStripePrice/createStripePrepaid.ts b/server/src/external/stripe/createStripePrice/createStripePrepaid.ts index e6d3eeb3a..752b1bf25 100644 --- a/server/src/external/stripe/createStripePrice/createStripePrepaid.ts +++ b/server/src/external/stripe/createStripePrice/createStripePrepaid.ts @@ -5,7 +5,7 @@ import { type Organization, type Price, type Product, - TierBehavior, + priceToStripeTiersMode, TierInfinite, type UsagePriceConfig, } from "@autumn/shared"; @@ -71,12 +71,14 @@ export const createStripePrepaid = async ({ }) => { const relatedEnt = getPriceEntitlement(price, entitlements); - let recurringData; + let recurringData: Partial | undefined; if (price.config!.interval !== BillingInterval.OneOff) { - recurringData = billingIntervalToStripe({ - interval: price.config!.interval, - intervalCount: price.config!.interval_count, - }); + recurringData = { + ...billingIntervalToStripe({ + interval: price.config!.interval, + intervalCount: price.config!.interval_count, + }), + }; } const config = price.config as UsagePriceConfig; @@ -114,8 +116,7 @@ export const createStripePrepaid = async ({ config.stripe_price_id = stripePrice.id; } else { const tiers = prepaidToStripeTiers({ price, org }); - const tiersMode = - price.tier_behavior === TierBehavior.VolumeBased ? "volume" : "graduated"; + const tiersMode = priceToStripeTiersMode({ price }); let priceAmountData = {}; if (tiers.length === 1) { diff --git a/server/src/external/stripe/stripePriceUtils.ts b/server/src/external/stripe/stripePriceUtils.ts index f63e4fb8c..6067b9ca0 100644 --- a/server/src/external/stripe/stripePriceUtils.ts +++ b/server/src/external/stripe/stripePriceUtils.ts @@ -25,7 +25,7 @@ export const billingIntervalToStripe = ({ }: { interval: BillingInterval; intervalCount?: number | null; -}): Stripe.PriceCreateParams.Recurring | Record => { +}): Partial => { const finalCount = intervalCount ?? 1; switch (interval) { case BillingInterval.Week: diff --git a/server/src/utils/scriptUtils/constructItem.ts b/server/src/utils/scriptUtils/constructItem.ts index 622f0c9bb..2ebbd22dc 100644 --- a/server/src/utils/scriptUtils/constructItem.ts +++ b/server/src/utils/scriptUtils/constructItem.ts @@ -126,6 +126,7 @@ export const constructArrearItem = ({ featureId, includedUsage = 10000, price = 0.1, + tiers, billingUnits = 1000, config = { on_increase: OnIncrease.ProrateImmediately, @@ -140,6 +141,7 @@ export const constructArrearItem = ({ featureId: string; includedUsage?: number; price?: number; + tiers?: { amount: number; to: number | "inf" }[]; billingUnits?: number; config?: ProductItemConfig; rolloverConfig?: RolloverConfig; @@ -152,7 +154,8 @@ export const constructArrearItem = ({ feature_id: featureId, usage_model: UsageModel.PayPerUse, included_usage: includedUsage, - price: price, + price: tiers ? undefined : price, + tiers: tiers, billing_units: billingUnits, interval: interval, interval_count: intervalCount, diff --git a/server/tests/integration/billing/attach/immediate-switch/immediate-switch-prepaid-volume.test.ts b/server/tests/integration/billing/attach/immediate-switch/immediate-switch-prepaid-volume.test.ts index c3f5d9696..9c96b8e3f 100644 --- a/server/tests/integration/billing/attach/immediate-switch/immediate-switch-prepaid-volume.test.ts +++ b/server/tests/integration/billing/attach/immediate-switch/immediate-switch-prepaid-volume.test.ts @@ -100,7 +100,7 @@ test.concurrent(`${chalk.yellowBright("attach-prepaid-volume: immediate switch, product_id: premium.id, options: [{ feature_id: TestFeature.Messages, quantity: newQuantity }], }); - expect(preview.total).toBeGreaterThan(0); + expect(preview.total).toEqual(30); await autumnV1.billing.attach({ customer_id: customerId, @@ -201,7 +201,7 @@ test.concurrent(`${chalk.yellowBright("attach-prepaid-volume: immediate switch, product_id: premium.id, options: [{ feature_id: TestFeature.Messages, quantity: newQuantity }], }); - expect(preview.total).toBeGreaterThan(0); + expect(preview.total).toEqual(10 + 30); await autumnV1.billing.attach({ customer_id: customerId, diff --git a/server/tests/integration/billing/attach/new-plan/attach-prepaid-volume.test.ts b/server/tests/integration/billing/attach/new-plan/attach-prepaid-volume.test.ts index 2e43a64a5..84e9d6770 100644 --- a/server/tests/integration/billing/attach/new-plan/attach-prepaid-volume.test.ts +++ b/server/tests/integration/billing/attach/new-plan/attach-prepaid-volume.test.ts @@ -327,3 +327,98 @@ test.concurrent(`${chalk.yellowBright("attach-prepaid-volume: quantity 0 → no latestTotal: BASE_PRICE, }); }); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 5: 4 tiers with included usage — volume pricing at correct tier +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * 4 tiers (billingUnits = 100): + * Tier 1: 0-200 units @ $15/pack + * Tier 2: 201-500 units @ $10/pack + * Tier 3: 501-1000 units @ $7/pack + * Tier 4: 1001+ units @ $5/pack + * + * With 100 included (free) and 900 total quantity: + * Paid packs: (900 - 100) / 100 = 8 packs + * Volume: all 8 packs at tier 3 rate ($7) = $56 + * Total: $20 base + $56 = $76 + */ +test.concurrent(`${chalk.yellowBright("attach-prepaid-volume: 4 tiers, 100 included, 900 total → $76")}`, async () => { + const customerId = "attach-prepaid-volume-4tier-incl"; + const quantity = 900; + const includedUsage = 100; + + // 4-tier pricing structure + const fourTiers = [ + { to: 200, amount: 15 }, + { to: 500, amount: 10 }, + { to: 1000, amount: 7 }, + { to: "inf" as const, amount: 5 }, + ]; + + // Paid packs after free: (900 - 100) / 100 = 8 packs + // 8 packs falls into tier 3 (800 paid units = 501-1000 range) + // Volume pricing: all 8 packs at $7 = $56 + const expectedPrepaidCost = 8 * 7; + + const volumeItem = items.volumePrepaidMessages({ + includedUsage, + billingUnits: BILLING_UNITS, + tiers: fourTiers, + }); + + const pro = products.pro({ + id: "pro-volume-4tier", + items: [volumeItem], + }); + + const { autumnV1, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [pro] }), + ], + actions: [], + }); + + // Preview must reflect volume pricing at tier 3 + const preview = await autumnV1.billing.previewAttach({ + customer_id: customerId, + product_id: pro.id, + options: [{ feature_id: TestFeature.Messages, quantity }], + }); + expect(preview.total).toBe(BASE_PRICE + expectedPrepaidCost); + + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: pro.id, + options: [{ feature_id: TestFeature.Messages, quantity }], + redirect_mode: "if_required", + }); + + const customer = await autumnV1.customers.get(customerId); + + await expectProductActive({ customer, productId: pro.id }); + + // Balance should equal total quantity (free + paid) + expectCustomerFeatureCorrect({ + customer, + featureId: TestFeature.Messages, + balance: quantity, + usage: 0, + }); + + await expectCustomerInvoiceCorrect({ + customer, + count: 1, + latestTotal: BASE_PRICE + expectedPrepaidCost, + }); + + await expectSubToBeCorrect({ + db: ctx.db, + customerId, + org: ctx.org, + env: ctx.env, + }); +}); diff --git a/server/tests/integration/billing/update-subscription/custom-plan/update-paid-billing-method.test.ts b/server/tests/integration/billing/update-subscription/custom-plan/update-paid-billing-method.test.ts index 42ddac947..e39303841 100644 --- a/server/tests/integration/billing/update-subscription/custom-plan/update-paid-billing-method.test.ts +++ b/server/tests/integration/billing/update-subscription/custom-plan/update-paid-billing-method.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import type { ApiCustomerV3 } from "@autumn/shared"; +import { type ApiCustomerV3 } from "@autumn/shared"; import { expectCustomerFeatureCorrect } from "@tests/integration/billing/utils/expectCustomerFeatureCorrect"; import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect"; import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect"; diff --git a/server/tests/integration/billing/update-subscription/custom-plan/update-paid-tier-behavior.test.ts b/server/tests/integration/billing/update-subscription/custom-plan/update-paid-tier-behavior.test.ts new file mode 100644 index 000000000..b3fad383f --- /dev/null +++ b/server/tests/integration/billing/update-subscription/custom-plan/update-paid-tier-behavior.test.ts @@ -0,0 +1,235 @@ +import { expect, test } from "bun:test"; +import type { ApiCustomerV3 } from "@autumn/shared"; +import { expectCustomerFeatureCorrect } from "@tests/integration/billing/utils/expectCustomerFeatureCorrect"; +import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect"; +import { calculateProration } from "@tests/integration/billing/utils/proration/calculateProration"; +import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect"; +import { TestFeature } from "@tests/setup/v2Features"; +import { items } from "@tests/utils/fixtures/items"; +import { products } from "@tests/utils/fixtures/products"; +import { advanceTestClock } from "@tests/utils/stripeUtils"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; +import chalk from "chalk"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// PAID-TO-PAID: TIER BEHAVIOR TRANSITIONS +// ═══════════════════════════════════════════════════════════════════════════════ + +const BILLING_UNITS = 100; + +// Tiers for prepaid pricing tests (same for graduated and volume) +const PREPAID_TIERS = [ + { to: 500, amount: 10 }, + { to: "inf" as const, amount: 5 }, +]; + +// 4.8 Graduated prepaid to volume prepaid (mid-cycle with proration) +test.concurrent(`${chalk.yellowBright("p2p: graduated prepaid to volume prepaid (mid-cycle proration)")}`, async () => { + const quantity = 800; + const basePrice = 20; + + // Graduated calculation: 5 packs × $10 + 3 packs × $5 = $65 + const graduatedCost = 5 * 10 + 3 * 5; + + // Volume calculation: 8 packs × $5 (tier 2 rate for all) = $40 + const volumeCost = 8 * 5; + + const graduatedItem = items.tieredPrepaidMessages({ + includedUsage: 0, + billingUnits: BILLING_UNITS, + tiers: PREPAID_TIERS, + }); + const priceItem = items.monthlyPrice({ price: basePrice }); + const pro = products.base({ id: "pro", items: [graduatedItem, priceItem] }); + + const { customerId, autumnV1, ctx, testClockId } = await initScenario({ + customerId: "p2p-grad-to-vol-proration", + setup: [ + s.customer({ paymentMethod: "success", testClock: true }), + s.products({ list: [pro] }), + ], + actions: [ + s.attach({ + productId: "pro", + options: [{ feature_id: TestFeature.Messages, quantity }], + }), + ], + }); + + // Verify initial invoice (base price + graduated prepaid) + const initialCustomer = + await autumnV1.customers.get(customerId); + await expectCustomerInvoiceCorrect({ + customer: initialCustomer, + count: 1, + latestTotal: basePrice + graduatedCost, + }); + + // Advance 15 days (mid-cycle) + const advancedTo = await advanceTestClock({ + stripeCli: ctx.stripeCli, + testClockId: testClockId!, + numberOfDays: 15, + }); + + // Change to volume prepaid with same quantity + const volumeItem = items.volumePrepaidMessages({ + includedUsage: 0, + billingUnits: BILLING_UNITS, + tiers: PREPAID_TIERS, + }); + + const updateParams = { + customer_id: customerId, + product_id: pro.id, + items: [volumeItem, priceItem], + }; + + const preview = await autumnV1.subscriptions.previewUpdate(updateParams); + + // Calculate prorated difference: credit old graduated, charge new volume + const proratedGraduated = await calculateProration({ + customerId, + advancedTo, + amount: graduatedCost, + }); + const proratedVolume = await calculateProration({ + customerId, + advancedTo, + amount: volumeCost, + }); + + // Volume is cheaper, so this should be a credit (negative) + const expectedAmount = proratedVolume - proratedGraduated; + + expect(preview.total).toEqual(expectedAmount); + + await autumnV1.subscriptions.update(updateParams); + + const customer = await autumnV1.customers.get(customerId); + + // Balance should remain at 800 (quantity unchanged) + expectCustomerFeatureCorrect({ + customer, + featureId: TestFeature.Messages, + balance: quantity, + usage: 0, + }); + + await expectCustomerInvoiceCorrect({ + customer, + count: 2, + latestTotal: preview.total, + }); + + await expectSubToBeCorrect({ + db: ctx.db, + customerId, + org: ctx.org, + env: ctx.env, + }); +}); + +// Tiers for consumable pricing tests +const CONSUMABLE_TIERS = [ + { to: 500, amount: 0.1 }, + { to: "inf" as const, amount: 0.05 }, +]; + +// 4.9 Volume prepaid to graduated tiered consumable +test.concurrent(`${chalk.yellowBright("p2p: volume prepaid to graduated tiered consumable")}`, async () => { + const quantity = 800; + const basePrice = 20; + + // Volume prepaid: 8 packs × $5 = $40 + const volumeCost = 8 * 5; + + const volumeItem = items.volumePrepaidMessages({ + includedUsage: 0, + billingUnits: BILLING_UNITS, + tiers: PREPAID_TIERS, + }); + const priceItem = items.monthlyPrice({ price: basePrice }); + const pro = products.base({ id: "pro", items: [volumeItem, priceItem] }); + + const { customerId, autumnV1, ctx } = await initScenario({ + customerId: "p2p-vol-to-tiered-cons", + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [pro] }), + ], + actions: [ + s.attach({ + productId: "pro", + options: [{ feature_id: TestFeature.Messages, quantity }], + }), + ], + }); + + // Track some usage (600 of 800 = 200 remaining) + const messagesUsage = 600; + await autumnV1.track( + { + customer_id: customerId, + feature_id: TestFeature.Messages, + value: messagesUsage, + }, + { timeout: 2000 }, + ); + + // Verify customer has usage tracked + const customerBefore = + await autumnV1.customers.get(customerId); + expect(customerBefore.features[TestFeature.Messages].usage).toBe( + messagesUsage, + ); + + // Change to tiered consumable with 100 included + // tier_behavior should be undefined (defaults to graduated) + const tieredConsumableItem = items.tieredConsumableMessages({ + includedUsage: 100, + billingUnits: 1, + tiers: CONSUMABLE_TIERS, + }); + + // Verify the item has no tier_behavior set (defaults to graduated) + expect(tieredConsumableItem.tier_behavior).toBeUndefined(); + + const updateParams = { + customer_id: customerId, + product_id: pro.id, + items: [tieredConsumableItem, priceItem], + }; + + const preview = await autumnV1.subscriptions.previewUpdate(updateParams); + + // Should refund full prepaid amount ($40) + expect(preview.total).toBe(-volumeCost); + + await autumnV1.subscriptions.update(updateParams); + + const customer = await autumnV1.customers.get(customerId); + + // Usage should be preserved (600 tracked) + // New included usage is 100, so balance = 100 - 600 = -500 (in overage) + expectCustomerFeatureCorrect({ + customer, + featureId: TestFeature.Messages, + includedUsage: tieredConsumableItem.included_usage, + balance: tieredConsumableItem.included_usage - messagesUsage, + usage: messagesUsage, + }); + + await expectCustomerInvoiceCorrect({ + customer, + count: 2, + latestTotal: preview.total, + }); + + await expectSubToBeCorrect({ + db: ctx.db, + customerId, + org: ctx.org, + env: ctx.env, + }); +}); diff --git a/server/tests/utils/fixtures/items.ts b/server/tests/utils/fixtures/items.ts index a6714e62c..7e4e5359a 100644 --- a/server/tests/utils/fixtures/items.ts +++ b/server/tests/utils/fixtures/items.ts @@ -579,6 +579,31 @@ const consumableWords = ({ interval, }); +/** + * Tiered consumable messages - graduated pricing with tiers (pay-per-use). + * Default tiers: 0-500 units at $0.10/unit, 501+ at $0.05/unit. + * IMPORTANT: Last tier MUST have `to: "inf"` - Stripe requires a catch-all tier. + * Note: tier_behavior is undefined, defaulting to graduated pricing. + */ +const tieredConsumableMessages = ({ + includedUsage = 0, + billingUnits = 1, + tiers = [ + { to: 500, amount: 0.1 }, + { to: "inf", amount: 0.05 }, + ], +}: { + includedUsage?: number; + billingUnits?: number; + tiers?: { to: number | "inf"; amount: number }[]; +} = {}): LimitedItem => + constructArrearItem({ + featureId: TestFeature.Messages, + tiers: tiers as { to: number; amount: number }[], + billingUnits, + includedUsage, + }) as LimitedItem; + // ═══════════════════════════════════════════════════════════════════ // ALLOCATED / SEATS (prorated billing) // ═══════════════════════════════════════════════════════════════════ @@ -701,6 +726,7 @@ export const items = { consumable, consumableMessages, consumableWords, + tieredConsumableMessages, // Allocated allocatedUsers, diff --git a/shared/utils/billingUtils/invoicingUtils/lineItemUtils/volumeTiersToLineAmount.ts b/shared/utils/billingUtils/invoicingUtils/lineItemUtils/volumeTiersToLineAmount.ts index 6f6cc0980..d62019854 100644 --- a/shared/utils/billingUtils/invoicingUtils/lineItemUtils/volumeTiersToLineAmount.ts +++ b/shared/utils/billingUtils/invoicingUtils/lineItemUtils/volumeTiersToLineAmount.ts @@ -2,7 +2,7 @@ import type { UsageTier } from "@models/productModels/priceModels/priceConfig/us import { Infinite } from "@models/productModels/productEnums"; import { roundUsageToNearestBillingUnit } from "@utils/billingUtils/usageUtils/roundUsageToNearestBillingUnit"; import { nullish } from "@utils/utils"; -import Decimal from "decimal.js"; +import { Decimal } from "decimal.js"; export const volumeTiersToLineAmount = ({ tiers, diff --git a/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeCreatePriceParams.ts b/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeCreatePriceParams.ts index f7c8bbe8f..7b7e503d7 100644 --- a/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeCreatePriceParams.ts +++ b/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeCreatePriceParams.ts @@ -1,5 +1,4 @@ import type { Organization } from "@models/orgModels/orgTable"; -import { TierBehavior } from "@models/productModels/priceModels/priceConfig/usagePriceConfig"; import type { Price } from "@models/productModels/priceModels/priceModels"; import type { FullProduct } from "@models/productModels/productModels"; import { orgToCurrency } from "@utils/orgUtils/convertOrgUtils"; @@ -8,6 +7,7 @@ import { priceToStripePrepaidV2Tiers } from "@utils/productUtils/priceUtils/conv import { priceToStripeProductName } from "@utils/productUtils/priceUtils/convertPrice/priceToStripeProductName"; import { priceToStripeRecurringParams } from "@utils/productUtils/priceUtils/convertPrice/priceToStripeRecurringParams"; import type Stripe from "stripe"; +import { priceToStripeTiersMode } from "./priceToStripeTiersMode"; export const priceToStripeCreatePriceParams = ({ price, @@ -41,9 +41,7 @@ export const priceToStripeCreatePriceParams = ({ }; const tiers = priceToStripePrepaidV2Tiers({ price, entitlement, org }); - - const tiersMode = - price.tier_behavior === TierBehavior.VolumeBased ? "volume" : "graduated"; + const tiersMode = priceToStripeTiersMode({ price }); let priceAmountData = {}; if (tiers.length === 1) { diff --git a/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeTiersMode.ts b/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeTiersMode.ts new file mode 100644 index 000000000..f5c79da92 --- /dev/null +++ b/shared/utils/productUtils/priceUtils/convertPrice/priceToStripeTiersMode.ts @@ -0,0 +1,7 @@ +import { type Price, TierBehavior } from "../../../.."; + +export const priceToStripeTiersMode = ({ price }: { price: Price }) => { + return price.tier_behavior === TierBehavior.VolumeBased + ? "volume" + : "graduated"; +}; diff --git a/shared/utils/productUtils/priceUtils/index.ts b/shared/utils/productUtils/priceUtils/index.ts index cd67d2543..9de0fcd3f 100644 --- a/shared/utils/productUtils/priceUtils/index.ts +++ b/shared/utils/productUtils/priceUtils/index.ts @@ -1,10 +1,12 @@ import { priceIsTieredOneOff } from "@utils/productUtils/priceUtils/classifyPrice/priceIsTieredOneOff.js"; import { priceToAllowanceInPacks } from "@utils/productUtils/priceUtils/convertPrice/priceToAllowanceInPacks.js"; import { priceToStripeCreatePriceParams } from "@utils/productUtils/priceUtils/convertPrice/priceToStripeCreatePriceParams.js"; +import { priceToStripeTiersMode } from "./convertPrice/priceToStripeTiersMode.js"; export * from "./classifyPrice/priceIsTieredOneOff.js"; export * from "./classifyPriceUtils.js"; export * from "./convertAmountUtils.js"; +export * from "./convertPrice/priceToStripeTiersMode.js"; export * from "./convertPriceUtils.js"; export * from "./findPrice/findPriceByFeatureId.js"; export * from "./formatPriceUtils.js"; @@ -14,6 +16,7 @@ export const priceUtils = { convert: { toAllowanceInPacks: priceToAllowanceInPacks, toStripeCreatePriceParams: priceToStripeCreatePriceParams, + toStripeTiersMode: priceToStripeTiersMode, }, isTieredOneOff: priceIsTieredOneOff, diff --git a/shared/utils/productV2Utils/productItemUtils/convertProductItem/productItemToPlanItemParamsV1.ts b/shared/utils/productV2Utils/productItemUtils/convertProductItem/productItemToPlanItemParamsV1.ts index 74e88531b..458a39ab7 100644 --- a/shared/utils/productV2Utils/productItemUtils/convertProductItem/productItemToPlanItemParamsV1.ts +++ b/shared/utils/productV2Utils/productItemUtils/convertProductItem/productItemToPlanItemParamsV1.ts @@ -41,6 +41,7 @@ export const productItemToPlanItemParamsV1 = ({ billing_units: planItemV1.price.billing_units, billing_method: planItemV1.price.billing_method, max_purchase: planItemV1.price.max_purchase ?? undefined, + tier_behavior: planItemV1.price.tier_behavior ?? undefined, } : undefined, proration: planItemV1.proration