Skip to content

pit: an ending can be bought and renewed - #152

Merged
ralyodio merged 1 commit into
mainfrom
feat/ending-checkout
Jul 31, 2026
Merged

pit: an ending can be bought and renewed#152
ralyodio merged 1 commit into
mainfrom
feat/ending-checkout

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

ENDING_PRICE_USD was $5 and charged by nothing. registerTld inserted a row and claiming was free and permanent — no term, no expiry, nothing to renew.

This is the lifecycle underneath that price: PRD 0005 §5's one-year term on a direct ending, plus the CoinPay checkout that starts one.

quoteTld        what an unclaimed ending costs        $5 × years, cap 10
quoteRenewal    what keeping one costs
openTldPurchase reserve it against other buyers
settleTldPurchase  hand it over, or extend the term

Modelled on the name flow, not invented beside it

Same shape and same reasons: keyed on the payment id so a webhook redelivery settles the same row; a conditional UPDATE to claim it, because CoinPay retries anything it never got an ack for and two deliveries both read pending before either write lands; a reservation that stops two people paying at once, with the UNIQUE constraint still the real arbiter at settlement.

An ending taken between checkout and confirmation is recorded refund_due and logged — real money against something the buyer can't have.

Renewals never shorten a term

Extends from whichever is later, the current expiry or now. Renewing early adds to what's left instead of throwing it away; renewing after a lapse doesn't backdate into the past. PRD 0005 R7. Both directions tested.

Grandfathering — deliberately not done

Both new columns are NULLable and every existing row keeps NULL. A NULL expiry means "no term recorded", which is what all ~250 endings claimed before today have, and isExpired reads it as not expired.

Backfilling an invented expiry would put a namespace on a clock its owner never agreed to. §21.8 wants a published grandfathering policy first — that's a decision, not a migration.

Claiming is still free. This adds the paid path; it doesn't switch the free one off. Doing that would break the bulk-claim flow mid-session and is a product call, not a refactor.

Tests

13, including the two races that cost money: a redelivered webhook, and an ending claimed in the gap. Full pwa suite green.

Next

Buy Now button for endings (now unblocked), then grace/auctions from §13.

🤖 Generated with Claude Code

ENDING_PRICE_USD was $5 and charged by nothing. registerTld inserted a row and
claiming was free and permanent — no term, no expiry, nothing to renew. This is
the lifecycle underneath that price: PRD 0005 §5's one-year term on a direct
ending, and the CoinPay checkout that starts one.

Modelled on the name purchase flow rather than invented beside it. Same shape,
same reasons: keyed on the payment id so a webhook redelivery settles the same
row; a conditional UPDATE to claim it, because CoinPay retries anything it
never got an ack for and two deliveries read 'pending' before either write
lands; a reservation that stops two people paying at once while the UNIQUE
constraint stays the real arbiter at settlement.

An ending taken between checkout and confirmation is recorded `refund_due` and
logged. That is real money against something the buyer cannot have, and the
name flow already decided that is not a thing to swallow.

A renewal extends from whichever is later, the current expiry or now. Renewing
early adds to what is left instead of throwing it away; renewing after a lapse
does not backdate the term into the past. PRD 0005 R7.

Both new columns are NULLable and every existing row keeps NULL. A NULL expiry
means "no term recorded", which is what all ~250 endings claimed before today
have, and isExpired deliberately reads it as not-expired. Backfilling an
invented expiry would put a namespace on a clock its owner never agreed to;
§21.8 wants a published grandfathering policy first, and that is a decision
rather than a migration.

13 tests, including the two races that cost money: a redelivered webhook, and
an ending claimed in the gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit a325de1 into main Jul 31, 2026
3 checks passed
@ralyodio
ralyodio deleted the feat/ending-checkout branch July 31, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant