@agentoria/paykit
v0.4.0
Published
A framework-agnostic, storage-agnostic payment toolkit — one PaymentProvider port with Stripe, 虎皮椒 (xunhupay), and manual adapters, currency/region routing, an idempotent order state machine, and receipts. Runs on Node, Cloudflare Workers, Deno, and Bun.
Maintainers
Readme
paykit
A framework-agnostic, storage-agnostic payment toolkit — one PaymentProvider port with
Stripe, 虎皮椒 (xunhupay), and manual adapters, currency/region routing, an idempotent order
state machine, and receipts. It runs unchanged on Node 18+, Cloudflare Workers, Deno, and Bun
because it uses only Web-standard APIs (fetch, Web Crypto, Request/Response).
Take a payment (China + overseas), record an order, settle a webhook idempotently, issue a receipt — without wiring it up from scratch in every project.
Install
npm install @agentoria/paykitQuick start
Wire a store, the providers you've configured, and a onPaid hook — that's it. paykit records
the money; your onPaid applies its meaning (grant a plan, add credits, ship the thing).
import { PaymentGateway } from "@agentoria/paykit";
import { manualProvider } from "@agentoria/paykit/providers/manual";
import { MemoryOrderStore } from "@agentoria/paykit/stores/memory";
const gateway = new PaymentGateway({
store: new MemoryOrderStore(), // or @agentoria/paykit/stores/d1 | /sqlite | /postgres
providers: [manualProvider], // add stripe / xunhupay / router
env: process.env, // providers read STRIPE_* / XUNHUPAY_* here
hooks: {
async onPaid(order) {
// fires exactly once per order, even under duplicate webhooks
await grantPlan(order.buyerId, order.item.sku);
},
},
});
// 1) open an order + start payment
const checkout = await gateway.checkout(
{ buyer: { id: "u1", email: "[email protected]" }, item: { sku: "pro", label: "Pro — Yearly" }, amount: { amount: 1999, currency: "USD" } },
{ baseUrl: "https://app.example.com" },
);
// → { orderId, orderNo, provider, redirectUrl? | qrCode? | manual? }
// 2) settle the gateway's webhook (idempotent; verifies signature + amount)
const res = await gateway.handleWebhook("stripe", request);
return new Response(res.ack, { status: res.ok ? 200 : 400 });Design
- One port, many gateways. A
PaymentProviderimplementsconfigured / createCheckout / parseWebhook / refund?. Adding a channel is a new file + one registry entry. - Storage-agnostic. Every persistence port (
OrderStore,CouponStore,InvoiceStore,AnomalyStore,OrderEventLog) ships an in-memory adapter plus SQL-backed adapters over a tinySqlDriver—@agentoria/paykit/stores/d1and@agentoria/paykit/stores/sqlite(d1CouponStore(env.DB),sqliteInvoiceStore(db), …). Bring any DB by implementing three methods; create every table in one idempotent call —d1MigrateAll(env.DB)/sqliteMigrateAll(db)(ormigrateAll(driver)) — or ship the exported*_SCHEMA. - Idempotent settlement. The order state machine (
pending → paid | canceled | expired,paid → refunded) is guarded at the store, so a webhook delivered twice settles once andonPaidfires once. - Region routing. A
routermeta-provider sends CNY → 虎皮椒 (WeChat/Alipay) and everything else → Stripe, so one deployment serves mainland China and overseas at once. - Hooks, not coupling. paykit never touches your users/plans tables — it calls
onPaid/onRefundedand hands you the order.
Money
Amounts are always integers in a currency's minor units (1999 = $19.99) + an ISO-4217
code. Never floats — what you charge always equals what you record. money(19.99, "USD") and
formatMoney({ amount: 1999, currency: "USD" }) help at the edges.
Roadmap
paykit is built in tiers; subpaths land as each ships.
- Tier 0 — core ✅
PaymentGateway, order state machine,PaymentProvider+OrderStoreports,manualprovider, in-memory store, hooks, Web-Crypto helpers. - Tier 1 — providers + routing ✅
stripe+xunhupay(虎皮椒) providers (real HMAC/MD5 webhook verification + refunds), currency/regionrouter(CNY → 虎皮椒 / else → Stripe), a portable MD5, and SQL stores (sql+d1+sqlite). - Tier 2 — framework glue + receipts ✅ a one-line
honomount —billingRoutes(config / checkout / orders / webhooks) or the fullercreateBillingRouter, which adds an opt-in block per store you pass (receipts + scan-verify, coupon preview + admin CRUD, invoice request + admin resolve/issue, anomaly reconcile), each/admin/*route gated by yourisAdmin— plus a framework-neutral webhook handler and a framework-neutral core router (@agentoria/paykit/web—createBillingHandler, a(Request) => Response | nullcovering config / checkout / orders / cancel / mock-pay / webhook, for a catch-all route in Astro / Next / Deno / Bun without pulling inhono); receipts (@agentoria/paykit/receipts) — issue from a paid order, merchant config, self-contained HTML, HMAC verify token; and a one-page A4 PDF renderer (@agentoria/paykit/receipt-pdf) with a verify QR + SHA-256 fingerprint —pdf-lib/qrcodeare optional peer deps and the CJK font is caller-supplied, so the core stays dependency-free. - Tier 3 — billing 🚧 Coinbase (crypto) provider (
@agentoria/paykit/providers/coinbase) ✅, discount coupons (@agentoria/paykit/coupons— pure pricing + aCouponStoreport) ✅, upgrade proration (@agentoria/paykit/prorate) ✅, tax-invoice requests + issue seam (@agentoria/paykit/invoices) ✅, and SQL adapters for the coupon + invoice stores (SqlCouponStore/SqlInvoiceStoreoverd1+sqlite) ✅. Still to come: subscriptions + renewal, apostgresstore. - Tier 4 — ops 🚧 order-lifecycle audit log (
@agentoria/paykit/order-events) ✅, payment-anomaly queue (@agentoria/paykit/anomalies) ✅, revenue rollup (@agentoria/paykit/metrics—summarizeOrders) ✅, and SQL adapters for the anomaly log + order-event log (SqlAnomalyStore/SqlOrderEventLog) ✅. Still to come: admin/finance helpers, docs + examples. - Tier 5 — client + UI 🚧 a framework-agnostic
@agentoria/paykit/client(a typedfetchwrapper over the mounted router — checkout, orders, coupon preview, receipt verify, invoices, admin) ✅, and headless@agentoria/paykit/reacthooks (useCheckout/useOrders/useCouponPreview/useCoupons) withreactas an optional peer dep ✅. Still to come: styled default components, more framework bindings.
License
MIT © WangYihang
