payloadcms/payload · error · Error
Cart ID not found in the PaymentIntent metadata
Error message
Cart ID not found in the PaymentIntent metadata
What it means
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.
Source
Thrown at packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts:93
}
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentID)
if (paymentIntent.status !== 'succeeded') {
throw new Error(`Payment not completed.`)
}
const cartID = paymentIntent.metadata.cartID
const cartItemsSnapshot = paymentIntent.metadata.cartItemsSnapshot
? JSON.parse(paymentIntent.metadata.cartItemsSnapshot)
: undefined
const shippingAddress = paymentIntent.metadata.shippingAddress
? JSON.parse(paymentIntent.metadata.shippingAddress)
: undefined
if (!cartID) {
throw new Error('Cart ID not found in the PaymentIntent metadata')
}
if (!cartItemsSnapshot || !Array.isArray(cartItemsSnapshot)) {
throw new Error('Cart items snapshot not found or invalid in the PaymentIntent metadata')
}
const order = await payload.create({
collection: ordersSlug,
data: {
amount: paymentIntent.amount,
currency: paymentIntent.currency.toUpperCase(),
...(req.user ? { customer: req.user.id } : { customerEmail }),
items: cartItemsSnapshot,
shippingAddress,
status: 'processing',
transactions: [transaction.id],
},
req,View on GitHub (pinned to 00c58b35c0)
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.
Example fix
// before: custom intent creation omits metadata
const intent = await stripe.paymentIntents.create({ amount, currency })
// after: include the plugin's expected metadata keys
const intent = await stripe.paymentIntents.create({
amount,
currency,
metadata: {
cartID: cart.id,
cartItemsSnapshot: JSON.stringify(items),
shippingAddress: JSON.stringify(shippingAddress),
},
}) Defensive patterns
Strategy: validation
Validate before calling
import Stripe from 'stripe'
async function ensureCartMetadata(stripe: Stripe, paymentIntentID: string): Promise<{ cartID: string }> {
const intent = await stripe.paymentIntents.retrieve(paymentIntentID)
const cartID = intent.metadata?.cartID
if (typeof cartID !== 'string' || cartID.length === 0) {
throw new Error('PaymentIntent is missing metadata.cartID; create it via initiatePayment')
}
return { cartID }
} Type guard
export function hasCartMetadata(intent: { metadata?: Record<string, unknown> }): intent is { metadata: { cartID: string } } {
return typeof intent.metadata?.cartID === 'string' && intent.metadata.cartID.length > 0
} Try / catch
try {
await payments.confirmOrder({ data: { paymentIntentID } })
} catch (err) {
if (err instanceof Error && /Cart ID not found in the PaymentIntent metadata/.test(err.message)) {
// intent created outside the plugin — record and route to manual handling
return { ok: false, reason: 'missing-cart-metadata', requiresManualReview: true }
}
throw err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Cart items snapshot not found or invalid in the PaymentInten
- No transaction found for the provided PaymentIntent ID
- Cart is empty or not provided.
- A valid amount is required to initiate a payment.
- Stripe secret key is required
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/92668a3b6b24445a.
Report an issue: GitHub.