chore: minor cleanup in update subscription quantity utils
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -67,7 +67,7 @@ export const computeSubscriptionUpdateNewCustomerProduct = ({
|
||||
|
||||
initOptions: {
|
||||
isCustom: true,
|
||||
subscriptionId: stripeSubscription?.id,
|
||||
subscriptionId: stripeSubscription.id,
|
||||
subscriptionScheduleId: stripeSubscriptionSchedule?.id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
21
shared/utils/cusProductUtils/findFeatureOptionsByFeature.ts
Normal file
21
shared/utils/cusProductUtils/findFeatureOptionsByFeature.ts
Normal file
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user