expo-iap 3.0.4 → 3.0.6
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/.eslintignore +1 -1
- package/.eslintrc.js +1 -0
- package/.prettierignore +1 -0
- package/CHANGELOG.md +10 -0
- package/CLAUDE.md +9 -1
- package/CONTRIBUTING.md +10 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +12 -12
- package/android/src/main/java/expo/modules/iap/PromiseUtils.kt +2 -2
- package/build/index.d.ts +28 -14
- package/build/index.d.ts.map +1 -1
- package/build/index.js +36 -15
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +3 -4
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +2 -4
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +2 -3
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +1 -3
- package/build/modules/ios.js.map +1 -1
- package/build/purchase-error.d.ts +8 -10
- package/build/purchase-error.d.ts.map +1 -1
- package/build/purchase-error.js +4 -2
- package/build/purchase-error.js.map +1 -1
- package/build/types.d.ts +159 -204
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -59
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts +5 -12
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +10 -75
- package/build/useIAP.js.map +1 -1
- package/ios/ExpoIap.podspec +1 -1
- package/ios/ExpoIapModule.swift +103 -89
- package/package.json +2 -1
- package/plugin/build/withIAP.js +4 -5
- package/plugin/src/withIAP.ts +4 -5
- package/scripts/update-types.mjs +61 -0
- package/src/index.ts +77 -29
- package/src/modules/android.ts +5 -7
- package/src/modules/ios.ts +3 -5
- package/src/purchase-error.ts +13 -16
- package/src/types.ts +183 -216
- package/src/useIAP.ts +19 -94
package/src/useIAP.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
getActiveSubscriptions,
|
|
19
19
|
hasActiveSubscriptions,
|
|
20
20
|
type ActiveSubscription,
|
|
21
|
+
type ProductTypeInput,
|
|
22
|
+
type PurchaseRequestInput,
|
|
21
23
|
restorePurchases,
|
|
22
24
|
} from './index';
|
|
23
25
|
import {
|
|
@@ -30,10 +32,9 @@ import {
|
|
|
30
32
|
Product,
|
|
31
33
|
Purchase,
|
|
32
34
|
ProductSubscription,
|
|
33
|
-
RequestPurchaseProps,
|
|
34
|
-
RequestSubscriptionPropsByPlatforms,
|
|
35
35
|
ErrorCode,
|
|
36
36
|
VoidResult,
|
|
37
|
+
ReceiptValidationResult,
|
|
37
38
|
} from './types';
|
|
38
39
|
import {PurchaseError} from './purchase-error';
|
|
39
40
|
import {
|
|
@@ -42,10 +43,6 @@ import {
|
|
|
42
43
|
isRecoverableError,
|
|
43
44
|
} from './utils/errorMapping';
|
|
44
45
|
|
|
45
|
-
// Deduplicate purchase success events across re-mounts (dev StrictMode, nav returns)
|
|
46
|
-
// Keep minimal in-memory state; safe for subscriptions since renewals use new ids
|
|
47
|
-
const handledPurchaseIds = new Set<string>();
|
|
48
|
-
|
|
49
46
|
type UseIap = {
|
|
50
47
|
connected: boolean;
|
|
51
48
|
products: Product[];
|
|
@@ -53,12 +50,8 @@ type UseIap = {
|
|
|
53
50
|
promotedProductIdIOS?: string;
|
|
54
51
|
subscriptions: ProductSubscription[];
|
|
55
52
|
availablePurchases: Purchase[];
|
|
56
|
-
currentPurchase?: Purchase;
|
|
57
|
-
currentPurchaseError?: PurchaseError;
|
|
58
53
|
promotedProductIOS?: Product;
|
|
59
54
|
activeSubscriptions: ActiveSubscription[];
|
|
60
|
-
clearCurrentPurchase: () => void;
|
|
61
|
-
clearCurrentPurchaseError: () => void;
|
|
62
55
|
finishTransaction: ({
|
|
63
56
|
purchase,
|
|
64
57
|
isConsumable,
|
|
@@ -69,13 +62,12 @@ type UseIap = {
|
|
|
69
62
|
getAvailablePurchases: () => Promise<void>;
|
|
70
63
|
fetchProducts: (params: {
|
|
71
64
|
skus: string[];
|
|
72
|
-
type?:
|
|
65
|
+
type?: ProductTypeInput;
|
|
73
66
|
}) => Promise<void>;
|
|
74
67
|
|
|
75
|
-
requestPurchase: (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}) => Promise<any>;
|
|
68
|
+
requestPurchase: (
|
|
69
|
+
params: PurchaseRequestInput,
|
|
70
|
+
) => ReturnType<typeof requestPurchaseInternal>;
|
|
79
71
|
validateReceipt: (
|
|
80
72
|
sku: string,
|
|
81
73
|
androidOptions?: {
|
|
@@ -84,7 +76,7 @@ type UseIap = {
|
|
|
84
76
|
accessToken: string;
|
|
85
77
|
isSub?: boolean;
|
|
86
78
|
},
|
|
87
|
-
) => Promise<
|
|
79
|
+
) => Promise<ReceiptValidationResult>;
|
|
88
80
|
restorePurchases: () => Promise<void>;
|
|
89
81
|
getPromotedProductIOS: () => Promise<Product | null>;
|
|
90
82
|
requestPurchaseOnPromotedProductIOS: () => Promise<void>;
|
|
@@ -111,10 +103,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
111
103
|
const [subscriptions, setSubscriptions] = useState<ProductSubscription[]>([]);
|
|
112
104
|
|
|
113
105
|
const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);
|
|
114
|
-
const [currentPurchase, setCurrentPurchase] = useState<Purchase>();
|
|
115
106
|
const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();
|
|
116
|
-
const [currentPurchaseError, setCurrentPurchaseError] =
|
|
117
|
-
useState<PurchaseError>();
|
|
118
107
|
const [promotedProductIdIOS] = useState<string>();
|
|
119
108
|
const [activeSubscriptions, setActiveSubscriptions] = useState<
|
|
120
109
|
ActiveSubscription[]
|
|
@@ -165,14 +154,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
165
154
|
subscriptionsRefState.current = subscriptions;
|
|
166
155
|
}, [subscriptions]);
|
|
167
156
|
|
|
168
|
-
const clearCurrentPurchase = useCallback(() => {
|
|
169
|
-
setCurrentPurchase(undefined);
|
|
170
|
-
}, []);
|
|
171
|
-
|
|
172
|
-
const clearCurrentPurchaseError = useCallback(() => {
|
|
173
|
-
setCurrentPurchaseError(undefined);
|
|
174
|
-
}, []);
|
|
175
|
-
|
|
176
157
|
const getSubscriptionsInternal = useCallback(
|
|
177
158
|
async (skus: string[]): Promise<void> => {
|
|
178
159
|
try {
|
|
@@ -194,7 +175,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
194
175
|
const fetchProductsInternal = useCallback(
|
|
195
176
|
async (params: {
|
|
196
177
|
skus: string[];
|
|
197
|
-
type?:
|
|
178
|
+
type?: ProductTypeInput;
|
|
198
179
|
}): Promise<void> => {
|
|
199
180
|
try {
|
|
200
181
|
const result = await fetchProducts(params);
|
|
@@ -261,49 +242,26 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
261
242
|
);
|
|
262
243
|
|
|
263
244
|
const finishTransaction = useCallback(
|
|
264
|
-
|
|
245
|
+
({
|
|
265
246
|
purchase,
|
|
266
247
|
isConsumable,
|
|
267
248
|
}: {
|
|
268
249
|
purchase: Purchase;
|
|
269
250
|
isConsumable?: boolean;
|
|
270
251
|
}): Promise<VoidResult | boolean> => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
});
|
|
276
|
-
} catch (err) {
|
|
277
|
-
throw err;
|
|
278
|
-
} finally {
|
|
279
|
-
if (purchase.id === currentPurchase?.id) {
|
|
280
|
-
clearCurrentPurchase();
|
|
281
|
-
}
|
|
282
|
-
if (purchase.id === currentPurchaseError?.productId) {
|
|
283
|
-
clearCurrentPurchaseError();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
252
|
+
return finishTransactionInternal({
|
|
253
|
+
purchase,
|
|
254
|
+
isConsumable,
|
|
255
|
+
});
|
|
286
256
|
},
|
|
287
|
-
[
|
|
288
|
-
currentPurchase?.id,
|
|
289
|
-
currentPurchaseError?.productId,
|
|
290
|
-
clearCurrentPurchase,
|
|
291
|
-
clearCurrentPurchaseError,
|
|
292
|
-
],
|
|
257
|
+
[],
|
|
293
258
|
);
|
|
294
259
|
|
|
295
260
|
const requestPurchaseWithReset = useCallback(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
clearCurrentPurchaseError();
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
return await requestPurchaseInternal(requestObj);
|
|
302
|
-
} catch (error) {
|
|
303
|
-
throw error;
|
|
304
|
-
}
|
|
261
|
+
(requestObj: PurchaseRequestInput) => {
|
|
262
|
+
return requestPurchaseInternal(requestObj);
|
|
305
263
|
},
|
|
306
|
-
[
|
|
264
|
+
[],
|
|
307
265
|
);
|
|
308
266
|
|
|
309
267
|
const refreshSubscriptionStatus = useCallback(
|
|
@@ -353,24 +311,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
353
311
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
354
312
|
// CRITICAL: Register listeners BEFORE initConnection to avoid race condition
|
|
355
313
|
// Events might fire immediately after initConnection, so listeners must be ready
|
|
356
|
-
console.log('[useIAP] Setting up event listeners BEFORE initConnection...');
|
|
357
|
-
|
|
358
314
|
// Register purchase update listener BEFORE initConnection to avoid race conditions.
|
|
359
315
|
subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(
|
|
360
316
|
async (purchase: Purchase) => {
|
|
361
|
-
console.log('[useIAP] Purchase success callback triggered:', purchase);
|
|
362
|
-
|
|
363
|
-
// Guard against duplicate emissions for the same transaction
|
|
364
|
-
const dedupeKey = purchase.id;
|
|
365
|
-
if (dedupeKey && handledPurchaseIds.has(dedupeKey)) {
|
|
366
|
-
console.log('[useIAP] Duplicate purchase event ignored:', dedupeKey);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (dedupeKey) handledPurchaseIds.add(dedupeKey);
|
|
370
|
-
|
|
371
|
-
setCurrentPurchaseError(undefined);
|
|
372
|
-
setCurrentPurchase(purchase);
|
|
373
|
-
|
|
374
317
|
if ('expirationDateIOS' in purchase) {
|
|
375
318
|
await refreshSubscriptionStatus(purchase.id);
|
|
376
319
|
}
|
|
@@ -388,16 +331,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
388
331
|
return; // Ignore initialization error before connected
|
|
389
332
|
}
|
|
390
333
|
const friendly = getUserFriendlyErrorMessage(error);
|
|
391
|
-
|
|
392
|
-
if (isUserCancelledError(error)) {
|
|
393
|
-
console.log('[useIAP] User cancelled purchase');
|
|
394
|
-
} else if (isRecoverableError(error)) {
|
|
395
|
-
console.log('[useIAP] Recoverable purchase error:', friendly);
|
|
396
|
-
} else {
|
|
334
|
+
if (!isUserCancelledError(error) && !isRecoverableError(error)) {
|
|
397
335
|
console.warn('[useIAP] Purchase error:', friendly);
|
|
398
336
|
}
|
|
399
|
-
setCurrentPurchase(undefined);
|
|
400
|
-
setCurrentPurchaseError(error);
|
|
401
337
|
|
|
402
338
|
if (optionsRef.current?.onPurchaseError) {
|
|
403
339
|
optionsRef.current.onPurchaseError(error);
|
|
@@ -409,7 +345,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
409
345
|
// iOS promoted products listener
|
|
410
346
|
subscriptionsRef.current.promotedProductsIOS = promotedProductListenerIOS(
|
|
411
347
|
(product: Product) => {
|
|
412
|
-
console.log('[useIAP] Promoted product callback triggered:', product);
|
|
413
348
|
setPromotedProductIOS(product);
|
|
414
349
|
|
|
415
350
|
if (optionsRef.current?.onPromotedProductIOS) {
|
|
@@ -419,14 +354,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
419
354
|
);
|
|
420
355
|
}
|
|
421
356
|
|
|
422
|
-
console.log(
|
|
423
|
-
'[useIAP] Event listeners registered, now calling initConnection...',
|
|
424
|
-
);
|
|
425
|
-
|
|
426
357
|
// NOW call initConnection after listeners are ready
|
|
427
358
|
const result = await initConnection();
|
|
428
359
|
setConnected(result);
|
|
429
|
-
console.log('[useIAP] initConnection result:', result);
|
|
430
360
|
if (!result) {
|
|
431
361
|
// If connection failed, clean up listeners
|
|
432
362
|
console.warn('[useIAP] Connection failed, cleaning up listeners...');
|
|
@@ -450,7 +380,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
450
380
|
currentSubscriptions.promotedProductIOS?.remove();
|
|
451
381
|
endConnection();
|
|
452
382
|
setConnected(false);
|
|
453
|
-
handledPurchaseIds.clear();
|
|
454
383
|
};
|
|
455
384
|
}, [initIapWithSubscriptions]);
|
|
456
385
|
|
|
@@ -462,12 +391,8 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
462
391
|
subscriptions,
|
|
463
392
|
finishTransaction,
|
|
464
393
|
availablePurchases,
|
|
465
|
-
currentPurchase,
|
|
466
|
-
currentPurchaseError,
|
|
467
394
|
promotedProductIOS,
|
|
468
395
|
activeSubscriptions,
|
|
469
|
-
clearCurrentPurchase,
|
|
470
|
-
clearCurrentPurchaseError,
|
|
471
396
|
getAvailablePurchases: getAvailablePurchasesInternal,
|
|
472
397
|
fetchProducts: fetchProductsInternal,
|
|
473
398
|
requestPurchase: requestPurchaseWithReset,
|