Learn how to set up, manage, and display multiple currencies in Stripe, including payments, subscriptions, reporting, and user currency switching.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
How to Handle Multiple Currencies in Stripe: A Comprehensive Guide
Step 1: Set Up Your Stripe Account for Multi-Currency Support
Before implementing multiple currencies in your application, ensure your Stripe account is properly configured:
// First, log in to your Stripe Dashboard
// Navigate to Settings > Account settings > Currency settings
// Enable the currencies you want to support
Note that Stripe supports displaying prices in multiple currencies but typically settles in your account's default currency, potentially incurring conversion fees.
Step 2: Initialize Stripe with API Keys
Set up your Stripe client with your API keys:
const stripe = require('stripe')('sk_test_your_secret_key');
// For frontend (using Stripe.js)
Step 3: Create Products with Multiple Currency Prices
When creating products, you can specify prices in different currencies:
// Create a product
const product = await stripe.products.create({
name: 'Premium Subscription',
description: 'Monthly premium subscription',
});
// Create prices for this product in different currencies
const usdPrice = await stripe.prices.create({
product: product.id,
unit\_amount: 1500, // $15.00
currency: 'usd',
recurring: {
interval: 'month',
},
});
const eurPrice = await stripe.prices.create({
product: product.id,
unit\_amount: 1300, // €13.00
currency: 'eur',
recurring: {
interval: 'month',
},
});
const gbpPrice = await stripe.prices.create({
product: product.id,
unit\_amount: 1100, // £11.00
currency: 'gbp',
recurring: {
interval: 'month',
},
});
Step 4: Store Currency-Price Mappings in Your Database
Create a structure to store price IDs for each currency:
// Example database schema (pseudocode)
const ProductSchema = {
stripeProductId: String,
name: String,
description: String,
prices: {
usd: { priceId: String, amount: Number },
eur: { priceId: String, amount: Number },
gbp: { priceId: String, amount: Number },
// other currencies as needed
}
};
// Store the product with its price IDs
const productRecord = {
stripeProductId: product.id,
name: 'Premium Subscription',
description: 'Monthly premium subscription',
prices: {
usd: { priceId: usdPrice.id, amount: 1500 },
eur: { priceId: eurPrice.id, amount: 1300 },
gbp: { priceId: gbpPrice.id, amount: 1100 }
}
};
Step 5: Determine the User's Preferred Currency
Implement logic to determine which currency to use:
function getUserCurrency(req) {
// Option 1: User preference (stored in profile)
if (req.user && req.user.preferredCurrency) {
return req.user.preferredCurrency;
}
// Option 2: Geographic detection
const countryToDefaultCurrency = {
'US': 'usd',
'CA': 'cad',
'GB': 'gbp',
'DE': 'eur',
'FR': 'eur',
// Add more country-currency mappings as needed
};
if (req.headers['cf-ipcountry']) { // If using Cloudflare
const country = req.headers['cf-ipcountry'];
if (countryToDefaultCurrency[country]) {
return countryToDefaultCurrency[country];
}
}
// Option 3: Browser locale detection (frontend JavaScript)
/\*
const locale = navigator.language || navigator.userLanguage;
const currency = getCurrencyFromLocale(locale);
\*/
// Fallback to default currency
return 'usd';
}
Step 6: Create Payment Intents with Appropriate Currency
When creating a Payment Intent, specify the correct currency:
async function createPayment(productId, currency) {
// Fetch product from your database
const product = await YourDatabase.findProduct(productId);
// Check if the product has a price in the requested currency
if (!product.prices[currency]) {
throw new Error(`Price not available in ${currency}`);
}
// Create a payment intent with the appropriate currency
const paymentIntent = await stripe.paymentIntents.create({
amount: product.prices[currency].amount,
currency: currency,
payment_method_types: ['card'],
metadata: {
productId: productId
}
});
return paymentIntent;
}
Step 7: Display Prices to Users
Format prices appropriately for display:
function formatPrice(amount, currency) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
minimumFractionDigits: 2
}).format(amount / 100); // Stripe amounts are in cents
}
// Example usage
const price = formatPrice(product.prices['usd'].amount, 'usd');
// Output: $15.00
// Frontend example with React
function PriceDisplay({ product, currency }) {
const price = formatPrice(product.prices[currency].amount, currency);
return {price};
}
Step 8: Create Checkout Sessions with Currency Support
For Stripe Checkout, specify the currency:
async function createCheckoutSession(productId, currency) {
// Fetch product from your database
const product = await YourDatabase.findProduct(productId);
// Get the appropriate price ID for the currency
const priceId = product.prices[currency].priceId;
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line\_items: [
{
price: priceId,
quantity: 1,
},
],
mode: 'payment',
success_url: 'https://your-website.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel\_url: 'https://your-website.com/cancel',
locale: currency === 'eur' ? 'fr' : 'en', // Optionally customize checkout locale
});
return session;
}
Step 9: Handle Currency Conversion for Reporting
For reporting and analytics, you might need to convert all transactions to a base currency:
async function getMonthlyRevenue(month, year) {
// Fetch payments for the given month
const payments = await stripe.paymentIntents.list({
created: {
gte: new Date(year, month-1, 1).getTime() / 1000,
lt: new Date(year, month, 1).getTime() / 1000
}
});
// Group by currency
const revenueByCurrency = {};
for (const payment of payments.data) {
if (payment.status === 'succeeded') {
const currency = payment.currency;
if (!revenueByCurrency[currency]) {
revenueByCurrency[currency] = 0;
}
revenueByCurrency[currency] += payment.amount;
}
}
// Convert to base currency (USD) for reporting
// Note: You'll need an exchange rate API for accurate conversion
const baseRevenue = await convertToBaseCurrency(revenueByCurrency, 'usd');
return {
byCurrency: revenueByCurrency,
totalInUSD: baseRevenue
};
}
async function convertToBaseCurrency(revenueByCurrency, baseCurrency) {
// Example implementation using an exchange rate API
let totalInBaseCurrency = 0;
for (const currency in revenueByCurrency) {
if (currency === baseCurrency) {
totalInBaseCurrency += revenueByCurrency[currency];
} else {
// Get exchange rate (you'd implement this with an API)
const rate = await getExchangeRate(currency, baseCurrency);
totalInBaseCurrency += revenueByCurrency[currency] \* rate;
}
}
return totalInBaseCurrency;
}
Step 10: Handle Webhooks for Multi-Currency Payments
Configure webhook handlers to process events from payments in different currencies:
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
'whsec_your_webhook\_secret'
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'payment\_intent.succeeded') {
const paymentIntent = event.data.object;
const currency = paymentIntent.currency;
const amountPaid = paymentIntent.amount;
// Process successful payment
// Consider currency-specific logic if needed
await processSuccessfulPayment(paymentIntent.id, amountPaid, currency);
}
res.status(200).send();
});
async function processSuccessfulPayment(paymentIntentId, amount, currency) {
// Record the payment in your database
await YourDatabase.recordPayment({
stripePaymentId: paymentIntentId,
amount: amount,
currency: currency,
timestamp: new Date(),
// Additional details as needed
});
// Handle currency-specific business logic if needed
if (currency === 'jpy') {
// Japanese Yen payments might need special processing
await sendJapaneseReceiptEmail(paymentIntentId);
}
}
Step 11: Implement Currency Switching for Users
Allow users to switch between available currencies:
// Backend route to update user currency preference
app.post('/api/set-currency', async (req, res) => {
const { currency } = req.body;
// Validate currency is supported
const supportedCurrencies = ['usd', 'eur', 'gbp', 'jpy', 'cad', 'aud'];
if (!supportedCurrencies.includes(currency)) {
return res.status(400).json({ error: 'Unsupported currency' });
}
// Update user preference in database
await YourDatabase.updateUser(req.user.id, { preferredCurrency: currency });
// Update session if needed
req.session.currency = currency;
return res.json({ success: true, currency });
});
// Frontend currency selector component
/\*
function CurrencySelector({ currentCurrency, onCurrencyChange }) {
const currencies = [
{ code: 'usd', symbol: '$', name: 'US Dollar' },
{ code: 'eur', symbol: '€', name: 'Euro' },
{ code: 'gbp', symbol: '£', name: 'British Pound' },
// Add more currencies as needed
];
return (
);
}
\*/
Step 12: Handle Subscriptions with Multiple Currencies
For recurring subscriptions, handle the currency selection at signup:
async function createSubscription(customerId, priceId, currency) {
try {
// Create the subscription
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on\_subscription' },
expand: ['latest_invoice.payment_intent'],
});
// Store subscription details with currency information
await YourDatabase.saveSubscription({
customerId: customerId,
subscriptionId: subscription.id,
currency: currency,
priceId: priceId,
status: subscription.status
});
return {
subscriptionId: subscription.id,
clientSecret: subscription.latest_invoice.payment_intent.client\_secret,
};
} catch (error) {
console.error('Subscription creation failed:', error);
throw error;
}
}
Step 13: Handle Currency Changes in Subscriptions
If a user wants to change the currency of their subscription:
async function changeSubscriptionCurrency(subscriptionId, newCurrency, productId) {
// Fetch the product to get the price ID for the new currency
const product = await YourDatabase.findProduct(productId);
const newPriceId = product.prices[newCurrency].priceId;
// You cannot directly change the currency of a subscription in Stripe
// Instead, you need to cancel the old one and create a new one
// 1. Retrieve the current subscription
const currentSubscription = await stripe.subscriptions.retrieve(subscriptionId);
const customerId = currentSubscription.customer;
// 2. Cancel the current subscription at period end
await stripe.subscriptions.update(subscriptionId, {
cancel_at_period\_end: true,
});
// 3. Create a new subscription in the new currency
// (ideally schedule this to start when the old one ends)
const endDate = new Date(currentSubscription.current_period_end \* 1000);
// 4. Create the new subscription with the new currency price
const newSubscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: newPriceId }],
trial_end: currentSubscription.current_period\_end,
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on\_subscription' },
expand: ['latest_invoice.payment_intent'],
});
// 5. Update your database records
await YourDatabase.updateSubscriptionCurrency({
oldSubscriptionId: subscriptionId,
newSubscriptionId: newSubscription.id,
newCurrency: newCurrency,
effectiveDate: endDate
});
return {
oldSubscriptionId: subscriptionId,
newSubscriptionId: newSubscription.id,
clientSecret: newSubscription.latest_invoice.payment_intent.client\_secret,
};
}
Step 14: Implement Exchange Rate Handling
For accurate reporting and conversions, implement an exchange rate service:
// Utility to fetch current exchange rates
class ExchangeRateService {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.exchangerate.host/latest';
this.cacheTimeout = 3600000; // 1 hour in milliseconds
this.rateCache = {
timestamp: 0,
rates: {}
};
}
async getRates(baseCurrency = 'USD') {
// Check if cache is still valid
const now = Date.now();
if (now - this.rateCache.timestamp < this.cacheTimeout &&
this.rateCache.baseCurrency === baseCurrency) {
return this.rateCache.rates;
}
// Fetch new rates
try {
const response = await fetch(`${this.baseUrl}?base=${baseCurrency}`);
const data = await response.json();
if (data && data.rates) {
// Update cache
this.rateCache = {
timestamp: now,
baseCurrency: baseCurrency,
rates: data.rates
};
return data.rates;
}
throw new Error('Invalid response from exchange rate API');
} catch (error) {
console.error('Failed to fetch exchange rates:', error);
// If cache exists but expired, still use it as fallback
if (this.rateCache.rates && Object.keys(this.rateCache.rates).length) {
return this.rateCache.rates;
}
throw error;
}
}
async convert(amount, fromCurrency, toCurrency) {
const rates = await this.getRates(fromCurrency.toUpperCase());
const rate = rates[toCurrency.toUpperCase()];
if (!rate) {
throw new Error(`Exchange rate not available for ${fromCurrency} to ${toCurrency}`);
}
return amount \* rate;
}
}
// Usage example
const exchangeService = new ExchangeRateService('your_api_key_if_needed');
const amountInUSD = await exchangeService.convert(100, 'EUR', 'USD');
Step 15: Implement Comprehensive Testing
Test your multi-currency implementation thoroughly:
// Example test for currency conversion using Jest
test('Currency conversion should work correctly', async () => {
// Mock the exchange rate API
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({
rates: {
USD: 1.1,
GBP: 0.85,
JPY: 130
}
})
})
);
const exchangeService = new ExchangeRateService('test\_key');
// Test EUR to USD conversion
const usdAmount = await exchangeService.convert(100, 'EUR', 'USD');
expect(usdAmount).toBeCloseTo(110, 2);
// Test EUR to GBP conversion
const gbpAmount = await exchangeService.convert(100, 'EUR', 'GBP');
expect(gbpAmount).toBeCloseTo(85, 2);
});
// Test creating a payment intent with different currencies
test('Creating payment intent with different currencies', async () => {
// Mock your database and Stripe API calls
YourDatabase.findProduct = jest.fn().mockResolvedValue({
id: 'prod\_123',
prices: {
usd: { priceId: 'price_usd_123', amount: 1500 },
eur: { priceId: 'price_eur_123', amount: 1300 }
}
});
stripe.paymentIntents.create = jest.fn().mockImplementation(
(params) => Promise.resolve({
id: 'pi\_test123',
amount: params.amount,
currency: params.currency,
client_secret: 'test_secret'
})
);
// Test USD payment
const usdPayment = await createPayment('prod\_123', 'usd');
expect(usdPayment.amount).toBe(1500);
expect(usdPayment.currency).toBe('usd');
// Test EUR payment
const eurPayment = await createPayment('prod\_123', 'eur');
expect(eurPayment.amount).toBe(1300);
expect(eurPayment.currency).toBe('eur');
// Test unsupported currency
await expect(createPayment('prod\_123', 'xyz')).rejects.toThrow();
});
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.