Expanding from US iOS to Google Play and EU
You shipped web checkout on US iOS. The regulatory trend is global. Here's the phase-by-phase rollout - Google ECLP, EU DMA, Japan - with fee math, enrollment deadlines, and a per-region kill switch you can build once and expand incrementally.
What you'll walk away with: Phase-by-phase rollout plan for Google ECLP, EU Apple DMA, EU Google, and Japan. Enrollment checklists. Fee comparison table across every region. A per-region remote config structure that lets you build the architecture once and expand by flipping flags.
Phase 1: Google Play US - External Content Links Program
Enrollment Deadline: January 28, 2026
All US developers using external content links must enroll via Play Console before this date. Failure to enroll blocks external link functionality - your existing links will stop working, not just new ones.
Google's External Content Links Program (ECLP) is the Android equivalent of Apple's US storefront ruling. It allows US Google Play developers to link to external payment pages. The fee model is different from Apple: Google charges 10% on auto-renewing subscriptions and 20% on one-time purchases for transactions completed within 24 hours of a link click. At current Google Small Business rates (15%), ECLP saves you 5% on subscriptions.
ECLP Enrollment Checklist
BillingClient country code or device locale as fallback platform === 'android' && isUS && eclpEnabled branch to your PaywallGate component enable_web_checkout_android_us remote config flag (separate kill switch from iOS) assetlinks.json) for RevenueCat redemption links on Android Android Storefront Detection
Android doesn't have a direct equivalent of StoreKit 2's Storefront.current. The most reliable approach is BillingClient - it returns the user's Play Store country. Use Google Play Billing Library 6+ for this.
// Google Play Billing Library 6+
val billingClient = BillingClient.newBuilder(context)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(result: BillingResult) {
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
// countryCode is the user's Play Store country
val countryCode = billingClient.productDetails
.firstOrNull()?.subscriptionOfferDetails
?.firstOrNull()?.pricingPhases
?.pricingPhaseList?.firstOrNull()?.priceAmountMicros
// Simpler: use BillingClient.isFeatureSupported or locale
val isUS = Locale.getDefault().country == "US"
}
}
override fun onBillingServiceDisconnected() { /* retry */ }
}) Phase 2: EU - DMA Compliance
For EU users, your storefront detection needs to identify the EU App Store region - not just "non-US." The EU fee tiers are meaningfully better than standard IAP (30%) but not as good as US web (3% Stripe only). Still worth enabling, especially at volume.
Small Business Program note: If your app revenue is under $1M annually, Apple waives the 2% initial acquisition fee in the EU. You're paying 5–13% rather than 7–15%. Apply for the EU Small Business Program separately - it's not automatic if you're in the US SBP.
EU detection: use store country code, not device locale
A German user with a UK App Store account is in the UK fee tier, not the EU tier. A UK user since Brexit is not subject to DMA. Always use the store's country code - Storefront.current.countryCode on iOS - and maintain an explicit list of EU country codes to check against.
Phase 3: Japan and Rest of World
Japan has been regulating alternative billing since 2021. South Korea has had mandatory alternative billing since 2022. The trend is clear and one-directional. Build your architecture for per-region billing from the start - don't hardcode "US only" in a way that forces a refactor later.
The right architecture is a remote config structure that maps country codes to billing modes. You ship the logic once. Each new region goes live by updating the config, not the app.
Per-Region Remote Config Structure
{
"web_checkout": {
"enabled_regions": {
"USA": { "ios": true, "android": true },
"DEU": { "ios": true, "android": false },
"FRA": { "ios": true, "android": false },
"JPN": { "ios": false, "android": false },
"default": { "ios": false, "android": false }
},
"checkout_urls": {
"ios_default": "https://checkout.yourapp.com/ios",
"android_default": "https://checkout.yourapp.com/android"
}
}
} Gate Logic with Per-Region Config
type BillingMode = 'web' | 'iap';
function getBillingMode(
countryCode: string,
platform: 'ios' | 'android',
config: WebCheckoutConfig
): BillingMode {
const regionConfig =
config.enabled_regions[countryCode] ??
config.enabled_regions['default'];
return regionConfig[platform] ? 'web' : 'iap';
}
// Usage in PaywallGate
const mode = getBillingMode(storefront.countryCode, platform, config);
if (mode === 'web') {
return <WebCheckoutButton url={config.checkout_urls[`${platform}_default`]} />;
}
return <IAPPaywall />; Fee Comparison Across All Regions
At 10,000 subscribers paying $9.99/month, the difference between IAP and web is $324K annually. Every percentage point of effective rate matters. This table shows the real cost of each path - platform fee plus payment processor.
| Region + Platform | Method | Platform Fee | Effective Rate |
|---|---|---|---|
| US iOS (web) | Stripe checkout | 0% Apple | ~3% |
| US iOS (IAP) | Apple IAP | 15–30% Apple | 15–30% |
| US Android (ECLP) | Stripe + ECLP | 10% Google (future) | ~13% |
| US Android (IAP) | Google Play Billing | 15–30% Google | 15–30% |
| EU Apple (DMA) | Stripe + DMA | 5–13% Apple | ~8–16% |
| EU Google (DMA) | Stripe + DMA | 10% Google (subs) | ~13% |
| Rest of World | IAP only | 15–30% | 15–30% |
Multi-Region Kill Switch Strategy
Each region and platform has independent policy risk. EU Apple introduced an unexpected fee change. Google ECLP deadlines shifted. Japan's timeline is still unclear. Your kill switch architecture needs to be per-region, per-platform - not a single global flag.
When EU introduces a new fee you didn't plan for, you disable web for EU Apple, keep it for US iOS and EU Google. No app update. No emergency release. The config structure above handles this - each key in enabled_regions is an independent switch.
Log which region and billing mode each user sees
Tag every paywall impression event with storefront_country, billing_mode, and platform. When a policy changes, you need to know exactly how many users are affected before you flip the flag. You'll also need this data for compliance audits in the EU.
Official Documentation
Related Articles
Want a custom expansion roadmap for your app?
Let's talk through your stack, current regions, and the fastest path to each new platform.
Book a 15-min Call →