Commerce Engine for One of Europe's Largest Resorts with 5M+ Annual Visitors
PortAventura World partnered with us to architect and build the headless commerce backend powering online sales across its theme parks and hotels. The platform handles massive traffic and complex ticket & hotel sales logic.



Client
When PortAventura World opened in 1995, it was a single theme park on the Costa Daurada near Barcelona. Three decades later, it's one of the largest leisure destinations in Europe – three theme parks, ten hotels with over 3,300 rooms, a convention centre, and a track record of more than 100 million visits.
In 2025 alone, over 5.3 million visitors passed through the gates. The resort's scale puts it alongside names like Europa-Park and Disneyland Paris in the European market.
But scale creates its own challenge. Millions of those visitors start their journey online – buying park tickets, booking hotel rooms, adding Express Passes and extras to their carts.
Behind each of those transactions sits a chain of interconnected systems: a ticketing platform for park entry and add-ons, a hotel reservation system managing real-time availability, and multiple payment gateways processing transactions across Europe.
PortAventura World needed a modern commerce backend that could hold all of that together in one checkout – at peak-season volumes, without a single mispriced ticket or lost reservation.
- One commerce infrastructure built on Medusa for 3 theme parks & 10 hotels
- Architecture designed to scale to 10 parallel server instances during peak traffic
- Dynamic per-day pricing engine spanning tickets, hotels, and bundles across multiple sales channels
Download case study

annual visitors
years of history
theme parks
Problems

Challenge
PortAventura World needed a commerce backend that could sell park tickets, hotel reservations, and extras through a single storefront, integrate with external systems, and handle the traffic volumes of one of Europe's busiest leisure destinations.
When 5M+ visitors per year are booking tickets and hotels online, the commerce platform becomes as operationally critical as the parks themselves. Downtime or a mispriced ticket during peak season costs real money per minute.
Peak Traffic at Resort Scale
The platform had to be built to handle massive traffic spikes during peak season – school holidays, long weekends, special events.
The architecture needed to support up to 10 server instances running simultaneously, with session data (applied promo codes, cart state) consistent across all of them, regardless of which instance handles the next request.
Products with Different Lifecycles
A standard product has stock. A ticket is tied to a specific date and has daily capacity limits. A hotel room needs to be held temporarily during checkout via an external reservation system. Each has its own availability logic, pricing model, and fulfilment path.
The platform had to combine all three in one cart, apply shared promotions, and process them through a single payment flow, while the customer sees one checkout.
Fulfillment Failures After Successful Payment
When a customer completes a payment, the backend still needs to confirm the purchase with external systems – one that issues park tickets, another that confirms hotel reservations. These are separate services with their own failure modes.
The critical scenario: a customer's card is charged successfully, but the external ticketing or reservation system returns an error. The money has left the customer's account. No ticket has been generated. No room has been confirmed.
This failure path doesn't exist in standard eCommerce. Most platforms assume: payment confirmed → order done.
Here, payment is only step one. The backend needed to detect these partial failures, recover from them automatically, and give operations staff a manual fallback – all without creating duplicate charges or orphaned bookings.
Promotions That Change by the Day
PortAventura World runs promotions where the same discount code applies to different percentages depending on which day the ticket is for. Thursday might be -20%. Friday might be -50%. The same code, different outcomes based on calendar context.
On top of that, the platform needed to support partner-specific codes: Carnet Jove (a Catalan youth card), where only the code prefix determines the discount type, and DIA-generated codes where hundreds of unique partner codes all map to one internal promotion rule.
Date-Based Cart Logic Across Multiple Product Types
In typical eCommerce, a customer adds a product to the cart and checks out. At PortAventura World, the same product, like an Express Pass, can appear in the cart multiple times, each tied to a different visit date.
A family buying a 3-day trip might add the same pass three times, each for a different day, each with different pricing and availability constraints.
This applies to tickets, hotel rooms, and extras – all in one cart, all with per-day pricing, all subject to daily capacity limits.
The cart engine had to validate availability per product per date in real time, and feel like a single purchase at the same time.
Locking a Hotel Room Before the Customer Pays
When a customer selects a hotel room and enters the payment flow, that room has to be held for them. If it isn't, another customer can book the same room while the first is still typing their card number.
The reservation hold had to happen before the payment gateway was called. Without this, peak-season inventory would be either double-booked or permanently blocked by abandoned carts.

Solutions
We served as the technical masterminds of the project – designing the architecture, implementing the full Medusa backend infrastructure with all custom business logic, and building every integration with PortAventura World's existing systems from the ground up.
The platform unifies three fundamentally different product types – date-based tickets, hotel reservations, and add-ons – in a single commerce engine. Each comes with its own pricing, availability, and fulfilment logic, all coordinated through custom modules.
It also combines legacy systems (ticketing & hotel management), external ticketing providers' systems, and seasonal promotions.

Reliability System for External Confirmations
PortAventura World's operations run on two external systems – Galaxy, which issues park tickets, parking, menus, and Express Passes, and TravelClick, which manages hotel availability and reservation confirmations. When a customer pays, the commerce backend needs both systems to confirm the purchase.
We built both integrations from scratch with a retry layer that catches these cases automatically. The system detects partial failures, retries the external confirmation in the background, and logs the result.
If automatic recovery fails, operations staff can re-trigger the confirmation manually from the custom Admin Panel – no refund-and-rebook cycle needed.
Extras as a Standalone Product Type
We extracted extras (parking, strollers, photos, and similar add-ons) into a dedicated product type with its own inventory sync through the park's ticketing system.
A built-in validation rule enforces that extras can only be purchased alongside a park ticket, preventing standalone orders that the resort couldn't fulfil.

Order Splitting by Product Type
A single cart can contain tickets, hotel reservations, and extras, but each product type follows a different fulfilment path. We implemented logic that automatically splits one cart into separate orders per product type at checkout.
This enables parallel fulfilment: the ticketing system processes park entry, the hotel system confirms the reservation, and extras are handled independently – all from what the customer experienced as a single purchase.
Reservation Hold Logic
When a customer selects a hotel room and enters the payment flow, that room needs to be locked before the payment gateway is called.
We implemented a TTL-based reservation hold system with cron jobs that release resources automatically if payment isn't completed within the time window. This prevents double-bookings without permanently blocking inventory.
Dynamic Pricing Engine
This engine includes a per-day price calendar for tickets and hotels, where prices change daily and are managed through the Admin Panel.
It supports fixed-amount and percentage-based price modifications, with separate Price Lists per Sales Channel.
This means the same room or ticket can carry different pricing for different distribution partners or campaigns, all managed centrally.

Bulk Availability Endpoint
Instead of querying availability product by product, we built a single endpoint that accepts an array of products, dates, and pagination parameters.
It returns all availability limits and prices, including active promotional pricing, in one call. This reduced frontend load times and cut the number of API requests during high-traffic periods.
Ticket Limits System
A custom module that validates and reserves daily availability for products with strict capacity restrictions. It's deeply integrated into the cart flow, so if a product hits its daily limit, the cart rejects the addition in real time.
Advanced Promotions Engine
We rebuilt Medusa's promotions layer to support context-dependent discounts. One code can trigger different discount values depending on the visit date, the day of the week, or the sales channel.
We also built a promotion rule that restricts how many times a single customer can use a specific discount code, tracked by email address. This prevents abuse of partner codes, for programmes like Carnet Jove and DIA, where codes are distributed widely and could otherwise be reused without limit.

Visual ID & QR Code Generation
Upon successful ticket purchase, the backend automatically generates a Visual ID and QR code for each ticket through the park's ticketing API.
These are embedded directly into the order confirmation flow – the customer receives a scannable entry pass without any manual processing by resort staff.
Bundle System
We designed a bundle product type that combines tickets, hotel stays, and extras into a single package with a calculated total price and discount. Bundles work across multiple Sales Channels.
This gives PortAventura World's team the ability to create and manage packaged offers, like a weekend deal with park entry, hotel, and parking, without engineering involvement.

Flexible vs. Multi-Day Tickets
We designed and implemented two distinct ticket models within the same commerce engine.
- Multi-day tickets are tied to a specific visit date selected in the cart – each date carries its own pricing and availability constraints.
- Flexible tickets are open-dated, with no date selection required at purchase.
Each model follows its own validation, pricing, and reservation logic, but both coexist in a single cart and checkout flow.
This allows PortAventura World to sell open-dated tickets for spontaneous visitors and date-specific passes for peak-season demand management, through one storefront, with one payment.
Redsys & Sequra Payment Providers
We integrated Redsys & Sequra – payment providers built from scratch – with Medusa's payment logic. Both include custom redirect handling and cryptographic verification.
Payload CMS Sync
A one-way product sync from Medusa to Payload CMS, running on an automatic cron job to keep the catalogue in sync without manual intervention.
Redis for Session Consistency
Redis maintains cart session data across all server instances. With up to 10 instances running in parallel during peak traffic, this prevents data loss when requests are routed to different servers.

Results
PortAventura World now operates on a commerce infrastructure built to match the scale of one of Europe's largest leisure destinations.
The platform is ready to serve over 5 million annual visits to the resort – families browsing tickets on a Tuesday evening, travel agents packaging hotel deals, tourists comparing Express Passes on their phones while standing in line at the airport.
Every one of those sessions hits a backend that holds together 3 product types, 6 external integrations, and per-day pricing across multiple sales channels – in a single checkout.
During peak season, the system scales to 10 parallel server instances. Cart sessions, applied promo codes, and reservation holds stay consistent across all of them. A customer can start on one server and finish on another without losing state.
This is what it takes to run commerce infrastructure for a resort, where a slow checkout page on a Saturday morning in July has a direct cost in unsold tickets.
The reliability layer catches the failure mode that matters most at this scale – successful payment, failed external confirmation. Instead of generating support tickets and manual refund cycles, the system auto-retries and gives operations staff a one-click recovery path. No revenue lost to API timeouts.
PortAventura World's commercial team now controls pricing, promotions, and bundled offers directly through the Admin Panel. Per-day ticket pricing, context-dependent discount codes, partner programmes, per-customer usage limits – all managed without engineering involvement. When the marketing team wants to launch a weekend bundle with park entry, hotel, and parking at a discounted rate, they configure it themselves and push it live.
This architecture was built with expansion in mind. New sales channels, additional product types, and future integrations can be added without rebuilding the core system. The result is a commerce platform that matches the operational complexity of the resort it serves.

Let’s build together
Let’s talk about how we can build your commerce project — tailored to your business, powered by Rigby

Got a commerce
modernization challenge?
Let's talk
A 30-minute Commerce Future Session - tailored to your stack, your challenges, your roadmap. No pitch. Just perspective. No preparation needed on your side.

“Whether you're exploring migration, struggling with legacy complexity, or just want a second opinion - we're here.”


















