FCHubFCHub.co

Exchange Rates

How FCHub Multi-Currency fetches, stores, caches, and manages exchange rates from multiple providers with automatic refresh and staleness detection.

Exchange rates are the engine behind display-layer multi-currency. The plugin fetches rates from an external provider, stores them with full history, caches the latest values, and automatically detects when rates go stale.

Rate Providers

Four providers are available. You choose one in the settings — the plugin fetches from that provider exclusively.

Slug: exchange_rate_api

The default provider. Reliable, wide currency coverage, and a free tier that's generous enough for most stores.

  • API Key: Required — get one at exchangerate-api.com
  • Endpoint: https://v6.exchangerate-api.com/v6/{key}/latest/{base}
  • Base currency: Uses your configured base currency directly

Refresh Cycle

The rate refresh runs on a WordPress cron schedule:

Cron Fires

The fchub_mc_refresh_rates event fires at the configured interval (default: every 6 hours). Changing the interval in settings reschedules the cron immediately.

Lock Acquired

A 120-second option-based lock prevents concurrent refreshes. If another refresh is already running, the new one exits silently.

Provider Fetched

The configured provider's fetchRates() method is called with the base currency. The provider returns an associative array of code => rate pairs.

Validation

Each rate is validated against the display currencies whitelist. Rates for currencies not in your display list are ignored. Zero or negative rates are skipped and logged as errors — because a rate of 0 would zero out every price on your storefront.

Storage

Valid rates are inserted into the fchub_mc_rate_history table and written to the WordPress object cache (group fchub_mc_rates, 1-hour TTL). Existing cache entries for the refreshed pairs are deleted first to prevent stale reads.

Pruning

Rate history rows older than 90 days are deleted. This keeps the table from growing unboundedly.

Hook Fired

fchub_mc/rates_refreshed action fires with the base currency and rate count.

You can also trigger a manual refresh from the admin: FluentCart > Settings > Multi-Currency > Exchange Rates > Refresh Now. This calls the same action as the cron job.

Storage Architecture

Rates live in three layers:

1. WordPress Object Cache (Hot)

The latest rate per currency pair is cached in WordPress object cache with group fchub_mc_rates and a 1-hour TTL. This is the first thing the plugin checks when it needs a rate.

On hosts with persistent object cache (Redis, Memcached), this means rate lookups are sub-millisecond. Without persistent cache, the cache only lasts for the current PHP request — but the database fallback is still fast.

2. Database Table (Warm)

The fchub_mc_rate_history table stores every rate ever fetched. When the cache misses, the plugin queries the most recent row for the requested currency pair.

ColumnTypeDescription
idBIGINT UNSIGNEDAuto-increment primary key
base_currencyCHAR(3)ISO 4217 base code
quote_currencyCHAR(3)ISO 4217 quote code
rateDECIMAL(18,8)Exchange rate (8 decimal places)
providerVARCHAR(64)Provider slug that fetched this rate
fetched_atDATETIMETimestamp in WordPress site timezone

Indexed on (base_currency, quote_currency, fetched_at) for efficient latest-rate lookups.

3. Synthetic Identity Rate

When the display currency equals the base currency, a synthetic 1.00000000 rate is returned. No database query, no cache lookup.

Staleness Detection

A rate is considered stale when its fetched_at timestamp is older than the configured stale threshold (default: 24 hours). The comparison uses WordPress's site timezone (current_time('timestamp')) to avoid UTC offset mismatches.

When staleness is detected:

BehaviourWhere
Admin warning noticeWordPress dashboard (for manage_options users)
Red dot on rate badgeCurrency switcher dropdown footer
Stale fallback appliesPrice display (base currency or last known rate)

Stale Fallback Options

SettingBehaviour
baseDisplay reverts to the base currency. The visitor sees all prices in the base currency until fresh rates arrive.
last_knownKeep using the stale rate. Prices continue to display in the selected currency, but the rate badge warns it's outdated.

Rate History and Pruning

Every rate refresh inserts new rows into the history table — existing rows are never updated. This gives you a complete audit trail of rate changes over time.

The cron job automatically deletes rows older than 90 days after each refresh. This is a hard-coded retention period designed to balance history depth with table size.

The RateHistoryQuery::forPair() method returns the last 30 rows for any currency pair, ordered newest first. This is used internally but also available for custom integrations.

Rounding

After the exchange rate multiplication, the resulting amount is rounded according to your settings:

ModeBehaviourExample (input: 12.345)
NoneTruncate (no rounding)12.34
Half UpStandard rounding12.35
Half DownRound half towards zero12.34
CeilAlways round up12.35
FloorAlways round down12.34

The precision setting controls the granularity:

PrecisionRounds ToExample (input: 12.345, Half Up)
0Nearest cent12.35
1Nearest 10 cents12.30
2Nearest whole unit12.00

Rounding is applied identically in both the PHP fchub_mc_format_price() function and the JavaScript projection engine.

On this page