PayKernel ships private workspace apps under examples/. They are consumer hosts, not SDK packages: private: true, never published. packages/* must not import examples/*.
The shared kernel is @paykernel/example-checkout-kernel. Five thin HTTP hosts map routes onto it. SDK packages used here are published (core 1.0.0, others 0.1.1) — bun add @paykernel/core works. Examples themselves are private (not published).
Hosts
| Directory | Package | Docs |
|---|---|---|
checkout-kernel |
@paykernel/example-checkout-kernel |
Checkout kernel |
bun-hono-sqlite |
@paykernel/example-bun-hono-sqlite |
Bun + Hono + SQLite |
bun-hono-postgres |
@paykernel/example-bun-hono-postgres |
Bun + Hono + Postgres |
bun-elysia-sqlite |
@paykernel/example-bun-elysia-sqlite |
Bun + Elysia + SQLite |
express-sqlite |
@paykernel/example-express-sqlite |
Express + SQLite |
cloudflare-workers-fetch |
@paykernel/example-cloudflare-workers-fetch |
Cloudflare Workers fetch |
Source index: examples/README.md.
Honesty
- Charges use the server catalog amount (
money("10.00", "USD")). Client JSONamountis ignored. Provider recon snapshots usegetPaymentmoney, neverorder.amountfrom the client. createPaymentgoes through the mock gateway. Stripe is wired for webhook verify only. Stripe metadataorderIdcannot fulfill a mock-charged order whose storedgatewayPaymentIddiffers.- HTTP status codes live in
@paykernel/integration-http(mapInboxOutcome), not in@paykernel/webhooks. The kernel re-exportsmapInboxOutcome. - Fulfillment runs only after an inbox claim, and only when the rematched event is
payment.succeededorcapture.completedandpayment.status === "paid", bound togatewayPaymentId. Never fulfill inonWebhookVerified. success: trueis not the fulfillment signal. Afteroutcome === "indeterminate"orreconciliationRequired, do notcreatePaymentagain. Lookup +decideReconciliationPolicyonly.- Webhook routes must read the raw body (
text()/express.raw), neverjson(), before verify. See Webhooks.
Shared routes
Hosts stay thin: createCheckoutKernel(), then createCheckoutHandlers(kernel) plus JSON helpers, or createCheckoutFetchApp / dispatchCheckoutRequest for a framework-less fetch.
| Method | Path | Notes |
|---|---|---|
POST |
/payments |
Create order + mock charge. Catalog amount only. Duplicate orderId → 409. |
POST |
/webhooks/stripe |
Raw body + stripe-signature. Status from mapInboxOutcome. |
GET |
/orders/:orderId |
Order book lookup (200 / 404). Kernel dispatch uses this path; examples/README.md writes /orders/:id. |
POST |
/internal/reconcile |
Test hook. Unauthenticated. Do not deploy. |
POST |
/internal/provider-paid |
Test hook. Injects a paid provider snapshot. Do not deploy. |
GET |
/internal/create-count |
Test hook. createPayment call count. Do not deploy. |
Without enableTestHooks: true, the three /internal/* handlers return 404 { "error": "not_found" }.
Kernel helpers
Do not reimplement mapInboxOutcome or Stripe HMAC. Import from the kernel:
import {
createCheckoutKernel,
createCheckoutHandlers,
createCheckoutFetchApp,
mapInboxOutcome,
signedStripePaidWebhook,
signStripeWebhook,
runCheckoutHttpScenarios,
} from "@paykernel/example-checkout-kernel";Webhook hosts pass raw text into the kernel / integration helper (processWebhookHttp: verify-only handleWebhook, then inbox claim; fulfillment only in the inbox handler):
const raw = await request.text(); // Hono: never c.req.json()
const sig = request.headers.get("stripe-signature");
await handlers.handleStripeWebhook(raw, sig);Hono / Elysia / Express / Workers examples use the matching @paykernel/integration-* helper instead of calling handleStripeWebhook themselves. Same kernel webhook.handler.
Run
From the repository root (workspace packages, not npm):
bun install
bun test examples
# or
bun run test:examplesRoot lint includes examples/*/src/**/*.ts. Root typecheck includes @paykernel/example-checkout-kernel only; the thin hosts have their own typecheck scripts. Production composition (Postgres, inbox, reconcile) is in Getting started, not these hosts.
Related
- Checkout kernel — composition, fulfillment, recon, fixtures
- Outcomes —
isPaidOutcome/ indeterminate - Money — catalog amounts
- Adapter selection — SQLite vs Postgres vs D1 vs Durable Objects
- Stores: SQLite · Postgres · D1 · Durable Objects