prelo
Plugins / Stripe checkout

Stripe checkout

Sell products or accept donations with Stripe-hosted checkout pages.

Rgithub.com/rgoodeIntegrationsprelo >= 0.4Updated Jul 2026
Use this plugin
1
2
or point your agent at the spec:
Your agent reads the spec and writes the code into your project. Review the diff like any change.

What this adds

"Buy" buttons on your site that hand off to Stripe's hosted checkout — products, one-off payments, or donations. Products are content the owner edits in Studio (name, price, image); orders land in an app table you can inspect. Card data never touches your server.

Requires a Stripe account — a paid third-party service (per-transaction fees). Clearly out of scope for projects that must stay account-free.

How it works

A product content type carries the catalog. A custom route creates a Stripe Checkout Session server-side using your secret key from an env var; the buyer pays on Stripe's page and returns to yours. A webhook route verifies Stripe's signature and records completed orders in an x_orders table.

The spec your agent followsexpand ▾
You are adding Stripe-hosted checkout to a Prelo project. Env vars: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, plus `SITE_URL` for return URLs. Use Stripe's REST API directly with `fetch` — do not add the `stripe` npm package unless the user asks (keeps the dependency budget).

1. **Content type.** In `cms.config.js`:

   ```js
   product: {
     label: "Products",
     fields: {
       price:       { type: "number", required: true },  // in cents
       currency:    { type: "select", options: ["usd", "eur", "gbp"], required: true },
       image:       { type: "image" },
       description: { type: "text" },
       mode:        { type: "select", options: ["payment", "donation"] },
     },
     studio: { columns: ["price", "currency", "mode"] },
   },
   ```

   Published = buyable. Prices live in the CMS (created inline on the Checkout Session), so the owner edits them in Studio without touching the Stripe dashboard.

2. **Orders table.** `hooks.boot`:

   ```js
   db.exec(`CREATE TABLE IF NOT EXISTS x_orders (
     id INTEGER PRIMARY KEY, session_id TEXT UNIQUE, product_slug TEXT,
     amount INTEGER, currency TEXT, email TEXT, status TEXT, created INTEGER)`);
   ```

3. **Checkout route.** `"POST /checkout"` in `routes:`:
   - Body: `{ slug }`. Look up the **published** product; 404 otherwise.
   - `POST https://api.stripe.com/v1/checkout/sessions` (form-encoded, `Authorization: Bearer STRIPE_SECRET_KEY`) with an inline `line_items[0][price_data]` from the product's price/currency/title, `mode=payment`, `success_url={SITE_URL}/thanks?session={CHECKOUT_SESSION_ID}`, `cancel_url` = the product page. For `mode: "donation"` products, use `submit_type=donate` and add a small preset-amount picker client-side that posts an `amount` override (validate: 100–50000 cents).
   - Insert a `pending` row in `x_orders`; respond `{ url }` and have the client redirect to it.
   - If `STRIPE_SECRET_KEY` is unset, respond `503` with a message naming the missing env var.

4. **Webhook route.** `"POST /stripe-webhook"`:
   - Verify the `Stripe-Signature` header against `STRIPE_WEBHOOK_SECRET` (HMAC-SHA256 of `{timestamp}.{payload}` per Stripe's scheme — implement it; ~15 lines). Reject on mismatch or >5-minute timestamp skew.
   - On `checkout.session.completed`, update the matching `x_orders` row: status `paid`, buyer email from the event. Always respond `200` fast.
   - Register the endpoint URL (`{CMS_URL}/api/x/stripe-webhook`) in a code comment + README note for the user to add in the Stripe dashboard.

5. **Frontend.** A product page/section rendering title, image (`?w=800`), description, formatted price, and a Buy button that POSTs `{slug}` to `/api/x/checkout` and redirects to the returned URL. A `/thanks` page confirming the purchase. Disable the button in-flight; surface the 503 message plainly.

6. **Verify.** With Stripe test keys: buy a product with card `4242 4242 4242 4242`, confirm the redirect round-trip, the webhook firing (Stripe CLI `stripe listen --forward-to` in dev), and the `x_orders` row flipping to `paid`.

After it's built

The owner adds and edits products in Studio; unpublishing takes one off sale. Orders are in x_orders (query it, or ask your agent to add a read-only Studio-adjacent report). Refunds and receipts stay in the Stripe dashboard, which remains the financial source of truth.