KahunaCart 1.4.0 is out, with 41 changes: 17 new features, 11 improvements and 13 fixes. The headline is the one people have asked for most since launch: selling in more than one currency.
One store, several currencies
Until now a KahunaCart store sold in one currency. If you wanted euros and pounds as well as dollars, you ran three stores or you didn't do it.
With 1.4.0 you keep one base currency and list the others you want to sell in under Configuration → General → Selling in More Than One Currency. Then you type a price for each one. A shopper in Germany browses in euros, pays in euros and gets a receipt in euros. Your accounts in dollars stay exactly as they were.
Nothing is converted. KahunaCart doesn't look up exchange rates or work a price out for you. You decide that the tee is €29 and £25, not whatever $32 happens to be today. A product with no price in a currency simply isn't sold in it, and the product page says "Not available in EUR" instead of guessing. The one exception is free: a product with a base price of 0 is free in every currency.
You can type the prices wherever you already work: in the product editor, with bulk edit, in a CSV file or in the catalogue file. Every currency gets its own column, and JPY gets no decimals while KWD gets three.
Everything with a price follows along
A price is only one of the amounts a store charges. Coupons, sales, price lists, quantity tiers, order minimums and maximums, payment method fees, shipping methods, collection point fees and store credit all take an amount per currency now. There is one rule for all of them: if there's no amount in a currency, the rule doesn't apply there. A "€5 off" coupon with no dollar amount simply doesn't work on a dollar cart. It never turns into $5 or $0.
The shopper's currency, picked for them
KahunaCart works out a shopper's currency from what it knows: a choice they already made, the country on their address, or a country header from your CDN, such as Cloudflare's CF-IPCountry. A switcher in the header lets them change it, and ?currency=EUR on a link opens a page in euros.
Once a shopper has a cart, the cart's currency wins. Switching moves the whole cart: every line is priced again in the new currency, and anything with no price there is taken out and named. If a cart already had a payment page opened at a provider, it keeps its currency for good and offers Start a new cart in EUR instead, so no payment is ever left pointing at the wrong amount.
Orders, refunds and reports stay in one currency
Every order is in exactly one currency, from the cart to the last refund. Lines, shipping, tax, payments, refunds and order changes are all in that currency, and invoices and emails print it.
Reports, the dashboard, customer lifetime spend and cart recovery add up one currency at a time, with a picker to choose which. They never add euros to dollars and call the result a total.
Payment methods that can't take an order's currency aren't offered for it. Every payment provider plugin has an update for this release, and Stripe 1.2.6 sends Icelandic krona, Ugandan shilling and Malagasy ariary at the scale Stripe expects.
Add-ons, agents and caching
Subscriptions 1.1.5, Bookings 1.1.5, Rentals 1.0.6 and Shipping 1.1.7 all price in each currency you sell in, and a subscription renews in the currency it started in.
AI shopping agents get the same treatment. The storefront API, the storefront MCP endpoint, UCP checkout, the product feeds and the structured data on product pages all work in every currency you sell in.
If you use a CDN, one rule keeps it simple: a page is only cached publicly when its address decides the currency. The caching section of the guide explains how to set that up.
A store that sells in one currency sees no change. If you never list a second currency, the storefront and the admin look and work as they did, and the new tables stay empty. Want to try it first? bin/plugin kahunacart seed --currencies=EUR,GBP fills a demo store with prices in euros and pounds.
Also in this release
- Every admin table has a search box, next to the sortable headings from 1.3.8.
- Choosing a payment method at checkout updates the total. The fee and its tax are added as soon as the method is picked, so the total the shopper sees is the total they pay.
- A catalogue file with a variant option that doesn't match is refused. If a variant names a value its option doesn't list, usually because the values are in a different order than the options, the import now stops and tells you which product, variant and label. It used to import, and the product page then showed every choice disabled.
Fixes
- The configuration export no longer carries the store's signing secret. Earlier exports included the secret the store signs order, invoice and sign-in links with, and importing a file replaced the target store's secret. The secret now stays on its store, and so do other keys and webhook addresses. If you have shared an export file, update.
- Subscriptions now handle payment method fees properly. Stripe left the fee off subscription payments, so the store paid it. Subscriptions 1.1.5 adds a choice: a plan's price can have the method's fee added (the subscriber pays it, with its tax, on every payment, and on an invoice renewal a cheaper method costs less) or included (the plan's price is what they pay, whatever the method). Added is the default, each plan can choose its own, and subscriptions that already exist renew as they always did. Update Stripe to 1.2.6 as well.
- A product with a fixed-price add-on can be saved from the editor again.
- Choosing a collection point at checkout no longer leaves the next step disabled.
- The payment method editor no longer throws away what you typed when you add or change a condition.
- Restoring a product revision no longer undoes a sale made at the same moment.
- A database retry after a deadlock no longer repeats a decision made on stale data.