# Upgrade-only rollout (Android prov1 → prov2)

Deploy to staging first. No automatic deployment, live database mutation, new
migration, or production environment change was performed during implementation.

**September 20 follow-up:** real/sandbox receipt isolation and a read-only Google
diagnostic are now available. Follow [environment setup](android-purchase-environment-isolation.md)
before retesting an account that also owns a real production subscription.

**September 22 follow-up:** the cancelled-subscription preflight now handles
Google's omitted renewal flag. See [the fix and staging retest guide](android-cancelled-upgrade-fix.md).
This is a backend-only parser fix; it does not disable environment isolation.

## Configuration

```dotenv
ANDROID_PURCHASE_NEW_FLOW_ENABLED=true
ANDROID_PURCHASE_MIN_APP_VERSION=1.4.6
ANDROID_SUBSCRIPTION_UPGRADES_ENABLED=true
```

The new flag defaults to false. Refresh Laravel's configuration through the
existing deployment process (`php artisan config:clear` if needed). Existing
purchase schema, Google credentials and RTDN routing from earlier steps remain
required; no additional Google Play or Pub/Sub configuration is introduced.

## Compatibility contract

`POST /api/android-subscription-upgrade/check` is authenticated and rate-limited.
It accepts receipt, target product and native app version plus
`subscription_change_support=prov1_to_prov2_v1`. It reads Google's current state
and purchase ownership; it does not write purchases or acknowledge/cancel payment.

Normal new subscriptions use the existing store request. An existing token can
be omitted only when Google explicitly reports it expired with past expiry.
Unknown, pending, different-account, ambiguous and unverifiable ownership do not
fall back to new checkout. Only a verified active prov1 → prov2 change is enabled.

Receipt handling requires the new explicit support marker, version 1.4.6+, and
eligibility under the existing minimum/main switch. Old or non-opted-in clients
retain their prior handlers. For eligible upgrades, Google's linkedPurchaseToken
must identify the same user's prov1 purchase. Persisting prov2 and retiring prov1
share a database transaction. Pending upgrades keep existing access.

## Rollback

- New flag false: stop starting upgrades; ordinary new purchases remain available.
  Keep validation/recovery of already-paid upgrades available.
- Existing main switch false: use the existing compatibility handler, including
  disabling the opt-in retirement logic. Store changes already completed are not
  reversed. Existing RTDN expiry handling remains necessary for those purchases.
- Raising the existing minimum gates upgrade-aware handling just like v2. Test
  in-flight recovery when using this emergency option.
- Webhook handlers, iOS endpoints, subscription IDs and response URLs are unchanged.

## Before production

Follow the app's `docs/ANDROID_UPGRADES_ONLY_TEST.md`: ordinary checkout, active
upgrade, cancellation, restore/restart, pending recovery, renewal/old-token expiry,
account mismatch, expired sandbox cache and an older 1.4.4 APK. The prior DF-DFERH-01
incident is not considered resolved until device testing succeeds.

Automated verification: 194 backend unit tests passed (1,320 assertions), including
23 upgrade cases. Database tests replace both default and explicit model MySQL
connections with in-memory SQLite; they do not use staging or production data.
