B2B platform for digital goods

Build a Multi-Currency Pricing Engine for a Digital Goods Reseller — 2026

The pricing-engine architecture used by serious B2C resellers: pull wholesale USD, apply FX (CBR/ECB/P2P-blend), markup per category, FX buffer, round, cache. Hourly for TRY/RUB/ARS, daily otherwise.

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.

Frequently asked questions

How often should I refresh FX rates?
Daily for stable major pairs (EUR/USD, GBP/USD). Hourly for volatile currencies — RUB, TRY, ARS moved 12-25% intra-month in Q1 2026. Real-time (1-5 min) only if you face active arbitrageurs front-running your pricing, which is rare below $1M/month.
What FX source should I use for RUB?
CBR (Russian Central Bank) is the legal reference rate but lags spot by 0.5-2%. For B2C pricing serving Russian customers, blend CBR with P2P market rates (Bybit P2P, Garantex equivalent) — typically a 0.7 × CBR + 0.3 × P2P-blend gives you realistic pricing that doesn't bleed margin during sudden P2P shifts.
What's a reasonable FX buffer?
1-2% for stable currencies, 2-4% for RUB/TRY/ARS, 5%+ for currencies with capital controls or active devaluation. The buffer covers the spread between when you quote the customer and when you settle with us. If your refresh interval is shorter, your buffer can be smaller.
Should I cache the final retail price or the raw inputs?
Cache the final retail price keyed by (SKU, customer_currency, customer_segment) with TTL matching your FX refresh interval. Caching raw inputs and recomputing per request wastes CPU and makes A/B testing markup rules harder. Keep an audit log of computed prices for chargeback evidence.
Get FoxReload API access

Related articles