{"record":{"id":"250ca595b487e8f9","repo":"payloadcms/payload","slug":"a-valid-amount-is-required-to-initiate-a-payment","errorCode":null,"errorMessage":"A valid amount is required to initiate a payment.","messagePattern":"A valid amount is required to initiate a payment\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-ecommerce/src/payments/adapters/stripe/initiatePayment.ts","lineNumber":42,"sourceCode":"\n    if (!secretKey) {\n      throw new Error('Stripe secret key is required.')\n    }\n\n    if (!currency) {\n      throw new Error('Currency is required.')\n    }\n\n    if (!cart || !cart.items || cart.items.length === 0) {\n      throw new Error('Cart is empty or not provided.')\n    }\n\n    if (!customerEmail || typeof customerEmail !== 'string') {\n      throw new Error('A valid customer email is required to make a purchase.')\n    }\n\n    if (!amount || typeof amount !== 'number' || amount <= 0) {\n      throw new Error('A valid amount is required to initiate a payment.')\n    }\n\n    const stripe = new Stripe(secretKey, {\n      // API version can only be the latest, stripe recommends ts ignoring it\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore - ignoring since possible versions are not type safe, only the latest version is recognised\n      apiVersion: apiVersion || '2025-06-30.preview',\n      appInfo: appInfo || {\n        name: 'Stripe Payload Plugin',\n        url: 'https://payloadcms.com',\n      },\n    })\n\n    try {\n      let customer = (\n        await stripe.customers.list({\n          email: customerEmail,\n        })","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-ecommerce/src/payments/adapters/stripe/initiatePayment.ts#L24-L60","documentation":"Thrown by Stripe initiatePayment when amount (cart.subtotal) is falsy, not a number, or <= 0. Stripe requires a positive integer amount in the smallest currency unit. The adapter derives amount = cart.subtotal at the top of the function, so a cart without a numeric subtotal, or with subtotal 0, triggers this guard before any Stripe call.","triggerScenarios":"cart.subtotal is undefined, null, a string, 0, or negative when initiatePayment runs. The check is `!amount || typeof amount !== 'number' || amount <= 0`.","commonSituations":"Cart subtotal not computed before checkout (client relies on server to total); free-cart edge case where subtotal legitimately is 0 (Stripe does not support zero-amount intents — use a different flow); subtotal stored as a string from a form; rounding producing 0 for tiny carts; currency-conversion bug yielding 0.","solutions":["Compute cart.subtotal server-side as a positive integer (cents) before initiatePayment.","For zero-amount carts (free items), short-circuit: skip Stripe and create the order directly.","Ensure subtotal is a Number, not a string, before calling initiate.","Validate amount >= 50 (Stripe's minimum in many currencies) to avoid Stripe-side rejections."],"exampleFix":"// before: subtotal undefined or string\nconst cart = { items, subtotal: undefined }\n// after: compute positive integer cents\nconst subtotal = items.reduce((s, i) => s + i.price * i.quantity, 0)\nif (!(subtotal > 0)) throw new Error('Invalid subtotal')\nconst cart = { items, subtotal }","handlingStrategy":"validation","validationCode":"function resolveAmount(subtotal: unknown): number {\n  if (typeof subtotal !== 'number' || !Number.isFinite(subtotal) || subtotal <= 0) {\n    throw new Error('A positive numeric subtotal (in smallest currency unit) is required')\n  }\n  return Math.round(subtotal)\n}\n\n// before initiatePayment — for zero-amount carts, skip Stripe and create the order directly\ndata.cart.subtotal = resolveAmount(data.cart.subtotal)","typeGuard":"export function isPositiveAmount(value: unknown): value is number {\n  return typeof value === 'number' && Number.isFinite(value) && value > 0\n}","tryCatchPattern":"try {\n  await payments.initiatePayment({ data })\n} catch (err) {\n  if (err instanceof Error && /valid amount/.test(err.message)) {\n    if (cart.subtotal === 0) {\n      // free order — bypass Stripe\n      return await createFreeOrder(cart)\n    }\n    return { ok: false, reason: 'invalid-amount' }\n  }\n  throw err\n}","preventionTips":["Compute cart.subtotal server-side as a positive integer (cents) before initiatePayment.","Short-circuit zero-amount carts to a non-Stripe order flow (Stripe does not allow 0-amount intents).","Ensure subtotal is a Number, not a string, before calling initiate.","Validate amount >= Stripe's minimum for the currency to avoid downstream rejections."],"tags":["stripe","amount","validation","cart","ecommerce"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}