Release v1.14.0 — monetization launch (E5-E8) #237
Reference in New Issue
Block a user
Delete Branch "development"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Release v1.14.0 — Monetization launch (E5–E8)
Promotes
developmenttomasterfor the first real-money production deploy. This PR only gates the promotion (test-only CI); the prod rollout is a separate manualworkflow_dispatchafter this merges + thev1.14.0tag + an explicit go.What ships
hint_balance/paid_accountreads/writes (columns kept for rollback safety).account_risktable./_gm/limitseditor), replacing the old flatMaxActiveQuickGames; the server-side guest gate on friends; the client per-kind New-Game lock.Migrations (additive, expand-contract — image-rollback-safe)
00012_account_risk,00013_reward_daily_cap,00014_guest_limits. The prod deploy detects them, holds a maintenance window and takes a consistentpg_dumpbefore applying.Post-merge (separate, manual)
Tag
v1.14.0on master →prod-deploy(confirm=deploy) → owner: verify a Robokassa test payment, then flipPROD_BACKEND_ROBOKASSA_TESTto live + redeploy; seed prod catalog prices; switch payments to prod. PITR (v1.13.0) is the safety net.Add the public /pay/robokassa/result callback proxy (rate-limited; forwards the provider's form parameters to the backend intake, the single writer, and echoes its "OK<InvId>" back to Robokassa) and the /pay/robokassa/{success,fail} browser-return redirects into the app. Route /pay/* to the gateway in the contour Caddyfile so the callback reaches the edge, not the landing catch-all. The backend intake now returns the echo body as JSON for the gateway to relay.Add the vkpay package: verify a VK Mini Apps payment ("голоса") callback signature — MD5 (mandated by VK's payment protocol) of the sig-excluded parameters, sorted by name and concatenated key=value, with the app secret appended; case-insensitive over the hex digest. Unit-tested (valid / tampered / wrong-secret / missing). First piece of the VK rail; the two-phase callback handler, the VK order branch and the client bridge follow.Wire the VK Mini Apps ("голоса") rail end to end, reusing the intake engine. The wallet.order endpoint branches by rail: a VK context opens a pending order (provider vk) and returns its id, which the client passes to VKWebAppShowOrderBox. VK's two-phase payment callback is verified at the gateway with the app protected key (GATEWAY_VK_APP_SECRET) and proxied to a backend intake handler: get_item returns the ordered pack's title and vote price; a chargeable order_status_change credits the vk segment exactly once (the same Fund, idempotent on VK's own order id) and records a succeeded event, so the dispatcher push refreshes the wallet. Integration test for the VK order->credit path.Money purchases are not permitted in the VK iOS app (Apple ToS), and the backend already refuses them (the CreateOrder VK-iOS freeze). Show the pack "Buy" muted there and explain on tap ("purchases are not available on this platform"), instead of letting the tap hit a 403 and a generic error toast. The storefront still lists the packs.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).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.