Stripe event: checkout.session.completed, check payment_status before you fulfill
The session completed, but with async payment methods that does not always mean paid. Stripe's fulfillment guide says it directly: check payment_status to determine if fulfillment is required.
checkout.session.completed: verify payment_status, then fulfill once
The customer finished the Checkout Session. Before you hand over the goods, confirm the money.
What to do on receipt
- Fetch the Session from the API with
line_itemsexpanded. Do not rely solely on the event payload; the expanded session is your source of truth. - Check
payment_status. Fulfill only when it ispaid. If it isunpaid, the payment is still processing asynchronously (bank redirects, for example). Wait forcheckout.session.async_payment_succeededinstead. - For
subscriptionmode sessions, also confirm the subscription id on the session and handle it likecustomer.subscription.created: provision onactive/trialingonly. - Dedupe by Checkout Session id, fulfill, record, return 200.
The trap
Fulfilling on completed without the payment_status check. With cards it is usually already paid, which is why this bug survives testing and then bites with the first bank-redirect customer. The other trap: fulfilling from the event payload's line items without expanding. The event does not include full line item detail; the expand does.
Checklist
- Your fulfill function must be idempotent by Session id: Stripe's guide calls this out explicitly because the event can arrive more than once.
checkout.session.expiredis the counterpart: the customer abandoned. Use it to release anything you held, not this handler.
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.