feat(kilo-pass): add completePlayPurchase mutation - #5608
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (1 files)
Previous Review Summary (commit ad753a5)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit ad753a5)Status: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Reviewed by grok-4.6 · Input: 161.8K · Output: 10.5K · Cached: 356.9K Review guidance: REVIEW.md from base branch |
| }); | ||
| } | ||
| try { | ||
| const purchase = await verifyGooglePlayKiloPassPurchase(input.purchaseToken); |
There was a problem hiding this comment.
bot: completePlayPurchase accepts provider tokens without validating Google Play's subscription lifecycle state, so a revoked/non-entitled purchase with a future expiry can be activated.
Suggested fix: Make verifyGooglePlayKiloPassPurchase (or an immediately adjacent completion guard) allowlist only Google Play states that grant entitlement before calling completeStoreKiloPassPurchase. Do not rely on a future expiryTimeMs alone: subscriptionState is decoded but currently unused, while the shared completion service writes status: 'active' on both insert and conflict update. Add verifier-level tests covering rejected revoked/on-hold/paused/pending states plus the valid active and canceling-but-unexpired cases.
Summary
completePlayPurchaseis a new mutation that finishes a Google Play Kilo Pass purchase. It verifies the Play purchase token, rejects an account-token mismatch, and completes the store purchase through the shared path. It records the completion on thegoogle_playtracking channel, and the newassertGooglePlayAccountTokenMatchesUserandmapPlayCompletionErrorhelpers give Play the account guard and error mapping the App Store already has.The
preflightPurchasemutation now handles the Play storefront. It resolves the product bygoogleProductIdand returnsunknown_productwhen that ID is missing or unknown, and it applies the single-account ownership guard through the untrustedgooglePurchaseTokenwith anowned_by_another_accountresult. On Play, thealready_subscribedcheck excludes a live GooglePlay subscription but still refuses a live Stripe or App Store subscription.The presentation and preflight input schemas add the optional
supportsNativePlayKiloPassflag; preflight also addsgoogleProductIdandgooglePurchaseToken. Clients that omit the new fields keep today's presentation and checks.completeAppStorePurchasenow rejects anything other than the exact iOS, App Store, Kilo Pass combination because the shared native-IAP gate now also admits Play.Files
apps/web/src/routers/kilo-pass-router.ts— imports the Google Play product lookup and verifier; addssupportsNativePlayKiloPass,googleProductId, andgooglePurchaseTokento the input schemas and threadssupportsNativePlayKiloPassthrough the presentation and preflight calls; adds the Play account-token assertion and the Play completion error mapping; resolves Play products bygoogleProductId, guards Play purchase tokens by owner, and excludes GooglePlay from the Play already-subscribed check; narrows the App Store completion guard to the exact iOS/App Store/Kilo Pass combination; adds thecompletePlayPurchasemutation that verifies the Play token, matches the account token, completes the store purchase, and tracks ongoogle_play.Tests: 1 test file updated —
apps/web/src/routers/kilo-pass-router.test.ts, 389 insertions.Generated: none.
Verification
No E2E report is attached for this level, and no manual test paths were run. Runtime verification runs once on the tip PR (level 6) after all levels land, and this level is reviewed on its own diff.
Visual Changes
Visual Changes: N/A
Reviewer Notes
Human steps: none.
Notes: E2E: bot-e2e — runtime verification runs once on the tip PR (level 6) after all levels land. This level is reviewed on its own diff.
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 #5598android-iap-e895-s5— feat(kilo-pass): add completePlayPurchase mutation #5608 ← this PRandroid-iap-e895-s6— feat(mobile): offer Google Play Kilo Pass purchase #5618 (tip)