{"record":{"id":"d64eaf70389249dc","repo":"payloadcms/payload","slug":"payment-not-completed","errorCode":null,"errorMessage":"Payment not completed.","messagePattern":"Payment not completed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts","lineNumber":80,"sourceCode":"        collection: transactionsSlug,\n        req,\n        where: {\n          'stripe.paymentIntentID': {\n            equals: paymentIntentID,\n          },\n        },\n      })\n\n      const transaction = transactionsResults.docs[0]\n\n      if (!transactionsResults.totalDocs || !transaction) {\n        throw new Error('No transaction found for the provided PaymentIntent ID')\n      }\n\n      const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentID)\n\n      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      }","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts#L62-L98","documentation":"Thrown by Stripe confirmOrder after retrieving the PaymentIntent: if paymentIntent.status !== 'succeeded' the adapter refuses to create an order. Confirming an order requires a successfully captured payment; any other status (requires_payment_method, requires_action, canceled, processing) is rejected. This prevents creating orders for incomplete or abandoned payments.","triggerScenarios":"Calling confirmOrder before the customer has completed the Stripe checkout/3DS challenge (status still requires_action), after a failed card (requires_payment_method), on a canceled intent, or while the intent is still processing. Also triggered by polling confirm too early in a redirect flow.","commonSituations":"Client calls confirm immediately after initiate without waiting for the user to finish payment; webhook-based confirm firing on payment_intent.payment_failed; 3DS authentication abandoned; Stripe still processing the capture.","solutions":["Only call confirmOrder after receiving a payment_intent.succeeded webhook (the reliable signal).","If polling client-side, retry confirm with backoff until status is succeeded or a terminal failure occurs.","Handle requires_action by redirecting the user to the next-action URL to complete authentication.","Inspect the intent status from the Stripe dashboard or via stripe.paymentIntents.retrieve to see why it is not succeeded."],"exampleFix":"// before: confirm right after redirect without checking status\nawait payments.confirmOrder({ data: { paymentIntentID } })\n// after: drive confirm off the succeeded webhook\napp.post('/webhooks/stripe', async (req, res) => {\n  const event = stripe.webhooks.constructEvent(...)\n  if (event.type === 'payment_intent.succeeded') {\n    await payments.confirmOrder({\n      data: { paymentIntentID: event.data.object.id },\n    })\n  }\n  res.json({ received: true })\n})","handlingStrategy":"validation","validationCode":"import Stripe from 'stripe'\n\nasync function ensurePaymentSucceeded(stripe: Stripe, paymentIntentID: string): Promise<Stripe.PaymentIntent> {\n  const intent = await stripe.paymentIntents.retrieve(paymentIntentID)\n  if (intent.status !== 'succeeded') {\n    throw new Error(`Payment not yet succeeded (status: ${intent.status})`)\n  }\n  return intent\n}\n\n// call before confirmOrder, or drive confirm from the succeeded webhook","typeGuard":"export function isSucceeded(intent: { status: string }): boolean {\n  return intent.status === 'succeeded'\n}","tryCatchPattern":"try {\n  await payments.confirmOrder({ data: { paymentIntentID } })\n} catch (err) {\n  if (err instanceof Error && /Payment not completed/.test(err.message)) {\n    // not terminal yet — reschedule, or wait for the webhook\n    return { ok: false, reason: 'payment-pending', retryAfter: 5 }\n  }\n  throw err\n}","preventionTips":["Drive confirmOrder from the payment_intent.succeeded webhook, not a client timer.","Handle requires_action by sending the user to the next_action redirect URL.","On canceled/failed intents, surface a terminal error instead of retrying confirm.","Log the intent status at confirm time to triage premature confirm attempts."],"tags":["stripe","payment-status","checkout","ecommerce","webhook"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}