# Prepaid usage bundles Teams on pay‑as‑you‑go can **buy a year of PDF processing up front, at a discount** — *"12 months for the price of 10."* You pre‑buy a pool of credits; they're spent **before** any metered billing and sit **outside** the monthly spend limit; unused capacity expires after 12 months. --- ## What this PR delivers **Buy → quote → invoice → pay (Stripe‑Quotes‑native).** - A team lead sizes the pool in the calculator (persisted as a quote row), which doubles as the quote page with a **"Download quote (PDF)"** — the PDF is **Stripe's own rendered quote** (same mechanism procurement uses), not an app‑generated document. - **Finalise** turns the accepted quote into an invoice; the lead can **download the invoice** or **pay online** (Stripe hosted invoice). **Card and bank‑transfer / PO** are both supported (payment‑method fork), on net terms. - The billing page loads the in‑flight quote/invoice on open, so the CTA resumes the right step (**View quote** / **Pay invoice to complete**) and offers **Cancel purchase** (voids the invoice + quote and restarts). **Prepaid is usable on its own — no subscription required.** The entitlement gate honours a live prepaid pool in both cases: - *Unsubscribed*: once the one‑time free grant is spent, a live pool keeps the team **fully entitled** (all feature gates) rather than degraded. - *Subscribed*: a team **at/over its metered cap** but holding a live pool stays fully entitled — prepaid draws are netted out of metered spend, so the pool genuinely sits outside the cap. Only when the free grant **and** the prepaid pool are both empty do billable categories stop. **Coordinated SaaS change (ships with this — `Stirling-PDF-SaaS` `v3` branch):** the `invoice.paid` webhook credits the pool idempotently (keyed on the invoice id) and settles the quote. Metered‑subscription provisioning is best‑effort and **classified** — a permanent Stripe 4xx (the single‑use hosted‑invoice card can't be attached) is a claimed no‑op (HTTP 200, no retry) so Stripe doesn't redeliver forever; only transient errors (5xx / connection / rate‑limit) retry. A failed credit now retries rather than silently dropping a paid bundle. ### Flow 1. Lead sizes the pool and agrees to the terms → the browser sends team + capacity + consent, **never a price**. 2. A leader‑gated server function looks up the price and creates a Stripe **quote** (line quantity = capacity). 3. Lead **finalises** → the quote becomes a Stripe **invoice**; download it or pay online (card or bank transfer). 4. On `invoice.paid`, the webhook **credits the prepaid pool** (idempotent) and settles the quote. 5. Usage then draws **free grant → prepaid pool → meter**; the pool is usable with no subscription. <img width="1280" height="920" alt="01-activation-fork" src="https://github.com/user-attachments/assets/e8981dc7-809d-4fc5-bfde-71e619096b7b" /> <img width="1280" height="920" alt="02-calculator" src="https://github.com/user-attachments/assets/81f6b886-1797-4c54-ba18-97d126be78e2" /> <img width="1120" height="600" alt="03-free-plan" src="https://github.com/user-attachments/assets/c4057fde-d965-47dd-93e8-f2f0f612aa73" /> <img width="1105" height="1285" alt="04-subscribed-prepaid" src="https://github.com/user-attachments/assets/9d1d0ef1-29d0-4bd4-9c17-ac5f68f88ca8" /> --- ## In a follow‑up (not this PR) 1. **Authoritative price via an inline fixed‑amount coupon** *(in progress in a separate PR).* Replace the percentage 12‑for‑10 coupon with an edge‑function‑computed **`amount_off`** coupon: the invoice shows a concrete "−$X.00" discount line, the total is deterministic (no percentage‑rounding drift), and the persisted price becomes **server‑authoritative**. Money‑mechanism change — needs validation against the Stripe test env, so it warrants its own testable PR. 2. **Metered auto‑resume when the pool empties.** Save the paying card at invoice time (`setup_future_usage`) for card payers → real `charge_automatically`; a cardless `send_invoice` subscription for bank‑transfer / PO. This makes the "processing continues at the metered rate" promise true for everyone. 3. **Provisioning idempotency hardening** (SaaS repo). Idempotency key on subscription creation + a conditional link RPC, so a webhook redelivery or link‑RPC failure can't create duplicate or orphaned subscriptions. 4. **Repo‑wide "credits" copy** across *all* of usage & billing (this PR only makes its own additions consistent). --- ## Known edges (current state) - **Cardless teams degrade when the pool empties.** An unsubscribed bundle team that runs the pool dry hits DEGRADED (metered paused), not automatic metered continuation — because no metered subscription gets provisioned off a hosted‑invoice card. The consent copy states processing "continues at the metered rate"; that promise is intentionally **ahead of the mechanism** (follow‑up 2), and the 12‑month term is the runway to deliver it. The prepaid capacity itself stays fully usable in the meantime. - **In‑app total vs charge can differ by ≤1¢** until follow‑up 1 lands. The **shared approval document (the Stripe quote PDF) and the actual invoice are already Stripe‑authoritative**; the persisted price shown in‑app is still a front‑end estimate (percentage‑coupon rounding), so it can differ from Stripe by a rounding cent. Resume‑time drift is fixed (frozen to persisted); exact‑to‑the‑penny parity arrives with the authoritative‑price follow‑up. - **Provisioning idempotency is latent, not live.** The duplicate/orphan‑subscription window only becomes reachable once card‑linking (follow‑up 2) makes provisioning actually run; hardening is tracked as follow‑up 3. - **One job can overshoot the spend cap via a near‑empty pool.** A subscribed team that has hit its metered cap but still holds a *nearly‑exhausted* pool is let through (the pool overrides the cap gate); if a job needs more than the pool has left, the pool drains to zero and the **remainder meters**, so that single job's remainder can bill just past the "never past your spend limit" ceiling. Bounded to one job's overshoot and only at the pool's tail; the alternative — blocking the job — would strand paid‑for capacity, so this is a deliberate trade. --- ## Testing - **Java** — `EntitlementServiceTest` (18) incl. unsubscribed‑live‑pool‑stays‑FULL, subscribed‑over‑cap‑with‑pool‑stays‑FULL, and lazy‑read guards. - **Frontend** — `useBundleFlowState` + `Usage` render tests; portal & SaaS `tsc`; i18n audit; `lint:colors`; toml‑sort; prettier. - **SaaS webhook** (`v3`) — Deno tests for terminal‑vs‑transient provisioning classification (rate‑limit treated as retryable), credit‑error‑retries, and an end‑to‑end no‑storm assertion on the unusable‑card path. *Preview:* the checkout runs in a Supabase function in `Stirling-PDF-SaaS` (`v3`); a live V2 preview is linked in the auto‑deploy comment below. Screenshots to be refreshed — the checkout modal changed since the originals.
Stirling PDF - The Open-Source PDF Platform
Stirling PDF is a powerful, open-source PDF editing platform. Run it as a personal desktop app, in the browser, or deploy it on your own servers with a private API. Edit, sign, redact, convert, and automate PDFs without sending documents to external services.
Key Capabilities
- Everywhere you work - Desktop client, browser UI, and self-hosted server with a private API.
- 50+ PDF tools - Edit, merge, split, sign, redact, convert, OCR, compress, and more.
- Automation & workflows - No-code pipelines direct in UI with APIs to process millions of PDFs.
- Enterprise‑grade - SSO, auditing, and flexible on‑prem deployments.
- Developer platform - REST APIs available for nearly all tools to integrate into your existing systems.
- Global UI - Interface available in 40+ languages.
For a full feature list, see the docs: https://docs.stirlingpdf.com
Quick Start
docker run -p 8080:8080 docker.stirlingpdf.com/stirlingtools/stirling-pdf
Then open: http://localhost:8080
For full installation options (including desktop and Kubernetes), see our Documentation Guide.
Resources
Support
- Community: Discord
- Bug Reports: GitHub Issues
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
This project uses Task as a unified command runner for all build, dev, and test commands. Run task dev to get started running the editor, run task to see the most common commands, or see the Developer Guide for full details.
For adding translations, see the Translation Guide.
License
Stirling PDF is open-core. See LICENSE for details.

