# Stripe payments, donations and subscriptions

Stripe Checkout for one-off purchases, donations with a progress bar, and subscriptions (memberships or monthly
giving). Stripe hosts the payment page, so cards, **Apple Pay**, **Google Pay** and Link work without any domain
verification and no card data touches the website.

Requires the `base` package.

## Install

Use the verified installer in the website container. It installs dependencies, checks checksums, and refuses to replace customer changes:

```sh
python /opt/runalio/catalog_install.py stripe-payments
python /opt/runalio/catalog_install.py --status
```

Review the preview and click Publish to promote backend code and pages. Production data remains separate.

To undo the last installation before publishing: `python /opt/runalio/catalog_install.py --rollback`.

## Settings

The customer enters these in Runalio, Settings → Website modules → this module (never in files or chat):

| Name | Required | Secret | Purpose |
|---|---|---|---|
| `STRIPE_SECRET_KEY` | yes | yes | Stripe secret key (sk_test_... first, then sk_live_...). |
| `STRIPE_WEBHOOK_SECRET` | yes | yes | Signing secret (whsec_...) of the webhook endpoint created in Stripe. |
| `STRIPE_PUBLISHABLE_KEY` | no | no | Only for custom Stripe.js integrations; Checkout does not need it. |
| `PAYMENT_RETURN_PATH` | no | no | Page visitors return to after paying, relative to the website, e.g. donate.html. Default: home page. |

## Configure what can be bought

Edit `/workspace/server/config/catalogue.json` (created from `catalogue.example.json`). Amounts are in cents and
are the only prices the server accepts; the browser can never change them.

```json
{"currency": "usd",
 "products": [{"id": "gift-card-50", "name": "Gift card", "amount": 5000, "max_quantity": 5}],
 "donations": {"enabled": true, "min": 500, "max": 1000000, "suggested": [2500, 5000, 10000], "goal": 1000000, "raised_before": 0},
 "subscriptions": [{"id": "monthly-supporter", "name": "Monthly supporter", "amount": 1000, "interval": "month"}]}
```

Set `"active": false` to hide an item. `raised_before` adds earlier donations to the progress bar.

## Add it to pages

Copy `site/payments.js` into `/workspace/site` and add `<script src="payments.js" defer></script>`, then:

```html
<button data-runalio-checkout data-product="gift-card-50">Buy a gift card</button>
<button data-runalio-checkout data-plan="monthly-supporter">Give monthly</button>
<form data-runalio-donate><label>Amount (USD) <input name="amount" type="number" min="5" step="1" required></label> <button>Donate</button></form>
<div data-runalio-donation-progress></div>
<p data-runalio-payment-status role="status"></p>
```

Visitors return to `PAYMENT_RETURN_PATH` (default the home page) with `?payment=success`; the status element then
confirms the payment.

## API (relative to the website)

| Request | Purpose |
|---|---|
| `GET api/payments/config` | Public catalogue, donation limits, which providers are set up |
| `POST api/payments/stripe/checkout` | `{product_id, quantity}`, `{donation_amount}` (cents) or `{subscription_id}`, returns `{url}` |
| `GET api/payments/stripe/session?session_id=` | Result for the return page (also records a payment whose webhook is late) |
| `POST api/payments/stripe/webhook` | Stripe events, signature-checked |
| `GET api/payments/totals` | Donations raised, number of gifts, goal |

Payments are recorded only after Stripe confirms them (webhook or return-page check), once per checkout, and appear
in the admin area under Payments, with CSV export. Refunds made in Stripe are marked as refunded.

## What the customer needs to do

1. Create a Stripe account at stripe.com (complete the business details before taking live payments).
2. In Runalio, Settings → Website modules → Stripe: add `STRIPE_SECRET_KEY` with the **test** secret key (`sk_test_...`).
3. In the Stripe Dashboard, Developers, Webhooks, add an endpoint:
   - URL: `<website address>api/payments/stripe/webhook` (for example `https://runalio.ai/sites/<slug>/api/payments/stripe/webhook`, or `https://their-domain.com/api/payments/stripe/webhook`)
   - Events: `checkout.session.completed`, `checkout.session.async_payment_succeeded`, `invoice.paid`, `charge.refunded`
   - Then add its signing secret as `STRIPE_WEBHOOK_SECRET`.
4. Publish the website and pay with the test card `4242 4242 4242 4242` (any future date, any CVC).
5. Replace both settings with the **live** key and the live webhook's secret. Stripe emails receipts if enabled in
   Stripe, Settings, Customer emails.

A paid Runalio package is needed so the backend (and its webhook) is always on.
