Skip to content

Stripe

Stripe uses Checkout Sessions: the user pays on Stripe's hosted page and the source of truth is the webhook, never the redirect back.

Configuration

ts
import { StripeGateway } from '@kaizen/payments-gateway'

StripeGateway.register({
  secret_key: process.env.STRIPE_SECRET_KEY!,        // sk_test_… / sk_live_…
  webhook_secret: process.env.STRIPE_WEBHOOK_SECRET!, // whsec_…
})
FieldTypeDescription
secret_keystringStripe API secret key
webhook_secretstringWebhook endpoint secret, to verify the signature

Environment variables

You provide the values from your own environment and pass them to StripeGateway.register():

ini
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx
VariableFieldDescription
STRIPE_SECRET_KEYsecret_keyAPI secret key (sk_test_… / sk_live_…)
STRIPE_WEBHOOK_SECRETwebhook_secretWebhook endpoint secret (whsec_…) to verify the signature

Create a charge

bash
curl -X POST http://localhost:3000/payments/charge \
  -H 'content-type: application/json' \
  -d '{
    "gateway": "stripe",
    "amount": 4900,
    "currency": "eur",
    "reference": "order-001",
    "redirect": {
      "success_url": "http://localhost:3000/ok",
      "cancel_url": "http://localhost:3000/ko"
    }
  }'
  • amount is in the currency's smallest unit (4900 = €49.00).
  • redirect.success_url / cancel_url travel per payment (in the body), not in the config.
  • The response has a redirect_url (the Stripe Checkout): send the user there.

The response has the same shape as every other gateway:

json
{
  "payment_id": "b9c1…",
  "gateway": "stripe",
  "status": "pending",
  "redirect_url": "https://checkout.stripe.com/c/pay/…"
}

End-to-end flow locally (Stripe CLI)

  1. Start your consumer (pnpm start).

  2. Open the Stripe CLI tunnel and copy the whsec_… it prints into STRIPE_WEBHOOK_SECRET; restart so it's picked up:

    bash
    stripe listen --events checkout.session.completed \
      --forward-to localhost:3000/webhooks/stripe

    TIP

    --events filters to the only thing we handle. Without it, the CLI forwards every event and the gateway responds 400 to the ones it doesn't support.

  3. Create a payment (the curl above) and open the redirect_url.

  4. Pay with the test card 4242 4242 4242 4242, a future date and any CVC.

  5. Stripe fires checkout.session.completed → the CLI forwards it → the gateway verifies the signature over the rawBody, the core matches the Payment by gateway_ref (= session.id), moves it to captured and emits payment.captured.

  6. Replaying the same event does not re-process: idempotency by (gateway, event_id) returns 200.

Integration notes

  • client_reference_id carries your reference inside Stripe; gateway_ref is the session.id, which is what matches the webhook to the Payment.
  • createPayment sends an Idempotency-Key (your reference), so retries don't create duplicate Checkout sessions.
  • The signature is always verified with stripe.webhooks.constructEvent over the raw body. Invalid signature → 400.