diff --git a/server/src/internal/customers/add-product/handleCreateCheckout.ts b/server/src/internal/customers/add-product/handleCreateCheckout.ts index dab338416..6900aa9d5 100644 --- a/server/src/internal/customers/add-product/handleCreateCheckout.ts +++ b/server/src/internal/customers/add-product/handleCreateCheckout.ts @@ -12,7 +12,7 @@ import { ErrCode } from "@/errors/errCodes.js"; import { getNextStartOfMonthUnix } from "@/internal/products/prices/billingIntervalUtils.js"; import { APIVersion } from "@autumn/shared"; import { SuccessCode } from "@autumn/shared"; -import { notNullish, nullish } from "@/utils/genUtils.js"; +import { notNullish } from "@/utils/genUtils.js"; import Stripe from "stripe"; @@ -48,7 +48,6 @@ export const handleCreateCheckout = async ({ }); } - // Handle first item set const { items } = itemSets[0]; attachParams.itemSets = itemSets; @@ -96,7 +95,14 @@ export const handleCreateCheckout = async ({ }; } - const checkout = await stripeCli.checkout.sessions.create({ + // Prepare checkout session parameters + let checkout; + + let paymentMethodSet = + notNullish(checkoutParams.payment_method_types) || + notNullish(checkoutParams.payment_method_configuration); + + const sessionParams = { customer: customer.processor.id, line_items: items, subscription_data: subscriptionData, @@ -108,21 +114,36 @@ export const handleCreateCheckout = async ({ ...(attachParams.metadata ? attachParams.metadata : {}), }, allow_promotion_codes: allowPromotionCodes, + invoice_creation: !isRecurring ? { enabled: true } : undefined, + saved_payment_method_options: { payment_method_save: "enabled" }, ...rewardData, - invoice_creation: !isRecurring - ? { - enabled: true, - } - : undefined, - - saved_payment_method_options: { - payment_method_save: "enabled", - }, - ...(attachParams.checkoutSessionParams || {}), - }); + }; - logger.info(`✅ Successfully created checkout for customer ${customer.id}`); + try { + checkout = await stripeCli.checkout.sessions.create(sessionParams); + logger.info( + `✅ Successfully created checkout for customer ${customer.id || customer.internal_id}` + ); + } catch (error: any) { + let msg = error.message; + if ( + msg && + msg.includes("No valid payment method types") && + !paymentMethodSet + ) { + checkout = await stripeCli.checkout.sessions.create({ + ...sessionParams, + payment_method_types: ["card"], + }); + + logger.info( + `✅ Created fallback checkout session with card payment method for customer ${customer.id || customer.internal_id}` + ); + } else { + throw error; + } + } if (returnCheckout) { return checkout; diff --git a/server/src/internal/customers/attach/handleSetupPayment.ts b/server/src/internal/customers/attach/handleSetupPayment.ts index cd9380fb0..a66cc2c50 100644 --- a/server/src/internal/customers/attach/handleSetupPayment.ts +++ b/server/src/internal/customers/attach/handleSetupPayment.ts @@ -1,8 +1,10 @@ import { routeHandler } from "@/utils/routerUtils.js"; import { getOrCreateCustomer } from "../cusUtils/getOrCreateCustomer.js"; -import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js"; +import { ExtendedRequest } from "@/utils/models/Request.js"; import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js"; import { createStripeCli } from "@/external/stripe/utils.js"; +import RecaseError from "@/utils/errorUtils.js"; +import { ErrCode } from "@/errors/errCodes.js"; export const handleSetupPayment = async (req: any, res: any) => routeHandler({ @@ -10,8 +12,8 @@ export const handleSetupPayment = async (req: any, res: any) => res, action: "setup_payment", handler: async (req: ExtendedRequest, res: any) => { - // 1. Get the customer const { db, env, org } = req; + const logger = req.logger; let { customer_id, customer_data, success_url, checkout_session_params } = req.body; @@ -31,17 +33,73 @@ export const handleSetupPayment = async (req: any, res: any) => }); const stripeCli = createStripeCli({ org, env }); - const session = await stripeCli.checkout.sessions.create({ + + // check if user already specified payment methods in their request + const hasUserSpecifiedPaymentMethods = + checkout_session_params && checkout_session_params.payment_method_types; + + const sessionParams = { customer: customer.processor?.id, mode: "setup", success_url: success_url || org.stripe_config?.success_url, currency: org.default_currency || "usd", ...(checkout_session_params as any), - }); + }; - return res.json({ - customer_id: customer.id, - url: session.url, - }); + try { + // let stripe automatically determine payment methods + const session = await stripeCli.checkout.sessions.create(sessionParams); + return res.json({ + customer_id: customer.id, + url: session.url, + }); + } catch (error: any) { + // payment method errors + if (error.message && + (error.message.includes("payment method") || + error.message.includes("No valid payment"))) { + + logger.warn("Stripe checkout session creation failed", { + customerId: customer.id, + error: error.message, + }); + + if (hasUserSpecifiedPaymentMethods) { + throw error; + } + + try { + // card payment method fallback + const fallbackSession = await stripeCli.checkout.sessions.create({ + ...sessionParams, + payment_method_types: ["card"], + }); + + logger.info("Created checkout session with card payment method", { + customerId: customer.id, + }); + + return res.json({ + customer_id: customer.id, + url: fallbackSession.url, + }); + } catch (fallbackError: any) { + // if fallback failed + logger.error("Failed to create checkout session even with card payment method", { + customerId: customer.id, + error: fallbackError.message, + }); + + throw new RecaseError({ + code: ErrCode.InvalidRequest, + message: "Unable to create checkout session. Please ensure you have activated card payment method in your Stripe dashboard.", + statusCode: 400, + }); + } + } + + // Re-throw errors + throw error; + } }, });