Multi-Currency Overview Multi-currency support lets international shoppers browse and pay in their local currency instead of your store's base currency. On Shopify, WooCommerce, and BigCommerce, this is a configuration task — each platform has built-in or app-based solutions that handle exchange rates, price display, and payment settlement automatically. Custom code is only required for headless storefronts. When to Use This Skill - When expanding to international markets where shoppers expect prices in their local currency - When analytics show significant traffic from non-base-currency coun…

) and AUD entry includes a label field (e.g. 'AU

Multi-Currency Overview Multi-currency support lets international shoppers browse and pay in their local currency instead of your store's base currency. On Shopify, WooCommerce, and BigCommerce, this is a configuration task — each platform has built-in or app-based solutions that handle exchange rates, price display, and payment settlement automatically. Custom code is only required for headless storefronts. When to Use This Skill - When expanding to international markets where shoppers expect prices in their local currency - When analytics show significant traffic from non-base-currency coun…

) to disambiguate from plain '

Multi-Currency Overview Multi-currency support lets international shoppers browse and pay in their local currency instead of your store's base currency. On Shopify, WooCommerce, and BigCommerce, this is a configuration task — each platform has built-in or app-based solutions that handle exchange rates, price display, and payment settlement automatically. Custom code is only required for headless storefronts. When to Use This Skill - When expanding to international markets where shoppers expect prices in their local currency - When analytics show significant traffic from non-base-currency coun…

\"\n },\n {\n \"name\": \"BASE_CURRENCY is USD\",\n \"max_score\": 6,\n \"description\": \"A BASE_CURRENCY constant is exported and its value is 'USD'\"\n },\n {\n \"name\": \"Intl.NumberFormat used\",\n \"max_score\": 15,\n \"description\": \"The formatPrice function uses Intl.NumberFormat (not manual string concatenation) to format the amount\"\n },\n {\n \"name\": \"style: currency option\",\n \"max_score\": 8,\n \"description\": \"Intl.NumberFormat is called with style: 'currency' option\"\n },\n {\n \"name\": \"Fraction digits from config\",\n \"max_score\": 10,\n \"description\": \"Both minimumFractionDigits and maximumFractionDigits are set from the currency's decimalDigits config value\"\n },\n {\n \"name\": \"Fallback handling\",\n \"max_score\": 8,\n \"description\": \"formatPrice includes a try/catch (or equivalent) with a fallback string that uses label ?? symbol plus amount.toFixed(decimalDigits) for unsupported environments\"\n },\n {\n \"name\": \"Locale parameter used\",\n \"max_score\": 9,\n \"description\": \"formatPrice accepts and passes a locale parameter to Intl.NumberFormat (defaults to 'en-US' when not provided)\"\n }\n ]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":2504,"content_sha256":"952ba25c9de3356cf9764e7ecd31b970a7f8277000f4aa51b877debcb02a4f0f"},{"filename":"evals/currency-config-and-formatting/task.md","content":"# Currency Display Utility for International Storefront\n\n## Problem/Feature Description\n\nA growing online retailer is launching in seven international markets simultaneously: the US, UK, EU, Canada, Australia, Japan, and Brazil. Their engineering team needs a reusable JavaScript module that correctly represents each currency's metadata and formats prices for display. Currently prices are being built as plain strings by concatenating symbols and numbers, which produces wrong output for currencies like EUR (symbol should follow the amount in some locales) and JPY (which has no decimal places). The QA team has flagged embarrassing bugs like \"€29.99\" being displayed in German storefronts instead of \"29,99 €\", and yen prices showing cents.\n\nThe team wants a clean, well-structured utility module they can import across the codebase. It needs to expose a currency configuration registry and a formatting function that handles locale-specific display correctly for all supported markets.\n\n## Output Specification\n\nProduce a single JavaScript file at `src/currency.js` that exports:\n\n1. A `SUPPORTED_CURRENCIES` object — a registry of all supported currencies with their metadata\n2. A `BASE_CURRENCY` string constant indicating the store's base currency\n3. A `formatPrice(amount, currency, locale)` function that returns a correctly formatted display string\n\nAlso produce a `src/currency.test.js` file that demonstrates the formatting function with at least 4 test cases covering different currencies and locales, printing results to stdout (use `console.log` or a simple assertion pattern — no test framework required). The test file should be runnable with `node src/currency.test.js`.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1698,"content_sha256":"2e58afb1cfb40f374c3f823bddeab91807588bbd85fd3d59f8731b8814fda391"},{"filename":"evals/currency-detection-and-stripe-integratio/criteria.json","content":"{\n \"context\": \"Tests whether the agent correctly implements the multi-priority currency detection chain, persists currency preferences with the right cookie settings, handles Stripe amount calculation for zero-decimal currencies, uses Stripe presentment currency, and follows the recommended approach for database storage of prices.\",\n \"type\": \"weighted_checklist\",\n \"checklist\": [\n {\n \"name\": \"Cookie checked first\",\n \"max_score\": 8,\n \"description\": \"Currency detection checks for a 'preferred_currency' cookie as the highest-priority signal before any other source\"\n },\n {\n \"name\": \"Accept-Language second\",\n \"max_score\": 7,\n \"description\": \"Currency detection falls through to the Accept-Language header as the second-priority source when no cookie is set\"\n },\n {\n \"name\": \"CF/CDN header third\",\n \"max_score\": 7,\n \"description\": \"Currency detection falls through to cf-ipcountry or x-country header as the third-priority source\"\n },\n {\n \"name\": \"Default to base currency\",\n \"max_score\": 6,\n \"description\": \"Currency detection defaults to the base currency (USD) when no other signal is available\"\n },\n {\n \"name\": \"Cookie name correct\",\n \"max_score\": 8,\n \"description\": \"The preference cookie is set with the name 'preferred_currency' (not a different name)\"\n },\n {\n \"name\": \"Cookie maxAge one year\",\n \"max_score\": 7,\n \"description\": \"The preference cookie is set with maxAge of 365 days (31536000000 ms or equivalent)\"\n },\n {\n \"name\": \"Cookie httpOnly true\",\n \"max_score\": 6,\n \"description\": \"The preference cookie is set with httpOnly: true\"\n },\n {\n \"name\": \"Cookie sameSite lax\",\n \"max_score\": 6,\n \"description\": \"The preference cookie is set with sameSite: 'lax'\"\n },\n {\n \"name\": \"Stripe amount multiply by 100\",\n \"max_score\": 8,\n \"description\": \"createPaymentIntent multiplies the display price by 100 (and uses Math.round) for standard currencies in the Stripe amount field\"\n },\n {\n \"name\": \"JPY not multiplied\",\n \"max_score\": 11,\n \"description\": \"createPaymentIntent does NOT multiply by 100 for JPY (zero-decimal currency) — passes the whole number amount to Stripe\"\n },\n {\n \"name\": \"Currency lowercased for Stripe\",\n \"max_score\": 9,\n \"description\": \"The currency field passed to Stripe is lowercased (e.g. 'eur', 'jpy') not uppercase\"\n },\n {\n \"name\": \"Base price in metadata\",\n \"max_score\": 10,\n \"description\": \"The Stripe paymentIntents.create parameters include metadata with the base_currency_amount (USD price) for reconciliation\"\n },\n {\n \"name\": \"Store base currency in DB\",\n \"max_score\": 7,\n \"description\": \"IMPLEMENTATION_NOTES.md or code comments state that prices are stored in the base currency (USD) in the database, not in the display currency\"\n }\n ]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":2955,"content_sha256":"5422f897553faf1d8db0fae31aac7f59453b7e87ad0a60ead2cab7c49e7eb1d1"},{"filename":"evals/currency-detection-and-stripe-integratio/task.md","content":"# Multi-Currency Checkout Middleware and Payment Integration\n\n## Problem/Feature Description\n\nA fashion e-commerce platform serving customers in North America, Europe, and the UK wants to improve their international shopping experience. Currently every customer sees prices in USD regardless of their location, and all Stripe charges are processed in USD. The engineering team has been asked to implement two things: automatic currency detection for incoming requests, and a Stripe payment integration that charges customers in their local currency.\n\nThe backend is an Express.js application. The team wants to detect each visitor's preferred currency automatically from available signals (browser language, CDN headers), let users override it manually, remember that preference so it persists across visits, and ensure the Stripe charge is created correctly for each currency. The team is particularly worried about getting the Stripe amount calculation right — a previous incident caused overcharges for Japanese customers.\n\nYour task is to implement the currency detection middleware, a currency preference API endpoint, and a Stripe payment intent creation helper. Since no live Stripe credentials or server are available, implement the logic as a self-contained module with stubs where needed. Document the key decisions in a `IMPLEMENTATION_NOTES.md` file.\n\n## Output Specification\n\nProduce the following files:\n\n1. `src/middleware/currencyDetection.js` — Express middleware that detects and attaches the appropriate currency to the request, plus a `setCurrencyPreference` handler\n2. `src/payments/stripeCharge.js` — a function `createPaymentIntent(displayPrice, customerCurrency, orderId, basePriceUSD)` that constructs (but doesn't necessarily execute) a correctly configured Stripe paymentIntents.create call, returning the parameters object\n3. `IMPLEMENTATION_NOTES.md` — brief notes (bullet points) explaining the currency detection priority order implemented and the database storage approach for prices\n\nThe `createPaymentIntent` function should be importable and testable — export it and include a short `src/payments/demo.js` that calls it with sample data for at least 3 currencies (include JPY) and prints the resulting parameter objects.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2266,"content_sha256":"6215769a2e0138367c2866538fd9226593721a749ee1283f65ccdd091e1d5536"},{"filename":"evals/exchange-rate-caching-and-price-rounding/criteria.json","content":"{\n \"context\": \"Tests whether the agent implements exchange rate caching with the correct TTL strategy, fetches from an appropriate API, applies psychological pricing rounding rules per the skill's specifications, and handles zero-decimal currencies and missing rate edge cases correctly.\",\n \"type\": \"weighted_checklist\",\n \"checklist\": [\n {\n \"name\": \"Redis cache key\",\n \"max_score\": 6,\n \"description\": \"The exchange rate cache uses the key 'exchange_rates' (not a different key name)\"\n },\n {\n \"name\": \"24-hour TTL\",\n \"max_score\": 8,\n \"description\": \"The cache TTL for display rates is set to 86400 seconds (24 hours), expressed as 60 * 60 * 24 or equivalent\"\n },\n {\n \"name\": \"Daily vs real-time distinction\",\n \"max_score\": 8,\n \"description\": \"Code or comments distinguish between daily cached rates for display and real-time rates for payment processing (e.g. a comment explaining this, or separate functions)\"\n },\n {\n \"name\": \"OXR or Fixer API\",\n \"max_score\": 7,\n \"description\": \"Exchange rate API URL references openexchangerates.org, fixer.io, or a named equivalent (not just a placeholder example.com URL)\"\n },\n {\n \"name\": \"Base currency returns early\",\n \"max_score\": 6,\n \"description\": \"convertPrice returns the original amount unchanged when targetCurrency equals the base currency (USD), without applying a rate\"\n },\n {\n \"name\": \"Missing rate error\",\n \"max_score\": 8,\n \"description\": \"convertPrice throws an error (not returns 0 or undefined) when the rate for a requested currency is not found\"\n },\n {\n \"name\": \"Sub-$10 rounds to .99\",\n \"max_score\": 10,\n \"description\": \"roundPrice produces an amount ending in .99 for input amounts less than 10 (e.g. 7.3 → 7.99)\"\n },\n {\n \"name\": \"$10-$99 rounds to nearest $0.05\",\n \"max_score\": 10,\n \"description\": \"roundPrice for amounts between 10 and 99 rounds to nearest $0.05 increment (e.g. uses Math.round(amount * 20) / 20 or equivalent logic)\"\n },\n {\n \"name\": \"$100+ rounds to whole number\",\n \"max_score\": 8,\n \"description\": \"roundPrice for amounts >= 100 returns a whole number (Math.round)\"\n },\n {\n \"name\": \"JPY rounds to integer\",\n \"max_score\": 10,\n \"description\": \"roundPrice for JPY (decimalDigits === 0) returns Math.round of the input, not a decimal\"\n },\n {\n \"name\": \"Demo includes JPY\",\n \"max_score\": 9,\n \"description\": \"The demo output includes at least one JPY conversion example showing a whole-number result\"\n },\n {\n \"name\": \"Cache check before fetch\",\n \"max_score\": 10,\n \"description\": \"getExchangeRates checks the cache first and only fetches from the API when no cached value exists\"\n }\n ]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":2816,"content_sha256":"d408f79ccc26f3ffecc132ef3ac00c91f7fd2aac80dd1db8c9bfab4c6cc31191"},{"filename":"evals/exchange-rate-caching-and-price-rounding/task.md","content":"# Exchange Rate Service for International Product Catalog\n\n## Problem/Feature Description\n\nA mid-size e-commerce company has started expanding globally and needs to display product prices in multiple currencies across their Node.js backend. Their current setup fetches a live exchange rate on every single product page request, which is hammering the rate API, causing slow page loads, and approaching monthly API quotas. The team needs to refactor the exchange rate layer to be efficient and cost-effective, while still giving shoppers accurate prices.\n\nAdditionally, a product manager noticed that after converting USD prices to other currencies, the displayed amounts look strange — things like €23.847 or AU$14.312. They want the numbers to look natural and appealing to shoppers in each market.\n\nYour task is to implement the exchange rate fetching, caching, and price conversion logic as a self-contained JavaScript module. Since no live Redis or API is available in this environment, implement the logic correctly and use mock/stub implementations (e.g. an in-memory cache object or a stub `redis` object) so the code is realistic and runnable. Write the module as if it will be dropped into a production codebase.\n\n## Output Specification\n\nProduce the following files:\n\n1. `src/exchangeRates.js` — the exchange rate service with caching and a `convertPrice(amountUSD, targetCurrency)` function\n2. `src/currencyRounding.js` — a `roundPrice(amount, currency)` function applying market-appropriate rounding\n3. `src/demo.js` — a runnable demo that imports both modules and prints conversion + rounding examples for at least 3 currencies (including JPY) and at least 3 different price points, showing before and after rounding. Run it with `node src/demo.js`.\n\nThe demo should print clearly labeled output showing original amounts, converted amounts, and final rounded display amounts.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1899,"content_sha256":"20ac892e97d932b33ebf260596b9082b1e0f7662ee50ffeed6807783e7fe74f6"},{"filename":"tile.json","content":"{\n \"name\": \"finsi/multi-currency\",\n \"version\": \"0.1.0\",\n \"summary\": \"Currency detection, conversion, rounding rules, and localized formatting\",\n \"skills\": {\n \"multi-currency\": {\n \"path\": \"SKILL.md\"\n }\n }\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":222,"content_sha256":"d43ba64a3cdccb4021f1f6b01902ec51b3e821fc8923662c6c71b8c30139b133"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Multi-Currency","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Multi-currency support lets international shoppers browse and pay in their local currency instead of your store's base currency. On Shopify, WooCommerce, and BigCommerce, this is a configuration task — each platform has built-in or app-based solutions that handle exchange rates, price display, and payment settlement automatically. Custom code is only required for headless storefronts.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When expanding to international markets where shoppers expect prices in their local currency","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When analytics show significant traffic from non-base-currency countries with low conversion rates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When implementing a currency selector in the site header","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When integrating Stripe or PayPal multi-currency settlement","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Instructions","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Determine your platform and multi-currency approach","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Platform","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recommended Approach","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Settlement Currency","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shopify (Shopify Payments)","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Enable ","type":"text"},{"text":"Markets","type":"text","marks":[{"type":"strong"}]},{"text":" and multi-currency in Shopify Admin","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stripe/Shopify settles in your payout currency; conversion is automatic","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shopify + non-Shopify gateway","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install ","type":"text"},{"text":"BEST Currency Converter","type":"text","marks":[{"type":"strong"}]},{"text":" or ","type":"text"},{"text":"MLV Auto Currency Switcher","type":"text","marks":[{"type":"strong"}]},{"text":" app","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Display-only conversion; payment taken in base currency","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WooCommerce","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install ","type":"text"},{"text":"WooCommerce Payments","type":"text","marks":[{"type":"strong"}]},{"text":" (for native multi-currency) or ","type":"text"},{"text":"WPML + WooCommerce Multilingual","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WooCommerce Payments settles in local currency; other gateways may convert","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BigCommerce","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Enable ","type":"text"},{"text":"Multi-Currency","type":"text","marks":[{"type":"strong"}]},{"text":" in Store Settings; use Stripe for multi-currency settlement","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BigCommerce handles display; Stripe settles in customer's currency","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custom / Headless","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use Stripe's multi-currency payment intents; fetch exchange rates from OpenExchangeRates","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stripe accepts charges in any currency and settles in your configured currency","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Set up multi-currency on your platform","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":4},"content":[{"text":"Shopify","type":"text"}]},{"type":"paragraph","content":[{"text":"Shopify's ","type":"text"},{"text":"Markets","type":"text","marks":[{"type":"strong"}]},{"text":" feature is the recommended way to implement multi-currency:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Go to ","type":"text"},{"text":"Settings → Markets","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click ","type":"text"},{"text":"Add market","type":"text","marks":[{"type":"strong"}]},{"text":" for each region (e.g., Europe, UK, Australia)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Under each market, go to ","type":"text"},{"text":"Currencies","type":"text","marks":[{"type":"strong"}]},{"text":" and enable the local currency (EUR, GBP, AUD)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shopify automatically converts prices using daily exchange rates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"To set manual price overrides for specific currencies (e.g., set EUR price to a fixed €99 instead of converting $99.00), go to the market and click ","type":"text"},{"text":"Manage","type":"text","marks":[{"type":"strong"}]},{"text":" to set currency-specific prices per product","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Exchange rate handling:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"By default, Shopify uses automatic exchange rates that update daily","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"To add a margin to the exchange rate (to cover your FX costs), go to ","type":"text"},{"text":"Settings → Markets → [Market] → Currencies","type":"text","marks":[{"type":"strong"}]},{"text":" and set a currency conversion rate adjustment (e.g., +2%)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Currency selector in storefront:","type":"text","marks":[{"type":"strong"}]},{"text":" Shopify themes from OS 2.0 (Dawn, Crave, etc.) include a built-in currency/country selector. In ","type":"text"},{"text":"Online Store → Themes → Customize","type":"text","marks":[{"type":"strong"}]},{"text":", add the ","type":"text"},{"text":"Header → Country/Region selector","type":"text","marks":[{"type":"strong"}]},{"text":" section.","type":"text"}]},{"type":"paragraph","content":[{"text":"Payment settlement:","type":"text","marks":[{"type":"strong"}]},{"text":" When using Shopify Payments, charges are made in the customer's currency. The payout to your bank account is in your default payout currency with Shopify handling the conversion. For Shopify Plus, you can configure multi-currency payouts with separate bank accounts per currency.","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"WooCommerce","type":"text"}]},{"type":"paragraph","content":[{"text":"Option A: WooCommerce Payments (native multi-currency)","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Install and activate ","type":"text"},{"text":"WooCommerce Payments","type":"text","marks":[{"type":"strong"}]},{"text":" (Stripe-powered)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Go to ","type":"text"},{"text":"WooCommerce → Settings → WooCommerce Payments → Multi-currency","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click ","type":"text"},{"text":"Add currencies","type":"text","marks":[{"type":"strong"}]},{"text":" and select the currencies you want to offer","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each currency, configure: exchange rate source (automatic from WooCommerce's daily feed or manual), rounding (e.g., round to .99), and whether to charge a conversion rate markup","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Enable ","type":"text"},{"text":"Currency switcher widget","type":"text","marks":[{"type":"strong"}]},{"text":": go to ","type":"text"},{"text":"Appearance → Widgets","type":"text","marks":[{"type":"strong"}]},{"text":" and add the ","type":"text"},{"text":"Currency Switcher","type":"text","marks":[{"type":"strong"}]},{"text":" widget to your header or sidebar","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Option B: WooCommerce Multilingual + WPML","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Install ","type":"text"},{"text":"WPML","type":"text","marks":[{"type":"strong"}]},{"text":" and ","type":"text"},{"text":"WooCommerce Multilingual & Multicurrency","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Go to ","type":"text"},{"text":"WPML → WooCommerce Multilingual → Currencies","type":"text","marks":[{"type":"strong"}]},{"text":" and add currencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each currency, set the exchange rate (manual or via automatic update) and rounding rules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The product price for each currency can be set manually per product or auto-converted","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Option C: Currency Switcher for WooCommerce (free plugin)","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"For display-only currency switching (payment taken in base currency), install ","type":"text"},{"text":"Currency Switcher for WooCommerce","type":"text","marks":[{"type":"strong"}]},{"text":" by Aelia. This converts displayed prices but settles in your base currency — simpler but requires customers to understand they will be charged in USD (or your base currency).","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"BigCommerce","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Go to ","type":"text"},{"text":"Settings → Store Setup → Currencies","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click ","type":"text"},{"text":"Add a currency","type":"text","marks":[{"type":"strong"}]},{"text":" and select from the dropdown","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each currency, configure:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exchange rate","type":"text","marks":[{"type":"strong"}]},{"text":": manual or auto-updated via BigCommerce's rate feed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rounding","type":"text","marks":[{"type":"strong"}]},{"text":": round to nearest 0.99, 0.95, or whole number","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Display settings","type":"text","marks":[{"type":"strong"}]},{"text":": where to show the currency selector","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set one currency as ","type":"text"},{"text":"Default","type":"text","marks":[{"type":"strong"}]},{"text":" — this is used in reporting and as the fallback","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Enable the currency selector: go to ","type":"text"},{"text":"Storefront → My Themes → Customize","type":"text","marks":[{"type":"strong"}]},{"text":" and add the currency selector component to your header","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Payment settlement with BigCommerce:","type":"text","marks":[{"type":"strong"}]},{"text":" Configure your payment gateway to accept charges in multiple currencies. Stripe (configured under ","type":"text"},{"text":"Settings → Payments → Stripe","type":"text","marks":[{"type":"strong"}]},{"text":") supports accepting payments in the customer's currency and settling in yours automatically.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":4},"content":[{"text":"Custom / Headless","type":"text"}]},{"type":"paragraph","content":[{"text":"Exchange rate fetching:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Fetch and cache daily exchange rates from OpenExchangeRates (free tier available)\nasync function getExchangeRates() {\n const cached = await redis.get('exchange_rates');\n if (cached) return JSON.parse(cached);\n\n const res = await fetch(\n `https://openexchangerates.org/api/latest.json?app_id=${process.env.OER_API_KEY}&base=USD&symbols=EUR,GBP,CAD,AUD,JPY`\n );\n const { rates } = await res.json();\n\n // Cache for 24 hours — daily rates are sufficient for display prices\n await redis.setex('exchange_rates', 86400, JSON.stringify(rates));\n return rates;\n}\n\nasync function convertPrice(amountUSD, targetCurrency) {\n if (targetCurrency === 'USD') return amountUSD;\n const rates = await getExchangeRates();\n return amountUSD * rates[targetCurrency];\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Stripe multi-currency charge:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Charge in customer's currency — Stripe handles conversion and settles in your payout currency\nconst paymentIntent = await stripe.paymentIntents.create({\n amount: Math.round(displayPrice * 100), // Amount in smallest unit (EUR cents, JPY yen)\n currency: customerCurrency.toLowerCase(), // 'eur', 'gbp', 'jpy'\n automatic_payment_methods: { enabled: true },\n metadata: { order_id: orderId, base_currency_amount: String(basePriceUSD) },\n});\n// Note: for JPY and other zero-decimal currencies, multiply by 1 (not 100)\nconst jpy_amount = Math.round(jpyPrice); // ¥3,000 → 3000, not 300000","type":"text"}]},{"type":"paragraph","content":[{"text":"Currency detection from browser:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Detect preferred currency from browser language or IP country header\nfunction detectCurrency(req) {\n // Check explicit user preference first\n if (req.cookies.preferred_currency) return req.cookies.preferred_currency;\n\n // Cloudflare and similar CDNs provide the visitor's country\n const country = req.headers['cf-ipcountry'];\n const countryToCurrency = { US:'USD', GB:'GBP', DE:'EUR', FR:'EUR', AU:'AUD', JP:'JPY', CA:'CAD' };\n return countryToCurrency[country] ?? 'USD';\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Verify correct behavior","type":"text"}]},{"type":"paragraph","content":[{"text":"Test these scenarios before going live:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Correct price display","type":"text","marks":[{"type":"strong"}]},{"text":": visit from a VPN with a UK IP — prices should show in GBP","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JPY zero-decimal","type":"text","marks":[{"type":"strong"}]},{"text":": Japanese prices should show as whole numbers (¥3,000 not ¥30.00)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Payment amount matches displayed price","type":"text","marks":[{"type":"strong"}]},{"text":": the Stripe charge amount must match what was shown","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Currency selector persists","type":"text","marks":[{"type":"strong"}]},{"text":": switching currency should persist across page navigation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cart totals in correct currency","type":"text","marks":[{"type":"strong"}]},{"text":": cart and checkout should use the selected currency","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use platform-native multi-currency","type":"text","marks":[{"type":"strong"}]},{"text":" — Shopify Markets, WooCommerce Payments, and BigCommerce's built-in feature handle exchange rates, rounding, and display correctly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refresh exchange rates daily","type":"text","marks":[{"type":"strong"}]},{"text":" — for display prices, daily rates are sufficient; do not fetch rates on every page load","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Store order amounts in your base currency","type":"text","marks":[{"type":"strong"}]},{"text":" — always record prices in your base currency in the database for consistent reporting; store the display currency and rate used for reference","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply rounding rules specific to each market","type":"text","marks":[{"type":"strong"}]},{"text":" — €9.99 looks natural; €9.847 does not; configure rounding in your platform's currency settings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test JPY specifically","type":"text","marks":[{"type":"strong"}]},{"text":" — JPY has zero decimal places; a common bug is displaying ¥29.99 instead of ¥3,000; verify by setting your test currency to JPY","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Pitfalls","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Problem","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prices flash to base currency on page load","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read the user's currency preference server-side (from a cookie) and render with the correct currency on first load; avoid client-side-only currency switching","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"JPY amount passed to Stripe as cents","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"For zero-decimal currencies (JPY, KRW), pass the whole amount — ¥3,000 → ","type":"text"},{"text":"amount: 3000","type":"text","marks":[{"type":"code_inline"}]},{"text":", not ","type":"text"},{"text":"amount: 300000","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Payment amount does not match displayed price","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recalculate the payment amount server-side using the stored exchange rate at checkout time; never trust client-submitted amounts","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exchange rate missing for a currency","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default to base currency or show an error; do not silently use a zero rate which would make products free","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shopify currency showing but not accepted at checkout","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Currency must be enabled under ","type":"text"},{"text":"Settings → Payments → Supported currencies","type":"text","marks":[{"type":"strong"}]},{"text":" in addition to ","type":"text"},{"text":"Settings → Markets","type":"text","marks":[{"type":"strong"}]},{"text":"; both settings must be configured","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@checkout-flow-optimization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@tax-calculation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@stripe-integration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@paypal-integration","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"multi-currency","risk":"critical","tags":["multi-currency","forex","localization","exchange-rates","formatting","i18n","checkout"],"tools":["claude-code","cursor","gemini-cli","copilot","codex-cli","kiro","opencode"],"author":"@skillopedia","source":{"stars":24,"repo_name":"awesome-ecommerce-skills","origin_url":"https://github.com/finsilabs/awesome-ecommerce-skills/blob/HEAD/skills/payments-checkout/multi-currency/SKILL.md","repo_owner":"finsilabs","body_sha256":"0ad639eef3f0ab0574c4a4f4aa737d71815a5f038237723d7585d8d505412609","cluster_key":"e578392457c3dd743dfa3819dc396e5f31a0635ff92ed98eb3f2148576bb0129","clean_bundle":{"format":"clean-skill-bundle-v1","source":"finsilabs/awesome-ecommerce-skills/skills/payments-checkout/multi-currency/SKILL.md","attachments":[{"id":"1235b96c-542b-5a29-ad4a-70913cdfa5eb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1235b96c-542b-5a29-ad4a-70913cdfa5eb/attachment.json","path":"evals/currency-config-and-formatting/criteria.json","size":2504,"sha256":"952ba25c9de3356cf9764e7ecd31b970a7f8277000f4aa51b877debcb02a4f0f","contentType":"application/json; charset=utf-8"},{"id":"cb157f33-b94c-5e4b-9ebe-ca4b385d49ac","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cb157f33-b94c-5e4b-9ebe-ca4b385d49ac/attachment.md","path":"evals/currency-config-and-formatting/task.md","size":1698,"sha256":"2e58afb1cfb40f374c3f823bddeab91807588bbd85fd3d59f8731b8814fda391","contentType":"text/markdown; charset=utf-8"},{"id":"1629d5f1-7b8a-5588-8c10-e479a3cb0695","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1629d5f1-7b8a-5588-8c10-e479a3cb0695/attachment.json","path":"evals/currency-detection-and-stripe-integratio/criteria.json","size":2955,"sha256":"5422f897553faf1d8db0fae31aac7f59453b7e87ad0a60ead2cab7c49e7eb1d1","contentType":"application/json; charset=utf-8"},{"id":"b3121005-75ca-52a8-aeea-da44ecd69637","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b3121005-75ca-52a8-aeea-da44ecd69637/attachment.md","path":"evals/currency-detection-and-stripe-integratio/task.md","size":2266,"sha256":"6215769a2e0138367c2866538fd9226593721a749ee1283f65ccdd091e1d5536","contentType":"text/markdown; charset=utf-8"},{"id":"2d9c4e25-4263-5e93-9467-5d2ab94ad261","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2d9c4e25-4263-5e93-9467-5d2ab94ad261/attachment.json","path":"evals/exchange-rate-caching-and-price-rounding/criteria.json","size":2816,"sha256":"d408f79ccc26f3ffecc132ef3ac00c91f7fd2aac80dd1db8c9bfab4c6cc31191","contentType":"application/json; charset=utf-8"},{"id":"7092a57f-3555-5bd3-a1d7-d164a2c9df49","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7092a57f-3555-5bd3-a1d7-d164a2c9df49/attachment.md","path":"evals/exchange-rate-caching-and-price-rounding/task.md","size":1899,"sha256":"20ac892e97d932b33ebf260596b9082b1e0f7662ee50ffeed6807783e7fe74f6","contentType":"text/markdown; charset=utf-8"},{"id":"bc6d3fb6-c3ef-5d95-8afe-4338e4fa52e6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bc6d3fb6-c3ef-5d95-8afe-4338e4fa52e6/attachment.json","path":"tile.json","size":222,"sha256":"d43ba64a3cdccb4021f1f6b01902ec51b3e821fc8923662c6c71b8c30139b133","contentType":"application/json; charset=utf-8"}],"bundle_sha256":"15dbdd077bfd2413729ab065714f9d80501e50476fd5a02e8167310a69d3c568","attachment_count":7,"text_attachments":7,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/payments-checkout/multi-currency/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"browser-automation-scraping","category_label":"Browser"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"browser-automation-scraping","triggers":["multi currency","currency conversion","international pricing","exchange rate","currency selector","localize prices"],"platforms":["shopify","woocommerce","bigcommerce","custom"],"date_added":"2026-03-12","difficulty":"intermediate","import_tag":"clean-skills-v1","description":"Let international shoppers browse and pay in their local currency with automatic detection, live exchange rates, and locale-specific price formatting"}},"renderedAt":1782980103964}

Multi-Currency Overview Multi-currency support lets international shoppers browse and pay in their local currency instead of your store's base currency. On Shopify, WooCommerce, and BigCommerce, this is a configuration task — each platform has built-in or app-based solutions that handle exchange rates, price display, and payment settlement automatically. Custom code is only required for headless storefronts. When to Use This Skill - When expanding to international markets where shoppers expect prices in their local currency - When analytics show significant traffic from non-base-currency coun…