Skip to content

Cloudflare Workers fetch

Thin Workers fetch adapter over the checkout kernel. Tests use single-host SQLite; production must not.

Updated View as Markdown

@paykernel/example-cloudflare-workers-fetch is a private workspace host (private: true). It is not published. The checkout kernel owns checkout, inbox, and reconciliation. This package maps fetch.

Source: examples/cloudflare-workers-fetch (README). There is no Wrangler config and no index.ts in this example — tests in Bun only.

Mapping

createCloudflareCheckoutFetch:

import { handleCloudflareWebhook } from "@paykernel/integration-cloudflare-workers";
import {
  dispatchCheckoutRequest,
  type CheckoutFetchApp,
  type CheckoutHttpOptions,
  type CheckoutKernel,
} from "@paykernel/example-checkout-kernel";

export function createCloudflareCheckoutFetch(
  kernel: CheckoutKernel,
  options: CheckoutHttpOptions = {},
): CheckoutFetchApp {
  return {
    async fetch(req: Request): Promise<Response> {
      const url = new URL(req.url);
      if (req.method === "POST" && url.pathname === "/webhooks/stripe") {
        return handleCloudflareWebhook(req, {
          gateway: kernel.webhook.gateway,
          client: kernel.webhook.client,
          engine: kernel.webhook.engine,
          handler: kernel.webhook.handler,
        });
      }
      return dispatchCheckoutRequest(kernel, req, options);
    },
  };
}

handleCloudflareWebhook reads request.text(), request.headers, and URL.searchParams, then processWebhookHttp. Correlation id is x-request-id, then x-correlation-id, then cf-ray (resolveCorrelationId).

The helper only rejects non-POST with 405. Callers must guard the path (POST /webhooks/stripe) before delegating. Without that guard every POST on the Worker would hit the Stripe verifier. This example already checks pathname.

Other routes (/payments, /orders/:orderId, test hooks) go through dispatchCheckoutRequest (raw req.text() on the Stripe path is not used there because the path already branched).

Fulfillment is only kernel.webhook.handler after the inbox claim. Never fulfill in onWebhookVerified. Status codes come from mapInboxOutcome, not @paykernel/webhooks.

Tests use SQLite — not production Workers

runCheckoutHttpScenarios("cloudflare-workers", …) calls createCheckoutKernel() with the default in-memory Bun SQLite store. That is single-host and one process. Do not use this example’s SQLite in production Workers.

D1 ≠ Durable Objects ≠ Turso ≠ local SQLite. Pick one production store and migrate explicitly (never on import / every request).

Store Package Factories in source (not invented)
D1 @paykernel/store-d1 createD1PaymentStores({ db: env.PAYMENTS_DB, clock? }) after migrateD1Adapter(env.PAYMENTS_DB). Or createD1Stores({ executor, clock? }) with createD1Executor(db).
Durable Objects @paykernel/store-durable-objects createDoPaymentStores({ namespace: env.PAYMENTS_DO, sharding }). Never one global DO. Hash sharding needs bindHashPartitionLayout / ensureDoHashPartitionLayout.
Local SQLite @paykernel/store-sqlite What this example’s tests actually run. Not a Workers production store.

CreateCheckoutKernelOptions accepts stores | storeFactory | executor (SQLite-shaped executor builds createSqliteStores).

Routes

Method Path Notes
POST /payments dispatchCheckoutRequest
POST /webhooks/stripe handleCloudflareWebhook after path guard
GET /orders/:orderId Order book
POST /internal/reconcile Test hook. Unauthenticated. Do not deploy.
POST /internal/provider-paid Test hook. Unauthenticated. Do not deploy.
GET /internal/create-count Test hook. Unauthenticated. Do not deploy.

Shared failure paths: Checkout kernel. Non-POST on the webhook helper → 405 from handleCloudflareWebhook.

Run

bun test examples/cloudflare-workers-fetch

That is Bun + sqlite, not wrangler dev.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close