Renewal refresh (#3), cycle-based validity (#2), labels + password change (#4) #7

Merged
Cordy merged 2 commits from feat/renewal-refresh into main 2026-07-29 22:53:34 +00:00
Owner

Three issues in one coherent slice:

#3 Renewal refresh — webhook matches subscription_id against existing records: match → extend expiry one period past max(now, current expiry), update seats, re-sign the same license id, replace the stored key, record the new txn (idempotency intact). No match → new license as before. /v1/checkin now returns {status, key} so instances pick up renewals automatically (companion: Cairn#58).

#2 Cycle-based validity — billing interval read from the price entity on the txn line items (billing_cycle.interval); monthly → 1 month + 3-day margin, yearly → 1 year + 3 days. issue() keeps its signature (delegates to new issueFor), so existing tests/admin path unchanged; admin issuance stays yearly with multi-year support.

#4 (partial)PATCH /v1/licenses/{id} sets an owner-gated label (not-found/not-owned indistinguishable); labels included in the GET /v1/licenses view. POST /v1/account/password verifies current, re-hashes, revokes all other sessions. Receipts deferred until the Paddle API key is provisioned.

New renewal_test.go: interval expiries, refresh semantics (same id, extended expiry, seats update, re-signed key, unknown-sub miss), checkin key delivery, label ownership, full password-change flow. gofmt/vet/build/test green on the runner.

Three issues in one coherent slice: **#3 Renewal refresh** — webhook matches `subscription_id` against existing records: match → extend expiry one period past max(now, current expiry), update seats, re-sign the **same license id**, replace the stored key, record the new txn (idempotency intact). No match → new license as before. `/v1/checkin` now returns `{status, key}` so instances pick up renewals automatically (companion: Cairn#58). **#2 Cycle-based validity** — billing interval read from the price entity on the txn line items (`billing_cycle.interval`); monthly → 1 month + 3-day margin, yearly → 1 year + 3 days. `issue()` keeps its signature (delegates to new `issueFor`), so existing tests/admin path unchanged; admin issuance stays yearly with multi-year support. **#4 (partial)** — `PATCH /v1/licenses/{id}` sets an owner-gated label (not-found/not-owned indistinguishable); labels included in the `GET /v1/licenses` view. `POST /v1/account/password` verifies current, re-hashes, revokes all *other* sessions. Receipts deferred until the Paddle API key is provisioned. New `renewal_test.go`: interval expiries, refresh semantics (same id, extended expiry, seats update, re-signed key, unknown-sub miss), checkin key delivery, label ownership, full password-change flow. gofmt/vet/build/test green on the runner.
Cordy merged commit 843e88092f into main 2026-07-29 22:53:34 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/cairn-license-server#7
No description provided.