react-native-iap 14.2.3 → 14.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 (29) hide show
  1. package/README.md +2 -6
  2. package/android/build.gradle +4 -5
  3. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +327 -547
  4. package/ios/HybridRnIap.swift +16 -0
  5. package/lib/module/index.js +43 -0
  6. package/lib/module/index.js.map +1 -1
  7. package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
  8. package/lib/typescript/src/index.d.ts +15 -0
  9. package/lib/typescript/src/index.d.ts.map +1 -1
  10. package/lib/typescript/src/specs/RnIap.nitro.d.ts +17 -0
  11. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  12. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +35 -0
  13. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +2 -0
  14. package/nitrogen/generated/android/c++/JNitroDeepLinkOptionsAndroid.hpp +58 -0
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +8 -0
  16. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroDeepLinkOptionsAndroid.kt +32 -0
  17. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
  18. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +19 -0
  19. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +2 -0
  20. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +38 -0
  21. package/nitrogen/generated/ios/swift/NitroDeepLinkOptionsAndroid.swift +84 -0
  22. package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +2 -0
  23. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +5 -0
  24. package/nitrogen/generated/shared/c++/NitroDeepLinkOptionsAndroid.hpp +72 -0
  25. package/package.json +1 -1
  26. package/plugin/build/withIAP.js +21 -18
  27. package/plugin/src/withIAP.ts +31 -23
  28. package/src/index.ts +48 -0
  29. package/src/specs/RnIap.nitro.ts +22 -0
@@ -0,0 +1,84 @@
1
+ ///
2
+ /// NitroDeepLinkOptionsAndroid.swift
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ import NitroModules
9
+
10
+ /**
11
+ * Represents an instance of `NitroDeepLinkOptionsAndroid`, backed by a C++ struct.
12
+ */
13
+ public typealias NitroDeepLinkOptionsAndroid = margelo.nitro.iap.NitroDeepLinkOptionsAndroid
14
+
15
+ public extension NitroDeepLinkOptionsAndroid {
16
+ private typealias bridge = margelo.nitro.iap.bridge.swift
17
+
18
+ /**
19
+ * Create a new instance of `NitroDeepLinkOptionsAndroid`.
20
+ */
21
+ init(skuAndroid: String?, packageNameAndroid: String?) {
22
+ self.init({ () -> bridge.std__optional_std__string_ in
23
+ if let __unwrappedValue = skuAndroid {
24
+ return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
25
+ } else {
26
+ return .init()
27
+ }
28
+ }(), { () -> bridge.std__optional_std__string_ in
29
+ if let __unwrappedValue = packageNameAndroid {
30
+ return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
31
+ } else {
32
+ return .init()
33
+ }
34
+ }())
35
+ }
36
+
37
+ var skuAndroid: String? {
38
+ @inline(__always)
39
+ get {
40
+ return { () -> String? in
41
+ if bridge.has_value_std__optional_std__string_(self.__skuAndroid) {
42
+ let __unwrapped = bridge.get_std__optional_std__string_(self.__skuAndroid)
43
+ return String(__unwrapped)
44
+ } else {
45
+ return nil
46
+ }
47
+ }()
48
+ }
49
+ @inline(__always)
50
+ set {
51
+ self.__skuAndroid = { () -> bridge.std__optional_std__string_ in
52
+ if let __unwrappedValue = newValue {
53
+ return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
54
+ } else {
55
+ return .init()
56
+ }
57
+ }()
58
+ }
59
+ }
60
+
61
+ var packageNameAndroid: String? {
62
+ @inline(__always)
63
+ get {
64
+ return { () -> String? in
65
+ if bridge.has_value_std__optional_std__string_(self.__packageNameAndroid) {
66
+ let __unwrapped = bridge.get_std__optional_std__string_(self.__packageNameAndroid)
67
+ return String(__unwrapped)
68
+ } else {
69
+ return nil
70
+ }
71
+ }()
72
+ }
73
+ @inline(__always)
74
+ set {
75
+ self.__packageNameAndroid = { () -> bridge.std__optional_std__string_ in
76
+ if let __unwrappedValue = newValue {
77
+ return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
78
+ } else {
79
+ return .init()
80
+ }
81
+ }()
82
+ }
83
+ }
84
+ }
@@ -44,6 +44,8 @@ namespace margelo::nitro::iap {
44
44
  prototype.registerHybridMethod("isTransactionVerifiedIOS", &HybridRnIapSpec::isTransactionVerifiedIOS);
45
45
  prototype.registerHybridMethod("getTransactionJwsIOS", &HybridRnIapSpec::getTransactionJwsIOS);
46
46
  prototype.registerHybridMethod("validateReceipt", &HybridRnIapSpec::validateReceipt);
47
+ prototype.registerHybridMethod("getStorefrontAndroid", &HybridRnIapSpec::getStorefrontAndroid);
48
+ prototype.registerHybridMethod("deepLinkToSubscriptionsAndroid", &HybridRnIapSpec::deepLinkToSubscriptionsAndroid);
47
49
  });
48
50
  }
49
51
 
@@ -33,6 +33,8 @@ namespace margelo::nitro::iap { struct NitroReceiptValidationResultIOS; }
33
33
  namespace margelo::nitro::iap { struct NitroReceiptValidationResultAndroid; }
34
34
  // Forward declaration of `NitroReceiptValidationParams` to properly resolve imports.
35
35
  namespace margelo::nitro::iap { struct NitroReceiptValidationParams; }
36
+ // Forward declaration of `NitroDeepLinkOptionsAndroid` to properly resolve imports.
37
+ namespace margelo::nitro::iap { struct NitroDeepLinkOptionsAndroid; }
36
38
 
37
39
  #include <NitroModules/Promise.hpp>
38
40
  #include "NitroProduct.hpp"
@@ -50,6 +52,7 @@ namespace margelo::nitro::iap { struct NitroReceiptValidationParams; }
50
52
  #include "NitroReceiptValidationResultIOS.hpp"
51
53
  #include "NitroReceiptValidationResultAndroid.hpp"
52
54
  #include "NitroReceiptValidationParams.hpp"
55
+ #include "NitroDeepLinkOptionsAndroid.hpp"
53
56
 
54
57
  namespace margelo::nitro::iap {
55
58
 
@@ -112,6 +115,8 @@ namespace margelo::nitro::iap {
112
115
  virtual std::shared_ptr<Promise<bool>> isTransactionVerifiedIOS(const std::string& sku) = 0;
113
116
  virtual std::shared_ptr<Promise<std::optional<std::string>>> getTransactionJwsIOS(const std::string& sku) = 0;
114
117
  virtual std::shared_ptr<Promise<std::variant<NitroReceiptValidationResultIOS, NitroReceiptValidationResultAndroid>>> validateReceipt(const NitroReceiptValidationParams& params) = 0;
118
+ virtual std::shared_ptr<Promise<std::string>> getStorefrontAndroid() = 0;
119
+ virtual std::shared_ptr<Promise<void>> deepLinkToSubscriptionsAndroid(const NitroDeepLinkOptionsAndroid& options) = 0;
115
120
 
116
121
  protected:
117
122
  // Hybrid Setup
@@ -0,0 +1,72 @@
1
+ ///
2
+ /// NitroDeepLinkOptionsAndroid.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
11
+ #include <NitroModules/JSIConverter.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
16
+ #include <NitroModules/NitroDefines.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+
21
+
22
+
23
+ #include <string>
24
+ #include <optional>
25
+
26
+ namespace margelo::nitro::iap {
27
+
28
+ /**
29
+ * A struct which can be represented as a JavaScript object (NitroDeepLinkOptionsAndroid).
30
+ */
31
+ struct NitroDeepLinkOptionsAndroid {
32
+ public:
33
+ std::optional<std::string> skuAndroid SWIFT_PRIVATE;
34
+ std::optional<std::string> packageNameAndroid SWIFT_PRIVATE;
35
+
36
+ public:
37
+ NitroDeepLinkOptionsAndroid() = default;
38
+ explicit NitroDeepLinkOptionsAndroid(std::optional<std::string> skuAndroid, std::optional<std::string> packageNameAndroid): skuAndroid(skuAndroid), packageNameAndroid(packageNameAndroid) {}
39
+ };
40
+
41
+ } // namespace margelo::nitro::iap
42
+
43
+ namespace margelo::nitro {
44
+
45
+ // C++ NitroDeepLinkOptionsAndroid <> JS NitroDeepLinkOptionsAndroid (object)
46
+ template <>
47
+ struct JSIConverter<margelo::nitro::iap::NitroDeepLinkOptionsAndroid> final {
48
+ static inline margelo::nitro::iap::NitroDeepLinkOptionsAndroid fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
49
+ jsi::Object obj = arg.asObject(runtime);
50
+ return margelo::nitro::iap::NitroDeepLinkOptionsAndroid(
51
+ JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "skuAndroid")),
52
+ JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "packageNameAndroid"))
53
+ );
54
+ }
55
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::iap::NitroDeepLinkOptionsAndroid& arg) {
56
+ jsi::Object obj(runtime);
57
+ obj.setProperty(runtime, "skuAndroid", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.skuAndroid));
58
+ obj.setProperty(runtime, "packageNameAndroid", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.packageNameAndroid));
59
+ return obj;
60
+ }
61
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
62
+ if (!value.isObject()) {
63
+ return false;
64
+ }
65
+ jsi::Object obj = value.getObject(runtime);
66
+ if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "skuAndroid"))) return false;
67
+ if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "packageNameAndroid"))) return false;
68
+ return true;
69
+ }
70
+ };
71
+
72
+ } // namespace margelo::nitro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-iap",
3
- "version": "14.2.3",
3
+ "version": "14.3.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",
@@ -18,12 +18,11 @@ const addLineToGradle = (content, anchor, lineToAdd, offset = 1) => {
18
18
  return lines.join('\n');
19
19
  };
20
20
  const modifyProjectBuildGradle = (gradle) => {
21
- // Add supportLibVersion to project build.gradle if not present
21
+ // Keep backward-compatible behavior: add supportLibVersion inside ext { } if missing
22
22
  if (!gradle.includes('supportLibVersion')) {
23
23
  const lines = gradle.split('\n');
24
24
  const extIndex = lines.findIndex((line) => line.trim() === 'ext {');
25
25
  if (extIndex !== -1) {
26
- // Insert supportLibVersion right after 'ext {' with proper indentation
27
26
  lines.splice(extIndex + 1, 0, 'supportLibVersion = "28.0.0"');
28
27
  return lines.join('\n');
29
28
  }
@@ -31,28 +30,32 @@ const modifyProjectBuildGradle = (gradle) => {
31
30
  return gradle;
32
31
  };
33
32
  exports.modifyProjectBuildGradle = modifyProjectBuildGradle;
33
+ const OPENIAP_COORD = 'io.github.hyochan.openiap:openiap-google';
34
+ const OPENIAP_VERSION = '1.1.0';
34
35
  const modifyAppBuildGradle = (gradle) => {
35
36
  let modified = gradle;
36
- // Add billing library dependencies to app-level build.gradle
37
- const billingDep = ` implementation "com.android.billingclient:billing-ktx:8.0.0"`;
38
- const gmsDep = ` implementation "com.google.android.gms:play-services-base:18.1.0"`;
39
- let hasAddedDependency = false;
40
- if (!modified.includes(billingDep)) {
41
- modified = addLineToGradle(modified, /dependencies\s*{/, billingDep);
42
- hasAddedDependency = true;
43
- }
44
- if (!modified.includes(gmsDep)) {
45
- modified = addLineToGradle(modified, /dependencies\s*{/, gmsDep, 1);
46
- hasAddedDependency = true;
47
- }
48
- // Log only once and only if we actually added dependencies
49
- if (hasAddedDependency && !hasLoggedPluginExecution) {
50
- console.log('🛠️ react-native-iap: Added billing dependencies to build.gradle');
37
+ // Replace legacy Billing/GMS instructions with OpenIAP Google library
38
+ // Remove any old billingclient or play-services-base lines we may have added previously
39
+ modified = modified
40
+ .replace(/^[ \t]*(implementation|api)[ \t]+["']com\.android\.billingclient:billing-ktx:[^"']+["'][ \t]*$/gim, '')
41
+ .replace(/^[ \t]*(implementation|api)[ \t]+["']com\.google\.android\.gms:play-services-base:[^"']+["'][ \t]*$/gim, '')
42
+ .replace(/\n{3,}/g, '\n\n');
43
+ const openiapDep = ` implementation "${OPENIAP_COORD}:${OPENIAP_VERSION}"`;
44
+ if (!modified.includes(OPENIAP_COORD)) {
45
+ if (!/dependencies\s*{/.test(modified)) {
46
+ modified += `\n\ndependencies {\n${openiapDep}\n}\n`;
47
+ }
48
+ else {
49
+ modified = addLineToGradle(modified, /dependencies\s*{/, openiapDep);
50
+ }
51
+ if (!hasLoggedPluginExecution) {
52
+ console.log(`🛠️ react-native-iap: Added OpenIAP (${OPENIAP_VERSION}) to build.gradle`);
53
+ }
51
54
  }
52
55
  return modified;
53
56
  };
54
57
  const withIapAndroid = (config) => {
55
- // Add IAP dependencies to app build.gradle
58
+ // Add OpenIAP dependency to app build.gradle
56
59
  config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
57
60
  config.modResults.contents = modifyAppBuildGradle(config.modResults.contents);
58
61
  return config;
@@ -31,12 +31,11 @@ const addLineToGradle = (
31
31
  };
32
32
 
33
33
  export const modifyProjectBuildGradle = (gradle: string): string => {
34
- // Add supportLibVersion to project build.gradle if not present
34
+ // Keep backward-compatible behavior: add supportLibVersion inside ext { } if missing
35
35
  if (!gradle.includes('supportLibVersion')) {
36
36
  const lines = gradle.split('\n');
37
37
  const extIndex = lines.findIndex((line) => line.trim() === 'ext {');
38
38
  if (extIndex !== -1) {
39
- // Insert supportLibVersion right after 'ext {' with proper indentation
40
39
  lines.splice(extIndex + 1, 0, 'supportLibVersion = "28.0.0"');
41
40
  return lines.join('\n');
42
41
  }
@@ -44,36 +43,45 @@ export const modifyProjectBuildGradle = (gradle: string): string => {
44
43
  return gradle;
45
44
  };
46
45
 
46
+ const OPENIAP_COORD = 'io.github.hyochan.openiap:openiap-google';
47
+ const OPENIAP_VERSION = '1.1.0';
48
+
47
49
  const modifyAppBuildGradle = (gradle: string): string => {
48
50
  let modified = gradle;
49
51
 
50
- // Add billing library dependencies to app-level build.gradle
51
- const billingDep = ` implementation "com.android.billingclient:billing-ktx:8.0.0"`;
52
- const gmsDep = ` implementation "com.google.android.gms:play-services-base:18.1.0"`;
53
-
54
- let hasAddedDependency = false;
55
-
56
- if (!modified.includes(billingDep)) {
57
- modified = addLineToGradle(modified, /dependencies\s*{/, billingDep);
58
- hasAddedDependency = true;
59
- }
60
- if (!modified.includes(gmsDep)) {
61
- modified = addLineToGradle(modified, /dependencies\s*{/, gmsDep, 1);
62
- hasAddedDependency = true;
63
- }
64
-
65
- // Log only once and only if we actually added dependencies
66
- if (hasAddedDependency && !hasLoggedPluginExecution) {
67
- console.log(
68
- '🛠️ react-native-iap: Added billing dependencies to build.gradle',
69
- );
52
+ // Replace legacy Billing/GMS instructions with OpenIAP Google library
53
+ // Remove any old billingclient or play-services-base lines we may have added previously
54
+ modified = modified
55
+ .replace(
56
+ /^[ \t]*(implementation|api)[ \t]+["']com\.android\.billingclient:billing-ktx:[^"']+["'][ \t]*$/gim,
57
+ '',
58
+ )
59
+ .replace(
60
+ /^[ \t]*(implementation|api)[ \t]+["']com\.google\.android\.gms:play-services-base:[^"']+["'][ \t]*$/gim,
61
+ '',
62
+ )
63
+ .replace(/\n{3,}/g, '\n\n');
64
+
65
+ const openiapDep = ` implementation "${OPENIAP_COORD}:${OPENIAP_VERSION}"`;
66
+
67
+ if (!modified.includes(OPENIAP_COORD)) {
68
+ if (!/dependencies\s*{/.test(modified)) {
69
+ modified += `\n\ndependencies {\n${openiapDep}\n}\n`;
70
+ } else {
71
+ modified = addLineToGradle(modified, /dependencies\s*{/, openiapDep);
72
+ }
73
+ if (!hasLoggedPluginExecution) {
74
+ console.log(
75
+ `🛠️ react-native-iap: Added OpenIAP (${OPENIAP_VERSION}) to build.gradle`,
76
+ );
77
+ }
70
78
  }
71
79
 
72
80
  return modified;
73
81
  };
74
82
 
75
83
  const withIapAndroid: ConfigPlugin = (config) => {
76
- // Add IAP dependencies to app build.gradle
84
+ // Add OpenIAP dependency to app build.gradle
77
85
  config = withAppBuildGradle(config, (config) => {
78
86
  config.modResults.contents = modifyAppBuildGradle(
79
87
  config.modResults.contents,
package/src/index.ts CHANGED
@@ -1181,6 +1181,54 @@ export const getStorefrontIOS = async (): Promise<string> => {
1181
1181
  }
1182
1182
  };
1183
1183
 
1184
+ /**
1185
+ * Gets the storefront country code from the underlying native store.
1186
+ * Returns a two-letter country code such as 'US', 'KR', or empty string on failure.
1187
+ *
1188
+ * Cross-platform alias aligning with expo-iap.
1189
+ */
1190
+ export const getStorefront = async (): Promise<string> => {
1191
+ if (Platform.OS === 'android') {
1192
+ try {
1193
+ // Optional since older builds may not have the method
1194
+ const result = await iap.getStorefrontAndroid?.();
1195
+ return result ?? '';
1196
+ } catch {
1197
+ return '';
1198
+ }
1199
+ }
1200
+ return getStorefrontIOS();
1201
+ };
1202
+
1203
+ /**
1204
+ * Deeplinks to native interface that allows users to manage their subscriptions
1205
+ * Cross-platform alias aligning with expo-iap
1206
+ */
1207
+ export const deepLinkToSubscriptions = async (
1208
+ options: {
1209
+ skuAndroid?: string;
1210
+ packageNameAndroid?: string;
1211
+ } = {},
1212
+ ): Promise<void> => {
1213
+ if (Platform.OS === 'android') {
1214
+ await iap.deepLinkToSubscriptionsAndroid?.({
1215
+ skuAndroid: options.skuAndroid,
1216
+ packageNameAndroid: options.packageNameAndroid,
1217
+ });
1218
+ return;
1219
+ }
1220
+ // iOS: Use manage subscriptions sheet (ignore returned purchases for deeplink parity)
1221
+ if (Platform.OS === 'ios') {
1222
+ try {
1223
+ await iap.showManageSubscriptionsIOS();
1224
+ } catch {
1225
+ // no-op
1226
+ }
1227
+ return;
1228
+ }
1229
+ return;
1230
+ };
1231
+
1184
1232
  /**
1185
1233
  * iOS only - Gets the original app transaction ID if the app was purchased from the App Store
1186
1234
  * @platform iOS
@@ -118,6 +118,14 @@ interface NitroFinishTransactionParams {
118
118
  android?: NitroFinishTransactionAndroidParams;
119
119
  }
120
120
 
121
+ /**
122
+ * Android deep link options for subscription management
123
+ */
124
+ interface NitroDeepLinkOptionsAndroid {
125
+ skuAndroid?: string;
126
+ packageNameAndroid?: string;
127
+ }
128
+
121
129
  // ╔══════════════════════════════════════════════════════════════════════════╗
122
130
  // ║ TYPES ║
123
131
  // ╚══════════════════════════════════════════════════════════════════════════╝
@@ -505,4 +513,18 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
505
513
  ): Promise<
506
514
  NitroReceiptValidationResultIOS | NitroReceiptValidationResultAndroid
507
515
  >;
516
+
517
+ /**
518
+ * Get Google Play storefront country code (Android)
519
+ * @platform Android
520
+ */
521
+ getStorefrontAndroid?(): Promise<string>;
522
+
523
+ /**
524
+ * Deep link to Play Store subscription management (Android)
525
+ * @platform Android
526
+ */
527
+ deepLinkToSubscriptionsAndroid?(
528
+ options: NitroDeepLinkOptionsAndroid,
529
+ ): Promise<void>;
508
530
  }