Licenses mint with the Paddle customer id as org — let the buyer name it before minting #17

Closed
opened 2026-07-31 10:07:38 +00:00 by Cordy · 0 comments
Owner

Dogfood finding (Nikola): first purchases mint licenses whose org payload field is the Paddle customer id (ctm_…), because checkout passes no custom_data.org and the webhook falls back to txn.CustomerID. Cairn then displays it verbatim: "Plan: Licensed — ctm_01kyt…". The fallback comment in paddle.go even says "until the account area lets customers name their organization" — orgs shipped since (#6), the webhook was never caught up.

Design (Nikola): the buyer should be able to name the org/license before the key is minted, since org is sealed into the signed payload and only changes on the next re-sign.

Plan:

  • Site (account.html): the buy box gains an "Organization name on the license" input, prefilled with the signed-in account's org name (already loaded by loadOrg), passed as custom_data.org at checkout. The webhook already prefers custom_data.org — no server change needed for this path.
  • Server fallback: when custom_data.org is absent/blank, resolve buyer email → account → org name (the store has it) instead of the Paddle customer id. A ctm_… org should become impossible for any buyer with an account.
  • Existing licenses: heal on their next re-sign (renewal or seat change) if the refresh path also fills org from the owning account's org name when the stored org looks like a ctm_ id — decide whether to do that or leave history as-is.
  • Caveat to document: org name is mutable in the account area, but the license snapshot is not — a rename propagates only at the next re-sign. Not a bug.

Note: per-license display labels already exist post-hoc (#4, PATCH /v1/licenses/{id} label) — this issue is specifically about the immutable org field inside the signed payload.

Dogfood finding (Nikola): first purchases mint licenses whose `org` payload field is the Paddle customer id (`ctm_…`), because checkout passes no `custom_data.org` and the webhook falls back to `txn.CustomerID`. Cairn then displays it verbatim: "Plan: Licensed — ctm_01kyt…". The fallback comment in `paddle.go` even says "until the account area lets customers name their organization" — orgs shipped since (#6), the webhook was never caught up. Design (Nikola): the buyer should be able to name the org/license **before the key is minted**, since `org` is sealed into the signed payload and only changes on the next re-sign. Plan: - **Site (`account.html`):** the buy box gains an "Organization name on the license" input, prefilled with the signed-in account's org name (already loaded by `loadOrg`), passed as `custom_data.org` at checkout. The webhook already prefers `custom_data.org` — no server change needed for this path. - **Server fallback:** when `custom_data.org` is absent/blank, resolve buyer email → account → org **name** (the store has it) instead of the Paddle customer id. A `ctm_…` org should become impossible for any buyer with an account. - **Existing licenses:** heal on their next re-sign (renewal or seat change) if the refresh path also fills org from the owning account's org name when the stored org looks like a `ctm_` id — decide whether to do that or leave history as-is. - **Caveat to document:** org name is mutable in the account area, but the license snapshot is not — a rename propagates only at the next re-sign. Not a bug. Note: per-license display labels already exist post-hoc (#4, `PATCH /v1/licenses/{id}` label) — this issue is specifically about the immutable `org` field inside the signed payload.
Cordy closed this issue 2026-07-31 10:16:47 +00:00
Sign in to join this conversation.
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#17
No description provided.