@agentoria/plankit
v0.4.1
Published
A framework-agnostic subscriptions & growth toolkit — plans, entitlements, metering/overage, referrals, coupons, gifting, activation, and dunning — layered on paykit. Runs on Node, Cloudflare Workers, Deno, and Bun.
Maintainers
Readme
plankit
A framework-agnostic subscriptions & growth toolkit, layered on paykit.
paykit moves the money (providers, orders, webhooks, receipts, invoices). plankit decides who gets what and grows it — plan catalogs, subscription lifecycle, entitlements/quota, usage metering & overage, and the growth loops (referrals, coupons, gifting, activation, dunning). Storage-agnostic ports with in-memory adapters and Web-standard APIs, so one build runs on Node 18+, Cloudflare Workers, Deno, and Bun.
paykit ──▶ plankit ──▶ your app
(payments) (plans + growth)Install
npm add @agentoria/plankit @agentoria/paykit # paykit is a peer dependencyQuick start
import { PlanCatalog } from "@agentoria/plankit/plans";
import { InMemorySubscriptionStore, Subscriptions } from "@agentoria/plankit/subscriptions";
const catalog = new PlanCatalog([
{ id: "free", label: "Free", price: 0, currency: "USD", interval: "none", includedUsd: 100 },
{ id: "pro-monthly", label: "Pro", price: 1900, currency: "USD", interval: "monthly", includedUsd: 2000, features: ["sso"] },
]);
const subs = new Subscriptions(new InMemorySubscriptionStore(), catalog);
// Bridge from paykit: grant the plan when a payment settles.
const gateway = new PaymentGateway({
// …providers, store…
hooks: { onPaid: async (order) => { await subs.grantFromOrder(order); } },
});
await subs.get("org-1"); // → the free plan by default
await subs.changePlan("org-1", "pro-monthly"); // upgrades, carrying unused time via paykit's proration
await subs.expireSweep(); // cron: lapse ended periods (→ free / past_due for dunning)Design
- Layered on paykit, one-way. plankit depends on paykit for the money-moving primitives (open a
checkout, settle a webhook,
carryOverMsproration, coupon pricing); paykit never knows about plans. An app that only takes one-off payments uses paykit alone. - Subject-agnostic. A subscription is keyed by an opaque
subjectId— an org id, a user id, whatever your app scopes tenancy on. The same code serves per-org SaaS and per-user apps. - Ports + adapters. Every store is an interface with an in-memory adapter and SQL-backed adapters
over paykit's
SqlDriver—@agentoria/plankit/stores/d1and@agentoria/plankit/stores/sqlite(d1SubscriptionStore(env.DB),sqliteGrantStore(db), …). One D1 / SQLite connection backs both paykit's and plankit's tables; call.migrate()or ship the exported*_SCHEMAconstants as migrations.
Monetization models
Four ways to charge, one per quadrant — pick what your product sells (or mix them):
| | prepaid (buy first) | postpaid (use, then bill) |
| ---------- | ---------------------------------- | -------------------------------------- |
| by time | access-window (buy a duration) | subscriptions (recurring periods) |
| by units | credits (a token / points wallet) | metering (usage → overage / PAYG) |
Roadmap
- Plans + subscriptions ✅
@agentoria/plankit/plans(catalog, price/period helpers) +@agentoria/plankit/subscriptions(lifecycle: grant /grantFromOrderbridge / change-plan with proration / renew / cancel / expire sweep). - Access windows ✅
@agentoria/plankit/access-window— the prepaid-time model: buy a duration of a plan, access runs until an expiry extended additively by each purchase (grantMonths/grantDays/grantFromOrder,isActive,expireSweep). No card-on-file — you buy time. - Credits ✅
@agentoria/plankit/credits— the prepaid-units model: a wallet of a per-periodallowanceplus non-expiringbonustop-ups;deductguards the balance atomically,resetrefreshes the allowance and realises the bonus spent,grantFromOrderbridges a credit-pack purchase. - Entitlements + metering ✅
@agentoria/plankit/entitlements(feature flags + numeric quotas with a usage store —consumefor the simple case, orreserve()→{ commit, release }to gate an operation that might fail, plusrefundto hand a unit back) and@agentoria/plankit/metering(cumulative usage → overage charges through paykit, watermarked so it never double-bills;advanceFromOrderbridges theonPaidsettlement). - Growth ✅
@agentoria/plankit/referrals(invite codes, attribution, mutual reward on conversion with refund rollback),@agentoria/plankit/activation(codes → plan grants),@agentoria/plankit/gifting(claimable plan grants). Coupons live in@agentoria/paykit/coupons— the pricing primitive belongs at the payment boundary. - Dunning ✅
@agentoria/plankit/dunning— grace period + backoff retries + notifications on a failed renewal; recovers on a successful retry, hands off toonExhausted(downgrade) when the schedule is spent. - SQL storage adapters ✅
@agentoria/plankit/stores/{sql,d1,sqlite}— durableSqlDriver-backed adapters for every port (subscriptions, overage, quota usage, dunning, activation, gifting, referrals), sharing one connection with paykit's stores. Create every table in one idempotent call —d1MigrateAll(env.DB)/sqliteMigrateAll(db)(ormigrateAll(driver)). Apostgresadapter is still to come. - Framework glue ✅
@agentoria/plankit/hono— a one-linecreateGrowthRoutermount with an opt-in block per manager: subscription (get / change-plan / cancel / renew), entitlement reads, referral (code + stats + attribution), activation redeem + admin catalog, gifting (claim + admin mint). Admin routes are gated by yourisAdmin; because plankit never applies plan grants itself, the activation + gifting blocks take anapplyGrantcallback — the seam where your app upgrades the subject.honois an optional peer dependency. For non-Hono hosts,@agentoria/plankit/web—createGrowthHandler, a framework-neutral(Request) => Response | nullover the subscription + entitlement core — drops into any catch-all route (Astro / Next / Deno / Bun) with nohonodependency. - Client + React ✅ a framework-agnostic
@agentoria/plankit/client(a typedfetchwrapper over the growth router — subscription, entitlements, referral, activation redeem, gifts, admin) and headless@agentoria/plankit/reacthooks (useSubscription/useEntitlement/useReferral/useActivation/useGifts) carrying loading/error state and nothing else.reactis an optional peer dep. Still to come: styled default components.
License
MIT © WangYihang
