Volley / Templates / Stripe Checkout

Payments template

Stripe Checkout sequence diagram

The whole hosted Checkout flow, from the Buy button to fulfilment, with the session request, both redirects and the checkout.session.completed webhook on their arrows.

Sequence diagram of Stripe Checkout across Customer, Browser, Server and Stripe: create a Checkout Session, redirect to Stripe, pay, redirect to the success page, then the checkout.session.completed webhook and a 200 OK.

Click any arrow in the editor to see its request and response as a table or the raw body. Change the hosts, ids and fields to match your integration, then share a link or embed it in your README.

Step by step

  1. The browser asks your server for a checkout. It sends the price and quantity, never an amount the customer could edit.
  2. Your server creates a Checkout Session. A form-encoded POST /v1/checkout/sessions with mode, line_items, success_url and cancel_url. Stripe answers with the session, including its url.
  3. The browser goes to Stripe. A 303 redirect to the session URL, where the customer pays on Stripe's page.
  4. Stripe sends the customer back to your success_url, with the session id filled in for {CHECKOUT_SESSION_ID}.
  5. Stripe calls your webhook. checkout.session.completed arrives with a Stripe-Signature header. Verify it, fulfil the order and answer 200.

Easy to get wrong

  • Fulfil from the webhook, not the success page. Customers close tabs, and the redirect can fail.
  • Webhooks can arrive more than once and out of order. Fulfil once per session id.
  • Verify the signature against the raw request body. Parsing the JSON first changes the bytes and the check fails.

The source

The whole diagram is this plain text. Copy it into your repo, or open it in the editor.

stripe-checkout.volley
title: Stripe Checkout with a fulfilment webhook
order: Customer, Browser, Server, Stripe

Customer -> Browser: Click **Buy now**
Browser -> Server: POST /checkout
in: Cart
```json
{ "price": "price_1QAbc2Def3", "quantity": 1 }
```

Server -> Stripe: POST /v1/checkout/sessions
in: Session request (form encoded)
  mode: payment
  line_items[0][price]: price_1QAbc2Def3
  line_items[0][quantity]: 1
  success_url: https://shop.example.com/success?session_id={CHECKOUT_SESSION_ID}
  cancel_url: https://shop.example.com/cart

Stripe --> Server: 200 OK
out: Checkout Session
```json
{
  "id": "cs_test_a1B2c3D4",
  "object": "checkout.session",
  "status": "open",
  "payment_status": "unpaid",
  "url": "https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4"
}
```

Server --> Browser: 303 to Stripe Checkout
out: Redirect
  Location: https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4

Browser -> Stripe: GET /c/pay/cs_test_a1B2c3D4
...: {fas-credit-card} Customer enters a card and pays

Stripe --> Browser: 303 to the success URL
out: Redirect
  Location: https://shop.example.com/success?session_id=cs_test_a1B2c3D4

Browser -> Server: GET /success?session_id=cs_test_a1B2c3D4
Server --> Browser: Thank-you page
note: Do not fulfil the order here. The customer can close the tab before this page loads.

=: {fab-stripe} Fulfilment

Stripe -> Server: POST /webhooks/stripe (checkout.session.completed)
in: Event
```http
POST /webhooks/stripe HTTP/1.1
Content-Type: application/json
Stripe-Signature: t=1760608800,v1=<redacted>

{
  "id": "evt_1QAbc9",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_test_a1B2c3D4",
      "payment_status": "paid",
      "amount_total": 4900,
      "currency": "usd",
      "customer_details": { "email": "ada@example.com" }
    }
  }
}
```

Server -> Server: Verify the signature and fulfil the order
note: Verify `Stripe-Signature` with the endpoint's signing secret, then fulfil once per session id. Stripe can deliver the same event more than once.

Server --> Stripe: 200 OK

Related

Make this Stripe Checkout diagram yours.

Open it in the editor, change it to match your integration and share the link. Free, with no account.