diff --git a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeQuantityUpdateDetails.ts b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeQuantityUpdateDetails.ts index 85099e421..5d9104fab 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeQuantityUpdateDetails.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeQuantityUpdateDetails.ts @@ -13,8 +13,8 @@ import type { QuantityUpdateDetails } from "@/internal/billing/v2/typesOld"; import { calculateEntitlementChange } from "./quantityUpdateUtils/calculateEntitlementChange"; import { calculateProrationAmount } from "./quantityUpdateUtils/calculateProrationAmount"; import { calculateQuantityDifferences } from "./quantityUpdateUtils/calculateQuantityDifferences"; -import { extractPriceConfig } from "./quantityUpdateUtils/extractPriceConfig"; import { mapStripeSubscriptionItem } from "./quantityUpdateUtils/mapStripeSubscriptionItem"; +import { resolvePriceForQuantityUpdate } from "./quantityUpdateUtils/resolvePriceForQuantityUpdate"; /** * Computes all details needed for a feature quantity update operation. @@ -46,7 +46,7 @@ export const computeQuantityUpdateDetails = ({ stripeSubscription: Stripe.Subscription; currentEpochMs: number; }): QuantityUpdateDetails => { - const { features, org } = ctx; + const { features } = ctx; const internalFeatureId = updatedOptions.internal_feature_id; const featureId = updatedOptions.feature_id; @@ -73,7 +73,7 @@ export const computeQuantityUpdateDetails = ({ updatedOptions, }); - const priceConfiguration = extractPriceConfig({ + const priceConfiguration = resolvePriceForQuantityUpdate({ customerProduct, updatedOptions, isUpgrade: quantityDifferences.isUpgrade, diff --git a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateCustomPlan/computeSubscriptionUpdateNewCustomerProduct.ts b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateCustomPlan/computeSubscriptionUpdateNewCustomerProduct.ts index bc2063029..aeb1e56bb 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateCustomPlan/computeSubscriptionUpdateNewCustomerProduct.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateCustomPlan/computeSubscriptionUpdateNewCustomerProduct.ts @@ -67,7 +67,7 @@ export const computeSubscriptionUpdateNewCustomerProduct = ({ initOptions: { isCustom: true, - subscriptionId: stripeSubscription?.id, + subscriptionId: stripeSubscription.id, subscriptionScheduleId: stripeSubscriptionSchedule?.id, }, }); diff --git a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateQuantityPlan.ts b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateQuantityPlan.ts index bcaf71e49..d5bfac685 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateQuantityPlan.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateQuantityPlan.ts @@ -1,4 +1,5 @@ import { + findFeatureOptionsByFeature, InternalError, OngoingCusProductActionEnum, type SubscriptionUpdateV0Params, @@ -42,16 +43,10 @@ export const computeSubscriptionUpdateQuantityPlan = ({ const currentEpochMs = testClockFrozenTime || Date.now(); const quantityUpdateDetails = featureQuantities.new.map((updatedOption) => { - const previousOption = featureQuantities.old.find( - (oldOption) => oldOption.feature_id === updatedOption.feature_id, - ); - - if (!previousOption) { - throw new Error( - `[Subscription Update] Cannot find previous options for feature: ${updatedOption.feature_id}. ` + - `This feature may not exist in the current subscription.`, - ); - } + const previousOption = findFeatureOptionsByFeature({ + featureOptions: customerProduct.options, + featureId: updatedOption.feature_id, + }); return computeQuantityUpdateDetails({ ctx, diff --git a/server/src/internal/billing/v2/subscriptionUpdate/compute/quantityUpdateUtils/extractPriceConfig.ts b/server/src/internal/billing/v2/subscriptionUpdate/compute/quantityUpdateUtils/resolvePriceForQuantityUpdate.ts similarity index 77% rename from server/src/internal/billing/v2/subscriptionUpdate/compute/quantityUpdateUtils/extractPriceConfig.ts rename to server/src/internal/billing/v2/subscriptionUpdate/compute/quantityUpdateUtils/resolvePriceForQuantityUpdate.ts index c5f1c6594..957d5d376 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/compute/quantityUpdateUtils/extractPriceConfig.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/compute/quantityUpdateUtils/resolvePriceForQuantityUpdate.ts @@ -4,15 +4,12 @@ import { type FullCustomerPrice, findCusPriceByFeature, InternalError, - OnDecrease, - OnIncrease, + type OnDecrease, + type OnIncrease, type Price, + priceToProrationConfig, type UsagePriceConfig, } from "@autumn/shared"; -import { - shouldBillNow, - shouldProrate, -} from "@/internal/products/prices/priceUtils/prorationConfigUtils"; /** * Extracts and validates price configuration for a quantity update. @@ -25,7 +22,7 @@ import { * @returns Price config including proration rules and billing units * @throws {InternalError} When internal_feature_id is missing or price not found */ -export const extractPriceConfig = ({ +export const resolvePriceForQuantityUpdate = ({ customerProduct, updatedOptions, isUpgrade, @@ -61,16 +58,16 @@ export const extractPriceConfig = ({ const price = customerPrice.price; const priceConfig = price.config as UsagePriceConfig; - const billingUnitsPerQuantity = priceConfig.billing_units || 1; + const billingUnitsPerQuantity = priceConfig.billing_units ?? 1; - const prorationBehaviorConfig = isUpgrade - ? price.proration_config?.on_increase || OnIncrease.ProrateImmediately - : price.proration_config?.on_decrease || OnDecrease.ProrateImmediately; - - const shouldApplyProration = shouldProrate(prorationBehaviorConfig); - const shouldFinalizeInvoiceImmediately = shouldBillNow( + const { prorationBehaviorConfig, - ); + shouldApplyProration, + shouldFinalizeInvoiceImmediately, + } = priceToProrationConfig({ + price, + isUpgrade, + }); return { customerPrice, diff --git a/server/src/internal/billing/v2/subscriptionUpdate/execute/executeSubscriptionUpdate.ts b/server/src/internal/billing/v2/subscriptionUpdate/execute/executeSubscriptionUpdate.ts index c95bf1428..2ac3ca414 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/execute/executeSubscriptionUpdate.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/execute/executeSubscriptionUpdate.ts @@ -41,7 +41,7 @@ export const executeSubscriptionUpdate = async ({ if (shouldUncancelSubscription) { await executeStripeSubscriptionUncancel({ ctx, - stripeSubscriptionId: stripeSubscription?.id ?? "", + stripeSubscriptionId: stripeSubscription.id, customerProduct, }); } @@ -58,7 +58,7 @@ export const executeSubscriptionUpdate = async ({ ctx, invoiceAction, stripeCustomerId: stripeCustomer.id, - stripeSubscriptionId: stripeSubscription?.id ?? "", + stripeSubscriptionId: stripeSubscription.id, customerProduct, }); } else { diff --git a/server/src/internal/billing/v2/subscriptionUpdate/fetch/updateSubscriptionContextSchema.ts b/server/src/internal/billing/v2/subscriptionUpdate/fetch/updateSubscriptionContextSchema.ts index 361909152..75416e216 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/fetch/updateSubscriptionContextSchema.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/fetch/updateSubscriptionContextSchema.ts @@ -5,7 +5,7 @@ export type UpdateSubscriptionContext = { fullCustomer: FullCustomer; // product: FullProduct; customerProduct: FullCusProduct; - stripeSubscription?: Stripe.Subscription; + stripeSubscription: Stripe.Subscription; stripeSubscriptionSchedule?: Stripe.SubscriptionSchedule; stripeCustomer: Stripe.Customer; paymentMethod?: Stripe.PaymentMethod; diff --git a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts index 5262db951..79d0e6862 100644 --- a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts +++ b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts @@ -9,6 +9,8 @@ import { getFeatureInvoiceDescription, OnDecrease, priceToInvoiceAmount, + shouldBillNow, + shouldProrate, type UsagePriceConfig, } from "@autumn/shared"; import { Decimal } from "decimal.js"; @@ -21,10 +23,6 @@ import { InvoiceService } from "@/internal/invoices/InvoiceService.js"; import { constructStripeInvoiceItem } from "@/internal/invoices/invoiceItemUtils/invoiceItemUtils.js"; import { createAndFinalizeInvoice } from "@/internal/invoices/invoiceUtils/createAndFinalizeInvoice.js"; import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js"; -import { - shouldBillNow, - shouldProrate, -} from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { notNullish } from "@/utils/genUtils.js"; import type { AutumnContext } from "../../../../../honoUtils/HonoEnv"; diff --git a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts index 6afc537b7..90e86f968 100644 --- a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts +++ b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts @@ -9,6 +9,8 @@ import { getFeatureInvoiceDescription, OnIncrease, priceToInvoiceAmount, + shouldBillNow, + shouldProrate, type UsagePriceConfig, } from "@autumn/shared"; import { Decimal } from "decimal.js"; @@ -21,10 +23,6 @@ import { InvoiceService } from "@/internal/invoices/InvoiceService.js"; import { constructStripeInvoiceItem } from "@/internal/invoices/invoiceItemUtils/invoiceItemUtils.js"; import { createAndFinalizeInvoice } from "@/internal/invoices/invoiceUtils/createAndFinalizeInvoice.js"; import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js"; -import { - shouldBillNow, - shouldProrate, -} from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { notNullish } from "@/utils/genUtils.js"; import type { AutumnContext } from "../../../../../honoUtils/HonoEnv"; diff --git a/server/src/internal/customers/attach/attachUtils/getContUseItems/getContUseInvoiceItems.ts b/server/src/internal/customers/attach/attachUtils/getContUseItems/getContUseInvoiceItems.ts index ba57c3117..4b0579f44 100644 --- a/server/src/internal/customers/attach/attachUtils/getContUseItems/getContUseInvoiceItems.ts +++ b/server/src/internal/customers/attach/attachUtils/getContUseItems/getContUseInvoiceItems.ts @@ -7,6 +7,7 @@ import { getFeatureInvoiceDescription, type PreviewLineItem, type Price, + shouldProrate, } from "@autumn/shared"; import { Decimal } from "decimal.js"; import type Stripe from "stripe"; @@ -20,7 +21,6 @@ import { newPriceToInvoiceDescription } from "@/internal/invoices/invoiceFormatU import { getCurContUseItems } from "@/internal/invoices/previewItemUtils/getCurContUseItems.js"; import { getDefaultPriceStr } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js"; import { priceToUsageModel } from "@/internal/products/prices/priceUtils/convertPrice.js"; -import { shouldProrate } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { getBillingType, getPriceEntitlement, diff --git a/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts b/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts index 510008c02..9bc65061c 100644 --- a/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts +++ b/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts @@ -4,6 +4,7 @@ import { type FullEntitlement, type PreviewLineItem, type Price, + shouldProrate, } from "@autumn/shared"; import { Decimal } from "decimal.js"; import type Stripe from "stripe"; @@ -15,7 +16,6 @@ import { getRelatedCusPrice, } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; import { priceToInvoiceItem } from "@/internal/products/prices/priceUtils/priceToInvoiceItem.js"; -import { shouldProrate } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { notNullish } from "@/utils/genUtils.js"; import { attachParamsToProduct } from "../convertAttachParams.js"; import { getContUseDowngradeItems } from "./getContUseDowngradeItems.js"; diff --git a/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts b/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts index d151f434a..fa72eceec 100644 --- a/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts +++ b/server/src/internal/products/prices/priceUtils/arrearProratedUtils/getUsageDiffLineItem.ts @@ -1,15 +1,15 @@ -import { constructPreviewItem } from "@/internal/invoices/previewItemUtils/constructPreviewItem.js"; import { - FullEntitlement, + type FullEntitlement, getFeatureInvoiceDescription, - Organization, - Price, - Product, - UsagePriceConfig, + type Organization, + type Price, + type Product, + shouldProrate, + type UsagePriceConfig, usageToFeatureName, } from "@autumn/shared"; import { Decimal } from "decimal.js"; -import { shouldProrate } from "../prorationConfigUtils.js"; +import { constructPreviewItem } from "@/internal/invoices/previewItemUtils/constructPreviewItem.js"; export const getUsageDiffLineItem = ({ prevBalance, @@ -54,7 +54,7 @@ export const getUsageDiffLineItem = ({ }); if (!isIncrease && !willProrate) { - let featureName = usageToFeatureName({ + const featureName = usageToFeatureName({ usage: usageDiff, feature: ent.feature, }); diff --git a/server/src/internal/products/product-items/productItemUtils/itemToPriceAndEnt.ts b/server/src/internal/products/product-items/productItemUtils/itemToPriceAndEnt.ts index 121d73899..65e042ff8 100644 --- a/server/src/internal/products/product-items/productItemUtils/itemToPriceAndEnt.ts +++ b/server/src/internal/products/product-items/productItemUtils/itemToPriceAndEnt.ts @@ -18,6 +18,7 @@ import { type Price, PriceType, type ProductItem, + shouldProrate, TierInfinite, UsageModel, type UsagePriceConfig, @@ -25,7 +26,6 @@ import { import { entsAreSame } from "@server/internal/products/entitlements/entitlementUtils"; import { pricesAreSame } from "@server/internal/products/prices/priceInitUtils"; import { getBillingType } from "@server/internal/products/prices/priceUtils"; -import { shouldProrate } from "@server/internal/products/prices/priceUtils/prorationConfigUtils"; import { itemCanBeProrated } from "@server/internal/products/product-items/productItemUtils/classifyItem"; import RecaseError from "@server/utils/errorUtils"; import { generateId, notNullish, nullish } from "@server/utils/genUtils"; diff --git a/server/src/trigger/arrearProratedUsage/createUpgradeProrationInvoice.ts b/server/src/trigger/arrearProratedUsage/createUpgradeProrationInvoice.ts index 723f6bd7d..5be849200 100644 --- a/server/src/trigger/arrearProratedUsage/createUpgradeProrationInvoice.ts +++ b/server/src/trigger/arrearProratedUsage/createUpgradeProrationInvoice.ts @@ -6,6 +6,8 @@ import { type Organization, type Price, type Product, + shouldBillNow, + shouldProrate, type UsagePriceConfig, } from "@autumn/shared"; import { Decimal } from "decimal.js"; @@ -14,10 +16,6 @@ import { getCusPaymentMethod } from "@/external/stripe/stripeCusUtils.js"; import { constructStripeInvoiceItem } from "@/internal/invoices/invoiceItemUtils/invoiceItemUtils.js"; import { createAndFinalizeInvoice } from "@/internal/invoices/invoiceUtils/createAndFinalizeInvoice.js"; import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js"; -import { - shouldBillNow, - shouldProrate, -} from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { formatUnixToDate } from "@/utils/genUtils.js"; import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js"; import type { Logger } from "../../external/logtail/logtailUtils"; diff --git a/server/src/trigger/arrearProratedUsage/handleProratedDowngrade.ts b/server/src/trigger/arrearProratedUsage/handleProratedDowngrade.ts index c912e6f05..3f1dc853f 100644 --- a/server/src/trigger/arrearProratedUsage/handleProratedDowngrade.ts +++ b/server/src/trigger/arrearProratedUsage/handleProratedDowngrade.ts @@ -9,6 +9,8 @@ import { type Organization, type Product, priceToInvoiceAmount, + shouldProrate, + shouldProrateDowngradeNow, type UsagePriceConfig, } from "@autumn/shared"; import { Decimal } from "decimal.js"; @@ -19,10 +21,6 @@ import { constructStripeInvoiceItem } from "@/internal/invoices/invoiceItemUtils import { createAndFinalizeInvoice } from "@/internal/invoices/invoiceUtils/createAndFinalizeInvoice.js"; import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js"; import { getReplaceables } from "@/internal/products/prices/priceUtils/arrearProratedUtils/getContUsageDowngradeItem.js"; -import { - shouldProrate, - shouldProrateDowngradeNow, -} from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { roundUsage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; import { formatUnixToDate } from "@/utils/genUtils.js"; import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js"; diff --git a/server/src/trigger/arrearProratedUsage/handleProratedUpgrade.ts b/server/src/trigger/arrearProratedUsage/handleProratedUpgrade.ts index 889198c72..0476599a4 100644 --- a/server/src/trigger/arrearProratedUsage/handleProratedUpgrade.ts +++ b/server/src/trigger/arrearProratedUsage/handleProratedUpgrade.ts @@ -6,12 +6,12 @@ import { type Organization, type Price, priceToInvoiceAmount, + shouldCreateInvoiceItem, type UsagePriceConfig, } from "@autumn/shared"; import type Stripe from "stripe"; import type { DrizzleCli } from "@/db/initDrizzle.js"; import { RepService } from "@/internal/customers/cusProducts/cusEnts/RepService.js"; -import { shouldCreateInvoiceItem } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { roundUsage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; import { getUsageFromBalance } from "../adjustAllowance.js"; import { createUpgradeProrationInvoice } from "./createUpgradeProrationInvoice.js"; diff --git a/shared/utils/billingUtils/index.ts b/shared/utils/billingUtils/index.ts index 4b5d26d22..66599202d 100644 --- a/shared/utils/billingUtils/index.ts +++ b/shared/utils/billingUtils/index.ts @@ -14,4 +14,5 @@ export * from "./invoicingUtils/lineItemBuilders/usagePriceToLineItem"; export * from "./invoicingUtils/lineItemUtils/priceToLineAmount"; export * from "./invoicingUtils/lineItemUtils/tiersToLineAmount"; export * from "./invoicingUtils/prorationUtils/applyProration"; +export * from "./invoicingUtils/prorationUtils/prorationConfigUtils"; export * from "./usageUtils/roundUsageToNearestBillingUnit"; diff --git a/server/src/internal/products/prices/priceUtils/prorationConfigUtils.ts b/shared/utils/billingUtils/invoicingUtils/prorationUtils/prorationConfigUtils.ts similarity index 53% rename from server/src/internal/products/prices/priceUtils/prorationConfigUtils.ts rename to shared/utils/billingUtils/invoicingUtils/prorationUtils/prorationConfigUtils.ts index 7b458f31b..17a84d753 100644 --- a/server/src/internal/products/prices/priceUtils/prorationConfigUtils.ts +++ b/shared/utils/billingUtils/invoicingUtils/prorationUtils/prorationConfigUtils.ts @@ -1,5 +1,10 @@ import { OnDecrease, OnIncrease } from "@autumn/shared"; +/** + * Determines if an invoice item should be created for a quantity increase. + * @param onIncrease - The proration behavior configuration for the quantity increase. + * @returns True if an invoice item should be created, false otherwise. + */ export const shouldCreateInvoiceItem = (onIncrease: OnIncrease) => { return ( onIncrease === OnIncrease.BillImmediately || @@ -8,6 +13,11 @@ export const shouldCreateInvoiceItem = (onIncrease: OnIncrease) => { ); }; +/** + * Determines if an invoice item should be billed immediately. + * @param onIncrease - The proration behavior configuration for the quantity increase. + * @returns True if an invoice item should be billed immediately, false otherwise. + */ export const shouldBillNow = (onIncrease: OnIncrease | OnDecrease) => { return ( onIncrease === OnIncrease.BillImmediately || @@ -16,6 +26,12 @@ export const shouldBillNow = (onIncrease: OnIncrease | OnDecrease) => { ); }; +/** + * Determines if a quantity downgrade should be prorated. + * @param onIncrease - The proration behavior configuration for the quantity increase. + * @param onDecrease - The proration behavior configuration for the quantity decrease. + * @returns True if a quantity downgrade should be prorated, false otherwise. + */ export const shouldProrateDowngradeNow = ({ onIncrease, onDecrease, @@ -36,6 +52,11 @@ export const shouldProrateDowngradeNow = ({ ); }; +/** + * Determines if a quantity upgrade or downgrade should be prorated. + * @param onIncrease - The proration behavior configuration for the quantity increase. + * @returns True if a quantity upgrade or downgrade should be prorated, false otherwise. + */ export const shouldProrate = (onIncrease?: OnIncrease | OnDecrease) => { if (!onIncrease) { return true; diff --git a/shared/utils/cusProductUtils/findFeatureOptionsByFeature.ts b/shared/utils/cusProductUtils/findFeatureOptionsByFeature.ts new file mode 100644 index 000000000..33b962a73 --- /dev/null +++ b/shared/utils/cusProductUtils/findFeatureOptionsByFeature.ts @@ -0,0 +1,21 @@ +import { InternalError } from "@api/errors"; +import type { FeatureOptions } from "@models/cusProductModels/cusProductModels"; + +export const findFeatureOptionsByFeature = ({ + featureOptions, + featureId, +}: { + featureOptions: FeatureOptions[]; + featureId: string; +}) => { + const previousOption = featureOptions.find( + (oldOption) => oldOption.feature_id === featureId, + ); + + if (!previousOption) { + throw new InternalError({ + message: `[Find Feature Options By Feature] Cannot find feature options for feature: ${featureId}.`, + }); + } + return previousOption; +}; diff --git a/shared/utils/index.ts b/shared/utils/index.ts index e1d993998..c77af11a8 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts @@ -40,6 +40,7 @@ export * from "./cusProductUtils/cusProductConstants.js"; export * from "./cusProductUtils/cusProductUtils.js"; export * from "./cusProductUtils/filterCusProductUtils.js"; export * from "./cusProductUtils/filterCusProductUtils.js"; +export * from "./cusProductUtils/findFeatureOptionsByFeature.js"; export * from "./cusProductUtils/getCusProductFromCustomer.js"; export * from "./cusProductUtils/productIdToCusProduct.js"; // Cus utils diff --git a/shared/utils/productUtils/priceUtils/convertPriceUtils.ts b/shared/utils/productUtils/priceUtils/convertPriceUtils.ts index 0709a78a6..5e4cbfe33 100644 --- a/shared/utils/productUtils/priceUtils/convertPriceUtils.ts +++ b/shared/utils/productUtils/priceUtils/convertPriceUtils.ts @@ -2,6 +2,11 @@ import type { Feature } from "@models/featureModels/featureModels"; import type { EntitlementWithFeature } from "@models/productModels/entModels/entModels"; import type { UsagePriceConfig } from "@models/productModels/priceModels/priceConfig/usagePriceConfig"; import type { Price } from "@models/productModels/priceModels/priceModels"; +import { + OnDecrease, + OnIncrease, +} from "@models/productV2Models/productItemModels/productItemEnums"; +import { shouldBillNow, shouldProrate } from "@utils/billingUtils"; import { priceToEnt } from "@utils/productUtils/convertProductUtils"; export const priceToFeature = ({ @@ -28,3 +33,30 @@ export const priceToFeature = ({ const ent = priceToEnt({ price, entitlements: ents ?? [] }); return ent?.feature; }; + +export const priceToProrationConfig = ({ + price, + isUpgrade, +}: { + price: Price; + isUpgrade: boolean; +}): { + prorationBehaviorConfig: OnIncrease | OnDecrease; + shouldApplyProration: boolean; + shouldFinalizeInvoiceImmediately: boolean; +} => { + const prorationBehaviorConfig = isUpgrade + ? (price.proration_config?.on_increase ?? OnIncrease.ProrateImmediately) + : (price.proration_config?.on_decrease ?? OnDecrease.ProrateImmediately); + + const shouldApplyProration = shouldProrate(prorationBehaviorConfig); + const shouldFinalizeInvoiceImmediately = shouldBillNow( + prorationBehaviorConfig, + ); + + return { + prorationBehaviorConfig, + shouldApplyProration, + shouldFinalizeInvoiceImmediately, + }; +};