Origin-aware refresh + self-service seat changes #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/seats"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #11
Closes #12
#11 — upgrades no longer extend expiry.
refresh()split into a thin wrapper (old contract, always extends — existing renewal tests untouched) andrefreshWith(..., extend bool). The webhook passesextend = txn.Origin != "subscription_update", so Paddle's mid-cycle quantity-change transactions update seats and re-sign the same license id while leavingExpiresAtexactly where it was; renewals behave as before. Regression test proves an upgrade leaves expiry untouched to the second and a subsequent renewal extends from the unchanged expiry.#12 —
POST /v1/licenses/{id}/seats. Org-admin only; license must belong to the caller's org, be unrevoked, and be subscription-backed (409 otherwise, with not-found/not-owned indistinguishable). Reads the subscription's current items from Paddle, PATCHes with the new quantity on the seat-carrying item andproration_billing_mode: "prorated_immediately", returns 202 — the license itself updates asynchronously when the prorated transaction completes and hits the webhook, and the instance installs the refreshed key at its next check-in. A Paddle 403 is logged with the key-scope hint since that's the almost-certain cause. End-to-end test against a fake Paddle asserts the exact PATCH body.The commercial floor (>50 seats) is deliberately not duplicated server-side — the Paddle price's quantity minimum owns that rule.
Site UI ("Change seats" on account.html) follows separately; the endpoint is inert until
PADDLE_API_KEYgains subscription write scope.