react-native-iap 14.0.1 → 14.1.1-rc.1

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 (55) hide show
  1. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +81 -22
  2. package/app.plugin.js +1 -1
  3. package/ios/HybridRnIap.swift +167 -11
  4. package/ios/ProductStore.swift +10 -0
  5. package/ios/reactnativeiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  6. package/ios/reactnativeiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  7. package/lib/module/helpers/subscription.js +9 -1
  8. package/lib/module/helpers/subscription.js.map +1 -1
  9. package/lib/module/hooks/useIAP.js +9 -13
  10. package/lib/module/hooks/useIAP.js.map +1 -1
  11. package/lib/module/index.js +43 -28
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/types.js +24 -16
  14. package/lib/module/types.js.map +1 -1
  15. package/lib/module/utils/error.js.map +1 -1
  16. package/lib/module/utils/type-bridge.js +64 -13
  17. package/lib/module/utils/type-bridge.js.map +1 -1
  18. package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
  19. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  20. package/lib/typescript/src/index.d.ts +12 -22
  21. package/lib/typescript/src/index.d.ts.map +1 -1
  22. package/lib/typescript/src/specs/RnIap.nitro.d.ts +15 -11
  23. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  24. package/lib/typescript/src/types.d.ts +58 -58
  25. package/lib/typescript/src/types.d.ts.map +1 -1
  26. package/lib/typescript/src/utils/error.d.ts.map +1 -1
  27. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  28. package/nitro.json +5 -1
  29. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +13 -4
  30. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -1
  31. package/nitrogen/generated/android/c++/JNitroProduct.hpp +40 -36
  32. package/nitrogen/generated/android/c++/JNitroPurchase.hpp +12 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +1 -1
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroProduct.kt +12 -9
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchase.kt +9 -0
  36. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +1 -1
  37. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -1
  38. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +13 -7
  39. package/nitrogen/generated/ios/swift/NitroProduct.swift +73 -43
  40. package/nitrogen/generated/ios/swift/NitroPurchase.swift +35 -2
  41. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +1 -1
  42. package/nitrogen/generated/shared/c++/NitroProduct.hpp +41 -37
  43. package/nitrogen/generated/shared/c++/NitroPurchase.hpp +13 -1
  44. package/package.json +9 -2
  45. package/plugin/build/src/withIAP.d.ts +3 -0
  46. package/plugin/build/src/withIAP.js +81 -0
  47. package/plugin/build/tsconfig.tsbuildinfo +1 -0
  48. package/plugin/tsconfig.tsbuildinfo +1 -1
  49. package/src/helpers/subscription.ts +36 -25
  50. package/src/hooks/useIAP.ts +188 -201
  51. package/src/index.ts +377 -356
  52. package/src/specs/RnIap.nitro.ts +15 -11
  53. package/src/types.ts +66 -62
  54. package/src/utils/error.ts +19 -19
  55. package/src/utils/type-bridge.ts +138 -75
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // External dependencies
2
- import { Platform } from 'react-native'
3
- import { NitroModules } from 'react-native-nitro-modules'
2
+ import {Platform} from 'react-native';
3
+ import {NitroModules} from 'react-native-nitro-modules';
4
4
 
5
5
  // Internal modules
6
6
  import type {
@@ -9,7 +9,7 @@ import type {
9
9
  NitroReceiptValidationParams,
10
10
  NitroReceiptValidationResultIOS,
11
11
  NitroReceiptValidationResultAndroid,
12
- } from './specs/RnIap.nitro'
12
+ } from './specs/RnIap.nitro';
13
13
  import type {
14
14
  Product,
15
15
  Purchase,
@@ -23,14 +23,15 @@ import type {
23
23
  ReceiptValidationResultAndroid,
24
24
  RequestPurchaseIosProps,
25
25
  RequestPurchaseAndroidProps,
26
- } from './types'
26
+ } from './types';
27
+ import type {ProductRequest} from './types';
27
28
  import {
28
29
  convertNitroProductToProduct,
29
30
  convertNitroPurchaseToPurchase,
30
31
  validateNitroProduct,
31
32
  validateNitroPurchase,
32
- } from './utils/type-bridge'
33
- import { parseErrorStringToJsonObj } from './utils/error'
33
+ } from './utils/type-bridge';
34
+ import {parseErrorStringToJsonObj} from './utils/error';
34
35
 
35
36
  // Export all types
36
37
  export type {
@@ -38,100 +39,106 @@ export type {
38
39
  NitroProduct,
39
40
  NitroPurchase,
40
41
  NitroPurchaseResult,
41
- } from './specs/RnIap.nitro'
42
- export * from './types'
43
- export * from './utils/error'
42
+ } from './specs/RnIap.nitro';
43
+ export * from './types';
44
+ export * from './utils/error';
44
45
 
45
46
  // Types for event listeners
46
47
  export interface EventSubscription {
47
- remove(): void
48
+ remove(): void;
48
49
  }
49
50
 
50
51
  // ActiveSubscription and PurchaseError types are already exported via 'export * from ./types'
51
52
 
52
53
  // Export hooks
53
- export { useIAP } from './hooks/useIAP'
54
+ export {useIAP} from './hooks/useIAP';
54
55
 
55
56
  // Development utilities removed - use type bridge functions directly if needed
56
57
 
57
58
  // Create the RnIap HybridObject instance (internal use only)
58
- const iap = NitroModules.createHybridObject<RnIap>('RnIap')
59
+ const iap = NitroModules.createHybridObject<RnIap>('RnIap');
59
60
 
60
61
  /**
61
62
  * Initialize connection to the store
62
63
  */
63
64
  export const initConnection = async (): Promise<boolean> => {
64
65
  try {
65
- return await iap.initConnection()
66
+ return await iap.initConnection();
66
67
  } catch (error) {
67
- console.error('Failed to initialize IAP connection:', error)
68
- throw error
68
+ console.error('Failed to initialize IAP connection:', error);
69
+ throw error;
69
70
  }
70
- }
71
+ };
71
72
 
72
73
  /**
73
74
  * End connection to the store
74
75
  */
75
76
  export const endConnection = async (): Promise<boolean> => {
76
77
  try {
77
- return await iap.endConnection()
78
+ return await iap.endConnection();
78
79
  } catch (error) {
79
- console.error('Failed to end IAP connection:', error)
80
- throw error
80
+ console.error('Failed to end IAP connection:', error);
81
+ throw error;
81
82
  }
82
- }
83
+ };
83
84
 
84
85
  /**
85
86
  * Fetch products from the store
86
87
  * @param params - Product request configuration
87
88
  * @param params.skus - Array of product SKUs to fetch
88
- * @param params.type - Type of products: 'inapp' for regular products (default) or 'subs' for subscriptions
89
+ * @param params.type - Optional filter: 'inapp' (default) for products, 'subs' for subscriptions, or 'all' for both.
89
90
  * @returns Promise<Product[]> - Array of products from the store
90
91
  *
91
92
  * @example
92
93
  * ```typescript
93
94
  * // Regular products
94
- * const products = await fetchProducts({
95
- * skus: ['product1', 'product2'],
96
- * type: 'inapp'
97
- * });
95
+ * const products = await fetchProducts({ skus: ['product1', 'product2'] });
98
96
  *
99
97
  * // Subscriptions
100
- * const subscriptions = await fetchProducts({
101
- * skus: ['sub1', 'sub2'],
102
- * type: 'subs'
103
- * });
98
+ * const subscriptions = await fetchProducts({ skus: ['sub1', 'sub2'], type: 'subs' });
104
99
  * ```
105
100
  */
106
101
  export const fetchProducts = async ({
107
102
  skus,
108
103
  type = 'inapp',
109
- }: {
110
- skus: string[]
111
- type?: 'inapp' | 'subs'
112
- }): Promise<Product[]> => {
104
+ }: ProductRequest): Promise<Product[]> => {
113
105
  try {
114
106
  if (!skus || skus.length === 0) {
115
- throw new Error('No SKUs provided')
107
+ throw new Error('No SKUs provided');
116
108
  }
117
109
 
118
- const nitroProducts = await iap.fetchProducts(skus, type)
110
+ if (type === 'all') {
111
+ const [inappNitro, subsNitro] = await Promise.all([
112
+ iap.fetchProducts(skus, 'inapp'),
113
+ iap.fetchProducts(skus, 'subs'),
114
+ ]);
115
+ const allNitro = [...inappNitro, ...subsNitro];
116
+ const validAll = allNitro.filter(validateNitroProduct);
117
+ if (validAll.length !== allNitro.length) {
118
+ console.warn(
119
+ `[fetchProducts] Some products failed validation: ${allNitro.length - validAll.length} invalid`,
120
+ );
121
+ }
122
+ return validAll.map(convertNitroProductToProduct);
123
+ }
124
+
125
+ const nitroProducts = await iap.fetchProducts(skus, type);
119
126
 
120
127
  // Validate and convert NitroProducts to TypeScript Products
121
- const validProducts = nitroProducts.filter(validateNitroProduct)
128
+ const validProducts = nitroProducts.filter(validateNitroProduct);
122
129
  if (validProducts.length !== nitroProducts.length) {
123
130
  console.warn(
124
- `[fetchProducts] Some products failed validation: ${nitroProducts.length - validProducts.length} invalid`
125
- )
131
+ `[fetchProducts] Some products failed validation: ${nitroProducts.length - validProducts.length} invalid`,
132
+ );
126
133
  }
127
134
 
128
- const typedProducts = validProducts.map(convertNitroProductToProduct)
129
- return typedProducts
135
+ const typedProducts = validProducts.map(convertNitroProductToProduct);
136
+ return typedProducts;
130
137
  } catch (error) {
131
- console.error('[fetchProducts] Failed:', error)
132
- throw error
138
+ console.error('[fetchProducts] Failed:', error);
139
+ throw error;
133
140
  }
134
- }
141
+ };
135
142
 
136
143
  /**
137
144
  * Request a purchase for products or subscriptions
@@ -175,65 +182,70 @@ export const requestPurchase = async ({
175
182
  request,
176
183
  type = 'inapp',
177
184
  }: {
178
- request: RequestPurchaseProps | RequestSubscriptionProps
179
- type?: 'inapp' | 'subs'
185
+ request: RequestPurchaseProps | RequestSubscriptionProps;
186
+ type?: 'inapp' | 'subs';
180
187
  }): Promise<void> => {
181
188
  try {
182
189
  // Validate platform-specific requests
183
190
  if (Platform.OS === 'ios') {
184
- const iosRequest = request.ios
191
+ const iosRequest = request.ios;
185
192
  if (!iosRequest?.sku) {
186
193
  throw new Error(
187
- 'Invalid request for iOS. The `sku` property is required.'
188
- )
194
+ 'Invalid request for iOS. The `sku` property is required.',
195
+ );
189
196
  }
190
197
  } else if (Platform.OS === 'android') {
191
- const androidRequest = request.android
198
+ const androidRequest = request.android;
192
199
  if (!androidRequest?.skus?.length) {
193
200
  throw new Error(
194
- 'Invalid request for Android. The `skus` property is required and must be a non-empty array.'
195
- )
201
+ 'Invalid request for Android. The `skus` property is required and must be a non-empty array.',
202
+ );
196
203
  }
197
204
  } else {
198
- throw new Error('Unsupported platform')
205
+ throw new Error('Unsupported platform');
199
206
  }
200
207
 
201
208
  // Transform the request for the unified interface
202
- const unifiedRequest: any = {}
209
+ const unifiedRequest: any = {};
203
210
 
204
211
  if (Platform.OS === 'ios' && request.ios) {
212
+ const iosReq = request.ios as RequestPurchaseIosProps;
213
+ const autoFinishSubs =
214
+ type === 'subs' && iosReq.andDangerouslyFinishTransactionAutomatically == null;
205
215
  unifiedRequest.ios = {
206
- ...request.ios,
207
- } as RequestPurchaseIosProps
216
+ ...iosReq,
217
+ // Align with native SwiftUI flow: auto-finish subscriptions by default
218
+ ...(autoFinishSubs
219
+ ? {andDangerouslyFinishTransactionAutomatically: true}
220
+ : {}),
221
+ } as RequestPurchaseIosProps;
208
222
  }
209
223
 
210
224
  if (Platform.OS === 'android' && request.android) {
211
225
  if (type === 'subs') {
212
- const subsRequest = request.android as RequestSubscriptionAndroidProps
226
+ const subsRequest = request.android as RequestSubscriptionAndroidProps;
213
227
  unifiedRequest.android = {
214
228
  ...subsRequest,
215
229
  subscriptionOffers: subsRequest.subscriptionOffers || [],
216
- } as RequestPurchaseAndroidProps
230
+ } as RequestPurchaseAndroidProps;
217
231
  } else {
218
- unifiedRequest.android = request.android
232
+ unifiedRequest.android = request.android;
219
233
  }
220
234
  }
221
235
 
222
236
  // Call unified method - returns void, listen for events instead
223
- await iap.requestPurchase(unifiedRequest)
237
+ await iap.requestPurchase(unifiedRequest);
224
238
  } catch (error) {
225
- console.error('Failed to request purchase:', error)
226
- throw error
239
+ console.error('Failed to request purchase:', error);
240
+ throw error;
227
241
  }
228
- }
242
+ };
229
243
 
230
244
  /**
231
245
  * Get available purchases (purchased items not yet consumed/finished)
232
246
  * @param params - Options for getting available purchases
233
- * @param params.alsoPublishToEventListenerIOS - Whether to also publish to event listener (iOS only)
234
- * @param params.onlyIncludeActiveItemsIOS - Whether to only include active items (iOS only)
235
- * @param params.alsoPublishToEventListener - @deprecated Use alsoPublishToEventListenerIOS instead
236
- * @param params.onlyIncludeActiveItems - @deprecated Use onlyIncludeActiveItemsIOS instead
247
+ * @param params.alsoPublishToEventListener - Whether to also publish to event listener
248
+ * @param params.onlyIncludeActiveItems - Whether to only include active items
237
249
  *
238
250
  * @example
239
251
  * ```typescript
@@ -243,67 +255,60 @@ export const requestPurchase = async ({
243
255
  * ```
244
256
  */
245
257
  export const getAvailablePurchases = async ({
246
- alsoPublishToEventListener = false,
247
- onlyIncludeActiveItems = true,
248
- alsoPublishToEventListenerIOS,
249
- onlyIncludeActiveItemsIOS,
258
+ alsoPublishToEventListenerIOS = false,
259
+ onlyIncludeActiveItemsIOS = true,
250
260
  }: PurchaseOptions = {}): Promise<Purchase[]> => {
251
261
  try {
252
262
  // Create unified options
253
- const options: any = {}
263
+ const options: any = {};
254
264
 
255
265
  if (Platform.OS === 'ios') {
256
- // Use new IOS suffixed parameters if provided, fallback to deprecated ones
266
+ // Provide both new and deprecated keys for compatibility
257
267
  options.ios = {
258
- alsoPublishToEventListenerIOS:
259
- alsoPublishToEventListenerIOS ?? alsoPublishToEventListener,
260
- onlyIncludeActiveItemsIOS:
261
- onlyIncludeActiveItemsIOS ?? onlyIncludeActiveItems,
262
- // Keep deprecated parameters for backward compatibility
263
- alsoPublishToEventListener:
264
- alsoPublishToEventListenerIOS ?? alsoPublishToEventListener,
265
- onlyIncludeActiveItems:
266
- onlyIncludeActiveItemsIOS ?? onlyIncludeActiveItems,
267
- }
268
+ alsoPublishToEventListenerIOS,
269
+ onlyIncludeActiveItemsIOS,
270
+ alsoPublishToEventListener: alsoPublishToEventListenerIOS,
271
+ onlyIncludeActiveItems: onlyIncludeActiveItemsIOS,
272
+ };
268
273
  } else if (Platform.OS === 'android') {
269
274
  // For Android, we need to call twice for inapp and subs
270
275
  const inappNitroPurchases = await iap.getAvailablePurchases({
271
- android: { type: 'inapp' },
272
- })
276
+ android: {type: 'inapp'},
277
+ });
273
278
  const subsNitroPurchases = await iap.getAvailablePurchases({
274
- android: { type: 'subs' },
275
- })
279
+ android: {type: 'subs'},
280
+ });
276
281
 
277
282
  // Validate and convert both sets of purchases
278
- const allNitroPurchases = [...inappNitroPurchases, ...subsNitroPurchases]
279
- const validPurchases = allNitroPurchases.filter(validateNitroPurchase)
283
+ const allNitroPurchases = [...inappNitroPurchases, ...subsNitroPurchases];
284
+ const validPurchases = allNitroPurchases.filter(validateNitroPurchase);
280
285
  if (validPurchases.length !== allNitroPurchases.length) {
281
286
  console.warn(
282
- `[getAvailablePurchases] Some Android purchases failed validation: ${allNitroPurchases.length - validPurchases.length} invalid`
283
- )
287
+ `[getAvailablePurchases] Some Android purchases failed validation: ${allNitroPurchases.length - validPurchases.length} invalid`,
288
+ );
284
289
  }
285
290
 
286
- return validPurchases.map(convertNitroPurchaseToPurchase)
291
+ return validPurchases.map(convertNitroPurchaseToPurchase);
287
292
  } else {
288
- throw new Error('Unsupported platform')
293
+ throw new Error('Unsupported platform');
289
294
  }
290
295
 
291
- const nitroPurchases = await iap.getAvailablePurchases(options)
296
+ const nitroPurchases = await iap.getAvailablePurchases(options);
292
297
 
293
298
  // Validate and convert NitroPurchases to TypeScript Purchases
294
- const validPurchases = nitroPurchases.filter(validateNitroPurchase)
299
+ const validPurchases = nitroPurchases.filter(validateNitroPurchase);
295
300
  if (validPurchases.length !== nitroPurchases.length) {
296
301
  console.warn(
297
- `[getAvailablePurchases] Some purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`
298
- )
302
+ `[getAvailablePurchases] Some purchases failed validation: ${nitroPurchases.length - validPurchases.length} invalid`,
303
+ );
299
304
  }
300
305
 
301
- return validPurchases.map(convertNitroPurchaseToPurchase)
306
+ return validPurchases.map(convertNitroPurchaseToPurchase);
302
307
  } catch (error) {
303
- console.error('Failed to get available purchases:', error)
304
- throw error
308
+ console.error('Failed to get available purchases:', error);
309
+ throw error;
305
310
  }
306
- }
311
+ };
307
312
 
308
313
  /**
309
314
  * Finish a transaction (consume or acknowledge)
@@ -325,45 +330,58 @@ export const finishTransaction = async ({
325
330
  }: FinishTransactionParams): Promise<NitroPurchaseResult | boolean> => {
326
331
  try {
327
332
  // Create unified params
328
- const params: any = {}
333
+ const params: any = {};
329
334
 
330
335
  if (Platform.OS === 'ios') {
331
336
  if (!purchase.id) {
332
- throw new Error('purchase.id required to finish iOS transaction')
337
+ throw new Error('purchase.id required to finish iOS transaction');
333
338
  }
334
339
  params.ios = {
335
340
  transactionId: purchase.id,
336
- }
341
+ };
337
342
  } else if (Platform.OS === 'android') {
338
- const androidPurchase = purchase as PurchaseAndroid
343
+ const androidPurchase = purchase as PurchaseAndroid;
339
344
  const token =
340
- androidPurchase.purchaseToken || androidPurchase.purchaseTokenAndroid
345
+ androidPurchase.purchaseToken || androidPurchase.purchaseTokenAndroid;
341
346
 
342
347
  if (!token) {
343
- throw new Error('purchaseToken required to finish Android transaction')
348
+ throw new Error('purchaseToken required to finish Android transaction');
344
349
  }
345
350
 
346
351
  params.android = {
347
352
  purchaseToken: token,
348
353
  isConsumable,
349
- }
354
+ };
350
355
  } else {
351
- throw new Error('Unsupported platform')
356
+ throw new Error('Unsupported platform');
352
357
  }
353
358
 
354
- const result = await iap.finishTransaction(params)
359
+ const result = await iap.finishTransaction(params);
355
360
 
356
361
  // Handle variant return type
357
362
  if (typeof result === 'boolean') {
358
- return result
363
+ return result;
359
364
  }
360
365
  // It's a PurchaseResult
361
- return result as NitroPurchaseResult
366
+ return result as NitroPurchaseResult;
362
367
  } catch (error) {
363
- console.error('Failed to finish transaction:', error)
364
- throw error
368
+ // If iOS transaction has already been auto-finished natively, treat as success
369
+ if (Platform.OS === 'ios') {
370
+ const err = parseErrorStringToJsonObj(error);
371
+ const msg = (err?.message || '').toString();
372
+ const code = (err?.code || '').toString();
373
+ if (
374
+ msg.includes('Transaction not found') ||
375
+ code === 'E_ITEM_UNAVAILABLE'
376
+ ) {
377
+ // Consider already finished
378
+ return true;
379
+ }
380
+ }
381
+ console.error('Failed to finish transaction:', error);
382
+ throw error;
365
383
  }
366
- }
384
+ };
367
385
 
368
386
  /**
369
387
  * Acknowledge a purchase (Android only)
@@ -375,11 +393,13 @@ export const finishTransaction = async ({
375
393
  * ```
376
394
  */
377
395
  export const acknowledgePurchaseAndroid = async (
378
- purchaseToken: string
396
+ purchaseToken: string,
379
397
  ): Promise<NitroPurchaseResult> => {
380
398
  try {
381
399
  if (Platform.OS !== 'android') {
382
- throw new Error('acknowledgePurchaseAndroid is only available on Android')
400
+ throw new Error(
401
+ 'acknowledgePurchaseAndroid is only available on Android',
402
+ );
383
403
  }
384
404
 
385
405
  const result = await iap.finishTransaction({
@@ -387,7 +407,7 @@ export const acknowledgePurchaseAndroid = async (
387
407
  purchaseToken,
388
408
  isConsumable: false,
389
409
  },
390
- })
410
+ });
391
411
 
392
412
  // Result is a variant, extract PurchaseResult
393
413
  if (typeof result === 'boolean') {
@@ -397,14 +417,14 @@ export const acknowledgePurchaseAndroid = async (
397
417
  code: '0',
398
418
  message: 'Success',
399
419
  purchaseToken,
400
- }
420
+ };
401
421
  }
402
- return result as NitroPurchaseResult
422
+ return result as NitroPurchaseResult;
403
423
  } catch (error) {
404
- console.error('Failed to acknowledge purchase Android:', error)
405
- throw error
424
+ console.error('Failed to acknowledge purchase Android:', error);
425
+ throw error;
406
426
  }
407
- }
427
+ };
408
428
 
409
429
  /**
410
430
  * Consume a purchase (Android only)
@@ -416,11 +436,11 @@ export const acknowledgePurchaseAndroid = async (
416
436
  * ```
417
437
  */
418
438
  export const consumePurchaseAndroid = async (
419
- purchaseToken: string
439
+ purchaseToken: string,
420
440
  ): Promise<NitroPurchaseResult> => {
421
441
  try {
422
442
  if (Platform.OS !== 'android') {
423
- throw new Error('consumePurchaseAndroid is only available on Android')
443
+ throw new Error('consumePurchaseAndroid is only available on Android');
424
444
  }
425
445
 
426
446
  const result = await iap.finishTransaction({
@@ -428,7 +448,7 @@ export const consumePurchaseAndroid = async (
428
448
  purchaseToken,
429
449
  isConsumable: true,
430
450
  },
431
- })
451
+ });
432
452
 
433
453
  // Result is a variant, extract PurchaseResult
434
454
  if (typeof result === 'boolean') {
@@ -438,21 +458,21 @@ export const consumePurchaseAndroid = async (
438
458
  code: '0',
439
459
  message: 'Success',
440
460
  purchaseToken,
441
- }
461
+ };
442
462
  }
443
- return result as NitroPurchaseResult
463
+ return result as NitroPurchaseResult;
444
464
  } catch (error) {
445
- console.error('Failed to consume purchase Android:', error)
446
- throw error
465
+ console.error('Failed to consume purchase Android:', error);
466
+ throw error;
447
467
  }
448
- }
468
+ };
449
469
 
450
470
  // ============================================================================
451
471
  // EVENT LISTENERS
452
472
  // ============================================================================
453
473
 
454
474
  // Store wrapped listeners for proper removal
455
- const listenerMap = new WeakMap<Function, Function>()
475
+ const listenerMap = new WeakMap<Function, Function>();
456
476
 
457
477
  /**
458
478
  * Purchase updated event listener
@@ -475,35 +495,35 @@ const listenerMap = new WeakMap<Function, Function>()
475
495
  * ```
476
496
  */
477
497
  export const purchaseUpdatedListener = (
478
- listener: (purchase: Purchase) => void
498
+ listener: (purchase: Purchase) => void,
479
499
  ): EventSubscription => {
480
500
  // Wrap the listener to convert NitroPurchase to Purchase
481
501
  const wrappedListener = (nitroPurchase: any) => {
482
502
  if (validateNitroPurchase(nitroPurchase)) {
483
- const convertedPurchase = convertNitroPurchaseToPurchase(nitroPurchase)
484
- listener(convertedPurchase)
503
+ const convertedPurchase = convertNitroPurchaseToPurchase(nitroPurchase);
504
+ listener(convertedPurchase);
485
505
  } else {
486
506
  console.error(
487
507
  'Invalid purchase data received from native:',
488
- nitroPurchase
489
- )
508
+ nitroPurchase,
509
+ );
490
510
  }
491
- }
511
+ };
492
512
 
493
513
  // Store the wrapped listener for removal
494
- listenerMap.set(listener, wrappedListener)
495
- iap.addPurchaseUpdatedListener(wrappedListener)
514
+ listenerMap.set(listener, wrappedListener);
515
+ iap.addPurchaseUpdatedListener(wrappedListener);
496
516
 
497
517
  return {
498
518
  remove: () => {
499
- const wrapped = listenerMap.get(listener)
519
+ const wrapped = listenerMap.get(listener);
500
520
  if (wrapped) {
501
- iap.removePurchaseUpdatedListener(wrapped as any)
502
- listenerMap.delete(listener)
521
+ iap.removePurchaseUpdatedListener(wrapped as any);
522
+ listenerMap.delete(listener);
503
523
  }
504
524
  },
505
- }
506
- }
525
+ };
526
+ };
507
527
 
508
528
  /**
509
529
  * Purchase error event listener
@@ -533,19 +553,19 @@ export const purchaseUpdatedListener = (
533
553
  * ```
534
554
  */
535
555
  export const purchaseErrorListener = (
536
- listener: (error: NitroPurchaseResult) => void
556
+ listener: (error: NitroPurchaseResult) => void,
537
557
  ): EventSubscription => {
538
558
  // Store the listener for removal
539
- listenerMap.set(listener, listener)
540
- iap.addPurchaseErrorListener(listener as any)
559
+ listenerMap.set(listener, listener);
560
+ iap.addPurchaseErrorListener(listener as any);
541
561
 
542
562
  return {
543
563
  remove: () => {
544
- iap.removePurchaseErrorListener(listener as any)
545
- listenerMap.delete(listener)
564
+ iap.removePurchaseErrorListener(listener as any);
565
+ listenerMap.delete(listener);
546
566
  },
547
- }
548
- }
567
+ };
568
+ };
549
569
 
550
570
  /**
551
571
  * iOS-only listener for App Store promoted product events.
@@ -568,42 +588,42 @@ export const purchaseErrorListener = (
568
588
  * @platform iOS
569
589
  */
570
590
  export const promotedProductListenerIOS = (
571
- listener: (product: Product) => void
591
+ listener: (product: Product) => void,
572
592
  ): EventSubscription => {
573
593
  if (Platform.OS !== 'ios') {
574
594
  console.warn(
575
- 'promotedProductListenerIOS: This listener is only available on iOS'
576
- )
577
- return { remove: () => {} }
595
+ 'promotedProductListenerIOS: This listener is only available on iOS',
596
+ );
597
+ return {remove: () => {}};
578
598
  }
579
599
 
580
600
  // Wrap the listener to convert NitroProduct to Product
581
601
  const wrappedListener = (nitroProduct: any) => {
582
602
  if (validateNitroProduct(nitroProduct)) {
583
- const convertedProduct = convertNitroProductToProduct(nitroProduct)
584
- listener(convertedProduct)
603
+ const convertedProduct = convertNitroProductToProduct(nitroProduct);
604
+ listener(convertedProduct);
585
605
  } else {
586
606
  console.error(
587
607
  'Invalid promoted product data received from native:',
588
- nitroProduct
589
- )
608
+ nitroProduct,
609
+ );
590
610
  }
591
- }
611
+ };
592
612
 
593
613
  // Store the wrapped listener for removal
594
- listenerMap.set(listener, wrappedListener)
595
- iap.addPromotedProductListenerIOS(wrappedListener)
614
+ listenerMap.set(listener, wrappedListener);
615
+ iap.addPromotedProductListenerIOS(wrappedListener);
596
616
 
597
617
  return {
598
618
  remove: () => {
599
- const wrapped = listenerMap.get(listener)
619
+ const wrapped = listenerMap.get(listener);
600
620
  if (wrapped) {
601
- iap.removePromotedProductListenerIOS(wrapped as any)
602
- listenerMap.delete(listener)
621
+ iap.removePromotedProductListenerIOS(wrapped as any);
622
+ listenerMap.delete(listener);
603
623
  }
604
624
  },
605
- }
606
- }
625
+ };
626
+ };
607
627
 
608
628
  // ============================================================================
609
629
  // iOS-SPECIFIC FUNCTIONS
@@ -618,30 +638,30 @@ export const promotedProductListenerIOS = (
618
638
  export const validateReceipt = async (
619
639
  sku: string,
620
640
  androidOptions?: {
621
- packageName: string
622
- productToken: string
623
- accessToken: string
624
- isSub?: boolean
625
- }
626
- ): Promise<ReceiptValidationResultIOS | ReceiptValidationResultAndroid> => {
641
+ packageName: string;
642
+ productToken: string;
643
+ accessToken: string;
644
+ isSub?: boolean;
645
+ },
646
+ ): Promise<import('./types').ReceiptValidationResult> => {
627
647
  if (!iap) {
628
648
  const errorJson = parseErrorStringToJsonObj(
629
- 'RnIap: Service not initialized. Call initConnection() first.'
630
- )
631
- throw new Error(errorJson.message)
649
+ 'RnIap: Service not initialized. Call initConnection() first.',
650
+ );
651
+ throw new Error(errorJson.message);
632
652
  }
633
653
 
634
654
  try {
635
655
  const params: NitroReceiptValidationParams = {
636
656
  sku,
637
657
  androidOptions,
638
- }
658
+ };
639
659
 
640
- const nitroResult = await iap.validateReceipt(params)
660
+ const nitroResult = await iap.validateReceipt(params);
641
661
 
642
662
  // Convert Nitro result to public API result
643
663
  if (Platform.OS === 'ios') {
644
- const iosResult = nitroResult as NitroReceiptValidationResultIOS
664
+ const iosResult = nitroResult as NitroReceiptValidationResultIOS;
645
665
  const result: ReceiptValidationResultIOS = {
646
666
  isValid: iosResult.isValid,
647
667
  receiptData: iosResult.receiptData,
@@ -649,11 +669,11 @@ export const validateReceipt = async (
649
669
  latestTransaction: iosResult.latestTransaction
650
670
  ? convertNitroPurchaseToPurchase(iosResult.latestTransaction)
651
671
  : undefined,
652
- }
653
- return result
672
+ };
673
+ return result;
654
674
  } else {
655
675
  // Android
656
- const androidResult = nitroResult as NitroReceiptValidationResultAndroid
676
+ const androidResult = nitroResult as NitroReceiptValidationResultAndroid;
657
677
  const result: ReceiptValidationResultAndroid = {
658
678
  autoRenewing: androidResult.autoRenewing,
659
679
  betaProduct: androidResult.betaProduct,
@@ -665,7 +685,7 @@ export const validateReceipt = async (
665
685
  gracePeriodEndDate: androidResult.gracePeriodEndDate,
666
686
  parentProductId: androidResult.parentProductId,
667
687
  productId: androidResult.productId,
668
- productType: androidResult.productType,
688
+ productType: (androidResult.productType === 'subs' ? 'subs' : 'inapp'),
669
689
  purchaseDate: androidResult.purchaseDate,
670
690
  quantity: androidResult.quantity,
671
691
  receiptId: androidResult.receiptId,
@@ -673,15 +693,15 @@ export const validateReceipt = async (
673
693
  term: androidResult.term,
674
694
  termSku: androidResult.termSku,
675
695
  testTransaction: androidResult.testTransaction,
676
- }
677
- return result
696
+ };
697
+ return result;
678
698
  }
679
699
  } catch (error) {
680
- console.error('[validateReceipt] Failed:', error)
681
- const errorJson = parseErrorStringToJsonObj(error)
682
- throw new Error(errorJson.message)
700
+ console.error('[validateReceipt] Failed:', error);
701
+ const errorJson = parseErrorStringToJsonObj(error);
702
+ throw new Error(errorJson.message);
683
703
  }
684
- }
704
+ };
685
705
 
686
706
  /**
687
707
  * Sync iOS purchases with App Store (iOS only)
@@ -690,24 +710,24 @@ export const validateReceipt = async (
690
710
  */
691
711
  export const syncIOS = async (): Promise<boolean> => {
692
712
  if (Platform.OS !== 'ios') {
693
- throw new Error('syncIOS is only available on iOS')
713
+ throw new Error('syncIOS is only available on iOS');
694
714
  }
695
715
 
696
716
  if (!iap) {
697
717
  const errorJson = parseErrorStringToJsonObj(
698
- 'RnIap: Service not initialized. Call initConnection() first.'
699
- )
700
- throw new Error(errorJson.message)
718
+ 'RnIap: Service not initialized. Call initConnection() first.',
719
+ );
720
+ throw new Error(errorJson.message);
701
721
  }
702
722
 
703
723
  try {
704
- return await iap.syncIOS()
724
+ return await iap.syncIOS();
705
725
  } catch (error) {
706
- console.error('[syncIOS] Failed:', error)
707
- const errorJson = parseErrorStringToJsonObj(error)
708
- throw new Error(errorJson.message)
726
+ console.error('[syncIOS] Failed:', error);
727
+ const errorJson = parseErrorStringToJsonObj(error);
728
+ throw new Error(errorJson.message);
709
729
  }
710
- }
730
+ };
711
731
 
712
732
  /**
713
733
  * Request the promoted product from the App Store (iOS only)
@@ -716,28 +736,28 @@ export const syncIOS = async (): Promise<boolean> => {
716
736
  */
717
737
  export const requestPromotedProductIOS = async (): Promise<Product | null> => {
718
738
  if (Platform.OS !== 'ios') {
719
- return null
739
+ return null;
720
740
  }
721
741
 
722
742
  if (!iap) {
723
743
  const errorJson = parseErrorStringToJsonObj(
724
- 'RnIap: Service not initialized. Call initConnection() first.'
725
- )
726
- throw new Error(errorJson.message)
744
+ 'RnIap: Service not initialized. Call initConnection() first.',
745
+ );
746
+ throw new Error(errorJson.message);
727
747
  }
728
748
 
729
749
  try {
730
- const nitroProduct = await iap.requestPromotedProductIOS()
750
+ const nitroProduct = await iap.requestPromotedProductIOS();
731
751
  if (nitroProduct) {
732
- return convertNitroProductToProduct(nitroProduct)
752
+ return convertNitroProductToProduct(nitroProduct);
733
753
  }
734
- return null
754
+ return null;
735
755
  } catch (error) {
736
- console.error('[getPromotedProductIOS] Failed:', error)
737
- const errorJson = parseErrorStringToJsonObj(error)
738
- throw new Error(errorJson.message)
756
+ console.error('[getPromotedProductIOS] Failed:', error);
757
+ const errorJson = parseErrorStringToJsonObj(error);
758
+ throw new Error(errorJson.message);
739
759
  }
740
- }
760
+ };
741
761
 
742
762
  /**
743
763
  * Present the code redemption sheet for offer codes (iOS only)
@@ -746,24 +766,24 @@ export const requestPromotedProductIOS = async (): Promise<Product | null> => {
746
766
  */
747
767
  export const presentCodeRedemptionSheetIOS = async (): Promise<boolean> => {
748
768
  if (Platform.OS !== 'ios') {
749
- return false
769
+ return false;
750
770
  }
751
771
 
752
772
  if (!iap) {
753
773
  const errorJson = parseErrorStringToJsonObj(
754
- 'RnIap: Service not initialized. Call initConnection() first.'
755
- )
756
- throw new Error(errorJson.message)
774
+ 'RnIap: Service not initialized. Call initConnection() first.',
775
+ );
776
+ throw new Error(errorJson.message);
757
777
  }
758
778
 
759
779
  try {
760
- return await iap.presentCodeRedemptionSheetIOS()
780
+ return await iap.presentCodeRedemptionSheetIOS();
761
781
  } catch (error) {
762
- console.error('[presentCodeRedemptionSheetIOS] Failed:', error)
763
- const errorJson = parseErrorStringToJsonObj(error)
764
- throw new Error(errorJson.message)
782
+ console.error('[presentCodeRedemptionSheetIOS] Failed:', error);
783
+ const errorJson = parseErrorStringToJsonObj(error);
784
+ throw new Error(errorJson.message);
765
785
  }
766
- }
786
+ };
767
787
 
768
788
  /**
769
789
  * Buy promoted product on iOS
@@ -772,24 +792,24 @@ export const presentCodeRedemptionSheetIOS = async (): Promise<boolean> => {
772
792
  */
773
793
  export const buyPromotedProductIOS = async (): Promise<void> => {
774
794
  if (Platform.OS !== 'ios') {
775
- throw new Error('buyPromotedProductIOS is only available on iOS')
795
+ throw new Error('buyPromotedProductIOS is only available on iOS');
776
796
  }
777
797
 
778
798
  if (!iap) {
779
799
  const errorJson = parseErrorStringToJsonObj(
780
- 'RnIap: Service not initialized. Call initConnection() first.'
781
- )
782
- throw new Error(errorJson.message)
800
+ 'RnIap: Service not initialized. Call initConnection() first.',
801
+ );
802
+ throw new Error(errorJson.message);
783
803
  }
784
804
 
785
805
  try {
786
- await iap.buyPromotedProductIOS()
806
+ await iap.buyPromotedProductIOS();
787
807
  } catch (error) {
788
- console.error('[buyPromotedProductIOS] Failed:', error)
789
- const errorJson = parseErrorStringToJsonObj(error)
790
- throw new Error(errorJson.message)
808
+ console.error('[buyPromotedProductIOS] Failed:', error);
809
+ const errorJson = parseErrorStringToJsonObj(error);
810
+ throw new Error(errorJson.message);
791
811
  }
792
- }
812
+ };
793
813
 
794
814
  /**
795
815
  * Clear unfinished transactions on iOS
@@ -798,24 +818,24 @@ export const buyPromotedProductIOS = async (): Promise<void> => {
798
818
  */
799
819
  export const clearTransactionIOS = async (): Promise<void> => {
800
820
  if (Platform.OS !== 'ios') {
801
- return
821
+ return;
802
822
  }
803
823
 
804
824
  if (!iap) {
805
825
  const errorJson = parseErrorStringToJsonObj(
806
- 'RnIap: Service not initialized. Call initConnection() first.'
807
- )
808
- throw new Error(errorJson.message)
826
+ 'RnIap: Service not initialized. Call initConnection() first.',
827
+ );
828
+ throw new Error(errorJson.message);
809
829
  }
810
830
 
811
831
  try {
812
- await iap.clearTransactionIOS()
832
+ await iap.clearTransactionIOS();
813
833
  } catch (error) {
814
- console.error('[clearTransactionIOS] Failed:', error)
815
- const errorJson = parseErrorStringToJsonObj(error)
816
- throw new Error(errorJson.message)
834
+ console.error('[clearTransactionIOS] Failed:', error);
835
+ const errorJson = parseErrorStringToJsonObj(error);
836
+ throw new Error(errorJson.message);
817
837
  }
818
- }
838
+ };
819
839
 
820
840
  /**
821
841
  * Begin a refund request for a product on iOS 15+
@@ -824,27 +844,27 @@ export const clearTransactionIOS = async (): Promise<void> => {
824
844
  * @platform iOS
825
845
  */
826
846
  export const beginRefundRequestIOS = async (
827
- sku: string
847
+ sku: string,
828
848
  ): Promise<string | null> => {
829
849
  if (Platform.OS !== 'ios') {
830
- return null
850
+ return null;
831
851
  }
832
852
 
833
853
  if (!iap) {
834
854
  const errorJson = parseErrorStringToJsonObj(
835
- 'RnIap: Service not initialized. Call initConnection() first.'
836
- )
837
- throw new Error(errorJson.message)
855
+ 'RnIap: Service not initialized. Call initConnection() first.',
856
+ );
857
+ throw new Error(errorJson.message);
838
858
  }
839
859
 
840
860
  try {
841
- return await iap.beginRefundRequestIOS(sku)
861
+ return await iap.beginRefundRequestIOS(sku);
842
862
  } catch (error) {
843
- console.error('[beginRefundRequestIOS] Failed:', error)
844
- const errorJson = parseErrorStringToJsonObj(error)
845
- throw new Error(errorJson.message)
863
+ console.error('[beginRefundRequestIOS] Failed:', error);
864
+ const errorJson = parseErrorStringToJsonObj(error);
865
+ throw new Error(errorJson.message);
846
866
  }
847
- }
867
+ };
848
868
 
849
869
  /**
850
870
  * Get subscription status for a product (iOS only)
@@ -854,25 +874,25 @@ export const beginRefundRequestIOS = async (
854
874
  */
855
875
  export const subscriptionStatusIOS = async (sku: string): Promise<any[]> => {
856
876
  if (Platform.OS !== 'ios') {
857
- throw new Error('subscriptionStatusIOS is only available on iOS')
877
+ throw new Error('subscriptionStatusIOS is only available on iOS');
858
878
  }
859
879
 
860
880
  if (!iap) {
861
881
  const errorJson = parseErrorStringToJsonObj(
862
- 'RnIap: Service not initialized. Call initConnection() first.'
863
- )
864
- throw new Error(errorJson.message)
882
+ 'RnIap: Service not initialized. Call initConnection() first.',
883
+ );
884
+ throw new Error(errorJson.message);
865
885
  }
866
886
 
867
887
  try {
868
- const statuses = await iap.subscriptionStatusIOS(sku)
869
- return statuses || []
888
+ const statuses = await iap.subscriptionStatusIOS(sku);
889
+ return statuses || [];
870
890
  } catch (error) {
871
- console.error('[subscriptionStatusIOS] Failed:', error)
872
- const errorJson = parseErrorStringToJsonObj(error)
873
- throw new Error(errorJson.message)
891
+ console.error('[subscriptionStatusIOS] Failed:', error);
892
+ const errorJson = parseErrorStringToJsonObj(error);
893
+ throw new Error(errorJson.message);
874
894
  }
875
- }
895
+ };
876
896
 
877
897
  /**
878
898
  * Get current entitlement for a product (iOS only)
@@ -881,31 +901,31 @@ export const subscriptionStatusIOS = async (sku: string): Promise<any[]> => {
881
901
  * @platform iOS
882
902
  */
883
903
  export const currentEntitlementIOS = async (
884
- sku: string
904
+ sku: string,
885
905
  ): Promise<Purchase | null> => {
886
906
  if (Platform.OS !== 'ios') {
887
- return null
907
+ return null;
888
908
  }
889
909
 
890
910
  if (!iap) {
891
911
  const errorJson = parseErrorStringToJsonObj(
892
- 'RnIap: Service not initialized. Call initConnection() first.'
893
- )
894
- throw new Error(errorJson.message)
912
+ 'RnIap: Service not initialized. Call initConnection() first.',
913
+ );
914
+ throw new Error(errorJson.message);
895
915
  }
896
916
 
897
917
  try {
898
- const nitroPurchase = await iap.currentEntitlementIOS(sku)
918
+ const nitroPurchase = await iap.currentEntitlementIOS(sku);
899
919
  if (nitroPurchase) {
900
- return convertNitroPurchaseToPurchase(nitroPurchase)
920
+ return convertNitroPurchaseToPurchase(nitroPurchase);
901
921
  }
902
- return null
922
+ return null;
903
923
  } catch (error) {
904
- console.error('[currentEntitlementIOS] Failed:', error)
905
- const errorJson = parseErrorStringToJsonObj(error)
906
- throw new Error(errorJson.message)
924
+ console.error('[currentEntitlementIOS] Failed:', error);
925
+ const errorJson = parseErrorStringToJsonObj(error);
926
+ throw new Error(errorJson.message);
907
927
  }
908
- }
928
+ };
909
929
 
910
930
  /**
911
931
  * Get latest transaction for a product (iOS only)
@@ -914,31 +934,31 @@ export const currentEntitlementIOS = async (
914
934
  * @platform iOS
915
935
  */
916
936
  export const latestTransactionIOS = async (
917
- sku: string
937
+ sku: string,
918
938
  ): Promise<Purchase | null> => {
919
939
  if (Platform.OS !== 'ios') {
920
- return null
940
+ return null;
921
941
  }
922
942
 
923
943
  if (!iap) {
924
944
  const errorJson = parseErrorStringToJsonObj(
925
- 'RnIap: Service not initialized. Call initConnection() first.'
926
- )
927
- throw new Error(errorJson.message)
945
+ 'RnIap: Service not initialized. Call initConnection() first.',
946
+ );
947
+ throw new Error(errorJson.message);
928
948
  }
929
949
 
930
950
  try {
931
- const nitroPurchase = await iap.latestTransactionIOS(sku)
951
+ const nitroPurchase = await iap.latestTransactionIOS(sku);
932
952
  if (nitroPurchase) {
933
- return convertNitroPurchaseToPurchase(nitroPurchase)
953
+ return convertNitroPurchaseToPurchase(nitroPurchase);
934
954
  }
935
- return null
955
+ return null;
936
956
  } catch (error) {
937
- console.error('[latestTransactionIOS] Failed:', error)
938
- const errorJson = parseErrorStringToJsonObj(error)
939
- throw new Error(errorJson.message)
957
+ console.error('[latestTransactionIOS] Failed:', error);
958
+ const errorJson = parseErrorStringToJsonObj(error);
959
+ throw new Error(errorJson.message);
940
960
  }
941
- }
961
+ };
942
962
 
943
963
  /**
944
964
  * Get pending transactions (iOS only)
@@ -947,51 +967,52 @@ export const latestTransactionIOS = async (
947
967
  */
948
968
  export const getPendingTransactionsIOS = async (): Promise<Purchase[]> => {
949
969
  if (Platform.OS !== 'ios') {
950
- return []
970
+ return [];
951
971
  }
952
972
 
953
973
  if (!iap) {
954
974
  const errorJson = parseErrorStringToJsonObj(
955
- 'RnIap: Service not initialized. Call initConnection() first.'
956
- )
957
- throw new Error(errorJson.message)
975
+ 'RnIap: Service not initialized. Call initConnection() first.',
976
+ );
977
+ throw new Error(errorJson.message);
958
978
  }
959
979
 
960
980
  try {
961
- const nitroPurchases = await iap.getPendingTransactionsIOS()
962
- return nitroPurchases.map(convertNitroPurchaseToPurchase)
981
+ const nitroPurchases = await iap.getPendingTransactionsIOS();
982
+ return nitroPurchases.map(convertNitroPurchaseToPurchase);
963
983
  } catch (error) {
964
- console.error('[getPendingTransactionsIOS] Failed:', error)
965
- const errorJson = parseErrorStringToJsonObj(error)
966
- throw new Error(errorJson.message)
984
+ console.error('[getPendingTransactionsIOS] Failed:', error);
985
+ const errorJson = parseErrorStringToJsonObj(error);
986
+ throw new Error(errorJson.message);
967
987
  }
968
- }
988
+ };
969
989
 
970
990
  /**
971
991
  * Show manage subscriptions screen (iOS only)
972
- * @returns Promise<boolean> - Success flag
992
+ * @returns Promise<Purchase[]> - Subscriptions where auto-renewal status changed
973
993
  * @platform iOS
974
994
  */
975
- export const showManageSubscriptionsIOS = async (): Promise<boolean> => {
995
+ export const showManageSubscriptionsIOS = async (): Promise<Purchase[]> => {
976
996
  if (Platform.OS !== 'ios') {
977
- return false
997
+ return [];
978
998
  }
979
999
 
980
1000
  if (!iap) {
981
1001
  const errorJson = parseErrorStringToJsonObj(
982
- 'RnIap: Service not initialized. Call initConnection() first.'
983
- )
984
- throw new Error(errorJson.message)
1002
+ 'RnIap: Service not initialized. Call initConnection() first.',
1003
+ );
1004
+ throw new Error(errorJson.message);
985
1005
  }
986
1006
 
987
1007
  try {
988
- return await iap.showManageSubscriptionsIOS()
1008
+ const nitroPurchases = await iap.showManageSubscriptionsIOS();
1009
+ return nitroPurchases.map(convertNitroPurchaseToPurchase);
989
1010
  } catch (error) {
990
- console.error('[showManageSubscriptionsIOS] Failed:', error)
991
- const errorJson = parseErrorStringToJsonObj(error)
992
- throw new Error(errorJson.message)
1011
+ console.error('[showManageSubscriptionsIOS] Failed:', error);
1012
+ const errorJson = parseErrorStringToJsonObj(error);
1013
+ throw new Error(errorJson.message);
993
1014
  }
994
- }
1015
+ };
995
1016
 
996
1017
  /**
997
1018
  * Check if user is eligible for intro offer (iOS only)
@@ -1000,27 +1021,27 @@ export const showManageSubscriptionsIOS = async (): Promise<boolean> => {
1000
1021
  * @platform iOS
1001
1022
  */
1002
1023
  export const isEligibleForIntroOfferIOS = async (
1003
- groupID: string
1024
+ groupID: string,
1004
1025
  ): Promise<boolean> => {
1005
1026
  if (Platform.OS !== 'ios') {
1006
- return false
1027
+ return false;
1007
1028
  }
1008
1029
 
1009
1030
  if (!iap) {
1010
1031
  const errorJson = parseErrorStringToJsonObj(
1011
- 'RnIap: Service not initialized. Call initConnection() first.'
1012
- )
1013
- throw new Error(errorJson.message)
1032
+ 'RnIap: Service not initialized. Call initConnection() first.',
1033
+ );
1034
+ throw new Error(errorJson.message);
1014
1035
  }
1015
1036
 
1016
1037
  try {
1017
- return await iap.isEligibleForIntroOfferIOS(groupID)
1038
+ return await iap.isEligibleForIntroOfferIOS(groupID);
1018
1039
  } catch (error) {
1019
- console.error('[isEligibleForIntroOfferIOS] Failed:', error)
1020
- const errorJson = parseErrorStringToJsonObj(error)
1021
- throw new Error(errorJson.message)
1040
+ console.error('[isEligibleForIntroOfferIOS] Failed:', error);
1041
+ const errorJson = parseErrorStringToJsonObj(error);
1042
+ throw new Error(errorJson.message);
1022
1043
  }
1023
- }
1044
+ };
1024
1045
 
1025
1046
  /**
1026
1047
  * Get receipt data (iOS only)
@@ -1029,24 +1050,24 @@ export const isEligibleForIntroOfferIOS = async (
1029
1050
  */
1030
1051
  export const getReceiptDataIOS = async (): Promise<string> => {
1031
1052
  if (Platform.OS !== 'ios') {
1032
- throw new Error('getReceiptDataIOS is only available on iOS')
1053
+ throw new Error('getReceiptDataIOS is only available on iOS');
1033
1054
  }
1034
1055
 
1035
1056
  if (!iap) {
1036
1057
  const errorJson = parseErrorStringToJsonObj(
1037
- 'RnIap: Service not initialized. Call initConnection() first.'
1038
- )
1039
- throw new Error(errorJson.message)
1058
+ 'RnIap: Service not initialized. Call initConnection() first.',
1059
+ );
1060
+ throw new Error(errorJson.message);
1040
1061
  }
1041
1062
 
1042
1063
  try {
1043
- return await iap.getReceiptDataIOS()
1064
+ return await iap.getReceiptDataIOS();
1044
1065
  } catch (error) {
1045
- console.error('[getReceiptDataIOS] Failed:', error)
1046
- const errorJson = parseErrorStringToJsonObj(error)
1047
- throw new Error(errorJson.message)
1066
+ console.error('[getReceiptDataIOS] Failed:', error);
1067
+ const errorJson = parseErrorStringToJsonObj(error);
1068
+ throw new Error(errorJson.message);
1048
1069
  }
1049
- }
1070
+ };
1050
1071
 
1051
1072
  /**
1052
1073
  * Check if transaction is verified (iOS only)
@@ -1055,27 +1076,27 @@ export const getReceiptDataIOS = async (): Promise<string> => {
1055
1076
  * @platform iOS
1056
1077
  */
1057
1078
  export const isTransactionVerifiedIOS = async (
1058
- sku: string
1079
+ sku: string,
1059
1080
  ): Promise<boolean> => {
1060
1081
  if (Platform.OS !== 'ios') {
1061
- return false
1082
+ return false;
1062
1083
  }
1063
1084
 
1064
1085
  if (!iap) {
1065
1086
  const errorJson = parseErrorStringToJsonObj(
1066
- 'RnIap: Service not initialized. Call initConnection() first.'
1067
- )
1068
- throw new Error(errorJson.message)
1087
+ 'RnIap: Service not initialized. Call initConnection() first.',
1088
+ );
1089
+ throw new Error(errorJson.message);
1069
1090
  }
1070
1091
 
1071
1092
  try {
1072
- return await iap.isTransactionVerifiedIOS(sku)
1093
+ return await iap.isTransactionVerifiedIOS(sku);
1073
1094
  } catch (error) {
1074
- console.error('[isTransactionVerifiedIOS] Failed:', error)
1075
- const errorJson = parseErrorStringToJsonObj(error)
1076
- throw new Error(errorJson.message)
1095
+ console.error('[isTransactionVerifiedIOS] Failed:', error);
1096
+ const errorJson = parseErrorStringToJsonObj(error);
1097
+ throw new Error(errorJson.message);
1077
1098
  }
1078
- }
1099
+ };
1079
1100
 
1080
1101
  /**
1081
1102
  * Get transaction JWS representation (iOS only)
@@ -1084,27 +1105,27 @@ export const isTransactionVerifiedIOS = async (
1084
1105
  * @platform iOS
1085
1106
  */
1086
1107
  export const getTransactionJwsIOS = async (
1087
- sku: string
1108
+ sku: string,
1088
1109
  ): Promise<string | null> => {
1089
1110
  if (Platform.OS !== 'ios') {
1090
- return null
1111
+ return null;
1091
1112
  }
1092
1113
 
1093
1114
  if (!iap) {
1094
1115
  const errorJson = parseErrorStringToJsonObj(
1095
- 'RnIap: Service not initialized. Call initConnection() first.'
1096
- )
1097
- throw new Error(errorJson.message)
1116
+ 'RnIap: Service not initialized. Call initConnection() first.',
1117
+ );
1118
+ throw new Error(errorJson.message);
1098
1119
  }
1099
1120
 
1100
1121
  try {
1101
- return await iap.getTransactionJwsIOS(sku)
1122
+ return await iap.getTransactionJwsIOS(sku);
1102
1123
  } catch (error) {
1103
- console.error('[getTransactionJwsIOS] Failed:', error)
1104
- const errorJson = parseErrorStringToJsonObj(error)
1105
- throw new Error(errorJson.message)
1124
+ console.error('[getTransactionJwsIOS] Failed:', error);
1125
+ const errorJson = parseErrorStringToJsonObj(error);
1126
+ throw new Error(errorJson.message);
1106
1127
  }
1107
- }
1128
+ };
1108
1129
 
1109
1130
  /**
1110
1131
  * Get the storefront identifier for the user's App Store account (iOS only)
@@ -1119,18 +1140,18 @@ export const getTransactionJwsIOS = async (
1119
1140
  */
1120
1141
  export const getStorefrontIOS = async (): Promise<string> => {
1121
1142
  if (Platform.OS !== 'ios') {
1122
- throw new Error('getStorefrontIOS is only available on iOS')
1143
+ throw new Error('getStorefrontIOS is only available on iOS');
1123
1144
  }
1124
1145
 
1125
1146
  try {
1126
1147
  // Call the native method to get storefront
1127
- const storefront = await iap.getStorefrontIOS()
1128
- return storefront
1148
+ const storefront = await iap.getStorefrontIOS();
1149
+ return storefront;
1129
1150
  } catch (error) {
1130
- console.error('Failed to get storefront:', error)
1131
- throw error
1151
+ console.error('Failed to get storefront:', error);
1152
+ throw error;
1132
1153
  }
1133
- }
1154
+ };
1134
1155
 
1135
1156
  /**
1136
1157
  * iOS only - Gets the original app transaction ID if the app was purchased from the App Store
@@ -1153,24 +1174,24 @@ export const getStorefrontIOS = async (): Promise<string> => {
1153
1174
  */
1154
1175
  export const getAppTransactionIOS = async (): Promise<string | null> => {
1155
1176
  if (Platform.OS !== 'ios') {
1156
- throw new Error('getAppTransactionIOS is only available on iOS')
1177
+ throw new Error('getAppTransactionIOS is only available on iOS');
1157
1178
  }
1158
1179
 
1159
1180
  try {
1160
1181
  // Call the native method to get app transaction
1161
- const appTransaction = await iap.getAppTransactionIOS()
1162
- return appTransaction
1182
+ const appTransaction = await iap.getAppTransactionIOS();
1183
+ return appTransaction;
1163
1184
  } catch (error) {
1164
- console.error('Failed to get app transaction:', error)
1165
- throw error
1185
+ console.error('Failed to get app transaction:', error);
1186
+ throw error;
1166
1187
  }
1167
- }
1188
+ };
1168
1189
 
1169
1190
  // Export subscription helpers
1170
1191
  export {
1171
1192
  getActiveSubscriptions,
1172
1193
  hasActiveSubscriptions,
1173
- } from './helpers/subscription'
1194
+ } from './helpers/subscription';
1174
1195
 
1175
1196
  // Type conversion utilities
1176
1197
  export {
@@ -1180,15 +1201,15 @@ export {
1180
1201
  validateNitroProduct,
1181
1202
  validateNitroPurchase,
1182
1203
  checkTypeSynchronization,
1183
- } from './utils/type-bridge'
1204
+ } from './utils/type-bridge';
1184
1205
 
1185
1206
  // Deprecated exports for backward compatibility
1186
1207
  /**
1187
1208
  * @deprecated Use acknowledgePurchaseAndroid instead
1188
1209
  */
1189
- export const acknowledgePurchase = acknowledgePurchaseAndroid
1210
+ export const acknowledgePurchase = acknowledgePurchaseAndroid;
1190
1211
 
1191
1212
  /**
1192
1213
  * @deprecated Use consumePurchaseAndroid instead
1193
1214
  */
1194
- export const consumePurchase = consumePurchaseAndroid
1215
+ export const consumePurchase = consumePurchaseAndroid;