Skip to content

Releases: recurso-dev/recurso

v0.6.0 — The usage-based billing release

Choose a tag to compare

@swapnull-in swapnull-in released this 18 Jul 18:26

The usage-based billing release: everything between a usage event and the
money it becomes — billable metrics, tiered pricing, unattended renewals,
prepaid wallets, minimum commitments, usage alerts, batch ingestion, an
append-only audit trail — plus the first wave of ecosystem integrations
(GoCardless, Adyen, NetSuite, Avalara, HubSpot, S3 export) shipped as
experimental pending sandbox certification.

Added

  • HubSpot CRM sync (EXPERIMENTAL) — a daily worker upserts every
    customer as a HubSpot contact (keyed by email) carrying
    recurso_customer_id and recurso_subscription_state
    (active/churned). Private-app token via HUBSPOT_ACCESS_TOKEN;
    SaaS egress, so blocked under RESIDENCY_MODE=self_hosted.
    Salesforce follows as its own spec (JWT auth design needed).

  • NetSuite accounting sync (EXPERIMENTAL) — a SuiteTalk REST adapter
    in the existing accounting-sync framework (customer / invoice / item
    upserts with Location-header id capture, ErrExternalGone remapping,
    per-line major-unit amounts). Residency-guarded like QuickBooks/Xero;
    connect with provider netsuite (RealmID = NetSuite account id).

  • Avalara AvaTax provider (EXPERIMENTAL) — US sales-tax quotes via
    uncommitted SalesOrder transactions (AVALARA_ACCOUNT_ID /
    AVALARA_LICENSE_KEY / AVALARA_COMPANY_CODE), sharing TaxJar's error
    taxonomy, 24h rate cache, and the RESIDENCY_MODE=self_hosted guard.
    TaxJar takes precedence when both are configured.

  • S3 finance export (EXPERIMENTAL) — a daily worker ships each
    tenant's general ledger CSV to operator-owned object storage
    (S3_EXPORT_BUCKET/REGION/PREFIX, S3_EXPORT_ENDPOINT for
    MinIO/R2), signed with a dependency-free SigV4 client. Idempotent keys
    per day; disabled unless configured.

  • Automation & Okta docs — Zapier/n8n recipes over the signed webhook
    surface and the Okta SAML SSO walkthrough (Track D6/D7 ride existing
    code).

  • GoCardless + Adyen gateways (EXPERIMENTAL) — bank-debit rails
    (SEPA/BACS via mandate-first billing requests) and global card
    processing (Checkout Sessions, off-session stored-method charges), both
    behind GATEWAY_CURRENCY_OVERRIDES (e.g. EUR=gocardless,SGD=adyen)
    with the existing INR→Razorpay / default→Stripe routing untouched.
    httptest-verified request shapes and idempotency keys; sandbox
    verification pending (founder-gated).

  • Append-only audit trail — every successful config-grade mutation
    (plans, metrics, charges, coupons, webhooks, wallets, alerts, team,
    settings, ...) is recorded via middleware with actor (dashboard user or
    API key), route, entity, and the truncated request payload.
    GET /v1/audit-logs filters by entity/actor/time; the table rejects
    UPDATE and DELETE at the database level.

  • Ingestion hardeningPOST /v1/usage/events/batch ingests up to
    500 events with per-item results, and events accept an optional
    transaction_id idempotency key: a retried event with the same
    (subscription, transaction_id) collapses to the original, so SDK retries
    can never inflate usage. A covering index on
    (subscription_id, dimension, timestamp) backs the aggregation hot path.

  • Usage threshold alerts/v1/usage-alerts configures per-
    subscription thresholds on a billable metric (absolute quantity or
    percent of the matching entitlement limit). The billing-cycle sweep
    evaluates them and fires AT MOST once per billing period per threshold —
    a conditional claim dedups concurrent sweeps — emitting the
    usage.alert.triggered webhook event plus an email to the customer.

  • Minimum commitmentsPUT /v1/subscriptions/{id}/commitment sets a
    per-period floor (minor units); when a period's subtotal (flat + add-ons

    • metered usage) falls short, a "Minimum commitment true-up" line fills
      exactly the difference, taxed at the plan HSN. The usage-amount preview
      reports the projected true-up. Overage needs nothing: usage past the
      floor bills naturally.
  • Prepaid wallets — money-denominated stored value per
    customer+currency (/v1/wallets, top-ups, transactions, auto-recharge).
    Invoices drain the wallet FIRST (wallet → credit notes → gateway), with
    residues consumed oldest-expiring-first so dated promotional credit is
    spent before it lapses; expired residue is written off by the sweep.
    Every movement is append-only with balance-after and posts balanced
    ledger legs (DR Cash / CR Customer Credit on top-up; DR Customer Credit /
    CR AR on drain). Auto-recharge tops up below-threshold wallets from the
    saved payment method on the billing-cycle tick.

  • Unattended renewals — a billing-cycle scheduler claims due,
    locally-billed subscriptions (leased at-most-once claims), generates the
    renewal invoice (flat fee in advance + metered usage in arrears),
    advances the period anchor-preservingly, and best-effort charges the
    saved payment method; declines flow to dunning. BILLING_CYCLE_INTERVAL
    configures the tick (default 5m, 0 disables). cancel_at_period_end
    subscriptions get their final usage rated, then cancel.

  • Metered mandate debits — UPI-mandate debit invoices now carry the
    subscription's rated usage lines, and the subscription billing period
    advances with each cycle (it previously never advanced for mandate
    subscriptions, leaving current-period usage reporting stale). Usage that
    would exceed the mandate's authorized ceiling is billed on a separate
    open invoice instead of over-charging.

  • SDK metering methods — Node recurso@1.3.0, Go v1.1.0, and Python
    1.2.0 expose billable metrics, plan charges, the usage-amount preview,
    and event properties.

  • Usage-based billing v1 (docs/spec_usage_billing.md) — the metering
    engine: billable metrics (POST/GET/PUT/DELETE /v1/billable-metrics)
    aggregate usage events by count, sum, max, or unique (distinct
    values of an event property); charges attach per_unit, graduated,
    volume, or package pricing for a metric to a plan
    (PUT/GET /v1/plans/{id}/charges), with per-currency amounts and
    sub-minor-unit decimal rates (0.0035/call) computed exactly and rounded
    once per line; rating at period close bills the elapsed period's
    usage in arrears on the renewal invoice as tax-resolved line items, with
    a usage_ratings window claim making retried generation idempotent;
    immediate cancellation bills the partial window on a final usage invoice.
    GET /v1/subscriptions/{id}/usage-amount previews the running period,
    and POST /v1/usage/events accepts optional properties.

v0.5.0 — The India release

Choose a tag to compare

@swapnull-in swapnull-in released this 18 Jul 18:26

The India release: the full statutory lifecycle (invoice → IRN → GSTR-1/3B →
TDS) from a billing engine you self-host, plus the correctness and security
hardening from a deep multi-agent review of the money paths.

Added

  • GST returns, return-readyGET /v1/india/gstr1 and
    GET /v1/india/gstr3b assemble both returns from the period's finalized
    invoices and credit notes: GSTR-1 with B2B/B2CS/CDNR sections and the HSN
    rollup; GSTR-3B as the net summary (Table 3.1(a) net of credit notes,
    Table 3.2 inter-state unregistered by place of supply, purchase-side
    sections as explicit zeros for the CA). Each response carries a
    gov_schema object in the official GSTN JSON shape — government field
    names, amounts in rupees — ready to validate with the Returns Offline Tool
    (ENG-203).
  • Self-hosted data residencyRESIDENCY_MODE=self_hosted hard-disables
    every optional third-party egress: telemetry (even when opted in),
    QuickBooks/Xero sync (the connect flow and existing connections), and the
    TaxJar API. Financial data leaves the deployment only through the payment
    gateways, GSP, SMTP, and webhook endpoints the operator configures.
    docs/india-data-residency.md states the guarantee for security reviews;
    every enforcement point is unit-tested.
  • TDS record-on-receipts — offline payments accept tds_amount for the
    portion a B2B customer withheld at source. It counts toward settling the
    invoice, accumulates on invoices.tds_amount, and posts
    DR TDS Receivable / CR AR in the ledger with the cash leg net of TDS.
  • Provable-ledger auditor outputs — trial balance, deferred-revenue
    rollforward, GL export, and the revenue-recognition waterfall, wired into
    the dashboard's Finance sidebar (ENG-192, ENG-194); deeper analytics
    endpoints (MRR waterfall, invoice aging, unit economics, revenue by plan
    and geography).
  • Real Redis infrastructure — distributed locking and idempotency backed
    by Redis, with REQUIRE_REDIS to fail closed on multi-instance
    deployments; the lock's mutual exclusion is proven by test (ENG-161,
    ENG-193).
  • Inbound webhook idempotency for Stripe and Razorpay events (ENG-162),
    and team email invites where teammates set their own password
    (ENG-196).
  • Official Go SDK — hand-crafted, stdlib-only recurso-go covering the
    full API surface.
  • Demo-data seeder (cmd/demo_seed) — additive, tenant-scoped, with
    --reset, covering rev-rec, referrals, gifts, and reconciling ledger
    postings.

Changed

  • SDKs moved to standalone repositoriesrecurso-go, recurso-node
    (1.2.0, responses/requests typed from the OpenAPI spec), and
    recurso-python (1.1.0, regenerated; unexpected API statuses now raise
    instead of returning None). The in-repo sdk/ directory and its CI job
    are gone; install/publishing docs point at the new repos.
  • Ledger documentation now states plainly: PostgreSQL is authoritative,
    TigerBeetle is an optional mirror.
  • Internal business material (pitch deck, review reports) moved out of the
    repository.

Fixed

  • Billing correctness family (ENG-140–154) — revenue is deferred NET of
    GST and unwound on cancel/refund/downgrade; signed double-entry balances;
    atomic trial-conversion billing; downgrade credits reverse GST and persist
    as adjustment credit notes applied at billing time; account credit is a
    real liability; cash postings are net of applied credit; first-period
    proration; no phantom revenue on UPI-Autopay debits.
  • Month-end billing dates — interval math clamps instead of overflowing
    (Jan 31 + 1 month = Feb 28, never Mar 3), and anchored subscriptions
    restore their day in long months (Feb 28 → Mar 31) instead of sticking at
    28.
  • Atomic-claim sweep (ENG-160–200, PHASE2) — one-shot semantics enforced
    by conditional updates everywhere a retry or race could double-fire:
    mandate debits (with the idempotency key proven to reach the gateway),
    trial activation, dunning steps and bandit weights, e-invoice IRN retries,
    gift redemption, quote→invoice conversion, cancel-flow retention offers,
    virtual-account credits, refund over-issue, and idempotency-key claims
    themselves; downgrade credit + plan flip commit in one transaction; the
    IRN is registered only after the invoice durably commits.
  • Graceful shutdown — background workers drain under a bounded
    WaitGroup; all schedulers stop concurrently and idempotently (a double
    Stop previously deadlocked the exiting process); in-flight webhook
    deliveries respect cancellation.
  • Tax fixes — CGST/SGST always split into equal halves; non-EU B2C
    digital exports are zero-rated instead of falling back to domestic VAT;
    export exemption requires an actual cross-border supply (GB→GB regression);
    single-digit GST state codes resolve; GST/VAT rounds instead of
    truncating.
  • Tenant isolation (ENG-157–169) — repository-level tenant scoping for
    handler-reachable mutations, invoice settlement, and mandate writes;
    offline payments settle an invoice only when covered and only for the
    matching customer/currency.

Security

  • Stripe webhook verification fails closed when the secret is unset;
    outbound webhook URLs are SSRF-guarded (ENG-175, ENG-177).
  • Portal session tokens hashed at rest; auth tokens atomically single-use;
    TOTP codes can't be replayed; per-account login lockout (ENG-145,
    ENG-151, ENG-176).
  • Owner role protected from admin privilege escalation; API-key creation
    gated; multiple cross-tenant IDORs closed (ENG-164, ENG-165, ENG-178,
    ENG-160).
  • O(1) API-key lookup replaces the bcrypt-per-key scan; trusted proxies
    configured so X-Forwarded-For can't bypass rate limits; /health no
    longer leaks connection errors (ENG-174, ENG-197, ENG-198).

v0.4.0

Choose a tag to compare

@swapnull-in swapnull-in released this 18 Jul 18:26

Added

  • Real hosted checkout — server-verified card/ACH collection via the Stripe
    Payment Element and UPI/cards/netbanking via Razorpay Checkout, smart-routed by
    currency (INR → Razorpay, else Stripe) (ENG-4).
  • Customer self-service portal — magic-link login, card update via Stripe
    SetupIntent, UPI mandate re-authorization, and invoice history; payment-recovery
    emails deep-link straight into it (ENG-5).
  • Smart dunning depth — off-session saved-card retries, retries settled
    through the double-entry ledger, and recovery deep-links (ENG-5).
  • US economic-nexus tracking — per-state year-to-date sales/transaction
    tracking that auto-establishes economic nexus when a threshold is crossed;
    GET /v1/settings/tax/nexus/status reports proximity. Dataset seeded
    uncertified pending professional review (ENG-16).
  • Dashboard redesign — shadcn foundation with stone design tokens, a
    monospace Money signature, a ⌘K command palette, and a Test-mode chip
    (ENG-135, ENG-136).
  • Cloud waitlist — API-backed early-access signup (POST /waitlist) (ENG-12).
  • Jurisdiction-aware invoice PDFs wired to real invoices.

Changed

  • API keys are now mode-gated (test vs. live). Newly created keys are issued
    as rsk_test_ (test) or rsk_live_ (live), and a key's mode must match the
    server's gateway_mode (reported at GET /version): a test key is accepted on
    a none/test server, a live key on a live server. Mismatches return
    401 key_mode_mismatch, so a test key can never move real money. New accounts
    get a test key by default; mint a live key with
    POST /v1/developer/keys {"mode":"live"}.

    Breaking: existing generated sk_live_ keys are grandfathered as live and
    keep working against a live-gateway server — but a request from such a key to a
    non-live server (the mock none gateway or gateway test mode) now returns
    401 key_mode_mismatch. If you develop against a non-live instance with an old
    sk_live_ key, either mint an rsk_test_ key (Settings → API Keys, or the
    developer-keys endpoint) or configure live gateway keys. The demo key
    sk_test_12345 is unaffected — it is grandfathered as a test key.

Fixed

  • Tenant-context audit — features that never worked against a real
    multi-tenant database: trial conversion (ENG-3), plan changes that silently
    never persisted (ENG-6), and four more background paths (ENG-134), plus
    tenant_id propagation across 11 handlers and silently-zero consolidated MRR.
  • Background-job robustness — NULL / timestamptz scans that aborted the
    nexus, churn, pre-charge, dunning-retry, and accounting sweeps against real
    data (ENG-143).
  • Dashboard — page crashes (Security, Referrals/Gifts on empty data), sparse
    or broken detail panels (Customer, Quote, Credit Note, Coupon), and
    non-functional create buttons/routes.
  • Live-key payment fixes — Razorpay UPI-Autopay registration payload, Stripe
    inactive method types, checkout failure states, and invoice PDF currency
    symbols.

Security

  • Removed unauthenticated portal routes that exposed cross-tenant PII (ENG-139).
  • GenAI text-to-SQL tenant isolation is now enforced by the database (dedicated
    schema + read-only role), not the prompt (ENG-137).
  • Tenant-gated the invoice PDF route; patched dependency CVEs flagged by
    govulncheck / Trivy (jwt/v4 4.5.2, rollup 4.62.2, vite 7.3.6, x/oauth2 0.27.0,
    goxmldsig 1.6.0, Go 1.25.12).

v0.3.0

Choose a tag to compare

@swapnull-in swapnull-in released this 18 Jul 18:26

Added

  • Opt-in anonymous telemetry — measures self-hosted activation (how
    many instances reach their first real invoice) so the project can see
    adoption without a hosted service. Strictly opt-in (TELEMETRY_OPTIN=
    true); default OFF means zero network calls and zero data written.
    When enabled: a random instance ID, once-ever milestone events, and a
    24h heartbeat with range-bucketed counts — never amounts, names,
    emails, keys, IDs, or exact numbers. docs/telemetry.md documents every
    payload and the one-line opt-out.
  • One-click deploy — Render, Railway, and DigitalOcean templates
    with README deploy buttons; a devcontainer for Codespaces; and a real
    Next.js starter in examples/ (pricing, signup, usage with entitlement
    headroom, feature gating) that builds and runs against make demo.
  • Competitor comparison pages — honest /vs/ pages for Lago,
    Flexprice, Kill Bill, Chargebee, and Stripe Billing.
  • Cloud operations groundwork — a keystroke-level provisioning
    runbook for the first manually-provisioned cloud customers, and a
    status-page plan.