feat(ads): rewarded video (VK) — client-attested credit + daily/hourly caps #228

Merged
developer merged 2 commits from feature/ads-rewarded into development 2026-07-09 23:33:10 +00:00
Owner

First E6 (ads) slice — rewarded video. A voluntary VK rewarded video credits chips.

VK reality (checked in the VK docs via Playwright): VK Mini App ads expose only a client-side data.result — no server verify. So the credit is client-attested (D29 amended), guarded by a server daily + hourly cap (config reward_daily_cap/reward_hourly_cap, default 50/10) — both anti-abuse (bounds a forger who skips the ad and calls the endpoint) and an economic conversion lever (limits free chips so players buy). Idempotent on a client nonce, order-less; payout from config rewarded_payout_chips (default 0 = off until set).

  • Backend: CreditReward + wallet.reward edge op (returns the wallet; reward_chips gates the CTA). Additive migration (two config columns) → no contour wipe, rollback-safe.
  • Client: ads-network abstraction (lib/ads.ts, VK impl) + VK bridge + Wallet "watch for chips" CTA + i18n.
  • Contour test stub (VITE_ADS_STUB → toast instead of a real ad; prod always real — a failed real ad must not credit) + a temporary diagnostic that logs the raw VK data.

Tests: backend integration (credit / nonce-idempotent / hourly-cap / disabled / non-VK) + codec unit. Local: all Go build+vet+gofmt, backend unit, full payments integration, UI check+unit+build, bundle budget (shared 30→31). Docs: PAYMENTS(+ru) §10, D29 amend, PLAN E6.

Owner-side after deploy:

  1. Contour starts with real VK ads (TEST_VITE_ADS_STUB unset) — crank a rewarded view; I read the logged raw VK data to confirm what it returns (harden to signature if any).
  2. Set rewarded_payout_chips > 0 in the contour config to enable rewarded (go-live gate).
  3. Later, set TEST_VITE_ADS_STUB=1 for routine toast-stub testing.
**First E6 (ads) slice — rewarded video.** A voluntary VK rewarded video credits chips. **VK reality (checked in the VK docs via Playwright):** VK Mini App ads expose only a client-side `data.result` — no server verify. So the credit is **client-attested** (D29 amended), guarded by a server **daily + hourly cap** (config `reward_daily_cap`/`reward_hourly_cap`, default 50/10) — both anti-abuse (bounds a forger who skips the ad and calls the endpoint) and an **economic conversion lever** (limits free chips so players buy). Idempotent on a client nonce, order-less; payout from config `rewarded_payout_chips` (default 0 = **off until set**). - **Backend:** `CreditReward` + `wallet.reward` edge op (returns the wallet; `reward_chips` gates the CTA). **Additive migration** (two config columns) → no contour wipe, rollback-safe. - **Client:** ads-network abstraction (`lib/ads.ts`, VK impl) + VK bridge + Wallet "watch for chips" CTA + i18n. - **Contour test stub** (`VITE_ADS_STUB` → toast instead of a real ad; **prod always real** — a failed real ad must not credit) + a **temporary diagnostic** that logs the raw VK `data`. **Tests:** backend integration (credit / nonce-idempotent / hourly-cap / disabled / non-VK) + codec unit. Local: all Go build+vet+gofmt, backend unit, full payments integration, UI check+unit+build, bundle budget (shared 30→31). **Docs:** PAYMENTS(+ru) §10, D29 amend, PLAN E6. **Owner-side after deploy:** 1. Contour starts with **real VK ads** (`TEST_VITE_ADS_STUB` unset) — crank a rewarded view; I read the logged raw VK `data` to confirm what it returns (harden to signature if any). 2. Set `rewarded_payout_chips` > 0 in the contour config to **enable** rewarded (go-live gate). 3. Later, set `TEST_VITE_ADS_STUB=1` for routine toast-stub testing.
developer added 1 commit 2026-07-09 22:42:01 +00:00
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
dbd76d53e8
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).
developer added 1 commit 2026-07-09 23:27:29 +00:00
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
9acf6ab3b4
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).
owner approved these changes 2026-07-09 23:32:39 +00:00
developer merged commit e08d3301bd into development 2026-07-09 23:33:10 +00:00
developer deleted branch feature/ads-rewarded 2026-07-09 23:33:11 +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#228