react-native-iap 14.3.9 → 14.4.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 (54) hide show
  1. package/NitroIap.podspec +11 -1
  2. package/README.md +2 -3
  3. package/android/build.gradle +24 -1
  4. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +369 -124
  5. package/android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt +64 -0
  6. package/ios/HybridRnIap.swift +525 -362
  7. package/ios/RnIapHelper.swift +224 -0
  8. package/ios/RnIapLog.swift +127 -0
  9. package/lib/module/hooks/useIAP.js +2 -34
  10. package/lib/module/hooks/useIAP.js.map +1 -1
  11. package/lib/module/index.js +52 -2
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/types.js.map +1 -1
  14. package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
  15. package/lib/typescript/src/hooks/useIAP.d.ts +0 -12
  16. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  17. package/lib/typescript/src/index.d.ts +3 -0
  18. package/lib/typescript/src/index.d.ts.map +1 -1
  19. package/lib/typescript/src/specs/RnIap.nitro.d.ts +24 -0
  20. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  21. package/lib/typescript/src/types.d.ts +8 -6
  22. package/lib/typescript/src/types.d.ts.map +1 -1
  23. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +64 -0
  24. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +4 -0
  25. package/nitrogen/generated/android/c++/JIapPlatform.hpp +3 -3
  26. package/nitrogen/generated/android/c++/JPurchaseAndroid.hpp +6 -2
  27. package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +4 -0
  28. package/nitrogen/generated/android/c++/JPurchaseState.hpp +6 -6
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +16 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapPlatform.kt +2 -2
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseAndroid.kt +4 -1
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +3 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseState.kt +5 -5
  34. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +32 -0
  35. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +4 -0
  36. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +82 -0
  37. package/nitrogen/generated/ios/swift/IapPlatform.swift +4 -4
  38. package/nitrogen/generated/ios/swift/PurchaseAndroid.swift +32 -2
  39. package/nitrogen/generated/ios/swift/PurchaseIOS.swift +13 -2
  40. package/nitrogen/generated/ios/swift/PurchaseState.swift +8 -8
  41. package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +4 -0
  42. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +4 -0
  43. package/nitrogen/generated/shared/c++/IapPlatform.hpp +5 -5
  44. package/nitrogen/generated/shared/c++/PurchaseAndroid.hpp +6 -2
  45. package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +5 -1
  46. package/nitrogen/generated/shared/c++/PurchaseState.hpp +11 -11
  47. package/openiap-versions.json +5 -0
  48. package/package.json +3 -2
  49. package/plugin/build/withIAP.js +35 -3
  50. package/plugin/src/withIAP.ts +44 -3
  51. package/src/hooks/useIAP.ts +3 -71
  52. package/src/index.ts +61 -2
  53. package/src/specs/RnIap.nitro.ts +28 -0
  54. package/src/types.ts +8 -6
@@ -67,12 +67,13 @@ namespace margelo::nitro::iap {
67
67
  std::optional<std::string> storefrontCountryCodeIOS SWIFT_PRIVATE;
68
68
  std::optional<std::string> subscriptionGroupIdIOS SWIFT_PRIVATE;
69
69
  double transactionDate SWIFT_PRIVATE;
70
+ std::string transactionId SWIFT_PRIVATE;
70
71
  std::optional<std::string> transactionReasonIOS SWIFT_PRIVATE;
71
72
  std::optional<std::string> webOrderLineItemIdIOS SWIFT_PRIVATE;
72
73
 
73
74
  public:
74
75
  PurchaseIOS() = default;
75
- explicit PurchaseIOS(std::optional<std::string> appAccountToken, std::optional<std::string> appBundleIdIOS, std::optional<std::string> countryCodeIOS, std::optional<std::string> currencyCodeIOS, std::optional<std::string> currencySymbolIOS, std::optional<std::string> environmentIOS, std::optional<double> expirationDateIOS, std::string id, std::optional<std::vector<std::string>> ids, bool isAutoRenewing, std::optional<bool> isUpgradedIOS, std::optional<PurchaseOfferIOS> offerIOS, std::optional<double> originalTransactionDateIOS, std::optional<std::string> originalTransactionIdentifierIOS, std::optional<std::string> ownershipTypeIOS, IapPlatform platform, std::string productId, PurchaseState purchaseState, std::optional<std::string> purchaseToken, double quantity, std::optional<double> quantityIOS, std::optional<std::string> reasonIOS, std::optional<std::string> reasonStringRepresentationIOS, std::optional<double> revocationDateIOS, std::optional<std::string> revocationReasonIOS, std::optional<std::string> storefrontCountryCodeIOS, std::optional<std::string> subscriptionGroupIdIOS, double transactionDate, std::optional<std::string> transactionReasonIOS, std::optional<std::string> webOrderLineItemIdIOS): appAccountToken(appAccountToken), appBundleIdIOS(appBundleIdIOS), countryCodeIOS(countryCodeIOS), currencyCodeIOS(currencyCodeIOS), currencySymbolIOS(currencySymbolIOS), environmentIOS(environmentIOS), expirationDateIOS(expirationDateIOS), id(id), ids(ids), isAutoRenewing(isAutoRenewing), isUpgradedIOS(isUpgradedIOS), offerIOS(offerIOS), originalTransactionDateIOS(originalTransactionDateIOS), originalTransactionIdentifierIOS(originalTransactionIdentifierIOS), ownershipTypeIOS(ownershipTypeIOS), platform(platform), productId(productId), purchaseState(purchaseState), purchaseToken(purchaseToken), quantity(quantity), quantityIOS(quantityIOS), reasonIOS(reasonIOS), reasonStringRepresentationIOS(reasonStringRepresentationIOS), revocationDateIOS(revocationDateIOS), revocationReasonIOS(revocationReasonIOS), storefrontCountryCodeIOS(storefrontCountryCodeIOS), subscriptionGroupIdIOS(subscriptionGroupIdIOS), transactionDate(transactionDate), transactionReasonIOS(transactionReasonIOS), webOrderLineItemIdIOS(webOrderLineItemIdIOS) {}
76
+ explicit PurchaseIOS(std::optional<std::string> appAccountToken, std::optional<std::string> appBundleIdIOS, std::optional<std::string> countryCodeIOS, std::optional<std::string> currencyCodeIOS, std::optional<std::string> currencySymbolIOS, std::optional<std::string> environmentIOS, std::optional<double> expirationDateIOS, std::string id, std::optional<std::vector<std::string>> ids, bool isAutoRenewing, std::optional<bool> isUpgradedIOS, std::optional<PurchaseOfferIOS> offerIOS, std::optional<double> originalTransactionDateIOS, std::optional<std::string> originalTransactionIdentifierIOS, std::optional<std::string> ownershipTypeIOS, IapPlatform platform, std::string productId, PurchaseState purchaseState, std::optional<std::string> purchaseToken, double quantity, std::optional<double> quantityIOS, std::optional<std::string> reasonIOS, std::optional<std::string> reasonStringRepresentationIOS, std::optional<double> revocationDateIOS, std::optional<std::string> revocationReasonIOS, std::optional<std::string> storefrontCountryCodeIOS, std::optional<std::string> subscriptionGroupIdIOS, double transactionDate, std::string transactionId, std::optional<std::string> transactionReasonIOS, std::optional<std::string> webOrderLineItemIdIOS): appAccountToken(appAccountToken), appBundleIdIOS(appBundleIdIOS), countryCodeIOS(countryCodeIOS), currencyCodeIOS(currencyCodeIOS), currencySymbolIOS(currencySymbolIOS), environmentIOS(environmentIOS), expirationDateIOS(expirationDateIOS), id(id), ids(ids), isAutoRenewing(isAutoRenewing), isUpgradedIOS(isUpgradedIOS), offerIOS(offerIOS), originalTransactionDateIOS(originalTransactionDateIOS), originalTransactionIdentifierIOS(originalTransactionIdentifierIOS), ownershipTypeIOS(ownershipTypeIOS), platform(platform), productId(productId), purchaseState(purchaseState), purchaseToken(purchaseToken), quantity(quantity), quantityIOS(quantityIOS), reasonIOS(reasonIOS), reasonStringRepresentationIOS(reasonStringRepresentationIOS), revocationDateIOS(revocationDateIOS), revocationReasonIOS(revocationReasonIOS), storefrontCountryCodeIOS(storefrontCountryCodeIOS), subscriptionGroupIdIOS(subscriptionGroupIdIOS), transactionDate(transactionDate), transactionId(transactionId), transactionReasonIOS(transactionReasonIOS), webOrderLineItemIdIOS(webOrderLineItemIdIOS) {}
76
77
  };
77
78
 
78
79
  } // namespace margelo::nitro::iap
@@ -113,6 +114,7 @@ namespace margelo::nitro {
113
114
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "storefrontCountryCodeIOS")),
114
115
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "subscriptionGroupIdIOS")),
115
116
  JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, "transactionDate")),
117
+ JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "transactionId")),
116
118
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "transactionReasonIOS")),
117
119
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "webOrderLineItemIdIOS"))
118
120
  );
@@ -147,6 +149,7 @@ namespace margelo::nitro {
147
149
  obj.setProperty(runtime, "storefrontCountryCodeIOS", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.storefrontCountryCodeIOS));
148
150
  obj.setProperty(runtime, "subscriptionGroupIdIOS", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.subscriptionGroupIdIOS));
149
151
  obj.setProperty(runtime, "transactionDate", JSIConverter<double>::toJSI(runtime, arg.transactionDate));
152
+ obj.setProperty(runtime, "transactionId", JSIConverter<std::string>::toJSI(runtime, arg.transactionId));
150
153
  obj.setProperty(runtime, "transactionReasonIOS", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.transactionReasonIOS));
151
154
  obj.setProperty(runtime, "webOrderLineItemIdIOS", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.webOrderLineItemIdIOS));
152
155
  return obj;
@@ -184,6 +187,7 @@ namespace margelo::nitro {
184
187
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "storefrontCountryCodeIOS"))) return false;
185
188
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "subscriptionGroupIdIOS"))) return false;
186
189
  if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, "transactionDate"))) return false;
190
+ if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "transactionId"))) return false;
187
191
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "transactionReasonIOS"))) return false;
188
192
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "webOrderLineItemIdIOS"))) return false;
189
193
  return true;
@@ -29,11 +29,11 @@ namespace margelo::nitro::iap {
29
29
  * An enum which can be represented as a JavaScript union (PurchaseState).
30
30
  */
31
31
  enum class PurchaseState {
32
- DEFERRED SWIFT_NAME(deferred) = 0,
33
- FAILED SWIFT_NAME(failed) = 1,
34
- PENDING SWIFT_NAME(pending) = 2,
35
- PURCHASED SWIFT_NAME(purchased) = 3,
36
- RESTORED SWIFT_NAME(restored) = 4,
32
+ PENDING SWIFT_NAME(pending) = 0,
33
+ PURCHASED SWIFT_NAME(purchased) = 1,
34
+ FAILED SWIFT_NAME(failed) = 2,
35
+ RESTORED SWIFT_NAME(restored) = 3,
36
+ DEFERRED SWIFT_NAME(deferred) = 4,
37
37
  UNKNOWN SWIFT_NAME(unknown) = 5,
38
38
  } CLOSED_ENUM;
39
39
 
@@ -47,11 +47,11 @@ namespace margelo::nitro {
47
47
  static inline margelo::nitro::iap::PurchaseState fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
48
48
  std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
49
49
  switch (hashString(unionValue.c_str(), unionValue.size())) {
50
- case hashString("deferred"): return margelo::nitro::iap::PurchaseState::DEFERRED;
51
- case hashString("failed"): return margelo::nitro::iap::PurchaseState::FAILED;
52
50
  case hashString("pending"): return margelo::nitro::iap::PurchaseState::PENDING;
53
51
  case hashString("purchased"): return margelo::nitro::iap::PurchaseState::PURCHASED;
52
+ case hashString("failed"): return margelo::nitro::iap::PurchaseState::FAILED;
54
53
  case hashString("restored"): return margelo::nitro::iap::PurchaseState::RESTORED;
54
+ case hashString("deferred"): return margelo::nitro::iap::PurchaseState::DEFERRED;
55
55
  case hashString("unknown"): return margelo::nitro::iap::PurchaseState::UNKNOWN;
56
56
  default: [[unlikely]]
57
57
  throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum PurchaseState - invalid value!");
@@ -59,11 +59,11 @@ namespace margelo::nitro {
59
59
  }
60
60
  static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::iap::PurchaseState arg) {
61
61
  switch (arg) {
62
- case margelo::nitro::iap::PurchaseState::DEFERRED: return JSIConverter<std::string>::toJSI(runtime, "deferred");
63
- case margelo::nitro::iap::PurchaseState::FAILED: return JSIConverter<std::string>::toJSI(runtime, "failed");
64
62
  case margelo::nitro::iap::PurchaseState::PENDING: return JSIConverter<std::string>::toJSI(runtime, "pending");
65
63
  case margelo::nitro::iap::PurchaseState::PURCHASED: return JSIConverter<std::string>::toJSI(runtime, "purchased");
64
+ case margelo::nitro::iap::PurchaseState::FAILED: return JSIConverter<std::string>::toJSI(runtime, "failed");
66
65
  case margelo::nitro::iap::PurchaseState::RESTORED: return JSIConverter<std::string>::toJSI(runtime, "restored");
66
+ case margelo::nitro::iap::PurchaseState::DEFERRED: return JSIConverter<std::string>::toJSI(runtime, "deferred");
67
67
  case margelo::nitro::iap::PurchaseState::UNKNOWN: return JSIConverter<std::string>::toJSI(runtime, "unknown");
68
68
  default: [[unlikely]]
69
69
  throw std::invalid_argument("Cannot convert PurchaseState to JS - invalid value: "
@@ -76,11 +76,11 @@ namespace margelo::nitro {
76
76
  }
77
77
  std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
78
78
  switch (hashString(unionValue.c_str(), unionValue.size())) {
79
- case hashString("deferred"):
80
- case hashString("failed"):
81
79
  case hashString("pending"):
82
80
  case hashString("purchased"):
81
+ case hashString("failed"):
83
82
  case hashString("restored"):
83
+ case hashString("deferred"):
84
84
  case hashString("unknown"):
85
85
  return true;
86
86
  default:
@@ -0,0 +1,5 @@
1
+ {
2
+ "apple": "1.2.2",
3
+ "google": "1.2.6",
4
+ "gql": "1.0.8"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-iap",
3
- "version": "14.3.9",
3
+ "version": "14.4.0",
4
4
  "description": "React Native In-App Purchases module for iOS and Android using Nitro",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -24,6 +24,7 @@
24
24
  "cpp",
25
25
  "nitrogen",
26
26
  "nitro.json",
27
+ "openiap-versions.json",
27
28
  "*.podspec",
28
29
  "app.plugin.js",
29
30
  "react-native.config.js",
@@ -67,7 +68,7 @@
67
68
  "prepare:husky": "husky install",
68
69
  "precommit": "lint-staged",
69
70
  "ci:check": "./scripts/ci-check.sh",
70
- "generate:types": "node scripts/update-types.mjs --tag 1.0.6",
71
+ "generate:types": "node scripts/update-types.mjs",
71
72
  "generate:icon": "cd docs/static/img && npx sharp-cli resize 32 32 --input icon.png --output favicon-32x32.png && npx sharp-cli resize 16 16 --input icon.png --output favicon-16x16.png && npx sharp-cli resize 192 192 --input icon.png --output android-chrome-192x192.png && npx sharp-cli resize 512 512 --input icon.png --output android-chrome-512x512.png && npx sharp-cli resize 180 180 --input icon.png --output apple-touch-icon.png && npx sharp-cli resize 1200 630 --input icon.png --output og-image.png && npx sharp-cli resize 150 150 --input icon.png --output favicon.png && npx sharp-cli --input favicon-32x32.png --output favicon.ico",
72
73
  "commitlint": "commitlint"
73
74
  },
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = exports.modifyProjectBuildGradle = void 0;
4
4
  const config_plugins_1 = require("expo/config-plugins");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
5
7
  const pkg = require('../../package.json');
6
8
  // Global flag to prevent duplicate logs
7
9
  let hasLoggedPluginExecution = false;
@@ -31,16 +33,46 @@ const modifyProjectBuildGradle = (gradle) => {
31
33
  };
32
34
  exports.modifyProjectBuildGradle = modifyProjectBuildGradle;
33
35
  const OPENIAP_COORD = 'io.github.hyochan.openiap:openiap-google';
34
- const OPENIAP_VERSION = '1.1.12';
36
+ function loadOpenIapConfig() {
37
+ const versionsPath = (0, node_path_1.resolve)(__dirname, '../../openiap-versions.json');
38
+ try {
39
+ const raw = (0, node_fs_1.readFileSync)(versionsPath, 'utf8');
40
+ const parsed = JSON.parse(raw);
41
+ const googleVersion = typeof parsed?.google === 'string' ? parsed.google.trim() : '';
42
+ if (!googleVersion) {
43
+ throw new Error('react-native-iap: "google" version missing or invalid in openiap-versions.json');
44
+ }
45
+ return { google: googleVersion };
46
+ }
47
+ catch (error) {
48
+ throw new Error(`react-native-iap: Unable to load openiap-versions.json (${error instanceof Error ? error.message : error})`);
49
+ }
50
+ }
51
+ let cachedOpenIapVersion = null;
52
+ const getOpenIapVersion = () => {
53
+ if (cachedOpenIapVersion) {
54
+ return cachedOpenIapVersion;
55
+ }
56
+ cachedOpenIapVersion = loadOpenIapConfig().google;
57
+ return cachedOpenIapVersion;
58
+ };
35
59
  const modifyAppBuildGradle = (gradle) => {
36
60
  let modified = gradle;
61
+ let openiapVersion;
62
+ try {
63
+ openiapVersion = getOpenIapVersion();
64
+ }
65
+ catch (error) {
66
+ config_plugins_1.WarningAggregator.addWarningAndroid('react-native-iap', `react-native-iap: Failed to resolve OpenIAP version (${error instanceof Error ? error.message : error})`);
67
+ return gradle;
68
+ }
37
69
  // Replace legacy Billing/GMS instructions with OpenIAP Google library
38
70
  // Remove any old billingclient or play-services-base lines we may have added previously
39
71
  modified = modified
40
72
  .replace(/^[ \t]*(implementation|api)[ \t]+["']com\.android\.billingclient:billing-ktx:[^"']+["'][ \t]*$/gim, '')
41
73
  .replace(/^[ \t]*(implementation|api)[ \t]+["']com\.google\.android\.gms:play-services-base:[^"']+["'][ \t]*$/gim, '')
42
74
  .replace(/\n{3,}/g, '\n\n');
43
- const openiapDep = ` implementation "${OPENIAP_COORD}:${OPENIAP_VERSION}"`;
75
+ const openiapDep = ` implementation "${OPENIAP_COORD}:${openiapVersion}"`;
44
76
  if (!modified.includes(OPENIAP_COORD)) {
45
77
  if (!/dependencies\s*{/.test(modified)) {
46
78
  modified += `\n\ndependencies {\n${openiapDep}\n}\n`;
@@ -49,7 +81,7 @@ const modifyAppBuildGradle = (gradle) => {
49
81
  modified = addLineToGradle(modified, /dependencies\s*{/, openiapDep);
50
82
  }
51
83
  if (!hasLoggedPluginExecution) {
52
- console.log(`🛠️ react-native-iap: Added OpenIAP (${OPENIAP_VERSION}) to build.gradle`);
84
+ console.log(`🛠️ react-native-iap: Added OpenIAP (${openiapVersion}) to build.gradle`);
53
85
  }
54
86
  }
55
87
  return modified;
@@ -7,6 +7,8 @@ import {
7
7
  } from 'expo/config-plugins';
8
8
  import type {ConfigPlugin} from 'expo/config-plugins';
9
9
  import type {ExpoConfig} from '@expo/config-types';
10
+ import {readFileSync} from 'node:fs';
11
+ import {resolve as resolvePath} from 'node:path';
10
12
 
11
13
  const pkg = require('../../package.json');
12
14
 
@@ -46,11 +48,50 @@ export const modifyProjectBuildGradle = (gradle: string): string => {
46
48
  };
47
49
 
48
50
  const OPENIAP_COORD = 'io.github.hyochan.openiap:openiap-google';
49
- const OPENIAP_VERSION = '1.1.12';
51
+
52
+ function loadOpenIapConfig(): {google: string} {
53
+ const versionsPath = resolvePath(__dirname, '../../openiap-versions.json');
54
+ try {
55
+ const raw = readFileSync(versionsPath, 'utf8');
56
+ const parsed = JSON.parse(raw);
57
+ const googleVersion =
58
+ typeof parsed?.google === 'string' ? parsed.google.trim() : '';
59
+ if (!googleVersion) {
60
+ throw new Error(
61
+ 'react-native-iap: "google" version missing or invalid in openiap-versions.json',
62
+ );
63
+ }
64
+ return {google: googleVersion};
65
+ } catch (error) {
66
+ throw new Error(
67
+ `react-native-iap: Unable to load openiap-versions.json (${error instanceof Error ? error.message : error})`,
68
+ );
69
+ }
70
+ }
71
+
72
+ let cachedOpenIapVersion: string | null = null;
73
+ const getOpenIapVersion = (): string => {
74
+ if (cachedOpenIapVersion) {
75
+ return cachedOpenIapVersion;
76
+ }
77
+ cachedOpenIapVersion = loadOpenIapConfig().google;
78
+ return cachedOpenIapVersion;
79
+ };
50
80
 
51
81
  const modifyAppBuildGradle = (gradle: string): string => {
52
82
  let modified = gradle;
53
83
 
84
+ let openiapVersion: string;
85
+ try {
86
+ openiapVersion = getOpenIapVersion();
87
+ } catch (error) {
88
+ WarningAggregator.addWarningAndroid(
89
+ 'react-native-iap',
90
+ `react-native-iap: Failed to resolve OpenIAP version (${error instanceof Error ? error.message : error})`,
91
+ );
92
+ return gradle;
93
+ }
94
+
54
95
  // Replace legacy Billing/GMS instructions with OpenIAP Google library
55
96
  // Remove any old billingclient or play-services-base lines we may have added previously
56
97
  modified = modified
@@ -64,7 +105,7 @@ const modifyAppBuildGradle = (gradle: string): string => {
64
105
  )
65
106
  .replace(/\n{3,}/g, '\n\n');
66
107
 
67
- const openiapDep = ` implementation "${OPENIAP_COORD}:${OPENIAP_VERSION}"`;
108
+ const openiapDep = ` implementation "${OPENIAP_COORD}:${openiapVersion}"`;
68
109
 
69
110
  if (!modified.includes(OPENIAP_COORD)) {
70
111
  if (!/dependencies\s*{/.test(modified)) {
@@ -74,7 +115,7 @@ const modifyAppBuildGradle = (gradle: string): string => {
74
115
  }
75
116
  if (!hasLoggedPluginExecution) {
76
117
  console.log(
77
- `🛠️ react-native-iap: Added OpenIAP (${OPENIAP_VERSION}) to build.gradle`,
118
+ `🛠️ react-native-iap: Added OpenIAP (${openiapVersion}) to build.gradle`,
78
119
  );
79
120
  }
80
121
  }
@@ -45,8 +45,6 @@ interface EventSubscription {
45
45
  type UseIap = {
46
46
  connected: boolean;
47
47
  products: Product[];
48
- promotedProductsIOS: Purchase[];
49
- promotedProductIdIOS?: string;
50
48
  subscriptions: ProductSubscription[];
51
49
  availablePurchases: Purchase[];
52
50
  promotedProductIOS?: Product;
@@ -57,16 +55,6 @@ type UseIap = {
57
55
  skus: string[];
58
56
  type?: ProductQueryType | null;
59
57
  }) => Promise<void>;
60
- /**
61
- * @deprecated Use fetchProducts({ skus, type: 'in-app' }) instead. This method will be removed in version 3.0.0.
62
- * Note: This method internally uses fetchProducts, so no deprecation warning is shown.
63
- */
64
- getProducts: (skus: string[]) => Promise<void>;
65
- /**
66
- * @deprecated Use fetchProducts({ skus, type: 'subs' }) instead. This method will be removed in version 3.0.0.
67
- * Note: This method internally uses fetchProducts, so no deprecation warning is shown.
68
- */
69
- getSubscriptions: (skus: string[]) => Promise<void>;
70
58
  requestPurchase: (
71
59
  params: RequestPurchaseProps,
72
60
  ) => Promise<RequestPurchaseResult | null>;
@@ -103,11 +91,9 @@ export interface UseIapOptions {
103
91
  export function useIAP(options?: UseIapOptions): UseIap {
104
92
  const [connected, setConnected] = useState<boolean>(false);
105
93
  const [products, setProducts] = useState<Product[]>([]);
106
- const [promotedProductsIOS] = useState<Purchase[]>([]);
107
94
  const [subscriptions, setSubscriptions] = useState<ProductSubscription[]>([]);
108
95
  const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);
109
96
  const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();
110
- const [promotedProductIdIOS] = useState<string>();
111
97
  const [activeSubscriptions, setActiveSubscriptions] = useState<
112
98
  ActiveSubscription[]
113
99
  >([]);
@@ -147,7 +133,6 @@ export function useIAP(options?: UseIapOptions): UseIap {
147
133
  const subscriptionsRef = useRef<{
148
134
  purchaseUpdate?: EventSubscription;
149
135
  purchaseError?: EventSubscription;
150
- promotedProductsIOS?: EventSubscription;
151
136
  promotedProductIOS?: EventSubscription;
152
137
  }>({});
153
138
 
@@ -157,54 +142,6 @@ export function useIAP(options?: UseIapOptions): UseIap {
157
142
  subscriptionsRefState.current = subscriptions;
158
143
  }, [subscriptions]);
159
144
 
160
- const getProductsInternal = useCallback(
161
- async (skus: string[]): Promise<void> => {
162
- try {
163
- const result = await fetchProducts({
164
- skus,
165
- type: 'in-app',
166
- });
167
- const newProducts = (result ?? []).filter(
168
- (item): item is Product => item.type === 'in-app',
169
- );
170
- setProducts((prevProducts: Product[]) =>
171
- mergeWithDuplicateCheck(
172
- prevProducts,
173
- newProducts,
174
- (product: Product) => product.id,
175
- ),
176
- );
177
- } catch (error) {
178
- console.error('Error fetching products:', error);
179
- }
180
- },
181
- [mergeWithDuplicateCheck],
182
- );
183
-
184
- const getSubscriptionsInternal = useCallback(
185
- async (skus: string[]): Promise<void> => {
186
- try {
187
- const result = await fetchProducts({
188
- skus,
189
- type: 'subs',
190
- });
191
- const newSubscriptions = (result ?? []).filter(
192
- (item): item is ProductSubscription => item.type === 'subs',
193
- );
194
- setSubscriptions((prevSubscriptions: ProductSubscription[]) =>
195
- mergeWithDuplicateCheck(
196
- prevSubscriptions,
197
- newSubscriptions,
198
- (subscription: ProductSubscription) => subscription.id,
199
- ),
200
- );
201
- } catch (error) {
202
- console.error('Error fetching subscriptions:', error);
203
- }
204
- },
205
- [mergeWithDuplicateCheck],
206
- );
207
-
208
145
  const fetchProductsInternal = useCallback(
209
146
  async (params: {
210
147
  skus: string[];
@@ -395,9 +332,11 @@ export function useIAP(options?: UseIapOptions): UseIap {
395
332
  });
396
333
 
397
334
  if (Platform.OS === 'ios') {
398
- subscriptionsRef.current.promotedProductsIOS = promotedProductListenerIOS(
335
+ // iOS promoted products listener
336
+ subscriptionsRef.current.promotedProductIOS = promotedProductListenerIOS(
399
337
  (product: Product) => {
400
338
  setPromotedProductIOS(product);
339
+
401
340
  if (optionsRef.current?.onPromotedProductIOS) {
402
341
  optionsRef.current.onPromotedProductIOS(product);
403
342
  }
@@ -410,9 +349,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
410
349
  if (!result) {
411
350
  // Clean up some listeners but leave purchaseError for potential retries
412
351
  subscriptionsRef.current.purchaseUpdate?.remove();
413
- subscriptionsRef.current.promotedProductsIOS?.remove();
414
352
  subscriptionsRef.current.purchaseUpdate = undefined;
415
- subscriptionsRef.current.promotedProductsIOS = undefined;
416
353
  return;
417
354
  }
418
355
  }, [getActiveSubscriptionsInternal, getAvailablePurchasesInternal]);
@@ -424,7 +361,6 @@ export function useIAP(options?: UseIapOptions): UseIap {
424
361
  return () => {
425
362
  currentSubscriptions.purchaseUpdate?.remove();
426
363
  currentSubscriptions.purchaseError?.remove();
427
- currentSubscriptions.promotedProductsIOS?.remove();
428
364
  currentSubscriptions.promotedProductIOS?.remove();
429
365
  // Keep connection alive across screens to avoid race conditions
430
366
  setConnected(false);
@@ -434,8 +370,6 @@ export function useIAP(options?: UseIapOptions): UseIap {
434
370
  return {
435
371
  connected,
436
372
  products,
437
- promotedProductsIOS,
438
- promotedProductIdIOS,
439
373
  subscriptions,
440
374
  finishTransaction,
441
375
  availablePurchases,
@@ -453,8 +387,6 @@ export function useIAP(options?: UseIapOptions): UseIap {
453
387
  console.warn('Failed to restore purchases:', e);
454
388
  }
455
389
  },
456
- getProducts: getProductsInternal,
457
- getSubscriptions: getSubscriptionsInternal,
458
390
  getPromotedProductIOS,
459
391
  requestPurchaseOnPromotedProductIOS,
460
392
  getActiveSubscriptions: getActiveSubscriptionsInternal,
package/src/index.ts CHANGED
@@ -443,7 +443,10 @@ export const getPromotedProductIOS: QueryField<
443
443
  }
444
444
 
445
445
  try {
446
- const nitroProduct = await IAP.instance.requestPromotedProductIOS();
446
+ const nitroProduct =
447
+ typeof IAP.instance.getPromotedProductIOS === 'function'
448
+ ? await IAP.instance.getPromotedProductIOS()
449
+ : await IAP.instance.requestPromotedProductIOS();
447
450
  if (!nitroProduct) {
448
451
  return null;
449
452
  }
@@ -638,6 +641,40 @@ export const getReceiptDataIOS: QueryField<'getReceiptDataIOS'> = async () => {
638
641
  }
639
642
  };
640
643
 
644
+ export const getReceiptIOS = async (): Promise<string> => {
645
+ if (Platform.OS !== 'ios') {
646
+ throw new Error('getReceiptIOS is only available on iOS');
647
+ }
648
+
649
+ try {
650
+ if (typeof IAP.instance.getReceiptIOS === 'function') {
651
+ return await IAP.instance.getReceiptIOS();
652
+ }
653
+ return await IAP.instance.getReceiptDataIOS();
654
+ } catch (error) {
655
+ console.error('[getReceiptIOS] Failed:', error);
656
+ const errorJson = parseErrorStringToJsonObj(error);
657
+ throw new Error(errorJson.message);
658
+ }
659
+ };
660
+
661
+ export const requestReceiptRefreshIOS = async (): Promise<string> => {
662
+ if (Platform.OS !== 'ios') {
663
+ throw new Error('requestReceiptRefreshIOS is only available on iOS');
664
+ }
665
+
666
+ try {
667
+ if (typeof IAP.instance.requestReceiptRefreshIOS === 'function') {
668
+ return await IAP.instance.requestReceiptRefreshIOS();
669
+ }
670
+ return await IAP.instance.getReceiptDataIOS();
671
+ } catch (error) {
672
+ console.error('[requestReceiptRefreshIOS] Failed:', error);
673
+ const errorJson = parseErrorStringToJsonObj(error);
674
+ throw new Error(errorJson.message);
675
+ }
676
+ };
677
+
641
678
  export const isTransactionVerifiedIOS: QueryField<
642
679
  'isTransactionVerifiedIOS'
643
680
  > = async (sku) => {
@@ -1258,13 +1295,35 @@ export const deepLinkToSubscriptions: MutationField<
1258
1295
  }
1259
1296
  if (Platform.OS === 'ios') {
1260
1297
  try {
1261
- await IAP.instance.showManageSubscriptionsIOS();
1298
+ if (typeof IAP.instance.deepLinkToSubscriptionsIOS === 'function') {
1299
+ await IAP.instance.deepLinkToSubscriptionsIOS();
1300
+ } else {
1301
+ await IAP.instance.showManageSubscriptionsIOS();
1302
+ }
1262
1303
  } catch (error) {
1263
1304
  console.warn('[deepLinkToSubscriptions] Failed on iOS:', error);
1264
1305
  }
1265
1306
  }
1266
1307
  };
1267
1308
 
1309
+ export const deepLinkToSubscriptionsIOS = async (): Promise<boolean> => {
1310
+ if (Platform.OS !== 'ios') {
1311
+ throw new Error('deepLinkToSubscriptionsIOS is only available on iOS');
1312
+ }
1313
+
1314
+ try {
1315
+ if (typeof IAP.instance.deepLinkToSubscriptionsIOS === 'function') {
1316
+ return await IAP.instance.deepLinkToSubscriptionsIOS();
1317
+ }
1318
+ await IAP.instance.showManageSubscriptionsIOS();
1319
+ return true;
1320
+ } catch (error) {
1321
+ console.error('[deepLinkToSubscriptionsIOS] Failed:', error);
1322
+ const errorJson = parseErrorStringToJsonObj(error);
1323
+ throw new Error(errorJson.message);
1324
+ }
1325
+ };
1326
+
1268
1327
  /**
1269
1328
  * iOS only - Gets the original app transaction ID if the app was purchased from the App Store
1270
1329
  * @platform iOS
@@ -369,6 +369,13 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
369
369
  */
370
370
  requestPromotedProductIOS(): Promise<NitroProduct | null>;
371
371
 
372
+ /**
373
+ * Retrieve the currently promoted product without initiating a purchase flow (iOS only)
374
+ * @returns Promise<NitroProduct | null> - The promoted product or null if none available
375
+ * @platform iOS
376
+ */
377
+ getPromotedProductIOS(): Promise<NitroProduct | null>;
378
+
372
379
  /**
373
380
  * Buy the promoted product from the App Store (iOS only)
374
381
  * @returns Promise<void>
@@ -443,6 +450,13 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
443
450
  */
444
451
  showManageSubscriptionsIOS(): Promise<NitroPurchase[]>;
445
452
 
453
+ /**
454
+ * Deep link to the native subscription management UI (iOS only)
455
+ * @returns Promise<boolean> - True if the deep link request succeeded
456
+ * @platform iOS
457
+ */
458
+ deepLinkToSubscriptionsIOS(): Promise<boolean>;
459
+
446
460
  /**
447
461
  * Check if user is eligible for intro offer (iOS only)
448
462
  * @param groupID - The subscription group ID
@@ -458,6 +472,20 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
458
472
  */
459
473
  getReceiptDataIOS(): Promise<string>;
460
474
 
475
+ /**
476
+ * Alias for getReceiptDataIOS maintained for compatibility (iOS only)
477
+ * @returns Promise<string> - Base64 encoded receipt data
478
+ * @platform iOS
479
+ */
480
+ getReceiptIOS(): Promise<string>;
481
+
482
+ /**
483
+ * Request a refreshed receipt from the App Store (iOS only)
484
+ * @returns Promise<string> - Updated Base64 encoded receipt data
485
+ * @platform iOS
486
+ */
487
+ requestReceiptRefreshIOS(): Promise<string>;
488
+
461
489
  /**
462
490
  * Check if transaction is verified (iOS only)
463
491
  * @param sku - The product SKU
package/src/types.ts CHANGED
@@ -128,9 +128,9 @@ export enum ErrorCode {
128
128
 
129
129
  export type FetchProductsResult = Product[] | ProductSubscription[] | null;
130
130
 
131
- export type IapEvent = 'promoted-product-ios' | 'purchase-error' | 'purchase-updated';
131
+ export type IapEvent = 'purchase-updated' | 'purchase-error' | 'promoted-product-ios';
132
132
 
133
- export type IapPlatform = 'android' | 'ios';
133
+ export type IapPlatform = 'ios' | 'android';
134
134
 
135
135
  export interface Mutation {
136
136
  /** Acknowledge a non-consumable purchase or subscription */
@@ -266,7 +266,7 @@ export interface ProductIOS extends ProductCommon {
266
266
  typeIOS: ProductTypeIOS;
267
267
  }
268
268
 
269
- export type ProductQueryType = 'all' | 'in-app' | 'subs';
269
+ export type ProductQueryType = 'in-app' | 'subs' | 'all';
270
270
 
271
271
  export interface ProductRequest {
272
272
  skus: string[];
@@ -327,7 +327,7 @@ export interface ProductSubscriptionIOS extends ProductCommon {
327
327
 
328
328
  export type ProductType = 'in-app' | 'subs';
329
329
 
330
- export type ProductTypeIOS = 'auto-renewable-subscription' | 'consumable' | 'non-consumable' | 'non-renewing-subscription';
330
+ export type ProductTypeIOS = 'consumable' | 'non-consumable' | 'auto-renewable-subscription' | 'non-renewing-subscription';
331
331
 
332
332
  export type Purchase = PurchaseAndroid | PurchaseIOS;
333
333
 
@@ -349,6 +349,7 @@ export interface PurchaseAndroid extends PurchaseCommon {
349
349
  quantity: number;
350
350
  signatureAndroid?: (string | null);
351
351
  transactionDate: number;
352
+ transactionId?: (string | null);
352
353
  }
353
354
 
354
355
  export interface PurchaseCommon {
@@ -399,6 +400,7 @@ export interface PurchaseIOS extends PurchaseCommon {
399
400
  storefrontCountryCodeIOS?: (string | null);
400
401
  subscriptionGroupIdIOS?: (string | null);
401
402
  transactionDate: number;
403
+ transactionId: string;
402
404
  transactionReasonIOS?: (string | null);
403
405
  webOrderLineItemIdIOS?: (string | null);
404
406
  }
@@ -428,7 +430,7 @@ export interface PurchaseOptions {
428
430
  onlyIncludeActiveItemsIOS?: (boolean | null);
429
431
  }
430
432
 
431
- export type PurchaseState = 'deferred' | 'failed' | 'pending' | 'purchased' | 'restored' | 'unknown';
433
+ export type PurchaseState = 'pending' | 'purchased' | 'failed' | 'restored' | 'deferred' | 'unknown';
432
434
 
433
435
  export interface Query {
434
436
  /** Get current StoreKit 2 entitlements (iOS 15+) */
@@ -654,7 +656,7 @@ export interface SubscriptionOfferIOS {
654
656
 
655
657
  export type SubscriptionOfferTypeIOS = 'introductory' | 'promotional';
656
658
 
657
- export type SubscriptionPeriodIOS = 'day' | 'empty' | 'month' | 'week' | 'year';
659
+ export type SubscriptionPeriodIOS = 'day' | 'week' | 'month' | 'year' | 'empty';
658
660
 
659
661
  export interface SubscriptionPeriodValueIOS {
660
662
  unit: SubscriptionPeriodIOS;