Use this page with AI Copy this into your coding assistant and add your request.
Read https://openiap.dev/docs/types/external-purchase-link and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.
My request: [describe what customers should be able to do]See an example request → External Purchase Link Types External Purchase Link (iOS)# iOS-specific feature for redirecting users to an external website for payment using Apple's StoreKit ExternalPurchase API. Available from iOS 17.4+ (notice sheet) and iOS 18.1+ (custom links).
presentExternalPurchaseLinkIOS is iOS only and opens the URL with UIApplication.open, not a StoreKit API.
Native references: Apple · StoreKit ExternalPurchase · External purchase entitlement
Important: External purchase links bypass StoreKit completely. No purchaseUpdatedListener will fire. You must implement deep links and server-side verification.
External Purchase APIs# API Description Availability canPresentExternalPurchaseNoticeIOSCheck if external purchase notice sheet can be presented iOS 17.4+ presentExternalPurchaseNoticeSheetIOSPresent Apple's compliance notice sheet (required before external purchase) iOS 17.4+ presentExternalPurchaseLinkIOSOpen external purchase URL in Safari iOS 16+
Types# ExternalPurchaseNoticeResultIOS Name Type Summary resultExternalPurchaseNoticeActionUser action on the notice sheet (continue or dismissed) errorstring?Optional error message if presentation failed externalPurchaseTokenstring?External purchase token returned when result === 'continue'. Report to Apple's External Purchase Server API.
ExternalPurchaseLinkResultIOS Name Type Summary successbooleanWhether the user completed the external purchase flow errorstring?Optional error message if presentation failed
ExternalPurchaseCustomLinkTokenResultIOS# Returned by getExternalPurchaseCustomLinkTokenIOS (iOS 18.1+).
Name Type Summary tokenstringToken to send to Apple's External Purchase Server reporting API. errorstring?Populated when token creation fails.
ExternalPurchaseCustomLinkNoticeResultIOS# Returned by showExternalPurchaseCustomLinkNoticeIOS (iOS 18.1+).
Name Type Summary continuedbooleanWhether the user chose to continue to the external purchase. errorstring?Populated when the sheet failed to present.
// Result from presenting external purchase link
struct ExternalPurchaseLinkResultIOS {
let success: Bool
let error: String?
}
// Result from presenting notice sheet (iOS 17.4+)
struct ExternalPurchaseNoticeResultIOS {
let result: ExternalPurchaseNoticeAction
let error: String?
// External purchase token returned when result == .continue
let externalPurchaseToken: String?
}
// User action on notice sheet
enum ExternalPurchaseNoticeAction: String {
case `continue` = "continue"
case dismissed = "dismissed"
}Kotlin example // Result from presenting external purchase link (iOS-only via KMP)
data class ExternalPurchaseLinkResultIOS(
val success: Boolean,
val error: String? = null
)
// Result from presenting notice sheet (iOS 17.4+)
data class ExternalPurchaseNoticeResultIOS(
val result: ExternalPurchaseNoticeAction,
val error: String? = null,
// External purchase token returned when result == Continue
val externalPurchaseToken: String? = null
)
// User action on notice sheet
enum class ExternalPurchaseNoticeAction(val rawValue: String) {
Continue("continue"),
Dismissed("dismissed")
}TypeScript example // Result from presenting external purchase link
interface ExternalPurchaseLinkResultIOS {
success: boolean;
error?: string;
}
// Result from presenting notice sheet (iOS 17.4+)
interface ExternalPurchaseNoticeResultIOS {
result: ExternalPurchaseNoticeAction;
error?: string;
// External purchase token returned when result === 'continue'
// (must be reported to Apple's External Purchase Server API)
externalPurchaseToken?: string;
}
// User action on notice sheet
type ExternalPurchaseNoticeAction = 'continue' | 'dismissed';Dart example // Result from presenting external purchase link
class ExternalPurchaseLinkResultIOS {
final bool success;
final String? error;
}
// Result from presenting notice sheet (iOS 17.4+)
class ExternalPurchaseNoticeResultIOS {
final ExternalPurchaseNoticeAction result;
final String? error;
// External purchase token returned when result == continue
final String? externalPurchaseToken;
}
// User action on notice sheet
enum ExternalPurchaseNoticeAction {
`continue`('continue'),
dismissed('dismissed');
}C# (MAUI) example using OpenIap;
public sealed record ExternalPurchaseLinkResultIOS
{
public required bool Success { get; init; }
public string? Error { get; init; }
}
public sealed record ExternalPurchaseNoticeResultIOS
{
public required ExternalPurchaseNoticeAction Result { get; init; }
public string? Error { get; init; }
// External purchase token returned when Result == Continue.
public string? ExternalPurchaseToken { get; init; }
}
public enum ExternalPurchaseNoticeAction
{
Continue,
Dismissed,
}GDScript example # Result from presenting external purchase link
class_name ExternalPurchaseLinkResultIOS
var success: bool
var error: String # optional
# Result from presenting notice sheet (iOS 17.4+)
class_name ExternalPurchaseNoticeResultIOS
var result: int # ExternalPurchaseNoticeAction
var error: String # optional
var external_purchase_token: String # optional
# User action on notice sheet
enum ExternalPurchaseNoticeAction {
CONTINUE,
DISMISSED
}External Purchase Flow# The external purchase flow requires 3 steps for Apple compliance:
Check availability - Verify the device supports external purchasePresent notice sheet - Show Apple's required disclosureOpen external link - Redirect to your payment pageComplete Example# import OpenIAP
@available(iOS 17.4, *)
func handleExternalPurchase(externalUrl: String) async {
do {
// Step 1: Check if external purchase is available
let canPresent = try await OpenIapModule.shared.canPresentExternalPurchaseNoticeIOS()
guard canPresent else {
print("External purchase not available on this device")
return
}
// Step 2: Present Apple's compliance notice sheet
let noticeResult = try await OpenIapModule.shared.presentExternalPurchaseNoticeSheetIOS()
guard noticeResult.result == .continue else {
print("User dismissed the notice sheet")
return
}
// Step 3: Open external purchase link
let linkResult = try await OpenIapModule.shared.presentExternalPurchaseLinkIOS(externalUrl)
if linkResult.success {
print("User redirected to external payment")
// Implement deep linking to handle return from payment
} else if let error = linkResult.error {
print("Failed: \(error)")
}
} catch {
print("External purchase error: \(error)")
}
}Kotlin example import io.github.hyochan.kmpiap.kmpIapInstance
import io.github.hyochan.kmpiap.ExternalPurchaseNoticeAction
// External purchase is iOS-only. For iOS targets in KMP:
suspend fun handleExternalPurchase(externalUrl: String) {
// Step 1: Check if external purchase is available
val canPresent = kmpIapInstance.canPresentExternalPurchaseNoticeIOS()
if (!canPresent) {
println("External purchase not available on this device")
return
}
// Step 2: Present Apple's compliance notice sheet
val noticeResult = kmpIapInstance.presentExternalPurchaseNoticeSheetIOS()
if (noticeResult.result == ExternalPurchaseNoticeAction.Dismissed) {
println("User dismissed the notice sheet")
return
}
// Step 3: Open external purchase link
val linkResult = kmpIapInstance.presentExternalPurchaseLinkIOS(externalUrl)
if (linkResult.success) {
println("User redirected to external payment")
// Implement deep linking to handle return from payment
} else {
println("Failed: ${linkResult.error}")
}
}
// For Android, use the Billing Programs APIs instead.TypeScript example import {
canPresentExternalPurchaseNoticeIOS,
presentExternalPurchaseNoticeSheetIOS,
presentExternalPurchaseLinkIOS,
} from 'expo-iap';
async function handleExternalPurchase(externalUrl: string) {
// Step 1: Check if external purchase is available
const canPresent = await canPresentExternalPurchaseNoticeIOS();
if (!canPresent) {
console.log('External purchase not available on this device');
return;
}
// Step 2: Present Apple's compliance notice sheet
const noticeResult = await presentExternalPurchaseNoticeSheetIOS();
if (noticeResult.result === 'dismissed') {
console.log('User dismissed the notice sheet');
return;
}
// Step 3: Open external purchase link
const linkResult = await presentExternalPurchaseLinkIOS(externalUrl);
if (linkResult.success) {
console.log('User redirected to external payment');
// Implement deep linking to handle return from payment
} else {
console.error('Failed:', linkResult.error);
}
}Dart example import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
Future<void> handleExternalPurchase(String externalUrl) async {
final iap = FlutterInappPurchase.instance;
// Step 1: Check if external purchase is available
final canPresent = await iap.canPresentExternalPurchaseNoticeIOS();
if (!canPresent) {
print('External purchase not available on this device');
return;
}
// Step 2: Present Apple's compliance notice sheet
final noticeResult = await iap.presentExternalPurchaseNoticeSheetIOS();
if (noticeResult.result == ExternalPurchaseNoticeAction.Dismissed) {
print('User dismissed the notice sheet');
return;
}
// Step 3: Open external purchase link
final linkResult = await iap.presentExternalPurchaseLinkIOS(externalUrl);
if (linkResult.success) {
print('User redirected to external payment');
// Implement deep linking to handle return from payment
} else {
print('Failed: ${linkResult.error}');
}
}C# (MAUI) example using OpenIap;
using OpenIap.Maui;
async Task HandleExternalPurchaseAsync(string externalUrl)
{
// Step 1: Check if external purchase is available.
var canPresent =
await OpenIapClient.Query.CanPresentExternalPurchaseNoticeIOSAsync();
if (!canPresent)
{
Console.WriteLine("External purchase not available on this device");
return;
}
// Step 2: Present Apple's compliance notice sheet.
var noticeResult =
await OpenIapClient.Mutation.PresentExternalPurchaseNoticeSheetIOSAsync();
if (noticeResult.Result == ExternalPurchaseNoticeAction.Dismissed)
{
Console.WriteLine("User dismissed the notice sheet");
return;
}
// Step 3: Open external purchase link.
var linkResult =
await OpenIapClient.Mutation.PresentExternalPurchaseLinkIOSAsync(externalUrl);
if (linkResult.Success)
{
Console.WriteLine("User redirected to external payment");
// Implement deep linking to handle return from payment.
}
else
{
Console.WriteLine(
quot;Failed: {linkResult.Error}");
}
}
// For Android, use the Billing Programs APIs instead.GDScript example func handle_external_purchase(external_url: String):
# Step 1: Check if external purchase is available
var can_present = await iap.can_present_external_purchase_notice_ios()
if not can_present:
print("External purchase not available on this device")
return
# Step 2: Present Apple's compliance notice sheet
var notice_result = await iap.present_external_purchase_notice_sheet_ios()
if notice_result.result == ExternalPurchaseNoticeAction.DISMISSED:
print("User dismissed the notice sheet")
return
# Step 3: Open external purchase link
var link_result = await iap.present_external_purchase_link_ios(external_url)
if link_result.success:
print("User redirected to external payment")
# Implement deep linking to handle return from payment
else:
print("Failed: %s" % link_result.error)Requirements# Requirement Details Platform iOS 17.4+ (notice sheet), iOS 18.1+ (custom links) Entitlement App must have StoreKit external purchase entitlement from Apple Deep Linking Implement deep linking for app return flow after payment Verification Handle purchase verification on your backend (no StoreKit receipt)
Android alternative: For Android, use isBillingProgramAvailableAndroid , launchExternalLinkAndroid , and createBillingProgramReportingDetailsAndroid .