NODE_01
Reputap
Google review collection via NFC, multi-location, with abuse controls.
- Role
- Founder, design and development
- Period
- 2026 to date
- Status
- In production
- Stack
- Next.js 16 · TypeScript · PostgreSQL · Prisma · Auth.js · Zod · GSAP · Resend · Stripe · Vercel
- Links
- Visit

The problem
A local business lives on its Google reviews, yet asking for one happens at the worst moment: the customer is paying and leaving. Existing tools put a QR code on the table and stop there. They cannot motivate the customer, cannot stop the same person from farming rewards, and give the staff nothing simple to do at the counter.
Reputap starts from a physical constraint: one NFC chip per location. You have to be there to tap. From that point the journey is guided: tap, review, prize wheel, ticket, validation by the merchant. The product is sold today to businesses in the Alpes-Maritimes, plate included.
The constraints
- Google offers no reliable API to prove a review was posted. The whole system had to work without that proof.
- Multi-location from day one. One SaaS customer can run several shops, each with its own tags, campaign and prizes.
- Zero friction for the end customer. No account, no app: a URL the phone opens when it touches the plate.
- A five-second gesture at the counter for staff who will never read documentation.
Architecture decisions
Move the fraud barrier to where a human already stands
Since the review cannot be verified, the reward is. A spin produces an ISSUED ticket with a unique code and an expiry. Nothing is handed over until the merchant marks it REDEEMED from the validation screen, or rejects it with a reason. Fraud becomes a counter problem, not a cryptography problem.
Draw the wheel on the server, animate on the client
The client never draws. It requests a spin; the server checks the session, cooldowns and caps, picks the prize by configured weight, writes spin and claim to the database and returns the result. The GSAP animation only plays that result. A tampered client cannot win more often.
Tenant isolation inside the data access layer
Every query goes through a data access layer that receives the current tenant and cannot forget it. The super_admin, owner and user roles are carried by Auth.js and checked server-side on every action. An owner cannot read another location's data, even by guessing an identifier.
Abuse rules configured per campaign, never hard-coded
One spin per scan session, a 24-hour cooldown per device, a soft limit per hashed IP, a cap of spins per tag per hour, unguessable NFC tokens with rotation, expiring tickets. Each rule is a campaign setting, because a night bar and a bakery do not see the same traffic.
The journey as it runs
- Step 1

- Step 2

- Step 3

- The plate opens a URL of the form
/s/{publicToken}. The server resolves the tag, the location and the active campaign, then creates ascan_sessionwith its anti-abuse metadata and expiry. - The page shows the location's branding and a single button: leave a review. The click records a review intent and opens the Google listing.
- On return, the wheel is available if the session is still valid. The draw is server-side, the animation is client-side.
- The ticket shows its code. At the counter, staff look it up by code and validate or reject.
Architecture
What is not solved
Operations

A super-admin console lists tenants, manages subscriptions and generates magic login links for support. A system dashboard shows database latency, disk usage and logs. Transactional email goes through Resend, billing is wired to Stripe, and everything runs on Vercel with PostgreSQL managed by Supabase.
Stack and conventions
Next.js 16 App Router with Server Actions, strict TypeScript, Prisma on PostgreSQL, Auth.js v5 with role-based access control, Zod validation on every input, SCSS Modules. The brief enforces files under 300 lines and a clean split between UI, orchestration, domain and data access. That is not vanity: it is what lets a new review provider or abuse rule land without touching the rest.