Skip to content

Commit 8c0e294

Browse files
hyochanclaude
andauthored
feat: sync with openiap v1.3.16 - ExternalPurchaseCustomLink API (iOS 18.1+) (#613)
## Summary - Sync with OpenIAP v1.3.16 (gql: 1.3.16, apple: 1.3.14, google: 1.3.27) - Add support for Apple's **ExternalPurchaseCustomLink** API (iOS 18.1+) - `isEligibleForExternalPurchaseCustomLinkIOS()` - Check eligibility - `getExternalPurchaseCustomLinkTokenIOS(tokenType)` - Get token for reporting - `showExternalPurchaseCustomLinkNoticeIOS(noticeType)` - Show disclosure notice ## Changes - Update openiap-versions.json - Regenerate Dart types with ExternalPurchaseCustomLink types - Add iOS native implementation (FlutterInappPurchasePlugin.swift) - Add Dart API wrappers (flutter_inapp_purchase.dart) - Update queryHandlers and mutationHandlers with new APIs - Add release blog post for v8.2.5 - Update llms.txt and llms-full.txt ## OpenIAP Versions | Package | Version | |---------|---------| | openiap-gql | 1.3.16 | | openiap-apple | 1.3.14 | | openiap-google | 1.3.27 | ## Test plan - [x] `flutter analyze` passes - [x] `flutter test` passes (198 tests) - [x] Verified iOS native code compiles 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Expanded public API surface for iOS 18.1+ to support ExternalPurchaseCustomLink flows: eligibility checks, token retrieval, and disclosure notices. * **Documentation** * Added detailed docs with end-to-end guidance, examples, and installation notes for external purchase custom link integration. * **Tests** * Added unit tests covering the new iOS external purchase custom link APIs and platform behaviors. * **Chores** * Updated related version metadata for platform integrations. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent f60c34f commit 8c0e294

File tree

8 files changed

+810
-12
lines changed

8 files changed

+810
-12
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
---
2+
slug: 8.2.5
3+
title: 8.2.5 - ExternalPurchaseCustomLink API (iOS 18.1+)
4+
authors: [hyochan]
5+
tags: [release, ios, storekit, external-purchase, ios-18]
6+
date: 2026-01-26
7+
---
8+
9+
# 8.2.5 Release Notes
10+
11+
flutter_inapp_purchase 8.2.5 adds support for Apple's **ExternalPurchaseCustomLink** API (iOS 18.1+) for apps using custom external purchase links.
12+
13+
<!-- truncate -->
14+
15+
## New Features
16+
17+
### ExternalPurchaseCustomLink API (iOS 18.1+)
18+
19+
The ExternalPurchaseCustomLink API enables apps to use custom external purchase links with token-based reporting to Apple. This is for apps that have been granted an entitlement to link out to external purchases.
20+
21+
Reference: [ExternalPurchaseCustomLink Documentation](https://developer.apple.com/documentation/storekit/externalpurchasecustomlink)
22+
23+
### New APIs
24+
25+
#### `isEligibleForExternalPurchaseCustomLinkIOS()`
26+
27+
Check if your app is eligible to use the ExternalPurchaseCustomLink API.
28+
29+
```dart
30+
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
31+
32+
final iap = FlutterInappPurchase.instance;
33+
final isEligible = await iap.isEligibleForExternalPurchaseCustomLinkIOS();
34+
if (isEligible) {
35+
// App can use custom external purchase links
36+
}
37+
```
38+
39+
#### `getExternalPurchaseCustomLinkTokenIOS(tokenType)`
40+
41+
Get an external purchase token for reporting to Apple's External Purchase Server API.
42+
43+
```dart
44+
// For new customer acquisition
45+
final acquisitionResult = await iap.getExternalPurchaseCustomLinkTokenIOS(
46+
ExternalPurchaseCustomLinkTokenTypeIOS.Acquisition,
47+
);
48+
if (acquisitionResult.token != null) {
49+
// Report to Apple's External Purchase Server API
50+
await reportToApple(acquisitionResult.token!);
51+
}
52+
53+
// For existing customer services
54+
final servicesResult = await iap.getExternalPurchaseCustomLinkTokenIOS(
55+
ExternalPurchaseCustomLinkTokenTypeIOS.Services,
56+
);
57+
if (servicesResult.token != null) {
58+
await reportToApple(servicesResult.token!);
59+
}
60+
```
61+
62+
#### `showExternalPurchaseCustomLinkNoticeIOS(noticeType)`
63+
64+
Display the system disclosure notice sheet before linking out to external purchases.
65+
66+
```dart
67+
final result = await iap.showExternalPurchaseCustomLinkNoticeIOS(
68+
ExternalPurchaseCustomLinkNoticeTypeIOS.Browser,
69+
);
70+
if (result.continued) {
71+
// User agreed to continue to external purchase
72+
// Now open your external purchase link
73+
await launchUrl(Uri.parse('https://your-store.com/checkout'));
74+
} else {
75+
// User cancelled
76+
}
77+
```
78+
79+
### Updated: `presentExternalPurchaseNoticeSheetIOS()`
80+
81+
Now returns `externalPurchaseToken` when user continues to external purchase.
82+
83+
```dart
84+
final result = await iap.presentExternalPurchaseNoticeSheetIOS();
85+
if (result.result == ExternalPurchaseNoticeAction.Continue) {
86+
print('Token: ${result.externalPurchaseToken}');
87+
// Report this token to Apple's External Purchase Server API
88+
}
89+
```
90+
91+
### New Types
92+
93+
```dart
94+
// Token types for getExternalPurchaseCustomLinkTokenIOS
95+
enum ExternalPurchaseCustomLinkTokenTypeIOS {
96+
Acquisition('acquisition'),
97+
Services('services');
98+
}
99+
100+
// Notice types for showExternalPurchaseCustomLinkNoticeIOS
101+
enum ExternalPurchaseCustomLinkNoticeTypeIOS {
102+
Browser('browser');
103+
}
104+
105+
// Result of token retrieval
106+
class ExternalPurchaseCustomLinkTokenResultIOS {
107+
final String? token;
108+
final String? error;
109+
}
110+
111+
// Result of showing notice
112+
class ExternalPurchaseCustomLinkNoticeResultIOS {
113+
final bool continued;
114+
final String? error;
115+
}
116+
```
117+
118+
### Complete External Purchase Custom Link Flow
119+
120+
```dart
121+
import 'dart:io';
122+
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
123+
import 'package:url_launcher/url_launcher.dart';
124+
125+
class ExternalPurchaseCustomLinkExample {
126+
final iap = FlutterInappPurchase.instance;
127+
bool isEligible = false;
128+
129+
Future<void> checkEligibility() async {
130+
if (!Platform.isIOS) return;
131+
isEligible = await iap.isEligibleForExternalPurchaseCustomLinkIOS();
132+
}
133+
134+
Future<void> handleExternalPurchase() async {
135+
if (!isEligible) return;
136+
137+
// Step 1: Show disclosure notice
138+
final noticeResult = await iap.showExternalPurchaseCustomLinkNoticeIOS(
139+
ExternalPurchaseCustomLinkNoticeTypeIOS.Browser,
140+
);
141+
if (!noticeResult.continued) {
142+
print('User cancelled');
143+
return;
144+
}
145+
146+
// Step 2: Get token for reporting
147+
final tokenResult = await iap.getExternalPurchaseCustomLinkTokenIOS(
148+
ExternalPurchaseCustomLinkTokenTypeIOS.Acquisition,
149+
);
150+
if (tokenResult.error != null) {
151+
print('Failed to get token: ${tokenResult.error}');
152+
return;
153+
}
154+
155+
// Step 3: Open external purchase link
156+
await launchUrl(Uri.parse('https://your-store.com/checkout'));
157+
158+
// Step 4: After purchase completes, report to Apple
159+
if (tokenResult.token != null) {
160+
await reportExternalPurchaseToApple(tokenResult.token!);
161+
}
162+
}
163+
}
164+
```
165+
166+
## OpenIAP Updates
167+
168+
| Package | Version |
169+
|---------|---------|
170+
| openiap-gql | 1.3.16 |
171+
| openiap-apple | 1.3.14 |
172+
| openiap-google | 1.3.27 |
173+
174+
## Installation
175+
176+
```yaml
177+
dependencies:
178+
flutter_inapp_purchase: ^8.2.5
179+
```
180+
181+
## References
182+
183+
- [Apple ExternalPurchaseCustomLink Documentation](https://developer.apple.com/documentation/storekit/externalpurchasecustomlink)
184+
- [OpenIAP Release Notes](https://www.openiap.dev/docs/updates/notes#gql-1-3-16-apple-1-3-14)
185+
186+
Questions or feedback? Reach out via [GitHub issues](https://github.com/hyochan/flutter_inapp_purchase/issues).

docs/static/llms-full.txt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,65 @@ Presents external purchase notice sheet (iOS 18.2+).
12101210
Future<ExternalPurchaseNoticeResultIOS> presentExternalPurchaseNoticeSheetIOS() async
12111211
```
12121212

1213+
#### isEligibleForExternalPurchaseCustomLinkIOS()
1214+
1215+
Check if app is eligible for ExternalPurchaseCustomLink API (iOS 18.1+).
1216+
1217+
```dart
1218+
Future<bool> isEligibleForExternalPurchaseCustomLinkIOS() async
1219+
```
1220+
1221+
#### getExternalPurchaseCustomLinkTokenIOS()
1222+
1223+
Get external purchase token for reporting to Apple (iOS 18.1+).
1224+
1225+
```dart
1226+
Future<ExternalPurchaseCustomLinkTokenResultIOS> getExternalPurchaseCustomLinkTokenIOS(
1227+
ExternalPurchaseCustomLinkTokenTypeIOS tokenType,
1228+
) async
1229+
```
1230+
1231+
**Parameters:**
1232+
- `tokenType` - Token type: `Acquisition` (new customers) or `Services` (existing customers)
1233+
1234+
#### showExternalPurchaseCustomLinkNoticeIOS()
1235+
1236+
Show ExternalPurchaseCustomLink notice sheet (iOS 18.1+).
1237+
1238+
```dart
1239+
Future<ExternalPurchaseCustomLinkNoticeResultIOS> showExternalPurchaseCustomLinkNoticeIOS(
1240+
ExternalPurchaseCustomLinkNoticeTypeIOS noticeType,
1241+
) async
1242+
```
1243+
1244+
**Parameters:**
1245+
- `noticeType` - Notice type: `Browser` (external purchases displayed in browser)
1246+
1247+
**Example:**
1248+
1249+
```dart
1250+
// Check eligibility
1251+
final isEligible = await iap.isEligibleForExternalPurchaseCustomLinkIOS();
1252+
1253+
if (isEligible) {
1254+
// Show disclosure notice
1255+
final notice = await iap.showExternalPurchaseCustomLinkNoticeIOS(
1256+
ExternalPurchaseCustomLinkNoticeTypeIOS.Browser,
1257+
);
1258+
if (notice.continued) {
1259+
// Get token for reporting to Apple
1260+
final result = await iap.getExternalPurchaseCustomLinkTokenIOS(
1261+
ExternalPurchaseCustomLinkTokenTypeIOS.Acquisition,
1262+
);
1263+
if (result.token != null) {
1264+
// Open external link and report to Apple's External Purchase Server API
1265+
await launchUrl(Uri.parse('https://your-store.com/checkout'));
1266+
await reportExternalPurchaseToApple(result.token!);
1267+
}
1268+
}
1269+
}
1270+
```
1271+
12131272
### Android-Specific Methods
12141273

12151274
#### acknowledgePurchaseAndroid()

docs/static/llms.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,31 @@ for (final purchase in purchases) {
410410
}
411411
```
412412

413+
### iOS External Purchase Custom Link (v8.2.5+, iOS 18.1+)
414+
415+
For apps with custom external purchase links entitlement:
416+
417+
```dart
418+
// Check eligibility
419+
final isEligible = await iap.isEligibleForExternalPurchaseCustomLinkIOS();
420+
421+
if (isEligible) {
422+
// Show disclosure notice
423+
final notice = await iap.showExternalPurchaseCustomLinkNoticeIOS(
424+
ExternalPurchaseCustomLinkNoticeTypeIOS.Browser,
425+
);
426+
if (notice.continued) {
427+
// Get token for reporting to Apple
428+
final result = await iap.getExternalPurchaseCustomLinkTokenIOS(
429+
ExternalPurchaseCustomLinkTokenTypeIOS.Acquisition,
430+
);
431+
if (result.token != null) {
432+
// Open external link and report to Apple's External Purchase Server API
433+
}
434+
}
435+
}
436+
```
437+
413438
## Error Handling
414439

415440
```dart

0 commit comments

Comments
 (0)