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

[external] Restaurant Package Display: API to UI Attribute Mapping Guide

This document details the process for mapping package attributes from an API response to their corresponding display elements on user interfaces for restaurant packages.

API Endpoint: GET /api/vendor/v1/restaurant_packages.json?restaurant_id={restaurant_id} GET /api/vendor/v1/restaurants/{restaurant_id}/find_available_packages.json?locale=en

Parameters:

  • restaurant_id: The ID of the restaurant for which you want to find packages.

Example Request: GET /api/vendor/v1/restaurant_packages.json?restaurant_id=997 GET /api/vendor/v1/restaurants/997/find_available_packages.json?locale=en

Example Response:

{
  "data": [
  {
    "id": "31784",
    "type": "restaurant_packages",
    "attributes": {
      "restaurant_id": 997,
      "original_price": {
        "price": null,
        "currency": "thb",
        "format": ""
      },
      "name": "NEW: AYCE Buffet with 1 Premium Dish & Free Flow Drinks 2 Hours!",
      "rules": [
        {
          "price_description": "",
          "price": "฿1,499",
          "min_seat": 2,
          "max_seat": "Infinity",
          "per_pack": null,
          "duration": 120,
          "kids_price_rate": 50
        }
      ]
    }
    ...
  }
  ],
  "success": true,
  "message": null
}

CDN Endpoint:

Production: https://images.hungryhub.com
Venus staging: https://hh-partner.sgp1.digitaloceanspaces.com

Package Card Mobile UI

Attributes

NoDescriptionAttributesNotes
(1)Package namename
(2)Package descriptiondescription
(3)Discounted pricerules[rules.length - 1].priceLast object/element of rules
(4)Original priceoriginal_priceShow original price only if it's more expensive than discounted price
(5)Pricing typepricing_typeNET price per pack/person
(6)Valid untilend_dateYYYY-MM-DD
(8)Bookable day and timeopening_hoursIf open or closed is null/undefined, show "N/A" (Not Available)
(9)Min paxrules[0].min_seat / package_info.min_paxIf the package has rules attributes, use the first object of rules attributes
If the package doesn't have rules attributes, use package_info.min_pax
(10)Max paxrules[rules.length -1].max_seat / package_info.max_paxIf the package has rules attributes, use the last object of rules attributes
If the rules[rules.length -1] equals to "Infinity", show "No limit" instead
If the package doesn't have rules attributes, use package_info.max_pax
(11)Time limitpackage_info.time_limitShow time limit if the package_type is ayce
(12)Kids pricekids_price_v2Show kids price if kids_price_v2 attributes exist and accept_kids equals to true in restaurant API (/vendor/v1/restaurants/{id}.json)
(13)Terms & conditionspackage_info.tnc_linkCheck terms & conditions explanation
(14)Menuhh_menu_v2_html_preview_link
menus
Check menu explanation below
(15)New restaurant iconcustom_labels.icon_urlIf the URL doesn't contain "http", it's necessary to add domain
(16)Package per pack rulesrules[0].per_pack
Only show if per_pack in the first rules is not null
If is_accept_many_quantity equals to true and max pax per pack (18) is greater than 1, show "1-max pax per pack(18)"
Else, show max pax per pack
(17)Courseno_of_coursesOnly show if course is not null
(18)Max pax per packrules[0].per_pack / package_info.per_packIf the package has rules attributes, use the first object of rules
If the package doesn't have rules attributes, use package_info.per_pack

Terms & Conditions Explanation

Example Response:

https://hungryhub.com/uploads/restaurants/tc/image/110/tc.png 

or

/uploads/hh_package/package/ayce/tnc_image/1277/Terms___Condition_AYCE__2_.png

Sometimes the response doesn't contain http, so it is necessary to add the domain (in CDN Endpoint section) to the URL.

Example response on hh-venus:

/uploads/hh_package/package/ayce/tnc_image/1277/Terms___Condition_AYCE__2_.png

Final URL will be:

https://hh-partner.sgp1.digitaloceanspaces.com/uploads/hh_package/package/ayce/tnc_image/1277/Terms___Condition_AYCE__2_.png

Example response on production:

/uploads/hh_package/package/ayce/tnc_image/3937/AYCE_TC.png

Final URL will be:

https://images.hungryhub.com/uploads/hh_package/package/ayce/tnc_image/5103/AYCE_TC.png

Menu Explanation

Menu can be displayed by using image, iMenuPro, or Menu V2.

  1. Show menu using image Attributes:
rules

If menus.type contains image or img and menus.data.img exist, show menu. Example response:

"menus": {
     "type": "image",
     "data": {
     "img": [
           {
            "thumb": "/uploads/hh_package/package_menu/image/17325/breakfast-cafe-menu.png",
            "original": "/uploads/hh_package/package_menu/image/17325/breakfast-cafe-menu.png"
           }
         ]
      }
 },

Notes:

  • If the URL doesn't contain "http", it's necessary to add domain (same as T&C)
  • Show menu as image if hh_menu_v2_html_preview_link is empty string

2. Show menu using Menu V2 Attributes:

hh_menu_v2_html_preview_link

If hh_menu_v2_html_preview_link exist, fetch/create http request using hh_menu_v2_html_preview_link as the URL to get the real HH menu link. Extract the response body as text, it will be used as iframe source. Example:

fetch(hh_menu_v2_html_preview_link)
   .then((response) => response.text())
   .then((text) => {
      real_hh_menu_v2 = text;
    })
)
<iframe
  src="real_hh_menu_v2"
></iframe>

3. Show menu using iMenuPro Attributes:

rules

If menus.type contains imenupro and menus.data.imenupro exist, show menu. Example response:

"menus": {
   "type": "imenupro",
    "data": {
        "imenupro": [
            {
                "loaded": false,
                "link": "https://hh-venus.my.id/en/restaurant_packages/1619/menu.html?updated_at=hh_package%2Frestaurant_packages%2F1619-20240502082134000000"
            }
         ]
     }
}

Menu will be displayed in iframe using menus.data.imenupro.link Notes:

  • If menus.type contains image or img, manipulate link in data by adding ?imenupro_only=true

Get Cities

This document outlines the procedure for mapping package attributes retrieved from an API response to their corresponding display elements on user interfaces, specifically for the "Get List Cities" functionality.

API Endpoint :

Below is the endpoint provided for retrieving the list of cities: Url : {{base_url}}/api/vendor/v1/cities.json

Response :

example response :

{
    "data": [
        {
            "id": "1",
            "type": "cities",
            "attributes": {
                "name": "Bangkok",
                "home_description": "Come 4 pay 3, Deal Dee Very March!",
                "icon": {
                    "url": "https://images.hungryhub.com/uploads/city/icon/1/Bangkok.png"
                }
            }
        },
        {
            "id": "2",
            "type": "cities",
            "attributes": {
                "name": "Chiang Mai",
                "home_description": "No.1 Dining App for Special Occasions",
                "icon": {
                    "url": "https://images.hungryhub.com/uploads/city/icon/2/Chiangmai.png"
                }
            }
        }
    ],
    "success": true,
    "message": null
}

Restaurant Detail Information

This document describes the procedure for mapping package attributes retrieved from an API response to the appropriate display elements in the user interface, specifically for the “restaurant details” functionality.

API Endpoint:

URL: {{base_url}}/api/vendor/v1/restaurants/{{restaurant_id}}.json?locale={{lang}}&include_pictures=true

The following is an example of the response returned by the above API:

Data Mapping

Restaurant Detail / Product Information

  1. Restaurant Name

To retrieve the restaurant name, use the following attribute: data.attributes.name Alternatively, if a specific language is required, you can utilize data.attributes.names, which contains objects for each available language. For example:

  • data.attributes.names.en for English
  • data.attributes.names.th for Thai
  • data.attributes.names.cn for Chinese This approach allows you to select the desired language-specific value directly.

2. Restaurant's city of origin

To determine the restaurant's city of origin, you can use data.attributes.city_id and match the city_id value against the corresponding results from the API response Get Cities (https://doc.clickup.com/d/h/8ca1fpw-33036/3e552c1a079c66d/8ca1fpw-44456)

3. Restaurant Images To display restaurant images, utilize the included attribute, which is an array containing the necessary details. Specifically, use included[].attributes.image_url, but only under the condition that included[].type equals restaurant_pictures.

4. Restaurant Description To display the restaurant description, you can use data.attributes.description.

The following is a complete explanation of the api data.attribute

NoAttribute NameDescription
1idUnique identifier for the restaurant.
2latLatitude coordinate of the restaurant's location.
3lngLongitude coordinate of the restaurant's location.
4largest_tableMaximum seating capacity for a single table.
5branch_idIdentifier for the branch, if applicable.
6min_party_sizeMinimum number of people required for a booking.
7nameDefault name of the restaurant.
8namesNames of the restaurant in different languages (e.g., English, Thai, Chinese).
9slugSEO-friendly identifier for the restaurant.
10is_favoritedIndicates whether the restaurant is marked as a favorite by the user.
11allow_bookingSpecifies if the restaurant accepts bookings.
12availabilityCurrent availability status of the restaurant. eg: in stock
13reviews_scoreAverage review score for the restaurant.
14reviews_countTotal number of reviews for the restaurant.
15map_locationURL to the Google Maps location of the restaurant.
16accept_group_bookingIndicates if the restaurant accepts group bookings.
17locationGeneral location or area where the restaurant is situated.
18primary_locationObject containing the primary location ID and name.
19cuisineGeneral cuisine type offered by the restaurant.
20primary_cuisineObject containing the primary cuisine ID and name.
21image_cover_urlURL for the cover image of the restaurant.
22last_booking_was_madeTimestamp of the most recent booking made for the restaurant.
23total_coversTotal seating capacity of the restaurant.
24name_enName of the restaurant in English.
25name_thName of the restaurant in Thai.
26city_idIdentifier for the city where the restaurant is located.
27addressFull address of the restaurant.
28parkingInformation on parking availability.
29corkage_chargeDetails about corkage fees for bringing external beverages.
30days_in_advanceNumber of days in advance bookings can be made.
31ambienceDescription of the restaurant's ambience, if provided.
32small_noteAdditional notes or remarks about the restaurant, if any.
33accept_kidsIndicates whether the restaurant allows children.
34booking_flowSpecifies the booking flow (e.g., date-first).
35has_multiple_pricingIndicates if the restaurant has multiple pricing tiers.
36earn_pointIndicates if customers can earn points for bookings.
37accept_voucherSpecifies if the restaurant accepts vouchers.
38descriptionDetailed description of the restaurant, including key features and signature dishes.
39custom_seatsArray specifying custom seating arrangements, if any.
40logo_urlURL for the restaurant's logo.
41custom_section_titleTitle of a custom section for additional information.
42custom_section_contentContent of a custom section, typically in HTML format.
43available_package_typesArray specifying the types of packages available (e.g., "pp").
44reservation_duration_in_hoursDuration of a reservation in hours.
45opening_hoursGeneral opening hours of the restaurant, if provided.
46opening_hours_todayRestaurant's opening hours for the current day.
47weekday_opening_hoursObject detailing opening hours for each day of the week.
48opening_hours_v2Alternative format for opening hours, if provided.
49hashtagsArray of hashtags related to the restaurant (e.g., "Michelin Guide").
50videosArray of video URLs related to the restaurant.
51cuisinesArray of additional cuisines offered by the restaurant.
52locationsArray of additional location details, including ID and title.
53has_delivery_pricing_tierIndicates whether the restaurant has pricing tiers for delivery.
54phoneGeneral contact phone number for the restaurant.
55phone_for_deliveryContact phone number specifically for delivery orders.
56covid19_ratingObject containing COVID-19 safety ratings for cleanliness, social distancing, and staff protection.
57tnc_image_urlURL for the terms and conditions image.
58linkURL linking to the restaurant's page on the website.
59price_summariesArray of price summaries, including the lowest and highest prices, package types, and pricing type.

How to When to Display the Kids quantity Button and Determine the Kids Price

Displaying the Kids Quantity Button In this step, we will outline the conditions under which the "Kids Quantity" button should be displayed.

API to Fetch Restaurant Details

  • To determine whether the "Kids Quantity" button should be displayed, query the following API: {base_url}/api/vendor/v1/restaurants/{restaurant_id}.json?locale={lang}&include_pictures=true. Key Attribute: Examine the data.attributes.accept_kids attribute within the restaurant-level data. This attribute is a boolean:
  •   *   If **true**, the restaurant accommodates kids, and the "Kids Quantity" button should be displayed.
    
    • If false, the button should not be displayed.

Determining the Kids Price

After hitting the following API: {base_url}/api/vendor/v1/restaurant_packages.json?restaurant_id={id}&include_restaurant=true. Use these two attributes to determine the kids price

  • Key Attributes:
    1. data.attributes.use_kids_price:
      • If false, the kids' price will be equivalent to the normal/adult price.
      • If true, proceed to the next step.
    2. data.attributes.kids_price_v2:
      • This attribute contains an array of objects. Iterate through this array to find the object with the highest price_value attribute.
      • The highest price_value from this array will be used as the kids' price.

By following these steps, you can correctly determine whether to display the "Kids Quantity" button and compute the appropriate pricing for kids.

Comemore Payless Feature

Description

The "Come More, Pay Less" feature is designed to offer group discounts based on predefined pricing logic. When enabled, larger groups benefit from a reduced per-person price by allowing some members to dine for free. This feature applies only to All-You-Can-Eat (AYCE) packages.

Attributes

There are two attributes used for the "Come More, Pay Less" feature in package API response:

  • comemore_payless (boolean) - Indicates whether the feature is enabled.
  • pricing_groups (object) Defines the pricing logic when the feature is active.

These attributes are available from the following API:

Restaurant Packages API

GET {{base_url}}/api/vendor/v1/restaurant_packages.json?restaurant_id=4391

Behavior Based on comemore_payless Value

When comemore_payless is false:

  • The pricing_groups attribute will be an empty object {}, indicating that the package follows standard pricing.
  • The following is an example response when the feature is disabled:
{
  "come_more_pay_less": false,
  "pricing_groups": {}
}

When comemore_payless is true:

  • The pricing_groups attribute contains key details defining the pricing logic.
  • The following is an example response when the feature is enabled:
{
  "come_more_pay_less": true,
  "pricing_groups": {
    "group_size": 4,
    "size_to_pay": 3,
    "price": 699,
    "price_format": "฿699",
    "currency": "THB",
    "sample_price_per_person": 524.25,
    "sample_price_per_person_format": "฿524.25",
    "start_date": "2023-10-01",
    "end_date": "2025-08-13"
  }
}
comemore_paylesspricing_groupsPricing Behavior
false{} (empty object)Standard pricing applies.
truePopulated with pricing detailsDiscount applied based on group_size and size_to_pay.

Pricing Logic

  • group_size and size_to_pay: These attributes indicate that for every group_size (e.g., 4 people), the total price is calculated based on size_to_pay (e.g., 3 people).

Pricing Calculation

  • If the total number of people is a multiple of group_size, the total price is determined by multiplying sample_price_per_person by the total number of people.
  • If the total number of people exceeds group_size, the first group_size people are charged using sample_price_per_person × group_size, while any additional person beyond that is charged based on the price attribute.

Validity Period

  • The feature is only applicable within the specified period, as defined by the start_date and end_date attributes.

Calculate Package Price API

  • In the /api/vendor/v1/restaurants/:restaurant_id/calculate_package_price.json API, the displayed price automatically updates to reflect the new price after applying the "Come More, Pay Less" promotion.
  • Reservation Date Requirement
  • To apply the "Come More, Pay Less" promotion correctly, you must provide the reservation date (reservation_date). This is necessary because the promotion is configured with specific start and end dates in the admin dashboard, and the reservation date must fall within this valid date range to be eligible.

Request Example:

POST {{base_url}}/api/vendor/v1/restaurants/:restaurant_id/calculate_package_price.json

{
    "adult": 3,
    "kids": 2,
    "reservation_date": "2025-05-28",
    "package_bought": [
        {
            "id": "32677",
            "quantity": 3,
            "menu_sections": []
        }
    ]
}
  • Selected Packages Attribute
  • Additional details are available in the selected_packages attribute, which includes the discount received by the user under the type promo.
  • Example response:
{
    "success": true,
    "message": null,
    "data": {
        "charge_price": 0,
        "charge_price_cents": 0,
        "currency": "THB",
        "total_price": 5000,
        "total_price_cents": 500000,
        "charge_amount_type": "",
        "charge_percent": 0,
        "charge_type": "",
        "total_package_price": 5000,
        "total_package_price_cents": 500000,
        "total_package_price_v2": 5000,
        "selected_packages": [
            {
                "id": 8086,
                "restaurant_package_id": "32677",
                "name": "Sharing Set for 3 People\tPrepaid",
                "quantity": 2,
                "type": "pack",
                "price": 1000000,
                "base_price": 500000,
                "dynamic_pricing_title": "Come 2 Pay 1"
            },
            {
                "id": 8086,
                "restaurant_package_id": "32677",
                "name": "Come more pay less",
                "quantity": 1,
                "type": "promo",
                "price": -500000,
                "base_price": 500000
            }
        ],
        "total_point_earn": 0,
        "is_dine_in": true,
        "charge_price_v2": 0,
        "total_price_v2": 5000,
        "used_voucher_amount_by_hh": 0,
        "used_voucher_amount_v2_by_hh": 0,
        "used_voucher_amount_by_restaurant": 0,
        "voucher_deductibles": [],
        "used_vouchers": [],
        "add_on_currency": "",
        "add_on_total_price": 0,
        "add_on_total_price_cents": 0,
        "add_on_total_price_v2": 0,
        "selected_add_ons": []
    }
}

How to Display the Menu Based on Attribute in the Package API Response

This guide explains how to dynamically render a restaurant menu based on the menu_type attribute returned in the package API response. Depending on the type, the menu can be displayed using images or an embedded iframe.

🔍 Overview

The menu_type attribute determines how the menu should be displayed. Based on its value, the system reads data from the menus and hh_menu_v2_html_preview_link attributes to extract the appropriate content—such as iMenupro links, image URLs, or HTML preview links—which are then rendered accordingly.

🧠 Logic Summary

The function checks the menu_type and sets the appropriate data for rendering:

Supported menu_type Values and Behaviors:

menu typeDisplayed AsData Source
imenuproiframeattributes.menus.data.imenupro[*].link
img / image elementsattributes.menus.data.img[*].original
menu_v2iframehh_menu_v2_html_preview_link → fetch → extract the url for iframe source
img_and_imenuproiframe + Combination of above
menu_v2_and_imgiframe + Combination of above
menu_v2_and_imenupro2 iframesiMenupro + Menu V2

🖼️ Rendering Strategy

1. Show Menu Using Image

Conditions:

  • menu_type includes "image" or "img"
  • menus.data.img is present

Example response:

"menus": {
  "type": "image",
  "data": {
    "img": [
      {
        "thumb": "/uploads/hh_package/package_menu/image/17325/breakfast-cafe-menu.png",
        "original": "/uploads/hh_package/package_menu/image/17325/breakfast-cafe-menu.png"
      }
    ]
  }
}

Notes:

  • If image URLs don't include "http", prepend the domain (same rule as T&C links).
  • Render <img src="original"> for each image.

2. Show Menu Using iMenuPro

Conditions:

  • menu_type includes "imenupro"
  • menus.data.imenupro is present

Example response:

"menus": {
  "type": "imenupro",
  "data": {
    "imenupro": [
      {
        "loaded": false,
        "link": "https://hh-venus.my.id/en/restaurant_packages/1619/menu.html?updated_at=..."
      }
    ]
  }
}

Rendering:

  • Use an iframe:
<iframe src="menus.data.imenupro[*].link"></iframe>

Notes:

  • If menus.type includes both "image" and "imenupro", append ?imenupro_only=true to the iMenuPro link to isolate it.

3. Show Menu Using Menu V2

Condition:

  • menu_type includes menu_v2
  • hh_menu_v2_html_preview_link exists and is not empty Behavior:
  • Perform a fetch request to the hh_menu_v2_html_preview_link
  • Extract the response as text and inject into an iframe Code:
fetch(hh_menu_v2_html_preview_link)
  .then(response => response.text())
  .then(url=> {
    real_hh_menu_v2 = url;
    // insert real_hh_menu_v2 into iframe src
  });

Rendering:

<iframe src="real_hh_menu_v2"></iframe>

💡 Combination Scenarios

img_and_imenupro or menu_v2_and_img

Render both image and iframe components. Respect the respective rendering logic for each content type.

Render two iframes:

  • One for iMenuPro
  • One for Menu V2 (via fetch + extract as above)

✅ Summary

  1. Check menu_type from the package response.
  2. Based on menu_type, determine sources: images, iMenuPro links, or Menu V2 links.
  3. Render appropriately:
    • <img> for images
    • <iframe> for iMenuPro and Menu V2
  4. If URLs are relative (e.g., image paths), prepend the correct domain.
  5. For mixed menu types, combine rendering strategies accordingly.