fe29c6fe58ab29aed49d451c5604b78d9ff636d2
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1507ceb793 |
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
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. |
||
|
|
eef90a152e |
feat(payments): per-channel Robokassa shops on the direct rail
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m0s
Split the single Robokassa direct rail into one merchant shop per channel (web / android; ios later), chosen by the trusted X-Platform subtype, while every shop still credits the one `direct` wallet — merchant-account separation for accounting and receipts, not a new wallet. - config: a channel-keyed shops registry (web seeds from the legacy vars or _WEB_*, android from _ANDROID_*); an empty shop leaves the rail dormant; per-shop validation. - intake: the order picks its shop by subtype (unknown falls back to web); the per-shop Result callback is verified by that shop's own Password2 at /pay/robokassa/result/<channel> (the gateway extracts the channel; Caddy's /pay/* glob already forwards it — no Caddyfile change). - persistence: an additive `shop` column on the order (migration 00015), recorded from the payment context, surfaced per entry in the admin report. - standalone apps sign in by email only, so a direct purchase keeps its email anchor. - docs: PAYMENTS (+ru) topology, deploy env vars + compose mapping, the decisions log (D41 revised for the ИП / 54-ФЗ move; D42-D44), the plan. Contour-safe: dormant until shops are configured; the migration is additive (no wipe); no client wire change. Fiscalization (Receipt/Email) and the gateway `direct/android` subtype follow when the ИП / RuStore are live. |
||
|
|
eb7fa98426 |
feat(payments): edit the rewarded-ads config from the admin catalog page
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 24s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Successful in 1m57s
The "watch for chips" rewarded payout and its per-day / per-hour caps live in the shared payments.config row and had no admin surface — they could only be changed by SQL, contrary to D32 (the rewarded rate is meant to be admin-configurable). Add a "Rewarded ads" form on the /_gm/catalog page: RewardConfig / SetRewardConfig on the service (non-negative validated), a setRewardConfig store writer (the singleton config row), the consoleSetReward handler + POST /_gm/catalog/reward route, and the form pre-filled from the current config. No migration — the columns already exist. The reward rate is a config value, not a sellable catalog atom (so a "no-ads forever" style product is still out of scope by D32/D33). Test: an integration test sets the config, checks the page pre-fill, and refuses a negative value. |
||
|
|
b5c8a04f0b |
fix(ads): share one interstitial cooldown timer across kinds
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 25s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
Cooldowns were tracked per kind ({move,hint}), so a hint ad and a move ad
did not see each other: after a hinted move's ad the move timer was still
zero, and the next plain move fired an ad immediately — two ads within a
minute, under the cooldown. Track a single shared last-shown time; the kind
only selects the required gap (hint 1m, move 5m, vs_ai 30m) measured from the
last interstitial of any kind. A hint can still fire on its shorter gap
(D30's "independent" hint cooldown), but never stacks a second ad within a
cooldown.
Tests: a hint uses the shorter shared gap; a move does not stack onto a
just-shown hint ad.
|
||
|
|
13be7c3d9a |
feat(ads): VK post-move interstitial + retire deprecated hint_balance/paid_account
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 23s
CI / ui (pull_request) Successful in 1m12s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
Interstitial video after a confirmed play or a hint, VK-only, offline banner-only. The gate is client-mirrored: the backend puts the config cooldowns (global 5m / vs_ai 30m / hint 1m) and a suppressed flag (the no-ads / no_banner gate, same as the banner) on Profile.ads via adsFor; the client self-gates on a per-kind last-shown time in localStorage, with the VITE_ADS_STUB contour "ad fired" toast. Never fires after a pass, exchange or resign. maybeShowInterstitial + vkShowInterstitial; the codec and gateway transcode carry the ads block. Also retires the deprecated accounts.hint_balance / paid_account domain usage (expand-contract, code only — the columns stay for a later DROP so image rollback stays DB-safe): drop the Account fields and their scan, the dead account.SpendHint, account.GrantHints and the admin grant-hints action; the in-game hint display now comes wholly from the payments hint benefit. Banner eligibility and account merge no longer read the legacy flags. Tests: ads.test.ts (the client-mirrored gate), the codec ads round-trip, the gateway ads transcode test, and a profile ads-config integration test (cooldowns + suppressed under no-ads / no_banner). Docs: PAYMENTS §10 (+ru) interstitial, the decision amends, backend README, the plan. |
||
|
|
9acf6ab3b4 |
fix(payments): VK-iOS freeze is purchase-only; remove the rewarded diagnostic
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 20s
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m5s
Testing the rewarded slice surfaced a contradiction: rewarded ads let a VK-iOS
user earn vk chips, but the blanket VK-iOS "spend freeze" then blocked spending
them (the wallet showed "5 (view-only)"). Apple's ToS forbids only BUYING in-app
values on VK-iOS (money -> chips), not spending or earning them — so the freeze is
corrected to purchase-only: vkFrozen() now gates only CreateOrder (the money-in
step), not spendableSources (spending). VK-wallet chips — earned via rewarded ads
or bought on the same account elsewhere (VK Android) — now spend on VK-iOS too.
(Owner's ToS finding.)
Also: the temporary contour diagnostic confirmed VK returns only {result:true} for
a rewarded view (no token/signature) — client-attested is final, no hardening
possible — so the diagnostic (the log and the diag wire field) is removed.
Docs: PAYMENTS(+ru) VK-iOS freeze + D17 amend + PLAN E6. Tests updated (gate /
wallet VK-iOS now spendable).
|
||
|
|
dbd76d53e8 |
feat(ads): rewarded video (VK) — client-attested credit + daily/hourly caps
CI / changes (pull_request) Successful in 4s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 26s
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
The first ads slice: a voluntary rewarded video credits chips. VK Mini App ads (VKWebAppShowNativeAds) expose only a client-side watch result — no server-to-server verify — so the credit is client-attested, guarded by a server daily + hourly cap (config reward_daily_cap / reward_hourly_cap, default 50 / 10). The caps are both anti-abuse (bounding a forger who skips the ad and calls the endpoint directly) and an economic conversion lever (limiting free chips so a player who wants more buys). D29 amended to VK's reality. Backend: CreditReward (VK-only, order-less, idempotent on a client nonce, floored by the caps; payout from config rewarded_payout_chips, default 0 = off) + the wallet.reward edge op returning the updated wallet (reward_chips gates the "watch for chips" CTA). Additive migration (two config columns). Client: the ads-network abstraction (lib/ads.ts, VK impl) + the VK bridge (vkRewardedReady / vkShowRewarded) + the Wallet CTA + i18n. A contour test stub (VITE_ADS_STUB -> a toast instead of a real ad; prod always real) and a temporary diagnostic that logs the raw VK data, so we confirm on the contour exactly what VK returns (harden to signature-verify if it carries one). Tests: backend integration (credit, nonce idempotency, hourly cap, disabled, non-VK refusal) + codec unit (reward wire). Docs: PAYMENTS(+ru) §10, D29 amend, PLAN E6. Bundle: shared budget 30->31 (reward i18n strings). |
||
|
|
1c06d1d0d1 |
feat(payments): chip wallet, store-compliance gate and benefit application
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m7s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
Stand up the internal chip/benefit mechanic behind the narrow payments interface: context-aware balances and benefits, an atomic chip spend, admin grants as zero-price value sales, the one-directional store-compliance gate (VK/TG same- origin only, web draws direct→vk→tg, VK-iOS frozen, untrusted fail-closed), and per-origin hint and no-ads application with term stacking. Reads are served from an in-process, account-keyed write-through cache (mirroring the suspension gate), so hot paths issue no query to the payments schema. Flip the online-game hint wallet and the ad-banner suppression from the deprecated accounts.hint_balance / paid_account columns to the payments benefit (a hint balance no longer suppresses the banner — only a no-ads benefit does), and fold chip segments and benefits by origin on account merge, inside the merge tx. Add the GET/POST /api/v1/user/wallet edge chain (REST → Connect → FlatBuffers) plus its codec unit test; no wallet UI yet. Bring the frozen owner decisions log into the repo at docs/PAYMENTS_DECISIONS_ru.md (it was untracked under .vscode) and reference it from PLAN.md; record the read-cache design and the present-sources interface in PLAN.md and docs/PAYMENTS.md (+ RU mirror). |