Build a Multi-Currency Pricing Engine for a Digital Goods Reseller — 2026
If you operate a B2C digital-goods storefront and you source from FoxReload (or any USD-denominated wholesale supplier), the pricing engine is the most important piece of code in your business. It directly determines margin, competitiveness and how often you lose money on FX. This guide walks through the architecture, the math, and a TypeScript reference implementation that handles the edge cases.
The pricing pipeline
The pipeline is a chain of pure transformations. Each step is testable in isolation:
wholesale_price_usd (from FoxReload catalog)
→ × fx_rate_to_customer_currency
→ × (1 + category_markup_pct)
→ × (1 + fx_buffer_pct)
→ round to currency-appropriate increment
→ cache for TTL
The reason to do this in exactly this order: you want markup applied to the customer-currency wholesale value (not USD), so that the percentage margin holds regardless of FX moves. The FX buffer applies on top because it covers settlement-time slippage, not margin.
TypeScript reference implementation
interface PricingInput {
sku: string;
wholesaleUsd: number; // from GET /v1/catalog
customerCurrency: 'RUB' | 'EUR' | 'USD' | 'TRY' | 'INR';
category: 'game-code' | 'gift-card' | 'esim' | 'recharge';
customerSegment: 'retail' | 'vip' | 'wholesale';
}
const CATEGORY_MARKUP: Record<string, number> = {
'game-code': 0.22, // 22% for game top-ups
'gift-card': 0.18, // 18% for gift cards
'esim': 0.35, // 35% for eSIM
'recharge': 0.12, // 12% for mobile recharge
};
const FX_BUFFER: Record<string, number> = {
'USD': 0.005,
'EUR': 0.01,
'RUB': 0.025, // RUB volatile, wider buffer
'TRY': 0.04, // TRY very volatile
'INR': 0.012,
};
const SEGMENT_DISCOUNT: Record<string, number> = {
'retail': 1.0,
'vip': 0.95,
'wholesale': 0.88,
};
async function computeRetailPrice(input: PricingInput): Promise<number> {
const fxRate = await getFxRate('USD', input.customerCurrency);
const wholesaleLocal = input.wholesaleUsd * fxRate;
const withMarkup = wholesaleLocal * (1 + CATEGORY_MARKUP[input.category]);
const withBuffer = withMarkup * (1 + FX_BUFFER[input.customerCurrency]);
const withSegment = withBuffer * SEGMENT_DISCOUNT[input.customerSegment];
return roundForCurrency(withSegment, input.customerCurrency);
}
function roundForCurrency(price: number, ccy: string): number {
if (ccy === 'RUB' || ccy === 'INR') return Math.ceil(price); // whole units
if (ccy === 'TRY') return Math.ceil(price * 2) / 2; // 0.50 increments
return Math.ceil(price * 100) / 100; // 0.01 increments
}
FX source selection
The FX rate isn't a single objective number — it depends on what you're hedging against. Practical sources by use case:
| Currency | Source | Refresh |
|---|---|---|
| EUR, GBP, JPY | ECB daily reference | daily 16:00 CET |
| RUB | 0.7 × CBR + 0.3 × P2P blend (Bybit, Garantex) | hourly |
| TRY | TCMB + 1% adjustment | hourly |
| ARS | BCRA official + blue-chip blend | hourly |
| INR | RBI reference | daily |
| USD/USDT | 1:1 (or live pair if relevant) | real-time |
For RUB specifically, pure CBR will lose you money during depreciation events because customers can buy USDT on Bybit faster than CBR updates. The blend stays close enough to market that you remain competitive without giving margin away.
Caching strategy
Cache key: (sku, customer_currency, customer_segment). TTL: match your FX refresh interval — 1 hour for volatile currencies, 24 hours for stable ones. Use Redis or equivalent with a soft-eviction pattern so you serve a slightly stale price (5-10 minutes old) rather than recomputing on every request.
Storage schema for the audit log:
CREATE TABLE pricing_decisions (
id BIGSERIAL PRIMARY KEY,
computed_at TIMESTAMPTZ NOT NULL,
sku TEXT NOT NULL,
customer_currency CHAR(3) NOT NULL,
customer_segment TEXT NOT NULL,
wholesale_usd NUMERIC(10,4) NOT NULL,
fx_rate NUMERIC(14,6) NOT NULL,
markup_pct NUMERIC(5,4) NOT NULL,
buffer_pct NUMERIC(5,4) NOT NULL,
final_price NUMERIC(12,2) NOT NULL,
cache_key TEXT NOT NULL,
INDEX idx_sku_time (sku, computed_at DESC)
);
Keep 90 days. The data is invaluable for chargeback disputes ("here's the exact price the customer saw at this timestamp") and for A/B testing markup rules.
That's the entire engine. Pull GET /v1/catalog from FoxReload, plug into the pipeline above, and you have margin-safe multi-currency pricing in production. Start at foxreload.com.
