{"record":{"id":"65b4edc893d2b8cd","repo":"payloadcms/payload","slug":"paymentintent-id-is-required","errorCode":null,"errorMessage":"PaymentIntent ID is required","messagePattern":"PaymentIntent ID is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts","lineNumber":33,"sourceCode":"    cartsSlug = 'carts',\n    data,\n    ordersSlug = 'orders',\n    req,\n    transactionsSlug = 'transactions',\n  }) => {\n    const payload = req.payload\n    const { apiVersion, appInfo, secretKey } = props || {}\n\n    const customerEmail = data.customerEmail\n\n    const paymentIntentID = data.paymentIntentID as string\n\n    if (!secretKey) {\n      throw new Error('Stripe secret key is required')\n    }\n\n    if (!paymentIntentID) {\n      throw new Error('PaymentIntent ID is required')\n    }\n\n    const stripe = new Stripe(secretKey, {\n      // API version can only be the latest, stripe recommends ts ignoring it\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore - ignoring since possible versions are not type safe, only the latest version is recognised\n      apiVersion: apiVersion || '2025-03-31.basil',\n      appInfo: appInfo || {\n        name: 'Stripe Payload Plugin',\n        url: 'https://payloadcms.com',\n      },\n    })\n\n    try {\n      let customer = (\n        await stripe.customers.list({\n          email: customerEmail,\n        })","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts#L15-L51","documentation":"Thrown by Stripe confirmOrder when data.paymentIntentID is falsy. confirmOrder needs the PaymentIntent ID (returned by initiatePayment) to look up the transaction and retrieve the intent from Stripe. Without it the adapter cannot correlate the order with a payment. This is an input-contract violation: the caller omitted the paymentIntentID field on the confirmOrder request body.","triggerScenarios":"A client calls the confirm-order endpoint without passing the paymentIntentID it received from initiatePayment, or passes it as an empty string/null. Also occurs if a frontend wires the confirm step to the wrong state variable.","commonSituations":"Frontend checkout flow loses the paymentIntentID between steps (page reload, state reset); a webhook handler calling confirmOrder without the ID; integration tests that skip the initiate step; misnamed fields (payment_intent_id vs paymentIntentID).","solutions":["Capture the paymentIntentID returned by initiatePayment and send it in the confirmOrder request body as paymentIntentID.","Validate the field client-side before calling confirm (non-empty string).","Store the paymentIntentID in a durable client store (sessionStorage / cookie) across the redirect flow.","If using a webhook, ensure the webhook reads the intent id from the Stripe event and forwards it."],"exampleFix":"// before: confirm call missing the id\nawait payments.confirmOrder({ data: {} })\n// after: thread the id from initiatePayment\nconst { paymentIntentID } = await payments.initiatePayment({ data })\nawait payments.confirmOrder({ data: { paymentIntentID } })","handlingStrategy":"validation","validationCode":"export function assertPaymentIntentID(value: unknown): asserts value is string {\n  if (typeof value !== 'string' || value.trim().length === 0) {\n    throw new Error('paymentIntentID is required and must be a non-empty string')\n  }\n}\n\n// before confirmOrder\nassertPaymentIntentID(data.paymentIntentID)\nawait payments.confirmOrder({ data })","typeGuard":"export function isPaymentIntentID(value: unknown): value is string {\n  return typeof value === 'string' && value.startsWith('pi_')\n}","tryCatchPattern":"try {\n  await payments.confirmOrder({ data: { paymentIntentID } })\n} catch (err) {\n  if (err instanceof Error && err.message === 'PaymentIntent ID is required') {\n    // client lost the id — re-initiate or surface a clear checkout error\n    return { ok: false, reason: 'missing-payment-intent', redirect: '/checkout' }\n  }\n  throw err\n}","preventionTips":["Persist paymentIntentID in sessionStorage/cookie across the Stripe redirect flow.","Validate the id client-side (starts with 'pi_', non-empty) before calling confirm.","Name the field exactly paymentIntentID (Stripe uses pi_ IDs; your contract uses paymentIntentID).","In integration tests, always chain initiate -> confirm to avoid orphan confirm calls."],"tags":["stripe","validation","checkout","ecommerce","input-contract"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}