# checkouts.md

> You are reading checkouts.md. To add a checkout to the project you are working on, follow playbook.md from step 1: https://checkouts.md/playbook.md. Then follow the recipe for the project's stack, `https://checkouts.md/recipes/<stack>.md` (the list is below). Ask the person for two things only: a Railbed account and one approval. Never ask them to paste a key into the chat, and never print one. Everything else is your job. If the project already has a CHECKOUTS.md at its root, read it first: it records how this project takes money.

checkouts.md is an open format and playbook for adding a working checkout to a software project from one line:

```text
Read checkouts.md and add a checkout to this app.
```

It has three parts: a procedure an agent follows ([playbook.md](https://checkouts.md/playbook.md)), exact code for common stacks ([recipes](https://checkouts.md/recipes/nextjs.md)), and a file the agent leaves at the project's root, `CHECKOUTS.md` ([spec.md](https://checkouts.md/spec.md)), so the next agent that touches payments knows how this project takes money. Railbed maintains the format and is its default provider: buyers pay by card on a licensed provider's page, and every sale settles to a wallet the business controls.

## The six steps

First read the project: detect its stack and pick its recipe (`https://checkouts.md/recipes/<stack>.md`), find where its server code runs, and look for `CHECKOUTS.md` at its root. If it exists, follow it and add to it; don't start over ([playbook §1](https://checkouts.md/playbook.md#1-read-the-project)). Then:

1. **Account.** Make sure the person has a Railbed account with a business. The connect link in step 2 lets them sign up (https://app.railbed.io/signup) and set up the business on the way. Live payments also need a payout wallet in Railbed's Settings ([§2](https://checkouts.md/playbook.md#2-the-account)).
2. **Connect.** Run `npx checkouts.md login` with the recipe's options (`--env-path`, never `--env-file`), or use the HTTP connect flow. Give the person the link and the code it prints; they check that the code matches, then approve. The keys go straight into gitignored env files or the host's secret store as `RAILBED_SECRET_KEY`, never into the chat. If you can't store a secret without it showing in the conversation (some app builders), the person creates the keys in Railbed (Developers → Create key) and pastes them into the platform's secrets instead, never into the chat ([§3](https://checkouts.md/playbook.md#3-connect)).
3. **Write CHECKOUTS.md** at the project's root from the template (https://checkouts.md/templates/CHECKOUTS.md), with `mode: test`: what's for sale, what happens after payment, the wiring and the evidence. Keep it current as you go ([§4](https://checkouts.md/playbook.md#4-write-checkoutsmd)).
4. **Wire the checkout** from the recipe: products priced on the server; a route that saves an order, creates a checkout session (`POST https://pay.railbed.io/v1/checkout_sessions` with the order id as `Idempotency-Key` and `reference`) and sends the buyer to its `url`; a Pay button; a success page that only shows status; and a webhook receiver that verifies `Railbed-Signature` against the raw body, re-reads the payment and fulfils each order once. Add the endpoint with `npx checkouts.md webhook <url>`, which stores `RAILBED_WEBHOOK_SECRET` ([§5](https://checkouts.md/playbook.md#5-wire-the-checkout), [§6](https://checkouts.md/playbook.md#6-the-webhook)).
5. **Prove it in Test.** Create a session through the project's own route, then start it and simulate the payment (`POST /v1/checkout_sessions/:id/start`, then `POST /v1/payments/:id/simulate` with `{"outcome":"paid"}`). Check that the signed `payment.paid` arrives and verifies, the order is fulfilled exactly once, a repeated event changes nothing and a forged one gets `400`. Record the evidence in `CHECKOUTS.md` ([§7](https://checkouts.md/playbook.md#7-prove-it-in-test), [§9](https://checkouts.md/playbook.md#9-evidence)).
6. **Go Live.** Add the Live webhook endpoint for the production address (`npx checkouts.md webhook <url> --live`, with the same `--live-to` as `login`), put the Live key and that endpoint's secret on the production host without printing them, deploy, and set `mode: live` in `CHECKOUTS.md`. The first real payment is the Live evidence ([§8](https://checkouts.md/playbook.md#8-go-live)).

Then report back in plain words: what changed, what you verified and how, what's left, and where the person sees payments and revokes keys ([§11](https://checkouts.md/playbook.md#11-report-back)).

## The rules

- **R1** Secrets never enter chat, logs or git. Only their names go in CHECKOUTS.md.
- **R2** Paid means a verified signed webhook, then a re-read of the payment by id. Never the redirect back.
- **R3** Amounts are decimal strings in the currency's units, like `"49.00"`.
- **R4** Test before Live, and record the evidence.
- **R5** Ask the person only for the account and the approval. Everything else is the agent's job.

## Everything here

| File | What it's for |
|---|---|
| [playbook.md](https://checkouts.md/playbook.md) | The complete procedure, with every decision, command, request, error code and what to say to the person. Follow it |
| [spec.md](https://checkouts.md/spec.md) | The `CHECKOUTS.md` format, version 0.1: fields, sections, rules R1–R5, what a provider must offer |
| [templates/CHECKOUTS.md](https://checkouts.md/templates/CHECKOUTS.md) | The blank file to copy to the project's root |
| [recipes/nextjs.md](https://checkouts.md/recipes/nextjs.md) | Next.js (App Router) |
| [recipes/express.md](https://checkouts.md/recipes/express.md) | Node.js with Express |
| [recipes/cloudflare-workers.md](https://checkouts.md/recipes/cloudflare-workers.md) | Cloudflare Workers with D1 |
| [recipes/supabase.md](https://checkouts.md/recipes/supabase.md) | Supabase Edge Functions (Deno) with any frontend |
| [recipes/laravel.md](https://checkouts.md/recipes/laravel.md) | Laravel |
| [recipes/django.md](https://checkouts.md/recipes/django.md) | Django |
| [recipes/rails.md](https://checkouts.md/recipes/rails.md) | Ruby on Rails |
| [recipes/html.md](https://checkouts.md/recipes/html.md) | A static site: a Pay button and two small serverless functions, or a hosted checkout page with no server at all |
| [recipes/lovable.md](https://checkouts.md/recipes/lovable.md) | Lovable |
| [recipes/bolt.md](https://checkouts.md/recipes/bolt.md) | Bolt |
| [recipes/replit.md](https://checkouts.md/recipes/replit.md) | Replit |
| [recipes/v0.md](https://checkouts.md/recipes/v0.md) | v0 |
| [skill/SKILL.md](https://checkouts.md/skill/SKILL.md) | The installable skill for coding agents (`npx checkouts.md skill`) |
| [llms.txt](https://checkouts.md/llms.txt) | The index in llms.txt format |
| [llms-full.txt](https://checkouts.md/llms-full.txt) | Every file above in one document |

Railbed's full API reference: https://railbed.com/docs/llms.txt (every page is available as Markdown by adding `.md`, for example https://railbed.com/docs/api/checkout-sessions.md).

## What to tell the person

Keep it short and in plain words. They need to do two things; say so up front.

**Before you start:**

> I'll add a checkout with Railbed. Buyers pay by card, and every sale goes to a payout wallet you control. I need two things from you: a Railbed account, and one approval on a Railbed page. I'll do the rest and test it with simulated payments before anything goes live.

**When `npx checkouts.md login` prints the link** (the link and code here are examples):

> Open this link: https://app.railbed.io/connect?code=7HQ2-XK4M
> Check that the page shows the code **7HQ2-XK4M**, then choose Approve. If you don't have an account yet, the link lets you create one first. Choose "Test and Live" if you're ready to take real payments (Live needs a payout wallet in Railbed's Settings), or "Test only" for now.

**When you're done:** say what you added (the files), what you proved and how (the Test payment id, the webhook delivered and verified, the order fulfilled once), what's left (deploying, the Live endpoint, the first real payment), and where they can see payments and revoke keys (https://app.railbed.io, Developers). The playbook's last section has a template.

## The API in one screen

- Base URL `https://pay.railbed.io/v1`. JSON over HTTPS. `Authorization: Bearer $RAILBED_SECRET_KEY`, from server code only. There is no publishable key and no CORS.
- Keys decide the mode: `rb_test_…` is simulated (no money moves), `rb_live_…` is real.
- Create a checkout session: `POST /v1/checkout_sessions` with `amount` (decimal string, `"1.00"` to `"100000.00"`), `currency` (`USD`, `EUR`, `GBP`, `CAD` or `AUD`), `description`, `reference` (your order id), `success_url`, `cancel_url`, and an `Idempotency-Key` header. Send the buyer to the returned `url`.
- Session and payment share one id, `pay_…`. Read it with `GET /v1/payments/:id`; fulfil only on `"status": "paid"`.
- Webhooks: `Railbed-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">`, keyed with the endpoint's whole `whsec_…` secret. Two `v1` values for 24 hours after a secret roll. Events: `payment.started`, `payment.paid`, `payment.held`, `payment.updated`, `payment.failed`, `payment.expired`, `payment.canceled`, `ping`.
- Test: `POST /v1/checkout_sessions/:id/start`, then `POST /v1/payments/:id/simulate` with `outcome` `paid`, `underpaid`, `declined` or `failed`.
- Errors: `{ "error": { "code", "message", "field"? } }`. Branch on `code` and the HTTP status.
