You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The wallet can buy a subscription but never shows what was bought. Three
questions a paying user will ask have no answer anywhere in the UI:
"I paid — where is my order?" If the tab was closed and the order id is
gone, the only remaining trace is Paymento's email. order-status is keyed by orderId alone and there is no list endpoint.
"What did I buy, and when?" Nothing records it. Settings → Subscription
shows the CURRENT plan, its expiry and two usage bars — no history.
"Why did my limits drop?" The gateway lazily demotes a lapsed paid
subscription to free with no error — silently lower limits. PlanDowngradeWatcher catches it and opens the upgrade modal with the
'expired' banner, but nothing says which plan expired or when.
GET /api/paymento/order-status?orderId=… already returns orderId, status
(created | pending | paid | failed), statusName (raw Paymento, incl. UserCanceled), fulfilled, confirming, settlement (on-chain details from
/verify), upgrade, maskedKey and planName. GET /api/paymento/key-info
(by x-api-key) returns maskedKey, planName, subscriptionState
(active | expired | inactive) and activeUntil.
So date, plan, amount, status, settlement and expiry are all available today.
What is missing is a way to ask for them per wallet, and a surface to show them.
The purchase IS linkable to the wallet on the normal path
PlanScreen.startCheckout always asks for an in-place upgrade of the key the
wallet already holds ("same key, new plan, fresh 30 days"); a fresh key is
minted only against a pre-upgrade gateway or when the user explicitly picks forceNewKey. That existing key is the identity-bound one provisioned at
onboarding through /auth/verify, which carries api_keys.owner_id = "<network>:<pubkey>".
So for a normal purchase the chain already closes server-side:
No schema change is needed to answer "the orders belonging to this wallet" for
the common case. Only a fresh-key purchase stays unlinked (api_keys rows
created outside getOrCreateOwnerKey leave owner_id NULL).
Options
A. Local ledger (wallet only). Stop deleting the PendingOrderRecord
(src/sdk/subscription/pendingOrder.ts, added by #504) at terminal status;
fold it into a compact completed entry — order id, plan name, price, created,
settled, status, key mask, never the key — and render Billing from it. No
gateway change, works for fresh-key purchases too. Cost: the history lives in
one browser and cannot be recovered after a wipe or on another device.
B. Server-side history endpoint (gateway). A read endpoint listing orders
whose upgrade_api_key belongs to the caller's owner key. Covers the normal
path with no schema change; survives reinstalls and works across devices.
Authentication must be the pubkey challenge (/auth/challenge + /auth/verify), NOT x-api-key. Keys are deliberately portable — the wallet
offers "Use a different key" and the timeout path offers "I have a key" — so a
key-authenticated endpoint would hand anyone holding a pasted key the buyer's
order history and their email. Signature auth exposes nothing new: only the
holder of the private key can read, and they already know both halves.
C. Bind fresh-key purchases too. The wallet already calls order-key-ack?orderId=… after storing a purchased key durably. Signing that
call would let the gateway set owner_id on the purchased key. Note two costs:
it ends the key's portability, and api_keys has a UNIQUE partial index on owner_id, so a wallet cannot own two keys as the schema stands.
Recommended: A + B — the ledger for the surface and for offline/fresh-key
coverage, the endpoint so the history survives a reinstall. C only if key
portability is being dropped anyway.
Two related facts the page should state honestly
There is no cancellation and no refund. The Paymento store has no cancel
endpoint; payment_sessions / refund_amount belong to the legacy /api/payment/* flow, not to this one. "Cancel this payment and start over"
sets a local abandonedAt only — the order lives on and can still settle
(which is Cut the abandon/background-settlement machinery: a recovered key from a cancelled order has nowhere to land #508). Nothing to cancel in the subscription sense either: it is
not recurring, it simply expires after 30 days.
A downgrade costs the remaining days.isPlanSelectable blocks only
re-buying the CURRENT active plan, so a cheaper plan is purchasable; on
fulfilment changePlan(key, planId, expiry) sets the new plan with a fresh
30 days and no proration. Buying down silently discards whatever was left.
Either warn in the UI or refuse the move while a paid plan is active.
#508 describes a key delivered by a cancelled-but-settled order that has
nowhere to land: the toast points at Settings → Subscription, which cannot
reach it, so a paid key is announced and then lost. A Billing list is exactly
that missing home — the order appears as a row with "key delivered" and an
"install this key" action.
Out of scope, worth filing separately
The browser learns about payment only by polling: the gateway has an IPN
callback from Paymento and its own 5-minute re-verification for 24h, but no
SSE or WebSocket anywhere, so the page updates on the next poll tick rather
than when the payment confirms.
Everything this page needs from the SGW — the per-wallet order listing, a push
so the screen reacts when a payment confirms, cancellation, proration and an
expiry signal — is collected in
unicitynetwork/aggregator-subscription#83. Options B and C above are items 1
and 6 there; option A (the local ledger) needs nothing from the gateway and can
ship on its own.
The downgrade warning this page would have to explain is filed separately as #510.
The wallet can buy a subscription but never shows what was bought. Three
questions a paying user will ask have no answer anywhere in the UI:
gone, the only remaining trace is Paymento's email.
order-statusis keyed byorderIdalone and there is no list endpoint.shows the CURRENT plan, its expiry and two usage bars — no history.
subscription to free with no error — silently lower limits.
PlanDowngradeWatchercatches it and opens the upgrade modal with the'expired' banner, but nothing says which plan expired or when.
The data already exists
paymento_orderscarriesorder_id,token,plan_id,email,price_cents,status,status_name,api_key/upgrade_api_key,key_revealed,settlement_json,fulfilled,awaiting_confirmation,created_at,updated_at.GET /api/paymento/order-status?orderId=…already returnsorderId,status(created | pending | paid | failed),
statusName(raw Paymento, incl.UserCanceled),fulfilled,confirming,settlement(on-chain details from/verify),
upgrade,maskedKeyandplanName.GET /api/paymento/key-info(by
x-api-key) returnsmaskedKey,planName,subscriptionState(active | expired | inactive) and
activeUntil.So date, plan, amount, status, settlement and expiry are all available today.
What is missing is a way to ask for them per wallet, and a surface to show them.
The purchase IS linkable to the wallet on the normal path
PlanScreen.startCheckoutalways asks for an in-place upgrade of the key thewallet already holds ("same key, new plan, fresh 30 days"); a fresh key is
minted only against a pre-upgrade gateway or when the user explicitly picks
forceNewKey. That existing key is the identity-bound one provisioned atonboarding through
/auth/verify, which carriesapi_keys.owner_id = "<network>:<pubkey>".So for a normal purchase the chain already closes server-side:
No schema change is needed to answer "the orders belonging to this wallet" for
the common case. Only a fresh-key purchase stays unlinked (
api_keysrowscreated outside
getOrCreateOwnerKeyleaveowner_idNULL).Options
A. Local ledger (wallet only). Stop deleting the
PendingOrderRecord(
src/sdk/subscription/pendingOrder.ts, added by #504) at terminal status;fold it into a compact completed entry — order id, plan name, price, created,
settled, status, key mask, never the key — and render Billing from it. No
gateway change, works for fresh-key purchases too. Cost: the history lives in
one browser and cannot be recovered after a wipe or on another device.
B. Server-side history endpoint (gateway). A read endpoint listing orders
whose
upgrade_api_keybelongs to the caller's owner key. Covers the normalpath with no schema change; survives reinstalls and works across devices.
Authentication must be the pubkey challenge (
/auth/challenge+/auth/verify), NOTx-api-key. Keys are deliberately portable — the walletoffers "Use a different key" and the timeout path offers "I have a key" — so a
key-authenticated endpoint would hand anyone holding a pasted key the buyer's
order history and their email. Signature auth exposes nothing new: only the
holder of the private key can read, and they already know both halves.
C. Bind fresh-key purchases too. The wallet already calls
order-key-ack?orderId=…after storing a purchased key durably. Signing thatcall would let the gateway set
owner_idon the purchased key. Note two costs:it ends the key's portability, and
api_keyshas a UNIQUE partial index onowner_id, so a wallet cannot own two keys as the schema stands.Recommended: A + B — the ledger for the surface and for offline/fresh-key
coverage, the endpoint so the history survives a reinstall. C only if key
portability is being dropped anyway.
Two related facts the page should state honestly
endpoint;
payment_sessions/refund_amountbelong to the legacy/api/payment/*flow, not to this one. "Cancel this payment and start over"sets a local
abandonedAtonly — the order lives on and can still settle(which is Cut the abandon/background-settlement machinery: a recovered key from a cancelled order has nowhere to land #508). Nothing to cancel in the subscription sense either: it is
not recurring, it simply expires after 30 days.
isPlanSelectableblocks onlyre-buying the CURRENT active plan, so a cheaper plan is purchasable; on
fulfilment
changePlan(key, planId, expiry)sets the new plan with a fresh30 days and no proration. Buying down silently discards whatever was left.
Either warn in the UI or refuse the move while a paid plan is active.
Bonus: this gives #508's orphaned key a home
#508 describes a key delivered by a cancelled-but-settled order that has
nowhere to land: the toast points at Settings → Subscription, which cannot
reach it, so a paid key is announced and then lost. A Billing list is exactly
that missing home — the order appears as a row with "key delivered" and an
"install this key" action.
Out of scope, worth filing separately
The browser learns about payment only by polling: the gateway has an IPN
callback from Paymento and its own 5-minute re-verification for 24h, but no
SSE or WebSocket anywhere, so the page updates on the next poll tick rather
than when the payment confirms.
Related: #501, #504, #508.
Where the gateway-side half is tracked
Everything this page needs from the SGW — the per-wallet order listing, a push
so the screen reacts when a payment confirms, cancellation, proration and an
expiry signal — is collected in
unicitynetwork/aggregator-subscription#83. Options B and C above are items 1
and 6 there; option A (the local ledger) needs nothing from the gateway and can
ship on its own.
The downgrade warning this page would have to explain is filed separately as
#510.