expo-iap 3.0.7 → 3.0.8
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/CLAUDE.md +12 -0
- package/android/build.gradle +1 -1
- package/build/index.d.ts +14 -66
- package/build/index.d.ts.map +1 -1
- package/build/index.js +149 -151
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +7 -12
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +13 -11
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +19 -35
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +86 -33
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +99 -76
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -0
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts +6 -11
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +44 -15
- package/build/useIAP.js.map +1 -1
- package/build/utils/purchase.d.ts +9 -0
- package/build/utils/purchase.d.ts.map +1 -0
- package/build/utils/purchase.js +34 -0
- package/build/utils/purchase.js.map +1 -0
- package/package.json +2 -2
- package/plugin/build/withIAP.js +3 -3
- package/plugin/src/withIAP.ts +3 -3
- package/src/index.ts +217 -250
- package/src/modules/android.ts +23 -22
- package/src/modules/ios.ts +123 -45
- package/src/types.ts +131 -85
- package/src/useIAP.ts +83 -42
- package/src/utils/purchase.ts +52 -0
- package/.copilot-instructions.md +0 -321
- package/.cursorrules +0 -321
package/src/index.ts
CHANGED
|
@@ -17,25 +17,32 @@ import {
|
|
|
17
17
|
} from './modules/android';
|
|
18
18
|
|
|
19
19
|
// Types
|
|
20
|
-
import {
|
|
20
|
+
import type {
|
|
21
|
+
AndroidSubscriptionOfferInput,
|
|
22
|
+
DeepLinkOptions,
|
|
23
|
+
FetchProductsResult,
|
|
24
|
+
MutationField,
|
|
25
|
+
MutationValidateReceiptArgs,
|
|
21
26
|
Product,
|
|
27
|
+
ProductAndroid,
|
|
28
|
+
ProductIOS,
|
|
29
|
+
ProductQueryType,
|
|
30
|
+
ProductSubscription,
|
|
22
31
|
Purchase,
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
PurchaseInput,
|
|
33
|
+
PurchaseOptions,
|
|
34
|
+
QueryField,
|
|
25
35
|
RequestPurchasePropsByPlatforms,
|
|
26
36
|
RequestPurchaseAndroidProps,
|
|
27
37
|
RequestPurchaseIosProps,
|
|
28
38
|
RequestSubscriptionPropsByPlatforms,
|
|
29
39
|
RequestSubscriptionAndroidProps,
|
|
30
40
|
RequestSubscriptionIosProps,
|
|
31
|
-
ProductSubscription,
|
|
32
|
-
PurchaseAndroid,
|
|
33
41
|
DiscountOfferInputIOS,
|
|
34
|
-
VoidResult,
|
|
35
|
-
ReceiptValidationResult,
|
|
36
|
-
AndroidSubscriptionOfferInput,
|
|
37
42
|
} from './types';
|
|
43
|
+
import {ErrorCode} from './types';
|
|
38
44
|
import {PurchaseError} from './purchase-error';
|
|
45
|
+
import {normalizePurchaseId, normalizePurchaseList} from './utils/purchase';
|
|
39
46
|
|
|
40
47
|
// Export all types
|
|
41
48
|
export * from './types';
|
|
@@ -90,32 +97,7 @@ export const emitter = (ExpoIapModule ||
|
|
|
90
97
|
/**
|
|
91
98
|
* TODO(v3.1.0): Remove legacy 'inapp' alias once downstream apps migrate to 'in-app'.
|
|
92
99
|
*/
|
|
93
|
-
export type ProductTypeInput =
|
|
94
|
-
export type InAppTypeInput = Exclude<ProductTypeInput, 'subs'>;
|
|
95
|
-
|
|
96
|
-
type PurchaseRequestInApp = {
|
|
97
|
-
request: RequestPurchasePropsByPlatforms;
|
|
98
|
-
type?: InAppTypeInput;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
type PurchaseRequestSubscription = {
|
|
102
|
-
request: RequestSubscriptionPropsByPlatforms;
|
|
103
|
-
type: 'subs';
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export type PurchaseRequestInput =
|
|
107
|
-
| PurchaseRequestInApp
|
|
108
|
-
| PurchaseRequestSubscription;
|
|
109
|
-
|
|
110
|
-
export type PurchaseRequest =
|
|
111
|
-
| {
|
|
112
|
-
request: RequestPurchaseProps;
|
|
113
|
-
type?: InAppTypeInput;
|
|
114
|
-
}
|
|
115
|
-
| {
|
|
116
|
-
request: RequestSubscriptionPropsByPlatforms;
|
|
117
|
-
type: 'subs';
|
|
118
|
-
};
|
|
100
|
+
export type ProductTypeInput = ProductQueryType | 'inapp';
|
|
119
101
|
|
|
120
102
|
const normalizeProductType = (type?: ProductTypeInput) => {
|
|
121
103
|
if (type === 'inapp') {
|
|
@@ -126,16 +108,22 @@ const normalizeProductType = (type?: ProductTypeInput) => {
|
|
|
126
108
|
|
|
127
109
|
if (!type || type === 'inapp' || type === 'in-app') {
|
|
128
110
|
return {
|
|
129
|
-
canonical: 'in-app' as
|
|
111
|
+
canonical: 'in-app' as ProductQueryType,
|
|
130
112
|
native: 'inapp' as const,
|
|
131
113
|
};
|
|
132
114
|
}
|
|
133
115
|
if (type === 'subs') {
|
|
134
116
|
return {
|
|
135
|
-
canonical: 'subs' as
|
|
117
|
+
canonical: 'subs' as ProductQueryType,
|
|
136
118
|
native: 'subs' as const,
|
|
137
119
|
};
|
|
138
120
|
}
|
|
121
|
+
if (type === 'all') {
|
|
122
|
+
return {
|
|
123
|
+
canonical: 'all' as ProductQueryType,
|
|
124
|
+
native: 'all' as const,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
139
127
|
throw new Error(`Unsupported product type: ${type}`);
|
|
140
128
|
};
|
|
141
129
|
|
|
@@ -144,8 +132,9 @@ export const purchaseUpdatedListener = (
|
|
|
144
132
|
) => {
|
|
145
133
|
console.log('[JS] Registering purchaseUpdatedListener');
|
|
146
134
|
const wrappedListener = (event: Purchase) => {
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
const normalized = normalizePurchaseId(event);
|
|
136
|
+
console.log('[JS] purchaseUpdatedListener fired:', normalized);
|
|
137
|
+
listener(normalized);
|
|
149
138
|
};
|
|
150
139
|
const emitterSubscription = emitter.addListener(
|
|
151
140
|
OpenIapEvent.PurchaseUpdated,
|
|
@@ -203,112 +192,103 @@ export const promotedProductListenerIOS = (
|
|
|
203
192
|
return emitter.addListener(OpenIapEvent.PromotedProductIOS, listener);
|
|
204
193
|
};
|
|
205
194
|
|
|
206
|
-
export
|
|
207
|
-
|
|
208
|
-
return Promise.resolve(result);
|
|
209
|
-
}
|
|
195
|
+
export const initConnection: MutationField<'initConnection'> = async () =>
|
|
196
|
+
ExpoIapModule.initConnection();
|
|
210
197
|
|
|
211
|
-
export
|
|
212
|
-
|
|
213
|
-
}
|
|
198
|
+
export const endConnection: MutationField<'endConnection'> = async () =>
|
|
199
|
+
ExpoIapModule.endConnection();
|
|
214
200
|
|
|
215
201
|
/**
|
|
216
202
|
* Fetch products with unified API (v2.7.0+)
|
|
217
203
|
*
|
|
218
|
-
* @param
|
|
219
|
-
* @param
|
|
220
|
-
* @param
|
|
221
|
-
*
|
|
222
|
-
* @example
|
|
223
|
-
* ```typescript
|
|
224
|
-
* // Regular products
|
|
225
|
-
* const products = await fetchProducts({
|
|
226
|
-
* skus: ['product1', 'product2'],
|
|
227
|
-
* type: 'in-app'
|
|
228
|
-
* });
|
|
229
|
-
*
|
|
230
|
-
* // Subscriptions
|
|
231
|
-
* const subscriptions = await fetchProducts({
|
|
232
|
-
* skus: ['sub1', 'sub2'],
|
|
233
|
-
* type: 'subs'
|
|
234
|
-
* });
|
|
235
|
-
* ```
|
|
204
|
+
* @param request - Product fetch configuration
|
|
205
|
+
* @param request.skus - Array of product SKUs to fetch
|
|
206
|
+
* @param request.type - Product query type: 'in-app', 'subs', or 'all'
|
|
236
207
|
*/
|
|
237
|
-
export const fetchProducts = async ({
|
|
238
|
-
skus,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
skus: string[];
|
|
242
|
-
type?: ProductTypeInput;
|
|
243
|
-
}): Promise<Product[] | ProductSubscription[]> => {
|
|
244
|
-
if (!skus?.length) {
|
|
208
|
+
export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
|
|
209
|
+
const {skus, type} = request ?? {};
|
|
210
|
+
|
|
211
|
+
if (!Array.isArray(skus) || skus.length === 0) {
|
|
245
212
|
throw new PurchaseError({
|
|
246
213
|
message: 'No SKUs provided',
|
|
247
214
|
code: ErrorCode.EmptySkuList,
|
|
248
215
|
});
|
|
249
216
|
}
|
|
250
217
|
|
|
251
|
-
const {canonical, native} = normalizeProductType(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
218
|
+
const {canonical, native} = normalizeProductType(
|
|
219
|
+
type as ProductTypeInput | undefined,
|
|
220
|
+
);
|
|
221
|
+
const skuSet = new Set(skus);
|
|
255
222
|
|
|
256
|
-
|
|
223
|
+
const filterIosItems = (
|
|
224
|
+
items: unknown[],
|
|
225
|
+
): Product[] | ProductSubscription[] =>
|
|
226
|
+
items.filter((item): item is ProductIOS | ProductSubscription => {
|
|
257
227
|
if (!isProductIOS(item)) {
|
|
258
228
|
return false;
|
|
259
229
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
item !== null &&
|
|
263
|
-
'id' in item &&
|
|
264
|
-
typeof item.id === 'string' &&
|
|
265
|
-
skus.includes(item.id);
|
|
266
|
-
return isValid;
|
|
230
|
+
const candidate = item as ProductIOS | ProductSubscription;
|
|
231
|
+
return typeof candidate.id === 'string' && skuSet.has(candidate.id);
|
|
267
232
|
});
|
|
268
233
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
234
|
+
const filterAndroidItems = (
|
|
235
|
+
items: unknown[],
|
|
236
|
+
): Product[] | ProductSubscription[] =>
|
|
237
|
+
items.filter((item): item is ProductAndroid | ProductSubscription => {
|
|
238
|
+
if (!isProductAndroid(item)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
const candidate = item as ProductAndroid | ProductSubscription;
|
|
242
|
+
return typeof candidate.id === 'string' && skuSet.has(candidate.id);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const castResult = (
|
|
246
|
+
items: Product[] | ProductSubscription[],
|
|
247
|
+
): FetchProductsResult => {
|
|
248
|
+
if (canonical === 'in-app') {
|
|
249
|
+
return items as Product[];
|
|
250
|
+
}
|
|
251
|
+
if (canonical === 'subs') {
|
|
252
|
+
return items as ProductSubscription[];
|
|
253
|
+
}
|
|
254
|
+
return items;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
if (Platform.OS === 'ios') {
|
|
258
|
+
const rawItems = await ExpoIapModule.fetchProducts({skus, type: native});
|
|
259
|
+
return castResult(filterIosItems(rawItems));
|
|
272
260
|
}
|
|
273
261
|
|
|
274
262
|
if (Platform.OS === 'android') {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
if (!isProductAndroid(item)) return false;
|
|
278
|
-
return (
|
|
279
|
-
typeof item === 'object' &&
|
|
280
|
-
item !== null &&
|
|
281
|
-
'id' in item &&
|
|
282
|
-
typeof item.id === 'string' &&
|
|
283
|
-
skus.includes(item.id)
|
|
284
|
-
);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
return canonical === 'in-app'
|
|
288
|
-
? (filteredItems as Product[])
|
|
289
|
-
: (filteredItems as ProductSubscription[]);
|
|
263
|
+
const rawItems = await ExpoIapModule.fetchProducts(native, skus);
|
|
264
|
+
return castResult(filterAndroidItems(rawItems));
|
|
290
265
|
}
|
|
291
266
|
|
|
292
267
|
throw new Error('Unsupported platform');
|
|
293
268
|
};
|
|
294
269
|
|
|
295
|
-
export const getAvailablePurchases
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
270
|
+
export const getAvailablePurchases: QueryField<
|
|
271
|
+
'getAvailablePurchases'
|
|
272
|
+
> = async (options) => {
|
|
273
|
+
const normalizedOptions: PurchaseOptions = {
|
|
274
|
+
alsoPublishToEventListenerIOS:
|
|
275
|
+
options?.alsoPublishToEventListenerIOS ?? false,
|
|
276
|
+
onlyIncludeActiveItemsIOS: options?.onlyIncludeActiveItemsIOS ?? true,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const resolvePurchases: () => Promise<Purchase[]> =
|
|
303
280
|
Platform.select({
|
|
304
281
|
ios: () =>
|
|
305
282
|
ExpoIapModule.getAvailableItems(
|
|
306
|
-
alsoPublishToEventListenerIOS,
|
|
307
|
-
onlyIncludeActiveItemsIOS,
|
|
308
|
-
)
|
|
309
|
-
android: () => ExpoIapModule.getAvailableItems()
|
|
310
|
-
})
|
|
311
|
-
|
|
283
|
+
normalizedOptions.alsoPublishToEventListenerIOS,
|
|
284
|
+
normalizedOptions.onlyIncludeActiveItemsIOS,
|
|
285
|
+
) as Promise<Purchase[]>,
|
|
286
|
+
android: () => ExpoIapModule.getAvailableItems() as Promise<Purchase[]>,
|
|
287
|
+
}) ?? (() => Promise.resolve([] as Purchase[]));
|
|
288
|
+
|
|
289
|
+
const purchases = await resolvePurchases();
|
|
290
|
+
return normalizePurchaseList(purchases);
|
|
291
|
+
};
|
|
312
292
|
|
|
313
293
|
/**
|
|
314
294
|
* Restore completed transactions (cross-platform behavior)
|
|
@@ -323,25 +303,15 @@ export const getAvailablePurchases = ({
|
|
|
323
303
|
* @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
|
|
324
304
|
* @returns Promise resolving to the list of available/restored purchases
|
|
325
305
|
*/
|
|
326
|
-
export const restorePurchases = async (
|
|
327
|
-
options: {
|
|
328
|
-
alsoPublishToEventListenerIOS?: boolean;
|
|
329
|
-
onlyIncludeActiveItemsIOS?: boolean;
|
|
330
|
-
} = {},
|
|
331
|
-
): Promise<Purchase[]> => {
|
|
306
|
+
export const restorePurchases: MutationField<'restorePurchases'> = async () => {
|
|
332
307
|
if (Platform.OS === 'ios') {
|
|
333
|
-
// Perform best-effort sync on iOS and ignore sync errors to avoid blocking restore flow
|
|
334
308
|
await syncIOS().catch(() => undefined);
|
|
335
309
|
}
|
|
336
310
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
options.alsoPublishToEventListenerIOS ?? false,
|
|
341
|
-
onlyIncludeActiveItemsIOS: options.onlyIncludeActiveItemsIOS ?? true,
|
|
311
|
+
await getAvailablePurchases({
|
|
312
|
+
alsoPublishToEventListenerIOS: false,
|
|
313
|
+
onlyIncludeActiveItemsIOS: true,
|
|
342
314
|
});
|
|
343
|
-
|
|
344
|
-
return purchases;
|
|
345
315
|
};
|
|
346
316
|
|
|
347
317
|
const offerToRecordIOS = (
|
|
@@ -417,11 +387,11 @@ function normalizeRequestProps(
|
|
|
417
387
|
* });
|
|
418
388
|
* ```
|
|
419
389
|
*/
|
|
420
|
-
export const requestPurchase = (
|
|
421
|
-
|
|
422
|
-
)
|
|
423
|
-
const {request, type} =
|
|
424
|
-
const {canonical, native} = normalizeProductType(type);
|
|
390
|
+
export const requestPurchase: MutationField<'requestPurchase'> = async (
|
|
391
|
+
args,
|
|
392
|
+
) => {
|
|
393
|
+
const {request, type} = args;
|
|
394
|
+
const {canonical, native} = normalizeProductType(type as ProductTypeInput);
|
|
425
395
|
const isInAppPurchase = canonical === 'in-app';
|
|
426
396
|
|
|
427
397
|
if (Platform.OS === 'ios') {
|
|
@@ -441,27 +411,24 @@ export const requestPurchase = (
|
|
|
441
411
|
withOffer,
|
|
442
412
|
} = normalizedRequest;
|
|
443
413
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
});
|
|
414
|
+
const offer = offerToRecordIOS(withOffer ?? undefined);
|
|
415
|
+
const purchase = await ExpoIapModule.requestPurchase({
|
|
416
|
+
sku,
|
|
417
|
+
andDangerouslyFinishTransactionAutomatically,
|
|
418
|
+
appAccountToken,
|
|
419
|
+
quantity,
|
|
420
|
+
withOffer: offer,
|
|
421
|
+
});
|
|
453
422
|
|
|
454
|
-
|
|
455
|
-
})();
|
|
423
|
+
return normalizePurchaseId(purchase as Purchase);
|
|
456
424
|
}
|
|
457
425
|
|
|
458
426
|
if (Platform.OS === 'android') {
|
|
459
427
|
if (isInAppPurchase) {
|
|
460
|
-
const normalizedRequest
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
);
|
|
428
|
+
const normalizedRequest = normalizeRequestProps(
|
|
429
|
+
request as RequestPurchasePropsByPlatforms,
|
|
430
|
+
'android',
|
|
431
|
+
) as RequestPurchaseAndroidProps | null | undefined;
|
|
465
432
|
|
|
466
433
|
if (!normalizedRequest?.skus?.length) {
|
|
467
434
|
throw new Error(
|
|
@@ -476,28 +443,25 @@ export const requestPurchase = (
|
|
|
476
443
|
isOfferPersonalized,
|
|
477
444
|
} = normalizedRequest;
|
|
478
445
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
446
|
+
const result = (await ExpoIapModule.requestPurchase({
|
|
447
|
+
type: native,
|
|
448
|
+
skuArr: skus,
|
|
449
|
+
purchaseToken: undefined,
|
|
450
|
+
replacementMode: -1,
|
|
451
|
+
obfuscatedAccountId: obfuscatedAccountIdAndroid,
|
|
452
|
+
obfuscatedProfileId: obfuscatedProfileIdAndroid,
|
|
453
|
+
offerTokenArr: [],
|
|
454
|
+
isOfferPersonalized: isOfferPersonalized ?? false,
|
|
455
|
+
})) as Purchase[];
|
|
456
|
+
|
|
457
|
+
return normalizePurchaseList(result);
|
|
491
458
|
}
|
|
492
459
|
|
|
493
460
|
if (canonical === 'subs') {
|
|
494
|
-
const normalizedRequest
|
|
495
|
-
| RequestSubscriptionAndroidProps
|
|
496
|
-
| null
|
|
497
|
-
| undefined = normalizeRequestProps(
|
|
461
|
+
const normalizedRequest = normalizeRequestProps(
|
|
498
462
|
request as RequestSubscriptionPropsByPlatforms,
|
|
499
463
|
'android',
|
|
500
|
-
);
|
|
464
|
+
) as RequestSubscriptionAndroidProps | null | undefined;
|
|
501
465
|
|
|
502
466
|
if (!normalizedRequest?.skus?.length) {
|
|
503
467
|
throw new Error(
|
|
@@ -519,21 +483,21 @@ export const requestPurchase = (
|
|
|
519
483
|
const replacementMode = replacementModeAndroid ?? -1;
|
|
520
484
|
const purchaseToken = purchaseTokenAndroid ?? undefined;
|
|
521
485
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
486
|
+
const result = (await ExpoIapModule.requestPurchase({
|
|
487
|
+
type: native,
|
|
488
|
+
skuArr: skus,
|
|
489
|
+
purchaseToken,
|
|
490
|
+
replacementMode,
|
|
491
|
+
obfuscatedAccountId: obfuscatedAccountIdAndroid,
|
|
492
|
+
obfuscatedProfileId: obfuscatedProfileIdAndroid,
|
|
493
|
+
offerTokenArr: normalizedOffers.map(
|
|
494
|
+
(offer: AndroidSubscriptionOfferInput) => offer.offerToken,
|
|
495
|
+
),
|
|
496
|
+
subscriptionOffers: normalizedOffers,
|
|
497
|
+
isOfferPersonalized: isOfferPersonalized ?? false,
|
|
498
|
+
})) as Purchase[];
|
|
499
|
+
|
|
500
|
+
return normalizePurchaseList(result);
|
|
537
501
|
}
|
|
538
502
|
|
|
539
503
|
throw new Error(
|
|
@@ -541,53 +505,60 @@ export const requestPurchase = (
|
|
|
541
505
|
);
|
|
542
506
|
}
|
|
543
507
|
|
|
544
|
-
|
|
508
|
+
throw new Error('Platform not supported');
|
|
545
509
|
};
|
|
546
510
|
|
|
547
|
-
|
|
511
|
+
const toPurchaseInput = (
|
|
512
|
+
purchase: Purchase | PurchaseInput,
|
|
513
|
+
): PurchaseInput => ({
|
|
514
|
+
id: purchase.id,
|
|
515
|
+
ids: purchase.ids ?? undefined,
|
|
516
|
+
isAutoRenewing: purchase.isAutoRenewing,
|
|
517
|
+
platform: purchase.platform,
|
|
518
|
+
productId: purchase.productId,
|
|
519
|
+
purchaseState: purchase.purchaseState,
|
|
520
|
+
purchaseToken: purchase.purchaseToken ?? null,
|
|
521
|
+
quantity: purchase.quantity,
|
|
522
|
+
transactionDate: purchase.transactionDate,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
export const finishTransaction: MutationField<'finishTransaction'> = async ({
|
|
548
526
|
purchase,
|
|
549
527
|
isConsumable = false,
|
|
550
|
-
}
|
|
551
|
-
purchase
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
return ExpoIapModule.consumePurchaseAndroid(token);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
return ExpoIapModule.acknowledgePurchaseAndroid(token);
|
|
588
|
-
},
|
|
589
|
-
}) || (() => Promise.reject(new Error('Unsupported Platform')))
|
|
590
|
-
)();
|
|
528
|
+
}) => {
|
|
529
|
+
const normalizedPurchase = toPurchaseInput(purchase);
|
|
530
|
+
|
|
531
|
+
if (Platform.OS === 'ios') {
|
|
532
|
+
const transactionId = normalizedPurchase.id;
|
|
533
|
+
if (!transactionId) {
|
|
534
|
+
throw new Error('purchase.id required to finish iOS transaction');
|
|
535
|
+
}
|
|
536
|
+
await ExpoIapModule.finishTransaction(transactionId);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (Platform.OS === 'android') {
|
|
541
|
+
const token = normalizedPurchase.purchaseToken ?? undefined;
|
|
542
|
+
|
|
543
|
+
if (!token) {
|
|
544
|
+
throw new PurchaseError({
|
|
545
|
+
message: 'Purchase token is required to finish transaction',
|
|
546
|
+
code: ErrorCode.DeveloperError,
|
|
547
|
+
productId: normalizedPurchase.productId,
|
|
548
|
+
platform: 'android',
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (isConsumable) {
|
|
553
|
+
await ExpoIapModule.consumePurchaseAndroid(token);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
await ExpoIapModule.acknowledgePurchaseAndroid(token);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
throw new Error('Unsupported Platform');
|
|
591
562
|
};
|
|
592
563
|
|
|
593
564
|
/**
|
|
@@ -638,18 +609,16 @@ export const getStorefront = (): Promise<string> => {
|
|
|
638
609
|
* - iOS: Send receipt data to Apple's verification endpoint from your server
|
|
639
610
|
* - Android: Use Google Play Developer API with service account credentials
|
|
640
611
|
*/
|
|
641
|
-
export const validateReceipt = async (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
accessToken: string;
|
|
647
|
-
isSub?: boolean;
|
|
648
|
-
},
|
|
649
|
-
): Promise<ReceiptValidationResult> => {
|
|
612
|
+
export const validateReceipt: MutationField<'validateReceipt'> = async (
|
|
613
|
+
options,
|
|
614
|
+
) => {
|
|
615
|
+
const {sku, androidOptions} = options as MutationValidateReceiptArgs;
|
|
616
|
+
|
|
650
617
|
if (Platform.OS === 'ios') {
|
|
651
|
-
return
|
|
652
|
-
}
|
|
618
|
+
return validateReceiptIOS({sku});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (Platform.OS === 'android') {
|
|
653
622
|
if (
|
|
654
623
|
!androidOptions ||
|
|
655
624
|
!androidOptions.packageName ||
|
|
@@ -660,16 +629,16 @@ export const validateReceipt = async (
|
|
|
660
629
|
'Android validation requires packageName, productToken, and accessToken',
|
|
661
630
|
);
|
|
662
631
|
}
|
|
663
|
-
return
|
|
632
|
+
return validateReceiptAndroid({
|
|
664
633
|
packageName: androidOptions.packageName,
|
|
665
634
|
productId: sku,
|
|
666
635
|
productToken: androidOptions.productToken,
|
|
667
636
|
accessToken: androidOptions.accessToken,
|
|
668
|
-
isSub: androidOptions.isSub,
|
|
637
|
+
isSub: androidOptions.isSub ?? undefined,
|
|
669
638
|
});
|
|
670
|
-
} else {
|
|
671
|
-
throw new Error('Platform not supported');
|
|
672
639
|
}
|
|
640
|
+
|
|
641
|
+
throw new Error('Platform not supported');
|
|
673
642
|
};
|
|
674
643
|
|
|
675
644
|
/**
|
|
@@ -690,22 +659,20 @@ export const validateReceipt = async (
|
|
|
690
659
|
* packageNameAndroid: 'com.example.app'
|
|
691
660
|
* });
|
|
692
661
|
*/
|
|
693
|
-
export const deepLinkToSubscriptions
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}): Promise<void> => {
|
|
662
|
+
export const deepLinkToSubscriptions: MutationField<
|
|
663
|
+
'deepLinkToSubscriptions'
|
|
664
|
+
> = async (options) => {
|
|
697
665
|
if (Platform.OS === 'ios') {
|
|
698
|
-
|
|
666
|
+
await deepLinkToSubscriptionsIOS();
|
|
667
|
+
return;
|
|
699
668
|
}
|
|
700
669
|
|
|
701
670
|
if (Platform.OS === 'android') {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
packageName: options?.packageNameAndroid,
|
|
705
|
-
});
|
|
671
|
+
await deepLinkToSubscriptionsAndroid((options as DeepLinkOptions) ?? null);
|
|
672
|
+
return;
|
|
706
673
|
}
|
|
707
674
|
|
|
708
|
-
|
|
675
|
+
throw new Error(`Unsupported platform: ${Platform.OS}`);
|
|
709
676
|
};
|
|
710
677
|
|
|
711
678
|
export * from './useIAP';
|