Multi Currency
Description / Background
Multi-Currency introduces the ability for users to view estimated prices in their preferred or local currency across Hungry Hub’s website and app. Prices are converted in real time using exchange rate APIs, allowing users to better understand the cost of restaurant packages in familiar monetary terms.
The actual billing remains in the restaurant’s base currency (THB or SGD) — ensuring no impact on backend calculations or payment processing. The feature supports multiple target currencies (USD, EUR, GBP, JPY, etc.) and provides clear indicators that displayed prices are estimates.
This enhancement improves international accessibility, transparency, and trust while laying the groundwork for Phase 2: Real Multi-Currency Payments.
Objectives
- User can see the package price from their selected currency
- Support for THB and SGD as base currencies
- Support target currency (Order by alphabetical sort)
- 🇦🇺 AUD (Australian Dollar)
- 🇨🇳 CNY (Chinese Yuan)
- 🇪🇺 EUR (Euro)
- 🇬🇧 GBP (British Pound Sterling)
- 🇭🇰 HKD (Hong Kong Dollar)
- 🇯🇵 JPY (Japanese Yen)
- 🇺🇸 USD (US Dollar)
- 🇲🇾 MYR (Malaysian Ringgit)
- User can see estimated prices in their local or preferred currency (e.g., USD, EUR, JPY) when browsing restaurant packages.
- User can switch between available currencies through a Currency Switcher on the navbar or within their profile settings.
- User can receive up to three suggested currencies based on personalization:
- Their previously selected or detected currency (cookies/location/IP).
- USD as a default fallback.
- The city currency of the restaurant being viewed (THB/SGD).
- User can filter restaurants by price in their selected currency, with automatic conversion handled on both filter and result display.
- User can view checkout prices in their chosen currency, with an on-screen alert explaining that billing will occur in the restaurant’s local currency.
- User can see consistent currency conversion across homepage, search results, package details, and offers pages.
- User can still receive receipts, confirmation emails, and booking edits in the restaurant’s base currency, ensuring clarity in payments.
- Admin can manage a global configuration section called “Exchange Rate Buffer” within the Admin Dashboard, ensuring consistent control of conversion parameters across all supported currencies.
- Admin can set a configurable buffer percentage (
{currency} buffer (%)) for each currency — both base (THB, SGD) and target (USD, EUR, GBP, etc.) — in the Admin Dashboard. (This buffer defines a tolerance margin applied to exchange rates to account for market fluctuations and risk management). - Admin can extend buffer settings to newly supported currencies like MYR (Malaysian Ringgit) to align with country expansion initiatives.
- Admin can track and monitor exchange rate anomalies via a monitoring dashboard used by product analysts. This helps detect sudden rate discrepancies or API failures in near real-time.
- Admin can ensure that buffers are applied only once — at the display level per package — and not to backend or total calculations, preventing inconsistency between shown and charged prices.
- Admin can enforce proper formatting standards for currencies (symbol position, comma vs. dot separators, and locale conventions) to maintain international accuracy and readability.
- Admin can monitor key performance indicators such as exchange rate fetch success rate, restaurant card click-through rates, and conversion rate changes for non-base currencies.
Scope
homepage language setting, all displayed currency, including dynamic pricing, original price, kid price etc
How to set exchange rate buffer
-
Log in to the Admin Dashboard.
-
Navigate to Settings
-
Locate the new section titled “Exchange Rate Buffer.”
This section controls how the system adjusts live exchange-rate conversions shown on the frontend.
Example:
- Base currency = THB, Target = USD
- Base rate: 1 THB = 0.031 USD
- Buffer = 2 % (0.02)
- Adjusted rate = 0.031 × 1.02 = 0.03162 USD/THB
- A 12.000 THB package → 12.000 × 0.03162 = 379.44 USD displayed to users
Sequence Diagram / Flow
@startuml
title Multi-Currency Display — System Context
skinparam componentStyle rectangle
skinparam shadowing false
actor User as U
actor Admin as A
package "Frontend (Web/App)" {
[Navbar Currency Switcher] as Switcher
[Homepage / Store / Detail / Menu] as Browse
[Search Page] as Search
[Checkout Page] as Checkout
[Profile / Offers / Benefit] as ProfileOffers
[Transactional Screens] as TxScreens
}
package "Services" {
[Exchange Rate API] as FXAPI
[Client Fallback Cache] as Fallback
[Monitoring Dashboard] as Monitor
}
package "Admin Dashboard" {
[Global Settings\n- Exchange Rate Buffer (%/currency)\n- Supported currencies] as AdminCfg
}
U --> Switcher : select/switch currency\n(cookies / location / IP hints)
U --> Browse : view prices (est.)
U --> Search : filter & view prices (est.)
U --> Checkout : view prices (est.)\n+ blue alert: "charged in base currency"
U --> ProfileOffers : view base + est. prices (where applicable)
U --> TxScreens : see base currency only\n(emails, payment, confirmations)
Browse --> FXAPI : request mid-market rates
Search --> FXAPI
Checkout --> FXAPI
ProfileOffers --> FXAPI
Browse --> Fallback : use last good rates if API fails
Search --> Fallback
Checkout --> Fallback
A --> AdminCfg : set buffer per currency\n(THB/SGD + targets e.g., USD, EUR, JPY…)
AdminCfg ..> Browse : buffer applied once at display level (per package)
AdminCfg ..> Search
AdminCfg ..> Checkout
AdminCfg ..> ProfileOffers
FXAPI --> Monitor : success/failure metrics
Browse --> Monitor : UI conversion anomalies
Search --> Monitor
Checkout --> Monitor
note right of TxScreens
Transactional contexts revert to
BASE currency (THB/SGD):
- Payment Page
- Booking Confirmation
- Edit/Modify (client)
- Emails
end note
@enduml
@startuml
title Page Price Rendering — Display Conversion with Buffer & Fallback
skinparam shadowing false
actor User as U
participant "Frontend Page" as FE
participant "Client Fallback Cache" as Fallback
participant "Exchange Rate API" as FXAPI
participant "Admin Buffer Settings" as AdminCfg
U -> FE : Open page (e.g., Restaurant Detail)
FE -> AdminCfg : Read buffer(%) for base→target
FE -> FXAPI : Get mid-market rate(base→target)
alt API success
FXAPI --> FE : rate(base→target)
FE -> FE : adjusted_rate = rate * (1 + buffer%)
FE -> FE : est_price = base_price * adjusted_rate
else API failure
FE -> Fallback : get last_good_rate(base→target)
alt Fallback hit
Fallback --> FE : last_good_rate
FE -> FE : adjusted_rate = last_good_rate * (1 + buffer%)
FE -> FE : est_price = base_price * adjusted_rate
else No cache
FE -> FE : show base currency only\n+ inline alert: "Can't convert…"
end
end
FE -> U : Render price in target currency\n(with locale formatting)\n+ blue note on checkout: "Charged in base currency"
@enduml
@startuml
title Search Filter — Double Conversion Logic
skinparam shadowing false
actor User as U
participant "Search UI" as UI
participant "Exchange Rate API" as FXAPI
participant "Client Fallback Cache" as Fallback
participant "Search Engine" as SE
U -> UI : Set price filter in TARGET currency (e.g., USD 20–50)
UI -> FXAPI : Fetch rate(target↔base)
alt API success
FXAPI --> UI : rate(target→base), rate(base→target)
else API failure
UI -> Fallback : read last_good_rate
Fallback --> UI : last_good_rate or miss
end
UI -> UI : Convert target filter → base filter\n(e.g., USD→THB) // for query
UI -> SE : Query with BASE currency filter
SE --> UI : Result set with BASE prices
UI -> UI : For display, convert BASE→TARGET\n(apply buffer once at display level)
UI -> U : Show results in TARGET currency\n(rounded per locale)
note right
Rules:
- Filter: Target→Base
- Display: Base→Target
- Apply buffer ONCE at display level (per item)
- Preserve consistency with checkout messaging
end note
@enduml
ERD
Backend Implementation
- Adds a new key
exchange_rate_buffersto theapp_configresponse. - This key contains a hash with buffer percentages for the following currencies: AUD, CNY, EUR, GBP, HKD, JPY, USD, SGD, MYR.
- For each currency, it reads the buffer from settings (e.g.,
exchange_rate_buffer_aud) and converts it to a float. - Introduces new fields to store buffer percentages for each currency.
- Each field is a float and defaults to
0.0, allowing admins to configure a buffer percentage for each supported currency. - Excludes the new buffer fields from the default settings section to avoid clutter.
- Adds a dedicated UI section titled “Exchange Rate Buffer”:
- For each currency, displays an input box where admins can set the buffer percentage.
- Includes a description to help admins understand the purpose (e.g., "5 = 5%").
- Adds English descriptions for each new buffer field, clarifying their meaning and usage for admins.
https://github.com/hungryhub-team/hh-server/pull/7268
Hybrid Implementation
- A new utility (
generateEstimatedPrice) is called in many locations to display prices in the user's selected currency, based on exchange rates, alongside original restaurant prices. - All price rendering logic (menus, packages, add-ons, search results, booking summaries, vouchers, etc.) now optionally shows the estimated price in the user's preferred currency.
- When the restaurant’s currency differs from the user's selection, both are shown (e.g., “SGD 20” and “THB 520”).
- The language picker is expanded to allow users to select both language and currency.
- New components (
CurrencyOptionView,CurrencyOption,LanguageDropdown) provide currency selection and explanations of how estimated pricing works. - Currency selection is stored in cookies and persists across sessions/pages.
- All components showing prices (e.g., booking summary, payment, confirmation, add-on detail, point redemption, package comparisons, QR menu) can now show estimated prices and/or currency labels.
- Conditional logic displays estimated price when base and selected currencies differ.
- New
RestaurantCurrencyLabel.vuedisplays the original restaurant price and currency when different from the user’s selection. - Search results and restaurant cards show both original and estimated prices.
- Search filters use selected currency for price ranges and display.
- Language settings now include currency selection.
- Currency is displayed in profile and can be changed in settings.
- Exchange rates are fetched and stored in a global config for client-side use.
- Dropdowns, tabs, and explanations are added to make currency selection clear and easy.
- Explanatory text is shown to inform users that payment is made in the restaurant's currency, but estimates are shown in their preferred currency.
- Price props are refactored to handle both original and estimated prices.
- Code is updated everywhere prices are displayed or inputted to support dynamic currency.
- Helper functions ensure correct codes for currencies and languages when switching.
https://github.com/hungryhub-team/hh-pegasus/pull/1892
PRD & Task
PRD:
https://app.clickup.com/9003122396/v/dc/8ca1fpw-7922/8ca1fpw-52476
https://app.clickup.com/9003122396/v/dc/8ca1fpw-7922
https://app.clickup.com/9003122396/v/dc/8ca1fpw-7922/8ca1fpw-46316
https://app.clickup.com/9003122396/v/dc/8ca1fpw-7922/8ca1fpw-46336
https://app.clickup.com/9003122396/v/dc/8ca1fpw-7922/8ca1fpw-54256
TASK: https://app.clickup.com/t/86cyg53nr
https://app.clickup.com/t/86d0mjw50 https://app.clickup.com/t/86d0m29t8
https://app.clickup.com/t/86d0pja2j
Design
https://www.figma.com/design/PvOG3Actzf6wjG0cyglyVC/Multi-Currency?node-id=0-1&t=3WTr983lWPRX51ed-1
API Blueprint
| Method | Path | URL | Description | Payload |
|---|---|---|---|---|
New Query
DB Schema / Database Migration
Improvement:
| Feature Name | Date | What Changed | Description |
|---|---|---|---|