FOX - WooCommerce Currency Switcher Professional

WooCommerce Checkout Currency Based on Customer Country, Not IP

Making the WooCommerce checkout currency based on the customer's country means one thing: a customer who enters a Swiss billing address pays in Swiss francs, whatever currency they browsed in and wherever their internet connection comes from. FOX does not do this from the address on its own. It has one built-in option that uses the country of the visitor's IP address, and the address case takes a short snippet that reuses your GeoIP rules. This guide explains both, the billing versus shipping pitfall behind most broken snippets, and the limits to know before you rely on it.

IP country or address country

Two different countries are involved, and they often differ for travellers, VPN users, people near a border and anyone ordering a gift for someone abroad.

CountryWhere it comes fromWhat FOX does with it
IP countryWooCommerce geolocation, on the first page viewGeoIP rules pick the first currency; the Checkout by GeoIP rules option can force it at checkout
Billing countrytyped or chosen by the customer at checkout, saved in their accountnothing by default; the snippet below uses it
Shipping countryonly when the customer ticks Ship to a different address?nothing by default; optional in the snippet

How the IP country is detected and mapped is covered in automatically change currency by country. This article is about the moment of payment.

Before you start

  • Is multiple allowed = Yes in the Advanced tab of WooCommerce → Settings → Currency. With No, FOX resets the checkout to the base currency, and no country logic can change that. The trade-offs of both settings are explained in show prices in local currency, charge in base currency.
  • Countries assigned in the GeoIP tab. Both methods below read the same lists: Switzerland in the CHF list, the euro-area countries in the EUR list, and so on. Keeping one map means the first visit and the checkout always agree.
  • Payment gateways that accept each currency. Use the Payments tab to offer each gateway only in currencies it can charge; see how to hide a payment gateway by currency.

The built-in option: Checkout by GeoIP rules

Checkout by GeoIP rules is in the Advanced tab. It appears only when Is multiple allowed is Yes and Individual fixed prices rules for each product is on, because it is meant for shops that set their own price per currency: without it, a customer could pick the currency in which your fixed price is cheapest.

When it is on, FOX switches the checkout and order payment pages, and every order review refresh, to the currency that the GeoIP tab assigns to the visitor's IP country. Visitors from countries without a rule keep the currency they selected. After each check FOX fires the woocs_force_pay_bygeoip_rules action with the country, the GeoIP currency (empty for unlisted countries) and the current currency, which is the place for your own fallback.

The option knows nothing about the address. A Swiss resident ordering from a hotel in Italy pays in euros. If that is a problem for your shop, use the snippet instead and switch this option off.

Checkout currency based on the customer's billing country

On the classic checkout, WooCommerce refreshes the order review over AJAX every time the customer changes the country, state or postcode, and fires the woocommerce_checkout_update_order_review action before it recalculates the totals. Switching the currency at that point makes the totals, shipping and payment methods appear in the new currency at once.

/*
 * FOX: the checkout currency follows the country the customer enters.
 * Country-to-currency map: the lists in the FOX GeoIP tab.
 * Needs "Is multiple allowed" = Yes. Classic (shortcode) checkout only.
 * Child theme functions.php or a code snippets plugin.
 */
add_action('woocommerce_checkout_update_order_review', function ($post_data) {
    global $WOOCS;
    if (empty($WOOCS)) {
        return;
    }
    parse_str((string) $post_data, $data);

    // true: use the shipping country when "Ship to a different address?" is ticked
    $use_delivery_country = false;

    $country = isset($data['billing_country']) ? wc_clean($data['billing_country']) : '';
    if ($use_delivery_country && !empty($data['ship_to_different_address']) && !empty($data['shipping_country'])) {
        $country = wc_clean($data['shipping_country']);
    }

    $currency = $WOOCS->get_currency_by_country($country);

    // Optional: countries without a GeoIP rule pay in the base currency
    // if (!$currency) { $currency = $WOOCS->default_currency; }

    if ($currency && $currency !== $WOOCS->current_currency) {
        $WOOCS->set_currency($currency);
    }
}, 10000);

$WOOCS->get_currency_by_country() returns the currency whose GeoIP list contains the country, or an empty string. $WOOCS->set_currency() stores the currency for the visitor, so the order is created in it when the customer places it. Priority 10000 runs the code after FOX's own handler on the same action. As written, customers from unlisted countries keep the currency they browsed in; uncomment the fallback line to make them pay in the base currency. If you would rather keep the country list in the code than in the GeoIP tab, switch currency by billing address instead of geolocation has versions with a hard-coded map.

Why billing_country and not shipping_country

The older snippets in our FAQs on changing currency by delivery country read shipping_country, and the most common report about them is "the currency is frozen". The reason is in what WooCommerce sends. With every refresh it posts the whole checkout form, including the shipping fields that stay hidden while Ship to a different address? is unticked. The hidden shipping country holds whatever was pre-filled, usually the geolocated or saved country, and it does not change when the customer edits the billing address. So the currency follows a field the customer never sees.

The billing country is always visible and always current. The ticked box arrives as ship_to_different_address, and only then is the shipping country the customer's real choice, which is exactly what $use_delivery_country = true checks. If WooCommerce → Settings → Shipping is set to force shipping to the billing address, there are no shipping fields at all and the billing country is the only one that matters.

Which country should decide? The billing country matches the card or account that pays, which payment providers check for fraud screening. The delivery country matches customs, duties and local pricing. Pick one rule and state it on the checkout page.

Restricting currencies by country

The snippet already enforces the rule where it matters: whatever currency a customer browsed in, a country with a GeoIP rule pays in its own currency. To also hide currencies from the switcher for visitors of a given IP country, so that Spanish visitors never see USD in the first place, see how to disallow a currency for a country. That filter works with the IP country, so it complements the checkout rule rather than replacing it.

Limits to know

  • Classic checkout only. The snippet works with the [woocommerce_checkout] shortcode checkout. The block-based Checkout updates the address through the WooCommerce Store API and never fires woocommerce_checkout_update_order_review. If your checkout page shows the Checkout block, replace it with the shortcode checkout for this logic.
  • Express payment buttons such as Apple Pay or Google Pay take the address from the wallet and skip the checkout form, so the currency stays whatever was active when the button was pressed.
  • The cart page shipping calculator does not run this hook; the currency changes when the customer reaches the checkout.
  • Do not combine it with Checkout by GeoIP rules. That option switches the checkout page to the IP currency when it loads, the snippet switches the order review to the address currency a moment later, and the page briefly shows two currencies. Use one rule.
  • The cart and checkout pages must be excluded from every page cache.

Logged-in customers: the saved billing country on every page

The checkout snippet acts only on the checkout page. For a members-only shop, or when a registered customer must see their own currency from the first page, read the saved billing country on every page view:

/*
 * FOX: logged-in customers always see the currency of their billing country.
 * Guests are not affected. Child theme functions.php or a code snippets plugin.
 */
add_action('wp_head', function () {
    global $WOOCS;
    if (empty($WOOCS) || !is_user_logged_in() || !function_exists('WC') || !WC()->customer) {
        return;
    }
    $currency = $WOOCS->get_currency_by_country(WC()->customer->get_billing_country());
    if ($currency && $currency !== $WOOCS->current_currency) {
        $WOOCS->set_currency($currency);
    }
}, 1000);

It runs after FOX has finished its own work on the page (priority 999), so it also wins over the first-visit GeoIP rules. Registered customers from a country with a GeoIP rule cannot switch currency while it is active; when they change the billing country in their account or at checkout, the next page follows the new country.

Test it

  1. Put a product in the cart and open the checkout in a private window.
  2. Change the billing country to one from each GeoIP list and watch the totals switch currency after each refresh.
  3. If you use the delivery variant, tick Ship to a different address?, pick another country, then untick it again: the currency must return to the billing country's.
  4. Place a test order with the gateway in test mode and check the order currency in the order screen and the e-mail.

If the totals do not change, check that the checkout page uses the shortcode, that Is multiple allowed is Yes, that the country is in a GeoIP list, and that no other snippet calls $WOOCS->set_currency() on the checkout. If it still fails with a default theme and only WooCommerce and FOX active, open a topic on the FOX support forum with the code you use and a link to the checkout, and we will look at it.