
Capabilities
- Payment gateway
- Back office
- Transactional email
- Interactive maps
Frontend
Backend
TrackLap
In ProgressTrackLap is a full platform for the motorcycle track-day ecosystem. Organisers publish events, configure skill-level groups, boxes, insurance and services, and collect enrolment fees; riders browse circuits, sign up, pay online and review their history. Angular 21 with Material and signals on the front, NestJS with TypeORM and PostgreSQL on the back. Payments are handled by two gateways living side by side: Stripe Connect and the organiser's own Redsys virtual terminal.
The challenge
The platform doesn't sell here — it charges on behalf of each organiser and keeps a commission. That forces split payments to be right, means no payment can ever be confirmed twice or for the wrong amount, and requires organisers to see exactly how much goes to Stripe and how much to the platform. And organisers don't all want the same thing: some prefer the convenience of Stripe, others want the money landing straight in their bank through the terminal they already have, so both gateways had to offer equivalent guarantees.
Implementation
Stripe Connect with destination charges
Each organiser connects a Standard account through Stripe onboarding; the platform processes the charge and pays out via transfer_data.destination. The fee (4% by default, configurable) is charged on top of the price: the rider sees a handling-fee line and the organiser receives the full ticket price.
A second gateway: the organiser's Redsys terminal
Organisers who prefer collecting into their own bank register their virtual terminal (merchant code, terminal and key) and the money reaches them directly, with no platform fee. The HMAC-SHA256 signature is generated in a pure domain layer with node:crypto — no third-party SDK anywhere on the money path — and the merchant key is stored encrypted with AES-256-GCM.
With Redsys, the signature is the authentication
The notification endpoint is public, so the signature is the only thing authenticating it: verified in constant time, with exact amount and currency checks, a transition to paid done as an atomic UPDATE that survives simultaneous notifications, and an OK response only on final decisions so Redsys retries if anything breaks. A contract test compares our signature byte for byte against the reference library.
Only the webhook confirms a payment
The success URL confirms nothing. Only the webhook — signature verified against the raw body — marks an enrolment as paid: it's idempotent and compares the charged amount against the expected one within a one-cent tolerance before trusting anything.
Refunds that reverse the fee
Organisers and admins can refund an enrolment; the refund is issued with reverse_transfer and refund_application_fee, so both the payout to the organiser and the platform commission are unwound.
Real-fee audit dashboard
The finance panel expands each charge's balance_transaction to read what Stripe actually charges, not just our own fee. From that it estimates the variable and fixed parts of the rate by least squares and derives the break-even amount — below it, a transaction loses money.
Packs that mint discount codes
Organisers sell prepaid packs; once payment confirms, the system generates codes redeemable at their circuits. Redemption is a single conditional UPDATE, safe under concurrency, and runs inside the enrolment transaction so a capacity failure never burns a code.
Sessions with rotating refresh tokens
Auth over HttpOnly cookies with rotating refresh tokens hashed in the database, role guards (rider, organiser, admin) and API keys for integrations.
Event-driven notifications
One event bus feeds both email (Handlebars templates over Nodemailer) and in-app notifications, with the emitter knowing nothing about its listeners. The front polls to refresh the unread badge.
Angular 21 front split by role
Standalone components, lazy loading per route, signals-based state and Leaflet circuit maps, with separate layouts and guards for the rider, organiser and admin areas.