payloadcms / payloadcms/payload
plugin-ecommerce: Stripe adapter hangs on Cloudflare Workers (workerd) — new Stripe() needs a fetch httpClient
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:
- Detect a non-Node runtime and default to
createFetchHttpClient(), or - Expose an
httpClient(or general Stripe config) option onstripeAdapterso 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
payload3.82.1,@payloadcms/plugin-ecommerce3.82.1,stripe^18.5.0- Deploy:
@opennextjs/cloudflare→ Cloudflare Workers (workerd), D1 database compatibility_flags: ["nodejs_compat"]- Reproduces under
opennextjs-cloudflare previewand in production; does not reproduce undernext dev(Node).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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