diff --git a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateAllPlan.ts b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateAllPlan.ts index 133fd731d..f9da01593 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateAllPlan.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdateAllPlan.ts @@ -7,3 +7,19 @@ // newItems: body.items, // products: [product], // }); + +import type { SubscriptionUpdateV0Params } from "../../../../../../../shared"; +import type { AutumnContext } from "../../../../../honoUtils/HonoEnv"; +import type { UpdateSubscriptionContext } from "../fetch/updateSubscriptionContextSchema"; + +export const computeSubscriptionUpdateCustomConfigurationPlan = ({ + ctx: AutumnContext, + updateSubscriptionContext, + params, +}: { + ctx: AutumnContext; + updateSubscriptionContext: UpdateSubscriptionContext; + params: SubscriptionUpdateV0Params; +}) => { + return {}; +}; diff --git a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdatePlan.ts b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdatePlan.ts index dd5ddc25b..2950595bc 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdatePlan.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/compute/computeSubscriptionUpdatePlan.ts @@ -22,6 +22,7 @@ export const computeSubscriptionUpdatePlan = ( }, ): SubscriptionUpdatePlan => { const intent = computeSubscriptionUpdateIntent(params); + const computePlan = getComputeSubscriptionUpdatePlanIntentMap(intent); return computePlan(ctx, { updateSubscriptionContext, params }); diff --git a/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchApiSubscriptionUpdateContext.ts b/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchApiSubscriptionUpdateContext.ts index a485bf452..ba19a9b6d 100644 --- a/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchApiSubscriptionUpdateContext.ts +++ b/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchApiSubscriptionUpdateContext.ts @@ -7,6 +7,7 @@ import type { AutumnContext } from "@/honoUtils/HonoEnv"; import { CusService } from "../../../../customers/CusService"; import { fetchStripeCustomerForBilling } from "../../fetch/fetchStripeUtils/fetchStripeCustomerForBilling"; import { fetchStripeSubscriptionForBilling } from "../../fetch/fetchStripeUtils/fetchStripeSubscriptionForBilling"; +import { fetchTargetCusProductForUpdate } from "./fetchTargetCusProductForUpdate"; import type { UpdateSubscriptionContext } from "./updateSubscriptionContextSchema"; /** @@ -42,9 +43,10 @@ export const fetchApiSubscriptionUpdateContext = async ( withEntities: true, }); - const targetCustomerProduct = fullCustomer.customer_products.find( - (cp) => cp.product.id === productId, - ); + const targetCustomerProduct = fetchTargetCusProductForUpdate({ + params, + fullCustomer, + }); if (!targetCustomerProduct) { throw new InternalError({ diff --git a/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchTargetCusProductForUpdate.ts b/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchTargetCusProductForUpdate.ts new file mode 100644 index 000000000..e098788ed --- /dev/null +++ b/server/src/internal/billing/v2/subscriptionUpdate/fetch/fetchTargetCusProductForUpdate.ts @@ -0,0 +1,32 @@ +import { + type FullCustomer, + isCusProductOnEntity, + type SubscriptionUpdateV0Params, +} from "@autumn/shared"; + +export const fetchTargetCusProductForUpdate = ({ + params, + fullCustomer, +}: { + params: SubscriptionUpdateV0Params; + fullCustomer: FullCustomer; +}) => { + const cusProducts = fullCustomer.customer_products; + const productId = params.product_id; + const internalEntityId = fullCustomer.entity?.internal_id; + const cusProductId = params.customer_product_id; + + if (cusProductId) { + return cusProducts.find((cp) => cp.id === cusProductId); + } + + return cusProducts.find((cp) => { + const productIdMatch = cp.product.id === productId; + const entityIdMatch = isCusProductOnEntity({ + cusProduct: cp, + internalEntityId, + }); + + return productIdMatch && entityIdMatch; + }); +}; diff --git a/shared/api/billing/subscriptionUpdate/subscriptionUpdateV0Params.ts b/shared/api/billing/subscriptionUpdate/subscriptionUpdateV0Params.ts index 4fc418665..b26a9ec82 100644 --- a/shared/api/billing/subscriptionUpdate/subscriptionUpdateV0Params.ts +++ b/shared/api/billing/subscriptionUpdate/subscriptionUpdateV0Params.ts @@ -4,7 +4,7 @@ import { ProductItemSchema } from "../../../models/productV2Models/productItemMo import { CustomerDataSchema } from "../../common/customerData"; import { EntityDataSchema } from "../../models"; -export const SubscriptionUpdateV0ParamsSchema = z.object({ +export const ExtSubscriptionUpdateV0ParamsSchema = z.object({ // Customer / Entity Info customer_id: z.string(), product_id: z.string().nullish(), @@ -26,6 +26,15 @@ export const SubscriptionUpdateV0ParamsSchema = z.object({ prorate_billing: z.boolean().optional(), }); +export const SubscriptionUpdateV0ParamsSchema = + ExtSubscriptionUpdateV0ParamsSchema.extend({ + customer_product_id: z.string().optional(), + }); + +export type ExtSubscriptionUpdateV0Params = z.infer< + typeof ExtSubscriptionUpdateV0ParamsSchema +>; + export type SubscriptionUpdateV0Params = z.infer< typeof SubscriptionUpdateV0ParamsSchema >; diff --git a/shared/utils/cusProductUtils/classifyCusProduct.ts b/shared/utils/cusProductUtils/classifyCusProduct.ts index c7048021e..67ceeb1ad 100644 --- a/shared/utils/cusProductUtils/classifyCusProduct.ts +++ b/shared/utils/cusProductUtils/classifyCusProduct.ts @@ -43,7 +43,12 @@ export const isCusProductTrialing = ({ ); }; -// ATTACH PRIMITIVES +/** + * Returns true if the customer product is assigned to the given entity, + * or if no entity is specified, true if the product is not assigned to any entity. + * @param cusProduct - The customer product object + * @param internalEntityId - The internal entity ID to check, or undefined to check for unassigned + */ export const isCusProductOnEntity = ({ cusProduct, internalEntityId,