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.
- package/build/index.d.ts +126 -33
- package/build/index.d.ts.map +1 -1
- package/build/index.js +123 -33
- package/build/index.js.map +1 -1
- package/build/kit-api.d.ts +54 -0
- package/build/kit-api.d.ts.map +1 -0
- package/build/kit-api.js +156 -0
- package/build/kit-api.js.map +1 -0
- package/build/modules/android.d.ts +22 -0
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +37 -0
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +69 -1
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +73 -1
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +241 -75
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +125 -3
- package/build/useIAP.js.map +1 -1
- package/build/useWebhookEvents.d.ts +26 -0
- package/build/useWebhookEvents.d.ts.map +1 -0
- package/build/useWebhookEvents.js +105 -0
- package/build/useWebhookEvents.js.map +1 -0
- package/build/webhook-client.d.ts +82 -0
- package/build/webhook-client.d.ts.map +1 -0
- package/build/webhook-client.js +176 -0
- package/build/webhook-client.js.map +1 -0
- package/openiap-versions.json +2 -2
- package/package.json +1 -1
- package/src/index.ts +141 -33
- package/src/kit-api.ts +229 -0
- package/src/modules/android.ts +47 -0
- package/src/modules/ios.ts +74 -1
- package/src/types.ts +247 -75
- package/src/useIAP.ts +125 -3
- package/src/useWebhookEvents.ts +155 -0
- 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
|
+
}
|
package/src/modules/android.ts
CHANGED
|
@@ -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'
|
package/src/modules/ios.ts
CHANGED
|
@@ -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
|
|
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,
|