PRD 08 — Sales & Payments
Status: draft · Priority tier: P0 (manual POS, transactions, manual methods, taxes) / P1 (pay later, policies, refund records) / P2 (gateway, recurring billing, wallet) · Size: L · Depends on: PRD 01 (business, currency, locations, staff), PRD 06 (plans, promo), PRD 07 (customers)
1. Summary
Everything money: the sales list, a point-of-sale "add transaction" flow for staff, transaction detail with void/refund/edit, checkout rules (pay later), payment methods (manual offline methods in v1, gateway later), taxes, optional wallet, policy texts, and the rules for retrying failed recurring charges.
2. Problem & goals
- Staff record a cash sale of a package in ≤ 9 clicks (original: ~9) and the customer gets credits and a receipt immediately.
- Every sale yields an immutable, numbered transaction with a visible timeline; no silent edits.
- Totals reconcile: price - discount + tax (or tax-inclusive) = amount, to the minor unit, in 100% of tests.
- Non-goals (v1): moving real money (no gateway), recurring charges, wallet, retail inventory, payroll.
3. Users & roles
Owner/admin, manager, front desk (create sales, mark paid), accountant ✱ (view + reports, no create). Customer sees receipts and checkout policy text.
4. Scope
In (v1)
- must: Sales list with date, item type, channel, method, status filters, search
- must: Add transaction (POS): customer, item (package/membership; retail, course, top-up later), discounts, pay now by manual method (cash, bank transfer, custom) marked as paid
- must: Free grant (zero-amount) transaction
- must: Transaction detail, timeline, invoice and receipt (PDF), send receipt
- must: Manual payment methods (preseeded cash, bank transfer; custom with details and image; deactivate, no hard delete)
- must: Taxes (one per country, inclusive or exclusive) applied to totals
- must: Refund as a record (reason, optional notify) and void
- should: Pay later with due date; reminder email; "allow booking with unpaid plan"
- should: Edit sale and edit payment with adjustment reason (audited)
- should: Policy texts (refund, class cancellation, appointment cancellation) shown at payment
-
should: Proof-of-payment upload on manual methods (customer-facing) Later (v2+)
-
must (when gateway lands): Payment provider connection, online card/wallet payments, gateway refunds, retry charge
- should: Recurring membership billing with retry/dunning rules
- could: Wallet (top-up, partial pay, refunds to wallet); waive, mark uncollectible, approve/decline for manual payments needing approval
-
could: Bill to account (invoice to a connected processor) Out
-
Handling card data ourselves: use a certified gateway only.
- "Mark as refund returns no money": we keep refund-as-record semantics for manual methods; gateway refunds are a separate action.
5. User stories
- US-08-01 As a front desk user, I want to sell a package for cash so that the customer can book right away.
- US-08-02 As a front desk user, I want to apply a promo code or custom discount so that I can honour offers.
- US-08-03 As an owner, I want every sale numbered with invoice and receipt so that accounting is clean.
- US-08-04 As an owner, I want to void a mistaken sale so that the plan is deactivated without touching bookings.
- US-08-05 As an owner, I want to record a refund with a reason so that books match what I returned outside the system.
- US-08-06 As an owner, I want to allow pay later with a due date so that corporate clients can pay after.
- US-08-07 As an owner, I want to define tax and whether prices include it so that receipts are legal.
- US-08-08 As an owner, I want bank transfer instructions and proof upload so that customers can pay offline.
- US-08-09 As an owner, I want failed recurring charges retried automatically so that I lose less revenue.
- US-08-10 As a customer, I want to read the refund and cancellation policy before paying.
6. Functional requirements
| ID | requirement | priority | evidence |
|---|---|---|---|
| FR-08-01 | Sales list columns: date, transaction no, customer, item name and type, payment method, price, discount, tax, amount, status chip, row menu; sortable; paged | must | S208 |
| FR-08-02 | Filters: search, date presets + custom range, item type (booking, package, membership), sales channel (web, back office), payment method, status; apply/cancel | must | S70-S72 |
| FR-08-03 | v1 statuses: paid, unpaid, overdue, voided, refunded; later: retrying, pending approval, declined, waived, uncollectible, refund processing | must | S72c, 04 Features |
| FR-08-04 | Add transaction step 1: type payable or free, note (printed on invoice), sold-at location, sold-by staff (optional, defaults to current user), customer search by name/phone/email with inline "add customer" | must | S73, S203, S74 |
| FR-08-05 | Item selection: tiles per item type; packages and memberships list with search, category, price shown for the customer's pricing group; ineligible items disabled with reason | must | S203c, 06 rules |
| FR-08-06 | Cart summary: item, details, edit price (permission), add discounts (promo code or custom cart discount), total, amount to pay | must | S204, S205d |
| FR-08-07 | Payment step: pay now vs pay later; pay now = mark as paid with method (cash, bank transfer, custom), paid-on date (default now), reference no, optional third-party fee | must | S205, S205e |
| FR-08-08 | Confirmation dialog before creating a paid transaction ("received outside the system") | must | S206 |
| FR-08-09 | On success: transaction no allocated (sequential per business, prefix by kind), receipt number, customer plan created with credits and dates, receipt emailed, completion screen with link to detail | must | S207, F73 |
| FR-08-10 | Free transaction creates the plan/item with amount 0 and no payment step | must | S73b |
| FR-08-11 | Pay later: create unpaid invoice with due date; optional generate invoice; plan active or not per checkout setting; reminder email before due date | should | S205c, S150 |
| FR-08-12 | Transaction detail: header (amount, status, paid-on, customer link, method, number), actions, payment submission, timeline, item details, payment details (invoice, reference, receipt no, amount, fee, net), receipt history, payment method/channel/source | must | S209 |
| FR-08-13 | Invoice and receipt PDFs; Send Receipt re-sends email and logs it | should | S209 |
| FR-08-14 | Void: confirm, optional internal reason; items on customer account become inactive; bookings unaffected; promo use restored | should | S209c |
| FR-08-15 | Refund (record): choose amount ≤ paid, required reason, optional notify customer, warning that no money is returned automatically; status refunded or partially refunded ✱ | must | S209b |
| FR-08-16 | Edit sale (paid-on date, location, sold-by, note, reference) and edit payment (amount, fee, nontaxable, method) with required adjustment reason; each saved as timeline event with before/after | should | S209d, S209e |
| FR-08-17 | Transactions are never deleted; corrections are events | must | derived |
| FR-08-18 | Manual payment methods list: cash and bank transfer preseeded; add custom (name, details ≤500 shown to customer, optional image); edit; deactivate (shows "deactivated", no delete) | must | S133, S133c, F56 |
| FR-08-19 | Manual method option: require proof-of-payment upload and custom input field (customer-facing checkout) — pricing tier: TBD | could | S133c |
| FR-08-20 | Payment provider connection (search by provider or method) | later | S133b |
| FR-08-21 | Taxes: one tax per business country with name, percent, optional registration number; edit, remove (confirm); toggle "prices include tax" | must | S123, F51 |
| FR-08-22 | Tax calc: discount applied before tax; exclusive adds tax on top; inclusive extracts tax from the price; "item is nontaxable" per payment; rounding half-up per line | must | S123, S209e, assumption |
| FR-08-23 | Checkout settings: pay later toggle; due = on fulfillment or within N days; send reminder; allow booking with unpaid plan; require active membership to buy a package | should | S150 |
| FR-08-24 | Wallet: enable setting with regulatory notice; customer balance, top-up, partial payment, refund to wallet | could | S159 |
| FR-08-25 | Policies: refund, class cancellation, appointment cancellation texts (≤2000 each); shown at checkout and in booking confirmation | should | S155 |
| FR-08-26 | Recurring billing rules: time of day to charge; retry up to N times within D days; email customers to update failed method; after all retries: membership status, invoice status, booking status in next cycle; customer self-cancel toggle | later | S151 |
| FR-08-27 | Role permissions for view, create, edit pre/post payment, void, refund, mark paid offline, pay later | must | S120 matrix |
| FR-08-28 | Time-of-day pricing mode setting (full slot vs pro-rated) | out | S150 |
7. Screens
| Our screen | Modelled on | States |
|---|---|---|
| Sales list | S70, S208 | empty, filled, filtered-empty, error |
| Add transaction (3 steps: customer+item, payment, done) | S73, S203-S207 | pick customer, cart, pay now, pay later, confirm dialog |
| Transaction detail + drawers (void, refund, edit sale, edit payment) | S209-S209e | paid, unpaid, voided, refunded |
| Settings: Payments (methods) | S133 | list, add drawer, deactivate confirm |
| Settings: Taxes | S123 | empty, one tax |
| Settings: Checkout | S150 | default, dirty |
| Settings: Wallet (later) | S159 | off |
| Settings: Policies | S155 | empty, filled |
| Settings: Membership billing rules (later) | S151 | default |
8. Flows
- Cash sale (≤ 9 clicks): Add transaction → customer → Packages tile → tick package → Next → Pay now / Mark as paid / Cash (optional reference) → Confirm → confirm dialog → done → detail.
- Pay later: same but choose Pay later; invoice due per checkout settings; later "Mark as paid" from detail.
- Refund record: detail → Mark as refund → amount, reason, notify → confirm.
- Void: detail → Void → reason → confirm. Edge cases: customer lacks eligibility for item; promo invalid; double submit (idempotency key); tax config missing (no tax); customer suspended (blocked); concurrent edits (optimistic lock).
9. Data model (✱ = guess)
- Transaction(id, number, kind payable|free, customer_id, location_id, sold_by, note, channel web|back_office, status, price, discount, tax, amount, fee, net, tax_inclusive_snapshot, paid_at, due_at, method_id, reference_no, invoice_no, receipt_no, currency, created_at)
- TransactionItem(transaction_id, kind, item_id, name_snapshot, unit_price, qty)
- TransactionEvent(transaction_id, type, actor, before, after, reason, at) — timeline
- Refund(id, transaction_id, amount, reason, notified, created_by)
- PaymentMethod(id, kind provider|manual, provider ✱, name, details, image, proof_required, custom_field, active)
- Tax(id, country, name, percent, registration_no) + Business.tax_inclusive
- CheckoutSettings, MembershipBillingRules, Policies (as in recon entities)
- WalletAccount(customer_id, balance) ✱ + WalletEntry (ledger) ✱
- Amounts as integer minor units; currency from business.
10. Business rules & edge cases
- Totals: discount before tax; amount = net + tax (exclusive) or net (inclusive); refund ≤ amount paid minus prior refunds.
- Voiding a paid transaction deactivates plans it created; refund does not (owner decides separately) ✱.
- Edits after payment require the post-payment edit permission and a reason.
- Receipts record the tax registration number and policy snapshot at the time of sale.
- A free class booking creates a zero-amount transaction (FREE_ prefix) for traceability (shared with PRD 02).
- Transaction number sequences never reuse numbers.
- Pay-later items stay "unpaid"; overdue is derived by due date.
- Deactivating a payment method does not alter past transactions.
- Customer notifications on receipt and refund follow notification settings (PRD 01).
- Idempotent create: same key returns the same transaction.
11. Permissions
| Action | Owner | Manager | Front desk | Accountant ✱ |
|---|---|---|---|---|
| View transactions | yes | yes | yes | yes |
| Create (incl. pay later) | yes | yes | yes | no |
| Mark paid offline | yes | yes | yes | no |
| Edit pre-/post-payment | yes | yes | pre only | no |
| Void / refund | yes | yes | no | no |
| Payments, taxes, checkout, wallet, policies settings | yes | no | no | no |
| Mirrors the original granular Sales and Settings permission nodes. |
12. Acceptance criteria
- Given a 5.00 package with 10% exclusive tax, When sold for cash, Then amount 5.50, tax 0.50, status paid, customer plan active.
- Given tax-inclusive prices, Then amount stays 5.00 and tax shows the extracted portion.
- Given a 20% promo and tax, Then tax is computed on the discounted price.
- Given a paid transaction, When voided, Then the plan becomes inactive and existing bookings remain.
- Given refund without reason, Then the action is disabled.
- Given refund amount above remaining paid amount, Then rejected.
- Given pay later with reminders on, Then a reminder is sent before the due date (lead time ✱); after due date status is overdue.
- Given a deactivated method, Then it does not appear in the add-transaction method list but remains on old transactions.
- Given the same submit twice, Then one transaction exists.
- Given a customer who is suspended, Then they cannot be selected for a payable sale.
13. Dependencies & open questions
Depends on PRD 01, 06, 07, 02/04 (booking payments), email (existing mail package).
Open questions:
- Payment gateway choice (OPEN, not decided). Candidates for an Indonesia/SEA launch: Xendit, Midtrans, Stripe (and others). Needs: local methods (bank transfer/virtual account, QRIS, e-wallets, cards), recurring/tokenised charges, fees, settlement time, marketplace/split needs. Original evidence only shows a provider-agnostic provider search; no gateway flow was observed. Blocks recurring memberships, online checkout, and gateway refunds.
- Currency, tax regime (e.g. PPN 11% in Indonesia) and rounding rules per target country.
- Legal invoice requirements (e-faktur etc.) vs simple receipts.
- Manual payment approval flow (pending approval, approve, decline) with proof upload: statuses seen, flow not observed.
- Refund semantics: record only (as original) vs gateway refund; partial refunds.
- Wallet: regulatory constraints (e-money rules) before enabling; scope v2.
- Bill to account and statuses (waived, uncollectible) not observed.
- Sales list had no export in original (reports hold exports): add CSV export on list?
- Pricing tier: TBD for proof-of-payment option and gateway fees.
14. Evidence
See evidence/evidence.md.
