01 Problem
Enrolling in a dance academy is not a checkout. A family chooses classes for one or more people, but whether each of them gets a place depends on the group: its age range, whether it is recruiting, and — for tournament formations — the result of a casting. What each person then pays depends on how many sessions a week they end up attending across all their groups, with a discount for siblings.
The platform has to carry an application from the first visit to a paid monthly pass without losing track of who applied, who was accepted where, what each person owes, and what has actually been paid.
02 Context
The academy runs a season-based timetable of groups across styles, age bands and levels — from open classes to tournament formations entered through casting. One person may train in several groups; one household may enroll several people at once.
Fees are monthly passes priced by weekly frequency (1×, 2×, 3× a week, or OPEN), plus single entries. A family discount applies to eligible lower-priced monthly passes within the same household. Money arrives online through Przelewy24, by bank transfer, by BLIK, and in cash at the studio — and all of it has to land against the same charges.
03 Requirements
- Browse classes and the timetable by age, style, level and instructor.
- One application for a whole family, with classes and a pass chosen per person.
- Applying is free and creates no financial obligation.
- Eligibility — age for the season, recruitment status, invite-only groups — enforced by the server.
- Staff decide per person and per class; overriding a rule is explicit and recorded.
- One monthly fee per person, derived from their total weekly sessions.
- Online and offline payments recorded against the same charges.
- Groups, timetable, attendance, billing and the cash till run from one admin panel.
04 Enrollment lifecycle
Enrollment moves through explicit states, and money enters only at the end: a submitted application is a request for a place, the studio decides, and acceptance is what creates a fee and a payment link.
Visitor
│
Class discovery catalogue · timetable
│
Application free · one per family
│
Server-side rules age · recruitment · pass
│
BNB review per person, per class
│
Acceptance membership · pass · charge
│
Payment pay link · online / offline
│
Billing state monthly · per participant05 Key decisions
Applying is not paying
Submitting the form creates applications, not an order. The charge and the pay link are issued only when staff accept, so a family is never asked to pay for a place the studio has not confirmed, and a declined application needs no refund.
The server owns eligibility and price
The form runs the same shared rule functions for instant feedback, but the submission endpoint re-checks everything. Recruitment and invite-only status are read from group records, not from the request, and the price is computed from the server-side price list — the client sends a pass choice, never an amount.
Decisions per person and per class
Each chosen class is its own selection with its own status, and the application’s status is derived from them rather than set by hand. One family form can end with one child accepted into two groups and another waiting for a casting result.
A pass belongs to a person, not a group
The monthly fee is derived from the number of unique weekly sessions across all of a person’s groups. Someone in three once-a-week groups pays for a 3× pass, not three 1× passes; a technique session shared by several formations counts once.
A payment is applied once
A Przelewy24 notification is accepted only with a valid signature and the exact expected amount, then confirmed with the provider’s verify call using the server’s own amount. The order is claimed as paid by a conditional update inside a transaction, so a repeated notification changes nothing and a second payment is recorded as a duplicate, not applied twice.
A month is billed once
Monthly charges are created in one transaction per run, with unique indexes that make an overlapping run or a retry unable to bill the same month twice. Automatic repricing may only lower an unpaid charge; raising one is an explicit correction that staff confirm.
06 Trade-offs
Human acceptance over instant confirmation
Every application waits for a staff decision before any money is involved. That costs studio time and makes families wait, but placement depends on things a form cannot judge: level, group fit and casting results.
Warnings where people know better
Age and closed recruitment block an application. Schedule collisions only warn, and a group is marked full by staff rather than by a seat counter. These stay staff decisions — informed by the system, not taken by it.
No silent price increases
The system lowers an unpaid charge on its own but never raises one. Increases need a correction that staff acknowledge — more manual steps, in exchange for no family ever seeing a higher amount that nobody decided on.
Links instead of accounts
Parents do not create accounts. Payments go through single-purpose pay links sent by e-mail. There are fewer credentials to secure and nothing to sign up for, but families do not get a self-service portal.
07 Operations
The studio side is a custom admin panel built on Payload CMS, organised around the jobs the office actually does:
08 Outcome
The platform runs the academy’s enrollment and billing in production at bnbacademyofdance.pl. In concrete terms:
Engineering verification — Unit, integration and end-to-end test suites; integration tests run against PostgreSQL, end-to-end tests against a mock payment provider.
Related: the DocAI architecture case study rests on the same principle in a different domain — a completed step is a proposal, and approval is a separate, recorded act.