Skip to content

Repository files navigation

MonetaKit

Pricing & subscriptions as code, PSP-agnostic.
Author your pricing in a Planfile, plan/apply it to any payment service provider, then check entitlements, take usage and handle webhooks at runtime.

# the moneta CLI (single static binary) + the runtime SDK
npm i -g monetakit          # or: curl -fsSL monetakit.dev/install | sh
npm i @monetakit/sdk
# Planfile — a price with a `recurring` block is a subscription; without one, one-time.
feature "sso"   { type = "boolean" }
feature "seats" { type = "seat" }

product "pro" {
  name       = "Professional"
  managed_by = "self"

  entitlements = {
    sso   = true
    seats = { included = 5, overage = 800 } # 800 = per extra seat (smallest unit)
  }

  price "monthly" {
    amount    = 2000
    currency  = "usd"
    recurring = { interval = "month" }
  }
  price "annual" {
    amount    = 20000
    currency  = "usd"
    recurring = { interval = "year" }
  }
}

routing { usd = "stripe" } # one catalog, fanned out by currency -> PSP

provider "stripe" { mode = "test" }
moneta plan             # diff your Planfile against each PSP's live state (--json for tooling)
moneta apply            # sync (immutable price chains + grandfather migrations; archive, never delete)
moneta import           # import an existing PSP catalog into a Planfile
moneta usage --customer cus_x --feature apiCalls --qty 5   # report metered usage
moneta check --product pro --feature seats --usage seats=5  # evaluate an entitlement against the lock (--json)
moneta drift            # out-of-band catalog-edit timeline (from the webhook drift monitor)

What works today

  • Author + sync (Stripe). Products; subscription / one-time / metered prices (per-unit, fractional, and graduated / volume tiers); features (boolean / seat / meter with sum|count|last); entitlements — an entitlement overage auto-compiles to a metered overage price; currency → PSP routing. plan / apply with immutable price version chains + existing- subscriber grandfather migrations, archive-never-delete (prevent-destroy), resolved price ids written back to the lock.
  • Money rigor. The 139 Stripe-supported currencies × ISO 4217 decimals; compile-time amount validation (unknown currency, over-8-digits, amount/amount_decimal exclusivity, ≤12 dp); human-readable amounts in compile / plan that catch the zero-decimal trap (amount = 100, currency = "twd" shows as 1.00 TWD, not NT$100).
  • Runtime SDK@monetakit/sdk, TypeScript, zero-dependency and edge-native (Web Crypto + fetch), so the same code runs on Cloudflare Workers / Node / Bun / Deno, under Next.js / Hono / TanStack Start:
    • check(catalog, customer, feature) — the entitlement gate (reference impl; other languages validated by shared conformance vectors).
    • createCheckout(...), parseWebhook(...) (signature-verified), resolvePriceId(...).
  • React bindings@monetakit/react: <Gate feature="…">, useEntitlement, <PricingTable>, with feature names typed to your Planfile via moneta codegen (a mistyped feature is a compile error).
  • Self-managed recurring engine (planfiled) — run subscriptions on a pure gateway instead of Stripe Billing (skip its ~0.5–0.8% fee): a durable queue claims each due period, rates it (per-unit / graduated / volume / metered usage), charges the saved card off-session, and records it in an idempotent ledger. Backends: SQLite, Postgres, and Cloudflare D1 behind one conformance suite (Postgres uses FOR UPDATE SKIP LOCKED; SQLite/D1 an atomic UPDATE … RETURNING — writes serialize).
  • Drift detection. Out-of-band edits in Stripe are caught in real time from webhooks (Terraform can only poll), classified display (tolerable) vs structural — with the count of active subscriptions affected as the major-impact indicator — appended to a versioned ledger, shown by moneta drift.
  • Versioned output contract. moneta plan --json and the drift ledger conform to schema/plan.schema.json / schema/drift.schema.json (formatVersion, additive-only) — like Terraform's plan JSON, for CI / dashboards / other-language tooling.
// runtime, self-hosted at the edge (Hono / Workers shown; Next.js & TanStack use the same calls)
import { check, createCheckout, parseWebhookRequest } from "@monetakit/sdk";
import catalog from "./planfile.lock.json";

app.post("/webhooks/stripe", async (c) => {
  const event = await parseWebhookRequest(c.req.raw, c.env.STRIPE_WEBHOOK_SECRET); // verifies + normalizes
  // event.priceKey === "product:pro:price:monthly" (from the price's pac.key metadata) -> upsert your store
});

app.get("/api/export", (c) => {
  if (!check(catalog, { product: userPlan }, "sso").allowed) return c.json({ error: "upgrade" }, 402);
  // ...
});

Architecture: Go spine, TS downstream

The authoring format is HCL (declarative → parsed, not evaluated), so the toolchain spine is Go (native HCL, single static binary, Terraform-style). The compiled Planfile (a JSON IR/lock) is the neutral contract; everything downstream — the runtime SDK, per-language checks — derives from it.

Planfile ──(Go, the only HCL parser)──▶ compiled lock (JSON) ──▶ runtime SDK & tooling (any language)
Layer Language
moneta CLI + stripe adapter, planfile compiler, adapterkit, core; the planfiled engine (rating / engine / store / scheduler) Go
runtime SDK (check / createCheckout / parseWebhook) + @monetakit/react TypeScript (reference; other langs via conformance)
schema/ (IR, capabilities, plan, drift JSON Schema) neutral, single source of truth

Monorepo layout

cmd/moneta/       CLI: compile / plan / apply / import / usage / drift
cmd/webhookd/     test-mode webhook receiver + real-time drift monitor
planfile/         the Planfile format: IR, HCL compile/decompile, validate, currency/amount rules
adapterkit/       PaymentProvider + optional UsageReporter / WebhookParser / DriftDetector
adapters/stripe/  tier: core (stripe-go) — Read/Apply, meters, ParseWebhook, DetectDrift
core/             diff/plan, plan --json + drift ledger, id-writeback, migration ledger, affected-subs
schema/           ir / capabilities / plan / drift  (versioned neutral contract)
rating/           self-managed rater: per-unit / graduated / volume tiers + usage aggregation
engine/           period processor: rate + off-session charge for one subscription period
store/            durable queue + idempotent ledger — SQLite & Postgres (one conformance suite)
scheduler/        the engine loop: claim due -> ProcessPeriod -> record
cmd/planfiled/    the self-managed recurring engine daemon (SQLite/Postgres + HTTP + timer)
packages/sdk/     @monetakit/sdk — check / checkout / webhook / catalog (+ conformance vectors)
packages/react/   @monetakit/react — Gate / useEntitlement / PricingTable (types via moneta codegen)
examples/         reference/Planfile, hono-cloudflare/, vite-react/  (compiled to planfile.lock.json)
docs/             planfile/ (format), monetakit/ (product + output contract)
website/          planfile.dev + monetakit.dev docs sites (Kura; pull docs/ via content.sources)

Adapter tiers

  • core — bundled in the CLI, the reference, highest bar. Today: Stripe (via stripe-go).
  • official — first-party, installed on demand. Planned: PayPal.
  • community — third-party monetakit-adapter-*, built against @monetakit/adapter-kit and validated by the conformance suite.

Roadmap

  • Dunning & invoicing — retry failed ledger charges with backoff; emit invoices from the ledger.
  • PayPal adapter (Rule-of-Three for the adapterkit interface).
  • More runtime targets — Next.js / TanStack Start examples (the SDK already runs there).

Docs: monetakit.dev (product) · planfile.dev (format) — Kura sites in website/, sourced from docs/.

About

Pricing & subscriptions as code — author pricing in a Planfile, compile to a canonical lock, plan/apply to any PSP. Self-hostable, PSP-agnostic.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

Sponsor
SponsoredKunjungi sekarang
Promo