How We Replaced Paper Vouchers With a QR Code a Partner Can Actually Verify
Visitors to Baja arrive without a trustworthy bilingual guide to the businesses around them. The businesses have the opposite problem: a restaurant, an excursion operator or a fishing charter can offer a visitor a discount, but a paper voucher cannot be checked, cannot be limited to one use, and cannot tell the owner who redeemed it. We built three connected products on one backend, a Tourist app, a Partner app and an admin web panel, with a freemium membership unlocked by in-app purchase and a one-time QR code that the partner scans and the server validates.
John Charos and Adam J. Moss came to us with a discovery and membership platform for the Baja region, built for two audiences at once. Visitors who need to know which businesses are worth their time and money, and local partners across eight categories who want a way to reach those visitors with an offer they still control.
The brief was bilingual from the start. In Baja that is not a localisation phase bolted on at the end. A platform that works in English and fails in Spanish only serves half the transaction.
A visitor to Baja has no single trustworthy source for where to eat, what to book and who to hire. Recommendations come from whoever is nearby, in whichever language they happen to speak, with no way to tell a curated business from an advertised one.
The partner side is harder. A local business that wants to offer a member discount has historically had one instrument: a printed voucher. A voucher cannot be checked against a real membership, cannot be limited to a single use, and leaves no record of who redeemed it or when. The business absorbs every discount it cannot verify, which is why most of them stop offering one.
And connectivity across the region is uneven. An app that assumes a signal at the moment of redemption will fail at the exact point where the partner is standing in front of the customer.
The obvious build order is the tourist experience first. We went the other way, because the redemption is the moment the platform either earns a partner or loses one, and every other decision, membership tiers, offer configuration, partner onboarding, has to serve it.
One-Time QR With Server-Side Validation
The tourist displays a code, the partner scans it, and the server decides whether it is valid. Validation never happens on the device, because a redemption a partner can approve locally is a redemption a screenshot can defeat. The code is single-use, so the same offer cannot be redeemed twice from one membership.
A Scanning App for People Standing at a Counter
Partners are standing at a counter or on a boat, so the scanning experience had to be a phone app rather than a web login. The same app carries listing management, offer configuration and scan history, which means the person redeeming the offer is the person who can see how many have been redeemed.
Offline Caching on Both Clients
Local caching and sync were built into the Tourist and Partner apps rather than added later. A visitor browsing guides on a beach with no signal still sees the guides, and a partner who scans during an outage still has the scan reconciled when the connection returns.
Bilingual Through the Data Model
English and Spanish run through the apps, the admin panel and the landing page. Curated guides and partner listings carry both languages at the record level, so a partner onboarded in Spanish is discoverable by a visitor browsing in English without a translation step in between.
Freemium With the Membership as the Gate
Discovery is open and redemption is the paid boundary. Single- and duo-user premium plans run through Apple and Google in-app purchase, which keeps the platform inside store billing rules and gives the partner confidence that a scanned code belongs to a paying member.
Each module maps to a product and a role. All three read from the same backend.
React Native carried both mobile clients so the Tourist and Partner apps could ship to two stores from one team, with the admin panel as a web SPA against the same REST middleware. That middleware holds the business logic, authentication, session management and caching, which keeps the three clients thin and keeps redemption rules in one place rather than three.
The SQL schema was scoped before the clients were built. Users, listings, offers, subscriptions and audit logs all had to relate cleanly, because a redemption touches every one of them at once and a disputed scan is answered from the audit log rather than from memory.
Firebase handles push, in-app notification and transactional email. In-app purchase runs through Apple and Google billing, validated server-side, so membership state is a property of the backend rather than a claim the device makes about itself.
A member finds a partner through a curated bilingual guide, opens an offer, and shows a code generated for that offer and that membership. The partner scans it in their own app and the server confirms or rejects it. The redemption is recorded against the member, the offer and the partner, and the code cannot be used again.
Partners configure their own listings and offers and see their own scan history, so the discount they extend is one they can measure. Administrators curate guides, onboard partners across all eight categories, configure offers and ads, and read platform analytics from one panel.
The platform is at MVP. These describe the system as built rather than results in market, and this section needs rewriting with real figures once it has launched.
“Our partners had all stopped offering discounts, and every one of them gave the same reason. They could not tell a real member from a photocopy. The scan is the product. Everything else is how people find it.”
We have built the validation path before, and we know it belongs at the start of the build.
Start the conversation