Case study
Naora: online booking where a Pix deposit holds the time
A booking app for salons, barbershops and beauty studios. The client picks a service, a day and a time, and the time is only hers after a small deposit paid by Pix. The business gets a dashboard, and the deposit goes straight to its Mercado Pago account.
The problem
Small beauty businesses in Brazil book clients by WhatsApp. A client says yes, the time is kept for her, someone else is turned away, and then she does not come. The chair stays empty and that hour is lost.
A deposit changes the commitment: whoever pays shows up, and whoever does not pay frees the time for someone else. Pix makes that deposit instant and cheap, so the whole flow can happen on a phone in under a minute.
Naora is also a real micro-SaaS, built to run in production at zero cost. On the Free plan it keeps 15% of each deposit; the Pro plan costs R$ 39.90 a month with no fee.
What I built
- A public page for each business, and booking in three steps: day, time, details. Days and times are plain links, so the page works before JavaScript loads and a time can be shared.
- The Pix deposit through Mercado Pago: QR code and copy-and-paste code, a 30-minute hold on the time, and a page that confirms by itself when the payment lands.
- The owner dashboard: services with price and deposit, opening hours, upcoming bookings with a WhatsApp link, Mercado Pago connected by OAuth, and the plan.
- Freemium billing: the 15% fee goes through Mercado Pago's split (application fee), and Pro is a Mercado Pago subscription.
- A one-click demo with a simulated Pix, Google sign-in, and an account area fixed in the header, so nobody gets stuck in the demo.
- Portuguese and English, light and dark theme, usable with a keyboard and a screen reader.
Decisions worth explaining
The same time is never sold twice
Two clients can tap the same time in the same second. The check and the insert run in one transaction holding a Postgres advisory lock for that business, so the second request waits, sees the first booking and is told the time is gone. An integration test fires 8 requests at once and expects exactly one to win.
The webhook only says “look again”
A notification from Mercado Pago never confirms a booking by itself. Naora reads the payment back from Mercado Pago with the business's own token and confirms only when it says approved. A forged notification, or one sent to another business's address, changes nothing. Each charge uses the booking id as its idempotency key.
A late payment is recorded, not double-booked
If a client pays after the hold ended and someone else already took the time, the payment is saved and shown to the owner, and no second booking is created.
Times live in the business's time zone
Slots are built in the business's own time zone, not the server's, which runs in the United States. Unit tests cover São Paulo and New York across daylight saving time.
No online booking without the deposit
An early version let a business without Mercado Pago take bookings with the deposit “arranged in person”, and the platform's fee simply disappeared. Now a rule decides when a service can be booked online, and the server, the public page and the dashboard all follow it.
Secrets stay secret
Mercado Pago tokens are stored encrypted with AES-256-GCM and refreshed before they expire. The OAuth state is signed with HMAC and tied to a nonce in an HttpOnly cookie. Each page gets a new nonce for its Content Security Policy.
Quality, measured
Checked on every pull request. Last checked on November 4, 2026.
| Area | Result | How it is checked |
|---|---|---|
| Tests | 107 unit · 38 integration · 24 end-to-end | Jest, PGlite (real Postgres in memory), Cypress against the production build |
| Accessibility | 0 axe violations · WCAG 2.2 AA | cypress-axe in Portuguese and English, light and dark, and a 320px screen |
| Concurrency | 1 of 8 simultaneous bookings wins | Integration test against the real transaction and lock |
| Security headers | Nonce-based CSP, HSTS, nosniff, frame-ancestors none | End-to-end test that reads the response headers |
| Code | TypeScript strict, 0 ESLint warnings | Typecheck and lint on every pull request |
The QA cycle
Naora is live, and its QA cycle is running now. The test plan has 38 test cases across 11 modules, a risk matrix that puts money first, and clear entry, exit and suspension criteria.
The rounds go from safe to real: the demo and accounts first, then a real Pix between two Mercado Pago accounts to confirm the 15% split on the statement, then the Pro subscription, and finally feedback from people outside the project in an open beta.
What I learned
- A business rule can hide in one friendly sentence. “The deposit is arranged in person” looked kind and quietly removed the revenue.
- Behind a proxy, a server does not know its own address. Redirects now use the public site address, after Google sign-in sent people to an internal one.
- A small visual change can change what a test sees. A sticky header made Cypress treat the skip link differently, and the right fix was in the page, not the test.
Try it
No sign-up needed: click “Try the demo” in the header, create a pretend salon and book as a client. The demo Pix is simulated and nothing is charged. It runs on Render's free plan, so the first visit can take about a minute. Open Naora.