mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
## Account-link Phase 2: metering + daily usage sync Phase 1 (already on main) let a self-hosted instance link a SaaS account and blocked billable work when it was over its limit. It blocked, but it never actually charged anything. This PR adds the metering + billing half. **It's off by default.** Everything sits behind `stirling.billing.account-link.metering.enabled`, on top of the existing `stirling.billing.account-link.enabled` master flag. Both have to be on for any of it to run, so it can't touch production. The billing model isn't going live yet — this is a dark merge. ### How it works 1. The instance classifies each billable request (API / AI / Automation — manual PDF editing stays free) and counts it locally into a per-period counter. 2. Once a day it reports its running totals to SaaS. 3. SaaS bills only the delta since the last report, reusing the existing charge path (free grant + wallet ledger + Stripe meter). No new money logic. 4. The portal shows current usage (synced spend plus anything not reported yet), and when you subscribe it now reflects the new plan right away instead of waiting for a cache to expire. ### What's worth a reviewer's eyes - **It can't double-charge.** SaaS only ever bills the delta, refuses a counter that goes backwards, dedups repeat/late reports on a monotonic sequence number, and takes a row lock so a duplicate delivery can't charge twice. - **The cap is enforced at the instance gate**, not in the charge path (same as the in-cloud flow). A $0 cap blocks all metered work. - Page counts use jpdfium so the instance and the cloud agree on the number that gets billed. - New SaaS surface: `POST /api/v1/instance/sync`, migrations V25 (`payg_instance_usage`) and V26 (allow the `LINKED_INSTANCE` job source), and a small `POST /api/v1/payg/wallet/refresh` the portal calls after checkout. ### Companion PR Stirling-PDF-SaaS #314 (on `v3`): the checkout edge function so the embedded Stripe flow finishes in-page instead of reloading, plus a `Deno.serve` migration so the edge functions actually deploy. ### Testing Java unit tests (proprietary + saas), portal vitest, and the SaaS edge-function tests all pass. Branch is merged up to date with main. ### Not done yet (doesn't block this merge — only matters once both flags are on) - V25 Supabase twin in the SaaS repo. - Same in-page checkout fix for the editor's upgrade modal. - A flags-on smoke test in staging (one real sync round-trip). --------- Co-authored-by: James Brunton <james@stirlingpdf.com>