Payment events and reconciliation Required
Let provider events move the order
Fulfil, grant access or mark a refund only when a verified provider event or API read says the money moved. Map each event type to one order transition.
How to do it
The rule confirm payments on the server explains why the success page proves nothing. Verify each event as in signed webhooks. Handle duplicates as in idempotent receivers, and expect any order, as in event order. Keep one table from event type to transition. Before acting, read the payment from the provider's API and compare amount, currency and order ID with the order. Answer 2xx within 5 seconds and queue the work. Log event types you do not handle, and ignore them.
Example
const EVENT_TRANSITIONS = {
'payment_intent.processing': 'processing',
'payment_intent.succeeded': 'paid',
'payment_intent.payment_failed': 'payment_failed',
}
async function onPaymentEvent(event) {
const to = EVENT_TRANSITIONS[event.type]
if (!to) return logIgnoredEvent(event.type)
const payment = await stripe.paymentIntents.retrieve(event.data.object.id)
const order = await orders.get(payment.metadata.orderId)
if (payment.amount !== order.totalMinor || payment.currency !== order.currency.toLowerCase()) {
throw new Error(`Payment ${payment.id} does not match order ${order.id}`)
}
await moveOrder(db, order.id, order.state, to, event.id)
}
EVENT_TRANSITIONS = {
"payment_intent.processing": "processing",
"payment_intent.succeeded": "paid",
"payment_intent.payment_failed": "payment_failed",
}
def on_payment_event(event) -> None:
to_state = EVENT_TRANSITIONS.get(event["type"])
if to_state is None:
return log_ignored_event(event["type"])
payment = stripe.PaymentIntent.retrieve(event["data"]["object"]["id"])
order = orders.get(payment.metadata["order_id"])
if payment.amount != order.total_minor or payment.currency != order.currency.lower():
raise PaymentMismatch(payment.id, order.id)
move_order(db, order.id, order.state, to_state, event["id"])
How to check
Send a signed test event for a payment whose amount differs from the order. The handler refuses to mark the order paid. Every event type enabled on the webhook endpoint appears in the mapping table or in an explicit ignore list.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers payments playbook: https://agentwranglers.se/playbook/payments/payment-events-and-reconciliation/events-drive-fulfilment.