payloadcms/payload · critical · Error

Stripe secret key is required

Error message

Stripe secret key is required

What it means

Thrown at the top of the Stripe confirmOrder adapter when props.secretKey is falsy. The Stripe SDK cannot be constructed without a secret key, so the adapter refuses to proceed. This is a configuration error in the ecommerce plugin: the stripe adapter was instantiated without supplying a secretKey (typically read from STRIPE_SECRET_KEY env var).

Source

Thrown at packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts:29

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

    const customerEmail = data.customerEmail

    const paymentIntentID = data.paymentIntentID as string

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

    if (!paymentIntentID) {
      throw new Error('PaymentIntent ID is required')
    }

    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-03-31.basil',
      appInfo: appInfo || {
        name: 'Stripe Payload Plugin',
        url: 'https://payloadcms.com',
      },
    })

    try {

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Set STRIPE_SECRET_KEY (and ensure the plugin reads that exact variable) in every environment that runs payments.
  2. Verify the value is loaded: console.log(Boolean(process.env.STRIPE_SECRET_KEY)) before building the adapter.
  3. Pass secretKey explicitly when constructing the stripe adapter object so both initiatePayment and confirmOrder share it.
  4. Add a startup check in payload.config.ts that throws early if the key is missing rather than failing per-request.

Example fix

// payload.config.ts
import { stripePlugin } from '@payloadcms/plugin-ecommerce'

// before: relying on an unset var
stripePlugin({ stripe: { secretKey: process.env.STRIPE_KEY } })

// after: assert at boot, read the canonical var
if (!process.env.STRIPE_SECRET_KEY) {
  throw new Error('STRIPE_SECRET_KEY is required')
}
stripePlugin({ stripe: { secretKey: process.env.STRIPE_SECRET_KEY } })
Defensive patterns

Strategy: validation

Validate before calling

// At boot, before registering the stripe adapter
function resolveStripeSecretKey(): string {
  const key = process.env.STRIPE_SECRET_KEY
  if (!key || typeof key !== 'string') {
    throw new Error('STRIPE_SECRET_KEY is required to enable Stripe payments')
  }
  return key
}

// pass into the adapter
stripePlugin({ stripe: { secretKey: resolveStripeSecretKey() } })

Type guard

export function hasSecretKey(props: unknown): props is { secretKey: string } {
  return typeof (props as { secretKey?: unknown })?.secretKey === 'string' &&
    (props as { secretKey: string }).secretKey.length > 0
}

Try / catch

try {
  await payments.confirmOrder({ data: { paymentIntentID } })
} catch (err) {
  if (err instanceof Error && err.message === 'Stripe secret key is required') {
    // configuration error — do not retry; alert ops
    console.error('STRIPE_SECRET_KEY is not configured')
    return { ok: false, reason: 'misconfigured-stripe' }
  }
  throw err
}

Prevention

When it happens

Trigger: The plugin's stripe adapter is registered with a missing or empty secretKey (e.g. secretKey: process.env.STRIPE_SECRET_KEY where the env var is unset in the current environment). confirmOrder is then called on that adapter instance.

Common situations: Forgetting to set STRIPE_SECRET_KEY in a CI/staging/production environment; using a .env file that is not loaded; deploying with a different env var name than the config reads; passing secretKey only to initiatePayment but not confirmOrder; test runs that instantiate the plugin without mocking secrets.

Related errors


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