{"record":{"id":"92668a3b6b24445a","repo":"payloadcms/payload","slug":"cart-id-not-found-in-the-paymentintent-metadata","errorCode":null,"errorMessage":"Cart ID not found in the PaymentIntent metadata","messagePattern":"Cart ID not found in the PaymentIntent metadata","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts","lineNumber":93,"sourceCode":"      }\n\n      const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentID)\n\n      if (paymentIntent.status !== 'succeeded') {\n        throw new Error(`Payment not completed.`)\n      }\n\n      const cartID = paymentIntent.metadata.cartID\n      const cartItemsSnapshot = paymentIntent.metadata.cartItemsSnapshot\n        ? JSON.parse(paymentIntent.metadata.cartItemsSnapshot)\n        : undefined\n\n      const shippingAddress = paymentIntent.metadata.shippingAddress\n        ? JSON.parse(paymentIntent.metadata.shippingAddress)\n        : undefined\n\n      if (!cartID) {\n        throw new Error('Cart ID not found in the PaymentIntent metadata')\n      }\n\n      if (!cartItemsSnapshot || !Array.isArray(cartItemsSnapshot)) {\n        throw new Error('Cart items snapshot not found or invalid in the PaymentIntent metadata')\n      }\n\n      const order = await payload.create({\n        collection: ordersSlug,\n        data: {\n          amount: paymentIntent.amount,\n          currency: paymentIntent.currency.toUpperCase(),\n          ...(req.user ? { customer: req.user.id } : { customerEmail }),\n          items: cartItemsSnapshot,\n          shippingAddress,\n          status: 'processing',\n          transactions: [transaction.id],\n        },\n        req,","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts#L75-L111","documentation":"Thrown by Stripe confirmOrder when paymentIntent.metadata.cartID is missing. The adapter stores cartID in PaymentIntent metadata during initiatePayment so that, on confirm, it can mark the source cart as purchased. A missing cartID means the metadata contract was broken — the intent was created without the plugin's normal flow, or metadata was stripped.","triggerScenarios":"confirmOrder is invoked on an intent whose metadata lacks cartID. This happens if the intent was created outside initiatePayment, if a custom initiate path omitted the cartID metadata, or if metadata was overwritten/lost in a Stripe migration or manual edit.","commonSituations":"Custom Stripe integration that reuses confirmOrder but not initiatePayment; intents created in the Stripe dashboard for testing; a fork of the plugin that dropped the metadata.cartID line; Stripe metadata size limits truncating large carts (metadata values are capped at 500 chars and 50 keys).","solutions":["Always create intents through initiatePayment so metadata.cartID is set from cart.id.","If you have a custom flow, set metadata: { cartID, cartItemsSnapshot, shippingAddress } when creating the PaymentIntent.","For test intents created in the dashboard, populate the cartID metadata field manually before confirming.","Verify metadata on the intent via stripe.paymentIntents.retrieve to confirm cartID is present."],"exampleFix":"// before: custom intent creation omits metadata\nconst intent = await stripe.paymentIntents.create({ amount, currency })\n// after: include the plugin's expected metadata keys\nconst intent = await stripe.paymentIntents.create({\n  amount,\n  currency,\n  metadata: {\n    cartID: cart.id,\n    cartItemsSnapshot: JSON.stringify(items),\n    shippingAddress: JSON.stringify(shippingAddress),\n  },\n})","handlingStrategy":"validation","validationCode":"import Stripe from 'stripe'\n\nasync function ensureCartMetadata(stripe: Stripe, paymentIntentID: string): Promise<{ cartID: string }> {\n  const intent = await stripe.paymentIntents.retrieve(paymentIntentID)\n  const cartID = intent.metadata?.cartID\n  if (typeof cartID !== 'string' || cartID.length === 0) {\n    throw new Error('PaymentIntent is missing metadata.cartID; create it via initiatePayment')\n  }\n  return { cartID }\n}","typeGuard":"export function hasCartMetadata(intent: { metadata?: Record<string, unknown> }): intent is { metadata: { cartID: string } } {\n  return typeof intent.metadata?.cartID === 'string' && intent.metadata.cartID.length > 0\n}","tryCatchPattern":"try {\n  await payments.confirmOrder({ data: { paymentIntentID } })\n} catch (err) {\n  if (err instanceof Error && /Cart ID not found in the PaymentIntent metadata/.test(err.message)) {\n    // intent created outside the plugin — record and route to manual handling\n    return { ok: false, reason: 'missing-cart-metadata', requiresManualReview: true }\n  }\n  throw err\n}","preventionTips":["Always create PaymentIntents via initiatePayment so metadata.cartID is set.","If you build a custom flow, include metadata: { cartID, cartItemsSnapshot, shippingAddress }.","Never let test intents from the Stripe dashboard reach the production confirm endpoint.","Periodically reconcile intents without cartID metadata as a data-quality check."],"tags":["stripe","metadata","cart","ecommerce","data-integrity"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}