{"record":{"id":"bdabac67c1fcb490","repo":"payloadcms/payload","slug":"cart-items-snapshot-not-found-or-invalid-in-the-pa","errorCode":null,"errorMessage":"Cart items snapshot not found or invalid in the PaymentIntent metadata","messagePattern":"Cart items snapshot not found or invalid in the PaymentIntent metadata","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts","lineNumber":97,"sourceCode":"      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,\n      })\n\n      const timestamp = new Date().toISOString()\n","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before: full cart embedded, risks truncation past 500 chars\nmetadata: { cartItemsSnapshot: JSON.stringify(largeCart) }\n// after: store a compact snapshot and hydrate from the cart record at confirm\nmetadata: { cartID: cart.id }\n// then in confirmOrder: const cart = await payload.findByID({ collection: cartsSlug, id: cartID })","handlingStrategy":"validation","validationCode":"function parseCartItemsSnapshot(raw: string | undefined): unknown[] {\n  if (!raw) throw new Error('cartItemsSnapshot metadata is missing')\n  let parsed: unknown\n  try {\n    parsed = JSON.parse(raw)\n  } catch {\n    throw new Error('cartItemsSnapshot metadata is not valid JSON (possibly truncated)')\n  }\n  if (!Array.isArray(parsed)) throw new Error('cartItemsSnapshot metadata is not an array')\n  return parsed\n}\n\n// keep snapshots small: store cartID only and hydrate from the carts collection at confirm","typeGuard":"export function isCartItemsSnapshot(value: unknown): value is unknown[] {\n  return Array.isArray(value)\n}","tryCatchPattern":"try {\n  await payments.confirmOrder({ data: { paymentIntentID } })\n} catch (err) {\n  if (err instanceof Error && /Cart items snapshot/.test(err.message)) {\n    // metadata was truncated or missing — hydrate from the cart record instead and retry\n    const cart = await payload.findByID({ collection: 'carts', id: cartID })\n    // re-initiate or call a custom confirm with the full items\n    return { ok: false, reason: 'snapshot-corrupt', cart }\n  }\n  throw err\n}","preventionTips":["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."],"tags":["stripe","metadata","cart","ecommerce","json","validation"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}