Skip to main content

Payment Intents & Hosted Checkout

Wire card collection via Stripe PI (server-controlled) or Checkout (fastest path).

C
Written by Catalin Fetean
Updated over 3 weeks ago

Audience: Developers, Finance Admins
Prerequisites: Stripe account/keys; webhook endpoint configured
Outcomes: Card collection with automatic SCA and webhook reconciliation

Environment

STRIPE_SECRET_KEY=sk_live_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx

Payment Intents (server-controlled)

1) Create PI (server)

curl -X POST $API_BASE/api/payments/intents -b cookies.txt \ -H 'Content-Type: application/json' \ -d '{ "orderId":"ord_123", "amount":150000, "currency":"USD", "reference":"ORD123-DEP", "metadata":{"orderId":"ord_123"} }'

Returns PI details (and client_secret if applicable).

2) Confirm on client (Elements)

  • Collect card data with Stripe.js Elements (backend never sees PAN)

  • Stripe handles SCA/3DS; handle requires_action by showing the modal

3) Reconcile via webhook (authoritative)

  • Orders move to DepositPaid only after verified payment_intent.succeeded (see Article 3)

Edge cases

  • requires_action → show 3DS, then confirm

  • Canceled PI → create a new one with same reference (idempotent handler)

QA checklist

  • Success triggers payment.succeeded → order advances

  • Declines produce payment.failed with provider reason logged

Hosted Checkout (fastest path)

Create Checkout Session (server)

curl -X POST $API_BASE/api/payments/stripe/checkout-session -b cookies.txt \ -H 'Content-Type: application/json' \ -d '{ "orderId":"ord_123", "amount":150000, "currency":"USD", "successUrl":"https://app.example.com/success", "cancelUrl":"https://app.example.com/cancel", "reference":"ORD123-DEP" }' # => { "url": "https://checkout.stripe.com/c/..." }

Redirect buyer to url. Trust webhook, not the success redirect.

Pros: Stripe hosts page and SCA; typically low PCI scope (confirm SAQ with assessor).

QA checklist

  • Test success/cancel paths

  • Verify payment_intent.succeeded webhook updates the order

Runbook: “3DS loops or fails”

  • Ensure client handles requires_action

  • Test both 3DS required and non-3DS test cards

Did this answer your question?