Stripe

Payments run through the tenant’s own Stripe account — Orchard orchestrates checkout and records orders, but funds settle to the tenant. Orchard never custodies money or touches card data (PCI SAQ A — see Security & compliance).

What the store owner configures

A store owner sets this up from Integrations → Stripe in the admin (/admin/settings/integrations/stripe), not you. It’s covered here so you know what state the integration can be in before you build against it.

  • Integration mode: direct or connect (see the callout below).
  • direct mode fields: the tenant’s own Stripe secret key (sk_...) and publishable key (pk_...).
  • connect mode fields: a connected account ID (acct_...) under an Orchard platform Stripe account.
  • Webhook signing secret (whsec_...) — required in both modes, generated by pointing a Stripe webhook endpoint at the URL below.
  • Test / live — each of the above is captured twice, once per Stripe environment, matching the test/live split of your own API keys.

connect mode is not a working checkout path today. The admin exposes it as a setup option, but the Stripe client Orchard uses for checkout and webhooks only reads a per-tenant secret key — it never applies the connected account’s Stripe-Account header. A tenant configured for connect mode has no secret key on file, so checkout will fail. Build against direct mode. If you’re evaluating Orchard for a multi-account/platform setup, confirm connect-mode support with your Orchard operator before committing to it.

Creating a checkout session

Your integration code doesn’t pick a mode or pass Stripe credentials — you call the same endpoint regardless of how the tenant is configured:

curl -X POST https://api-test.theorchard.dev/api/v1/checkout/sessions \
  -H "Authorization: Bearer sk_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [{ "product_slug": "coldbrew-concentrate", "quantity": 2 }],
    "return_url": "https://your-store.com/checkout/success",
    "cancel_url": "https://your-store.com/cart",
    "customer_email": "shopper@example.com"
  }'

Response:

{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_...",
  "order_id": "0191...o9",
  "order_number": "ORD-0001"
}

Redirect the shopper to checkout_url. See Cart to checkout to order for the full flow, and Subscriptions for recurring carts.

Inbound webhooks — Stripe to Orchard

Stripe delivers events to POST /api/webhooks/stripe/{tenant_slug}, verified with the tenant’s Stripe signing secret. This is internal to Orchard — it’s how Orchard learns a payment succeeded, a subscription renewed, or a dispute was opened, and it is a completely different thing from the outbound webhooks Orchard sends to your integration (order.paid, subscription.created, etc.). You never configure or consume the Stripe-inbound endpoint directly; it’s set up once by the store owner from the Stripe settings page, which shows the exact URL to paste into a Stripe webhook endpoint.

The signing secret can be rotated from the same page (TOTP-gated). Rotation creates a new Stripe webhook endpoint at the same URL, disables the prior one, and reveals the new secret once, in-page — it’s never placed in a URL or log.

What this means for you as an integrator

You build against one surface — /api/v1 and webhooks — and Stripe is resolved behind it based on your API key’s tenant. You never pass Stripe credentials or select a mode; that’s entirely the store owner’s configuration (with the connect-mode caveat above).