Exchange Rates

PerfLocale can refresh your per-language currency rates on a schedule. The scheduling, the cross-calculation, the circuit breaker and the admin UI are all in the plugin — but no rate provider ships with it. Until your site wires one up, PerfLocale contacts no exchange-rate service at all, and the Auto-Sync fields say so.

Quick Start

  1. Register a rate source (see Supplying rates below) — this step is not optional; nothing is bundled.
  2. Go to PerfLocale > Settings > Addons > WooCommerce
  3. Enable Per-Language Currency
  4. Configure a currency code for each language (the code is auto-detected from the language locale — pl_PL → PLN, en_GB → GBP, etc. — on first add)
  5. Enable Auto-Sync Rates
  6. Select your registered provider and a sync interval
  7. Click Sync Now to verify — the per-language rate inputs update to the freshly-fetched values and persist across page reloads. WooCommerce cart / checkout / mini-cart price conversion uses the same synced rates at runtime.

Per-row manual override. Setting manual_rate => true on a specific language entry (programmatically, or via wp option patch update perflocale_settings wc_currencies <slug> manual_rate 1) pins that row to its manually-entered value and excludes it from the auto-sync feed, even when global Auto-Sync is on. Useful for hedging contracts or B2B fixed rates. If you never wire up a provider, every row is effectively a manual rate — multi-currency display still works, the numbers just don’t move on their own.

Supplying rates

PerfLocale bundles no exchange-rate service. Earlier builds shipped several; they were removed so that installing the plugin never adds an outbound dependency you didn’t ask for. Two filter seams remain, and you need exactly one of them:

  • perflocale/woocommerce/exchange_rate_providers — register a provider with a fetch_callback. It gets the scheduling, the provider dropdown, the “Sync Now” button, the last-sync status line and the circuit breaker for free. Best when you’re calling a rate API.
  • perflocale/woocommerce/exchange_rates_fetched — return the rate array directly. Best when the numbers come from somewhere that isn’t an HTTP call: your ERP, a treasury feed, a hard-coded contract table.

If neither is wired, a sync run logs “No exchange-rate provider configured” and stops without making a request. If you supply rates through exchange_rates_fetched alone, leave the provider dropdown empty — the sync still runs, because PerfLocale checks for that filter before bailing out.

Choosing a provider is a decision about privacy and terms as much as accuracy: whatever you register is a third-party service your store now talks to on a schedule, and its terms and privacy policy are yours to disclose, not ours.

Sync Intervals

IntervalRequests/dayRequests/month
Every Hour24~720
Every 2 Hours12~360
Every 4 Hours6~180
Every 6 Hours4~120
Every 8 Hours3~90
Every 12 Hours2~60
Once Daily (default)1~30
Once Weekly~0.14~4
Once Monthly~0.031

Choose an interval that fits within your API plan limits. For most stores, daily is sufficient since exchange rates don't change drastically within a day.

API Key Management

Because no rate provider is bundled, PerfLocale defines no exchange-rate API-key constants. (The seven credentials that do have canonical env-var / constant names are all machine-translation ones — see API Keys: Environment Variables & Constants.)

A provider registered through the filter can declare needs_key and key_setting. PerfLocale then reads perflocale_settings[key_setting] before each sync and refuses to run when it’s empty (“API key missing” in the log). It does not render an input for that key — the admin key fields disappeared along with the bundled providers — so if you use key_setting, populate it yourself, e.g. wp option patch update perflocale_settings my_rates_key '…'.

Keeping the key out of the database

The simpler and safer route is to skip key_setting entirely and read your own environment variable or constant inside fetch_callback:

'needs_key'      => false, // nothing read from, or stored in, settings
'fetch_callback' => function ( string $base, array $targets, string $api_key ): array {
	$key = getenv( 'MY_RATES_API_KEY' ) ?: ( defined( 'MY_RATES_API_KEY' ) ? MY_RATES_API_KEY : '' );
	// ...call your API with $key, return [ 'EUR' => 0.92, ... ]
},

If the key does live in the database

Anything stored in perflocale_settings whose name ends in _api_key, _token, _key, _secret or _password is redacted from PerfLocale’s settings export, so a custom provider’s key is covered automatically if you name it that way. Importing a redacted export leaves the live credential in place rather than blanking it; on a fresh site you re-enter the key by hand.

Reasons to prefer your own env var / constant over a stored setting:

  • Not stored in the database (not exposed in backups or SQL dumps)
  • Not visible in the WordPress admin UI
  • Can differ per environment (staging vs production)
  • Cannot be changed by other plugins or admin users

Custom Provider via Filter

Register your exchange rate provider using the perflocale/woocommerce/exchange_rate_providers filter. This is the seam that used to hold the bundled providers, so the shape is unchanged:

add_filter( 'perflocale/woocommerce/exchange_rate_providers', function( array $providers ): array {
	$providers['my_custom_api'] = [
		'name'           => 'My Custom API',
		'needs_key'      => true,
		'key_setting'    => 'wc_my_custom_key',
		'fetch_callback' => function( string $base_currency, array $target_currencies, string $api_key ): array {
			$response = wp_remote_get( 'https://my-api.example.com/rates?base=' . $base_currency . '&key=' . $api_key );

			if ( is_wp_error( $response ) ) {
				return [];
			}

			$body = json_decode( wp_remote_retrieve_body( $response ), true );

			$rates = [];
			foreach ( $target_currencies as $code ) {
				if ( isset( $body['rates'][ $code ] ) ) {
					$rates[ $code ] = (float) $body['rates'][ $code ];
				}
			}

			return $rates;
		},
	];

	return $providers;
} );

Once registered, the provider appears in the Exchange Rate Provider dropdown on the WooCommerce settings subtab (which otherwise just explains that nothing is registered). fetch_callback receives the store’s base currency, the target codes derived from your per-language currency table, and the value of key_setting — an empty string when needs_key is false. Return [ 'EUR' => 0.92, … ]; return [] on failure and PerfLocale feeds the fx_sync circuit breaker, which skips subsequent ticks for a cooldown instead of retrying into an outage.

Hooks Reference

Filters

perflocale/woocommerce/exchange_rate_providers

Register the exchange rate providers available to the store. The default list is empty.

add_filter( 'perflocale/woocommerce/exchange_rate_providers', function( $providers ) {
	// Add a provider (see example above)
	$providers['my_api'] = [ ... ];

	// Remove one another plugin registered
	unset( $providers['someone_elses'] );

	return $providers;
} );

perflocale/woocommerce/exchange_rates_fetched

Filter rates after they are fetched but before they are saved.

add_filter( 'perflocale/woocommerce/exchange_rates_fetched', function( array $rates, string $base, string $provider ): array {
	// Add a margin to all rates
	foreach ( $rates as $code => $rate ) {
		$rates[ $code ] = $rate * 1.02; // 2% markup
	}
	return $rates;
}, 10, 3 );

Actions

perflocale/woocommerce/exchange_rates_synced

Fires after rates are successfully saved.

add_action( 'perflocale/woocommerce/exchange_rates_synced', function( array $rates, string $base, string $provider ): void {
	// Log the sync
	error_log( sprintf( 'Exchange rates synced from %s: %s', $provider, wp_json_encode( $rates ) ) );

	// Clear page cache
	if ( function_exists( 'wp_cache_flush' ) ) {
		wp_cache_flush();
	}
}, 10, 3 );

Troubleshooting

Rates not updating

  1. Check that WP-Cron is running. Some hosts disable WP-Cron; you may need a server-side cron job:
    */5 * * * * curl -s https://example.com/wp-cron.php > /dev/null 2>&1
  2. Click Sync Now in the settings to test manually.
  3. Enable WP_DEBUG and WP_DEBUG_LOG in wp-config.php to see sync errors:
    define( 'WP_DEBUG', true );
    define( 'WP_DEBUG_LOG', true );
    Check wp-content/debug.log for lines starting with PerfLocale Exchange Rate Sync:.

"No exchange-rate provider configured"

Expected on a fresh install — nothing is bundled. Register one through perflocale/woocommerce/exchange_rate_providers and select it in the dropdown, or return rates straight from perflocale/woocommerce/exchange_rates_fetched and leave the dropdown empty.

"API key missing" error

Your provider declared needs_key with a key_setting, and that setting is empty. PerfLocale renders no input for it, so write the value yourself (wp option patch update perflocale_settings <key_setting> '…') — or drop needs_key and resolve the credential inside fetch_callback.

Rates differ from expected

  • Free API tiers often return the previous day’s rates.
  • Cross-calculation (when your source’s base differs from your store base) introduces minor rounding.
  • Different data sources disagree slightly; two providers rarely return identical numbers.

"No rates returned" error

  • Your fetch_callback (or the exchange_rates_fetched filter) returned an empty array — the log line names which one.
  • Check that your target currency codes are valid ISO 4217 codes (e.g., EUR, GBP, JPY).
  • Repeated empty results — or a response whose rates are all non-numeric or non-positive — trip the fx_sync circuit breaker; the next few scheduled ticks are skipped until it cools down.