react-native-iap 15.1.0 → 15.2.0
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/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +54 -0
- package/ios/HybridRnIap.swift +39 -1
- package/lib/module/hooks/useIAP.js +11 -1
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +74 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +10 -0
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +17 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +16 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +8 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +18 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +12 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +32 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +2 -0
- package/openiap-versions.json +2 -2
- package/package.json +1 -1
- package/src/hooks/useIAP.ts +23 -0
- package/src/index.ts +92 -0
- package/src/specs/RnIap.nitro.ts +23 -0
- package/src/types.ts +16 -1
package/src/hooks/useIAP.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
showAlternativeBillingDialogAndroid,
|
|
26
26
|
createAlternativeBillingTokenAndroid,
|
|
27
27
|
userChoiceBillingListenerAndroid,
|
|
28
|
+
subscriptionBillingIssueListener,
|
|
28
29
|
isStandardIOS,
|
|
29
30
|
} from '../';
|
|
30
31
|
|
|
@@ -112,6 +113,16 @@ export interface UseIapOptions {
|
|
|
112
113
|
onError?: (error: Error) => void;
|
|
113
114
|
onPromotedProductIOS?: (product: Product) => void;
|
|
114
115
|
onUserChoiceBillingAndroid?: (details: UserChoiceBillingDetails) => void;
|
|
116
|
+
/**
|
|
117
|
+
* Fires when an active subscription enters a billing-issue state
|
|
118
|
+
* (StoreKit 2 Message.billingIssue on iOS 18+, Purchase.isSuspended on
|
|
119
|
+
* Play Billing 8.1+). Not invoked on Meta Horizon.
|
|
120
|
+
*
|
|
121
|
+
* Recommended: call deepLinkToSubscriptions on the returned purchase so
|
|
122
|
+
* the user can update their payment method in the platform subscription
|
|
123
|
+
* center.
|
|
124
|
+
*/
|
|
125
|
+
onSubscriptionBillingIssue?: (purchase: Purchase) => void;
|
|
115
126
|
/**
|
|
116
127
|
* @deprecated Use enableBillingProgramAndroid instead.
|
|
117
128
|
* - 'user-choice' → 'user-choice-billing'
|
|
@@ -178,6 +189,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
178
189
|
purchaseError?: EventSubscription;
|
|
179
190
|
promotedProductIOS?: EventSubscription;
|
|
180
191
|
userChoiceBillingAndroid?: EventSubscription;
|
|
192
|
+
subscriptionBillingIssue?: EventSubscription;
|
|
181
193
|
}>({});
|
|
182
194
|
|
|
183
195
|
// Track if component is mounted to prevent listener leaks on early unmount
|
|
@@ -463,6 +475,15 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
463
475
|
}
|
|
464
476
|
});
|
|
465
477
|
}
|
|
478
|
+
|
|
479
|
+
// Always attach so callers that supply `onSubscriptionBillingIssue` later
|
|
480
|
+
// (after the hook has already set up listeners) still receive events.
|
|
481
|
+
if (!subscriptionsRef.current.subscriptionBillingIssue) {
|
|
482
|
+
subscriptionsRef.current.subscriptionBillingIssue =
|
|
483
|
+
subscriptionBillingIssueListener((purchase: Purchase) => {
|
|
484
|
+
optionsRef.current?.onSubscriptionBillingIssue?.(purchase);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
466
487
|
}, [getActiveSubscriptionsInternal, getAvailablePurchasesInternal]);
|
|
467
488
|
|
|
468
489
|
// Shared helper: clean up all listeners
|
|
@@ -471,10 +492,12 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
471
492
|
subscriptionsRef.current.purchaseError?.remove();
|
|
472
493
|
subscriptionsRef.current.promotedProductIOS?.remove();
|
|
473
494
|
subscriptionsRef.current.userChoiceBillingAndroid?.remove();
|
|
495
|
+
subscriptionsRef.current.subscriptionBillingIssue?.remove();
|
|
474
496
|
subscriptionsRef.current.purchaseUpdate = undefined;
|
|
475
497
|
subscriptionsRef.current.purchaseError = undefined;
|
|
476
498
|
subscriptionsRef.current.promotedProductIOS = undefined;
|
|
477
499
|
subscriptionsRef.current.userChoiceBillingAndroid = undefined;
|
|
500
|
+
subscriptionsRef.current.subscriptionBillingIssue = undefined;
|
|
478
501
|
}, []);
|
|
479
502
|
|
|
480
503
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
package/src/index.ts
CHANGED
|
@@ -279,12 +279,14 @@ export const resetListenerState = (): void => {
|
|
|
279
279
|
promotedProductNativeAttached = false;
|
|
280
280
|
userChoiceBillingNativeAttached = false;
|
|
281
281
|
developerProvidedBillingNativeAttached = false;
|
|
282
|
+
subscriptionBillingIssueNativeAttached = false;
|
|
282
283
|
// Clear all JS listeners since native side clears them in endConnection
|
|
283
284
|
purchaseUpdateJsListeners.clear();
|
|
284
285
|
purchaseErrorJsListeners.clear();
|
|
285
286
|
promotedProductJsListeners.clear();
|
|
286
287
|
userChoiceBillingJsListeners.clear();
|
|
287
288
|
developerProvidedBillingJsListeners.clear();
|
|
289
|
+
subscriptionBillingIssueJsListeners.clear();
|
|
288
290
|
};
|
|
289
291
|
|
|
290
292
|
export const purchaseUpdatedListener = (
|
|
@@ -565,6 +567,96 @@ export const developerProvidedBillingListenerAndroid = (
|
|
|
565
567
|
};
|
|
566
568
|
};
|
|
567
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Listen for subscription billing-issue events (cross-platform).
|
|
572
|
+
*
|
|
573
|
+
* Fires when an active subscription enters a billing-issue state:
|
|
574
|
+
* - iOS 18+ / Mac Catalyst 18+: via StoreKit 2 `Message.Reason.billingIssue`.
|
|
575
|
+
* - Android (Play Billing 8.1+): when `isSuspendedAndroid === true` is observed.
|
|
576
|
+
* - Horizon / iOS 17 / older platforms: never fires.
|
|
577
|
+
*
|
|
578
|
+
* Recommended UX: on fire, call `deepLinkToSubscriptions()` so the user can
|
|
579
|
+
* update their payment method in the platform subscription center.
|
|
580
|
+
*
|
|
581
|
+
* @param listener - Function to call with the affected Purchase
|
|
582
|
+
* @returns EventSubscription with remove() method to unsubscribe
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```typescript
|
|
586
|
+
* const subscription = subscriptionBillingIssueListener((purchase) => {
|
|
587
|
+
* console.warn('Subscription needs attention:', purchase.productId);
|
|
588
|
+
* deepLinkToSubscriptions({skuAndroid: purchase.productId, packageNameAndroid: 'com.example.app'});
|
|
589
|
+
* });
|
|
590
|
+
*
|
|
591
|
+
* subscription.remove();
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
type NitroSubscriptionBillingIssueListener = Parameters<
|
|
595
|
+
RnIap['addSubscriptionBillingIssueListener']
|
|
596
|
+
>[0];
|
|
597
|
+
|
|
598
|
+
const subscriptionBillingIssueJsListeners = new Set<(purchase: Purchase) => void>();
|
|
599
|
+
let subscriptionBillingIssueNativeAttached = false;
|
|
600
|
+
const subscriptionBillingIssueNativeHandler: NitroSubscriptionBillingIssueListener =
|
|
601
|
+
(nitroPurchase) => {
|
|
602
|
+
if (!validateNitroPurchase(nitroPurchase)) {
|
|
603
|
+
RnIapConsole.warn(
|
|
604
|
+
'[subscriptionBillingIssueListener] dropped malformed native payload',
|
|
605
|
+
);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const purchase = convertNitroPurchaseToPurchase(nitroPurchase);
|
|
609
|
+
for (const listener of subscriptionBillingIssueJsListeners) {
|
|
610
|
+
try {
|
|
611
|
+
listener(purchase);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
RnIapConsole.error(
|
|
614
|
+
'[subscriptionBillingIssueListener] callback threw:',
|
|
615
|
+
e,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
function tryAttachSubscriptionBillingIssueNative(): void {
|
|
622
|
+
if (subscriptionBillingIssueNativeAttached) return;
|
|
623
|
+
try {
|
|
624
|
+
IAP.instance.addSubscriptionBillingIssueListener(
|
|
625
|
+
subscriptionBillingIssueNativeHandler,
|
|
626
|
+
);
|
|
627
|
+
subscriptionBillingIssueNativeAttached = true;
|
|
628
|
+
} catch (e) {
|
|
629
|
+
const msg = toErrorMessage(e);
|
|
630
|
+
if (msg.includes('Nitro runtime not installed')) {
|
|
631
|
+
RnIapConsole.warn(
|
|
632
|
+
'[subscriptionBillingIssueListener] Nitro not ready yet; will retry on next registration after initConnection()',
|
|
633
|
+
);
|
|
634
|
+
} else {
|
|
635
|
+
throw e;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export const subscriptionBillingIssueListener = (
|
|
641
|
+
listener: (purchase: Purchase) => void,
|
|
642
|
+
): EventSubscription => {
|
|
643
|
+
subscriptionBillingIssueJsListeners.add(listener);
|
|
644
|
+
// Retry attachment every call so a listener registered before initConnection()
|
|
645
|
+
// doesn't stay permanently inert once Nitro is ready.
|
|
646
|
+
try {
|
|
647
|
+
tryAttachSubscriptionBillingIssueNative();
|
|
648
|
+
} catch (error) {
|
|
649
|
+
subscriptionBillingIssueJsListeners.delete(listener);
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return {
|
|
654
|
+
remove: () => {
|
|
655
|
+
subscriptionBillingIssueJsListeners.delete(listener);
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
};
|
|
659
|
+
|
|
568
660
|
// ------------------------------
|
|
569
661
|
// Query API
|
|
570
662
|
// ------------------------------
|
package/src/specs/RnIap.nitro.ts
CHANGED
|
@@ -1095,6 +1095,29 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
|
|
|
1095
1095
|
listener: (details: DeveloperProvidedBillingDetailsAndroid) => void,
|
|
1096
1096
|
): void;
|
|
1097
1097
|
|
|
1098
|
+
/**
|
|
1099
|
+
* Add a listener for subscription billing-issue events (cross-platform).
|
|
1100
|
+
*
|
|
1101
|
+
* Fires when a user's active subscription enters a state that needs attention
|
|
1102
|
+
* (payment method failed, card expired, etc.). Unifies:
|
|
1103
|
+
* - StoreKit 2 `Message.Reason.billingIssue` (iOS 18+ / Mac Catalyst 18+)
|
|
1104
|
+
* - Google Play Billing `Purchase.isSuspended` (Play Billing 8.1+)
|
|
1105
|
+
*
|
|
1106
|
+
* NOT fired on Meta Horizon (Billing 7.0 compat SDK lacks the suspended signal).
|
|
1107
|
+
*
|
|
1108
|
+
* @param listener - Called with the affected Purchase
|
|
1109
|
+
*/
|
|
1110
|
+
addSubscriptionBillingIssueListener(
|
|
1111
|
+
listener: (purchase: NitroPurchase) => void,
|
|
1112
|
+
): void;
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Remove a subscription billing-issue listener.
|
|
1116
|
+
*/
|
|
1117
|
+
removeSubscriptionBillingIssueListener(
|
|
1118
|
+
listener: (purchase: NitroPurchase) => void,
|
|
1119
|
+
): void;
|
|
1120
|
+
|
|
1098
1121
|
// ╔════════════════════════════════════════════════════════════════════════╗
|
|
1099
1122
|
// ║ BILLING PROGRAMS API (Android 8.2.0+) ║
|
|
1100
1123
|
// ╚════════════════════════════════════════════════════════════════════════╝
|
package/src/types.ts
CHANGED
|
@@ -457,7 +457,7 @@ export interface ExternalPurchaseNoticeResultIOS {
|
|
|
457
457
|
|
|
458
458
|
export type FetchProductsResult = ProductOrSubscription[] | Product[] | ProductSubscription[] | null;
|
|
459
459
|
|
|
460
|
-
export type IapEvent = 'purchase-updated' | 'purchase-error' | 'promoted-product-ios' | 'user-choice-billing-android' | 'developer-provided-billing-android';
|
|
460
|
+
export type IapEvent = 'purchase-updated' | 'purchase-error' | 'promoted-product-ios' | 'user-choice-billing-android' | 'developer-provided-billing-android' | 'subscription-billing-issue';
|
|
461
461
|
|
|
462
462
|
export type IapPlatform = 'ios' | 'android';
|
|
463
463
|
|
|
@@ -1560,6 +1560,20 @@ export interface Subscription {
|
|
|
1560
1560
|
purchaseError: PurchaseError;
|
|
1561
1561
|
/** Fires when a purchase completes successfully or a pending purchase resolves */
|
|
1562
1562
|
purchaseUpdated: Purchase;
|
|
1563
|
+
/**
|
|
1564
|
+
* Fires when an active subscription enters a billing-issue state that needs user action
|
|
1565
|
+
* (payment method failed, card expired, etc.). Cross-platform unification:
|
|
1566
|
+
*
|
|
1567
|
+
* - iOS 18+: delivered via StoreKit 2 `Message.Reason.billingIssue`.
|
|
1568
|
+
* - Android (Play flavor, Billing 8.1+): emitted when `isSuspended == true` is first detected
|
|
1569
|
+
* on a previously healthy subscription. Requires Google Play Billing Library 8.1.0 or newer.
|
|
1570
|
+
* - Android (Horizon flavor): NOT emitted. The Horizon Billing Compatibility SDK implements
|
|
1571
|
+
* the Play Billing 7.0 API surface which does not expose a suspended-subscription signal.
|
|
1572
|
+
*
|
|
1573
|
+
* Listeners should not assume the event will fire on every store. Direct users to the
|
|
1574
|
+
* platform subscription management UI (`deepLinkToSubscriptions`) to resolve the issue.
|
|
1575
|
+
*/
|
|
1576
|
+
subscriptionBillingIssue: Purchase;
|
|
1563
1577
|
/**
|
|
1564
1578
|
* Fires when a user selects alternative billing in the User Choice Billing dialog (Android only)
|
|
1565
1579
|
* Only triggered when the user selects alternative billing instead of Google Play billing
|
|
@@ -1965,6 +1979,7 @@ export type SubscriptionArgsMap = {
|
|
|
1965
1979
|
promotedProductIOS: never;
|
|
1966
1980
|
purchaseError: never;
|
|
1967
1981
|
purchaseUpdated: never;
|
|
1982
|
+
subscriptionBillingIssue: never;
|
|
1968
1983
|
userChoiceBillingAndroid: never;
|
|
1969
1984
|
};
|
|
1970
1985
|
|