payloadcms/payload · error · Error

Currency is required.

Error message

Currency is required.

What it means

Thrown by Stripe initiatePayment when data.currency is falsy. Stripe requires a currency code (e.g. 'usd') to create a PaymentIntent, and the adapter also uses it to look up the correct price field per currency config. Omitting currency is an input-contract violation: the initiate request body must include currency.

Source

Thrown at packages/plugin-ecommerce/src/payments/adapters/stripe/initiatePayment.ts:30

export const initiatePayment: (props: Props) => NonNullable<PaymentAdapter>['initiatePayment'] =
  (props) =>
  async ({ data, req, transactionsSlug }) => {
    const payload = req.payload
    const { apiVersion, appInfo, secretKey } = props || {}

    const customerEmail = data.customerEmail
    const currency = data.currency
    const cart = data.cart
    const amount = cart.subtotal
    const billingAddressFromData = data.billingAddress
    const shippingAddressFromData = data.shippingAddress

    if (!secretKey) {
      throw new Error('Stripe secret key is required.')
    }

    if (!currency) {
      throw new Error('Currency is required.')
    }

    if (!cart || !cart.items || cart.items.length === 0) {
      throw new Error('Cart is empty or not provided.')
    }

    if (!customerEmail || typeof customerEmail !== 'string') {
      throw new Error('A valid customer email is required to make a purchase.')
    }

    if (!amount || typeof amount !== 'number' || amount <= 0) {
      throw new Error('A valid amount is required to initiate a payment.')
    }

    const stripe = new Stripe(secretKey, {
      // API version can only be the latest, stripe recommends ts ignoring it
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore - ignoring since possible versions are not type safe, only the latest version is recognised

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Send data.currency as a lowercase ISO 4217 code (e.g. 'usd') in the initiatePayment request body.
  2. Default the currency server-side from the store config if the client omits it, before calling initiate.
  3. Validate currency client-side and disable the pay button until selected.
  4. Ensure the currency exists in currenciesConfig so downstream price lookups also succeed.

Example fix

// before
await payments.initiatePayment({ data: { cart, customerEmail } })
// after
await payments.initiatePayment({
  data: { cart, customerEmail, currency: selectedCurrency },
})
Defensive patterns

Strategy: validation

Validate before calling

function resolveCurrency(value: unknown, fallback?: string): string {
  if (typeof value === 'string' && /^[a-z]{3}$/i.test(value)) return value.toLowerCase()
  if (fallback) return fallback.toLowerCase()
  throw new Error('A valid 3-letter currency code is required')
}

// before initiatePayment
data.currency = resolveCurrency(data.currency, storeDefaultCurrency)

Type guard

export function isCurrencyCode(value: unknown): value is string {
  return typeof value === 'string' && /^[a-z]{3}$/i.test(value)
}

Try / catch

try {
  await payments.initiatePayment({ data })
} catch (err) {
  if (err instanceof Error && err.message === 'Currency is required.') {
    // prompt the customer to select a currency
    return { ok: false, reason: 'currency-required' }
  }
  throw err
}

Prevention

When it happens

Trigger: initiatePayment is called without data.currency, or with currency set to null/empty string. The check runs before any Stripe call, so it fails fast.

Common situations: Frontend checkout form that doesn't submit the currency field; multi-currency store where the selected currency isn't propagated to the initiate call; default currency not set and client assumes server fills it in; currency stored under a different key (e.g. currencyCode).

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/5af86fff45bf75db. Report an issue: GitHub.