Payments and billing
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.
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
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 chargesremind(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 checkouturl; the user returns tobackwith?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. Usedryto 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.