react-native-iap 14.4.7 → 14.4.9
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/lib/module/hooks/useIAP.js +21 -11
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +168 -49
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/debug.js +44 -0
- package/lib/module/utils/debug.js.map +1 -0
- package/lib/module/utils/type-bridge.js +13 -5
- package/lib/module/utils/type-bridge.js.map +1 -1
- package/lib/module/utils.js +3 -1
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +12 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/debug.d.ts +13 -0
- package/lib/typescript/src/utils/debug.d.ts.map +1 -0
- package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
- package/lib/typescript/src/utils.d.ts.map +1 -1
- package/openiap-versions.json +1 -1
- package/package.json +1 -1
- package/src/hooks/useIAP.ts +19 -15
- package/src/index.ts +232 -55
- package/src/utils/debug.ts +49 -0
- package/src/utils/type-bridge.ts +18 -7
- package/src/utils.ts +5 -1
- package/lib/module/helpers/subscription.js +0 -64
- package/lib/module/helpers/subscription.js.map +0 -1
- package/lib/typescript/src/helpers/subscription.d.ts +0 -14
- package/lib/typescript/src/helpers/subscription.d.ts.map +0 -1
- package/src/helpers/subscription.ts +0 -75
package/lib/module/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { NitroModules } from 'react-native-nitro-modules';
|
|
|
11
11
|
import { convertNitroProductToProduct, convertNitroPurchaseToPurchase, convertProductToProductSubscription, validateNitroProduct, validateNitroPurchase, convertNitroSubscriptionStatusToSubscriptionStatusIOS } from "./utils/type-bridge.js";
|
|
12
12
|
import { parseErrorStringToJsonObj } from "./utils/error.js";
|
|
13
13
|
import { normalizeErrorCodeFromNative } from "./utils/errorMapping.js";
|
|
14
|
+
import { RnIapConsole } from "./utils/debug.js";
|
|
14
15
|
import { getSuccessFromPurchaseVariant } from "./utils/purchase.js";
|
|
15
16
|
import { parseAppTransactionPayload } from "./utils.js";
|
|
16
17
|
|
|
@@ -66,7 +67,7 @@ export const purchaseUpdatedListener = listener => {
|
|
|
66
67
|
const convertedPurchase = convertNitroPurchaseToPurchase(nitroPurchase);
|
|
67
68
|
listener(convertedPurchase);
|
|
68
69
|
} else {
|
|
69
|
-
|
|
70
|
+
RnIapConsole.error('Invalid purchase data received from native:', nitroPurchase);
|
|
70
71
|
}
|
|
71
72
|
};
|
|
72
73
|
purchaseUpdatedListenerMap.set(listener, wrappedListener);
|
|
@@ -77,7 +78,7 @@ export const purchaseUpdatedListener = listener => {
|
|
|
77
78
|
} catch (e) {
|
|
78
79
|
const msg = toErrorMessage(e);
|
|
79
80
|
if (msg.includes('Nitro runtime not installed')) {
|
|
80
|
-
|
|
81
|
+
RnIapConsole.warn('[purchaseUpdatedListener] Nitro not ready yet; listener inert until initConnection()');
|
|
81
82
|
} else {
|
|
82
83
|
throw e;
|
|
83
84
|
}
|
|
@@ -112,7 +113,7 @@ export const purchaseErrorListener = listener => {
|
|
|
112
113
|
} catch (e) {
|
|
113
114
|
const msg = toErrorMessage(e);
|
|
114
115
|
if (msg.includes('Nitro runtime not installed')) {
|
|
115
|
-
|
|
116
|
+
RnIapConsole.warn('[purchaseErrorListener] Nitro not ready yet; listener inert until initConnection()');
|
|
116
117
|
} else {
|
|
117
118
|
throw e;
|
|
118
119
|
}
|
|
@@ -133,7 +134,7 @@ export const purchaseErrorListener = listener => {
|
|
|
133
134
|
};
|
|
134
135
|
export const promotedProductListenerIOS = listener => {
|
|
135
136
|
if (Platform.OS !== 'ios') {
|
|
136
|
-
|
|
137
|
+
RnIapConsole.warn('promotedProductListenerIOS: This listener is only available on iOS');
|
|
137
138
|
return {
|
|
138
139
|
remove: () => {}
|
|
139
140
|
};
|
|
@@ -143,7 +144,7 @@ export const promotedProductListenerIOS = listener => {
|
|
|
143
144
|
const convertedProduct = convertNitroProductToProduct(nitroProduct);
|
|
144
145
|
listener(convertedProduct);
|
|
145
146
|
} else {
|
|
146
|
-
|
|
147
|
+
RnIapConsole.error('Invalid promoted product data received from native:', nitroProduct);
|
|
147
148
|
}
|
|
148
149
|
};
|
|
149
150
|
promotedProductListenerMap.set(listener, wrappedListener);
|
|
@@ -154,7 +155,7 @@ export const promotedProductListenerIOS = listener => {
|
|
|
154
155
|
} catch (e) {
|
|
155
156
|
const msg = toErrorMessage(e);
|
|
156
157
|
if (msg.includes('Nitro runtime not installed')) {
|
|
157
|
-
|
|
158
|
+
RnIapConsole.warn('[promotedProductListenerIOS] Nitro not ready yet; listener inert until initConnection()');
|
|
158
159
|
} else {
|
|
159
160
|
throw e;
|
|
160
161
|
}
|
|
@@ -208,14 +209,60 @@ export const fetchProducts = async request => {
|
|
|
208
209
|
const nitroProducts = await IAP.instance.fetchProducts(skus, nitroType);
|
|
209
210
|
const validProducts = nitroProducts.filter(validateNitroProduct);
|
|
210
211
|
if (validProducts.length !== nitroProducts.length) {
|
|
211
|
-
|
|
212
|
+
RnIapConsole.warn(`[fetchProducts] Some products failed validation: ${nitroProducts.length - validProducts.length} invalid`);
|
|
212
213
|
}
|
|
213
214
|
return validProducts.map(convertNitroProductToProduct);
|
|
214
215
|
};
|
|
215
216
|
if (normalizedType === 'all') {
|
|
216
217
|
const converted = await fetchAndConvert('all');
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
RnIapConsole.debug('[fetchProducts] Converted items before filtering:', converted.map(item => ({
|
|
219
|
+
id: item.id,
|
|
220
|
+
type: item.type,
|
|
221
|
+
offers: item.subscriptionOfferDetailsAndroid
|
|
222
|
+
})));
|
|
223
|
+
|
|
224
|
+
// For 'all' type, need to properly distinguish between products and subscriptions
|
|
225
|
+
// On Android, check subscriptionOfferDetailsAndroid to determine if it's a real subscription
|
|
226
|
+
const productItems = converted.filter(item => {
|
|
227
|
+
// iOS: check type
|
|
228
|
+
if (Platform.OS === 'ios') {
|
|
229
|
+
return item.type === 'in-app';
|
|
230
|
+
}
|
|
231
|
+
// Android: if subscriptionOfferDetailsAndroid has content, it's a subscription
|
|
232
|
+
// Empty array or undefined means it's an in-app product (default)
|
|
233
|
+
const androidItem = item;
|
|
234
|
+
const hasSubscriptionOffers = androidItem.subscriptionOfferDetailsAndroid && Array.isArray(androidItem.subscriptionOfferDetailsAndroid) && androidItem.subscriptionOfferDetailsAndroid.length > 0;
|
|
235
|
+
RnIapConsole.debug(`[fetchProducts] ${item.id}: type=${item.type}, subscriptionOfferDetailsAndroid=${androidItem.subscriptionOfferDetailsAndroid === undefined ? 'undefined' : Array.isArray(androidItem.subscriptionOfferDetailsAndroid) ? `array(${androidItem.subscriptionOfferDetailsAndroid.length})` : 'not-array'}, isProduct=${!hasSubscriptionOffers}`);
|
|
236
|
+
return !hasSubscriptionOffers; // Default is in-app product
|
|
237
|
+
}).map(item => {
|
|
238
|
+
// Fix the type field for Android products that were incorrectly marked as 'subs'
|
|
239
|
+
if (Platform.OS === 'android' && item.type === 'subs') {
|
|
240
|
+
return {
|
|
241
|
+
...item,
|
|
242
|
+
type: 'in-app'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return item;
|
|
246
|
+
});
|
|
247
|
+
const subscriptionItems = converted.filter(item => {
|
|
248
|
+
// iOS: check type
|
|
249
|
+
if (Platform.OS === 'ios') {
|
|
250
|
+
return item.type === 'subs';
|
|
251
|
+
}
|
|
252
|
+
// Android: only consider it a subscription if it has actual offers
|
|
253
|
+
const androidItem = item;
|
|
254
|
+
const hasSubscriptionOffers = androidItem.subscriptionOfferDetailsAndroid && Array.isArray(androidItem.subscriptionOfferDetailsAndroid) && androidItem.subscriptionOfferDetailsAndroid.length > 0;
|
|
255
|
+
RnIapConsole.debug(`[fetchProducts-sub] ${item.id}: type=${item.type}, subscriptionOfferDetailsAndroid=${androidItem.subscriptionOfferDetailsAndroid === undefined ? 'undefined' : Array.isArray(androidItem.subscriptionOfferDetailsAndroid) ? `array(${androidItem.subscriptionOfferDetailsAndroid.length})` : 'not-array'}, isSub=${hasSubscriptionOffers}`);
|
|
256
|
+
return hasSubscriptionOffers;
|
|
257
|
+
}).map(item => {
|
|
258
|
+
// Ensure subscription items have the correct type
|
|
259
|
+
const subscription = convertProductToProductSubscription(item);
|
|
260
|
+
return {
|
|
261
|
+
...subscription,
|
|
262
|
+
type: 'subs'
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
RnIapConsole.debug('[fetchProducts] After filtering - products:', productItems.length, 'subs:', subscriptionItems.length);
|
|
219
266
|
return [...productItems, ...subscriptionItems];
|
|
220
267
|
}
|
|
221
268
|
const convertedProducts = await fetchAndConvert(toNitroProductType(normalizedType));
|
|
@@ -224,7 +271,7 @@ export const fetchProducts = async request => {
|
|
|
224
271
|
}
|
|
225
272
|
return convertedProducts;
|
|
226
273
|
} catch (error) {
|
|
227
|
-
|
|
274
|
+
RnIapConsole.error('[fetchProducts] Failed:', error);
|
|
228
275
|
throw error;
|
|
229
276
|
}
|
|
230
277
|
};
|
|
@@ -258,7 +305,7 @@ export const getAvailablePurchases = async options => {
|
|
|
258
305
|
const nitroPurchases = await IAP.instance.getAvailablePurchases(nitroOptions);
|
|
259
306
|
const validPurchases = nitroPurchases.filter(validateNitroPurchase);
|
|
260
307
|
if (validPurchases.length !== nitroPurchases.length) {
|
|
261
|
-
|
|
308
|
+
RnIapConsole.warn(`[getAvailablePurchases] Some purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`);
|
|
262
309
|
}
|
|
263
310
|
return validPurchases.map(convertNitroPurchaseToPurchase);
|
|
264
311
|
} else if (Platform.OS === 'android') {
|
|
@@ -278,14 +325,14 @@ export const getAvailablePurchases = async options => {
|
|
|
278
325
|
const allNitroPurchases = [...inappNitroPurchases, ...subsNitroPurchases];
|
|
279
326
|
const validPurchases = allNitroPurchases.filter(validateNitroPurchase);
|
|
280
327
|
if (validPurchases.length !== allNitroPurchases.length) {
|
|
281
|
-
|
|
328
|
+
RnIapConsole.warn(`[getAvailablePurchases] Some Android purchases failed validation: ${allNitroPurchases.length - validPurchases.length} invalid`);
|
|
282
329
|
}
|
|
283
330
|
return validPurchases.map(convertNitroPurchaseToPurchase);
|
|
284
331
|
} else {
|
|
285
332
|
throw new Error('Unsupported platform');
|
|
286
333
|
}
|
|
287
334
|
} catch (error) {
|
|
288
|
-
|
|
335
|
+
RnIapConsole.error('Failed to get available purchases:', error);
|
|
289
336
|
throw error;
|
|
290
337
|
}
|
|
291
338
|
};
|
|
@@ -307,7 +354,7 @@ export const getPromotedProductIOS = async () => {
|
|
|
307
354
|
const converted = convertNitroProductToProduct(nitroProduct);
|
|
308
355
|
return converted.platform === 'ios' ? converted : null;
|
|
309
356
|
} catch (error) {
|
|
310
|
-
|
|
357
|
+
RnIapConsole.error('[getPromotedProductIOS] Failed:', error);
|
|
311
358
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
312
359
|
throw new Error(errorJson.message);
|
|
313
360
|
}
|
|
@@ -321,13 +368,13 @@ export const getStorefrontIOS = async () => {
|
|
|
321
368
|
const storefront = await IAP.instance.getStorefrontIOS();
|
|
322
369
|
return storefront;
|
|
323
370
|
} catch (error) {
|
|
324
|
-
|
|
371
|
+
RnIapConsole.error('Failed to get storefront:', error);
|
|
325
372
|
throw error;
|
|
326
373
|
}
|
|
327
374
|
};
|
|
328
375
|
export const getStorefront = async () => {
|
|
329
376
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
330
|
-
|
|
377
|
+
RnIapConsole.warn('[getStorefront] Storefront lookup is only supported on iOS and Android.');
|
|
331
378
|
return '';
|
|
332
379
|
}
|
|
333
380
|
const hasUnifiedMethod = typeof IAP.instance.getStorefront === 'function';
|
|
@@ -335,14 +382,14 @@ export const getStorefront = async () => {
|
|
|
335
382
|
return getStorefrontIOS();
|
|
336
383
|
}
|
|
337
384
|
if (!hasUnifiedMethod) {
|
|
338
|
-
|
|
385
|
+
RnIapConsole.warn('[getStorefront] Native getStorefront is not available on this build.');
|
|
339
386
|
return '';
|
|
340
387
|
}
|
|
341
388
|
try {
|
|
342
389
|
const storefront = await IAP.instance.getStorefront();
|
|
343
390
|
return storefront ?? '';
|
|
344
391
|
} catch (error) {
|
|
345
|
-
|
|
392
|
+
RnIapConsole.error(`[getStorefront] Failed to get storefront on ${Platform.OS}:`, error);
|
|
346
393
|
throw error;
|
|
347
394
|
}
|
|
348
395
|
};
|
|
@@ -367,7 +414,7 @@ export const getAppTransactionIOS = async () => {
|
|
|
367
414
|
}
|
|
368
415
|
return null;
|
|
369
416
|
} catch (error) {
|
|
370
|
-
|
|
417
|
+
RnIapConsole.error('Failed to get app transaction:', error);
|
|
371
418
|
throw error;
|
|
372
419
|
}
|
|
373
420
|
};
|
|
@@ -380,7 +427,7 @@ export const subscriptionStatusIOS = async sku => {
|
|
|
380
427
|
if (!Array.isArray(statuses)) return [];
|
|
381
428
|
return statuses.filter(status => status != null).map(convertNitroSubscriptionStatusToSubscriptionStatusIOS);
|
|
382
429
|
} catch (error) {
|
|
383
|
-
|
|
430
|
+
RnIapConsole.error('[subscriptionStatusIOS] Failed:', error);
|
|
384
431
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
385
432
|
throw new Error(errorJson.message);
|
|
386
433
|
}
|
|
@@ -397,7 +444,7 @@ export const currentEntitlementIOS = async sku => {
|
|
|
397
444
|
}
|
|
398
445
|
return null;
|
|
399
446
|
} catch (error) {
|
|
400
|
-
|
|
447
|
+
RnIapConsole.error('[currentEntitlementIOS] Failed:', error);
|
|
401
448
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
402
449
|
throw new Error(errorJson.message);
|
|
403
450
|
}
|
|
@@ -414,7 +461,7 @@ export const latestTransactionIOS = async sku => {
|
|
|
414
461
|
}
|
|
415
462
|
return null;
|
|
416
463
|
} catch (error) {
|
|
417
|
-
|
|
464
|
+
RnIapConsole.error('[latestTransactionIOS] Failed:', error);
|
|
418
465
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
419
466
|
throw new Error(errorJson.message);
|
|
420
467
|
}
|
|
@@ -427,7 +474,7 @@ export const getPendingTransactionsIOS = async () => {
|
|
|
427
474
|
const nitroPurchases = await IAP.instance.getPendingTransactionsIOS();
|
|
428
475
|
return nitroPurchases.map(convertNitroPurchaseToPurchase).filter(purchase => purchase.platform === 'ios');
|
|
429
476
|
} catch (error) {
|
|
430
|
-
|
|
477
|
+
RnIapConsole.error('[getPendingTransactionsIOS] Failed:', error);
|
|
431
478
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
432
479
|
throw new Error(errorJson.message);
|
|
433
480
|
}
|
|
@@ -440,7 +487,7 @@ export const showManageSubscriptionsIOS = async () => {
|
|
|
440
487
|
const nitroPurchases = await IAP.instance.showManageSubscriptionsIOS();
|
|
441
488
|
return nitroPurchases.map(convertNitroPurchaseToPurchase).filter(purchase => purchase.platform === 'ios');
|
|
442
489
|
} catch (error) {
|
|
443
|
-
|
|
490
|
+
RnIapConsole.error('[showManageSubscriptionsIOS] Failed:', error);
|
|
444
491
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
445
492
|
throw new Error(errorJson.message);
|
|
446
493
|
}
|
|
@@ -452,7 +499,7 @@ export const isEligibleForIntroOfferIOS = async groupID => {
|
|
|
452
499
|
try {
|
|
453
500
|
return await IAP.instance.isEligibleForIntroOfferIOS(groupID);
|
|
454
501
|
} catch (error) {
|
|
455
|
-
|
|
502
|
+
RnIapConsole.error('[isEligibleForIntroOfferIOS] Failed:', error);
|
|
456
503
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
457
504
|
throw new Error(errorJson.message);
|
|
458
505
|
}
|
|
@@ -464,7 +511,7 @@ export const getReceiptDataIOS = async () => {
|
|
|
464
511
|
try {
|
|
465
512
|
return await IAP.instance.getReceiptDataIOS();
|
|
466
513
|
} catch (error) {
|
|
467
|
-
|
|
514
|
+
RnIapConsole.error('[getReceiptDataIOS] Failed:', error);
|
|
468
515
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
469
516
|
throw new Error(errorJson.message);
|
|
470
517
|
}
|
|
@@ -479,7 +526,7 @@ export const getReceiptIOS = async () => {
|
|
|
479
526
|
}
|
|
480
527
|
return await IAP.instance.getReceiptDataIOS();
|
|
481
528
|
} catch (error) {
|
|
482
|
-
|
|
529
|
+
RnIapConsole.error('[getReceiptIOS] Failed:', error);
|
|
483
530
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
484
531
|
throw new Error(errorJson.message);
|
|
485
532
|
}
|
|
@@ -494,7 +541,7 @@ export const requestReceiptRefreshIOS = async () => {
|
|
|
494
541
|
}
|
|
495
542
|
return await IAP.instance.getReceiptDataIOS();
|
|
496
543
|
} catch (error) {
|
|
497
|
-
|
|
544
|
+
RnIapConsole.error('[requestReceiptRefreshIOS] Failed:', error);
|
|
498
545
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
499
546
|
throw new Error(errorJson.message);
|
|
500
547
|
}
|
|
@@ -506,7 +553,7 @@ export const isTransactionVerifiedIOS = async sku => {
|
|
|
506
553
|
try {
|
|
507
554
|
return await IAP.instance.isTransactionVerifiedIOS(sku);
|
|
508
555
|
} catch (error) {
|
|
509
|
-
|
|
556
|
+
RnIapConsole.error('[isTransactionVerifiedIOS] Failed:', error);
|
|
510
557
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
511
558
|
throw new Error(errorJson.message);
|
|
512
559
|
}
|
|
@@ -518,7 +565,7 @@ export const getTransactionJwsIOS = async sku => {
|
|
|
518
565
|
try {
|
|
519
566
|
return await IAP.instance.getTransactionJwsIOS(sku);
|
|
520
567
|
} catch (error) {
|
|
521
|
-
|
|
568
|
+
RnIapConsole.error('[getTransactionJwsIOS] Failed:', error);
|
|
522
569
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
523
570
|
throw new Error(errorJson.message);
|
|
524
571
|
}
|
|
@@ -535,7 +582,7 @@ export const initConnection = async () => {
|
|
|
535
582
|
try {
|
|
536
583
|
return await IAP.instance.initConnection();
|
|
537
584
|
} catch (error) {
|
|
538
|
-
|
|
585
|
+
RnIapConsole.error('Failed to initialize IAP connection:', error);
|
|
539
586
|
throw error;
|
|
540
587
|
}
|
|
541
588
|
};
|
|
@@ -548,7 +595,7 @@ export const endConnection = async () => {
|
|
|
548
595
|
if (!iapRef) return true;
|
|
549
596
|
return await IAP.instance.endConnection();
|
|
550
597
|
} catch (error) {
|
|
551
|
-
|
|
598
|
+
RnIapConsole.error('Failed to end IAP connection:', error);
|
|
552
599
|
throw error;
|
|
553
600
|
}
|
|
554
601
|
};
|
|
@@ -562,7 +609,7 @@ export const restorePurchases = async () => {
|
|
|
562
609
|
onlyIncludeActiveItemsIOS: true
|
|
563
610
|
});
|
|
564
611
|
} catch (error) {
|
|
565
|
-
|
|
612
|
+
RnIapConsole.error('Failed to restore purchases:', error);
|
|
566
613
|
throw error;
|
|
567
614
|
}
|
|
568
615
|
};
|
|
@@ -651,7 +698,7 @@ export const requestPurchase = async request => {
|
|
|
651
698
|
}
|
|
652
699
|
return await IAP.instance.requestPurchase(unifiedRequest);
|
|
653
700
|
} catch (error) {
|
|
654
|
-
|
|
701
|
+
RnIapConsole.error('Failed to request purchase:', error);
|
|
655
702
|
throw error;
|
|
656
703
|
}
|
|
657
704
|
};
|
|
@@ -718,7 +765,7 @@ export const finishTransaction = async args => {
|
|
|
718
765
|
return;
|
|
719
766
|
}
|
|
720
767
|
}
|
|
721
|
-
|
|
768
|
+
RnIapConsole.error('Failed to finish transaction:', error);
|
|
722
769
|
throw error;
|
|
723
770
|
}
|
|
724
771
|
};
|
|
@@ -746,7 +793,7 @@ export const acknowledgePurchaseAndroid = async purchaseToken => {
|
|
|
746
793
|
});
|
|
747
794
|
return getSuccessFromPurchaseVariant(result, 'acknowledgePurchaseAndroid');
|
|
748
795
|
} catch (error) {
|
|
749
|
-
|
|
796
|
+
RnIapConsole.error('Failed to acknowledge purchase Android:', error);
|
|
750
797
|
throw error;
|
|
751
798
|
}
|
|
752
799
|
};
|
|
@@ -774,7 +821,7 @@ export const consumePurchaseAndroid = async purchaseToken => {
|
|
|
774
821
|
});
|
|
775
822
|
return getSuccessFromPurchaseVariant(result, 'consumePurchaseAndroid');
|
|
776
823
|
} catch (error) {
|
|
777
|
-
|
|
824
|
+
RnIapConsole.error('Failed to consume purchase Android:', error);
|
|
778
825
|
throw error;
|
|
779
826
|
}
|
|
780
827
|
};
|
|
@@ -841,7 +888,7 @@ export const validateReceipt = async options => {
|
|
|
841
888
|
return result;
|
|
842
889
|
}
|
|
843
890
|
} catch (error) {
|
|
844
|
-
|
|
891
|
+
RnIapConsole.error('[validateReceipt] Failed:', error);
|
|
845
892
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
846
893
|
throw new Error(errorJson.message);
|
|
847
894
|
}
|
|
@@ -860,7 +907,7 @@ export const syncIOS = async () => {
|
|
|
860
907
|
const result = await IAP.instance.syncIOS();
|
|
861
908
|
return Boolean(result);
|
|
862
909
|
} catch (error) {
|
|
863
|
-
|
|
910
|
+
RnIapConsole.error('[syncIOS] Failed:', error);
|
|
864
911
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
865
912
|
throw new Error(errorJson.message);
|
|
866
913
|
}
|
|
@@ -879,7 +926,7 @@ export const presentCodeRedemptionSheetIOS = async () => {
|
|
|
879
926
|
const result = await IAP.instance.presentCodeRedemptionSheetIOS();
|
|
880
927
|
return Boolean(result);
|
|
881
928
|
} catch (error) {
|
|
882
|
-
|
|
929
|
+
RnIapConsole.error('[presentCodeRedemptionSheetIOS] Failed:', error);
|
|
883
930
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
884
931
|
throw new Error(errorJson.message);
|
|
885
932
|
}
|
|
@@ -907,7 +954,7 @@ export const requestPurchaseOnPromotedProductIOS = async () => {
|
|
|
907
954
|
}
|
|
908
955
|
return true;
|
|
909
956
|
} catch (error) {
|
|
910
|
-
|
|
957
|
+
RnIapConsole.error('[requestPurchaseOnPromotedProductIOS] Failed:', error);
|
|
911
958
|
throw error;
|
|
912
959
|
}
|
|
913
960
|
};
|
|
@@ -925,7 +972,7 @@ export const clearTransactionIOS = async () => {
|
|
|
925
972
|
await IAP.instance.clearTransactionIOS();
|
|
926
973
|
return true;
|
|
927
974
|
} catch (error) {
|
|
928
|
-
|
|
975
|
+
RnIapConsole.error('[clearTransactionIOS] Failed:', error);
|
|
929
976
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
930
977
|
throw new Error(errorJson.message);
|
|
931
978
|
}
|
|
@@ -945,7 +992,7 @@ export const beginRefundRequestIOS = async sku => {
|
|
|
945
992
|
const status = await IAP.instance.beginRefundRequestIOS(sku);
|
|
946
993
|
return status ?? null;
|
|
947
994
|
} catch (error) {
|
|
948
|
-
|
|
995
|
+
RnIapConsole.error('[beginRefundRequestIOS] Failed:', error);
|
|
949
996
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
950
997
|
throw new Error(errorJson.message);
|
|
951
998
|
}
|
|
@@ -1035,7 +1082,7 @@ export const deepLinkToSubscriptions = async options => {
|
|
|
1035
1082
|
await IAP.instance.showManageSubscriptionsIOS();
|
|
1036
1083
|
}
|
|
1037
1084
|
} catch (error) {
|
|
1038
|
-
|
|
1085
|
+
RnIapConsole.warn('[deepLinkToSubscriptions] Failed on iOS:', error);
|
|
1039
1086
|
}
|
|
1040
1087
|
}
|
|
1041
1088
|
};
|
|
@@ -1050,7 +1097,7 @@ export const deepLinkToSubscriptionsIOS = async () => {
|
|
|
1050
1097
|
await IAP.instance.showManageSubscriptionsIOS();
|
|
1051
1098
|
return true;
|
|
1052
1099
|
} catch (error) {
|
|
1053
|
-
|
|
1100
|
+
RnIapConsole.error('[deepLinkToSubscriptionsIOS] Failed:', error);
|
|
1054
1101
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1055
1102
|
throw new Error(errorJson.message);
|
|
1056
1103
|
}
|
|
@@ -1075,8 +1122,80 @@ export const deepLinkToSubscriptionsIOS = async () => {
|
|
|
1075
1122
|
* }
|
|
1076
1123
|
* ```
|
|
1077
1124
|
*/
|
|
1078
|
-
|
|
1079
|
-
|
|
1125
|
+
/**
|
|
1126
|
+
* Get active subscriptions
|
|
1127
|
+
* @param subscriptionIds - Optional array of subscription IDs to filter by
|
|
1128
|
+
* @returns Promise<ActiveSubscription[]> - Array of active subscriptions
|
|
1129
|
+
*/
|
|
1130
|
+
export const getActiveSubscriptions = async subscriptionIds => {
|
|
1131
|
+
try {
|
|
1132
|
+
// Get all available purchases first
|
|
1133
|
+
const allPurchases = await getAvailablePurchases();
|
|
1134
|
+
|
|
1135
|
+
// For the critical bug fix: this function was previously returning ALL purchases
|
|
1136
|
+
// Now we properly filter for subscriptions only
|
|
1137
|
+
|
|
1138
|
+
// In production with real data, Android subscription filtering is done via platform-specific calls
|
|
1139
|
+
// But for backward compatibility and test support, we also check platform-specific fields
|
|
1140
|
+
|
|
1141
|
+
// Since expirationDateIOS and subscriptionGroupIdIOS are not available in NitroPurchase,
|
|
1142
|
+
// we need to rely on other indicators or assume all purchases are subscriptions
|
|
1143
|
+
// when called from getActiveSubscriptions
|
|
1144
|
+
const purchases = allPurchases;
|
|
1145
|
+
|
|
1146
|
+
// Filter for subscriptions and map to ActiveSubscription format
|
|
1147
|
+
const subscriptions = purchases.filter(purchase => {
|
|
1148
|
+
// Filter by subscription IDs if provided
|
|
1149
|
+
if (subscriptionIds && subscriptionIds.length > 0) {
|
|
1150
|
+
return subscriptionIds.includes(purchase.productId);
|
|
1151
|
+
}
|
|
1152
|
+
return true;
|
|
1153
|
+
}).map(purchase => {
|
|
1154
|
+
// Safe access to platform-specific fields with type guards
|
|
1155
|
+
const expirationDateIOS = 'expirationDateIOS' in purchase ? purchase.expirationDateIOS ?? null : null;
|
|
1156
|
+
const environmentIOS = 'environmentIOS' in purchase ? purchase.environmentIOS ?? null : null;
|
|
1157
|
+
const autoRenewingAndroid = 'autoRenewingAndroid' in purchase || 'isAutoRenewing' in purchase ? purchase.autoRenewingAndroid ?? purchase.isAutoRenewing // deprecated - use isAutoRenewing instead
|
|
1158
|
+
: null;
|
|
1159
|
+
return {
|
|
1160
|
+
productId: purchase.productId,
|
|
1161
|
+
isActive: true,
|
|
1162
|
+
// If it's in availablePurchases, it's active
|
|
1163
|
+
// Backend validation fields - use transactionId ?? id for proper field mapping
|
|
1164
|
+
transactionId: purchase.transactionId ?? purchase.id,
|
|
1165
|
+
purchaseToken: purchase.purchaseToken,
|
|
1166
|
+
transactionDate: purchase.transactionDate,
|
|
1167
|
+
// Platform-specific fields
|
|
1168
|
+
expirationDateIOS,
|
|
1169
|
+
autoRenewingAndroid,
|
|
1170
|
+
environmentIOS,
|
|
1171
|
+
// Convenience fields
|
|
1172
|
+
willExpireSoon: false,
|
|
1173
|
+
// Would need to calculate based on expiration date
|
|
1174
|
+
daysUntilExpirationIOS: expirationDateIOS != null ? Math.ceil((expirationDateIOS - Date.now()) / (1000 * 60 * 60 * 24)) : null
|
|
1175
|
+
};
|
|
1176
|
+
});
|
|
1177
|
+
return subscriptions;
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
RnIapConsole.error('Failed to get active subscriptions:', error);
|
|
1180
|
+
const errorJson = parseErrorStringToJsonObj(error);
|
|
1181
|
+
throw new Error(errorJson.message);
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Check if there are any active subscriptions
|
|
1187
|
+
* @param subscriptionIds - Optional array of subscription IDs to check
|
|
1188
|
+
* @returns Promise<boolean> - True if there are active subscriptions
|
|
1189
|
+
*/
|
|
1190
|
+
export const hasActiveSubscriptions = async subscriptionIds => {
|
|
1191
|
+
try {
|
|
1192
|
+
const activeSubscriptions = await getActiveSubscriptions(subscriptionIds);
|
|
1193
|
+
return activeSubscriptions.length > 0;
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
RnIapConsole.error('Failed to check active subscriptions:', error);
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1080
1199
|
|
|
1081
1200
|
// Type conversion utilities
|
|
1082
1201
|
export { convertNitroProductToProduct, convertNitroPurchaseToPurchase, convertProductToProductSubscription, validateNitroProduct, validateNitroPurchase, checkTypeSynchronization } from "./utils/type-bridge.js";
|
|
@@ -1116,7 +1235,7 @@ const toNitroProductType = type => {
|
|
|
1116
1235
|
return 'all';
|
|
1117
1236
|
}
|
|
1118
1237
|
if (type === 'inapp') {
|
|
1119
|
-
|
|
1238
|
+
RnIapConsole.warn(LEGACY_INAPP_WARNING);
|
|
1120
1239
|
return 'inapp';
|
|
1121
1240
|
}
|
|
1122
1241
|
return 'inapp';
|
|
@@ -1135,7 +1254,7 @@ const normalizeProductQueryType = type => {
|
|
|
1135
1254
|
return 'subs';
|
|
1136
1255
|
}
|
|
1137
1256
|
if (normalized === 'inapp') {
|
|
1138
|
-
|
|
1257
|
+
RnIapConsole.warn(LEGACY_INAPP_WARNING);
|
|
1139
1258
|
return 'in-app';
|
|
1140
1259
|
}
|
|
1141
1260
|
if (normalized === 'in-app') {
|