react-native-iap 14.4.10 → 14.4.12

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 (94) hide show
  1. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +195 -12
  2. package/ios/HybridRnIap.swift +115 -2
  3. package/ios/RnIapHelper.swift +32 -1
  4. package/lib/module/hooks/useIAP.js +24 -3
  5. package/lib/module/hooks/useIAP.js.map +1 -1
  6. package/lib/module/index.js +275 -2
  7. package/lib/module/index.js.map +1 -1
  8. package/lib/module/types.js +18 -0
  9. package/lib/module/types.js.map +1 -1
  10. package/lib/module/utils/type-bridge.js +39 -1
  11. package/lib/module/utils/type-bridge.js.map +1 -1
  12. package/lib/typescript/plugin/src/withIAP.d.ts +27 -0
  13. package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
  14. package/lib/typescript/src/hooks/useIAP.d.ts +6 -1
  15. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  16. package/lib/typescript/src/index.d.ts +133 -0
  17. package/lib/typescript/src/index.d.ts.map +1 -1
  18. package/lib/typescript/src/specs/RnIap.nitro.d.ts +101 -2
  19. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  20. package/lib/typescript/src/types.d.ts +116 -13
  21. package/lib/typescript/src/types.d.ts.map +1 -1
  22. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  23. package/nitrogen/generated/android/NitroIapOnLoad.cpp +2 -0
  24. package/nitrogen/generated/android/c++/JAlternativeBillingModeAndroid.hpp +62 -0
  25. package/nitrogen/generated/android/c++/JExternalPurchaseLinkResultIOS.hpp +58 -0
  26. package/nitrogen/generated/android/c++/JExternalPurchaseNoticeAction.hpp +59 -0
  27. package/nitrogen/generated/android/c++/JExternalPurchaseNoticeResultIOS.hpp +60 -0
  28. package/nitrogen/generated/android/c++/JFunc_void_NitroProduct.hpp +2 -0
  29. package/nitrogen/generated/android/c++/JFunc_void_UserChoiceBillingDetails.hpp +78 -0
  30. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +136 -3
  31. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +9 -1
  32. package/nitrogen/generated/android/c++/JInitConnectionConfig.hpp +55 -0
  33. package/nitrogen/generated/android/c++/JNitroOneTimePurchaseOfferDetail.hpp +61 -0
  34. package/nitrogen/generated/android/c++/JNitroProduct.hpp +16 -2
  35. package/nitrogen/generated/android/c++/JNitroPurchase.hpp +74 -2
  36. package/nitrogen/generated/android/c++/JPurchaseAndroid.hpp +4 -0
  37. package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +4 -0
  38. package/nitrogen/generated/android/c++/JUserChoiceBillingDetails.hpp +75 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AlternativeBillingModeAndroid.kt +22 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/ExternalPurchaseLinkResultIOS.kt +32 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/ExternalPurchaseNoticeAction.kt +21 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/ExternalPurchaseNoticeResultIOS.kt +32 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Func_void_UserChoiceBillingDetails.kt +81 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +43 -1
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/InitConnectionConfig.kt +29 -0
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroOneTimePurchaseOfferDetail.kt +35 -0
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroProduct.kt +10 -1
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchase.kt +55 -1
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseAndroid.kt +3 -0
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +3 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/UserChoiceBillingDetails.kt +32 -0
  52. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.cpp +24 -0
  53. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +174 -0
  54. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +21 -0
  55. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +84 -3
  56. package/nitrogen/generated/ios/swift/AlternativeBillingModeAndroid.swift +44 -0
  57. package/nitrogen/generated/ios/swift/ExternalPurchaseLinkResultIOS.swift +65 -0
  58. package/nitrogen/generated/ios/swift/ExternalPurchaseNoticeAction.swift +40 -0
  59. package/nitrogen/generated/ios/swift/ExternalPurchaseNoticeResultIOS.swift +65 -0
  60. package/nitrogen/generated/ios/swift/Func_void_ExternalPurchaseLinkResultIOS.swift +47 -0
  61. package/nitrogen/generated/ios/swift/Func_void_ExternalPurchaseNoticeResultIOS.swift +47 -0
  62. package/nitrogen/generated/ios/swift/Func_void_UserChoiceBillingDetails.swift +47 -0
  63. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +9 -1
  64. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +168 -2
  65. package/nitrogen/generated/ios/swift/InitConnectionConfig.swift +47 -0
  66. package/nitrogen/generated/ios/swift/NitroOneTimePurchaseOfferDetail.swift +57 -0
  67. package/nitrogen/generated/ios/swift/NitroProduct.swift +91 -1
  68. package/nitrogen/generated/ios/swift/NitroPurchase.swift +520 -1
  69. package/nitrogen/generated/ios/swift/PurchaseAndroid.swift +31 -1
  70. package/nitrogen/generated/ios/swift/PurchaseIOS.swift +31 -1
  71. package/nitrogen/generated/ios/swift/UserChoiceBillingDetails.swift +58 -0
  72. package/nitrogen/generated/shared/c++/AlternativeBillingModeAndroid.hpp +80 -0
  73. package/nitrogen/generated/shared/c++/ExternalPurchaseLinkResultIOS.hpp +72 -0
  74. package/nitrogen/generated/shared/c++/ExternalPurchaseNoticeAction.hpp +76 -0
  75. package/nitrogen/generated/shared/c++/ExternalPurchaseNoticeResultIOS.hpp +74 -0
  76. package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +8 -0
  77. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +22 -2
  78. package/nitrogen/generated/shared/c++/InitConnectionConfig.hpp +69 -0
  79. package/nitrogen/generated/shared/c++/NitroOneTimePurchaseOfferDetail.hpp +75 -0
  80. package/nitrogen/generated/shared/c++/NitroProduct.hpp +17 -2
  81. package/nitrogen/generated/shared/c++/NitroPurchase.hpp +74 -2
  82. package/nitrogen/generated/shared/c++/PurchaseAndroid.hpp +5 -1
  83. package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +5 -1
  84. package/nitrogen/generated/shared/c++/UserChoiceBillingDetails.hpp +72 -0
  85. package/openiap-versions.json +3 -3
  86. package/package.json +3 -3
  87. package/plugin/build/withIAP.d.ts +27 -0
  88. package/plugin/build/withIAP.js +91 -1
  89. package/plugin/src/withIAP.ts +162 -0
  90. package/src/hooks/useIAP.ts +47 -1
  91. package/src/index.ts +313 -2
  92. package/src/specs/RnIap.nitro.ts +131 -1
  93. package/src/types.ts +124 -13
  94. package/src/utils/type-bridge.ts +76 -16
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = exports.modifyProjectBuildGradle = void 0;
3
+ exports.default = exports.withIosAlternativeBilling = exports.modifyProjectBuildGradle = void 0;
4
4
  const config_plugins_1 = require("expo/config-plugins");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_path_1 = require("node:path");
@@ -115,6 +115,92 @@ const withIapAndroid = (config) => {
115
115
  });
116
116
  return config;
117
117
  };
118
+ /** Add external purchase entitlements and Info.plist configuration */
119
+ const withIosAlternativeBilling = (config, options) => {
120
+ if (!options || !options.countries || options.countries.length === 0) {
121
+ return config;
122
+ }
123
+ // Add entitlements
124
+ config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
125
+ // Always add basic external purchase entitlement when countries are specified
126
+ config.modResults['com.apple.developer.storekit.external-purchase'] = true;
127
+ if (!hasLoggedPluginExecution) {
128
+ console.log('✅ Added com.apple.developer.storekit.external-purchase to entitlements');
129
+ }
130
+ // Add external purchase link entitlement if enabled
131
+ if (options.enableExternalPurchaseLink) {
132
+ config.modResults['com.apple.developer.storekit.external-purchase-link'] =
133
+ true;
134
+ if (!hasLoggedPluginExecution) {
135
+ console.log('✅ Added com.apple.developer.storekit.external-purchase-link to entitlements');
136
+ }
137
+ }
138
+ // Add streaming entitlement if enabled
139
+ if (options.enableExternalPurchaseLinkStreaming) {
140
+ config.modResults['com.apple.developer.storekit.external-purchase-link-streaming'] = true;
141
+ if (!hasLoggedPluginExecution) {
142
+ console.log('✅ Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements');
143
+ }
144
+ }
145
+ return config;
146
+ });
147
+ // Add Info.plist configuration
148
+ config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
149
+ const plist = config.modResults;
150
+ // Helper function to normalize country codes to uppercase
151
+ const normalize = (code) => code.trim().toUpperCase();
152
+ // 1. SKExternalPurchase (Required)
153
+ const normalizedCountries = options.countries?.map(normalize);
154
+ plist.SKExternalPurchase = normalizedCountries;
155
+ if (!hasLoggedPluginExecution) {
156
+ console.log(`✅ Added SKExternalPurchase with countries: ${normalizedCountries?.join(', ')}`);
157
+ }
158
+ // 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
159
+ if (options.links && Object.keys(options.links).length > 0) {
160
+ plist.SKExternalPurchaseLink = Object.fromEntries(Object.entries(options.links).map(([code, url]) => [
161
+ normalize(code),
162
+ url,
163
+ ]));
164
+ if (!hasLoggedPluginExecution) {
165
+ console.log(`✅ Added SKExternalPurchaseLink for ${Object.keys(options.links).length} countries`);
166
+ }
167
+ }
168
+ // 3. SKExternalPurchaseMultiLink (iOS 17.5+)
169
+ if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
170
+ plist.SKExternalPurchaseMultiLink = Object.fromEntries(Object.entries(options.multiLinks).map(([code, urls]) => [
171
+ normalize(code),
172
+ urls,
173
+ ]));
174
+ if (!hasLoggedPluginExecution) {
175
+ console.log(`✅ Added SKExternalPurchaseMultiLink for ${Object.keys(options.multiLinks).length} countries`);
176
+ }
177
+ }
178
+ // 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
179
+ if (options.customLinkRegions && options.customLinkRegions.length > 0) {
180
+ plist.SKExternalPurchaseCustomLinkRegions =
181
+ options.customLinkRegions.map(normalize);
182
+ if (!hasLoggedPluginExecution) {
183
+ console.log(`✅ Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions
184
+ .map(normalize)
185
+ .join(', ')}`);
186
+ }
187
+ }
188
+ // 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
189
+ if (options.streamingLinkRegions &&
190
+ options.streamingLinkRegions.length > 0) {
191
+ plist.SKExternalPurchaseLinkStreamingRegions =
192
+ options.streamingLinkRegions.map(normalize);
193
+ if (!hasLoggedPluginExecution) {
194
+ console.log(`✅ Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions
195
+ .map(normalize)
196
+ .join(', ')}`);
197
+ }
198
+ }
199
+ return config;
200
+ });
201
+ return config;
202
+ };
203
+ exports.withIosAlternativeBilling = withIosAlternativeBilling;
118
204
  const withIapIosFollyWorkaround = (config, props) => {
119
205
  const newKey = props?.ios?.['with-folly-no-coroutines'];
120
206
  const oldKey = props?.ios?.['with-folly-no-couroutines'];
@@ -165,6 +251,10 @@ const withIAP = (config, props) => {
165
251
  try {
166
252
  let result = withIapAndroid(config);
167
253
  result = withIapIosFollyWorkaround(result, props);
254
+ // Add iOS alternative billing configuration if provided
255
+ if (props?.iosAlternativeBilling) {
256
+ result = withIosAlternativeBilling(result, props.iosAlternativeBilling);
257
+ }
168
258
  // Set flag after first execution to prevent duplicate logs
169
259
  hasLoggedPluginExecution = true;
170
260
  return result;
@@ -4,6 +4,8 @@ import {
4
4
  withAndroidManifest,
5
5
  withAppBuildGradle,
6
6
  withPodfile,
7
+ withEntitlementsPlist,
8
+ withInfoPlist,
7
9
  } from 'expo/config-plugins';
8
10
  import type {ConfigPlugin} from 'expo/config-plugins';
9
11
  import type {ExpoConfig} from '@expo/config-types';
@@ -165,6 +167,154 @@ const withIapAndroid: ConfigPlugin = (config) => {
165
167
  return config;
166
168
  };
167
169
 
170
+ export interface IosAlternativeBillingConfig {
171
+ /** Country codes where external purchases are supported (ISO 3166-1 alpha-2) */
172
+ countries?: string[];
173
+ /** External purchase URLs per country (iOS 15.4+) */
174
+ links?: Record<string, string>;
175
+ /** Multiple external purchase URLs per country (iOS 17.5+, up to 5 per country) */
176
+ multiLinks?: Record<string, string[]>;
177
+ /** Custom link regions (iOS 18.1+) */
178
+ customLinkRegions?: string[];
179
+ /** Streaming link regions for music apps (iOS 18.2+) */
180
+ streamingLinkRegions?: string[];
181
+ /** Enable external purchase link entitlement */
182
+ enableExternalPurchaseLink?: boolean;
183
+ /** Enable external purchase link streaming entitlement (music apps only) */
184
+ enableExternalPurchaseLinkStreaming?: boolean;
185
+ }
186
+
187
+ /** Add external purchase entitlements and Info.plist configuration */
188
+ const withIosAlternativeBilling: ConfigPlugin<
189
+ IosAlternativeBillingConfig | undefined
190
+ > = (config, options) => {
191
+ if (!options || !options.countries || options.countries.length === 0) {
192
+ return config;
193
+ }
194
+
195
+ // Add entitlements
196
+ config = withEntitlementsPlist(config, (config) => {
197
+ // Always add basic external purchase entitlement when countries are specified
198
+ config.modResults['com.apple.developer.storekit.external-purchase'] = true;
199
+ if (!hasLoggedPluginExecution) {
200
+ console.log(
201
+ '✅ Added com.apple.developer.storekit.external-purchase to entitlements',
202
+ );
203
+ }
204
+
205
+ // Add external purchase link entitlement if enabled
206
+ if (options.enableExternalPurchaseLink) {
207
+ config.modResults['com.apple.developer.storekit.external-purchase-link'] =
208
+ true;
209
+ if (!hasLoggedPluginExecution) {
210
+ console.log(
211
+ '✅ Added com.apple.developer.storekit.external-purchase-link to entitlements',
212
+ );
213
+ }
214
+ }
215
+
216
+ // Add streaming entitlement if enabled
217
+ if (options.enableExternalPurchaseLinkStreaming) {
218
+ config.modResults[
219
+ 'com.apple.developer.storekit.external-purchase-link-streaming'
220
+ ] = true;
221
+ if (!hasLoggedPluginExecution) {
222
+ console.log(
223
+ '✅ Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements',
224
+ );
225
+ }
226
+ }
227
+
228
+ return config;
229
+ });
230
+
231
+ // Add Info.plist configuration
232
+ config = withInfoPlist(config, (config) => {
233
+ const plist = config.modResults;
234
+
235
+ // Helper function to normalize country codes to uppercase
236
+ const normalize = (code: string) => code.trim().toUpperCase();
237
+
238
+ // 1. SKExternalPurchase (Required)
239
+ const normalizedCountries = options.countries?.map(normalize);
240
+ plist.SKExternalPurchase = normalizedCountries;
241
+ if (!hasLoggedPluginExecution) {
242
+ console.log(
243
+ `✅ Added SKExternalPurchase with countries: ${normalizedCountries?.join(
244
+ ', ',
245
+ )}`,
246
+ );
247
+ }
248
+
249
+ // 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
250
+ if (options.links && Object.keys(options.links).length > 0) {
251
+ plist.SKExternalPurchaseLink = Object.fromEntries(
252
+ Object.entries(options.links).map(([code, url]) => [
253
+ normalize(code),
254
+ url,
255
+ ]),
256
+ );
257
+ if (!hasLoggedPluginExecution) {
258
+ console.log(
259
+ `✅ Added SKExternalPurchaseLink for ${
260
+ Object.keys(options.links).length
261
+ } countries`,
262
+ );
263
+ }
264
+ }
265
+
266
+ // 3. SKExternalPurchaseMultiLink (iOS 17.5+)
267
+ if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
268
+ plist.SKExternalPurchaseMultiLink = Object.fromEntries(
269
+ Object.entries(options.multiLinks).map(([code, urls]) => [
270
+ normalize(code),
271
+ urls,
272
+ ]),
273
+ );
274
+ if (!hasLoggedPluginExecution) {
275
+ console.log(
276
+ `✅ Added SKExternalPurchaseMultiLink for ${
277
+ Object.keys(options.multiLinks).length
278
+ } countries`,
279
+ );
280
+ }
281
+ }
282
+
283
+ // 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
284
+ if (options.customLinkRegions && options.customLinkRegions.length > 0) {
285
+ plist.SKExternalPurchaseCustomLinkRegions =
286
+ options.customLinkRegions.map(normalize);
287
+ if (!hasLoggedPluginExecution) {
288
+ console.log(
289
+ `✅ Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions
290
+ .map(normalize)
291
+ .join(', ')}`,
292
+ );
293
+ }
294
+ }
295
+
296
+ // 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
297
+ if (
298
+ options.streamingLinkRegions &&
299
+ options.streamingLinkRegions.length > 0
300
+ ) {
301
+ plist.SKExternalPurchaseLinkStreamingRegions =
302
+ options.streamingLinkRegions.map(normalize);
303
+ if (!hasLoggedPluginExecution) {
304
+ console.log(
305
+ `✅ Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions
306
+ .map(normalize)
307
+ .join(', ')}`,
308
+ );
309
+ }
310
+ }
311
+
312
+ return config;
313
+ });
314
+
315
+ return config;
316
+ };
317
+
168
318
  type IapPluginProps = {
169
319
  ios?: {
170
320
  // Enable to inject Folly coroutine-disabling macros into Podfile during prebuild
@@ -172,6 +322,13 @@ type IapPluginProps = {
172
322
  // @deprecated Use 'with-folly-no-coroutines'. Kept for backward compatibility.
173
323
  'with-folly-no-couroutines'?: boolean;
174
324
  };
325
+ /**
326
+ * iOS Alternative Billing configuration.
327
+ * Configure external purchase countries, links, and entitlements.
328
+ * Requires approval from Apple.
329
+ * @platform ios
330
+ */
331
+ iosAlternativeBilling?: IosAlternativeBillingConfig;
175
332
  };
176
333
 
177
334
  const withIapIosFollyWorkaround: ConfigPlugin<IapPluginProps | undefined> = (
@@ -236,6 +393,10 @@ const withIAP: ConfigPlugin<IapPluginProps | undefined> = (config, props) => {
236
393
  try {
237
394
  let result = withIapAndroid(config);
238
395
  result = withIapIosFollyWorkaround(result, props);
396
+ // Add iOS alternative billing configuration if provided
397
+ if (props?.iosAlternativeBilling) {
398
+ result = withIosAlternativeBilling(result, props.iosAlternativeBilling);
399
+ }
239
400
  // Set flag after first execution to prevent duplicate logs
240
401
  hasLoggedPluginExecution = true;
241
402
  return result;
@@ -270,4 +431,5 @@ const pluginExport: IapPluginCallable = ((
270
431
  props?: IapPluginProps,
271
432
  ) => _wrapped(config, props)) as unknown as IapPluginCallable;
272
433
 
434
+ export {withIosAlternativeBilling};
273
435
  export {pluginExport as default};
@@ -19,6 +19,10 @@ import {
19
19
  restorePurchases as restorePurchasesTopLevel,
20
20
  getPromotedProductIOS,
21
21
  requestPurchaseOnPromotedProductIOS,
22
+ checkAlternativeBillingAvailabilityAndroid,
23
+ showAlternativeBillingDialogAndroid,
24
+ createAlternativeBillingTokenAndroid,
25
+ userChoiceBillingListenerAndroid,
22
26
  } from '../';
23
27
 
24
28
  // Types
@@ -27,6 +31,8 @@ import type {
27
31
  ProductQueryType,
28
32
  RequestPurchaseProps,
29
33
  RequestPurchaseResult,
34
+ AlternativeBillingModeAndroid,
35
+ UserChoiceBillingDetails,
30
36
  } from '../types';
31
37
  import type {
32
38
  ActiveSubscription,
@@ -75,12 +81,20 @@ type UseIap = {
75
81
  subscriptionIds?: string[],
76
82
  ) => Promise<ActiveSubscription[]>;
77
83
  hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
84
+ // Alternative billing (Android)
85
+ checkAlternativeBillingAvailabilityAndroid?: () => Promise<boolean>;
86
+ showAlternativeBillingDialogAndroid?: () => Promise<boolean>;
87
+ createAlternativeBillingTokenAndroid?: (
88
+ sku?: string,
89
+ ) => Promise<string | null>;
78
90
  };
79
91
 
80
92
  export interface UseIapOptions {
81
93
  onPurchaseSuccess?: (purchase: Purchase) => void;
82
94
  onPurchaseError?: (error: PurchaseError) => void;
83
95
  onPromotedProductIOS?: (product: Product) => void;
96
+ onUserChoiceBillingAndroid?: (details: UserChoiceBillingDetails) => void;
97
+ alternativeBillingModeAndroid?: AlternativeBillingModeAndroid;
84
98
  }
85
99
 
86
100
  /**
@@ -133,6 +147,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
133
147
  purchaseUpdate?: EventSubscription;
134
148
  purchaseError?: EventSubscription;
135
149
  promotedProductIOS?: EventSubscription;
150
+ userChoiceBillingAndroid?: EventSubscription;
136
151
  }>({});
137
152
 
138
153
  const subscriptionsRefState = useRef<ProductSubscription[]>([]);
@@ -346,7 +361,29 @@ export function useIAP(options?: UseIapOptions): UseIap {
346
361
  );
347
362
  }
348
363
 
349
- const result = await initConnection();
364
+ // Add user choice billing listener for Android (if provided)
365
+ if (
366
+ Platform.OS === 'android' &&
367
+ optionsRef.current?.onUserChoiceBillingAndroid
368
+ ) {
369
+ subscriptionsRef.current.userChoiceBillingAndroid =
370
+ userChoiceBillingListenerAndroid((details) => {
371
+ if (optionsRef.current?.onUserChoiceBillingAndroid) {
372
+ optionsRef.current.onUserChoiceBillingAndroid(details);
373
+ }
374
+ });
375
+ }
376
+
377
+ // Initialize connection with config
378
+ const config =
379
+ Platform.OS === 'android' &&
380
+ optionsRef.current?.alternativeBillingModeAndroid
381
+ ? {
382
+ alternativeBillingModeAndroid:
383
+ optionsRef.current.alternativeBillingModeAndroid,
384
+ }
385
+ : undefined;
386
+ const result = await initConnection(config);
350
387
  setConnected(result);
351
388
  if (!result) {
352
389
  // Clean up some listeners but leave purchaseError for potential retries
@@ -364,6 +401,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
364
401
  currentSubscriptions.purchaseUpdate?.remove();
365
402
  currentSubscriptions.purchaseError?.remove();
366
403
  currentSubscriptions.promotedProductIOS?.remove();
404
+ currentSubscriptions.userChoiceBillingAndroid?.remove();
367
405
  // Keep connection alive across screens to avoid race conditions
368
406
  setConnected(false);
369
407
  };
@@ -393,5 +431,13 @@ export function useIAP(options?: UseIapOptions): UseIap {
393
431
  requestPurchaseOnPromotedProductIOS,
394
432
  getActiveSubscriptions: getActiveSubscriptionsInternal,
395
433
  hasActiveSubscriptions: hasActiveSubscriptionsInternal,
434
+ // Alternative billing (Android only)
435
+ ...(Platform.OS === 'android'
436
+ ? {
437
+ checkAlternativeBillingAvailabilityAndroid,
438
+ showAlternativeBillingDialogAndroid,
439
+ createAlternativeBillingTokenAndroid,
440
+ }
441
+ : {}),
396
442
  };
397
443
  }