payloadcms/payload · error · Error

A valid customer email is required to make a purchase.

Error message

A valid customer email is required to make a purchase.

What it means

Thrown by Stripe initiatePayment when data.customerEmail is falsy or not a string. The adapter creates/lists a Stripe customer by email and records it on the transaction, so a valid email is mandatory. The check `!customerEmail || typeof customerEmail !== 'string'` rejects undefined, null, numbers, and objects.

Source

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

    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
      apiVersion: apiVersion || '2025-06-30.preview',
      appInfo: appInfo || {
        name: 'Stripe Payload Plugin',
        url: 'https://payloadcms.com',
      },
    })

    try {

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Collect and validate a customer email (RFC-light check) before enabling payment.
  2. Send data.customerEmail as a string (derive from req.user.email for logged-in users).
  3. Map your field name to customerEmail before calling initiatePayment.
  4. Require email server-side and return a clear validation error to the client if absent.

Example fix

// before
await payments.initiatePayment({ data: { cart, currency, customerEmail: req.user } })
// after
const customerEmail = req.user?.email ?? formData.email
if (typeof customerEmail !== 'string') throw new Error('Email required')
await payments.initiatePayment({ data: { cart, currency, customerEmail } })
Defensive patterns

Strategy: validation

Validate before calling

function resolveCustomerEmail(value: unknown, user?: { email?: string }): string {
  const email = typeof value === 'string' ? value : user?.email
  if (typeof email !== 'string' || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
    throw new Error('A valid customer email is required')
  }
  return email
}

data.customerEmail = resolveCustomerEmail(data.customerEmail, req.user)

Type guard

export function isCustomerEmail(value: unknown): value is string {
  return typeof value === 'string' && /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)
}

Try / catch

try {
  await payments.initiatePayment({ data })
} catch (err) {
  if (err instanceof Error && /valid customer email/.test(err.message)) {
    return { ok: false, reason: 'email-required', redirect: '/checkout?step=contact' }
  }
  throw err
}

Prevention

When it happens

Trigger: initiatePayment called without customerEmail, with an empty value, or with a non-string (e.g. an object {email}). Anonymous checkout where email was never collected.

Common situations: Guest checkout form missing email validation; email stored as user.email but the request sends the whole user object; SSR where the session user has no email; frontend reusing a logged-out state; email field named emailAddress vs customerEmail.

Related errors


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