External Purchase
Overview
External purchase allows you to redirect users to external payment systems instead of using platform-native billing (StoreKit on iOS, Google Play Billing on Android). This enables alternative payment methods and can reduce platform fees.
Platform Support
| Platform | Feature Name | Minimum OS Version | Native Framework |
|---|---|---|---|
| iOS | External Purchase URL | iOS 17.4+ (Notice Sheet) iOS 18.2+ (New APIs) | StoreKit 2 |
| Android | Alternative Billing / Billing Programs | Android 6.0+ (API 23) | Google Play Billing 6.2+ (legacy), 8.2.0+ (External Content Links), 8.2.1+ (External Offers), 8.3.0+ (External Payments) |
Common Requirements#
Both platforms require the following infrastructure to support external purchases:
Backend Verification System
- Payment gateway integration
- Purchase verification endpoint
- Entitlement management system
- Transaction logging and auditing
Platform-Specific Implementation#
iOS - External Purchase
iOS supports external purchase through StoreKit's External Purchase API. The notice sheet presents within the app and returns results immediately - no browser redirect required.
Basic Usage (iOS 18.2+)
iOS 18.2+ provides dedicated APIs for external purchase flow with notice sheet and link presentation:
import OpenIAP
@available(iOS 18.2, *)
func handleExternalPurchaseFlow() async {
let externalUrl = "https://your-payment-site.com/checkout"
do {
// Step 1: Check if notice sheet can be presented
let canPresent = try await OpenIapModule.shared
.canPresentExternalPurchaseNoticeIOS()
guard canPresent else {
print("External purchase notice sheet not available")
return
}
// Step 2: Present notice sheet (Apple's info sheet)
let noticeResult = try await OpenIapModule.shared
.presentExternalPurchaseNoticeSheetIOS()
if noticeResult.result == .continue {
// Step 3: Present external purchase link
let linkResult = try await OpenIapModule.shared
.presentExternalPurchaseLinkIOS(externalUrl)
if linkResult.success {
print("User acknowledged external purchase")
// User approved external purchase
// Call your backend API to initiate purchase
// await yourBackend.createPurchase(productId, userId)
} else {
print("External purchase link failed: \(linkResult.error ?? "")")
}
} else {
print("User dismissed notice sheet")
}
} catch {
print("External purchase error: \(error)")
}
}Requirements
- iOS 17.4+ - Minimum version for External Purchase API
- iOS 18.2+ - Recommended for dedicated external purchase APIs (
canPresentExternalPurchaseNoticeIOS,presentExternalPurchaseNoticeSheetIOS,presentExternalPurchaseLinkIOS) - StoreKit 2 - Uses StoreKit 2 framework
- Entitlement required -
com.apple.developer.storekit.external-purchaseentitlement must be configured in App Store Connect - Country code configuration - Must specify supported country codes in
SKExternalPurchasearray in Info.plist. Only available in EU countries and South Korea. See Apple Documentation - No deep linking required - Notice sheet presents within the app and returns results immediately
Configuration (iOS)
Add entitlement and country codes in Info.plist. Use lowercase ISO 3166-1 alpha-2 country codes:
1. SKExternalPurchase (Required)
Specify countries where your app supports external purchases:
<key>SKExternalPurchase</key>
<array>
<!-- South Korea -->
<string>kr</string>
<!-- Netherlands -->
<string>nl</string>
<!-- EU Countries (examples) -->
<string>de</string> <!-- Germany -->
<string>fr</string> <!-- France -->
<string>it</string> <!-- Italy -->
<string>es</string> <!-- Spain -->
<!-- Add other EU country codes as needed -->
</array>
<!-- Entitlement required -->
<!-- com.apple.developer.storekit.external-purchase: true -->2. SKExternalPurchaseLink (Optional - iOS 15.4+)
Provide destination URLs for each country. Required if using com.apple.developer.storekit.external-purchase-link entitlement:
<key>SKExternalPurchaseLink</key>
<dict>
<key>nl</key>
<string>https://your-site.com/checkout</string>
<key>de</key>
<string>https://your-site.com/de/checkout</string>
<key>kr</key>
<string>https://your-site.com/kr/checkout</string>
</dict>3. SKExternalPurchaseMultiLink (iOS 17.5+)
Provide multiple URLs (up to 5) for each country. Use this instead of SKExternalPurchaseLink for iOS 17.5+:
<key>SKExternalPurchaseMultiLink</key>
<dict>
<key>es</key>
<array>
<string>https://your-site.com/es1</string>
<string>https://your-site.com/new-user-es</string>
<string>https://your-site.com/seasonal-sale-es</string>
</array>
<key>fr</key>
<array>
<string>https://your-site.com/fr</string>
<string>https://your-site.com/global-sale</string>
</array>
<key>it</key>
<array>
<string>https://your-site.com/global-sale</string>
</array>
</dict>4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
For custom links to communicate and promote offers. Required if using com.apple.developer.storekit.external-purchase-link entitlement and ExternalPurchaseCustomLink API:
<key>SKExternalPurchaseCustomLinkRegions</key>
<array>
<string>de</string> <!-- Germany -->
<string>fr</string> <!-- France -->
<string>nl</string> <!-- Netherlands -->
<!-- Add other EU country codes -->
</array>5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
For music streaming apps only. Required if using com.apple.developer.storekit.external-purchase-link-streaming entitlement:
<key>SKExternalPurchaseLinkStreamingRegions</key>
<array>
<string>at</string> <!-- Austria -->
<string>de</string> <!-- Germany -->
<string>fr</string> <!-- France -->
<string>nl</string> <!-- Netherlands -->
<string>is</string> <!-- Iceland -->
<string>no</string> <!-- Norway -->
<!-- Add other EU country codes -->
</array>Common Issues (iOS)
| Issue | Cause | Solution |
|---|---|---|
| Notice Sheet Not Showing | Missing entitlement or country code | Add com.apple.developer.storekit.external-purchase entitlement and configure SKExternalPurchase array |
| canPresent Returns False | Device region not supported | Check device is in supported country (EU, NL, KR) |
| FeatureNotSupported Error | iOS version too old | Requires iOS 17.4+ (notice sheet), iOS 18.2+ (new APIs) |
Implementation Flow#
The complete external purchase flow involves coordination between your app, external website, and backend. The flow differs between iOS and Android:
iOS Flow (iOS 18.2+)
| Step | API / Action | Description |
|---|---|---|
| 1 | canPresentExternalPurchaseNoticeIOS() | Check if device supports external purchase notice sheet |
| 2 | presentExternalPurchaseNoticeSheetIOS() | Show Apple's notice sheet informing user about external purchase |
| 3 | User Action | User taps "Continue" or dismisses the notice sheet |
| 4 | presentExternalPurchaseLinkIOS(url) | If user continued, present external purchase link (user acknowledges external purchase) |
| 5 | Backend Payment Processing | App calls backend API to process payment with payment gateway (Stripe, PayPal, etc.) and grant entitlements |
| 6 | Unlock Content | App unlocks purchased content after backend confirmation |
Complete Examples#
For complete, production-ready examples with full UI implementation, please refer to the native example apps:
iOS (SwiftUI)
- AlternativeBillingScreen.swift - Complete iOS 18.2+ implementation with notice sheet and external purchase link presentation
This example demonstrates:
- Complete UI implementation with state management
- Notice sheet presentation (iOS 18.2+)
- Deep link handling and verification flow
- Error handling and user feedback
- Production-ready code patterns
Related Documentation#
- External Purchase Types - Type definitions and parameters
- Onside Store Setup - alternative marketplace flow for
expo-iap - Alternative Billing Types - Type definitions and config
- Request Purchase API - API reference for requestPurchase
- Error Codes - Error handling reference
Native References#
- Apple · StoreKit External Purchase entitlement
- Apple · StoreKit ExternalPurchase — notice sheet and link APIs
- Google · User Choice Billing
- Google · Alternative billing in Play Billing Library
- Google · Play Billing 8.3.0 — External Payments (Japan)