Currency Conversion in Analytics
All BillHive analytics queries that aggregate monetary amounts apply currency conversion to normalize values into the tenant’s base currency. This ensures dashboards and reports present consistent, comparable figures regardless of the currencies used across individual subscriptions and invoices.
Conversion Pattern
Every analytics SQL query that sums monetary amounts uses a Common Table Expression (CTE) to load the tenant’s currency rates, then joins and divides:
WITH currency_rates AS (
SELECT currency_code, conversion_rate
FROM subscription.currency
WHERE tenant_id = $1
)
SELECT
SUM(amount / COALESCE(cr.conversion_rate, 1)) AS amount_in_base
FROM some_table t
LEFT JOIN currency_rates cr
ON cr.currency_code = t.currency::text
WHERE t.tenant_id = $1Key Details
conversion_raterepresents target currency units per 1 base currency unit (e.g., if base is EUR and target is USD with rate 1.08, then $108 / 1.08 = 100 EUR)COALESCE(..., 1)ensures amounts in the base currency (or unmapped currencies) pass through unchanged::textcast is required becausecurrencycolumns use a PostgreSQL enum type (subscription."Currency"), while thecurrency_codecolumn in the rates table isvarchar
Affected Analytics Queries
The following queries in the reporting service apply currency conversion:
Revenue & Recurring Metrics
| Query | Description |
|---|---|
| ARR Summary | Annual Recurring Revenue total, converted to base currency |
| ARR Monthly | Month-over-month ARR trend with converted amounts |
| MRR Summary | Monthly Recurring Revenue total, converted to base currency |
| MRR Monthly | Month-over-month MRR trend with converted amounts |
Subscription Health
| Query | Description |
|---|---|
| Renewal Rate (Overall) | Revenue-weighted renewal percentage using converted amounts |
| Renewal Rate (Monthly) | Monthly renewal trend with currency-normalized revenue |
| Renewal Rate (By Plan) | Per-plan renewal analysis with converted revenue |
Payment Performance
| Query | Description |
|---|---|
| Payment Success (Overall) | Success rate and total volume in base currency |
| Payment Success (Monthly) | Monthly payment trend with converted totals |
| Payment Failure Reasons | Failure breakdown with converted amounts per reason |
Dunning & Collections
| Query | Description |
|---|---|
| Dunning Summary | Total dunning amounts converted to base currency. Currency label sourced from tenant’s base_currency_code instead of hardcoded USD. |
| Dunning Monthly | Monthly dunning trend with converted amounts |
| Dunning By Gateway | Per-gateway dunning breakdown with converted amounts |
Frontend Integration
The analytics frontend dynamically resolves the tenant’s base currency for display:
- On page load, the component fetches both the analytics data and the tenant’s currency list in parallel using
forkJoin - The base currency is identified from the currency list (the entry where
isBaseCurrencyistrue) - The currency code is mapped to a display symbol using a
currency_mapper(e.g.,USD→$,EUR→€,INR→₹) - All chart labels, tooltips, and formatted values use the resolved currency symbol instead of a hardcoded
$
forkJoin({
data: httpService.getAnalyticsData(),
currencies: httpService.getCurrencies(),
}) → resolve base currency → render charts with correct symbolFallback Behavior
| Scenario | Behavior |
|---|---|
| No conversion rate found for a currency | Amount passes through unchanged (COALESCE(..., 1)) |
| No base currency configured on tenant | Falls back to 'USD' |
| Currency mapper has no symbol for a code | Displays the raw currency code (e.g., EUR) |