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

  1. 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.
  2. Ensure initiatePayment writes cartItemsSnapshot as JSON.stringify(arrayOfItems).
  3. If you must embed large carts, split across multiple metadata keys or use a side store.
  4. 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

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


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/bdabac67c1fcb490. Report an issue: GitHub.