payloadcms/payload · error · Error
Cart items snapshot not found or invalid in the PaymentInten
Error message
Cart items snapshot not found or invalid in the PaymentIntent metadata
What it means
Thrown by Stripe confirmOrder when paymentIntent.metadata.cartItemsSnapshot is missing or not an array after JSON.parse. confirmOrder reconstructs the cart contents from this snapshot to populate the order's items, so a missing/malformed snapshot makes order creation unsafe. The snapshot is written by initiatePayment as a JSON-stringified array in the PaymentIntent metadata.
Source
Thrown at packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts:97
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,
})
const timestamp = new Date().toISOString()
View on GitHub (pinned to 00c58b35c0)
Solutions
- Keep cart snapshots small: store a reference (e.g. cart ID) in metadata and fetch full items from the carts collection at confirm time instead of embedding the full array.
- Ensure initiatePayment writes cartItemsSnapshot as JSON.stringify(arrayOfItems).
- If you must embed large carts, split across multiple metadata keys or use a side store.
- Validate the parsed value is an array before relying on it (the plugin does this; your flow should too).
Example fix
// before: full cart embedded, risks truncation past 500 chars
metadata: { cartItemsSnapshot: JSON.stringify(largeCart) }
// after: store a compact snapshot and hydrate from the cart record at confirm
metadata: { cartID: cart.id }
// then in confirmOrder: const cart = await payload.findByID({ collection: cartsSlug, id: cartID }) Defensive patterns
Strategy: validation
Validate before calling
function parseCartItemsSnapshot(raw: string | undefined): unknown[] {
if (!raw) throw new Error('cartItemsSnapshot metadata is missing')
let parsed: unknown
try {
parsed = JSON.parse(raw)
} catch {
throw new Error('cartItemsSnapshot metadata is not valid JSON (possibly truncated)')
}
if (!Array.isArray(parsed)) throw new Error('cartItemsSnapshot metadata is not an array')
return parsed
}
// keep snapshots small: store cartID only and hydrate from the carts collection at confirm Type guard
export function isCartItemsSnapshot(value: unknown): value is unknown[] {
return Array.isArray(value)
} Try / catch
try {
await payments.confirmOrder({ data: { paymentIntentID } })
} catch (err) {
if (err instanceof Error && /Cart items snapshot/.test(err.message)) {
// metadata was truncated or missing — hydrate from the cart record instead and retry
const cart = await payload.findByID({ collection: 'carts', id: cartID })
// re-initiate or call a custom confirm with the full items
return { ok: false, reason: 'snapshot-corrupt', cart }
}
throw err
} Prevention
- Store only a cart reference (cartID) in PaymentIntent metadata; fetch items from the cart at confirm time to avoid Stripe's 500-char limit.
- If you must embed items, keep the serialized payload well under 500 chars and validate JSON.parse in a try/catch.
- Add an admin check that flags intents whose metadata.cartItemsSnapshot fails to parse.
- Document the metadata contract for any custom Stripe integration so cartItemsSnapshot stays an array.
When it happens
Trigger: The PaymentIntent metadata.cartItemsSnapshot is absent, holds a non-array JSON value (object/string/number), or exceeds Stripe's 500-char metadata value cap and got truncated into invalid JSON. Also thrown if a custom flow set the field to a non-array.
Common situations: Large carts whose serialized JSON exceeds Stripe's 500-char-per-metadata-value limit and is silently truncated; intents created outside initiatePayment; a forked plugin that serialized items differently; corrupt metadata after a Stripe export/import.
Related errors
- Cart ID not found in the PaymentIntent metadata
- Cart is empty or not provided.
- A valid amount is required to initiate a payment.
- PaymentIntent ID is required
- Currency is required.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/bdabac67c1fcb490.
Report an issue: GitHub.