react-native-iap 14.4.6 → 14.4.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/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 +2 -2
- 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/src/index.ts
CHANGED
|
@@ -33,6 +33,10 @@ import type {
|
|
|
33
33
|
RequestSubscriptionAndroidProps,
|
|
34
34
|
RequestSubscriptionIosProps,
|
|
35
35
|
RequestSubscriptionPropsByPlatforms,
|
|
36
|
+
ActiveSubscription,
|
|
37
|
+
PurchaseAndroid,
|
|
38
|
+
ProductAndroid,
|
|
39
|
+
ProductSubscriptionAndroid,
|
|
36
40
|
} from './types';
|
|
37
41
|
import {
|
|
38
42
|
convertNitroProductToProduct,
|
|
@@ -44,6 +48,7 @@ import {
|
|
|
44
48
|
} from './utils/type-bridge';
|
|
45
49
|
import {parseErrorStringToJsonObj} from './utils/error';
|
|
46
50
|
import {normalizeErrorCodeFromNative} from './utils/errorMapping';
|
|
51
|
+
import {RnIapConsole} from './utils/debug';
|
|
47
52
|
import {getSuccessFromPurchaseVariant} from './utils/purchase';
|
|
48
53
|
import {parseAppTransactionPayload} from './utils';
|
|
49
54
|
|
|
@@ -154,7 +159,7 @@ export const purchaseUpdatedListener = (
|
|
|
154
159
|
const convertedPurchase = convertNitroPurchaseToPurchase(nitroPurchase);
|
|
155
160
|
listener(convertedPurchase);
|
|
156
161
|
} else {
|
|
157
|
-
|
|
162
|
+
RnIapConsole.error(
|
|
158
163
|
'Invalid purchase data received from native:',
|
|
159
164
|
nitroPurchase,
|
|
160
165
|
);
|
|
@@ -169,7 +174,7 @@ export const purchaseUpdatedListener = (
|
|
|
169
174
|
} catch (e) {
|
|
170
175
|
const msg = toErrorMessage(e);
|
|
171
176
|
if (msg.includes('Nitro runtime not installed')) {
|
|
172
|
-
|
|
177
|
+
RnIapConsole.warn(
|
|
173
178
|
'[purchaseUpdatedListener] Nitro not ready yet; listener inert until initConnection()',
|
|
174
179
|
);
|
|
175
180
|
} else {
|
|
@@ -211,7 +216,7 @@ export const purchaseErrorListener = (
|
|
|
211
216
|
} catch (e) {
|
|
212
217
|
const msg = toErrorMessage(e);
|
|
213
218
|
if (msg.includes('Nitro runtime not installed')) {
|
|
214
|
-
|
|
219
|
+
RnIapConsole.warn(
|
|
215
220
|
'[purchaseErrorListener] Nitro not ready yet; listener inert until initConnection()',
|
|
216
221
|
);
|
|
217
222
|
} else {
|
|
@@ -238,7 +243,7 @@ export const promotedProductListenerIOS = (
|
|
|
238
243
|
listener: (product: Product) => void,
|
|
239
244
|
): EventSubscription => {
|
|
240
245
|
if (Platform.OS !== 'ios') {
|
|
241
|
-
|
|
246
|
+
RnIapConsole.warn(
|
|
242
247
|
'promotedProductListenerIOS: This listener is only available on iOS',
|
|
243
248
|
);
|
|
244
249
|
return {remove: () => {}};
|
|
@@ -249,7 +254,7 @@ export const promotedProductListenerIOS = (
|
|
|
249
254
|
const convertedProduct = convertNitroProductToProduct(nitroProduct);
|
|
250
255
|
listener(convertedProduct);
|
|
251
256
|
} else {
|
|
252
|
-
|
|
257
|
+
RnIapConsole.error(
|
|
253
258
|
'Invalid promoted product data received from native:',
|
|
254
259
|
nitroProduct,
|
|
255
260
|
);
|
|
@@ -264,7 +269,7 @@ export const promotedProductListenerIOS = (
|
|
|
264
269
|
} catch (e) {
|
|
265
270
|
const msg = toErrorMessage(e);
|
|
266
271
|
if (msg.includes('Nitro runtime not installed')) {
|
|
267
|
-
|
|
272
|
+
RnIapConsole.warn(
|
|
268
273
|
'[promotedProductListenerIOS] Nitro not ready yet; listener inert until initConnection()',
|
|
269
274
|
);
|
|
270
275
|
} else {
|
|
@@ -323,7 +328,7 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
|
|
|
323
328
|
const nitroProducts = await IAP.instance.fetchProducts(skus, nitroType);
|
|
324
329
|
const validProducts = nitroProducts.filter(validateNitroProduct);
|
|
325
330
|
if (validProducts.length !== nitroProducts.length) {
|
|
326
|
-
|
|
331
|
+
RnIapConsole.warn(
|
|
327
332
|
`[fetchProducts] Some products failed validation: ${nitroProducts.length - validProducts.length} invalid`,
|
|
328
333
|
);
|
|
329
334
|
}
|
|
@@ -332,13 +337,94 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
|
|
|
332
337
|
|
|
333
338
|
if (normalizedType === 'all') {
|
|
334
339
|
const converted = await fetchAndConvert('all');
|
|
335
|
-
|
|
336
|
-
|
|
340
|
+
|
|
341
|
+
RnIapConsole.debug(
|
|
342
|
+
'[fetchProducts] Converted items before filtering:',
|
|
343
|
+
converted.map((item) => ({
|
|
344
|
+
id: item.id,
|
|
345
|
+
type: item.type,
|
|
346
|
+
offers: (item as ProductSubscriptionAndroid)
|
|
347
|
+
.subscriptionOfferDetailsAndroid,
|
|
348
|
+
})),
|
|
337
349
|
);
|
|
338
|
-
const subscriptionItems = converted
|
|
339
|
-
.filter((item) => item.type === 'subs')
|
|
340
|
-
.map(convertProductToProductSubscription);
|
|
341
350
|
|
|
351
|
+
// For 'all' type, need to properly distinguish between products and subscriptions
|
|
352
|
+
// On Android, check subscriptionOfferDetailsAndroid to determine if it's a real subscription
|
|
353
|
+
const productItems = converted
|
|
354
|
+
.filter((item): item is Product => {
|
|
355
|
+
// iOS: check type
|
|
356
|
+
if (Platform.OS === 'ios') {
|
|
357
|
+
return item.type === 'in-app';
|
|
358
|
+
}
|
|
359
|
+
// Android: if subscriptionOfferDetailsAndroid has content, it's a subscription
|
|
360
|
+
// Empty array or undefined means it's an in-app product (default)
|
|
361
|
+
const androidItem = item as ProductAndroid;
|
|
362
|
+
const hasSubscriptionOffers =
|
|
363
|
+
androidItem.subscriptionOfferDetailsAndroid &&
|
|
364
|
+
Array.isArray(androidItem.subscriptionOfferDetailsAndroid) &&
|
|
365
|
+
androidItem.subscriptionOfferDetailsAndroid.length > 0;
|
|
366
|
+
|
|
367
|
+
RnIapConsole.debug(
|
|
368
|
+
`[fetchProducts] ${item.id}: type=${item.type}, subscriptionOfferDetailsAndroid=${
|
|
369
|
+
androidItem.subscriptionOfferDetailsAndroid === undefined
|
|
370
|
+
? 'undefined'
|
|
371
|
+
: Array.isArray(androidItem.subscriptionOfferDetailsAndroid)
|
|
372
|
+
? `array(${androidItem.subscriptionOfferDetailsAndroid.length})`
|
|
373
|
+
: 'not-array'
|
|
374
|
+
}, isProduct=${!hasSubscriptionOffers}`,
|
|
375
|
+
);
|
|
376
|
+
return !hasSubscriptionOffers; // Default is in-app product
|
|
377
|
+
})
|
|
378
|
+
.map((item) => {
|
|
379
|
+
// Fix the type field for Android products that were incorrectly marked as 'subs'
|
|
380
|
+
if (Platform.OS === 'android' && item.type === 'subs') {
|
|
381
|
+
return {
|
|
382
|
+
...item,
|
|
383
|
+
type: 'in-app' as const,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return item;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const subscriptionItems = converted
|
|
390
|
+
.filter((item) => {
|
|
391
|
+
// iOS: check type
|
|
392
|
+
if (Platform.OS === 'ios') {
|
|
393
|
+
return item.type === 'subs';
|
|
394
|
+
}
|
|
395
|
+
// Android: only consider it a subscription if it has actual offers
|
|
396
|
+
const androidItem = item as ProductAndroid;
|
|
397
|
+
const hasSubscriptionOffers =
|
|
398
|
+
androidItem.subscriptionOfferDetailsAndroid &&
|
|
399
|
+
Array.isArray(androidItem.subscriptionOfferDetailsAndroid) &&
|
|
400
|
+
androidItem.subscriptionOfferDetailsAndroid.length > 0;
|
|
401
|
+
|
|
402
|
+
RnIapConsole.debug(
|
|
403
|
+
`[fetchProducts-sub] ${item.id}: type=${item.type}, subscriptionOfferDetailsAndroid=${
|
|
404
|
+
androidItem.subscriptionOfferDetailsAndroid === undefined
|
|
405
|
+
? 'undefined'
|
|
406
|
+
: Array.isArray(androidItem.subscriptionOfferDetailsAndroid)
|
|
407
|
+
? `array(${androidItem.subscriptionOfferDetailsAndroid.length})`
|
|
408
|
+
: 'not-array'
|
|
409
|
+
}, isSub=${hasSubscriptionOffers}`,
|
|
410
|
+
);
|
|
411
|
+
return hasSubscriptionOffers;
|
|
412
|
+
})
|
|
413
|
+
.map((item) => {
|
|
414
|
+
// Ensure subscription items have the correct type
|
|
415
|
+
const subscription = convertProductToProductSubscription(item);
|
|
416
|
+
return {
|
|
417
|
+
...subscription,
|
|
418
|
+
type: 'subs' as const,
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
RnIapConsole.debug(
|
|
423
|
+
'[fetchProducts] After filtering - products:',
|
|
424
|
+
productItems.length,
|
|
425
|
+
'subs:',
|
|
426
|
+
subscriptionItems.length,
|
|
427
|
+
);
|
|
342
428
|
return [...productItems, ...subscriptionItems] as FetchProductsResult;
|
|
343
429
|
}
|
|
344
430
|
|
|
@@ -354,7 +440,7 @@ export const fetchProducts: QueryField<'fetchProducts'> = async (request) => {
|
|
|
354
440
|
|
|
355
441
|
return convertedProducts as FetchProductsResult;
|
|
356
442
|
} catch (error) {
|
|
357
|
-
|
|
443
|
+
RnIapConsole.error('[fetchProducts] Failed:', error);
|
|
358
444
|
throw error;
|
|
359
445
|
}
|
|
360
446
|
};
|
|
@@ -396,7 +482,7 @@ export const getAvailablePurchases: QueryField<
|
|
|
396
482
|
|
|
397
483
|
const validPurchases = nitroPurchases.filter(validateNitroPurchase);
|
|
398
484
|
if (validPurchases.length !== nitroPurchases.length) {
|
|
399
|
-
|
|
485
|
+
RnIapConsole.warn(
|
|
400
486
|
`[getAvailablePurchases] Some purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`,
|
|
401
487
|
);
|
|
402
488
|
}
|
|
@@ -415,7 +501,7 @@ export const getAvailablePurchases: QueryField<
|
|
|
415
501
|
const allNitroPurchases = [...inappNitroPurchases, ...subsNitroPurchases];
|
|
416
502
|
const validPurchases = allNitroPurchases.filter(validateNitroPurchase);
|
|
417
503
|
if (validPurchases.length !== allNitroPurchases.length) {
|
|
418
|
-
|
|
504
|
+
RnIapConsole.warn(
|
|
419
505
|
`[getAvailablePurchases] Some Android purchases failed validation: ${allNitroPurchases.length - validPurchases.length} invalid`,
|
|
420
506
|
);
|
|
421
507
|
}
|
|
@@ -425,7 +511,7 @@ export const getAvailablePurchases: QueryField<
|
|
|
425
511
|
throw new Error('Unsupported platform');
|
|
426
512
|
}
|
|
427
513
|
} catch (error) {
|
|
428
|
-
|
|
514
|
+
RnIapConsole.error('Failed to get available purchases:', error);
|
|
429
515
|
throw error;
|
|
430
516
|
}
|
|
431
517
|
};
|
|
@@ -453,7 +539,7 @@ export const getPromotedProductIOS: QueryField<
|
|
|
453
539
|
const converted = convertNitroProductToProduct(nitroProduct);
|
|
454
540
|
return converted.platform === 'ios' ? (converted as ProductIOS) : null;
|
|
455
541
|
} catch (error) {
|
|
456
|
-
|
|
542
|
+
RnIapConsole.error('[getPromotedProductIOS] Failed:', error);
|
|
457
543
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
458
544
|
throw new Error(errorJson.message);
|
|
459
545
|
}
|
|
@@ -470,14 +556,14 @@ export const getStorefrontIOS: QueryField<'getStorefrontIOS'> = async () => {
|
|
|
470
556
|
const storefront = await IAP.instance.getStorefrontIOS();
|
|
471
557
|
return storefront;
|
|
472
558
|
} catch (error) {
|
|
473
|
-
|
|
559
|
+
RnIapConsole.error('Failed to get storefront:', error);
|
|
474
560
|
throw error;
|
|
475
561
|
}
|
|
476
562
|
};
|
|
477
563
|
|
|
478
564
|
export const getStorefront: QueryField<'getStorefront'> = async () => {
|
|
479
565
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
480
|
-
|
|
566
|
+
RnIapConsole.warn(
|
|
481
567
|
'[getStorefront] Storefront lookup is only supported on iOS and Android.',
|
|
482
568
|
);
|
|
483
569
|
return '';
|
|
@@ -490,7 +576,7 @@ export const getStorefront: QueryField<'getStorefront'> = async () => {
|
|
|
490
576
|
}
|
|
491
577
|
|
|
492
578
|
if (!hasUnifiedMethod) {
|
|
493
|
-
|
|
579
|
+
RnIapConsole.warn(
|
|
494
580
|
'[getStorefront] Native getStorefront is not available on this build.',
|
|
495
581
|
);
|
|
496
582
|
return '';
|
|
@@ -500,7 +586,7 @@ export const getStorefront: QueryField<'getStorefront'> = async () => {
|
|
|
500
586
|
const storefront = await IAP.instance.getStorefront();
|
|
501
587
|
return storefront ?? '';
|
|
502
588
|
} catch (error) {
|
|
503
|
-
|
|
589
|
+
RnIapConsole.error(
|
|
504
590
|
`[getStorefront] Failed to get storefront on ${Platform.OS}:`,
|
|
505
591
|
error,
|
|
506
592
|
);
|
|
@@ -535,7 +621,7 @@ export const getAppTransactionIOS: QueryField<
|
|
|
535
621
|
|
|
536
622
|
return null;
|
|
537
623
|
} catch (error) {
|
|
538
|
-
|
|
624
|
+
RnIapConsole.error('Failed to get app transaction:', error);
|
|
539
625
|
throw error;
|
|
540
626
|
}
|
|
541
627
|
};
|
|
@@ -554,7 +640,7 @@ export const subscriptionStatusIOS: QueryField<
|
|
|
554
640
|
.filter((status): status is NitroSubscriptionStatus => status != null)
|
|
555
641
|
.map(convertNitroSubscriptionStatusToSubscriptionStatusIOS);
|
|
556
642
|
} catch (error) {
|
|
557
|
-
|
|
643
|
+
RnIapConsole.error('[subscriptionStatusIOS] Failed:', error);
|
|
558
644
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
559
645
|
throw new Error(errorJson.message);
|
|
560
646
|
}
|
|
@@ -575,7 +661,7 @@ export const currentEntitlementIOS: QueryField<
|
|
|
575
661
|
}
|
|
576
662
|
return null;
|
|
577
663
|
} catch (error) {
|
|
578
|
-
|
|
664
|
+
RnIapConsole.error('[currentEntitlementIOS] Failed:', error);
|
|
579
665
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
580
666
|
throw new Error(errorJson.message);
|
|
581
667
|
}
|
|
@@ -596,7 +682,7 @@ export const latestTransactionIOS: QueryField<'latestTransactionIOS'> = async (
|
|
|
596
682
|
}
|
|
597
683
|
return null;
|
|
598
684
|
} catch (error) {
|
|
599
|
-
|
|
685
|
+
RnIapConsole.error('[latestTransactionIOS] Failed:', error);
|
|
600
686
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
601
687
|
throw new Error(errorJson.message);
|
|
602
688
|
}
|
|
@@ -617,7 +703,7 @@ export const getPendingTransactionsIOS: QueryField<
|
|
|
617
703
|
(purchase): purchase is PurchaseIOS => purchase.platform === 'ios',
|
|
618
704
|
);
|
|
619
705
|
} catch (error) {
|
|
620
|
-
|
|
706
|
+
RnIapConsole.error('[getPendingTransactionsIOS] Failed:', error);
|
|
621
707
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
622
708
|
throw new Error(errorJson.message);
|
|
623
709
|
}
|
|
@@ -638,7 +724,7 @@ export const showManageSubscriptionsIOS: MutationField<
|
|
|
638
724
|
(purchase): purchase is PurchaseIOS => purchase.platform === 'ios',
|
|
639
725
|
);
|
|
640
726
|
} catch (error) {
|
|
641
|
-
|
|
727
|
+
RnIapConsole.error('[showManageSubscriptionsIOS] Failed:', error);
|
|
642
728
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
643
729
|
throw new Error(errorJson.message);
|
|
644
730
|
}
|
|
@@ -654,7 +740,7 @@ export const isEligibleForIntroOfferIOS: QueryField<
|
|
|
654
740
|
try {
|
|
655
741
|
return await IAP.instance.isEligibleForIntroOfferIOS(groupID);
|
|
656
742
|
} catch (error) {
|
|
657
|
-
|
|
743
|
+
RnIapConsole.error('[isEligibleForIntroOfferIOS] Failed:', error);
|
|
658
744
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
659
745
|
throw new Error(errorJson.message);
|
|
660
746
|
}
|
|
@@ -668,7 +754,7 @@ export const getReceiptDataIOS: QueryField<'getReceiptDataIOS'> = async () => {
|
|
|
668
754
|
try {
|
|
669
755
|
return await IAP.instance.getReceiptDataIOS();
|
|
670
756
|
} catch (error) {
|
|
671
|
-
|
|
757
|
+
RnIapConsole.error('[getReceiptDataIOS] Failed:', error);
|
|
672
758
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
673
759
|
throw new Error(errorJson.message);
|
|
674
760
|
}
|
|
@@ -685,7 +771,7 @@ export const getReceiptIOS = async (): Promise<string> => {
|
|
|
685
771
|
}
|
|
686
772
|
return await IAP.instance.getReceiptDataIOS();
|
|
687
773
|
} catch (error) {
|
|
688
|
-
|
|
774
|
+
RnIapConsole.error('[getReceiptIOS] Failed:', error);
|
|
689
775
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
690
776
|
throw new Error(errorJson.message);
|
|
691
777
|
}
|
|
@@ -702,7 +788,7 @@ export const requestReceiptRefreshIOS = async (): Promise<string> => {
|
|
|
702
788
|
}
|
|
703
789
|
return await IAP.instance.getReceiptDataIOS();
|
|
704
790
|
} catch (error) {
|
|
705
|
-
|
|
791
|
+
RnIapConsole.error('[requestReceiptRefreshIOS] Failed:', error);
|
|
706
792
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
707
793
|
throw new Error(errorJson.message);
|
|
708
794
|
}
|
|
@@ -718,7 +804,7 @@ export const isTransactionVerifiedIOS: QueryField<
|
|
|
718
804
|
try {
|
|
719
805
|
return await IAP.instance.isTransactionVerifiedIOS(sku);
|
|
720
806
|
} catch (error) {
|
|
721
|
-
|
|
807
|
+
RnIapConsole.error('[isTransactionVerifiedIOS] Failed:', error);
|
|
722
808
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
723
809
|
throw new Error(errorJson.message);
|
|
724
810
|
}
|
|
@@ -734,7 +820,7 @@ export const getTransactionJwsIOS: QueryField<'getTransactionJwsIOS'> = async (
|
|
|
734
820
|
try {
|
|
735
821
|
return await IAP.instance.getTransactionJwsIOS(sku);
|
|
736
822
|
} catch (error) {
|
|
737
|
-
|
|
823
|
+
RnIapConsole.error('[getTransactionJwsIOS] Failed:', error);
|
|
738
824
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
739
825
|
throw new Error(errorJson.message);
|
|
740
826
|
}
|
|
@@ -751,7 +837,7 @@ export const initConnection: MutationField<'initConnection'> = async () => {
|
|
|
751
837
|
try {
|
|
752
838
|
return await IAP.instance.initConnection();
|
|
753
839
|
} catch (error) {
|
|
754
|
-
|
|
840
|
+
RnIapConsole.error('Failed to initialize IAP connection:', error);
|
|
755
841
|
throw error;
|
|
756
842
|
}
|
|
757
843
|
};
|
|
@@ -764,7 +850,7 @@ export const endConnection: MutationField<'endConnection'> = async () => {
|
|
|
764
850
|
if (!iapRef) return true;
|
|
765
851
|
return await IAP.instance.endConnection();
|
|
766
852
|
} catch (error) {
|
|
767
|
-
|
|
853
|
+
RnIapConsole.error('Failed to end IAP connection:', error);
|
|
768
854
|
throw error;
|
|
769
855
|
}
|
|
770
856
|
};
|
|
@@ -780,7 +866,7 @@ export const restorePurchases: MutationField<'restorePurchases'> = async () => {
|
|
|
780
866
|
onlyIncludeActiveItemsIOS: true,
|
|
781
867
|
});
|
|
782
868
|
} catch (error) {
|
|
783
|
-
|
|
869
|
+
RnIapConsole.error('Failed to restore purchases:', error);
|
|
784
870
|
throw error;
|
|
785
871
|
}
|
|
786
872
|
};
|
|
@@ -908,7 +994,7 @@ export const requestPurchase: MutationField<'requestPurchase'> = async (
|
|
|
908
994
|
|
|
909
995
|
return await IAP.instance.requestPurchase(unifiedRequest);
|
|
910
996
|
} catch (error) {
|
|
911
|
-
|
|
997
|
+
RnIapConsole.error('Failed to request purchase:', error);
|
|
912
998
|
throw error;
|
|
913
999
|
}
|
|
914
1000
|
};
|
|
@@ -980,7 +1066,7 @@ export const finishTransaction: MutationField<'finishTransaction'> = async (
|
|
|
980
1066
|
return;
|
|
981
1067
|
}
|
|
982
1068
|
}
|
|
983
|
-
|
|
1069
|
+
RnIapConsole.error('Failed to finish transaction:', error);
|
|
984
1070
|
throw error;
|
|
985
1071
|
}
|
|
986
1072
|
};
|
|
@@ -1013,7 +1099,7 @@ export const acknowledgePurchaseAndroid: MutationField<
|
|
|
1013
1099
|
});
|
|
1014
1100
|
return getSuccessFromPurchaseVariant(result, 'acknowledgePurchaseAndroid');
|
|
1015
1101
|
} catch (error) {
|
|
1016
|
-
|
|
1102
|
+
RnIapConsole.error('Failed to acknowledge purchase Android:', error);
|
|
1017
1103
|
throw error;
|
|
1018
1104
|
}
|
|
1019
1105
|
};
|
|
@@ -1044,7 +1130,7 @@ export const consumePurchaseAndroid: MutationField<
|
|
|
1044
1130
|
});
|
|
1045
1131
|
return getSuccessFromPurchaseVariant(result, 'consumePurchaseAndroid');
|
|
1046
1132
|
} catch (error) {
|
|
1047
|
-
|
|
1133
|
+
RnIapConsole.error('Failed to consume purchase Android:', error);
|
|
1048
1134
|
throw error;
|
|
1049
1135
|
}
|
|
1050
1136
|
};
|
|
@@ -1120,7 +1206,7 @@ export const validateReceipt: MutationField<'validateReceipt'> = async (
|
|
|
1120
1206
|
return result;
|
|
1121
1207
|
}
|
|
1122
1208
|
} catch (error) {
|
|
1123
|
-
|
|
1209
|
+
RnIapConsole.error('[validateReceipt] Failed:', error);
|
|
1124
1210
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1125
1211
|
throw new Error(errorJson.message);
|
|
1126
1212
|
}
|
|
@@ -1140,7 +1226,7 @@ export const syncIOS: MutationField<'syncIOS'> = async () => {
|
|
|
1140
1226
|
const result = await IAP.instance.syncIOS();
|
|
1141
1227
|
return Boolean(result);
|
|
1142
1228
|
} catch (error) {
|
|
1143
|
-
|
|
1229
|
+
RnIapConsole.error('[syncIOS] Failed:', error);
|
|
1144
1230
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1145
1231
|
throw new Error(errorJson.message);
|
|
1146
1232
|
}
|
|
@@ -1162,7 +1248,7 @@ export const presentCodeRedemptionSheetIOS: MutationField<
|
|
|
1162
1248
|
const result = await IAP.instance.presentCodeRedemptionSheetIOS();
|
|
1163
1249
|
return Boolean(result);
|
|
1164
1250
|
} catch (error) {
|
|
1165
|
-
|
|
1251
|
+
RnIapConsole.error('[presentCodeRedemptionSheetIOS] Failed:', error);
|
|
1166
1252
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1167
1253
|
throw new Error(errorJson.message);
|
|
1168
1254
|
}
|
|
@@ -1197,7 +1283,7 @@ export const requestPurchaseOnPromotedProductIOS: MutationField<
|
|
|
1197
1283
|
|
|
1198
1284
|
return true;
|
|
1199
1285
|
} catch (error) {
|
|
1200
|
-
|
|
1286
|
+
RnIapConsole.error('[requestPurchaseOnPromotedProductIOS] Failed:', error);
|
|
1201
1287
|
throw error;
|
|
1202
1288
|
}
|
|
1203
1289
|
};
|
|
@@ -1218,7 +1304,7 @@ export const clearTransactionIOS: MutationField<
|
|
|
1218
1304
|
await IAP.instance.clearTransactionIOS();
|
|
1219
1305
|
return true;
|
|
1220
1306
|
} catch (error) {
|
|
1221
|
-
|
|
1307
|
+
RnIapConsole.error('[clearTransactionIOS] Failed:', error);
|
|
1222
1308
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1223
1309
|
throw new Error(errorJson.message);
|
|
1224
1310
|
}
|
|
@@ -1241,7 +1327,7 @@ export const beginRefundRequestIOS: MutationField<
|
|
|
1241
1327
|
const status = await IAP.instance.beginRefundRequestIOS(sku);
|
|
1242
1328
|
return status ?? null;
|
|
1243
1329
|
} catch (error) {
|
|
1244
|
-
|
|
1330
|
+
RnIapConsole.error('[beginRefundRequestIOS] Failed:', error);
|
|
1245
1331
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1246
1332
|
throw new Error(errorJson.message);
|
|
1247
1333
|
}
|
|
@@ -1334,7 +1420,7 @@ export const deepLinkToSubscriptions: MutationField<
|
|
|
1334
1420
|
await IAP.instance.showManageSubscriptionsIOS();
|
|
1335
1421
|
}
|
|
1336
1422
|
} catch (error) {
|
|
1337
|
-
|
|
1423
|
+
RnIapConsole.warn('[deepLinkToSubscriptions] Failed on iOS:', error);
|
|
1338
1424
|
}
|
|
1339
1425
|
}
|
|
1340
1426
|
};
|
|
@@ -1351,7 +1437,7 @@ export const deepLinkToSubscriptionsIOS = async (): Promise<boolean> => {
|
|
|
1351
1437
|
await IAP.instance.showManageSubscriptionsIOS();
|
|
1352
1438
|
return true;
|
|
1353
1439
|
} catch (error) {
|
|
1354
|
-
|
|
1440
|
+
RnIapConsole.error('[deepLinkToSubscriptionsIOS] Failed:', error);
|
|
1355
1441
|
const errorJson = parseErrorStringToJsonObj(error);
|
|
1356
1442
|
throw new Error(errorJson.message);
|
|
1357
1443
|
}
|
|
@@ -1376,11 +1462,102 @@ export const deepLinkToSubscriptionsIOS = async (): Promise<boolean> => {
|
|
|
1376
1462
|
* }
|
|
1377
1463
|
* ```
|
|
1378
1464
|
*/
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* Get active subscriptions
|
|
1467
|
+
* @param subscriptionIds - Optional array of subscription IDs to filter by
|
|
1468
|
+
* @returns Promise<ActiveSubscription[]> - Array of active subscriptions
|
|
1469
|
+
*/
|
|
1470
|
+
export const getActiveSubscriptions: QueryField<
|
|
1471
|
+
'getActiveSubscriptions'
|
|
1472
|
+
> = async (subscriptionIds) => {
|
|
1473
|
+
try {
|
|
1474
|
+
// Get all available purchases first
|
|
1475
|
+
const allPurchases = await getAvailablePurchases();
|
|
1476
|
+
|
|
1477
|
+
// For the critical bug fix: this function was previously returning ALL purchases
|
|
1478
|
+
// Now we properly filter for subscriptions only
|
|
1479
|
+
|
|
1480
|
+
// In production with real data, Android subscription filtering is done via platform-specific calls
|
|
1481
|
+
// But for backward compatibility and test support, we also check platform-specific fields
|
|
1482
|
+
|
|
1483
|
+
// Since expirationDateIOS and subscriptionGroupIdIOS are not available in NitroPurchase,
|
|
1484
|
+
// we need to rely on other indicators or assume all purchases are subscriptions
|
|
1485
|
+
// when called from getActiveSubscriptions
|
|
1486
|
+
const purchases = allPurchases;
|
|
1487
|
+
|
|
1488
|
+
// Filter for subscriptions and map to ActiveSubscription format
|
|
1489
|
+
const subscriptions = purchases
|
|
1490
|
+
.filter((purchase) => {
|
|
1491
|
+
// Filter by subscription IDs if provided
|
|
1492
|
+
if (subscriptionIds && subscriptionIds.length > 0) {
|
|
1493
|
+
return subscriptionIds.includes(purchase.productId);
|
|
1494
|
+
}
|
|
1495
|
+
return true;
|
|
1496
|
+
})
|
|
1497
|
+
.map((purchase): ActiveSubscription => {
|
|
1498
|
+
// Safe access to platform-specific fields with type guards
|
|
1499
|
+
const expirationDateIOS =
|
|
1500
|
+
'expirationDateIOS' in purchase
|
|
1501
|
+
? ((purchase as PurchaseIOS).expirationDateIOS ?? null)
|
|
1502
|
+
: null;
|
|
1503
|
+
|
|
1504
|
+
const environmentIOS =
|
|
1505
|
+
'environmentIOS' in purchase
|
|
1506
|
+
? ((purchase as PurchaseIOS).environmentIOS ?? null)
|
|
1507
|
+
: null;
|
|
1508
|
+
|
|
1509
|
+
const autoRenewingAndroid =
|
|
1510
|
+
'autoRenewingAndroid' in purchase || 'isAutoRenewing' in purchase
|
|
1511
|
+
? ((purchase as PurchaseAndroid).autoRenewingAndroid ??
|
|
1512
|
+
(purchase as PurchaseAndroid).isAutoRenewing) // deprecated - use isAutoRenewing instead
|
|
1513
|
+
: null;
|
|
1514
|
+
|
|
1515
|
+
return {
|
|
1516
|
+
productId: purchase.productId,
|
|
1517
|
+
isActive: true, // If it's in availablePurchases, it's active
|
|
1518
|
+
// Backend validation fields - use transactionId ?? id for proper field mapping
|
|
1519
|
+
transactionId: purchase.transactionId ?? purchase.id,
|
|
1520
|
+
purchaseToken: purchase.purchaseToken,
|
|
1521
|
+
transactionDate: purchase.transactionDate,
|
|
1522
|
+
// Platform-specific fields
|
|
1523
|
+
expirationDateIOS,
|
|
1524
|
+
autoRenewingAndroid,
|
|
1525
|
+
environmentIOS,
|
|
1526
|
+
// Convenience fields
|
|
1527
|
+
willExpireSoon: false, // Would need to calculate based on expiration date
|
|
1528
|
+
daysUntilExpirationIOS:
|
|
1529
|
+
expirationDateIOS != null
|
|
1530
|
+
? Math.ceil(
|
|
1531
|
+
(expirationDateIOS - Date.now()) / (1000 * 60 * 60 * 24),
|
|
1532
|
+
)
|
|
1533
|
+
: null,
|
|
1534
|
+
};
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
return subscriptions;
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
RnIapConsole.error('Failed to get active subscriptions:', error);
|
|
1540
|
+
const errorJson = parseErrorStringToJsonObj(error);
|
|
1541
|
+
throw new Error(errorJson.message);
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* Check if there are any active subscriptions
|
|
1547
|
+
* @param subscriptionIds - Optional array of subscription IDs to check
|
|
1548
|
+
* @returns Promise<boolean> - True if there are active subscriptions
|
|
1549
|
+
*/
|
|
1550
|
+
export const hasActiveSubscriptions: QueryField<
|
|
1551
|
+
'hasActiveSubscriptions'
|
|
1552
|
+
> = async (subscriptionIds) => {
|
|
1553
|
+
try {
|
|
1554
|
+
const activeSubscriptions = await getActiveSubscriptions(subscriptionIds);
|
|
1555
|
+
return activeSubscriptions.length > 0;
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
RnIapConsole.error('Failed to check active subscriptions:', error);
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1384
1561
|
|
|
1385
1562
|
// Type conversion utilities
|
|
1386
1563
|
export {
|
|
@@ -1436,7 +1613,7 @@ const toNitroProductType = (
|
|
|
1436
1613
|
return 'all';
|
|
1437
1614
|
}
|
|
1438
1615
|
if (type === 'inapp') {
|
|
1439
|
-
|
|
1616
|
+
RnIapConsole.warn(LEGACY_INAPP_WARNING);
|
|
1440
1617
|
return 'inapp';
|
|
1441
1618
|
}
|
|
1442
1619
|
return 'inapp';
|
|
@@ -1462,7 +1639,7 @@ const normalizeProductQueryType = (
|
|
|
1462
1639
|
return 'subs';
|
|
1463
1640
|
}
|
|
1464
1641
|
if (normalized === 'inapp') {
|
|
1465
|
-
|
|
1642
|
+
RnIapConsole.warn(LEGACY_INAPP_WARNING);
|
|
1466
1643
|
return 'in-app';
|
|
1467
1644
|
}
|
|
1468
1645
|
if (normalized === 'in-app') {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug logger for React Native IAP
|
|
3
|
+
* Only logs when explicitly enabled for library development
|
|
4
|
+
* Silent for all library users (even in their dev mode)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Check if we're in library development mode
|
|
8
|
+
// This will be false for library users, even in their dev environment
|
|
9
|
+
const isLibraryDevelopment = () => {
|
|
10
|
+
// Only show logs if explicitly enabled via environment variable
|
|
11
|
+
// Library developers can set: RN_IAP_DEV_MODE=true
|
|
12
|
+
return (
|
|
13
|
+
process.env.RN_IAP_DEV_MODE === 'true' ||
|
|
14
|
+
(global as any).RN_IAP_DEV_MODE === true
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const RnIapConsole = {
|
|
19
|
+
log: (...args: any[]) => {
|
|
20
|
+
if (isLibraryDevelopment()) {
|
|
21
|
+
console.log('[RN-IAP]', ...args);
|
|
22
|
+
}
|
|
23
|
+
// Silent for library users
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
debug: (...args: any[]) => {
|
|
27
|
+
if (isLibraryDevelopment()) {
|
|
28
|
+
console.debug('[RN-IAP Debug]', ...args);
|
|
29
|
+
}
|
|
30
|
+
// Silent for library users
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
warn: (...args: any[]) => {
|
|
34
|
+
// Warnings are always shown
|
|
35
|
+
console.warn('[RN-IAP]', ...args);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
error: (...args: any[]) => {
|
|
39
|
+
// Errors are always shown
|
|
40
|
+
console.error('[RN-IAP]', ...args);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
info: (...args: any[]) => {
|
|
44
|
+
if (isLibraryDevelopment()) {
|
|
45
|
+
console.info('[RN-IAP]', ...args);
|
|
46
|
+
}
|
|
47
|
+
// Silent for library users
|
|
48
|
+
},
|
|
49
|
+
};
|