payloadcms / payloadcms/payload

plugin-ecommerce: Stripe adapter hangs on Cloudflare Workers (workerd) — new Stripe() needs a fetch httpClient

Open Beginner friendly
#17,201 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the Bug

The Stripe payment adapter instantiates the SDK without an httpClient:

// packages/plugin-ecommerce/src/payments/adapters/stripe/initiatePayment.ts
// packages/plugin-ecommerce/src/payments/adapters/stripe/confirmOrder.ts
const stripe = new Stripe(secretKey, {
  apiVersion: apiVersion || '...',
  appInfo: appInfo || { ... },
})

With no httpClient, the Stripe Node SDK falls back to its default Node https-module (socket-based) client. That client does not work on Cloudflare Workers (workerd), where all outbound requests must go through fetch. The result: initiatePayment (and confirmOrder) hang indefinitely — no error, no timeout, the request never resolves.

The insidious part: this is invisible in local development. next dev runs on Node, where the default client works fine, so the entire checkout passes local testing. It only fails on the real Workers runtime (opennextjs-cloudflare preview or production), where checkout silently hangs.

Root cause

The Stripe Node SDK requires httpClient: Stripe.createFetchHttpClient() to run on Workers/edge runtimes. The adapter neither sets it nor exposes a way for the consumer to set it (stripeAdapter accepts secretKey / publishableKey / webhookSecret, but no httpClient or Stripe config passthrough).

Fix (verified end-to-end on workerd)

Adding the fetch HTTP client to both new Stripe() calls resolves it completely:

const stripe = new Stripe(secretKey, {
  httpClient: Stripe.createFetchHttpClient(),
  apiVersion: apiVersion || '...',
  appInfo: appInfo || { ... },
})

With this change, the full money path runs on workerd: initiatePayment → PaymentIntent + transaction, confirmOrder → order created + stock decremented. Without it, initiatePayment hangs forever.

Two possible directions:

  1. Detect a non-Node runtime and default to createFetchHttpClient(), or
  2. Expose an httpClient (or general Stripe config) option on stripeAdapter so consumers can inject it. This would also let people avoid patching the package to deploy on Workers.
Ruled out

Bumping compatibility_date (2025-08-15 → 2025-11-01) with nodejs_compat enabled does not fix it — it is specifically the HTTP client, not a missing Node polyfill.

Environment
  • payload 3.82.1, @payloadcms/plugin-ecommerce 3.82.1, stripe ^18.5.0
  • Deploy: @opennextjs/cloudflare → Cloudflare Workers (workerd), D1 database
  • compatibility_flags: ["nodejs_compat"]
  • Reproduces under opennextjs-cloudflare preview and in production; does not reproduce under next dev (Node).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/plugin-ecommerce/src/payments/adapters/stripe/initiatePayment.ts and confirmOrder.ts, then inspect how stripeAdapter options reach each Stripe constructor. Reproduce the checkout path under opennextjs-cloudflare preview or workerd rather than next dev. Done means both payment operations complete without hanging and the chosen fetch-client configuration is available to the adapter.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, typescript
Domain
backend, payments
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.