payloadcms/payload · error · Error

Payment not completed.

Error message

Payment not completed.

What it means

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.

Source

Thrown at packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts:80

        collection: transactionsSlug,
        req,
        where: {
          'stripe.paymentIntentID': {
            equals: paymentIntentID,
          },
        },
      })

      const transaction = transactionsResults.docs[0]

      if (!transactionsResults.totalDocs || !transaction) {
        throw new Error('No transaction found for the provided PaymentIntent ID')
      }

      const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentID)

      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')
      }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Only call confirmOrder after receiving a payment_intent.succeeded webhook (the reliable signal).
  2. If polling client-side, retry confirm with backoff until status is succeeded or a terminal failure occurs.
  3. Handle requires_action by redirecting the user to the next-action URL to complete authentication.
  4. Inspect the intent status from the Stripe dashboard or via stripe.paymentIntents.retrieve to see why it is not succeeded.

Example fix

// before: confirm right after redirect without checking status
await payments.confirmOrder({ data: { paymentIntentID } })
// after: drive confirm off the succeeded webhook
app.post('/webhooks/stripe', async (req, res) => {
  const event = stripe.webhooks.constructEvent(...)
  if (event.type === 'payment_intent.succeeded') {
    await payments.confirmOrder({
      data: { paymentIntentID: event.data.object.id },
    })
  }
  res.json({ received: true })
})
Defensive patterns

Strategy: validation

Validate before calling

import Stripe from 'stripe'

async function ensurePaymentSucceeded(stripe: Stripe, paymentIntentID: string): Promise<Stripe.PaymentIntent> {
  const intent = await stripe.paymentIntents.retrieve(paymentIntentID)
  if (intent.status !== 'succeeded') {
    throw new Error(`Payment not yet succeeded (status: ${intent.status})`)
  }
  return intent
}

// call before confirmOrder, or drive confirm from the succeeded webhook

Type guard

export function isSucceeded(intent: { status: string }): boolean {
  return intent.status === 'succeeded'
}

Try / catch

try {
  await payments.confirmOrder({ data: { paymentIntentID } })
} catch (err) {
  if (err instanceof Error && /Payment not completed/.test(err.message)) {
    // not terminal yet — reschedule, or wait for the webhook
    return { ok: false, reason: 'payment-pending', retryAfter: 5 }
  }
  throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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