Releases: recurso-dev/recurso
Release list
v0.6.0 — The usage-based billing release
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_idandrecurso_subscription_state
(active/churned). Private-app token viaHUBSPOT_ACCESS_TOKEN;
SaaS egress, so blocked underRESIDENCY_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,ErrExternalGoneremapping,
per-line major-unit amounts). Residency-guarded like QuickBooks/Xero;
connect with providernetsuite(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 theRESIDENCY_MODE=self_hostedguard.
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_ENDPOINTfor
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
behindGATEWAY_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-logsfilters by entity/actor/time; the table rejects
UPDATE and DELETE at the database level. -
Ingestion hardening —
POST /v1/usage/events/batchingests up to
500 events with per-item results, and events accept an optional
transaction_ididempotency 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-alertsconfigures 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.triggeredwebhook event plus an email to the customer. -
Minimum commitments —
PUT /v1/subscriptions/{id}/commitmentsets 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.
- metered usage) falls short, a "Minimum commitment true-up" line fills
-
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 (default5m,0disables).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, Gov1.1.0, and Python
1.2.0expose 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 bycount,sum,max, orunique(distinct
values of an event property); charges attachper_unit,graduated,
volume, orpackagepricing 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
ausage_ratingswindow claim making retried generation idempotent;
immediate cancellation bills the partial window on a final usage invoice.
GET /v1/subscriptions/{id}/usage-amountpreviews the running period,
andPOST /v1/usage/eventsaccepts optionalproperties.
v0.5.0 — The India release
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-ready —
GET /v1/india/gstr1and
GET /v1/india/gstr3bassemble 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_schemaobject 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 residency —
RESIDENCY_MODE=self_hostedhard-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.mdstates the guarantee for security reviews;
every enforcement point is unit-tested. - TDS record-on-receipts — offline payments accept
tds_amountfor the
portion a B2B customer withheld at source. It counts toward settling the
invoice, accumulates oninvoices.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, withREQUIRE_REDISto 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-gocovering 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 repositories —
recurso-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 returningNone). The in-reposdk/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;/healthno
longer leaks connection errors (ENG-174, ENG-197, ENG-198).
v0.4.0
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/statusreports 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
asrsk_test_(test) orrsk_live_(live), and a key's mode must match the
server'sgateway_mode(reported atGET /version): a test key is accepted on
anone/testserver, a live key on aliveserver. 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 mocknonegateway 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 anrsk_test_key (Settings → API Keys, or the
developer-keys endpoint) or configure live gateway keys. The demo key
sk_test_12345is 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_idpropagation 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
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 againstmake 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.