Skip to content

Sell Premium with Google Play Billing in a dedicated Play Store build - #125

Open
micahflee wants to merge 2 commits into
mainfrom
124-play-billing
Open

micahflee wants to merge 2 commits into
mainfrom
124-play-billing

Conversation

@micahflee

Copy link
Copy Markdown
Member

Closes #124.

Summary

Google Play removed the app for leading users to a payment method other than Google Play's billing system. This splits Android into two builds and makes Google Play Billing the only purchase path in the Play one:

 Android builds
-├── systems.lockdown.cydmobile        # one build, external Stripe checkout
+├── systems.lockdown.cydmobile        # Play build: Google Play Billing only
+│   └── ANDROID_DISTRIBUTION=play     # (default) → PremiumUpsellMode "play_billing"
+└── systems.lockdown.cydmobile.alt    # non-Play build: keeps external checkout
+    └── ANDROID_DISTRIBUTION=other

The Play purchase flow mirrors the iOS App Store flow on top of the cyd-server endpoints from lockdown-systems/cyd-server#198:

purchasePremium(plan)
  GET /user/google-play/subscription     → obfuscated_account_id
  requestPurchase(google)                → skus + obfuscatedAccountId + base-plan offerToken
purchaseUpdatedListener(purchase)
  POST /user/google-play/subscription    → {purchase_token}
  finishTransaction                      → only after cyd-server verifies and grants Premium

Manage links (CydAccountBar, AccountMenuSheet, paywall components) open the Google Play subscription center in the Play build; everything else keeps the dashboard URL. The iOS App Store path is behaviorally unchanged.

Evidence

  • Before: no Google Play purchase path; Android builds used the external Stripe checkout (the Payments policy violation).
    After: full jest suite green (82 suites, 942 passed), typecheck and repo lint clean.

New tests cover each layer:

  • constants/__tests__/subscriptions.test.ts — mode selection for play/other/ios builds and the product catalogs
  • services/__tests__/cyd-api-client.test.ts — GET/POST /user/google-play/subscription, including surfacing the server's 409 account-mismatch reason
  • contexts/__tests__/CydAccountProvider.play.test.tsx — purchase request carries the obfuscated ID and base-plan offer token; a purchase is acknowledged only after server verification grants Premium; a rejected purchase is never acknowledged; restore; Play subscription-center manage URL
  • services/__tests__/app-config.test.ts — application-ID and google-services split by ANDROID_DISTRIBUTION
  • Play-mode UI tests in the paywall and account-bar suites

End-to-end testing against the dev API via the Play Console internal track still needs the manual Play Console prerequisites below.

Manual prerequisites (owner)

  • Play Console: premium_annual / premium_monthly subscription products, license testers
  • New keystore for the .alt build (EAS will offer to create one per application ID; CI expects ANDROID_ALT_* secrets)
  • Register systems.lockdown.cydmobile.alt in Firebase and commit its config as google-services-alt.json — required for push notifications on the alt build; the build_android_alt CI job fails with instructions until it exists

Deliberate additions beyond the issue plan

  • Firebase app for the alt build: the google-services Gradle plugin fails on a package-name mismatch, so the alt application ID needs its own google-services file; without it the alt build cannot compile.
  • The GitHub Release APK is now the alt build, not the Play build. The Play build job no longer produces an APK; distributing the Play build outside Play would defeat the two-build split.
  • Naming generalization (storePurchaseState, restoreStorePurchases): the issue plan says to generalize the StoreKit code paths to Play; one shared purchase-state surface for both storefronts is that generalization. The iOS behavior and its tests are unchanged.

Merge Danger

Door: two-way — the code is trivially revertible, but see the release coordination below.

Blast Radius: release-wide. Until the coordinated release happens, the Play build cannot be submitted (needs the Play Console products + cyd-server deployed to prod), and the first tag after merge will fail the new build_android_alt CI job until the ANDROID_ALT_* secrets and google-services-alt.json exist. Existing direct-download APK users cannot upgrade in place to the .alt build (different application ID).

(This was written by an LLM.)

…#124)

Google Play removed the app for leading users to a payment method other
than Google Play's billing system. Split Android into two builds:

- Play build (systems.lockdown.cydmobile, ANDROID_DISTRIBUTION=play, the
  default): Google Play Billing is the only purchase path. Purchases
  carry the account's server-issued obfuscated ID, cyd-server verifies
  each purchase token, and the purchase is acknowledged only after
  verification grants Premium. Manage links open the Google Play
  subscription center.
- Non-Play build (systems.lockdown.cydmobile.alt, ANDROID_DISTRIBUTION=
  other): keeps the external Stripe checkout, direct-download only, with
  its own EAS profile, signing, and release workflow job.

Generalizes the existing App Store purchase flow in
CydAccountProvider to both storefronts (storePurchaseState,
restoreStorePurchases); the iOS path is behaviorally unchanged.
- Surface the server's own reason (e.g. the 409 account-mismatch
  message) when the Google Play subscription endpoints reject a
  request, instead of a generic failure string.
- Prefer a product's plain base-plan offer (no offerId) when starting a
  Play purchase, so a promotional offer in the Play Console can't
  change what the paywall's shown price buys.
- Replace the preposition-composing helper with literal per-storefront
  messages, and drop the inert GOOGLE_PLAY_* product IDs from the alt
  EAS profile.
- Document that EAS tracks the alt keystore per application ID.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Play Store: Violation of Payments policy

1 participant