expo-iap 4.2.2 → 4.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/build/index.d.ts +126 -33
  2. package/build/index.d.ts.map +1 -1
  3. package/build/index.js +123 -33
  4. package/build/index.js.map +1 -1
  5. package/build/kit-api.d.ts +54 -0
  6. package/build/kit-api.d.ts.map +1 -0
  7. package/build/kit-api.js +156 -0
  8. package/build/kit-api.js.map +1 -0
  9. package/build/modules/android.d.ts +22 -0
  10. package/build/modules/android.d.ts.map +1 -1
  11. package/build/modules/android.js +37 -0
  12. package/build/modules/android.js.map +1 -1
  13. package/build/modules/ios.d.ts +69 -1
  14. package/build/modules/ios.d.ts.map +1 -1
  15. package/build/modules/ios.js +73 -1
  16. package/build/modules/ios.js.map +1 -1
  17. package/build/types.d.ts +241 -75
  18. package/build/types.d.ts.map +1 -1
  19. package/build/types.js.map +1 -1
  20. package/build/useIAP.d.ts.map +1 -1
  21. package/build/useIAP.js +125 -3
  22. package/build/useIAP.js.map +1 -1
  23. package/build/useWebhookEvents.d.ts +26 -0
  24. package/build/useWebhookEvents.d.ts.map +1 -0
  25. package/build/useWebhookEvents.js +105 -0
  26. package/build/useWebhookEvents.js.map +1 -0
  27. package/build/webhook-client.d.ts +82 -0
  28. package/build/webhook-client.d.ts.map +1 -0
  29. package/build/webhook-client.js +176 -0
  30. package/build/webhook-client.js.map +1 -0
  31. package/openiap-versions.json +2 -2
  32. package/package.json +1 -1
  33. package/src/index.ts +141 -33
  34. package/src/kit-api.ts +229 -0
  35. package/src/modules/android.ts +47 -0
  36. package/src/modules/ios.ts +74 -1
  37. package/src/types.ts +247 -75
  38. package/src/useIAP.ts +125 -3
  39. package/src/useWebhookEvents.ts +155 -0
  40. package/src/webhook-client.ts +314 -0
package/src/kit-api.ts ADDED
@@ -0,0 +1,229 @@
1
+ // Tiny fetch wrapper around kit's `/v1` HTTP surface for use by the JS
2
+ // SDK consumers (react-native-iap + expo-iap). Mirrors the shape of
3
+ // `packages/mcp-server/src/kit-client.ts` so the same operations are
4
+ // reachable from both LLM tools and end-user apps without each
5
+ // duplicating the URL layout.
6
+
7
+ export type KitApiOptions = {
8
+ apiKey: string;
9
+ baseUrl?: string;
10
+ // Optional fetch override for runtimes without a global (older RN
11
+ // builds) or for injection in tests.
12
+ fetchImpl?: (input: string, init?: RequestInit) => Promise<Response>;
13
+ };
14
+
15
+ export type KitSubscription = {
16
+ id: string;
17
+ productId: string;
18
+ platform: 'IOS' | 'Android';
19
+ state: string;
20
+ expiresAt?: number;
21
+ renewsAt?: number;
22
+ willRenew?: boolean;
23
+ cancellationReason?: string;
24
+ currency?: string;
25
+ priceAmountMicros?: number;
26
+ startedAt: number;
27
+ updatedAt: number;
28
+ purchaseToken: string;
29
+ userId?: string;
30
+ };
31
+
32
+ export type EntitlementsResponse = {
33
+ userId: string;
34
+ productIds: string[];
35
+ subscriptions: KitSubscription[];
36
+ };
37
+
38
+ export type StatusResponse = {
39
+ active: boolean;
40
+ subscription: KitSubscription | null;
41
+ };
42
+
43
+ const DEFAULT_BASE_URL = 'https://kit.openiap.dev';
44
+
45
+ // Merge caller-supplied headers with kit defaults (`accept`,
46
+ // optionally `content-type`). When the runtime exposes a global
47
+ // `Headers` constructor we use it directly so callers passing a
48
+ // `Headers` instance (a `HeadersInit`) keep that exact instance's
49
+ // values. When `Headers` is missing — older React Native builds where
50
+ // the operator wires up `fetchImpl` without a `Headers` polyfill —
51
+ // we fall back to a case-insensitive merge into a plain record so
52
+ // the request still goes through. Either way, caller-set values take
53
+ // precedence over kit defaults.
54
+ function mergeHeaders(
55
+ callerHeaders: HeadersInit | undefined,
56
+ hasBody: boolean,
57
+ ): HeadersInit {
58
+ if (typeof Headers === 'function') {
59
+ const merged = new Headers(callerHeaders);
60
+ if (!merged.has('accept')) merged.set('accept', 'application/json');
61
+ if (hasBody && !merged.has('content-type')) {
62
+ merged.set('content-type', 'application/json');
63
+ }
64
+ return merged;
65
+ }
66
+ // Plain-object fallback path. Build a case-insensitive name map
67
+ // from whatever the caller passed (Headers-shaped, array-of-pairs,
68
+ // or plain record) and re-emit as a record `fetchImpl` accepts.
69
+ const lower = new Map<string, {name: string; value: string}>();
70
+ const setIfAbsent = (name: string, value: string) => {
71
+ const key = name.toLowerCase();
72
+ if (!lower.has(key)) lower.set(key, {name, value});
73
+ };
74
+ const setForce = (name: string, value: string) => {
75
+ const key = name.toLowerCase();
76
+ lower.set(key, {name, value});
77
+ };
78
+ if (callerHeaders) {
79
+ if (Array.isArray(callerHeaders)) {
80
+ for (const [name, value] of callerHeaders) setForce(name, value);
81
+ } else if (
82
+ typeof (callerHeaders as {forEach?: unknown}).forEach === 'function'
83
+ ) {
84
+ // `Headers`-like (without being our `typeof Headers === "function"`
85
+ // global). RN polyfills sometimes attach `Headers` only to
86
+ // request/response instances rather than the global scope.
87
+ // Standard signature is `forEach((value, key, parent))`; we
88
+ // bind the first two positionally so a polyfill that omits
89
+ // the third argument still works. `key` is the header name.
90
+ (
91
+ callerHeaders as {
92
+ forEach: (cb: (value: string, key: string) => void) => void;
93
+ }
94
+ ).forEach((value, key) => setForce(key, value));
95
+ } else {
96
+ for (const [name, value] of Object.entries(
97
+ callerHeaders as Record<string, string>,
98
+ )) {
99
+ setForce(name, value);
100
+ }
101
+ }
102
+ }
103
+ setIfAbsent('accept', 'application/json');
104
+ if (hasBody) setIfAbsent('content-type', 'application/json');
105
+ const out: Record<string, string> = {};
106
+ for (const {name, value} of lower.values()) out[name] = value;
107
+ return out;
108
+ }
109
+
110
+ export class KitApiError extends Error {
111
+ constructor(
112
+ readonly status: number,
113
+ readonly body: unknown,
114
+ message: string,
115
+ ) {
116
+ super(message);
117
+ this.name = 'KitApiError';
118
+ }
119
+ }
120
+
121
+ export function kitApi(options: KitApiOptions) {
122
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
123
+ const fetchImpl: (input: string, init?: RequestInit) => Promise<Response> =
124
+ (() => {
125
+ if (options.fetchImpl) return options.fetchImpl;
126
+ if (typeof fetch === 'function') {
127
+ return (input: string, init?: RequestInit) => fetch(input, init);
128
+ }
129
+ throw new Error(
130
+ 'kitApi requires a fetch implementation. Pass `fetchImpl` for runtimes without a global fetch.',
131
+ );
132
+ })();
133
+
134
+ async function call<T>(path: string, init?: RequestInit): Promise<T> {
135
+ // Normalize headers without depending on a global `Headers`
136
+ // constructor: older React Native runtimes ship `fetch` (or a
137
+ // polyfill via `fetchImpl`) without exposing `Headers` globally.
138
+ // The prior implementation crashed before the first request on
139
+ // those runtimes. We use `new Headers()` when available (preserves
140
+ // caller-supplied `Headers` instances exactly), and otherwise fall
141
+ // back to a small case-insensitive merge into a plain record.
142
+ // Either way, kit defaults only apply when the caller hasn't set
143
+ // the same name.
144
+ const headers = mergeHeaders(init?.headers, init?.body != null);
145
+ // Prepend a leading slash if `path` is missing one. Today's
146
+ // call sites all hard-code the leading "/", but normalizing here
147
+ // makes the helper safe for future additions and matches the
148
+ // already-stripped `baseUrl` (PR #124
149
+ // (https://github.com/hyodotdev/openiap/pull/124) review).
150
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
151
+ const response = await fetchImpl(`${baseUrl}${normalizedPath}`, {
152
+ ...init,
153
+ headers,
154
+ });
155
+ const text = await response.text();
156
+ // Empty body normalizes to null so callers expecting JSON
157
+ // (status / entitlements / list*) don't get a truthy ""
158
+ // and crash on property access.
159
+ let parsed: unknown = null;
160
+ let parseError: unknown = null;
161
+ if (text) {
162
+ try {
163
+ parsed = JSON.parse(text);
164
+ } catch (error) {
165
+ // Non-JSON body (a misconfigured proxy returning HTML, a
166
+ // CDN-injected error page, etc.) on a 2xx response would
167
+ // otherwise reach the caller as `parsed = text` and crash
168
+ // on property access via `parsed as T`. Throw a structured
169
+ // KitApiError instead so callers see a typed failure.
170
+ parseError = error;
171
+ }
172
+ }
173
+ if (!response.ok) {
174
+ // Surface the raw body (text or parsed) on the error path so
175
+ // operators can read the upstream error message verbatim.
176
+ throw new KitApiError(
177
+ response.status,
178
+ parsed ?? text,
179
+ `kit ${path} returned ${response.status}`,
180
+ );
181
+ }
182
+ if (parseError) {
183
+ throw new KitApiError(
184
+ response.status,
185
+ text,
186
+ `kit ${path} returned a non-JSON ${response.status} body (${
187
+ parseError instanceof Error ? parseError.message : String(parseError)
188
+ })`,
189
+ );
190
+ }
191
+ return parsed as T;
192
+ }
193
+
194
+ return {
195
+ apiKey: options.apiKey,
196
+ baseUrl,
197
+
198
+ /** GET /v1/subscriptions/status — the `active` boolean is the
199
+ * fastest gate for "is this user paying?". */
200
+ status: (userId: string) =>
201
+ call<StatusResponse>(
202
+ `/v1/subscriptions/status/${encodeURIComponent(
203
+ options.apiKey,
204
+ )}?userId=${encodeURIComponent(userId)}`,
205
+ ),
206
+
207
+ /** GET /v1/subscriptions/entitlements — every productId the user
208
+ * is entitled to. Use this when feature gating depends on which
209
+ * specific tier the user owns. */
210
+ entitlements: (userId: string) =>
211
+ call<EntitlementsResponse>(
212
+ `/v1/subscriptions/entitlements/${encodeURIComponent(
213
+ options.apiKey,
214
+ )}?userId=${encodeURIComponent(userId)}`,
215
+ ),
216
+
217
+ /** POST /v1/subscriptions/bind-user — call after a successful
218
+ * verifyReceipt so kit knows which userId owns the verified
219
+ * `purchaseToken`. Idempotent. */
220
+ bindUser: (purchaseToken: string, userId: string) =>
221
+ call<{ok: boolean; bound: boolean}>(
222
+ `/v1/subscriptions/bind-user/${encodeURIComponent(options.apiKey)}`,
223
+ {
224
+ method: 'POST',
225
+ body: JSON.stringify({purchaseToken, userId}),
226
+ },
227
+ ),
228
+ };
229
+ }
@@ -127,11 +127,46 @@ export const validateReceiptAndroid = async ({
127
127
  return response.json();
128
128
  };
129
129
 
130
+ /**
131
+ * Consume a purchase token so the user can purchase the same product again
132
+ * (Android consumable products). Prefer using `finishTransaction` with
133
+ * `isConsumable: true`, which dispatches to this under the hood.
134
+ *
135
+ * @see {@link https://www.openiap.dev/docs/apis/android/consume-purchase-android}
136
+ */
137
+ export const consumePurchaseAndroid: MutationField<
138
+ 'consumePurchaseAndroid'
139
+ > = async (purchaseToken) => {
140
+ const result = await ExpoIapModule.consumePurchaseAndroid(purchaseToken);
141
+
142
+ if (typeof result === 'boolean') {
143
+ return result;
144
+ }
145
+
146
+ if (result && typeof result === 'object') {
147
+ const record = result as Record<string, unknown>;
148
+ if (typeof record.success === 'boolean') {
149
+ return record.success;
150
+ }
151
+ if (typeof record.responseCode === 'number') {
152
+ return record.responseCode === 0;
153
+ }
154
+ }
155
+
156
+ throw new Error(
157
+ `consumePurchaseAndroid returned an unexpected response payload: ${JSON.stringify(
158
+ result,
159
+ )}`,
160
+ );
161
+ };
162
+
130
163
  /**
131
164
  * Acknowledge a product (on Android.) No-op on iOS.
132
165
  * @param {Object} params - The parameters object
133
166
  * @param {string} params.token - The product's token (on Android)
134
167
  * @returns {Promise<VoidResult | void>}
168
+ *
169
+ * @see {@link https://www.openiap.dev/docs/apis/android/acknowledge-purchase-android}
135
170
  */
136
171
  export const acknowledgePurchaseAndroid: MutationField<
137
172
  'acknowledgePurchaseAndroid'
@@ -182,6 +217,8 @@ export const openRedeemOfferCodeAndroid = async (): Promise<void> => {
182
217
  * // Proceed with alternative billing flow
183
218
  * }
184
219
  * ```
220
+ *
221
+ * @see {@link https://www.openiap.dev/docs/apis/android/check-alternative-billing-availability-android}
185
222
  */
186
223
  export const checkAlternativeBillingAvailabilityAndroid: MutationField<
187
224
  'checkAlternativeBillingAvailabilityAndroid'
@@ -212,6 +249,8 @@ export const checkAlternativeBillingAvailabilityAndroid: MutationField<
212
249
  * }
213
250
  * }
214
251
  * ```
252
+ *
253
+ * @see {@link https://www.openiap.dev/docs/apis/android/show-alternative-billing-dialog-android}
215
254
  */
216
255
  export const showAlternativeBillingDialogAndroid: MutationField<
217
256
  'showAlternativeBillingDialogAndroid'
@@ -242,6 +281,8 @@ export const showAlternativeBillingDialogAndroid: MutationField<
242
281
  * });
243
282
  * }
244
283
  * ```
284
+ *
285
+ * @see {@link https://www.openiap.dev/docs/apis/android/create-alternative-billing-token-android}
245
286
  */
246
287
  export const createAlternativeBillingTokenAndroid: MutationField<
247
288
  'createAlternativeBillingTokenAndroid'
@@ -267,6 +308,8 @@ export const createAlternativeBillingTokenAndroid: MutationField<
267
308
  * // Proceed with billing program flow
268
309
  * }
269
310
  * ```
311
+ *
312
+ * @see {@link https://www.openiap.dev/docs/apis/android/is-billing-program-available-android}
270
313
  */
271
314
  export const isBillingProgramAvailableAndroid: MutationField<
272
315
  'isBillingProgramAvailableAndroid'
@@ -290,6 +333,8 @@ export const isBillingProgramAvailableAndroid: MutationField<
290
333
  * linkUri: 'https://your-payment-site.com',
291
334
  * });
292
335
  * ```
336
+ *
337
+ * @see {@link https://www.openiap.dev/docs/apis/android/launch-external-link-android}
293
338
  */
294
339
  export const launchExternalLinkAndroid: MutationField<
295
340
  'launchExternalLinkAndroid'
@@ -313,6 +358,8 @@ export const launchExternalLinkAndroid: MutationField<
313
358
  * // Report details.externalTransactionToken to Google Play within 24 hours
314
359
  * await reportToGooglePlay(details.externalTransactionToken);
315
360
  * ```
361
+ *
362
+ * @see {@link https://www.openiap.dev/docs/apis/android/create-billing-program-reporting-details-android}
316
363
  */
317
364
  export const createBillingProgramReportingDetailsAndroid: MutationField<
318
365
  'createBillingProgramReportingDetailsAndroid'
@@ -53,6 +53,8 @@ export function isProductIOS<T extends {platform?: string}>(
53
53
  * @throws Error if called on non-iOS platform
54
54
  *
55
55
  * @platform iOS
56
+ *
57
+ * @see {@link https://www.openiap.dev/docs/apis/ios/sync-ios}
56
58
  */
57
59
  export const syncIOS: MutationField<'syncIOS'> = async () => {
58
60
  return !!(await ExpoIapModule.syncIOS());
@@ -66,6 +68,8 @@ export const syncIOS: MutationField<'syncIOS'> = async () => {
66
68
  * @throws Error if called on non-iOS platform
67
69
  *
68
70
  * @platform iOS
71
+ *
72
+ * @see {@link https://www.openiap.dev/docs/apis/ios/is-eligible-for-intro-offer-ios}
69
73
  */
70
74
  export const isEligibleForIntroOfferIOS: QueryField<
71
75
  'isEligibleForIntroOfferIOS'
@@ -84,6 +88,8 @@ export const isEligibleForIntroOfferIOS: QueryField<
84
88
  * @throws Error if called on non-iOS platform
85
89
  *
86
90
  * @platform iOS
91
+ *
92
+ * @see {@link https://www.openiap.dev/docs/apis/ios/subscription-status-ios}
87
93
  */
88
94
  export const subscriptionStatusIOS: QueryField<
89
95
  'subscriptionStatusIOS'
@@ -103,6 +109,8 @@ export const subscriptionStatusIOS: QueryField<
103
109
  * @throws Error if called on non-iOS platform
104
110
  *
105
111
  * @platform iOS
112
+ *
113
+ * @see {@link https://www.openiap.dev/docs/apis/ios/current-entitlement-ios}
106
114
  */
107
115
  export const currentEntitlementIOS: QueryField<
108
116
  'currentEntitlementIOS'
@@ -122,6 +130,8 @@ export const currentEntitlementIOS: QueryField<
122
130
  * @throws Error if called on non-iOS platform
123
131
  *
124
132
  * @platform iOS
133
+ *
134
+ * @see {@link https://www.openiap.dev/docs/apis/ios/latest-transaction-ios}
125
135
  */
126
136
  export const latestTransactionIOS: QueryField<'latestTransactionIOS'> = async (
127
137
  sku,
@@ -141,6 +151,8 @@ export const latestTransactionIOS: QueryField<'latestTransactionIOS'> = async (
141
151
  * @throws Error if called on non-iOS platform
142
152
  *
143
153
  * @platform iOS
154
+ *
155
+ * @see {@link https://www.openiap.dev/docs/apis/ios/begin-refund-request-ios}
144
156
  */
145
157
  export const beginRefundRequestIOS: MutationField<
146
158
  'beginRefundRequestIOS'
@@ -160,6 +172,8 @@ export const beginRefundRequestIOS: MutationField<
160
172
  * @throws Error if called on non-iOS platform
161
173
  *
162
174
  * @platform iOS
175
+ *
176
+ * @see {@link https://www.openiap.dev/docs/apis/ios/show-manage-subscriptions-ios}
163
177
  */
164
178
  export const showManageSubscriptionsIOS: MutationField<
165
179
  'showManageSubscriptionsIOS'
@@ -177,6 +191,8 @@ export const showManageSubscriptionsIOS: MutationField<
177
191
  * Apple's verifyReceipt endpoint, not directly from the app.
178
192
  *
179
193
  * @returns {Promise<string>} Base64 encoded receipt data
194
+ *
195
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-receipt-data-ios}
180
196
  */
181
197
  export const getReceiptDataIOS: QueryField<'getReceiptDataIOS'> = async () => {
182
198
  return ExpoIapModule.getReceiptDataIOS();
@@ -184,6 +200,24 @@ export const getReceiptDataIOS: QueryField<'getReceiptDataIOS'> = async () => {
184
200
 
185
201
  export const getReceiptIOS = getReceiptDataIOS;
186
202
 
203
+ /**
204
+ * Get the current App Store storefront country code on iOS.
205
+ *
206
+ * @deprecated Use cross-platform `getStorefront` from the main index instead.
207
+ * The native module exposes a single `getStorefront` AsyncFunction that already
208
+ * resolves to the iOS storefront on iOS. This helper is kept as an iOS-only
209
+ * alias so consumers who previously imported `getStorefrontIOS` do not break.
210
+ *
211
+ * @returns {Promise<string>} ISO 3166-1 alpha-2 country code (e.g. "US")
212
+ *
213
+ * @platform iOS
214
+ *
215
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-storefront-ios}
216
+ */
217
+ export const getStorefrontIOS: QueryField<'getStorefrontIOS'> = async () => {
218
+ return ExpoIapModule.getStorefront();
219
+ };
220
+
187
221
  /**
188
222
  * Refresh the receipt data from Apple's servers and return the updated receipt.
189
223
  * This calls AppStore.sync() before reading the receipt, ensuring the latest
@@ -208,6 +242,8 @@ export const requestReceiptRefreshIOS = async (): Promise<string> => {
208
242
  * @throws Error if called on non-iOS platform
209
243
  *
210
244
  * @platform iOS
245
+ *
246
+ * @see {@link https://www.openiap.dev/docs/apis/ios/is-transaction-verified-ios}
211
247
  */
212
248
  export const isTransactionVerifiedIOS: QueryField<
213
249
  'isTransactionVerifiedIOS'
@@ -227,6 +263,8 @@ export const isTransactionVerifiedIOS: QueryField<
227
263
  * @throws Error if called on non-iOS platform
228
264
  *
229
265
  * @platform iOS
266
+ *
267
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-transaction-jws-ios}
230
268
  */
231
269
  export const getTransactionJwsIOS: QueryField<'getTransactionJwsIOS'> = async (
232
270
  sku,
@@ -253,6 +291,8 @@ export const getTransactionJwsIOS: QueryField<'getTransactionJwsIOS'> = async (
253
291
  * jwsRepresentation: string;
254
292
  * latestTransaction?: Purchase;
255
293
  * }>}
294
+ *
295
+ * @see {@link https://www.openiap.dev/docs/apis/ios/validate-receipt-ios}
256
296
  */
257
297
  const validateReceiptIOSImpl = async (props: VerifyPurchaseProps | string) => {
258
298
  const sku =
@@ -282,6 +322,8 @@ export const validateReceiptIOS =
282
322
  * @throws Error if called on non-iOS platform or tvOS
283
323
  *
284
324
  * @platform iOS
325
+ *
326
+ * @see {@link https://www.openiap.dev/docs/apis/ios/present-code-redemption-sheet-ios}
285
327
  */
286
328
  export const presentCodeRedemptionSheetIOS: MutationField<
287
329
  'presentCodeRedemptionSheetIOS'
@@ -302,6 +344,8 @@ export const presentCodeRedemptionSheetIOS: MutationField<
302
344
  *
303
345
  * @platform iOS
304
346
  * @since iOS 16.0
347
+ *
348
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-app-transaction-ios}
305
349
  */
306
350
  export const getAppTransactionIOS: QueryField<
307
351
  'getAppTransactionIOS'
@@ -318,6 +362,8 @@ export const getAppTransactionIOS: QueryField<
318
362
  * @throws Error if called on non-iOS platform
319
363
  *
320
364
  * @platform iOS
365
+ *
366
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-promoted-product-ios}
321
367
  */
322
368
  export const getPromotedProductIOS: QueryField<
323
369
  'getPromotedProductIOS'
@@ -337,6 +383,8 @@ export const getPromotedProductIOS: QueryField<
337
383
  * @throws Error if called on non-iOS platform or no promoted product is available
338
384
  *
339
385
  * @platform iOS
386
+ *
387
+ * @see {@link https://www.openiap.dev/docs/apis/ios/request-purchase-on-promoted-product-ios}
340
388
  */
341
389
  export const requestPurchaseOnPromotedProductIOS =
342
390
  async (): Promise<boolean> => {
@@ -349,6 +397,8 @@ export const requestPurchaseOnPromotedProductIOS =
349
397
  *
350
398
  * @returns Promise resolving to array of pending transactions
351
399
  * @platform iOS
400
+ *
401
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-pending-transactions-ios}
352
402
  */
353
403
  export const getPendingTransactionsIOS: QueryField<
354
404
  'getPendingTransactionsIOS'
@@ -357,6 +407,11 @@ export const getPendingTransactionsIOS: QueryField<
357
407
  return (transactions ?? []) as PurchaseIOS[];
358
408
  };
359
409
 
410
+ /**
411
+ * List every StoreKit transaction (finished + unfinished) for the current user.
412
+ *
413
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-all-transactions-ios}
414
+ */
360
415
  export const getAllTransactionsIOS: QueryField<
361
416
  'getAllTransactionsIOS'
362
417
  > = async () => {
@@ -369,6 +424,8 @@ export const getAllTransactionsIOS: QueryField<
369
424
  *
370
425
  * @returns Promise resolving when transaction is cleared
371
426
  * @platform iOS
427
+ *
428
+ * @see {@link https://www.openiap.dev/docs/apis/ios/clear-transaction-ios}
372
429
  */
373
430
  export const clearTransactionIOS: MutationField<
374
431
  'clearTransactionIOS'
@@ -386,10 +443,16 @@ export const deepLinkToSubscriptionsIOS = (): Promise<void> =>
386
443
  Linking.openURL('https://apps.apple.com/account/subscriptions');
387
444
 
388
445
  /**
389
- * Check if the device can present an external purchase notice sheet (iOS 18.2+).
446
+ * Check if the device can present an external purchase notice sheet (iOS 17.4+).
447
+ *
448
+ * Wraps `ExternalPurchase.canPresent`, which Apple introduced in iOS 17.4.
449
+ * Note: the notice sheet itself (`presentExternalPurchaseNoticeSheetIOS`)
450
+ * still requires iOS 18.2+; only the eligibility check is available earlier.
390
451
  *
391
452
  * @returns Promise resolving to true if the notice sheet can be presented
392
453
  * @platform iOS
454
+ *
455
+ * @see {@link https://www.openiap.dev/docs/apis/ios/can-present-external-purchase-notice-ios}
393
456
  */
394
457
  export const canPresentExternalPurchaseNoticeIOS: QueryField<
395
458
  'canPresentExternalPurchaseNoticeIOS'
@@ -404,6 +467,8 @@ export const canPresentExternalPurchaseNoticeIOS: QueryField<
404
467
  *
405
468
  * @returns Promise resolving to the result with action, token, and error if any
406
469
  * @platform iOS
470
+ *
471
+ * @see {@link https://www.openiap.dev/docs/apis/ios/present-external-purchase-notice-sheet-ios}
407
472
  */
408
473
  export const presentExternalPurchaseNoticeSheetIOS =
409
474
  async (): Promise<ExternalPurchaseNoticeResultIOS> => {
@@ -417,6 +482,8 @@ export const presentExternalPurchaseNoticeSheetIOS =
417
482
  * @param url - The external purchase URL to open
418
483
  * @returns Promise resolving to the result with success status and error if any
419
484
  * @platform iOS
485
+ *
486
+ * @see {@link https://www.openiap.dev/docs/apis/ios/present-external-purchase-link-ios}
420
487
  */
421
488
  export const presentExternalPurchaseLinkIOS: MutationField<
422
489
  'presentExternalPurchaseLinkIOS'
@@ -432,6 +499,8 @@ export const presentExternalPurchaseLinkIOS: MutationField<
432
499
  * @returns Promise resolving to true if eligible
433
500
  * @platform iOS
434
501
  * @see https://developer.apple.com/documentation/storekit/externalpurchasecustomlink/iseligible
502
+ *
503
+ * @see {@link https://www.openiap.dev/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios}
435
504
  */
436
505
  export const isEligibleForExternalPurchaseCustomLinkIOS =
437
506
  async (): Promise<boolean> => {
@@ -446,6 +515,8 @@ export const isEligibleForExternalPurchaseCustomLinkIOS =
446
515
  * @returns Promise resolving to the token result with token string or error
447
516
  * @platform iOS
448
517
  * @see https://developer.apple.com/documentation/storekit/externalpurchasecustomlink/token(for:)
518
+ *
519
+ * @see {@link https://www.openiap.dev/docs/apis/ios/get-external-purchase-custom-link-token-ios}
449
520
  */
450
521
  export const getExternalPurchaseCustomLinkTokenIOS = async (
451
522
  tokenType: ExternalPurchaseCustomLinkTokenTypeIOS,
@@ -470,6 +541,8 @@ export const getExternalPurchaseCustomLinkTokenIOS = async (
470
541
  * @returns Promise resolving to the result with continued status and error if any
471
542
  * @platform iOS
472
543
  * @see https://developer.apple.com/documentation/storekit/externalpurchasecustomlink/shownotice(type:)
544
+ *
545
+ * @see {@link https://www.openiap.dev/docs/apis/ios/show-external-purchase-custom-link-notice-ios}
473
546
  */
474
547
  export const showExternalPurchaseCustomLinkNoticeIOS = async (
475
548
  noticeType: ExternalPurchaseCustomLinkNoticeTypeIOS,