feat(payments): live payment-availability kill switch + per-account override #267

Merged
developer merged 1 commits from feature/payment-kill-switch into development 2026-07-14 14:39:36 +00:00
Owner

What

Let an operator disable purchases live from the admin — a whole rail/channel or one account — and show the user a localized reason on their next attempt, instead of a silent dead button (the owner's pain point: broken payments with no explanation).

Changes

  • Rail kill switchpayments.rail_status (per rail direct:web / direct:android / vk / telegram): enabled + a per-language message, edited on the catalog page. Fail-open (no row ⇒ enabled — payments are never accidentally killed).
  • Intake gateCanPurchase in handleWalletOrder, before the order, returns payment_unavailable + the localized message. Orthogonal to the security gates.
  • Per-account overridepayments.account_payment_override (a row only for non-default; default = no row, cleared by delete): allow / deny / default, edited on the user card. allow bypasses ONLY the ops rail switch, never the security gates (trusted platform, the email anchor, the VK-iOS freeze, the min client version).
  • Wire — an additive ExecuteResponse.message envelope field (frozen-contract-safe); the gateway forwards a backend domain-error message (DomainMessage); the client reads it into GatewayError.message and shows it on a payment_unavailable buy attempt.
  • Admin — rail toggles on the catalog page; the override control on the user card.
  • DocsPAYMENTS.md (+_ru), the decisions log (D45/D46), the plan. Fiscalization flipped to cabinet-side (owner decision) — no itemized-receipt code.

Safety

  • Contour-safe / fail-open: the additive migration (two new tables) applies forward with no wipe; the wire add is additive; nothing is disabled until an operator acts.
  • Migration 00016; no schema break; no client-wire break beyond the additive envelope field.

Verification

  • go build/vet ./backend/... ./gateway/..., gofmt clean; all backend + gateway unit tests green (incl. the TDD PurchaseGate); integration green (migration 00016 + the full kill-switch / override end-to-end on real Postgres); UI svelte-check 0/0, vitest 634 passed.

Stacked on

Follows the multi-shop PR (#266, merged) — reuses its per-channel rail concept.

## What Let an operator disable purchases **live** from the admin — a whole rail/channel or one account — and show the user a **localized reason** on their next attempt, instead of a silent dead button (the owner's pain point: broken payments with no explanation). ## Changes - **Rail kill switch** — `payments.rail_status` (per rail `direct:web` / `direct:android` / `vk` / `telegram`): `enabled` + a per-language message, edited on the catalog page. **Fail-open** (no row ⇒ enabled — payments are never accidentally killed). - **Intake gate** — `CanPurchase` in `handleWalletOrder`, before the order, returns `payment_unavailable` + the localized message. **Orthogonal to the security gates.** - **Per-account override** — `payments.account_payment_override` (a row only for non-default; default = no row, cleared by delete): allow / deny / default, edited on the user card. **`allow` bypasses ONLY the ops rail switch, never the security gates** (trusted platform, the email anchor, the VK-iOS freeze, the min client version). - **Wire** — an additive `ExecuteResponse.message` envelope field (frozen-contract-safe); the gateway forwards a backend domain-error message (`DomainMessage`); the client reads it into `GatewayError.message` and shows it on a `payment_unavailable` buy attempt. - **Admin** — rail toggles on the catalog page; the override control on the user card. - **Docs** — `PAYMENTS.md` (+`_ru`), the decisions log (D45/D46), the plan. Fiscalization flipped to cabinet-side (owner decision) — no itemized-receipt code. ## Safety - **Contour-safe / fail-open:** the additive migration (two new tables) applies forward with no wipe; the wire add is additive; nothing is disabled until an operator acts. - Migration `00016`; no schema break; no client-wire break beyond the additive envelope field. ## Verification - `go build/vet ./backend/... ./gateway/...`, `gofmt` clean; all backend + gateway **unit tests green** (incl. the TDD `PurchaseGate`); **integration green** (migration `00016` + the full kill-switch / override end-to-end on real Postgres); **UI** `svelte-check` 0/0, `vitest` 634 passed. ## Stacked on Follows the multi-shop PR (#266, merged) — reuses its per-channel rail concept.
developer added 1 commit 2026-07-14 14:30:27 +00:00
feat(payments): live payment-availability kill switch + per-account override
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m15s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m0s
1507ceb793
Let an operator disable purchases live from the admin — a whole rail/channel or
one account — and show the user a localized reason on their next attempt, so a
provider outage or a misconfig is explained instead of a silent dead button.

- rail kill switch (payments.rail_status, per rail direct:web / direct:android /
  vk / telegram): enabled + a per-language message, edited on the catalog page.
  Fail-open — a rail with no row stays enabled, so payments are never accidentally
  killed. The intake gate (CanPurchase in handleWalletOrder, before the order)
  returns payment_unavailable + the localized message, orthogonal to the security
  gates.
- per-account override (payments.account_payment_override, a row only for
  non-default): allow / deny / default, edited on the user card. "allow" bypasses
  ONLY the ops rail switch, never the security gates (trusted platform, the email
  anchor, the VK-iOS freeze, the min client version).
- wire: an additive ExecuteResponse.message envelope field (frozen-contract-safe);
  the gateway forwards a backend domain-error message; the client shows it on a
  payment_unavailable buy attempt.
- admin: rail toggles on the catalog page, the override control on the user card.
- tests: the pure gate (unit, TDD), the store + gate + override end-to-end
  (integration, migration 00016), the client (svelte-check / vitest).
- docs: PAYMENTS (+ru), the decisions log (D45/D46). Fiscalization stays
  cabinet-side (owner decision) — no itemized-receipt code.

Contour-safe: additive migration (two new tables, no wipe), the wire add is
additive, and fail-open so nothing is disabled until an operator acts.
owner approved these changes 2026-07-14 14:39:00 +00:00
developer merged commit 74842dc624 into development 2026-07-14 14:39:36 +00:00
developer deleted branch feature/payment-kill-switch 2026-07-14 14:39:36 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#267