PAY-66Recommended
Set a price in each currency instead of converting at checkout
Store a fixed price for each currency you sell in, in the price catalogue. Never compute a customer's price from a live exchange rate at checkout.
How to do it
A price converted at checkout changes every day, so the product page, the cart and the invoice can disagree. Round prices per currency, such as 129 SEK and 12 EUR, chosen on purpose. Pick the currency from the customer's billing country, not the browser language, and keep it for the life of a subscription. Review the prices when a rate moves more than a set share, such as 10%, and change them as a new price. Keep each price in the versioned price catalogue. A provider feature that converts for you is fine when it fixes the rate for the whole checkout and shows the rate to the customer.
Examples
SQL example
CREATE TABLE price_amounts (
price_id text NOT NULL REFERENCES prices (id),
currency char(3) NOT NULL,
amount_minor bigint NOT NULL CHECK (amount_minor >= 0),
PRIMARY KEY (price_id, currency)
);
Python example
def amount_for(db, price_id: str, currency: str) -> int:
row = db.execute(
"SELECT amount_minor FROM price_amounts WHERE price_id = %s AND currency = %s",
(price_id, currency),
).fetchone()
if row is None:
raise ValueError(f"No {currency} amount for {price_id}")
return row[0]
JavaScript example
export async function amountFor(db, priceId, currency) {
const { rows } = await db.query('SELECT amount_minor FROM price_amounts WHERE price_id = $1 AND currency = $2', [priceId, currency])
if (!rows.length) throw new Error(`No ${currency} amount for ${priceId}`)
return Number(rows[0].amount_minor)
}
How to check
Search the checkout code for calls to an exchange rate service. None are found. A test loads the same product as a Swedish and a German customer and gets the stored SEK and EUR prices.
Sources
Related rules
- PAY-4 Keep prices in a versioned catalogue in Payments and billing
- PAY-70 Offering card holders their home currency at a hidden mark-up in Payments and billing
- LOC-27 Take the currency from the amount and not from the locale in Localization
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/payments/currencies-and-settlement/price-per-currency">Agent Wranglers payments and billing playbook</a>. In code you write, add a comment next to it with the same link.