6. Money and payments
Rule. Record the debt first, add a payment rail second. Money is always an integer in minor units with its currency. Stripe and Razorpay are both supported, alongside offline payments with receipts.
Why. A space can owe or be owed money with no payment provider. Floats lose cents. Currencies differ in decimals (JPY none, KWD three).
How to apply.
- Ledger before gateway. Every charge is recorded whether or not an online provider is configured. The ledger is permanent core; only the online rail (checkout, refunds, provider settings, webhooks) is removable. A purge never deletes a payment.
- Minor units and currency. Store integers (paise, cents) with an ISO currency code, or fixed-point
decimals. Never
float. Never add across currencies. Round with the currency’s own decimals. - Providers. Each space or organisation uses its own Stripe or Razorpay account; there is no platform fee or platform-wide fallback key. Support both in every product that takes money. Supporting offline too: cash, bank transfer, UPI, card terminal, pay later, recorded by staff.
- Offline needs a receipt. Marking a charge paid offline writes a settlement record (method, reference, received date) once; corrections are new rows that amend, never edits. An optional maker-checker rule can require a second person to confirm.
- Idempotent. The same webhook event or order key never settles twice (processed-event table, order idempotency key). Razorpay deliveries are de-duplicated by event id and by a hash of the signed body.
- Webhooks are verified with the provider’s signature, per space, before any state changes; unknown tenants and archived spaces are refused.
- Provider switch keeps old keys usable. After a space switches from one provider to the other, the old provider’s keys stay usable for settling and refunding existing charges. Rotating or clearing keys refuses while collections are pending.
- Unknown outcome stays pending. A provider timeout or unreadable response leaves the charge pending and reserved; it is never guessed as paid or failed.
- Refunds follow the space’s refund policy, are recorded as their own rows and never exceed what was paid.
- Secrets never leave the server. Provider keys and webhook secrets are write-only in every screen, encrypted at rest with a key bound to the tenant, and never in logs, audit metadata, URLs or API replies. Only publishable keys go to the browser or the app.
- Receipts are numbered without gaps; credit notes for refunds; tax receipts (for example GST) where a product needs them.
- Prize money and payouts are recorded by a person, never sent automatically.
Across the org.
| Product | Money today |
|---|---|
| SpaceWorks | Stripe and Razorpay per space, offline with receipts, charge tracking independent of the payments rail, reconciliation, refunds |
| Events | Razorpay and Stripe on the organiser’s account, pay at the desk, refunds, PDF receipts, chargeback alerts |
| Machines | Razorpay or Stripe, or desk payments (cash, UPI, bank, card terminal), optional maker-checker |
| Inventory | Razorpay, Stripe, cash or bank, or pay later, for late fees and damage charges |
| Bookings | Razorpay Checkout, Stripe Checkout, cash, UPI, bank, pay later, gapless receipts and credit notes |
| Organization | Razorpay, Stripe, UPI, bank, cash, GST receipts |
| Workspace | Finance records (income, expenses, bills, reimbursements, payroll records); no payment gateway found |
| iOS / Android | Server-selected checkout (iOS: Stripe SDK and Razorpay per README); Android: Stripe Android SDK per the SpaceWorks mobile spec, Razorpay not checked |
Part of Space Works product rules, published from docs/product-rules.md. Edit it there.