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
- 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.
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
- 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.
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
- PaymentIntent ID is required
- Cart is empty or not provided.
- Stripe secret key is required
- No transaction found for the provided PaymentIntent ID
- Cart ID not found in the PaymentIntent metadata
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/d64eaf70389249dc.
Report an issue: GitHub.