feat(kilo-pass): handle Play real-time notifications - #5598
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of Play replacement receipt identity (refund lookup by original purchase token, reconcile skip, deferred expiry/refund coverage) found no remaining issues. Files Reviewed (3 files)
Previous Review Summaries (6 snapshots, latest commit 361e4c9)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 361e4c9)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of post-commit Play acknowledgement, retry-without-regrant, and older-voided-order coverage found no remaining issues. Files Reviewed (2 files)
Previous review (commit 4d511fb)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of Play RTDN voided-purchase refunds, paid-order settlement, entitlement reconciliation, and bonus clawback found no remaining issues. Files Reviewed (4 files)
Previous review (commit a15a860)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of Play RTDN refund rollback, stale cancel/expiry reconciliation, JSON 400 handling, and restart cancel-flag replay found no remaining issues. Files Reviewed (4 files)
Previous review (commit 05b7315)Status: No Issues Found | Recommendation: Merge Executive SummaryPlay RTDN OIDC auth now verifies the configured service-account email claim; incremental review found no remaining issues. Files Reviewed (3 files)
Previous review (commit 280d7cc)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (6 files)
Fix these issues in Kilo Cloud Previous review (commit b909b8c)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (6 files)
Reviewed by grok-4.6 · Input: 148.5K · Output: 17.7K · Cached: 685.3K Review guidance: REVIEW.md from base branch |
Three defects in the Play notification path: - A failed credit clawback wrote a success audit and marked the revoke event processed, so refunded credits stayed with the buyer and Pub/Sub never redelivered. The clawback error now rolls the transaction back. - A delayed expiry or cancellation ended a subscription that had already renewed or recovered. Both now reconcile against the Play state the handler already fetched, which is the source of truth. - A request body that is not JSON returned 500, so Pub/Sub redelivered a message that can never succeed. It now returns 400.
Summary
Kilo Pass subscriptions bought on Google Play for Android now follow the store's subscription events.
A new processor turns Google Play Real-time Developer Notifications into Kilo Pass lifecycle actions. It decodes the base64 Pub/Sub payload, rejects a package-name mismatch, and routes each notification type: purchase, renewal, recovery, and restart complete the store purchase; canceled marks end-of-period cancellation; expired ends the subscription; revoked reverses the granted credits and ends it; unknown types are claimed and ignored. Each event claims a
kilo_pass_store_eventsrow before it acts, so a delayed or redelivered message cannot apply a purchase or a reversal twice, and the stored payload token is redacted.Files
apps/web/src/lib/kilo-pass/google-play-notifications.ts— new processor. It base64-decodes the Pub/Sub payload, checks the package name, fetches and decodes the Play subscription purchase, and claims thekilo_pass_store_eventsrow with a five-minute stale reclaim. It routes each notification type to its lifecycle action, reverses base, bonus, and promo credits on revocation, skips purchase completion when a processed revoked event already settled, and marks the event processed.The notification endpoint authenticates each Pub/Sub push before it reaches the processor. It verifies the OIDC bearer token against the
GOOGLE_PLAY_RTDN_PUSH_AUDIENCEaudience and now also requires the token's verifiedemailclaim to equalGOOGLE_PLAY_RTDN_PUSH_SERVICE_ACCOUNT_EMAIL, so an arbitrary GCP service account cannot invoke the billing webhook. A missing, invalid, or mismatched token returns 401, a malformed body returns 400, a fresh in-flight duplicate returns 503 for Pub/Sub to retry, and a processing error returns 500 without leaking details.Files
apps/web/src/app/api/kilo-pass/play/notifications/route.ts— new POST route. It parses the body shape, reads both environment values, verifies the bearer withOAuth2Client, checks the email andemail_verifiedclaims against the configured service account, delegates to the processor, and reports failures to Sentry.ENVIRONMENT.md— catalogs the newGOOGLE_PLAY_RTDN_PUSH_SERVICE_ACCOUNT_EMAILvalue.trackKiloPassPurchaseCompletednow accepts agoogle_playchannel next toapp_store, and the property branch now splits onstripe. A Google Play completion therefore emits the samekilo_pass_purchase_completedevent with the provider transaction, product, and environment properties.Files
apps/web/src/lib/kilo-pass/posthog-tracking.ts— addsgoogle_playto the store channel union and changes the property branch condition to compare againststripe.Tests: 3 files changed —
route.test.tsandgoogle-play-notifications.test.tsadded,posthog-tracking.test.tsupdated.Generated: none.
Verification
No manual test paths were run. No E2E report is attached for this level — a lower PR names its own change alone, and the tip PR carries the E2E report.
Visual Changes
Visual Changes: N/A
Reviewer Notes
GOOGLE_PLAY_RTDN_PUSH_AUDIENCEto the public HTTPS URL of the notifications endpoint. Runpnpm web:env set GOOGLE_PLAY_RTDN_PUSH_AUDIENCE.GOOGLE_PLAY_RTDN_PUSH_SERVICE_ACCOUNT_EMAILto the email of the Play service account that the Pub/Sub push uses. The route returns 401 for every notification until both values are set.Correctness verification, September 7
Provider state now updates grace, hold, pause, restore, recovery, and cancellation without replaying credits. Refunds reverse only the matching order and prevent later bonuses. Delayed refunded purchase events settle without endless retries.
4d511fb84.Stacked PRs — merge bottom to top. Each level shows only its own diff.
Runtime verification (E2E, user advocacy, simplify) runs on the tip PR over every level.
Every level keeps its own checks, its own bot review, and its own threads; each one is answered on its own PR.
Each level is its own deliverable: it builds and passes its own checks alone.
A finding on a level is repaired on that level, then carried upward with stack.sh forward.
android-iap-e895— feat(commerce): add Play Kilo Pass presentation flag #5581android-iap-e895-s2— feat(kilo-pass): add Google Play publisher SDK #5582android-iap-e895-s3— feat(kilo-pass): add Google Play purchase verifier #5586android-iap-e895-s4— feat(kilo-pass): handle Play real-time notifications #5598 ← this PRandroid-iap-e895-s5— feat(kilo-pass): add completePlayPurchase mutation #5608android-iap-e895-s6— feat(mobile): offer Google Play Kilo Pass purchase #5618 (tip)Expanded correctness coverage
16015d47374b30cf28e3b1b9b7f7635e4abcca86.Live evidence
parity-server-ack-interrupted,parity-server-ack-relaunch,parity-server-ack-restored,parity-final-server-route-active, andparity-final-server-route-restore.Explicit limits
Cleanup
Google Play tier changes
7b70df79fcaf375e362fce70b5c7f990380d42e0.Verification
Evidence and limits
Cleanup