import { ExtendedRequest } from "@/utils/models/Request.js"; import { AttachParams, AttachResultSchema, } from "../../../cusProducts/AttachParams.js"; import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js"; import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js"; import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js"; import { APIVersion, AttachConfig, AttachScenario, CusProductStatus, ProcessorType, SuccessCode, } from "@autumn/shared"; import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js"; import { attachToInsertParams } from "@/internal/products/productUtils.js"; import { attachToInvoiceResponse, insertInvoiceFromAttach, } from "@/internal/invoices/invoiceUtils.js"; import { updateSubsDiffInt } from "./updateSubsDiffInt.js"; export const handleUpgradeDiffInterval = async ({ req, res, attachParams, config, }: { req: ExtendedRequest; res?: any; attachParams: AttachParams; config: AttachConfig; }) => { const logger = req.logtail; const { stripeCli, customer, products, cusProducts } = attachParams; const { carryUsage, disableTrial } = config; const product = products[0]; let { curMainProduct: curCusProduct } = getExistingCusProducts({ product, cusProducts: cusProducts || [], internalEntityId: attachParams.internalEntityId, }); curCusProduct = curCusProduct!; const stripeSubs = await getStripeSubs({ stripeCli, subIds: curCusProduct.subscription_ids, expand: ["items.data.price.tiers"], }); logger.info("1. Updating current subscriptions in Stripe"); let { newSubs, invoice, newInvoiceIds } = await updateSubsDiffInt({ db: req.db, curCusProduct, stripeCli, attachParams, stripeSubs, logger, config, }); logger.info("3. Expiring old cus product"); await CusProductService.update({ db: req.db, cusProductId: curCusProduct.id, updates: { subscription_ids: curCusProduct.subscription_ids!.filter( (subId) => subId !== newSubs[0].id ), processor: { type: ProcessorType.Stripe, subscription_id: null, }, status: CusProductStatus.Expired, }, }); // Insert new cus product logger.info("4. Creating new cus product"); await createFullCusProduct({ db: req.db, attachParams: attachToInsertParams(attachParams, products[0]), subscriptionIds: newSubs.map((sub) => sub.id), anchorToUnix: newSubs.length > 0 ? newSubs[0].current_period_end * 1000 : undefined, disableFreeTrial: disableTrial, carryExistingUsages: carryUsage, carryOverTrial: true, scenario: AttachScenario.Upgrade, logger, }); // Insert invoices logger.info("5. Inserting invoices"); const batchInsertInvoice = []; for (const invoiceId of newInvoiceIds || []) { batchInsertInvoice.push( insertInvoiceFromAttach({ db: req.db, attachParams, invoiceId, logger, }) ); } await Promise.all(batchInsertInvoice); let curProductName = curCusProduct.product.name; if (res) { let apiVersion = attachParams.apiVersion || APIVersion.v1; if (apiVersion >= APIVersion.v1_1) { res.status(200).json( AttachResultSchema.parse({ customer_id: customer.id, product_ids: products.map((p) => p.id), code: SuccessCode.UpgradedToNewProduct, message: `Successfully upgraded from ${curProductName} to ${product.name}`, invoice: config.invoiceOnly ? attachToInvoiceResponse({ invoice: invoice || undefined }) : undefined, }) ); } else { res.status(200).json({ success: true, message: `Successfully upgraded from ${curProductName} to ${product.name}`, }); } } };