Skip to content
Work / PVR Cinemas booking platform

Booking for 600+ screens,where money and seats agree.

The checkout and ticketing for PVR Cinemas, India's largest cinema chain. Built at Appinventiv, launching 2026.

600+screens on the platform at launch

ClientPVR Cinemas, via Appinventiv
My roleFrontend Developer
When2025 to 2026
What I ownedCheckout, payments, wallet, tickets
Built withNext.js, React, Redux Toolkit, Razorpay
StatusLaunching 2026
Screenshot: PVR seat map with hold timer

The problem

When thousands of people book at the same moment, the worst thing that can happen is a customer paying for a seat that someone else got. It costs a refund, a support call, and usually the customer. The platform had to make that impossible, not just unlikely.

The engineering version
Payment authorization and seat allocation are two systems with two clocks. Webhooks arrive late or twice, users close tabs mid-checkout, and the booking write can fail after Razorpay has already captured the money. The checkout state machine has to be correct across every one of those orderings.
PVR ticket checkout from picking a seat to a ticket on the phone
Customer picks seatsSeat selectionseat slice, 480s hold timerPaysPaymentRazorpay order + authorizationSeat is locked inBooking confirmedwebhook verify, reconcile, writeBooking step failsRefund goes out automatically. Nobody ischarged for a seat they did not get.compensating refund, seat released, idempotent retryreverse paymentTicket on their phoneQR ticket issuedQR issuance, share link
Top row is what the customer sees. The green branch is the case most booking systems get wrong: money taken, seat lost. Here it resolves itself.

What was hard, and what I did about it

Paid, but no seat

The money is taken. Then the booking step times out, or the seat was taken a second earlier. Without care, the customer is charged for nothing.

What I did. Made sure the same request can be retried safely, and put an automatic refund on the failure path. One payment always ends in exactly one seat or one refund.

Implementation notes
order_id used as the idempotency key. Webhook signature verified before anything is trusted. A reconcile step compares gateway state and booking state before the ticket is issued, and enqueues a refund if they disagree. Duplicate webhooks are a no-op.

Wallet, refunds, partial payments

Customers pay partly from a wallet, partly by card. A refund has to go back to the right place in the right amount.

What I did. Built a wallet layer that checks the balance before checkout and splits refunds across wallet and card, so ticket state and money state never drift apart.

Implementation notes
Wallet slice with pending and settled balances. The refund split is derived from the original payment breakdown, never recomputed from the total, so partial refunds cannot over-refund either side.

A seat map that stays fast

Hundreds of seats, live availability, a countdown, and it has to feel instant on a mid-range phone.

What I did. Organized the seat data so that when one seat changes, only that seat redraws, not the whole map.

Implementation notes
entityAdapter keyed by seat id, one memoized selector per row, and a single interval for the hold timer rather than one per seat.
Screenshot: Showtime discovery with distance filter
Showtime discovery with distance filter
Screenshot: QR ticket and share sheet
QR ticket and share sheet
600+screens on the platform at launch
1outcome per payment: a seat or a refund
Q4 2026public launch

Ask me about

  • How the reconcile step decides between issuing a ticket and issuing a refund
  • What a Razorpay integration honestly costs in time, and where the time goes
  • What the PCI DSS scope looked like on the frontend, and what it did not cover
  • What I would change about the module boundaries today

Built with Next.js, React, TypeScript, Redux Toolkit, Razorpay, Tailwind CSS.

Next case study

SnapWap

Open SnapWap
Lakshay Agarwal, 2026Updated September 2026. Noida / Delhi NCR.