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

Dynamic Pricing

Description / Background

Restaurant wants to set multiple pricing for each day, they want to have weekday prices, weekend prices and holiday prices. They want to fully customize the price for each day.

Glossary

Private (https://app.clickup.com/9003122396/docs/8ca1fpw-35796/8ca1fpw-41516)

Objectives

  • User can find dynamic pricing on store page
  • User can see pricing option on package details
  • User can see detail pricing for Public Holiday / special day
  • User can scroll detail pricing for public holiday / special day if its more than 3 days
  • User can see minimum 2 types of pricing options
  • User can only see current and future pricing option
  • User can see the detail pricing by hovering
  • User can see the pricing name (custom from inputing on admin dashboard)
  • User can see separate holiday price

If the package have 3 holiday, valentine, new year, and christmas. It will show as 3 different section card on user side.

  • User can see the promotion badge on top of Book + button on store page

  • User can see the promotion badge on the restaurant card on homepage

  • User can see the promotion badge on the restaurant card on search page

  • User can see the promotion badge on the restaurant card on group landing page

  • User can see the promotion badge (%) on the pricing on package detail page

  • There are 5 category of pricing option :
    1. Weekday (Mon - Fri)
    2. Weekend (Sat - Sun)
    3. Everyday
    4. Custom
    5. Holidays / Special Day
  • Admin can add pricing option on each package
  • Admin can add multiple pricing option
  • Admin can see 3 pricing type:
    1. Normal price (only have 1 price)
    2. price by party size (have multiple price based on party size)
    3. price by day (custom price for each day)
  • Admin can see 4 default custom day option:
    1. Weekday (Mon - Fri)
    2. Weekend (Sat - Sun)
    3. Everyday
    4. Custom (Admin can pick which day)
  • There are 2 section of Price by day:
    1. Custom Day (required)
    2. Holidays / SpecialDay
  • Admin can add title on custom day
  • Admin can see the public holiday / special day section

  • Admin can setting specific start and end dining date on public holidays/special day section
  • Admin can add the holiday title (TH, CN and EN) on public holidays/special day section
  • Admin can set name for Price by day
  • Admin and Owner will be able to see the price per person after selecting the date, time and number of people when creating the booking.
  • Admin and Owner will be able to see the price per person after selecting the date, time and number of people when editing the booking.
  • Admin can set the promotion badge on edit package:
    1. for dynamic pricing promotion
      1. Open Edit Package
      2. Go to price section
      3. Pick price type Price by Day
      4. Go to Public Holidays /Special Days section
      5. Checked the Display Promotion Badge
      6. Insert the percentage number
    2. for come more pay less promotion
      1. Open Edit Package
      2. Go to promotion section
      3. Checked the Come more pay less
    3. for top up badge
      1. Open Edit Package
      2. Go to promotion section
      3. Checked the Display Top Up Badge
  • The system will show the highest promotion percentage
  • The Top Up Badge can be used for :
    1. Normal Price
    2. Price by Party Size
    3. Price by Day
    4. Price by Day → Public holidays / Special days
    5. If there are Top up, special day and come more pay less , should use one of the highest percentage between them
  • The Come more pay less can be used for:
    1. Normal Price
    2. Price by Party Size
    3. Price by Day → Public holidays / Special days
    4. If there are discount promotion and come more pay less , should use one of the highest percentage between them
  • The System will transform the come more pay less into percentage to choose which bagde will shows:

100% - ((Group size to pay / group size) *100%)

Come 4 pay 3 → 25% off

Come 5 pay 4 → 20% off

Scope

Old Store Page, New Store Page, Admin For All platform

Location

Detail Package

How to find Dynamic Pricing

  • Open Store Page
  • Pick the package that you want to book
  • Click "View Menu" button
  • You can find the Dynamic pricing there

How to set Dynamic Pricing

Private (https://app.clickup.com/9003122396/docs/8ca1fpw-34056/8ca1fpw-39296)

From Restaurant Page

From Package Page

For Vendor

  • Click Update Channel
  • Now you can find the dynamic pricing response on the API

Sequence Diagram / Flow

-

ERD

-

PRD

Private (https://app.clickup.com/9003122396/docs/8ca1fpw-7922/8ca1fpw-36596) Private (https://app.clickup.com/9003122396/docs/8ca1fpw-7922/8ca1fpw-36576) Private (https://app.clickup.com/9003122396/docs/8ca1fpw-11562/8ca1fpw-36616)

Private (https://app.clickup.com/t/86cuyeq08) Private (https://app.clickup.com/t/86cvmtnec) Private (https://app.clickup.com/t/86cw7tme2) Private (https://app.clickup.com/t/86cwm3wkw) Private (https://app.clickup.com/t/86cwavp3m) Private (https://app.clickup.com/t/86cx3baf1)

Backend Implementation

  • Remove app/concepts/booking_cpt/cell/landing_page.rb
  • Remove include PackagePricingValidationConcern
  • Add pricing validation on app/controllers/admin/packages/base_controller.rb
  • Add model concern DynamicPricingsForPackage
  • Change code [package.pricing].flatten.compact to package.dynamic_pricings
  • Replace .pricing_type to .dynamic_price_pricing_model
  • replace .pricing_model to .dynamic_price_dynamic_pricing_type
  • create api to send dynamic pricing to partner portal
  • call the api when owner picking the package name on create booking or edit booking, based on the date, time, adult and kids they picked. It will show the price on the package list before the package name

  • Added and modified several sections to support displaying and interacting with promotion badges.
  • Introduced promotionBadgeDisplay and promotionBadgePercentage fields within form components and their related logic.
  • Added validation and error handling for the new fields.
  • Added a method to fetch the highest promotion badge from a list of restaurants.
  • Included promotion_badge_text in the list of keys for dynamic pricing JSON representation.
  • Added promotion_badge_text to the pricing JSON representation.
  • Added methods to handle promotion badge logic, including determining the type and text of the promotion.
  • Added private methods for formatting and updating related fields.
  • Added a new field, promotion_badge_percentage.
  • Added a new field, promotion_badge_percentage.
  • Added attributes to serialize promotion badge information.
  • Included promotion badge in the restaurant payload.
  • Added a new worker to handle expired promotion badge dynamic prices.
  • A new worker to process expired promotion badges.
  • Added methods to determine the highest promotion badge for a restaurant.
  • Added methods to calculate and format promotion badge percentages and texts.

github.com https://github.com/hungryhub-team/hh-server/pull/5548/files

github.com https://github.com/hungryhub-team/book-bite/pull/647/files

github.com https://github.com/hungryhub-team/hh-server/pull/6420

Frontend Implementation

  • Use min_seat and max_seat from package level if the pricing setting was by_day
  • If type normal no need to show multiple pricing.
  • package title will generated by min_seat and max_seat. If min_seat and max_seat numbering will be show like this: "{min_seat} - {max_seat} People" but if max_seat value Infinity will be "{min_seat} and more"
	"min_seat": "1",
    "max_seat": "Infinity",
  • Added a new field promotionBadge to the GraphQL queries, which includes promotionType and promotionText.
  •  New icon files added.
  • Introduced a new Vue component PromotionBadge for displaying promotional badges.
  • Integrated the new PromotionBadge component into various restaurant and favorite card components.
  • Adjusted layout and styles to accommodate the new PromotionBadge.
  • Added support for promotionBadge in package-related components.
  • Enhanced dynamic pricing and package card components to display promotional information.
  • Updated restaurant models and mappers to include promotionBadge data.
  • Updated relevant store and type definitions to include promotionBadge.
  • Integrated PromotionBadge into various partial and section components.

github.com https://github.com/hungryhub-team/hh-pegasus/pull/478/files

github.com https://github.com/hungryhub-team/hh-pegasus/pull/552

github.com https://github.com/hungryhub-team/hh-pegasus/pull/1530

Design

Dynamic Pricing Created with Figma

www.figma.com

Show exact price on package name Created with Figma

API Blueprint

Top-Level Attributes

  • mode : A string indicate pricing model. Possible value are per_pack , per_person , per_set .
  • type: A string indicating the pricing type. Possible values are "normal", "by_party_size", and "by_day".
  • rules: An array of objects, each representing a specific pricing rule according to the type.
  • kids_price_policy: A string describing the pricing policy for children.
  • come_more_pay_less_rules : A string describing the come more payless policy.

Attributes within rules

  • title : A string describing the dynamic pricing title
  • price
  • min_seat
  • max_seat
  • category_name: A string describing the dynamic pricing category name (custom_day or not)
  • days: An array of objects, each representing a specific days of the price
  • duration: not used
  • kids_price
  • start_date
  • end_date
  • dynamic_pricing_type:  A string describing the dynamic pricing pricing type (normal, by_party_size, by_day)
  • price_cents: A string indicating the price of the product in cents.
  • kids_price_cents: A string indicating the kids price of the product in cents
  • price_currency: The currency of the pricing
  • id: Dynamic pricing rules id

Common Attributes for All Types

  • title: A string describing the rule. This is only populated if the type is "by_day".
  • price: A string indicating the price of the product. The currency symbol is included.
  • kids_price: A string indicating the price for children. The currency symbol is included.
  • start_date: A string or nil value representing the start date for the pricing rule, formatted as "YYYY-MM-DD". This is only populated if the type is "by_day" and there is a specific date range for the rule.
  • end_date: A string or nil value representing the end date for the pricing rule, formatted as "YYYY-MM-DD". This is only populated if the type is "by_day" and there is a specific date range for the rule.

Attributes for "normal" and "by_party_size" Types

  • min_seat: A string indicating the minimum number of seats required to qualify for the price. For "normal" type, this is typically the string "2", and for "by_party_size" type, it varies based on the tier.
  • max_seat: A string indicating the maximum number of seats that qualify for the price. This can be a number or the string "Infinity".

Attributes for "by_day" Type

  • category_name: A string indicating the category of the day, such as "weekday", "weekend", or "holiday".
  • days: An array of integers representing the days of the week to which the pricing rule applies. Sunday is represented by 0, Monday by 1, and so on up to Saturday represented by 6.

Notes

  • The title, min_seat, max_seat, and category_name attributes are only relevant for the "by_day" pricing type and will be empty strings for other types.
  • The days attribute is an empty array for the "normal" and "by_party_size" pricing types, as these do not depend on specific days of the week.
  • The start_date and end_date attributes are used to define specific date ranges for special pricing rules and are nil when not applicable.
  • The kids_price_policy provides a human-readable description of the pricing rules for children, which may vary depending on the pricing type and rules defined.
MethodPathURLDescriptionResponsePayload
allall package apiAdd dynamic_pricing response normal per_person{
"dynamic_pricing": {
"mode": "per_person",
"type": "normal",
"rules": [
{
"title": "normal",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "Infinity",
"category_name": "",
"days": [],
"duration": 120,
"kids_price": "฿590",
"start_date": "",
"end_date": "",
"dynamic_pricing_type": "normal",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
}
],
"kids_price_policy": "",
"come_more_pay_less_rules": {}
}
}
allall package apiAdd dynamic_pricing response per_person and per_party_size{
"dynamic_pricing": {
"mode": "per_person",
"type": "by_party_size",
"rules": [
{
"title": "null",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "5",
"category_name": "",
"days": [],
"duration": 120,
"kids_price": "฿590",
"start_date": "",
"end_date": "",
"dynamic_pricing_type": "by_party_size",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
}
],
"kids_price_policy": "",
"come_more_pay_less_rules": {}
}
}
allall package apiAdd dynamic_pricing response per_person and per_day{
"dynamic_pricing": {
"mode": "per_person",
"type": "by_day",
"rules": [
{
"title": "normal",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "13",
"category_name": "",
"days": [
"Sunday",
"Saturday"
],
"duration": 120,
"kids_price": "฿590",
"start_date": "",
"end_date": "",
"dynamic_pricing_type": "custom_day",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
}
],
"kids_price_policy": "",
"come_more_pay_less_rules": {}
}
}
allall package apiadd dynamic pricing response per_day with holiday{
"dynamic_pricing": {
"mode": "per_person",
"type": "by_day",
"rules": [
{
"title": "eee",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "23",
"category_name": "custom_day",
"days": [
"Sunday",
"Saturday"
],
"duration": 120,
"kids_price": "฿590",
"start_date": "",
"end_date": "",
"dynamic_pricing_type": "by_day",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
},
{
"title": "fefee",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "23",
"category_name": "holiday",
"days": [],
"duration": 120,
"kids_price": "฿590",
"start_date": "2024-10-18",
"end_date": "2024-10-18",
"dynamic_pricing_type": "by_day",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
}

],
"kids_price_policy": "",
"come_more_pay_less_rules": {}
}
}
allall package apiAdd dynamic_pricing response normal per_pack{
"dynamic_pricing": {
"mode": "per_pack",
"type": "normal",
"rules": [
{
"title": "normal",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "Infinity",
"category_name": "",
"days": [],
"duration": 120,
"kids_price": "฿590",
"start_date": "",
"end_date": "",
"dynamic_pricing_type": "normal",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
}
],
"kids_price_policy": "",
"come_more_pay_less_rules": {}
}
}
allall package apiadd dynamic pricing response per_pack per_day with holiday{
"dynamic_pricing": {
"mode": "per_pack",
"type": "by_day",
"rules": [
{
"title": "eee",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "23",
"category_name": "custom_day",
"days": [
"Sunday",
"Saturday"
],
"duration": 120,
"kids_price": "฿590",
"start_date": "",
"end_date": "",
"dynamic_pricing_type": "by_day",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
},
{
"title": "fefee",
"price": "฿1,190",
"min_seat": "1",
"max_seat": "23",
"category_name": "holiday",
"days": [],
"duration": 120,
"kids_price": "฿590",
"start_date": "2024-10-18",
"end_date": "2024-10-18",
"dynamic_pricing_type": "by_day",
"price_cents": 2000000,
"kids_price_cents": 0,
"price_currency": "THB",
"id": 2447
}

],
"kids_price_policy": "",
"come_more_pay_less_rules": {}
}
}
POST/restaurant_packages/dynamic_pricing_listGet package with dynamic pricing.
Hit this API when admin and owner picking the package name, based on the date, time, adult and kids they picked

{
"success":true,
"message":"",
"data":[
{ "id":37640,
"name":"International Buffet + Free Flow Alcoholic Drinks (Test)",
"total_price":100,
"total_adult_price":100,
"quantity":1,
"adult":"1",
"price":100,
"package_type":"ayce",
"price_type":"per_person",
"base_price":100,
"allow_mix":true
}
]}
{
"adult": "1",
"kids": "3",
"quantity": 1,
"reservation_date": "16-01-2025",
"mix_and_match": true,
"package_type": "ayce"
}

New Query

  • Change column pricing_model on hh_package_package_attrs table default to per_person
  • Change column dynamic_pricing_type on hh_package_package_attrs table to normal
  • Ignore column kids_price_rate on all package table

DB Schema / Database Migration

  • Add column dynamic_pricing_type and pricing_model to hh_package_package_attrs table
  • Add column title , by_day_category , start_date , end_date , selected_days , dynamic_pricing_type , pricing_model to hh_package_pricings table
    • Add column start_date , end_date to hh_package_pricing_groups
  • Add translation for kids_policy on HhPackage::PackageAttr add_monetize
  • Add monetize to kids_price on hh_package_pricings and when column currency is not exist on hh_package_pricings table using currency: { present: false }
  • add promotion_badge_percentage to hh_package_package_attrs table, column type decimal
  • add promotion_badge_percentage to hh_package_pricings table, column type decimal

Improvement:

Feature NameDateWhat ChangedDescription
Show exact price for dynamic pricing and come more pay lessNov 2024Change price on create and edit bookingChange price on admin and partner portal
Public holiday adjustmentDec 2024Add adjustment on holiday setting at edit package
promotion badgemarch 2025Add promotion badge

New updates, pls review

Overview

The dynamic pricing API provides various pricing configurations based on two modes and multiple types. The modes are per_person and per_pack, and the types within the per_person mode is normal, by_party_size, and by_day.

Modes

Per Person Mode

In this mode, the price is calculated based on the number of individuals. It supports three types of pricing: normal, by_party_size, and by_day.

Per Pack Mode

In this mode, the price is calculated based on the number of packs. A pack is usually for 4 persons, but this can vary. If the number of persons exceeds the pack size, the price is calculated based on the number of packs required or the user can choose more than the required number of packs.

Types

Normal Mode

This type indicates a fixed price per person or pack.

By Party Size

This type offers discounts for larger party sizes, with different pricing tiers based on the number of persons.

By Day

This type provides different prices for different days, which can include weekdays, weekends, and special days.

API Response Structure

The API response is a JSON object with two main keys: per_person and per_pack, each containing samples of pricing configurations.

Common Attributes for All Modes and Types

  • mode: Indicates the pricing mode (per_person or per_pack).
  • type: Indicates the pricing type (normal, by_party_size, or by_day).
  • rules: An array of rule objects that define the pricing details.
  • kids_price_policy: A description of the pricing policy for children.

Rule Object Attributes

For normal and by_party_size Types
  • price: The price for the product (required).
  • min_seat: The minimum number of seats required to qualify for the price (required for by_party_size and normal).
  • max_seat: The maximum number of seats that qualify for the price. Use Infinity to represent 'No limit' (required for by_party_size and normal).
  • kids_price: The price for children (required).
For by_day Type
  • title: A description of the rule, such as "Weekday" or "Valentine's Day" (required).
  • category_name: The category of the day, such as "weekday", "weekend", or "special_days" (required).
  • days: An array of integers representing the days of the week to which the pricing rule applies. Sunday is represented by 0, Monday by 1, and so on up to Saturday represented by 6 (required for custom category, ignored otherwise).
  • start_date: The start date for the pricing rule, formatted as "YYYY-MM-DD" (optional, required for special_days).
  • end_date: The end date for the pricing rule, formatted as "YYYY-MM-DD" (optional, required for special_days).

Per Pack Specific Attributes

  • per_pack: The number of persons included in a pack (required for per_pack mode, ignored for per_person mode).

example of mode per person and type normal

https://hh-engineering.my.id/api/v5/restaurant_packages.json?client_type=web&corporate_event_id=&include_restaurant=false&locale=en&restaurant_id=837&pricing_type=per_person&sample=sample_1&__cache=false

example of mode per person and type is by day

https://hh-engineering.my.id/api/v5/restaurant_packages.json?client_type=web&corporate_event_id=&include_restaurant=false&locale=en&restaurant_id=837&pricing_type=per_person&sample=sample_2&__cache=false 

https://hh-engineering.my.id/api/v5/restaurant_packages.json?client_type=web&corporate_event_id=&include_restaurant=false&locale=en&restaurant_id=837&pricing_type=per_person&sample=sample_3&__cache=false

example of mode per person and type is by party size

https://hh-engineering.my.id/api/v5/restaurant_packages.json?client_type=web&corporate_event_id=&include_restaurant=false&locale=en&restaurant_id=837&pricing_type=per_person&sample=sample_4&__cache=false

you can change the sample=sample_x until sample_7

example of mode per pack:

https://hh-engineering.my.id/api/v5/restaurant_packages.json?client_type=web&corporate_event_id=&include_restaurant=false&locale=en&restaurant_id=837&pricing_type=per_pack&sample=sample_1&__cache=false