Commerce & Checkout

Checkout stays simple for the customer, while the backend remains highly robust.

The customer sees a calm, understandable journey. Behind it sit Delivery Groups, slot capacity, Character capacity, temporary holds, wallet/coupon validation, a payment state machine and server-side recalculation before order creation.

Server authoritative
Race-safe reservations

The unified path from cart to authoritative result

Each step establishes context for the next. We do not ask for Character Delivery before the delivery time is known, and we never treat a browser return as proof of payment success.

01Cart
02Identity
03Recipient / Address
04Delivery Groups
05Dates / Slots
06Gift Services
07Quote + Wallet
08Payment
09Authoritative Result

A mixed cart is explained before it surprises the customer

When product readiness differs, the order becomes multiple Delivery Groups. Each group owns its own timing, fees and services, while all groups still belong to one order.

Delivery Group A
Available sooner
Toy AReady today
Toy BReady today
6–9
Tuesday · 6–9 PM

Balloons and Mario can be attached specifically to this delivery group.

Delivery Group B
Needs preparation
Special itemReady tomorrow
3–6
Wednesday · 3–6 PM

It has independent timing, fee and capacity while remaining part of the same order.

Slot and Character are independent systems — test the capacity yourself

A delivery slot may be available while Mario is full, or the reverse. Checkout combines both systems only when the customer requests the Character service.

Delivery Slot4 remaining
Mario Capacity1 remaining
The slot and Mario can both be selectedBoth capacity sources are available in this context.
Character availability

effective capacity − (confirmed + valid holds). Expired holds do not count, even if the cleanup worker has not deleted the row yet.

Two customers see the last capacity unit at the same time

The UI may show “1 left” to both. Only one transaction should succeed; the other recalculates capacity and receives a clear recovery result.

Customer A

Mario · 1 left

Ready to send a reservation request.

VS
Customer B

Mario · 1 left

Ready to send a reservation request.

A

Simulation has not started.

B

Simulation has not started.

Browser return ≠ Payment success

The backend owns the final state. Provider webhook and customer return can arrive in any order, so we need idempotency, deduplication and legal state transitions.

PENDING attempt

Before opening the provider session, create a payment attempt with a unique reference tied to the checkout/order context.

Webhook + callback

Both can report or request status from the backend, but no path may mark the same order paid twice.

Commit once

After verification: mark paid once, commit reservations, then emit notifications and analytics downstream.

Expected errors have explicit names and designed recovery

Instead of a generic “Something went wrong,” every critical error expresses a real business condition that the UI can handle deliberately.