feat(registry): Google Play: verify, acknowledge and revoke Play Billing purchases #19

Merged
kblack0610 merged 1 commit from feat/registry-google-play into main 2026-11-04 16:37:10 +00:00
Owner

Premium phase 8, registry side: Google Play purchase verification.

  • POST /api/v1/play/purchases {game, itemId, purchaseToken} (account, rate limited): reads purchases.products.get for the game's package (GOOGLE_PLAY_PACKAGES) and the item's storeIds.play, requires obfuscatedExternalAccountId to be the caller's account, grants with source google_play under the purchase token, then acknowledges. Pending grants nothing; canceled revokes. Resending a token is safe and retries a failed acknowledgement.
  • Voided purchases: hourly voidedpurchases pass over the last 29 days revokes listed tokens; POST /api/v1/admin/play/reconcile runs it now.
  • Auth: service account JWT (RS256) to the key's token_uri, access token cached; stdlib only, no new dependency. No migration: the purchase token is the receipt id.
  • Off (503) without GOOGLE_PLAY_SERVICE_ACCOUNT and GOOGLE_PLAY_PACKAGES; a key that does not parse stops boot.
  • README: env vars, routes, flow, setup steps (service account key in rbw as google_play_service_account).

assumed: the client must set the obfuscated account id to the registry account id, and a purchase without one is refused (403), because otherwise anyone holding a token could claim it; the Unity IAP client is not built yet, so it can comply.
assumed: catalog store key stays play (already documented), the entitlement source is google_play (per the task).
assumed: a retired catalog item is still verified, since the purchase was paid for.
assumed: voids arrive by polling voidedpurchases (cadence playVoidedEvery), since no Pub/Sub topic for RTDN exists.

Checks: gofmt -l clean, go vet clean, go test ./... in registry/ against Docker Postgres and MinIO: 123 passed, 7 of them new (googleplay_test.go, fake Google token endpoint that checks the JWT signature plus the Developer API). One full run had TestForgejoSignInRefusesOthers fail once; it passed alone and on the next full run, and this diff does not touch admin sign-in.

Not here: Unity IAP client (waits for phase 2 client), App Store (no iOS build), creating the service account and Play products (human setup, README steps).

https://claude.ai/code/session_01BALUC5m2PmpaoFufz7oZVE

Premium phase 8, registry side: Google Play purchase verification. - `POST /api/v1/play/purchases` `{game, itemId, purchaseToken}` (account, rate limited): reads `purchases.products.get` for the game's package (`GOOGLE_PLAY_PACKAGES`) and the item's `storeIds.play`, requires `obfuscatedExternalAccountId` to be the caller's account, grants with source `google_play` under the purchase token, then acknowledges. Pending grants nothing; canceled revokes. Resending a token is safe and retries a failed acknowledgement. - Voided purchases: hourly `voidedpurchases` pass over the last 29 days revokes listed tokens; `POST /api/v1/admin/play/reconcile` runs it now. - Auth: service account JWT (RS256) to the key's `token_uri`, access token cached; stdlib only, no new dependency. No migration: the purchase token is the receipt id. - Off (503) without `GOOGLE_PLAY_SERVICE_ACCOUNT` and `GOOGLE_PLAY_PACKAGES`; a key that does not parse stops boot. - README: env vars, routes, flow, setup steps (service account key in rbw as `google_play_service_account`). assumed: the client must set the obfuscated account id to the registry account id, and a purchase without one is refused (403), because otherwise anyone holding a token could claim it; the Unity IAP client is not built yet, so it can comply. assumed: catalog store key stays `play` (already documented), the entitlement source is `google_play` (per the task). assumed: a retired catalog item is still verified, since the purchase was paid for. assumed: voids arrive by polling voidedpurchases (cadence `playVoidedEvery`), since no Pub/Sub topic for RTDN exists. Checks: `gofmt -l` clean, `go vet` clean, `go test ./...` in registry/ against Docker Postgres and MinIO: 123 passed, 7 of them new (googleplay_test.go, fake Google token endpoint that checks the JWT signature plus the Developer API). One full run had `TestForgejoSignInRefusesOthers` fail once; it passed alone and on the next full run, and this diff does not touch admin sign-in. Not here: Unity IAP client (waits for phase 2 client), App Store (no iOS build), creating the service account and Play products (human setup, README steps). https://claude.ai/code/session_01BALUC5m2PmpaoFufz7oZVE
POST /api/v1/play/purchases reads purchases.products for the game's package and the
item's storeIds.play, requires the purchase's obfuscated account id to be the caller,
grants with source google_play under the purchase token, and acknowledges. An hourly
voidedpurchases pass (and POST /api/v1/admin/play/reconcile) revokes voided tokens.
Service account JWT auth with the stdlib, no new dependency. Off (503) without
GOOGLE_PLAY_SERVICE_ACCOUNT and GOOGLE_PLAY_PACKAGES. README has the setup steps.

Claude-Session: https://claude.ai/code/session_01BALUC5m2PmpaoFufz7oZVE
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kblack0610/bnb-game-servers!19
No description provided.