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
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
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.
This commit is contained in:
@@ -72,6 +72,16 @@
|
||||
{{if or .Finance.Abuse .Finance.Loss}}<li><b>Refund risk</b> <span class="warn">{{if .Finance.Abuse}}abuse-flagged{{end}}{{if .Finance.Loss}} · loss {{.Finance.Loss}} chips{{end}}</span></li>{{end}}
|
||||
</ul>
|
||||
{{else}}<p class="note">no balances or benefits</p>{{end}}
|
||||
<h3>Payment override</h3>
|
||||
<form class="form" method="post" action="/_gm/users/{{.ID}}/purchase-override">
|
||||
<label>Purchases for this account
|
||||
<select name="override">
|
||||
<option value="default" {{if eq .PurchaseOverride "default"}}selected{{end}}>Default (follow the rail switch)</option>
|
||||
<option value="allow" {{if eq .PurchaseOverride "allow"}}selected{{end}}>Always allow (bypasses the rail switch only, not security)</option>
|
||||
<option value="deny" {{if eq .PurchaseOverride "deny"}}selected{{end}}>Always deny</option>
|
||||
</select></label>
|
||||
<div><button type="submit">Save</button></div>
|
||||
</form>
|
||||
<h3>Ledger</h3>
|
||||
{{$uid := .ID}}
|
||||
{{if .Finance.Ledger}}
|
||||
|
||||
Reference in New Issue
Block a user