From d25b2b7dbc39ca17122bf8dc0cb2877ad5024a65 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 9 Sep 2025 10:36:57 -0700 Subject: [PATCH] feat: setup_payment in attach body --- .../handleCheckoutCompleted.ts | 10 ++++++ .../handleCheckoutSub.ts | 4 +-- .../handleRemainingSets.ts | 4 ++- .../handleSetupCheckout.ts | 34 +++++++++++++++++++ .../add-product/handleCreateCheckout.ts | 16 ++++++++- .../attachParams/getAttachParams.ts | 1 + .../handleCheckoutErrors.ts | 14 ++++++++ .../customers/cusProducts/AttachParams.ts | 1 + shared/models/attachModels/attachBody.ts | 1 + 9 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleSetupCheckout.ts create mode 100644 server/src/internal/customers/attach/attachUtils/handleAttachErrors/handleCheckoutErrors.ts diff --git a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted.ts b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted.ts index e9a67f6be..f76c66aa4 100644 --- a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted.ts +++ b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted.ts @@ -23,6 +23,7 @@ import { getOptionsFromCheckoutSession } from "./handleCheckoutCompleted/getOpti import { getEarliestPeriodEnd } from "../stripeSubUtils/convertSubUtils.js"; import { notNullish } from "@/utils/genUtils.js"; import { CusService } from "@/internal/customers/CusService.js"; +import { handleSetupCheckout } from "./handleCheckoutCompleted/handleSetupCheckout.js"; export const handleCheckoutSessionCompleted = async ({ req, @@ -75,6 +76,15 @@ export const handleCheckoutSessionCompleted = async ({ checkoutSession.metadata?.autumn_metadata_id ); + if (attachParams.setupPayment) { + await handleSetupCheckout({ + req, + db, + attachParams, + }); + return; + } + const checkoutSub = checkoutSession.subscription as Stripe.Subscription | null; diff --git a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleCheckoutSub.ts b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleCheckoutSub.ts index e3e4ce412..137b58c3d 100644 --- a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleCheckoutSub.ts +++ b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleCheckoutSub.ts @@ -27,9 +27,7 @@ export const handleCheckoutSub = async ({ }) => { const { org, customer } = attachParams; - if (!subscription) { - return; - } + if (!subscription) return; const { start, end } = subToPeriodStartEnd({ sub: subscription }); diff --git a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleRemainingSets.ts b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleRemainingSets.ts index aa75d4c8c..de792b439 100644 --- a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleRemainingSets.ts +++ b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleRemainingSets.ts @@ -26,7 +26,9 @@ export const handleRemainingSets = async ({ let remainingSets = itemSets ? itemSets.slice(1) : []; const remainingItems = remainingSets.flatMap((set) => set.items); - let invoiceIds: string[] = [checkoutSession.invoice as string]; + let invoiceIds: string[] = checkoutSession.invoice + ? [checkoutSession.invoice as string] + : []; // Replace items with empty price if needed... for (const price of attachParams.prices) { diff --git a/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleSetupCheckout.ts b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleSetupCheckout.ts new file mode 100644 index 000000000..e0ec4379f --- /dev/null +++ b/server/src/external/stripe/webhookHandlers/handleCheckoutCompleted/handleSetupCheckout.ts @@ -0,0 +1,34 @@ +import { DrizzleCli } from "@/db/initDrizzle.js"; +import { handleAddProduct } from "@/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.js"; +import { getDefaultAttachConfig } from "@/internal/customers/attach/attachUtils/getAttachConfig.js"; +import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js"; +import { ExtendedRequest } from "@/utils/models/Request.js"; +import { AttachBranch } from "@autumn/shared"; +import Stripe from "stripe"; +import { createStripeCli } from "../../utils.js"; + +export const handleSetupCheckout = async ({ + req, + db, + attachParams, +}: { + req: ExtendedRequest; + db: DrizzleCli; + attachParams: AttachParams; +}) => { + const logger = req.logger; + const { org, customer } = attachParams; + + logger.info(`HANDLING SETUP CHECKOUT COMPLETED`); + + // 1. Check attach prices... + await handleAddProduct({ + req, + attachParams: { + ...attachParams, + stripeCli: createStripeCli({ org, env: customer.env }), + }, + branch: AttachBranch.New, + config: getDefaultAttachConfig(), + }); +}; diff --git a/server/src/internal/customers/add-product/handleCreateCheckout.ts b/server/src/internal/customers/add-product/handleCreateCheckout.ts index add1c6f58..2b1b40d9a 100644 --- a/server/src/internal/customers/add-product/handleCreateCheckout.ts +++ b/server/src/internal/customers/add-product/handleCreateCheckout.ts @@ -119,7 +119,7 @@ export const handleCreateCheckout = async ({ notNullish(checkoutParams.payment_method_types) || notNullish(checkoutParams.payment_method_configuration); - const sessionParams = { + let sessionParams = { customer: customer.processor.id, line_items: items, subscription_data: subscriptionData, @@ -145,6 +145,20 @@ export const handleCreateCheckout = async ({ : undefined, } satisfies Stripe.Checkout.SessionCreateParams; + if (attachParams.setupPayment) { + sessionParams = { + customer: customer.processor?.id, + mode: "setup", + success_url: successUrl || toSuccessUrl({ org, env: customer.env }), + currency: org.default_currency || "usd", + ...(checkoutParams as any), + metadata: { + ...(attachParams.checkoutSessionParams?.metadata || {}), + autumn_metadata_id: metaId, + }, + }; + } + try { checkout = await stripeCli.checkout.sessions.create(sessionParams); logger.info( diff --git a/server/src/internal/customers/attach/attachUtils/attachParams/getAttachParams.ts b/server/src/internal/customers/attach/attachUtils/attachParams/getAttachParams.ts index c977573a8..a63a7c9ce 100644 --- a/server/src/internal/customers/attach/attachUtils/attachParams/getAttachParams.ts +++ b/server/src/internal/customers/attach/attachUtils/attachParams/getAttachParams.ts @@ -80,6 +80,7 @@ export const getAttachParams = async ({ disableFreeTrial: attachBody.free_trial === false || false, checkoutSessionParams: attachBody.checkout_session_params, isCustom: attachBody.is_custom, + setupPayment: attachBody.setup_payment, }; return { diff --git a/server/src/internal/customers/attach/attachUtils/handleAttachErrors/handleCheckoutErrors.ts b/server/src/internal/customers/attach/attachUtils/handleAttachErrors/handleCheckoutErrors.ts new file mode 100644 index 000000000..4f2cbffdd --- /dev/null +++ b/server/src/internal/customers/attach/attachUtils/handleAttachErrors/handleCheckoutErrors.ts @@ -0,0 +1,14 @@ +import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js"; +import { AttachBranch } from "@autumn/shared"; + +export const handleCheckoutErrors = ({ + attachParams, + branch, +}: { + attachParams: AttachParams; + branch: AttachBranch; +}) => { + if (attachParams.setupPayment) { + // Make sure only usage prices are added? + } +}; diff --git a/server/src/internal/customers/cusProducts/AttachParams.ts b/server/src/internal/customers/cusProducts/AttachParams.ts index 388f061f0..104480130 100644 --- a/server/src/internal/customers/cusProducts/AttachParams.ts +++ b/server/src/internal/customers/cusProducts/AttachParams.ts @@ -76,6 +76,7 @@ export type AttachParams = { finalizeInvoice?: boolean; req?: any; fromCancel?: boolean; + setupPayment?: boolean; }; export type InsertCusProductParams = { diff --git a/shared/models/attachModels/attachBody.ts b/shared/models/attachModels/attachBody.ts index e102f941d..c50249dbb 100644 --- a/shared/models/attachModels/attachBody.ts +++ b/shared/models/attachModels/attachBody.ts @@ -56,6 +56,7 @@ export const AttachBodySchema = z // Checkout params skip_checkout: z.boolean().optional(), + setup_payment: z.boolean().optional(), }) .refine( (data) => {