# How to handle multiple currencies in Stripe

- Tool: Stripe
- Difficulty: Intermediate
- Time required: 15 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## TL;DR

Handle multiple currencies in Stripe by specifying the currency code (lowercase ISO 4217) when creating PaymentIntents or Checkout Sessions. Stripe settles to your default currency at the current exchange rate. Use Adaptive Pricing for automatic localized pricing in Checkout, or create separate Price objects per currency for fixed international pricing.

## Multi-Currency Payments with Stripe

Stripe supports 135+ currencies. When you create a PaymentIntent, you specify the presentment currency — what the customer sees and pays. Stripe converts to your settlement currency (the currency of your bank account) at the current exchange rate, charging a 1% conversion fee on top of standard processing fees. For Stripe Checkout, Adaptive Pricing can auto-convert prices to the customer's local currency. Alternatively, create separate Price objects for each currency for fixed rates.

## Before you start

- A Stripe account with your settlement currency configured
- Node.js 18+ with the stripe npm package
- Understanding of ISO 4217 currency codes (usd, eur, gbp, jpy, etc.)

## Step-by-step guide

### 1. Create a PaymentIntent in a specific currency

Specify the currency parameter as a lowercase ISO 4217 code. The amount is in the smallest unit of that currency.

```
// Charge €25.00 (EUR)
const paymentIntentEUR = await stripe.paymentIntents.create({
  amount: 2500,       // 2500 cents = €25.00
  currency: 'eur',
  automatic_payment_methods: { enabled: true },
});

// Charge £15.00 (GBP)
const paymentIntentGBP = await stripe.paymentIntents.create({
  amount: 1500,       // 1500 pence = £15.00
  currency: 'gbp',
  automatic_payment_methods: { enabled: true },
});

// Charge ¥3000 (JPY — zero-decimal currency)
const paymentIntentJPY = await stripe.paymentIntents.create({
  amount: 3000,       // 3000 yen (no cents)
  currency: 'jpy',
  automatic_payment_methods: { enabled: true },
});
```

**Expected result:** PaymentIntents are created in EUR, GBP, and JPY. Stripe converts to your settlement currency at the current exchange rate.

### 2. Use dynamic currency in Checkout Sessions

Pass the customer's preferred currency to your Checkout Session endpoint. Stripe displays the price in that currency.

```
app.post('/create-checkout-session', async (req, res) => {
  const { currency = 'usd', amount } = req.body;

  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [
      {
        price_data: {
          currency: currency.toLowerCase(),
          product_data: { name: 'Premium Widget' },
          unit_amount: amount, // In smallest unit of currency
        },
        quantity: 1,
      },
    ],
    success_url: 'https://yoursite.com/success',
    cancel_url: 'https://yoursite.com/cancel',
  });

  res.json({ url: session.url });
});
```

**Expected result:** The Checkout page displays the price in the specified currency with the correct symbol and format.

### 3. Enable Adaptive Pricing for automatic conversion

Stripe's Adaptive Pricing (for Checkout and Payment Links) automatically shows prices in the customer's local currency based on their location. Enable it in your Dashboard or when creating Prices.

```
// Create a Price with Adaptive Pricing enabled
const price = await stripe.prices.create({
  product: 'prod_xxx',
  unit_amount: 2000,          // Base price: $20.00 USD
  currency: 'usd',
  currency_options: {
    eur: { unit_amount: 1850 },  // Fixed: €18.50
    gbp: { unit_amount: 1600 },  // Fixed: £16.00
  },
});

// Or use the Price in a Checkout Session
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{ price: price.id, quantity: 1 }],
  // Stripe auto-selects currency based on customer location
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
});
```

**Expected result:** Customers in Europe see EUR pricing, UK customers see GBP, and others see USD or their local equivalent.

### 4. Handle zero-decimal currencies correctly

Some currencies like JPY, KRW, VND don't have sub-units (cents). The amount value IS the full amount. Build a helper to handle this.

```
const ZERO_DECIMAL_CURRENCIES = [
  'bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga',
  'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf',
];

function toStripeAmount(dollarAmount, currency) {
  if (ZERO_DECIMAL_CURRENCIES.includes(currency.toLowerCase())) {
    return Math.round(dollarAmount); // No multiplication needed
  }
  return Math.round(dollarAmount * 100); // Convert to cents
}

// Usage:
toStripeAmount(20, 'usd');  // 2000 (cents)
toStripeAmount(3000, 'jpy'); // 3000 (yen)
```

**Expected result:** The helper correctly converts display amounts to Stripe amounts for both regular and zero-decimal currencies.

## Complete code example

File: `multi-currency-server.js`

```javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();
app.use(express.json());

const ZERO_DECIMAL_CURRENCIES = [
  'bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga',
  'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf',
];

function toSmallestUnit(amount, currency) {
  const cur = currency.toLowerCase();
  if (ZERO_DECIMAL_CURRENCIES.includes(cur)) {
    return Math.round(amount);
  }
  return Math.round(amount * 100);
}

function fromSmallestUnit(amount, currency) {
  const cur = currency.toLowerCase();
  if (ZERO_DECIMAL_CURRENCIES.includes(cur)) {
    return amount;
  }
  return amount / 100;
}

// Multi-currency checkout
app.post('/create-checkout-session', async (req, res) => {
  const { displayAmount, currency = 'usd', productName = 'Widget' } = req.body;

  try {
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [
        {
          price_data: {
            currency: currency.toLowerCase(),
            product_data: { name: productName },
            unit_amount: toSmallestUnit(displayAmount, currency),
          },
          quantity: 1,
        },
      ],
      success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${req.headers.origin}/cancel`,
    });

    res.json({ url: session.url });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Multi-currency PaymentIntent
app.post('/create-payment-intent', async (req, res) => {
  const { displayAmount, currency = 'usd' } = req.body;

  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: toSmallestUnit(displayAmount, currency),
      currency: currency.toLowerCase(),
      automatic_payment_methods: { enabled: true },
    });

    res.json({ clientSecret: paymentIntent.client_secret });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(4000, () => console.log('Multi-currency server on port 4000'));
```

## Common mistakes

- **Treating zero-decimal currencies like regular currencies** — undefined Fix: For JPY, KRW, VND, and other zero-decimal currencies, the amount IS the full amount. Passing 2000 for JPY means ¥2,000 — not ¥20.00. Use a helper function to handle conversion.
- **Assuming settlement happens in the presentment currency** — undefined Fix: Stripe converts to your settlement currency (your bank account's currency) at the current rate. A €25 charge settles in USD if your bank is USD. Stripe charges 1% for currency conversion.
- **Hardcoding prices without considering exchange rates** — undefined Fix: If you set EUR prices as a simple conversion of USD, exchange rates will drift. Either use Adaptive Pricing or regularly update your currency_options with current rates.
- **Using uppercase currency codes** — undefined Fix: Stripe requires lowercase ISO 4217 codes: 'usd', not 'USD'. Always .toLowerCase() the currency before passing to Stripe.

## Best practices

- Always use lowercase ISO 4217 currency codes (usd, eur, gbp, jpy)
- Build a helper function that handles zero-decimal currency conversion
- Use Adaptive Pricing in Checkout for automatic localized pricing
- Create fixed currency_options on Prices for markets where you want specific pricing
- Account for Stripe's 1% currency conversion fee in your pricing
- Test with multiple currencies using card 4242 4242 4242 4242 in test mode
- Display the correct currency symbol on your frontend using Intl.NumberFormat

## Frequently asked questions

### How many currencies does Stripe support?

Stripe supports 135+ currencies for payment processing. The exact list depends on your country and payment methods. Check your Dashboard → Settings → Payment methods to see which currencies are enabled.

### What is the currency conversion fee?

Stripe charges a 1% fee on top of the standard processing fee when the presentment currency differs from your settlement currency. For example, if you charge in EUR but settle in USD, there's an extra 1% fee.

### Can I settle in multiple currencies?

Yes, if you add bank accounts in different currencies. For example, add a EUR bank account to settle EUR charges directly without conversion. Go to Settings → Bank accounts and scheduling.

### What are zero-decimal currencies?

Currencies like JPY, KRW, and VND don't have sub-units (cents). For JPY, amount: 5000 means ¥5,000. For USD, amount: 5000 means $50.00. Always check if a currency is zero-decimal before converting.

### Can RapidDev help with international payment setup?

Yes. RapidDev helps businesses set up multi-currency payment systems including localized pricing tables, automatic currency detection, exchange rate management, and multi-region settlement optimization.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-handle-multiple-currencies-in-stripe
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-handle-multiple-currencies-in-stripe
