Back to Selected Work

    Tonne Fitness

    A gym's lead-gen site with a fully wired ecommerce checkout bolted on for supplements.

    Next.jsTypeScriptDynamoDBRazorpayView live

    What it does

    Two products in one app

    The gym side — trial bookings, corporate wellness inquiries, an AI chat that qualifies leads — writes to DynamoDB and hands off to a person over WhatsApp. There's no automated booking or membership payment; a human closes the sale. The supplements side is a real store: browse, a server-persisted cart, Razorpay checkout, and a payment pipeline that only trusts its own server, not the browser.

    Architecture

    Order confirmation only ever comes from the webhook

    Cart

    Persisted server-side per user, not just localStorage — checkout starts from what the server has on file.

    Create order

    Server re-derives the total and 18% GST from the cart it holds — never the client's numbers — creates the Razorpay order, and writes a Pending order record before any money moves.

    Razorpay checkout · third-party

    Customer pays through Razorpay's hosted widget.

    optimistic UI nudge, not a confirmation

    Payment callback

    Flips the order to 'Awaiting Confirmation' so the customer sees progress — this path is never trusted as proof of payment.

    server-to-server, HMAC-verified

    Razorpay webhook · third-party

    The actual confirmation: verifies the signature, flips the order to Processing, deletes the cart, and emails the customer and the store in parallel.

    Order confirmed

    The full raw Razorpay payment and order payloads are stashed alongside the structured record — nothing about a transaction gets lost to a schema gap.

    The payment callback in the browser can be interrupted, closed early, or spoofed, so it never gets the final say — it only nudges the UI forward. The webhook is the one path allowed to mark an order paid, exactly the way Razorpay's own integration guidance describes it.

    Worth being precise about

    A shared database, not a headless CMS

    The site and a small internal CMS are two separately deployed Next.js apps that never call each other — there's no API between them. What connects them is a shared DynamoDB table set and one S3 bucket: editing a product in the CMS writes straight to the same table the site reads from, picked up on the site's next catalog refresh rather than instantly. It's a shared-database integration, not the API-driven headless-CMS pattern the term usually implies.

    Worth stating plainly: the gym side of the site is lead capture, not a booking platform. Trial requests and the corporate wellness form log a lead and stop there — closing the sale happens over WhatsApp, by a person, not by any code in this repo.