diff --git a/plans/names-codes/2026-10-21-store-api.md b/plans/names-codes/2026-10-21-store-api.md index fe426a7057..d83da3167b 100644 --- a/plans/names-codes/2026-10-21-store-api.md +++ b/plans/names-codes/2026-10-21-store-api.md @@ -53,7 +53,7 @@ The database backs this with unique constraints: one live registration per commi In one transaction, rows are locked in this order: the invoice, then the registration, then a prepaid code or a balance. Rows a transaction inserts are new and wait on nobody, so `/spend` may insert its invoice and order after closing a holder (which releases that holder's balance row) and before writing the balance again. -**An overdue order is closed by the next order.** When a new order from the same wallet and name meets a live order whose open invoice has passed its expiry time with no money received (the poller may not have marked it yet; a funded invoice is never closed this way, as the sweep never expires one), the new order closes it inside its own transaction, under the label hash's turn: the old invoice becomes `expired`, its registration loses `live`, and only then is the new order inserted. The provider's cancel of the old invoice is sent afterwards, best effort, as the poller would. Settlement queues a registration only while it is still live; a payment that arrives for a closed order is a late payment (it stays `unpaid`, the operator is alerted, and support refunds it), never a second live order. +**An overdue order is closed by the next order.** When a new order from the same wallet and name meets a live order whose open invoice has passed its expiry time with no money received (the poller may not have marked it yet; a funded invoice is never closed this way, as the sweep never expires one), the new order closes it inside its own transaction, under the label hash's turn: the old invoice becomes `expired`, its registration loses `live`, and only then is the new order inserted. The provider's cancel of the old invoice is sent afterwards, best effort, as the poller would. Settlement queues a registration only while it is still live; a payment that arrives for a closed order is a late payment (it stays `unpaid`, and what arrived is credited to that order's balance, section 8; owner, 2026-10-22), never a second live order. ### Formats @@ -406,7 +406,7 @@ The badge invoice's fields, with `invoiceId`, `kind: "name"` and the registratio The long poll for a paid order. - Without `wait`, it answers at once. - With `wait`, it holds while the status is `wait`, the payment marks are `seenPaid` and `seenFull` (each compared only when sent), and the registration's `version` is `seenVersion` (when sent). It answers on any change, or unchanged after at most 30 seconds (the mock: 20). -- An invoice that reads `paid` while its registration is `unpaid` (paid after it closed) is answered at once and never held: there is nothing more to wait for. Support refunds it. +- An invoice that reads `paid` while its registration is `unpaid` (paid after it closed) is answered at once and never held: there is nothing more to wait for. What arrived is credited to the order's balance (section 8), which its view shows. - An open invoice past its expiry with no money received reads `expired`; a funded one stays `open` until it settles. ### `GET /api/registration/:id?seenVersion=` @@ -491,9 +491,9 @@ Finds a name code's orders. Used by Check your code (the name code's verdict and A paid order whose name someone registered first keeps what it is worth as a balance, which buys another name (owner, 2026-10-22). There is no code for a taken order. - **What it is:** US cents, held per order and owned by that order's owner code. Balances are never merged, across orders or wallets: one order's balance pays for one new order at a time. A balance never expires. -- **Credited:** with the order's `price`, in the write that makes a paid order `taken` (paid by invoice, from a balance, or both), so a `taken` order with a payment always has a balance. A move to a cheaper name adds the difference (`/name`, above). A code order gets none: codes carry no money, and its code is released (section 4). +- **Credited:** with the order's `price`, in the write that makes a paid order `taken` (paid by invoice, from a balance, or both), so a `taken` order with a payment always has a balance. A move to a cheaper name adds the difference (`/name`, above). A late payment (section 1), on any name invoice closed before its money arrived (a difference invoice included), adds what arrived to the closed order's own balance, in the settlement's transaction (owner, 2026-10-22): the cents the provider reports received, at the invoice's rate, each new figure adding only its increase over what that invoice already credited, so a repeated or growing report never credits twice. A payment the provider reports with no cents figure credits nothing and alerts the operator. A code order gets none: codes carry no money, and its code is released (section 4). - **Spent:** only by `/spend` (below), signed by the owner, for a new order of the same owner. When the balance covers the new name's price, the price is spent at once and the rest stays as balance. When it does not, all of it pays, and an invoice is made for the difference. -- **Held:** while that invoice is open, the new order holds the balance: no other spend and no refund takes it. Settlement queues the new order and spends what it held, in one transaction. Whatever closes the invoice unpaid (a cancel, its expiry, the poller's sweep, a next order closing it as overdue, section 1) releases the hold in the transaction that closes the order. A payment that arrives after that is a late payment (section 1); the balance is not touched. A hold by an order whose invoice is past its expiry time with no money received reads as free (`held` false), and the next `/spend` of that balance closes that order first, in its own transaction, as section 1 closes an overdue order (below). A refund does not: it waits for the poller's sweep to close the holder. +- **Held:** while that invoice is open, the new order holds the balance: no other spend and no refund takes it. Settlement queues the new order and spends what it held, in one transaction. Whatever closes the invoice unpaid (a cancel, its expiry, the poller's sweep, a next order closing it as overdue, section 1) releases the hold in the transaction that closes the order. A payment that arrives after that is a late payment (section 1): it is credited to the closed order's own balance, and the released balance is not touched. A hold by an order whose invoice is past its expiry time with no money received reads as free (`held` false), and the next `/spend` of that balance closes that order first, in its own transaction, as section 1 closes an overdue order (below). A refund does not: it waits for the poller's sweep to close the holder. - **Refunded:** only by support, when the buyer writes with the order's reference (its invoice id, or its registration id for an order without an invoice). The operator command `//refund ` (task plan, T26) sets the balance to 0 and records the cents; support then pays them back by hand, by the methods that paid them, which the command lists back through every balance the money came from. A held balance is never refunded, overdue holder or not: the command names the holder and its invoice's expiry, and support runs it again once the poller has closed the holder. There is no refund endpoint, no automatic refund, and no refund address given to the service. - **Concurrency:** each hold, spending and refund is one conditional write on the balance row (its `amount` as read, and no holder, or this holder), in the balance's turn (section 1). A writer outside the turn (settlement, the poller, a second process running the operator command) can only make the write refuse, never spend a balance twice. Every write is in the transaction of the order change it belongs to, so a restart finds both or neither. - **Closing a holder** of another name: `/spend` holds the new name's turn, not the holder's. The close is the compare-and-set on the holder's `version` and the conditional expiry of its unfunded invoice (section 1), so it is safe without that turn. A holder no longer live when the transaction runs (cancelled, swept, or closed by its own name's next order meanwhile) is skipped, and the hold write decides; a holder still live that cannot be closed (its money arrived meanwhile) answers `409 balance_changed` with the current balance. @@ -620,7 +620,7 @@ Each line: the difference, the choice here, and what changes in the mock (`webap 34. **`linksRefused`** in the registration view, set when the reveal records a links refusal and cleared by a successful `/name`. Mock: in the view. Page: offer `/name` with corrected links from the view, not only from its own record. 35. **A held code on `/name`.** A code still reserved by its order is accepted on `/name` even if revoked or expired since, as on `/retry`. Mock: in `retarget`. 36. **No code for a taken order** (owner, 2026-10-22). Chosen: `POST /api/registration/:id/code`, the phase `code_issued`, the view's `codeLength` and the tags `code_length_changed`, `code_unavailable` (and `code_conflict` on names) are gone. Mock: drop `takenCode`, its route, `codeLengthFor`, `issuedCodeLength` and the phase. Page: drop `takenOrderCode`, `takeCodeFor`, `takenCodeOf`, `api.takeCode`, the record's `takenCode`, Take a code, Here is a code instead, the `code-issued` kind and the "Code taken" chip, and their tests. -37. **Balances** (owner, 2026-10-22). Chosen: a paid order that ends `taken` keeps its `price` as a balance; `POST /api/registration/:id/spend` buys another name with it, paying only the difference; the view's `balance` and `fromBalance`; `409 balance_changed`; refunds by support only (section 8). Mock: a balance per order, credited when a paid order is taken; `spend`; settlement spends a hold and every close of the invoice releases it; a hold by an overdue order reads free; a refund of a held balance refused; `POST /control/refund/:reference` for the tests. Page: Your names shows each balance with Use, and Refund, a popup to write to support with the reference; the search and Check your order show the balance and "Pay $X more" (or what stays); the taken page says the payment is kept as a balance; the payment page of a difference invoice says what the balance paid. +37. **Balances** (owner, 2026-10-22). Chosen: a paid order that ends `taken` keeps its `price` as a balance; `POST /api/registration/:id/spend` buys another name with it, paying only the difference; the view's `balance` and `fromBalance`; `409 balance_changed`; refunds by support only (section 8). Mock: a balance per order, credited when a paid order is taken; `spend`; settlement spends a hold and every close of the invoice releases it; a hold by an overdue order reads free; a refund of a held balance refused; a late payment (`/control/settle` on a closed invoice) credited to that order's balance; `POST /control/refund/:reference` for the tests. Page: Your names shows each balance with Use, and Refund, a popup to write to support with the reference; the search and Check your order show the balance and "Pay $X more" (or what stays); the taken page says the payment is kept as a balance; the payment page of a difference invoice says what the balance paid; a payment that arrived after its invoice closed says it is kept as the order's balance, not "support refunds it". 38. **`/name` not from `taken`; a cheaper move keeps the difference** (manager's ruling, 2026-10-22). Chosen: a `taken` order is final and not live; a move to a cheaper name adds the difference to the order's balance. Mock: in `retarget`. Page: Choose another name leaves the taken page (Use the balance replaces it); for a failed order, "the rest is not refunded" becomes "the rest stays as your balance". ## Appendix B. Error tags