react-native-iap 15.2.3 → 15.3.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.
Files changed (39) hide show
  1. package/README.md +31 -51
  2. package/android/build.gradle +92 -22
  3. package/android/gradle.properties +5 -1
  4. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +24 -8
  5. package/android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt +3 -1
  6. package/ios/HybridRnIap.swift +172 -73
  7. package/lib/module/hooks/useIAP.js +1 -1
  8. package/lib/module/hooks/useIAP.js.map +1 -1
  9. package/lib/module/index.js +138 -165
  10. package/lib/module/index.js.map +1 -1
  11. package/lib/module/types.js.map +1 -1
  12. package/lib/typescript/src/hooks/useIAP.d.ts +22 -16
  13. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  14. package/lib/typescript/src/index.d.ts +61 -90
  15. package/lib/typescript/src/index.d.ts.map +1 -1
  16. package/lib/typescript/src/specs/RnIap.nitro.d.ts +5 -16
  17. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  18. package/lib/typescript/src/types.d.ts +63 -49
  19. package/lib/typescript/src/types.d.ts.map +1 -1
  20. package/nitro.json +0 -1
  21. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +11 -6
  22. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +2 -2
  23. package/nitrogen/generated/android/c++/JPurchaseUpdatedListenerOptions.hpp +61 -0
  24. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +4 -9
  25. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseUpdatedListenerOptions.kt +38 -0
  26. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +27 -0
  27. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
  28. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +9 -4
  29. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +2 -2
  30. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +8 -12
  31. package/nitrogen/generated/ios/swift/PurchaseUpdatedListenerOptions.swift +61 -0
  32. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +5 -2
  33. package/nitrogen/generated/shared/c++/PurchaseUpdatedListenerOptions.hpp +85 -0
  34. package/openiap-versions.json +3 -3
  35. package/package.json +4 -6
  36. package/src/hooks/useIAP.ts +23 -15
  37. package/src/index.ts +198 -215
  38. package/src/specs/RnIap.nitro.ts +10 -18
  39. package/src/types.ts +66 -49
@@ -28,13 +28,14 @@ import { parseAppTransactionPayload } from "./utils.js";
28
28
 
29
29
  export * from "./types.js";
30
30
  export * from "./utils/error.js";
31
- const LEGACY_INAPP_WARNING = "[react-native-iap] `type: 'inapp'` is deprecated and will be removed in v14.4.0. Use 'in-app' instead.";
31
+ const LEGACY_INAPP_WARNING = "[react-native-iap] `type: 'inapp'` is deprecated and will be removed in a future major version. Use 'in-app' instead.";
32
32
  const toErrorMessage = error => {
33
33
  if (typeof error === 'object' && error !== null && 'message' in error && error.message != null) {
34
34
  return String(error.message);
35
35
  }
36
36
  return String(error ?? '');
37
37
  };
38
+ const unsupportedPlatformError = () => new Error(`Unsupported platform: ${Platform.OS}`);
38
39
  // ActiveSubscription and PurchaseError types are already exported via 'export * from ./types'
39
40
 
40
41
  // Export hooks
@@ -114,11 +115,15 @@ const IAP = {
114
115
  // ============================================================================
115
116
 
116
117
  const purchaseUpdateJsListeners = new Set();
118
+ const purchaseUpdateDuplicateJsListeners = new Set();
117
119
  let purchaseUpdateNativeAttached = false;
118
- const purchaseUpdateNativeHandler = nitroPurchase => {
120
+ let purchaseUpdateDuplicateNativeAttached = false;
121
+ let purchaseUpdateNativeToken = null;
122
+ let purchaseUpdateDuplicateNativeToken = null;
123
+ const emitPurchaseUpdateToListeners = (nitroPurchase, listeners) => {
119
124
  if (validateNitroPurchase(nitroPurchase)) {
120
125
  const convertedPurchase = convertNitroPurchaseToPurchase(nitroPurchase);
121
- for (const listener of purchaseUpdateJsListeners) {
126
+ for (const listener of listeners) {
122
127
  try {
123
128
  listener(convertedPurchase);
124
129
  } catch (e) {
@@ -129,6 +134,12 @@ const purchaseUpdateNativeHandler = nitroPurchase => {
129
134
  RnIapConsole.error('Invalid purchase data received from native — productId:', nitroPurchase?.productId ?? 'unknown');
130
135
  }
131
136
  };
137
+ const purchaseUpdateNativeHandler = nitroPurchase => {
138
+ emitPurchaseUpdateToListeners(nitroPurchase, purchaseUpdateJsListeners);
139
+ };
140
+ const purchaseUpdateDuplicateNativeHandler = nitroPurchase => {
141
+ emitPurchaseUpdateToListeners(nitroPurchase, purchaseUpdateDuplicateJsListeners);
142
+ };
132
143
  const purchaseErrorJsListeners = new Set();
133
144
  let purchaseErrorNativeAttached = false;
134
145
  const purchaseErrorNativeHandler = error => {
@@ -168,6 +179,9 @@ const promotedProductNativeHandler = nitroProduct => {
168
179
  */
169
180
  export const resetListenerState = () => {
170
181
  purchaseUpdateNativeAttached = false;
182
+ purchaseUpdateDuplicateNativeAttached = false;
183
+ purchaseUpdateNativeToken = null;
184
+ purchaseUpdateDuplicateNativeToken = null;
171
185
  purchaseErrorNativeAttached = false;
172
186
  promotedProductNativeAttached = false;
173
187
  userChoiceBillingNativeAttached = false;
@@ -175,17 +189,21 @@ export const resetListenerState = () => {
175
189
  subscriptionBillingIssueNativeAttached = false;
176
190
  // Clear all JS listeners since native side clears them in endConnection
177
191
  purchaseUpdateJsListeners.clear();
192
+ purchaseUpdateDuplicateJsListeners.clear();
178
193
  purchaseErrorJsListeners.clear();
179
194
  promotedProductJsListeners.clear();
180
195
  userChoiceBillingJsListeners.clear();
181
196
  developerProvidedBillingJsListeners.clear();
182
197
  subscriptionBillingIssueJsListeners.clear();
183
198
  };
184
- export const purchaseUpdatedListener = listener => {
185
- purchaseUpdateJsListeners.add(listener);
186
- if (!purchaseUpdateNativeAttached) {
199
+ export const purchaseUpdatedListener = (listener, options) => {
200
+ const receiveDuplicateTransactionUpdatesIOS = Platform.OS === 'ios' && options?.dedupeTransactionIOS === false;
201
+ const listeners = receiveDuplicateTransactionUpdatesIOS ? purchaseUpdateDuplicateJsListeners : purchaseUpdateJsListeners;
202
+ listeners.add(listener);
203
+ if (!purchaseUpdateNativeAttached && !receiveDuplicateTransactionUpdatesIOS) {
187
204
  try {
188
- IAP.instance.addPurchaseUpdatedListener(purchaseUpdateNativeHandler);
205
+ const token = IAP.instance.addPurchaseUpdatedListener(purchaseUpdateNativeHandler);
206
+ purchaseUpdateNativeToken = typeof token === 'number' ? token : null;
189
207
  purchaseUpdateNativeAttached = true;
190
208
  } catch (e) {
191
209
  const msg = toErrorMessage(e);
@@ -196,9 +214,50 @@ export const purchaseUpdatedListener = listener => {
196
214
  }
197
215
  }
198
216
  }
217
+ if (!purchaseUpdateDuplicateNativeAttached && receiveDuplicateTransactionUpdatesIOS) {
218
+ try {
219
+ const nativeOptions = {
220
+ dedupeTransactionIOS: false
221
+ };
222
+ const token = IAP.instance.addPurchaseUpdatedListener(purchaseUpdateDuplicateNativeHandler, nativeOptions);
223
+ purchaseUpdateDuplicateNativeToken = typeof token === 'number' ? token : null;
224
+ purchaseUpdateDuplicateNativeAttached = true;
225
+ } catch (e) {
226
+ const msg = toErrorMessage(e);
227
+ if (msg.includes('Nitro runtime not installed')) {
228
+ RnIapConsole.warn('[purchaseUpdatedListener] Nitro not ready yet; listener inert until initConnection()');
229
+ } else {
230
+ throw e;
231
+ }
232
+ }
233
+ }
234
+ let removed = false;
199
235
  return {
200
236
  remove: () => {
201
- purchaseUpdateJsListeners.delete(listener);
237
+ if (removed) {
238
+ return;
239
+ }
240
+ removed = true;
241
+ listeners.delete(listener);
242
+ if (listeners.size > 0) {
243
+ return;
244
+ }
245
+ const token = receiveDuplicateTransactionUpdatesIOS ? purchaseUpdateDuplicateNativeToken : purchaseUpdateNativeToken;
246
+ if (token == null) {
247
+ return;
248
+ }
249
+ try {
250
+ IAP.instance.removePurchaseUpdatedListener(token);
251
+ if (receiveDuplicateTransactionUpdatesIOS) {
252
+ purchaseUpdateDuplicateNativeToken = null;
253
+ purchaseUpdateDuplicateNativeAttached = false;
254
+ } else {
255
+ purchaseUpdateNativeToken = null;
256
+ purchaseUpdateNativeAttached = false;
257
+ }
258
+ } catch (e) {
259
+ RnIapConsole.warn('[purchaseUpdatedListener] native remove failed:', e);
260
+ }
202
261
  }
203
262
  };
204
263
  };
@@ -272,7 +331,7 @@ export const promotedProductListenerIOS = listener => {
272
331
  * const subscription = userChoiceBillingListenerAndroid((details) => {
273
332
  * console.log('User chose alternative billing');
274
333
  * console.log('Products:', details.products);
275
- * console.log('Token:', details.externalTransactionToken);
334
+ * console.log('External transaction token received; send it to your backend without logging it.');
276
335
  *
277
336
  * // Send token to backend for Google Play reporting
278
337
  * await reportToGooglePlay(details.externalTransactionToken);
@@ -339,7 +398,7 @@ export const userChoiceBillingListenerAndroid = listener => {
339
398
  * ```typescript
340
399
  * const subscription = developerProvidedBillingListenerAndroid((details) => {
341
400
  * console.log('User chose developer billing');
342
- * console.log('Token:', details.externalTransactionToken);
401
+ * console.log('External transaction token received; send it to your backend without logging it.');
343
402
  *
344
403
  * // Process payment through your external payment system
345
404
  * await processExternalPayment();
@@ -490,7 +549,7 @@ export const subscriptionBillingIssueListener = listener => {
490
549
  * @remarks This is a regular promise-based call. Don't confuse with `request*` APIs
491
550
  * (`requestPurchase`), which are event-based.
492
551
  *
493
- * @see {@link https://www.openiap.dev/docs/apis/fetch-products}
552
+ * @see {@link https://openiap.dev/docs/apis/fetch-products}
494
553
  */
495
554
  export const fetchProducts = async request => {
496
555
  const {
@@ -597,7 +656,7 @@ export const fetchProducts = async request => {
597
656
  * }
598
657
  * ```
599
658
  *
600
- * @see {@link https://www.openiap.dev/docs/apis/get-available-purchases}
659
+ * @see {@link https://openiap.dev/docs/apis/get-available-purchases}
601
660
  */
602
661
  export const getAvailablePurchases = async options => {
603
662
  const alsoPublishToEventListenerIOS = Boolean(options?.alsoPublishToEventListenerIOS ?? false);
@@ -642,7 +701,7 @@ export const getAvailablePurchases = async options => {
642
701
  }
643
702
  return validPurchases.map(convertNitroPurchaseToPurchase);
644
703
  } else {
645
- throw new Error('Unsupported platform');
704
+ throw unsupportedPlatformError();
646
705
  }
647
706
  } catch (error) {
648
707
  RnIapConsole.error('Failed to get available purchases:', error);
@@ -655,7 +714,7 @@ export const getAvailablePurchases = async options => {
655
714
  * @returns Promise<Product | null> - The promoted product or null if none available
656
715
  * @platform iOS
657
716
  *
658
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-promoted-product-ios}
717
+ * @see {@link https://openiap.dev/docs/apis/ios/get-promoted-product-ios}
659
718
  */
660
719
  export const getPromotedProductIOS = async () => {
661
720
  if (Platform.OS !== 'ios') {
@@ -692,7 +751,7 @@ export const requestPromotedProductIOS = getPromotedProductIOS;
692
751
  * console.log('User storefront:', storefront); // e.g., 'USA', 'GBR', 'KOR'
693
752
  * ```
694
753
  *
695
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-storefront-ios}
754
+ * @see {@link https://openiap.dev/docs/apis/ios/get-storefront-ios}
696
755
  */
697
756
  export const getStorefrontIOS = async () => {
698
757
  if (Platform.OS !== 'ios') {
@@ -710,7 +769,7 @@ export const getStorefrontIOS = async () => {
710
769
  /**
711
770
  * Return the user's storefront country code.
712
771
  *
713
- * @see {@link https://www.openiap.dev/docs/apis/get-storefront}
772
+ * @see {@link https://openiap.dev/docs/apis/get-storefront}
714
773
  */
715
774
  export const getStorefront = async () => {
716
775
  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
@@ -753,7 +812,7 @@ export const getStorefront = async () => {
753
812
  * }
754
813
  * ```
755
814
  *
756
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-app-transaction-ios}
815
+ * @see {@link https://openiap.dev/docs/apis/ios/get-app-transaction-ios}
757
816
  */
758
817
  export const getAppTransactionIOS = async () => {
759
818
  if (Platform.OS !== 'ios') {
@@ -788,7 +847,7 @@ export const getAppTransactionIOS = async () => {
788
847
  * @throws Error when called on non-iOS platforms or when IAP is not initialized
789
848
  * @platform iOS
790
849
  *
791
- * @see {@link https://www.openiap.dev/docs/apis/ios/subscription-status-ios}
850
+ * @see {@link https://openiap.dev/docs/apis/ios/subscription-status-ios}
792
851
  */
793
852
  export const subscriptionStatusIOS = async sku => {
794
853
  if (Platform.OS !== 'ios') {
@@ -816,7 +875,7 @@ export const subscriptionStatusIOS = async sku => {
816
875
  * @returns Promise<Purchase | null> - Current entitlement or null
817
876
  * @platform iOS
818
877
  *
819
- * @see {@link https://www.openiap.dev/docs/apis/ios/current-entitlement-ios}
878
+ * @see {@link https://openiap.dev/docs/apis/ios/current-entitlement-ios}
820
879
  */
821
880
  export const currentEntitlementIOS = async sku => {
822
881
  if (Platform.OS !== 'ios') {
@@ -847,7 +906,7 @@ export const currentEntitlementIOS = async sku => {
847
906
  * @returns Promise<Purchase | null> - Latest transaction or null
848
907
  * @platform iOS
849
908
  *
850
- * @see {@link https://www.openiap.dev/docs/apis/ios/latest-transaction-ios}
909
+ * @see {@link https://openiap.dev/docs/apis/ios/latest-transaction-ios}
851
910
  */
852
911
  export const latestTransactionIOS = async sku => {
853
912
  if (Platform.OS !== 'ios') {
@@ -877,7 +936,7 @@ export const latestTransactionIOS = async sku => {
877
936
  * @returns Promise<Purchase[]> - Array of pending transactions
878
937
  * @platform iOS
879
938
  *
880
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-pending-transactions-ios}
939
+ * @see {@link https://openiap.dev/docs/apis/ios/get-pending-transactions-ios}
881
940
  */
882
941
  export const getPendingTransactionsIOS = async () => {
883
942
  if (Platform.OS !== 'ios') {
@@ -901,7 +960,7 @@ export const getPendingTransactionsIOS = async () => {
901
960
  /**
902
961
  * List every StoreKit transaction (finished + unfinished) for the current user.
903
962
  *
904
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-all-transactions-ios}
963
+ * @see {@link https://openiap.dev/docs/apis/ios/get-all-transactions-ios}
905
964
  */
906
965
  export const getAllTransactionsIOS = async () => {
907
966
  if (Platform.OS !== 'ios') {
@@ -927,7 +986,7 @@ export const getAllTransactionsIOS = async () => {
927
986
  * @returns Promise<Purchase[]> - Subscriptions where auto-renewal status changed
928
987
  * @platform iOS
929
988
  *
930
- * @see {@link https://www.openiap.dev/docs/apis/ios/show-manage-subscriptions-ios}
989
+ * @see {@link https://openiap.dev/docs/apis/ios/show-manage-subscriptions-ios}
931
990
  */
932
991
  export const showManageSubscriptionsIOS = async () => {
933
992
  if (Platform.OS !== 'ios') {
@@ -954,7 +1013,7 @@ export const showManageSubscriptionsIOS = async () => {
954
1013
  * @returns Promise<boolean> - Eligibility status
955
1014
  * @platform iOS
956
1015
  *
957
- * @see {@link https://www.openiap.dev/docs/apis/ios/is-eligible-for-intro-offer-ios}
1016
+ * @see {@link https://openiap.dev/docs/apis/ios/is-eligible-for-intro-offer-ios}
958
1017
  */
959
1018
  export const isEligibleForIntroOfferIOS = async groupID => {
960
1019
  if (Platform.OS !== 'ios') {
@@ -979,7 +1038,7 @@ export const isEligibleForIntroOfferIOS = async groupID => {
979
1038
  * @returns Promise<string> - Base64 encoded receipt data
980
1039
  * @platform iOS
981
1040
  *
982
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-receipt-data-ios}
1041
+ * @see {@link https://openiap.dev/docs/apis/ios/get-receipt-data-ios}
983
1042
  */
984
1043
  export const getReceiptDataIOS = async () => {
985
1044
  if (Platform.OS !== 'ios') {
@@ -1048,7 +1107,7 @@ export const requestReceiptRefreshIOS = async () => {
1048
1107
  * @returns Promise<boolean> - Verification status
1049
1108
  * @platform iOS
1050
1109
  *
1051
- * @see {@link https://www.openiap.dev/docs/apis/ios/is-transaction-verified-ios}
1110
+ * @see {@link https://openiap.dev/docs/apis/ios/is-transaction-verified-ios}
1052
1111
  */
1053
1112
  export const isTransactionVerifiedIOS = async sku => {
1054
1113
  if (Platform.OS !== 'ios') {
@@ -1074,7 +1133,7 @@ export const isTransactionVerifiedIOS = async sku => {
1074
1133
  * @returns Promise<string | null> - JWS representation or null
1075
1134
  * @platform iOS
1076
1135
  *
1077
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-transaction-jws-ios}
1136
+ * @see {@link https://openiap.dev/docs/apis/ios/get-transaction-jws-ios}
1078
1137
  */
1079
1138
  export const getTransactionJwsIOS = async sku => {
1080
1139
  if (Platform.OS !== 'ios') {
@@ -1115,7 +1174,7 @@ export const getTransactionJwsIOS = async sku => {
1115
1174
  * @remarks When using `useIAP()`, connection is auto-managed on mount/unmount —
1116
1175
  * pass options to the hook instead of calling this directly.
1117
1176
  *
1118
- * @see {@link https://www.openiap.dev/docs/apis/init-connection}
1177
+ * @see {@link https://openiap.dev/docs/apis/init-connection}
1119
1178
  */
1120
1179
  export const initConnection = async config => {
1121
1180
  try {
@@ -1135,7 +1194,7 @@ export const initConnection = async config => {
1135
1194
  /**
1136
1195
  * Close the store connection and release resources.
1137
1196
  *
1138
- * @see {@link https://www.openiap.dev/docs/apis/end-connection}
1197
+ * @see {@link https://openiap.dev/docs/apis/end-connection}
1139
1198
  */
1140
1199
  export const endConnection = async () => {
1141
1200
  try {
@@ -1158,7 +1217,7 @@ export const endConnection = async () => {
1158
1217
  /**
1159
1218
  * Restore non-consumable and active subscription purchases.
1160
1219
  *
1161
- * @see {@link https://www.openiap.dev/docs/apis/restore-purchases}
1220
+ * @see {@link https://openiap.dev/docs/apis/restore-purchases}
1162
1221
  */
1163
1222
  export const restorePurchases = async () => {
1164
1223
  try {
@@ -1205,7 +1264,7 @@ export const restorePurchases = async () => {
1205
1264
  * @remarks Event-based. Listen for the result via {@link purchaseUpdatedListener} /
1206
1265
  * {@link purchaseErrorListener}, or use `useIAP({ onPurchaseSuccess, onPurchaseError })`.
1207
1266
  *
1208
- * @see {@link https://www.openiap.dev/docs/apis/request-purchase}
1267
+ * @see {@link https://openiap.dev/docs/apis/request-purchase}
1209
1268
  */
1210
1269
  export const requestPurchase = async request => {
1211
1270
  try {
@@ -1232,7 +1291,7 @@ export const requestPurchase = async request => {
1232
1291
  throw new Error('Invalid request for Android. The `skus` property is required and must be a non-empty array.');
1233
1292
  }
1234
1293
  } else {
1235
- throw new Error('Unsupported platform');
1294
+ throw unsupportedPlatformError();
1236
1295
  }
1237
1296
  const unifiedRequest = {};
1238
1297
 
@@ -1259,6 +1318,18 @@ export const requestPurchase = async request => {
1259
1318
  if (iosRequest.advancedCommerceData) {
1260
1319
  iosPayload.advancedCommerceData = iosRequest.advancedCommerceData;
1261
1320
  }
1321
+ if (isSubs) {
1322
+ const subscriptionRequest = iosRequest;
1323
+ if (subscriptionRequest.introductoryOfferEligibility !== undefined) {
1324
+ iosPayload.introductoryOfferEligibility = subscriptionRequest.introductoryOfferEligibility;
1325
+ }
1326
+ if (subscriptionRequest.promotionalOfferJWS) {
1327
+ iosPayload.promotionalOfferJWS = subscriptionRequest.promotionalOfferJWS;
1328
+ }
1329
+ if (subscriptionRequest.winBackOffer) {
1330
+ iosPayload.winBackOffer = subscriptionRequest.winBackOffer;
1331
+ }
1332
+ }
1262
1333
  unifiedRequest.ios = iosPayload;
1263
1334
  }
1264
1335
 
@@ -1340,7 +1411,7 @@ export const requestPurchase = async request => {
1340
1411
  * @remarks **Critical:** Android purchases must be finalized within 3 days or Google
1341
1412
  * auto-refunds. iOS unfinished transactions replay on every app launch.
1342
1413
  *
1343
- * @see {@link https://www.openiap.dev/docs/apis/finish-transaction}
1414
+ * @see {@link https://openiap.dev/docs/apis/finish-transaction}
1344
1415
  */
1345
1416
  export const finishTransaction = async args => {
1346
1417
  const {
@@ -1370,7 +1441,7 @@ export const finishTransaction = async args => {
1370
1441
  }
1371
1442
  };
1372
1443
  } else {
1373
- throw new Error('Unsupported platform');
1444
+ throw unsupportedPlatformError();
1374
1445
  }
1375
1446
  const result = await IAP.instance.finishTransaction(params);
1376
1447
  const success = getSuccessFromPurchaseVariant(result, 'finishTransaction');
@@ -1404,7 +1475,7 @@ export const finishTransaction = async args => {
1404
1475
  * await acknowledgePurchaseAndroid('purchase_token_here');
1405
1476
  * ```
1406
1477
  *
1407
- * @see {@link https://www.openiap.dev/docs/apis/android/acknowledge-purchase-android}
1478
+ * @see {@link https://openiap.dev/docs/apis/android/acknowledge-purchase-android}
1408
1479
  */
1409
1480
  export const acknowledgePurchaseAndroid = async purchaseToken => {
1410
1481
  try {
@@ -1440,7 +1511,7 @@ export const acknowledgePurchaseAndroid = async purchaseToken => {
1440
1511
  * await consumePurchaseAndroid('purchase_token_here');
1441
1512
  * ```
1442
1513
  *
1443
- * @see {@link https://www.openiap.dev/docs/apis/android/consume-purchase-android}
1514
+ * @see {@link https://openiap.dev/docs/apis/android/consume-purchase-android}
1444
1515
  */
1445
1516
  export const consumePurchaseAndroid = async purchaseToken => {
1446
1517
  try {
@@ -1494,7 +1565,7 @@ export const consumePurchaseAndroid = async purchaseToken => {
1494
1565
  * });
1495
1566
  * ```
1496
1567
  *
1497
- * @see {@link https://www.openiap.dev/docs/apis/validate-receipt}
1568
+ * @see {@link https://openiap.dev/docs/apis/validate-receipt}
1498
1569
  */
1499
1570
  export const validateReceipt = async options => {
1500
1571
  const {
@@ -1603,7 +1674,7 @@ export const validateReceipt = async options => {
1603
1674
  * @param options - Receipt validation options containing the SKU
1604
1675
  * @returns Promise resolving to receipt validation result
1605
1676
  *
1606
- * @see {@link https://www.openiap.dev/docs/features/validation#verify-purchase}
1677
+ * @see {@link https://openiap.dev/docs/features/validation#verify-purchase}
1607
1678
  */
1608
1679
  export const verifyPurchase = validateReceipt;
1609
1680
 
@@ -1614,7 +1685,7 @@ export const verifyPurchase = validateReceipt;
1614
1685
  * consumers who imported `validateReceiptIOS` — which is still declared on the
1615
1686
  * OpenIAP Query interface — keep working. Throws on non-iOS platforms.
1616
1687
  *
1617
- * @see {@link https://www.openiap.dev/docs/apis/ios/validate-receipt-ios}
1688
+ * @see {@link https://openiap.dev/docs/apis/ios/validate-receipt-ios}
1618
1689
  */
1619
1690
  export const validateReceiptIOS = async options => {
1620
1691
  if (Platform.OS !== 'ios') {
@@ -1645,7 +1716,7 @@ export const validateReceiptIOS = async options => {
1645
1716
  * });
1646
1717
  * ```
1647
1718
  *
1648
- * @see {@link https://www.openiap.dev/docs/features/validation#verify-purchase-with-provider}
1719
+ * @see {@link https://openiap.dev/docs/features/validation#verify-purchase-with-provider}
1649
1720
  */
1650
1721
  export const verifyPurchaseWithProvider = async options => {
1651
1722
  try {
@@ -1686,7 +1757,7 @@ export const verifyPurchaseWithProvider = async options => {
1686
1757
  * @returns Promise<boolean>
1687
1758
  * @platform iOS
1688
1759
  *
1689
- * @see {@link https://www.openiap.dev/docs/apis/ios/sync-ios}
1760
+ * @see {@link https://openiap.dev/docs/apis/ios/sync-ios}
1690
1761
  */
1691
1762
  export const syncIOS = async () => {
1692
1763
  if (Platform.OS !== 'ios') {
@@ -1712,7 +1783,7 @@ export const syncIOS = async () => {
1712
1783
  * @returns Promise<boolean> - Indicates whether the redemption sheet was presented
1713
1784
  * @platform iOS
1714
1785
  *
1715
- * @see {@link https://www.openiap.dev/docs/apis/ios/present-code-redemption-sheet-ios}
1786
+ * @see {@link https://openiap.dev/docs/apis/ios/present-code-redemption-sheet-ios}
1716
1787
  */
1717
1788
  export const presentCodeRedemptionSheetIOS = async () => {
1718
1789
  if (Platform.OS !== 'ios') {
@@ -1753,7 +1824,7 @@ export const presentCodeRedemptionSheetIOS = async () => {
1753
1824
  * @returns Promise<boolean> - true when the request triggers successfully
1754
1825
  * @platform iOS
1755
1826
  *
1756
- * @see {@link https://www.openiap.dev/docs/apis/ios/request-purchase-on-promoted-product-ios}
1827
+ * @see {@link https://openiap.dev/docs/apis/ios/request-purchase-on-promoted-product-ios}
1757
1828
  */
1758
1829
  export const requestPurchaseOnPromotedProductIOS = async () => {
1759
1830
  if (Platform.OS !== 'ios') {
@@ -1788,7 +1859,7 @@ export const requestPurchaseOnPromotedProductIOS = async () => {
1788
1859
  * @returns Promise<boolean>
1789
1860
  * @platform iOS
1790
1861
  *
1791
- * @see {@link https://www.openiap.dev/docs/apis/ios/clear-transaction-ios}
1862
+ * @see {@link https://openiap.dev/docs/apis/ios/clear-transaction-ios}
1792
1863
  */
1793
1864
  export const clearTransactionIOS = async () => {
1794
1865
  if (Platform.OS !== 'ios') {
@@ -1815,7 +1886,7 @@ export const clearTransactionIOS = async () => {
1815
1886
  * @returns Promise<string | null> - The refund status or null if not available
1816
1887
  * @platform iOS
1817
1888
  *
1818
- * @see {@link https://www.openiap.dev/docs/apis/ios/begin-refund-request-ios}
1889
+ * @see {@link https://openiap.dev/docs/apis/ios/begin-refund-request-ios}
1819
1890
  */
1820
1891
  export const beginRefundRequestIOS = async sku => {
1821
1892
  if (Platform.OS !== 'ios') {
@@ -1840,7 +1911,7 @@ export const beginRefundRequestIOS = async sku => {
1840
1911
  * Deeplinks to native interface that allows users to manage their subscriptions
1841
1912
  * Cross-platform alias aligning with expo-iap
1842
1913
  *
1843
- * @see {@link https://www.openiap.dev/docs/apis/deep-link-to-subscriptions}
1914
+ * @see {@link https://openiap.dev/docs/apis/deep-link-to-subscriptions}
1844
1915
  */
1845
1916
  export const deepLinkToSubscriptions = async options => {
1846
1917
  const resolvedOptions = options ?? undefined;
@@ -1852,16 +1923,14 @@ export const deepLinkToSubscriptions = async options => {
1852
1923
  return;
1853
1924
  }
1854
1925
  if (Platform.OS === 'ios') {
1855
- try {
1856
- if (typeof IAP.instance.deepLinkToSubscriptionsIOS === 'function') {
1857
- await IAP.instance.deepLinkToSubscriptionsIOS();
1858
- } else {
1859
- await IAP.instance.showManageSubscriptionsIOS();
1860
- }
1861
- } catch (error) {
1862
- RnIapConsole.warn('[deepLinkToSubscriptions] Failed on iOS:', error);
1926
+ if (typeof IAP.instance.deepLinkToSubscriptionsIOS === 'function') {
1927
+ await IAP.instance.deepLinkToSubscriptionsIOS();
1928
+ } else {
1929
+ await IAP.instance.showManageSubscriptionsIOS();
1863
1930
  }
1931
+ return;
1864
1932
  }
1933
+ throw unsupportedPlatformError();
1865
1934
  };
1866
1935
  export const deepLinkToSubscriptionsIOS = async () => {
1867
1936
  if (Platform.OS !== 'ios') {
@@ -1898,7 +1967,7 @@ export const deepLinkToSubscriptionsIOS = async () => {
1898
1967
  * @param subscriptionIds - Optional array of subscription IDs to filter by
1899
1968
  * @returns Promise<ActiveSubscription[]> - Array of active subscriptions
1900
1969
  *
1901
- * @see {@link https://www.openiap.dev/docs/apis/get-active-subscriptions}
1970
+ * @see {@link https://openiap.dev/docs/apis/get-active-subscriptions}
1902
1971
  */
1903
1972
  export const getActiveSubscriptions = async subscriptionIds => {
1904
1973
  try {
@@ -1919,7 +1988,7 @@ export const getActiveSubscriptions = async subscriptionIds => {
1919
1988
  environmentIOS: sub.environmentIOS ?? null,
1920
1989
  willExpireSoon: sub.willExpireSoon ?? null,
1921
1990
  daysUntilExpirationIOS: sub.daysUntilExpirationIOS ?? null,
1922
- // 🆕 renewalInfoIOS - subscription lifecycle information (iOS only)
1991
+ // renewalInfoIOS contains subscription lifecycle information on iOS.
1923
1992
  renewalInfoIOS: sub.renewalInfoIOS ? {
1924
1993
  willAutoRenew: sub.renewalInfoIOS.willAutoRenew ?? false,
1925
1994
  autoRenewPreference: sub.renewalInfoIOS.autoRenewPreference ?? null,
@@ -1954,90 +2023,6 @@ export const getActiveSubscriptions = async subscriptionIds => {
1954
2023
  }
1955
2024
  };
1956
2025
 
1957
- // OLD IMPLEMENTATION - REPLACED WITH NATIVE CALL
1958
- /*
1959
- export const getActiveSubscriptions_OLD: QueryField<
1960
- 'getActiveSubscriptions'
1961
- > = async (subscriptionIds) => {
1962
- try {
1963
- // Get all available purchases first
1964
- const allPurchases = await getAvailablePurchases();
1965
-
1966
- // For the critical bug fix: this function was previously returning ALL purchases
1967
- // Now we properly filter for subscriptions only
1968
-
1969
- // In production with real data, Android subscription filtering is done via platform-specific calls
1970
- // But for backward compatibility and test support, we also check platform-specific fields
1971
-
1972
- // Since expirationDateIOS and subscriptionGroupIdIOS are not available in NitroPurchase,
1973
- // we need to rely on other indicators or assume all purchases are subscriptions
1974
- // when called from getActiveSubscriptions
1975
- const purchases = allPurchases;
1976
-
1977
- // Filter for subscriptions and map to ActiveSubscription format
1978
- const subscriptions = purchases
1979
- .filter((purchase) => {
1980
- // Filter by subscription IDs if provided
1981
- if (subscriptionIds && subscriptionIds.length > 0) {
1982
- return subscriptionIds.includes(purchase.productId);
1983
- }
1984
- return true;
1985
- })
1986
- .map((purchase): ActiveSubscription => {
1987
- // Safe access to platform-specific fields with type guards
1988
- const expirationDateIOS =
1989
- 'expirationDateIOS' in purchase
1990
- ? ((purchase as PurchaseIOS).expirationDateIOS ?? null)
1991
- : null;
1992
-
1993
- const environmentIOS =
1994
- 'environmentIOS' in purchase
1995
- ? ((purchase as PurchaseIOS).environmentIOS ?? null)
1996
- : null;
1997
-
1998
- const autoRenewingAndroid =
1999
- 'autoRenewingAndroid' in purchase || 'isAutoRenewing' in purchase
2000
- ? ((purchase as PurchaseAndroid).autoRenewingAndroid ??
2001
- (purchase as PurchaseAndroid).isAutoRenewing) // deprecated - use isAutoRenewing instead
2002
- : null;
2003
-
2004
- // 🆕 Extract renewalInfoIOS if available
2005
- const renewalInfoIOS =
2006
- 'renewalInfoIOS' in purchase
2007
- ? ((purchase as PurchaseIOS).renewalInfoIOS ?? null)
2008
- : null;
2009
-
2010
- return {
2011
- productId: purchase.productId,
2012
- isActive: true, // If it's in availablePurchases, it's active
2013
- // Backend validation fields - use transactionId ?? id for proper field mapping
2014
- transactionId: purchase.transactionId ?? purchase.id,
2015
- purchaseToken: purchase.purchaseToken,
2016
- transactionDate: purchase.transactionDate,
2017
- // Platform-specific fields
2018
- expirationDateIOS,
2019
- autoRenewingAndroid,
2020
- environmentIOS,
2021
- renewalInfoIOS,
2022
- // Convenience fields
2023
- willExpireSoon: false, // Would need to calculate based on expiration date
2024
- daysUntilExpirationIOS:
2025
- expirationDateIOS != null
2026
- ? Math.ceil(
2027
- (expirationDateIOS - Date.now()) / (1000 * 60 * 60 * 24),
2028
- )
2029
- : null,
2030
- };
2031
- });
2032
-
2033
- return subscriptions;
2034
- } catch (error) {
2035
- RnIapConsole.error('Failed to get active subscriptions:', error);
2036
- const errorJson = parseErrorStringToJsonObj(error);
2037
- throw new Error(errorJson.message);
2038
- }
2039
- };
2040
-
2041
2026
  /**
2042
2027
  * Check if the user has any active subscriptions (OpenIAP compliant)
2043
2028
  * Returns true if the user has at least one active subscription, false otherwise.
@@ -2046,7 +2031,7 @@ export const getActiveSubscriptions_OLD: QueryField<
2046
2031
  * @param subscriptionIds - Optional array of subscription IDs to check
2047
2032
  * @returns Promise<boolean> - True if there are active subscriptions
2048
2033
  *
2049
- * @see {@link https://www.openiap.dev/docs/apis/has-active-subscriptions}
2034
+ * @see {@link https://openiap.dev/docs/apis/has-active-subscriptions}
2050
2035
  */
2051
2036
  export const hasActiveSubscriptions = async subscriptionIds => {
2052
2037
  try {
@@ -2150,7 +2135,7 @@ const normalizeProductQueryType = type => {
2150
2135
  * }
2151
2136
  * ```
2152
2137
  *
2153
- * @see {@link https://www.openiap.dev/docs/apis/android/check-alternative-billing-availability-android}
2138
+ * @see {@link https://openiap.dev/docs/apis/android/check-alternative-billing-availability-android}
2154
2139
  */
2155
2140
  export const checkAlternativeBillingAvailabilityAndroid = async () => {
2156
2141
  if (Platform.OS !== 'android') {
@@ -2187,7 +2172,7 @@ export const checkAlternativeBillingAvailabilityAndroid = async () => {
2187
2172
  * }
2188
2173
  * ```
2189
2174
  *
2190
- * @see {@link https://www.openiap.dev/docs/apis/android/show-alternative-billing-dialog-android}
2175
+ * @see {@link https://openiap.dev/docs/apis/android/show-alternative-billing-dialog-android}
2191
2176
  */
2192
2177
  export const showAlternativeBillingDialogAndroid = async () => {
2193
2178
  if (Platform.OS !== 'android') {
@@ -2224,7 +2209,7 @@ export const showAlternativeBillingDialogAndroid = async () => {
2224
2209
  * }
2225
2210
  * ```
2226
2211
  *
2227
- * @see {@link https://www.openiap.dev/docs/apis/android/create-alternative-billing-token-android}
2212
+ * @see {@link https://openiap.dev/docs/apis/android/create-alternative-billing-token-android}
2228
2213
  */
2229
2214
  export const createAlternativeBillingTokenAndroid = async sku => {
2230
2215
  if (Platform.OS !== 'android') {
@@ -2238,18 +2223,6 @@ export const createAlternativeBillingTokenAndroid = async sku => {
2238
2223
  }
2239
2224
  };
2240
2225
 
2241
- /**
2242
- * Parameters for launching an external link (Android 8.2.0+).
2243
- */
2244
-
2245
- /**
2246
- * Result of checking billing program availability (Android 8.2.0+).
2247
- */
2248
-
2249
- /**
2250
- * Reporting details for external transactions (Android 8.2.0+).
2251
- */
2252
-
2253
2226
  /**
2254
2227
  * Enable a billing program before initConnection (Android only).
2255
2228
  * Must be called BEFORE initConnection() to configure the BillingClient.
@@ -2293,7 +2266,7 @@ export const enableBillingProgramAndroid = program => {
2293
2266
  * }
2294
2267
  * ```
2295
2268
  *
2296
- * @see {@link https://www.openiap.dev/docs/apis/android/is-billing-program-available-android}
2269
+ * @see {@link https://openiap.dev/docs/apis/android/is-billing-program-available-android}
2297
2270
  */
2298
2271
  export const isBillingProgramAvailableAndroid = async program => {
2299
2272
  if (Platform.OS !== 'android') {
@@ -2330,7 +2303,7 @@ export const isBillingProgramAvailableAndroid = async program => {
2330
2303
  * });
2331
2304
  * ```
2332
2305
  *
2333
- * @see {@link https://www.openiap.dev/docs/apis/android/create-billing-program-reporting-details-android}
2306
+ * @see {@link https://openiap.dev/docs/apis/android/create-billing-program-reporting-details-android}
2334
2307
  */
2335
2308
  export const createBillingProgramReportingDetailsAndroid = async program => {
2336
2309
  if (Platform.OS !== 'android') {
@@ -2369,7 +2342,7 @@ export const createBillingProgramReportingDetailsAndroid = async program => {
2369
2342
  * }
2370
2343
  * ```
2371
2344
  *
2372
- * @see {@link https://www.openiap.dev/docs/apis/android/launch-external-link-android}
2345
+ * @see {@link https://openiap.dev/docs/apis/android/launch-external-link-android}
2373
2346
  */
2374
2347
  export const launchExternalLinkAndroid = async params => {
2375
2348
  if (Platform.OS !== 'android') {
@@ -2411,7 +2384,7 @@ export const launchExternalLinkAndroid = async params => {
2411
2384
  * }
2412
2385
  * ```
2413
2386
  *
2414
- * @see {@link https://www.openiap.dev/docs/apis/ios/can-present-external-purchase-notice-ios}
2387
+ * @see {@link https://openiap.dev/docs/apis/ios/can-present-external-purchase-notice-ios}
2415
2388
  */
2416
2389
  export const canPresentExternalPurchaseNoticeIOS = async () => {
2417
2390
  if (Platform.OS !== 'ios') {
@@ -2441,7 +2414,7 @@ export const canPresentExternalPurchaseNoticeIOS = async () => {
2441
2414
  * }
2442
2415
  * ```
2443
2416
  *
2444
- * @see {@link https://www.openiap.dev/docs/apis/ios/present-external-purchase-notice-sheet-ios}
2417
+ * @see {@link https://openiap.dev/docs/apis/ios/present-external-purchase-notice-sheet-ios}
2445
2418
  */
2446
2419
  export const presentExternalPurchaseNoticeSheetIOS = async () => {
2447
2420
  if (Platform.OS !== 'ios') {
@@ -2470,7 +2443,7 @@ export const presentExternalPurchaseNoticeSheetIOS = async () => {
2470
2443
  * }
2471
2444
  * ```
2472
2445
  *
2473
- * @see {@link https://www.openiap.dev/docs/apis/ios/present-external-purchase-link-ios}
2446
+ * @see {@link https://openiap.dev/docs/apis/ios/present-external-purchase-link-ios}
2474
2447
  */
2475
2448
  export const presentExternalPurchaseLinkIOS = async url => {
2476
2449
  if (Platform.OS !== 'ios') {
@@ -2504,7 +2477,7 @@ export const presentExternalPurchaseLinkIOS = async url => {
2504
2477
  * }
2505
2478
  * ```
2506
2479
  *
2507
- * @see {@link https://www.openiap.dev/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios}
2480
+ * @see {@link https://openiap.dev/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios}
2508
2481
  */
2509
2482
  export const isEligibleForExternalPurchaseCustomLinkIOS = async () => {
2510
2483
  if (Platform.OS !== 'ios') {
@@ -2537,7 +2510,7 @@ export const isEligibleForExternalPurchaseCustomLinkIOS = async () => {
2537
2510
  * }
2538
2511
  * ```
2539
2512
  *
2540
- * @see {@link https://www.openiap.dev/docs/apis/ios/get-external-purchase-custom-link-token-ios}
2513
+ * @see {@link https://openiap.dev/docs/apis/ios/get-external-purchase-custom-link-token-ios}
2541
2514
  */
2542
2515
  export const getExternalPurchaseCustomLinkTokenIOS = async tokenType => {
2543
2516
  if (Platform.OS !== 'ios') {
@@ -2570,7 +2543,7 @@ export const getExternalPurchaseCustomLinkTokenIOS = async tokenType => {
2570
2543
  * }
2571
2544
  * ```
2572
2545
  *
2573
- * @see {@link https://www.openiap.dev/docs/apis/ios/show-external-purchase-custom-link-notice-ios}
2546
+ * @see {@link https://openiap.dev/docs/apis/ios/show-external-purchase-custom-link-notice-ios}
2574
2547
  */
2575
2548
  export const showExternalPurchaseCustomLinkNoticeIOS = async noticeType => {
2576
2549
  if (Platform.OS !== 'ios') {