Files
cfw-autumn/server/src/internal/billing/v2/updateSubscription/setup/setupUpdateSubscriptionBillingContext.ts
2026-01-11 11:01:19 +00:00

115 lines
3.3 KiB
TypeScript

import { formatMs, type UpdateSubscriptionV0Params } from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv";
import { setupStripeBillingContext } from "@/internal/billing/v2/providers/stripe/setup/setupStripeBillingContext";
import { setupBillingCycleAnchor } from "@/internal/billing/v2/setup/setupBillingCycleAnchor";
import { setupFeatureQuantitiesContext } from "@/internal/billing/v2/setup/setupFeatureQuantitiesContext";
import { setupFullCustomerContext } from "@/internal/billing/v2/setup/setupFullCustomerContext";
import { setupInvoiceModeContext } from "@/internal/billing/v2/setup/setupInvoiceModeContext";
import { setupResetCycleAnchor } from "@/internal/billing/v2/setup/setupResetCycleAnchor";
import { setupTrialContext } from "@/internal/billing/v2/setup/setupTrialContext";
import { setupUpdateSubscriptionProductContext } from "@/internal/billing/v2/updateSubscription/setup/setupUpdateSubscriptionProductContext";
import type { UpdateSubscriptionBillingContext } from "../../billingContext";
/**
* Fetch the context for updating a subscription
* @param ctx - The context
* @param body - The body of the request
* @returns The update subscription context
*/
export const setupUpdateSubscriptionBillingContext = async ({
ctx,
params,
}: {
ctx: AutumnContext;
params: UpdateSubscriptionV0Params;
}): Promise<UpdateSubscriptionBillingContext> => {
const fullCustomer = await setupFullCustomerContext({
ctx,
params,
});
const { customerProduct, fullProduct, customPrices, customEnts } =
await setupUpdateSubscriptionProductContext({
ctx,
fullCustomer,
params,
});
const featureQuantities = setupFeatureQuantitiesContext({
ctx,
featureQuantitiesParams: params,
fullProduct,
currentCustomerProduct: customerProduct,
});
const {
stripeSubscription,
stripeSubscriptionSchedule,
stripeCustomer,
paymentMethod,
testClockFrozenTime,
} = await setupStripeBillingContext({
ctx,
fullCustomer,
targetCustomerProduct: customerProduct,
});
const currentEpochMs = testClockFrozenTime ?? Date.now();
// 1. Setup trial context first
const trialContext = setupTrialContext({
stripeSubscription,
customerProduct,
currentEpochMs,
params,
fullProduct,
});
// 3. Determine final anchor based on product transitions
let billingCycleAnchorMs = setupBillingCycleAnchor({
stripeSubscription,
customerProduct,
newFullProduct: fullProduct,
trialContext,
currentEpochMs,
});
// 4. Trial ends at overrides reset cycle anchor
if (trialContext?.trialEndsAt) {
billingCycleAnchorMs = trialContext.trialEndsAt;
}
const resetCycleAnchorMs = setupResetCycleAnchor({
billingCycleAnchorMs,
customerProduct,
newFullProduct: fullProduct,
});
console.log("Billing cycle anchor: ", formatMs(billingCycleAnchorMs));
console.log("Trial ends at: ", formatMs(trialContext?.trialEndsAt));
console.log("Reset cycle anchor: ", formatMs(resetCycleAnchorMs));
const invoiceMode = setupInvoiceModeContext({ params });
return {
fullCustomer,
fullProducts: [fullProduct],
customerProduct,
stripeSubscription,
stripeSubscriptionSchedule,
stripeCustomer,
paymentMethod,
currentEpochMs,
billingCycleAnchorMs,
resetCycleAnchorMs,
invoiceMode,
featureQuantities,
customPrices,
customEnts,
trialContext,
};
};