---
title: Payments and billing
description: A catalogue, charges and subscriptions, invoices, and payments online, by transfer or in cash.
section: Server
order: 9
---

# Payments and billing

<p class="lead">Billing is built in: a catalogue, charges, invoices, and payments online, by bank transfer or in cash. It fits anyone who bills people, from a school canteen to club memberships.</p>

## The pieces

| | |
|---|---|
| **Issuer** | The billing entity, e.g. a parents' committee or an after-school club. Each has its own bank account, invoice numbering and payment provider |
| **Item** | A catalogue entry with a price and a unit: each, day, month or year |
| **Account** | Who a charge is for, e.g. a student. Payers are resolved from the app's own data (`custodies` by default), so billing doesn't keep a separate list |
| **Charge** | One item billed to one account: posted manually (optionally for a whole group) or generated from a subscription |
| **Subscription** | A recurring charge: meals on Mondays and Thursdays, a monthly fee, a year of chess club |
| **Invoice** | An account's open charges, with a number, a due date, and a structured reference used to match bank transfers |
| **Payment** | Settles invoices: transfer, cash, or online |
| **Wallet** | Prepaid balance. A booked meal is debited immediately and refunded if cancelled in time |

All amounts are integer cents, never floats.

## Billing a period

```ts title="app.ts"
const billing = sluurp.billing;

await billing.charge({ account: pupil.id, item: trip.id });   // one charge
await billing.run({ dry: true });                             // preview a run
await billing.run({});                                        // bill subscriptions, invoice open charges
```

`remind(id)` emails a reminder for an unpaid invoice; `cancel(id)` cancels it.

## Collecting payments

- **Online** via Stripe or Mollie (per issuer); both support cards and Bancontact. `billing.pay({ invoice, back })` returns a checkout `url`; the user returns to `back` with `?paid=<id>`, and the provider's webhook records the payment.
- **Bank transfer:** pass bank statement lines to `billing.match(lines)`; each is matched to its invoice by reference. Use `dry` to preview.
- **Cash:** `billing.record(payment)`.
- **Wallet top-up:** `billing.pay({ account, issuer, amount_cents })`, between €1 and €1,000.

## Permissions

Managing billing (catalogue, charges, runs, recording payments) is governed by the `manage` rule in billing settings; without one, only superusers can. Everyone else sees their own accounts and those they pay for. `billing.certificate(account, year)` returns the tax-deductible amount paid per issuer for a year (in Belgium: childcare for children under 14).

Stripe, Mollie and Paddle accounts are configured under **Payments** in the admin UI.

Paddle is a merchant of record: it charges the sales tax and pays it where it is due. Its checkout opens on a page the server serves, `/api/payments/paddle/<payee>`, so a Paddle payee also needs Paddle's client-side token, and the site approved in Paddle's checkout settings. A `pdl_sdbx_…` key pays in Paddle's sandbox.
