Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 = birthday discriminator
  • 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 with add_on_type discriminator
  • app/controllers/api/v1/ — partner API v1 CRUD for birthday add-ons
  • app/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

  1. 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.
  2. For diners: Open a restaurant page and scroll to the “Birthday Celebration” section (if the restaurant has paid birthday add-ons).
  3. For diners: At checkout, select “Birthday” as the occasion to see a free add-on offer (if the restaurant has one configured).
  4. 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

  1. Enable feature flag ff_birthday_add_on_v1 (if applicable).
  2. For restaurants: Log into the partner portal → Add-Ons → Create New → fill in birthday add-on details (name, price, cover image, items, etc.).
  3. Price = ฿0 creates a free birthday add-on (hidden on store page, surfaces at checkout when occasion = Birthday).
  4. Price > ฿0 creates a paid birthday add-on (appears in Birthday Celebration section on store page).
  5. 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 gains add_on_type column (default standard, set to birthday for birthday add-ons), item_type (items_list / items_image), is_free_birthday_add_on boolean.
  • No new tables — reuses existing restaurant_add_ons, restaurant_add_on_items, restaurant_add_on_translations tables.
  • 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 = birthday discriminator.
  • Supports item_type = items_list (canonical free form with Cake item) or items_image (alternate image mode).
  • V5 responses expose add_on_type, item_type, and is_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 surfaces
  • CheckoutChargeSummary.vue — integrates birthday selection into checkout
  • QrMenuImage.vue — expands birthday add-on detail presentation
  • AddOnPricing.vue — adds birthday-specific card selection and pricing
  • RestaurantBodyMain.vue — enforces exclusive paid birthday add-on selection
  • RestaurantSectionNavDesktop.vue — shows birthday badges in add-on navigation
  • BirthdayAddOnBadge.vue — highlights birthday offerings on restaurant cards
  • addOnPackage.ts (+176/-2) — centralizes birthday add-on formatting and metadata
  • addOnQuantity.ts (+47/-4) — supports exclusive birthday quantities during checkout
  • Pack.ts — defines birthday add-on schemas and models; preserves birthday metadata in hybrid package types
  • getRestaurantAddons.ts — groups birthday packages by celebration tag
  • getBookingProcessData.ts — preserves birthday metadata through booking transformations
  • mergeSearchResult.ts — fetches and maps restaurant birthday metadata
  • RestaurantPage.astro — wires birthday experiences into restaurant pages
  • Booking.ts — preserves birthday metadata in hybrid package types (+11); tracks booking state in bookingCharge.ts
  • BookingCard.vue — displays birthday selections in summaries; updates in bookingPageCard.ts and booking.ts
  • CheckOutPage.vue (+121) — birthday occasion selection and checkout integration
  • BirthdayCelebrationCheckout.vue (+168), BirthdayCheckoutCard.vue (+102), BirthdayCelebrationContent.vue (+291), BirthdayCelebrationMobile.vue (+83), RestaurantBirthdayCelebrationDesktop.vue (+150), RestaurantBirthdayCelebrationMobile.vue — dedicated birthday celebration components
  • AddOnPage.vue, AddOnSection.vue, AddOnItemsSection.vue, AddOnMenuView.vue, AddOnCardDetailView.vue, AddOnSelectButton.vue (+82), AddOnTimeAdvance.vue — add-on selection and configuration
  • RestaurantHeaderDesktop.astro (+33/-17), RestaurantHeaderMobile.astro (+13), RestaurantPageDesktop.astro, RestaurantPageMobile.astro — header and page-level birthday integration
  • RestaurantBottomSheetMobile.vue (+63), useRestaurantBottomSheet.ts — mobile birthday bottom sheet
  • index.ts — exports BIRTHDAY occasion identifier
  • restaurant.json — translates birthday celebration interface copy (+15 per locale across 8 languages)

Bug fixes:

  • bookingCharge.ts — tracks birthday occasions and corrects booking state
  • SelectedAddOnPackages.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

API Blueprint

MethodPathURLDescriptionPayload
GET/api/v1/restaurant_add_onsPartner API list with add_on_type=birthday filter
POST/api/v1/restaurant_add_onsPartner API create birthday add-on{ name, price, add_on_type: "birthday", items: [...] }
PATCH/api/v1/restaurant_add_ons/:idPartner API update birthday add-on
DELETE/api/v1/restaurant_add_ons/:idPartner API delete birthday add-on
GET/api/v5/restaurants/:idRestaurant detail with birthday fields{ add_ons: [ { add_on_type, is_free_birthday_add_on } ] }
GET/api/v5/restaurant_packages/:id/add_onsAdd-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_type discriminator, item_type, and is_free_birthday_add_on columns were introduced in v1. This PR extends the system with birthday-specific behavior; no new migrations required.

Improvement:

Feature NameDateWhat ChangedDescription
Birthday Celebration Add-On2026-08-20Backend hh-server PR #8609Adds 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-On2026-08-21Hybrid hh-pegasus PR #3133Adds diner-facing: discovery icon, Birthday Celebration section, paid/free add-on selection, checkout occasion gating, 83 files, +2186/-213.
Birthday Celebration Add-On2026-08-21Hybrid hh-felidae PR #414Adds search service birthday discovery integration, +51/-2 across 12 files.