Building a Ticket Booking Platform from Scratch: My Role & Technical Journey
I’ve been working on a ticket and food booking platform for cinemas at my current company. In this blog, I’ll share my role as an engineer and the technical challenges I tackled.
We were tasked to deliver four key components:
- Web app
- Mobile app
- Kiosk system
- Ticketless entry app
I worked on three of these (everything except the mobile app).
Understanding Vista
Before anything, you need to understand Vista. Vista is a cloud service widely used across the world to manage showtimes, food, and beverages. It includes all the modules needed to run a cinema. We built our platform on top of Vista, leveraging its architecture and customizing it to meet the client’s needs.
Scope of Work
Landing page, a flashy banner, with a quick book option which is dynamic, that is you selecting any 1 option filters out the rest of the options, so we call it Dynamic Quick book, under that is movies listing, again with multi filter selection, under that is misc stuff like offers, a dynamic footer serving content country wise
Performance optimisation
There is no SSR (server side rendering for this page) we do use (generateMetadata), we had one initially for the banner but we saw that it was blocking out main thread as the api was taking too much time so initial load was slow, so we used lazy loading & suspense (love suspense fr) for non essential parts of the page and created a view on the backend so that fetching banners images, movie details from multiple tables isn’t slow + cached all the get api’s
Moving to schedules page, when you click on “book now” it takes you to schedules page, which shows list of all available dates and under that mall wise schedules for movies with the experience it is serving, the date for each date re-fetched every time, which should be optimized to not re-fetch for already fetched data (More work for me!), filters don’t filter the dates showing user dates with empty schedules (Bad UX). Now if you click on any 1 schedule it takes you to seat layout.
Seat Layout
Every auditorium or theatre has a unique seat layout, rendering each one of them correctly matching exactly like the real audi each was very important task as a wrong depiction of the seat layout on the web would be a disaster
I’ve sort of also hacked my way into creating a perfect seat layout, the layout is good now. special mention DMarwah for helping me add a final fix.
The seat layout also has a mini map (which is on of my fav implementations) which allows small screen users to understand the seat layout by giving showing them the entire seating layout in one small view
On the seat layout you can check the schedules using the Side panel the dates and schedules are available tho it doesn’t show the experience (might be good ux to show up on hover)
Now comes the feature development part (client requirements) we want to give users an option to apply Bank offers & Loyalty program offers on Seat layout themselves ! After selecting the seats, extraordinary . Let’s see how bank offers work
Bank offers
We render bank discounts to the user, the user needs to validate their card in order to apply the discount, if the users card is validated we apply the discount, now since the user has not selected seats, they are pre applying the discounts, we check the minimum and maximum number of seats required to apply the discount, with the area category code, then check the new ticket price and update the ticket type code selected by the user
The bank isApplied flag is then turned on and when user continues, to next page, the apply api is called validating the same logic and returning the updated discount to the client.
To integrate this with our payment gateway, since using bank offers require us to pay using card only, dibsy (very good payment gateway, had fun working with the team) created a card tokenisation feature for us allowing us to tokenise the card on seat layout and allowing user to only pay using that card on the checkout page.
const checkMinimumSeatRequirement = (item) => {
const sameAreaData = selectedSeats?.reduce((acc, seat) => {
if (acc[seat.areaName]) {
acc[seat.areaName].count += 1
acc[seat.areaName].Price = seat.price
} else {
acc[seat.areaName] = {
count: 1,
Price: seat?.price,
areaCategoryCode: seat?.areaCategoryCode,
}
}
return acc
}, {})
Loyalty program
Loyalty program has few basic validation checks for
- Sufficient points balance
- Seat area category compatibility
- Maximum quantity limits
- Selected seats availability
Loyalty can be applied at 2 pages, seat layout & checkout
A. Seat Selection Phase (seatLayout = true):
- Calculates discount locally based on selected offers
- No API call until final booking
- Updates loyaltyDiscount state directly
B. Payment/Booking Phase (seatLayout = false):
- Makes API call to ApplyGroupLoyalty
- Updates pricing and discount from server response
Promotion offers
Promotion offers which are internal novo cinemas offers are also implemented on this page, but the feature’s flow has been changed from what was discussed during development flow so this is WIP.
Food & Beverages engine
Food and beverages was one of the most challenging state management I have worked on
For this implementation I use one source of truth (cart
) and mirror only the UI‑critical pieces we use (fnbPriceDetails
).
Technical Jargon
Adding / removing without modifiers
const immediateUpdateNoModifiers = (item, qtyDelta) =>
opening the dialog box
Situation | What opens |
---|---|
Item has neither modifiers nor alternates | immediateUpdateNoModifiers (no dialog) |
First time adding, or item has alternates | Modifier Modal |
Item is already in cart multiple times / combos and user clicks – | Aggregator Modal (choose which combo line to change) |
Item is in cart, + pressed again, but no alternates | Aggregator Modal (quick qty) |
Selecting modifiers
handleModifierChange(modId, qty, groupId, idx)
Radio‑groups (max = 1) replace any previous choice.
Quantity selectors increment/decrement per key
key = ${itemId}-${modifierId}-${index}
-> unique inside modifierSelections
.
selectedByGroup
keeps which radio is active so the input is checked.
Shows each unique combo for that base item.
aggregatorCombos
is re‑derived fromcart
every timeaggregatorItem
orcart
changes.- Qty buttons call
handleAggregatorComboQtyChange
, which repeats the same clamp‑to‑0/20 logic and writes through both global arrays. - Expanding the combo (
toggleComboOpen
) simply reveals the modifiers string so the user sees what makes that line unique.
Implementation Take‑Aways
- Keep one source of truth (
cart
) and mirror only the UI‑critical pieces you need (fnbPriceDetails
). - Hash complex selections into a deterministic key (
generateComboId
) so updates are simple array operations. - Isolate transient modal state; never mutate the cart until validation passes and the user confirms.
- Enforce business constraints (min/max per modifier group, item qty ≤ 20) at the point of change, not later.
That covers the moving parts: state flows, dialog orchestration, validation, quantity guards, and price construction.
Checkout page
After the user selects the food & beverages they want they continue to the checkout page, where we have Payment & Offers
Let’s talk about offers first
- Loyalty offers: These are synced with the offers on the home page user can apply, remove grouped offers here
- Bank offers: If you have applied a bank offer on the seat layout page your card number’s first and last 4 digit would already be present prompting you to just enter your cvv and expiry and checkout, otherwise you can also remove the offers & pay using other cards, or just apply the offer if you’re here for the first time
- Promotions & offers: These are custom offers decided by the team
- Other than these we also have voucher code, which can applied on any screen and you will get a voucher discount
- Novo wallet : There is a personal wallet for the users which users can use to pay, they can top up their wallet and pay using that
- Users can purchase gift cards which can be used at the time of checkout
After applying the offers if your total amount goes to 0 you can just proceed and your ticket will be booked otherwise you need to pay the rest of the amount
Payment
For payment we have the following options
- apple pay
- google pay
- credit card
- debit card
Apple pay is the most widely used payment method in Qatar, after deployment we have been monitoring the apple pay transaction status and it has been 97% success rates.
This covers the entire booking flow from seat layout to checkout page, There are 2 other flows, private booking & Kiosk.
For Kiosk systems, We have the entire booking flow with direct food and beverages flow and POS machine integration and writing code in .NET, will write a blog on that soon
This project has been a deep dive into large-scale system design, state management, and performance optimization. It taught me how to balance user experience, business requirements, and technical complexity. In the next blog, I’ll cover private booking flow and our .NET-based kiosk implementation.”