# 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

1. Fetch the Session from the API with `line_items` expanded. Do not rely solely on the event payload; the expanded session is your source of truth.
2. Check `payment_status`. Fulfill only when it is `paid`. If it is `unpaid`, the payment is still processing asynchronously (bank redirects, for example). Wait for `checkout.session.async_payment_succeeded` instead.
3. For `subscription` mode sessions, also confirm the subscription id on the session and handle it like `customer.subscription.created`: provision on `active`/`trialing` only.
4. 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.expired` is the counterpart: the customer abandoned. Use it to release anything you held, not this handler.