Birthday Celebration Add-On
Description / Background
Birthday Celebration Add-On is a new feature that enables restaurants to self-serve birthday add-ons through the partner portal and enables diners to discover and book these add-ons through the diner-facing app. The feature reuses the existing add-on model with an add_on_type discriminator and supports both paid and free (฿0) birthday add-ons with distinct behaviors. Paid birthday add-ons appear in a fixed “Birthday Celebration” section on the restaurant page and are discoverable via a dynamic icon on restaurant cards. Free birthday add-ons are hidden from the store page and cart, and only surface when the diner selects “Birthday” as the occasion at checkout.
Problem: Diners planning a birthday booking have no easy way to find or add birthday-specific add-ons. Restaurants have no self-service way to create birthday add-ons — everything is handled by HH staff behind the scenes.
Solution: A new partner-portal screen for restaurant self-service birthday add-on creation, a “Birthday Celebration” section on restaurant pages, discovery icon on restaurant cards, free-add-on gating via checkout occasion selection, and “exactly one birthday add-on per booking” enforcement at checkout.
Objectives
- Diners can see a discovery icon on restaurant cards when the restaurant has at least one active birthday add-on (paid or free), using the restaurant’s actual birthday add-on imagery dynamically instead of one fixed icon
- Diners can tap the discovery icon and deep-link directly to the restaurant’s Birthday Celebration section
- Diners can browse paid birthday add-ons in a fixed “Birthday Celebration” section on the restaurant page when the restaurant has at least one active paid birthday add-on
- Diners can select exactly one birthday add-on per booking (quantity one); selecting another swaps the current selection
- Diners can add birthday add-ons to cart and checkout with correct pricing and booking state
- Diners can be routed to the Payment Gateway flow when a paid birthday add-on is selected
- Diners selecting “Birthday” as the occasion at checkout see an offer for a free (฿0) birthday add-on if the restaurant has one configured
- Diners cannot add more than one birthday add-on per booking — checkout is the authoritative gate
- Diners see the free birthday add-on labeled as “Free” in cards, cart, and checkout, but as “฿0” in the order summary/price breakdown
- Diners can swap birthday add-ons in cart with a confirmation message
- Diners see the birthday add-on in their booking confirmation and can receive accurate email communications
- Restaurants can create and manage their own birthday add-ons through the partner portal
- Restaurants can set any price including ฿0 for a free birthday add-on
- Restaurants can set their own daily limits for free birthday add-ons
- Restaurants cannot create birthday add-ons through the internal admin dashboard — only via partner portal
- Restaurants setting ฿0 for a birthday add-on create a “free” birthday add-on that is hidden until the diner selects Birthday as the occasion at checkout
- Restaurants setting a price above ฿0 create a “paid” birthday add-on that appears in the Birthday Celebration section on the store page
- Restaurants can add a cover image (420×240px recommended, 10 MB max) and menu image (2048×2048px recommended, 10 MB max) to their birthday add-ons
- Restaurants can include items in their birthday add-on with at least one “Cake” row required, plus two optional rows pre-filled with suggestions
- Restaurants can tag birthday add-ons for filtering in the partner portal list
- Restaurants can set a minimum booking lead time that defaults to “Follow Restaurant Level”
- Restaurants cannot create birthday add-ons via group login accounts (same restriction as package creation)
- Birthday add-ons are excluded from the standard admin add-on catalog list
- Birthday add-ons use the existing commission inheritance logic: restaurant-level override → latest non-partner-created package rate → 12.50% fallback
- Birthday add-ons behave like any other add-on in reporting, exports, analytics, and booking details
- Birthday add-ons are excluded from demand-based pricing
- Vouchers work on birthday add-ons the same as any other add-on
- The attach rate for birthday add-ons reaches at least 1% of eligible bookings within 60 days
- At least 150 restaurants create at least one birthday add-on within 60 days
- The discovery icon click-through rate reaches at least 3%
- Checkout failures for birthday bookings stay under 0.5%
- Birthday add-on revenue at participating restaurants is additional (not cannibalizing other add-ons)
Terms & Conditions
- Exactly one birthday add-on, quantity one, can be attached to a booking — checkout is the authoritative gate.
- A free (฿0) birthday add-on is hidden from the store page and cart, only surfaces when the diner selects “Birthday” as the occasion at checkout.
- No birthday verification or sensitive data collection is required.
- Birthday add-ons are created only through the partner portal, not through the internal admin dashboard.
- Birthday add-ons are excluded from the regular admin add-on catalog list.
- Commission follows the existing inheritance logic: restaurant-level override → latest non-partner-created package rate → 12.50% fallback.
- Birthday add-ons are excluded from demand-based pricing but vouchers work normally.
- Group login accounts cannot create birthday add-ons (same as package creation).
Scope
- Partner portal create/edit flow for birthday add-ons (new screen)
- Restaurant discovery icon on cards (homepage, search, group landing, bitelist)
- Restaurant page “Birthday Celebration” section (fixed heading, paid add-ons only)
- Birthday add-on card and detail presentation
- Cart and checkout behavior (one-per-booking enforcement, pricing)
- Free birthday add-on flow (hidden until occasion = Birthday at checkout)
- Booking confirmation and reporting integration
- Backend: add-on model with
add_on_type = birthdaydiscriminator - Backend: partner API v1 CRUD, filtering, validation
- Backend: V5 discovery fields and menu compatibility
- Backend: booking rules, free-add-on limits, occasion gating
- Backend: admin filtering, pricing inheritance
- Localization across 8 languages
Upcoming
- Filter/sort by birthday availability on discovery surfaces
- Native translations for incomplete locale coverage
Out of Scope
- Birthday verification or sensitive data collection
- Voucher exclusion (vouchers work like any other add-on)
- Demand-based pricing inclusion (birthday add-ons excluded)
Location
Backend (hh-server)
app/models/— add-on model withadd_on_typediscriminatorapp/controllers/api/v1/— partner API v1 CRUD for birthday add-onsapp/serializers/api/v5/— V5 discovery fields (add_on_type,item_type,is_free_birthday_add_on)app/services/— birthday booking rules, occasion gating
Hybrid (hh-pegasus / hh-felidae)
- Restaurant cards — discovery icon
- Restaurant page — Birthday Celebration section
- Add-on cards and detail views
- Cart and checkout flow
- Booking confirmation
- Localization
How to find Birthday Celebration Add-On
- For diners: Look for a small icon on restaurant cards (homepage, search, group landing, bitelist). Tapping it deep-links to the restaurant’s Birthday Celebration section.
- For diners: Open a restaurant page and scroll to the “Birthday Celebration” section (if the restaurant has paid birthday add-ons).
- For diners: At checkout, select “Birthday” as the occasion to see a free add-on offer (if the restaurant has one configured).
- For restaurants: Log into the partner portal and look for the new “Add-On” creation screen — birthday add-ons are created there, not in the admin dashboard.
How to set Birthday Celebration Add-On
- Enable feature flag
ff_birthday_add_on_v1(if applicable). - For restaurants: Log into the partner portal → Add-Ons → Create New → fill in birthday add-on details (name, price, cover image, items, etc.).
- Price = ฿0 creates a free birthday add-on (hidden on store page, surfaces at checkout when occasion = Birthday).
- Price > ฿0 creates a paid birthday add-on (appears in Birthday Celebration section on store page).
- For diners: Select a birthday add-on at checkout, or select “Birthday” as the occasion to see a free add-on offer.
Sequence Diagram / Flow
Restaurant creates birthday add-on (partner portal)
sequenceDiagram
participant R as Restaurant Partner
participant PP as Partner Portal
participant API as Partner API v1
participant DB as Add-On Table
R->>PP: Opens Add-On create screen
PP->>R: Renders form (cover image, price, items)
R->>PP: Fills form, sets price (paid or ฿0)
PP->>API: POST /api/v1/restaurant_add_ons
API->>DB: Creates add_on_type=birthday row
API-->>PP: Returns created add-on
PP-->>R: Shows success
Diner discovers birthday add-on (restaurant card icon)
sequenceDiagram
participant D as Diner
participant C as Restaurant Card (Homepage/Search)
participant API as Restaurant API
participant RP as Restaurant Page
D->>C: Browses restaurant cards
C->>D: Shows birthday discovery icon (if has add-ons)
D->>C: Taps icon or card
C->>RP: Deep-links to ?section=birthday-celebration
RP->>D: Loads Birthday Celebration section
Diner books paid birthday add-on
sequenceDiagram
participant D as Diner
participant RP as Restaurant Page
participant Cart as Cart
participant CO as Checkout
participant API as Booking API
D->>RP: Selects paid birthday add-on
RP->>Cart: Adds to cart (swap if another birthday selected)
D->>CO: Proceeds to checkout
CO->>CO: Enforces exactly one birthday add-on
CO->>API: Submits booking
API->>API: Validates one-per-booking rule
API-->>D: Confirms booking with birthday add-on
Diner claims free birthday add-on at checkout
sequenceDiagram
participant D as Diner
participant CO as Checkout
participant OCC as Occasion Selector
participant API as Booking API
D->>CO: Selects "Birthday" as occasion
OCC->>CO: Triggers free add-on offer
CO->>D: Shows "Celebrating a birthday? {Restaurant} treats you to {Add-on}, Free"
D->>CO: Accepts free add-on
CO->>API: Submits booking with free birthday add-on
API-->>D: Confirms booking with free add-on
ERD
- Modified table
restaurant_add_ons— existing table gainsadd_on_typecolumn (defaultstandard, set tobirthdayfor birthday add-ons),item_type(items_list/items_image),is_free_birthday_add_onboolean. - No new tables — reuses existing
restaurant_add_ons,restaurant_add_on_items,restaurant_add_on_translationstables. - No schema migrations required for this PR (reuses existing add-on system).
Backend Implementation
- Birthday add-ons reuse the existing add-on model with
add_on_type = birthdaydiscriminator. - Supports
item_type = items_list(canonical free form with Cake item) oritems_image(alternate image mode). - V5 responses expose
add_on_type,item_type, andis_free_birthday_add_on. - Partner API v1 provides CRUD operations with filtering, pagination, validation.
- Partner create/update operations use authenticated restaurant context, do not trust request-body
restaurant_id. - Group-login accounts are blocked during birthday add-on selection.
- Birthday add-ons excluded from standard admin add-on catalog list.
- Birthday V5 menus use existing image-gallery contract for consumer compatibility.
- Birthday add-ons excluded from demand-based pricing.
- Vouchers work on birthday add-ons the same as any other add-on.
- Commission inheritance: restaurant-level override → latest non-partner-created package rate → 12.50% fallback.
- Daily-limit enforcement using existing read-and-count behavior.
- No N+1 query issues in listing endpoints.
- Feature gated by existing birthday feature flag.
PRs:
Hybrid Implementation
hh-pegasus (consumer web/app):
RestaurantDetailEntry.vue— adds responsive birthday celebration discovery surfacesCheckoutChargeSummary.vue— integrates birthday selection into checkoutQrMenuImage.vue— expands birthday add-on detail presentationAddOnPricing.vue— adds birthday-specific card selection and pricingRestaurantBodyMain.vue— enforces exclusive paid birthday add-on selectionRestaurantSectionNavDesktop.vue— shows birthday badges in add-on navigationBirthdayAddOnBadge.vue— highlights birthday offerings on restaurant cardsaddOnPackage.ts(+176/-2) — centralizes birthday add-on formatting and metadataaddOnQuantity.ts(+47/-4) — supports exclusive birthday quantities during checkoutPack.ts— defines birthday add-on schemas and models; preserves birthday metadata in hybrid package typesgetRestaurantAddons.ts— groups birthday packages by celebration taggetBookingProcessData.ts— preserves birthday metadata through booking transformationsmergeSearchResult.ts— fetches and maps restaurant birthday metadataRestaurantPage.astro— wires birthday experiences into restaurant pagesBooking.ts— preserves birthday metadata in hybrid package types (+11); tracks booking state inbookingCharge.tsBookingCard.vue— displays birthday selections in summaries; updates inbookingPageCard.tsandbooking.tsCheckOutPage.vue(+121) — birthday occasion selection and checkout integrationBirthdayCelebrationCheckout.vue(+168),BirthdayCheckoutCard.vue(+102),BirthdayCelebrationContent.vue(+291),BirthdayCelebrationMobile.vue(+83),RestaurantBirthdayCelebrationDesktop.vue(+150),RestaurantBirthdayCelebrationMobile.vue— dedicated birthday celebration componentsAddOnPage.vue,AddOnSection.vue,AddOnItemsSection.vue,AddOnMenuView.vue,AddOnCardDetailView.vue,AddOnSelectButton.vue(+82),AddOnTimeAdvance.vue— add-on selection and configurationRestaurantHeaderDesktop.astro(+33/-17),RestaurantHeaderMobile.astro(+13),RestaurantPageDesktop.astro,RestaurantPageMobile.astro— header and page-level birthday integrationRestaurantBottomSheetMobile.vue(+63),useRestaurantBottomSheet.ts— mobile birthday bottom sheetindex.ts— exportsBIRTHDAYoccasion identifierrestaurant.json— translates birthday celebration interface copy (+15 per locale across 8 languages)
Bug fixes:
bookingCharge.ts— tracks birthday occasions and corrects booking stateSelectedAddOnPackages.vue— displays paid birthday selections in summaries
Formatting:
RestaurantHeaderItem.vue— refines restaurant header offer layout
PRs:
hh-felidae (search service):
- Adds birthday add-on discovery to restaurant card surfaces.
- Integrates with existing GraphQL schemas for birthday metadata.
- Supports deep-linking to birthday celebration section.
PRs:
PRD & Task
- PRD: Birthday Celebration Add-On — https://app.clickup.com/9003122396/v/dc/8ca1fpw-11562/8ca1fpw-66296
- Task (Backend): Birthday Celebration Add-On Backend Implementation — https://app.clickup.com/t/9003122396/86d3wymeh (
CU-86d3wymeh, status: in progress, assignee: Ali) - Task (Full): Implement Birthday Celebration add-on flow — https://app.clickup.com/t/9003122396/86d3twr3r (
CU-86d3twr3r, status: live (reviewing), priority: high, assignees: Wawan Hermawan, Monyta Rahmania, Ali, Fandi) - Figma: Birthday Add-On — https://www.figma.com/design/EYaKok1i60yH6onyeiy8f7/Birthday-Add-On
API Blueprint
| Method | Path | URL | Description | Payload |
|---|---|---|---|---|
| GET | /api/v1/restaurant_add_ons | Partner API list with add_on_type=birthday filter | ||
| POST | /api/v1/restaurant_add_ons | Partner API create birthday add-on | { name, price, add_on_type: "birthday", items: [...] } | |
| PATCH | /api/v1/restaurant_add_ons/:id | Partner API update birthday add-on | ||
| DELETE | /api/v1/restaurant_add_ons/:id | Partner API delete birthday add-on | ||
| GET | /api/v5/restaurants/:id | Restaurant detail with birthday fields | { add_ons: [ { add_on_type, is_free_birthday_add_on } ] } | |
| GET | /api/v5/restaurant_packages/:id/add_ons | Add-on list with birthday metadata |
DB Schema / Database Migration
- No new tables — reuses existing
restaurant_add_ons,restaurant_add_on_items,restaurant_add_on_translations. - The
add_on_typediscriminator,item_type, andis_free_birthday_add_oncolumns were introduced in v1. This PR extends the system with birthday-specific behavior; no new migrations required.
Improvement:
| Feature Name | Date | What Changed | Description |
|---|---|---|---|
| Birthday Celebration Add-On | 2026-08-20 | Backend hh-server PR #8609 | Adds birthday add-on backend: add-on model discriminator, partner API v1 CRUD, V5 discovery fields, booking rules, occasion gating, 287 RSpec examples. |
| Birthday Celebration Add-On | 2026-08-21 | Hybrid hh-pegasus PR #3133 | Adds diner-facing: discovery icon, Birthday Celebration section, paid/free add-on selection, checkout occasion gating, 83 files, +2186/-213. |
| Birthday Celebration Add-On | 2026-08-21 | Hybrid hh-felidae PR #414 | Adds search service birthday discovery integration, +51/-2 across 12 files. |