react-native-iap 14.0.1 → 14.1.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 (45) hide show
  1. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +81 -22
  2. package/ios/HybridRnIap.swift +151 -11
  3. package/ios/ProductStore.swift +10 -0
  4. package/lib/module/helpers/subscription.js +9 -1
  5. package/lib/module/helpers/subscription.js.map +1 -1
  6. package/lib/module/hooks/useIAP.js +9 -13
  7. package/lib/module/hooks/useIAP.js.map +1 -1
  8. package/lib/module/index.js +43 -28
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/types.js +24 -16
  11. package/lib/module/types.js.map +1 -1
  12. package/lib/module/utils/type-bridge.js +64 -13
  13. package/lib/module/utils/type-bridge.js.map +1 -1
  14. package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
  15. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  16. package/lib/typescript/src/index.d.ts +12 -22
  17. package/lib/typescript/src/index.d.ts.map +1 -1
  18. package/lib/typescript/src/specs/RnIap.nitro.d.ts +15 -11
  19. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  20. package/lib/typescript/src/types.d.ts +58 -58
  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/nitro.json +5 -1
  24. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +13 -4
  25. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -1
  26. package/nitrogen/generated/android/c++/JNitroProduct.hpp +40 -36
  27. package/nitrogen/generated/android/c++/JNitroPurchase.hpp +12 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +1 -1
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroProduct.kt +12 -9
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchase.kt +9 -0
  31. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +1 -1
  32. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -1
  33. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +13 -7
  34. package/nitrogen/generated/ios/swift/NitroProduct.swift +73 -43
  35. package/nitrogen/generated/ios/swift/NitroPurchase.swift +35 -2
  36. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +1 -1
  37. package/nitrogen/generated/shared/c++/NitroProduct.hpp +41 -37
  38. package/nitrogen/generated/shared/c++/NitroPurchase.hpp +13 -1
  39. package/package.json +9 -2
  40. package/src/helpers/subscription.ts +13 -2
  41. package/src/hooks/useIAP.ts +8 -21
  42. package/src/index.ts +58 -38
  43. package/src/specs/RnIap.nitro.ts +15 -11
  44. package/src/types.ts +66 -62
  45. package/src/utils/type-bridge.ts +80 -16
package/src/types.ts CHANGED
@@ -16,7 +16,13 @@ export type ChangeEventPayload = {
16
16
  value: string
17
17
  }
18
18
 
19
- export type ProductType = 'inapp' | 'subs'
19
+ // iOS detailed product types (4 types)
20
+ export enum ProductTypeIOS {
21
+ consumable = 'consumable',
22
+ nonConsumable = 'nonConsumable',
23
+ autoRenewableSubscription = 'autoRenewableSubscription',
24
+ nonRenewingSubscription = 'nonRenewingSubscription',
25
+ }
20
26
 
21
27
  // ============================================================================
22
28
  // COMMON TYPES (Base types shared across all platforms)
@@ -32,8 +38,8 @@ export type ProductCommon = {
32
38
  title: string
33
39
  /** Product description */
34
40
  description: string
35
- /** Product type: 'inapp' for one-time purchases (consumable/non-consumable), 'subs' for subscriptions */
36
- type: ProductType
41
+ /** Product type: 'inapp' or 'subs' for Android compatibility */
42
+ type: 'inapp' | 'subs' // Note: this is the actual product type, not for filtering
37
43
  /** Display name for the product */
38
44
  displayName?: string
39
45
  /** Formatted price string for display (e.g., "$9.99") */
@@ -69,6 +75,21 @@ export type PurchaseCommon = {
69
75
  purchaseToken?: string
70
76
  /** Platform identifier ('ios' or 'android') */
71
77
  platform?: string
78
+ /** Purchase quantity (defaults to 1) */
79
+ quantity: number
80
+ /** Purchase state (common field) */
81
+ purchaseState: PurchaseState
82
+ /** Auto-renewable subscription flag (common field) */
83
+ isAutoRenewing: boolean
84
+ }
85
+
86
+ export enum PurchaseState {
87
+ pending = 'pending',
88
+ purchased = 'purchased',
89
+ failed = 'failed',
90
+ restored = 'restored', // iOS only
91
+ deferred = 'deferred', // iOS only
92
+ unknown = 'unknown',
72
93
  }
73
94
 
74
95
  export type ProductSubscriptionCommon = ProductCommon & {
@@ -128,6 +149,7 @@ export type ProductIOS = ProductCommon & {
128
149
  jsonRepresentationIOS: string
129
150
  platform: 'ios'
130
151
  subscriptionInfoIOS?: SubscriptionInfo
152
+ typeIOS: ProductTypeIOS // Detailed iOS product type
131
153
  // deprecated fields
132
154
  displayName?: string
133
155
  isFamilyShareable?: boolean
@@ -245,12 +267,6 @@ export type ProductSubscriptionAndroid = ProductAndroid & {
245
267
  subscriptionOfferDetails?: ProductSubscriptionAndroidOfferDetails[]
246
268
  }
247
269
 
248
- export enum PurchaseAndroidState {
249
- UNSPECIFIED_STATE = 0,
250
- PURCHASED = 1,
251
- PENDING = 2,
252
- }
253
-
254
270
  export type PurchaseAndroid = PurchaseCommon & {
255
271
  platform: 'android'
256
272
  /**
@@ -259,8 +275,8 @@ export type PurchaseAndroid = PurchaseCommon & {
259
275
  purchaseTokenAndroid?: string
260
276
  dataAndroid?: string
261
277
  signatureAndroid?: string
278
+ /** @deprecated Use the common `isAutoRenewing` field instead */
262
279
  autoRenewingAndroid?: boolean
263
- purchaseStateAndroid?: PurchaseAndroidState
264
280
  isAcknowledgedAndroid?: boolean
265
281
  packageNameAndroid?: string
266
282
  developerPayloadAndroid?: string
@@ -322,13 +338,21 @@ export type Purchase =
322
338
  // REQUEST TYPES
323
339
  // ============================================================================
324
340
 
341
+ // Product request parameters for fetching products from the store
342
+ export interface ProductRequest {
343
+ /** Product SKUs to fetch */
344
+ skus: string[]
345
+ /** Filter type: "inapp" (default), "subs", or "all" */
346
+ type?: 'inapp' | 'subs' | 'all'
347
+ }
348
+
325
349
  // iOS-specific purchase request parameters
326
350
  export interface RequestPurchaseIosProps {
327
351
  readonly sku: string
328
352
  readonly andDangerouslyFinishTransactionAutomatically?: boolean
329
353
  readonly appAccountToken?: string
330
354
  readonly quantity?: number
331
- readonly withOffer?: PaymentDiscount
355
+ readonly withOffer?: DiscountOffer
332
356
  }
333
357
 
334
358
  // Android-specific purchase request parameters
@@ -418,7 +442,7 @@ export type PurchaseResult = {
418
442
  }
419
443
 
420
444
  // Additional iOS types
421
- export type PaymentDiscount = {
445
+ export type DiscountOffer = {
422
446
  identifier: string
423
447
  keyIdentifier: string
424
448
  nonce: string
@@ -447,22 +471,12 @@ export type AppTransactionIOS = {
447
471
  // ============================================================================
448
472
 
449
473
  /**
450
- * Options for getAvailablePurchases methods
474
+ * Options for getAvailablePurchases and getPurchaseHistories methods
451
475
  */
452
476
  export interface PurchaseOptions {
453
- /**
454
- * @deprecated Use alsoPublishToEventListenerIOS instead
455
- * Whether to also publish purchases to event listener (iOS only)
456
- */
457
- alsoPublishToEventListener?: boolean
458
- /**
459
- * @deprecated Use onlyIncludeActiveItemsIOS instead
460
- * Whether to only include active items (iOS only)
461
- */
462
- onlyIncludeActiveItems?: boolean
463
- /** Whether to also publish purchases to event listener (iOS only) */
477
+ /** Whether to also publish purchases to event listener */
464
478
  alsoPublishToEventListenerIOS?: boolean
465
- /** Whether to only include active items (subscriptions that are still active) (iOS only) */
479
+ /** Whether to only include active items (subscriptions that are still active) */
466
480
  onlyIncludeActiveItemsIOS?: boolean
467
481
  }
468
482
 
@@ -513,7 +527,7 @@ export interface IapContext {
513
527
  /** Initialize connection to the store */
514
528
  initConnection(): Promise<boolean>
515
529
  /** End connection to the store */
516
- endConnection(): Promise<void>
530
+ endConnection(): Promise<boolean>
517
531
  /** Sync purchases (iOS only) */
518
532
  sync(): Promise<void>
519
533
 
@@ -521,11 +535,11 @@ export interface IapContext {
521
535
  /**
522
536
  * Fetch products from the store
523
537
  * @param params.skus - Array of product SKUs to fetch
524
- * @param params.type - Type of products: 'inapp' for regular products or 'subs' for subscriptions
538
+ * @param params.type - Type of products: 'inapp' for regular products, 'subs' for subscriptions, 'all' to fetch both. Defaults to 'inapp'
525
539
  */
526
540
  fetchProducts(params: {
527
541
  skus: string[]
528
- type?: ProductType // 'inapp' | 'subs', defaults to 'inapp'
542
+ type?: 'inapp' | 'subs' | 'all' // Defaults to 'inapp'
529
543
  }): Promise<Product[] | SubscriptionProduct[]>
530
544
 
531
545
  // Purchase methods
@@ -561,7 +575,13 @@ export interface IapContext {
561
575
  // Receipt validation
562
576
  /** Validate a receipt (server-side validation recommended) */
563
577
  validateReceipt(
564
- options: ValidateReceiptProps
578
+ sku: string,
579
+ androidOptions?: {
580
+ packageName: string
581
+ productToken: string
582
+ accessToken: string
583
+ isSub?: boolean
584
+ }
565
585
  ): Promise<ReceiptValidationResult>
566
586
  }
567
587
 
@@ -580,7 +600,7 @@ export interface PurchaseError {
580
600
  /**
581
601
  * Validation options for receipt validation
582
602
  */
583
- export interface ValidateReceiptProps {
603
+ export interface ReceiptValidationProps {
584
604
  /** Product SKU to validate */
585
605
  sku: string
586
606
  /** Android-specific validation options */
@@ -595,7 +615,7 @@ export interface ValidateReceiptProps {
595
615
  /**
596
616
  * iOS receipt validation result
597
617
  */
598
- export interface ReceiptIOS {
618
+ export interface ReceiptValidationResultIOS {
599
619
  /** Whether the receipt is valid */
600
620
  isValid: boolean
601
621
  /** Receipt data string */
@@ -609,35 +629,6 @@ export interface ReceiptIOS {
609
629
  /**
610
630
  * Android receipt validation result
611
631
  */
612
- export interface ReceiptAndroid {
613
- /** Whether the receipt is valid */
614
- isValid: boolean
615
- /** Receipt data string */
616
- receiptData: string
617
- /** JWS representation */
618
- jwsRepresentation: string
619
- /** Latest transaction if available */
620
- latestTransaction?: Purchase
621
- }
622
-
623
- /**
624
- * Receipt validation result from receipt validation
625
- */
626
- export type ReceiptValidationResult = ReceiptAndroid | ReceiptIOS
627
-
628
- /**
629
- * New iOS receipt validation result (matches user specification)
630
- */
631
- export interface ReceiptValidationResultIOS {
632
- isValid: boolean
633
- receiptData: string
634
- jwsRepresentation: string
635
- latestTransaction?: Purchase
636
- }
637
-
638
- /**
639
- * New Android receipt validation result (matches user specification)
640
- */
641
632
  export interface ReceiptValidationResultAndroid {
642
633
  autoRenewing: boolean
643
634
  betaProduct: boolean
@@ -649,7 +640,7 @@ export interface ReceiptValidationResultAndroid {
649
640
  gracePeriodEndDate: number
650
641
  parentProductId: string
651
642
  productId: string
652
- productType: string
643
+ productType: 'inapp' | 'subs'
653
644
  purchaseDate: number
654
645
  quantity: number
655
646
  receiptId: string
@@ -659,6 +650,13 @@ export interface ReceiptValidationResultAndroid {
659
650
  testTransaction: boolean
660
651
  }
661
652
 
653
+ /**
654
+ * Receipt validation result from receipt validation
655
+ */
656
+ export type ReceiptValidationResult =
657
+ | ReceiptValidationResultAndroid
658
+ | ReceiptValidationResultIOS
659
+
662
660
  /**
663
661
  * Represents an active subscription with platform-specific details
664
662
  */
@@ -667,9 +665,15 @@ export interface ActiveSubscription {
667
665
  productId: string
668
666
  /** Whether the subscription is currently active */
669
667
  isActive: boolean
668
+ /** Transaction identifier for backend validation */
669
+ transactionId: string
670
+ /** JWT token (iOS) or purchase token (Android) for backend validation */
671
+ purchaseToken?: string
672
+ /** Transaction timestamp */
673
+ transactionDate: number
670
674
  /** iOS: Subscription expiration date */
671
675
  expirationDateIOS?: Date
672
- /** Android: Whether the subscription auto-renews */
676
+ /** @deprecated Use the common `isAutoRenewing` field instead */
673
677
  autoRenewingAndroid?: boolean
674
678
  /** iOS: Environment where the subscription was purchased (Production/Sandbox) */
675
679
  environmentIOS?: string
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { NitroProduct, NitroPurchase } from '../specs/RnIap.nitro'
11
11
  import type { Product, Purchase, SubscriptionProduct } from '../types'
12
+ import { PurchaseState, ProductTypeIOS } from '../types'
12
13
  import { Platform } from 'react-native'
13
14
 
14
15
  // ============================================================================
@@ -39,8 +40,27 @@ export function convertNitroProductToProduct(
39
40
  if (Platform.OS === 'ios') {
40
41
  // Map iOS fields from Nitro to TypeScript types
41
42
  const iosProduct = product as any // Temporarily cast to access iOS fields
42
- iosProduct.isFamilyShareable = nitroProduct.isFamilyShareable
43
- iosProduct.jsonRepresentation = nitroProduct.jsonRepresentation
43
+ iosProduct.isFamilyShareable = (nitroProduct as any).isFamilyShareableIOS
44
+ iosProduct.jsonRepresentation = (nitroProduct as any).jsonRepresentationIOS
45
+ // Detailed iOS product type - directly from the native field
46
+ const typeIOSValue: string | undefined = (nitroProduct as any).typeIOS
47
+
48
+ switch (typeIOSValue) {
49
+ case 'consumable':
50
+ iosProduct.typeIOS = ProductTypeIOS.consumable
51
+ break
52
+ case 'nonConsumable':
53
+ iosProduct.typeIOS = ProductTypeIOS.nonConsumable
54
+ break
55
+ case 'autoRenewableSubscription':
56
+ iosProduct.typeIOS = ProductTypeIOS.autoRenewableSubscription
57
+ break
58
+ case 'nonRenewingSubscription':
59
+ iosProduct.typeIOS = ProductTypeIOS.nonRenewingSubscription
60
+ break
61
+ default:
62
+ iosProduct.typeIOS = undefined
63
+ }
44
64
  iosProduct.subscriptionPeriodUnitIOS =
45
65
  nitroProduct.subscriptionPeriodUnitIOS
46
66
  iosProduct.subscriptionPeriodNumberIOS =
@@ -57,16 +77,25 @@ export function convertNitroProductToProduct(
57
77
  } else if (Platform.OS === 'android') {
58
78
  // Map Android fields from Nitro to TypeScript types
59
79
  const androidProduct = product as any // Temporarily cast to access Android fields
60
- androidProduct.originalPrice = nitroProduct.originalPrice
61
- androidProduct.originalPriceAmountMicros =
62
- nitroProduct.originalPriceAmountMicros
63
- androidProduct.introductoryPriceValue = nitroProduct.introductoryPriceValue
64
- androidProduct.introductoryPriceCycles =
65
- nitroProduct.introductoryPriceCycles
66
- androidProduct.introductoryPricePeriod =
67
- nitroProduct.introductoryPricePeriod
68
- androidProduct.subscriptionPeriod = nitroProduct.subscriptionPeriod
69
- androidProduct.freeTrialPeriod = nitroProduct.freeTrialPeriod
80
+ androidProduct.originalPrice = (nitroProduct as any).originalPriceAndroid
81
+ androidProduct.originalPriceAmountMicros = (
82
+ nitroProduct as any
83
+ ).originalPriceAmountMicrosAndroid
84
+ androidProduct.introductoryPriceValue = (
85
+ nitroProduct as any
86
+ ).introductoryPriceValueAndroid
87
+ androidProduct.introductoryPriceCycles = (
88
+ nitroProduct as any
89
+ ).introductoryPriceCyclesAndroid
90
+ androidProduct.introductoryPricePeriod = (
91
+ nitroProduct as any
92
+ ).introductoryPricePeriodAndroid
93
+ androidProduct.subscriptionPeriod = (
94
+ nitroProduct as any
95
+ ).subscriptionPeriodAndroid
96
+ androidProduct.freeTrialPeriod = (
97
+ nitroProduct as any
98
+ ).freeTrialPeriodAndroid
70
99
 
71
100
  // Map subscription offer details (parse from JSON string)
72
101
  if (nitroProduct.subscriptionOfferDetailsAndroid) {
@@ -83,8 +112,9 @@ export function convertNitroProductToProduct(
83
112
  // Create flattened offer fields for easier access in example code
84
113
  androidProduct.oneTimePurchaseOfferFormattedPrice =
85
114
  nitroProduct.displayPrice
86
- androidProduct.oneTimePurchaseOfferPriceAmountMicros =
87
- nitroProduct.originalPriceAmountMicros
115
+ androidProduct.oneTimePurchaseOfferPriceAmountMicros = (
116
+ nitroProduct as any
117
+ ).originalPriceAmountMicrosAndroid
88
118
  androidProduct.oneTimePurchaseOfferPriceCurrencyCode = nitroProduct.currency
89
119
  }
90
120
 
@@ -130,6 +160,11 @@ export function convertNitroPurchaseToPurchase(
130
160
  transactionReceipt: '', // Will be set by native layer
131
161
  purchaseToken: nitroPurchase.purchaseToken,
132
162
  platform: nitroPurchase.platform as 'ios' | 'android',
163
+ // Common fields from NitroPurchase
164
+ quantity: nitroPurchase.quantity || 1,
165
+ purchaseState:
166
+ (nitroPurchase.purchaseState as PurchaseState) || PurchaseState.unknown,
167
+ isAutoRenewing: nitroPurchase.isAutoRenewing || false,
133
168
  }
134
169
 
135
170
  // Add platform-specific fields
@@ -141,19 +176,42 @@ export function convertNitroPurchaseToPurchase(
141
176
  iosPurchase.originalTransactionIdentifierIOS =
142
177
  nitroPurchase.originalTransactionIdentifierIOS
143
178
  iosPurchase.appAccountToken = nitroPurchase.appAccountToken
179
+ // Fill common quantity from iOS-specific quantity when available
180
+ if (typeof nitroPurchase.quantityIOS === 'number') {
181
+ purchase.quantity = nitroPurchase.quantityIOS
182
+ }
144
183
  } else if (Platform.OS === 'android') {
145
184
  const androidPurchase = purchase as any
146
185
  androidPurchase.purchaseTokenAndroid = nitroPurchase.purchaseTokenAndroid
147
186
  androidPurchase.dataAndroid = nitroPurchase.dataAndroid
148
187
  androidPurchase.signatureAndroid = nitroPurchase.signatureAndroid
149
- androidPurchase.autoRenewingAndroid = nitroPurchase.autoRenewingAndroid
150
- androidPurchase.purchaseStateAndroid = nitroPurchase.purchaseStateAndroid
188
+ // Support both old and new field names for backward compatibility
189
+ androidPurchase.autoRenewingAndroid =
190
+ nitroPurchase.autoRenewingAndroid ?? nitroPurchase.isAutoRenewing
191
+ // no longer surface purchaseStateAndroid on TS side
151
192
  androidPurchase.isAcknowledgedAndroid = nitroPurchase.isAcknowledgedAndroid
152
193
  androidPurchase.packageNameAndroid = nitroPurchase.packageNameAndroid
153
194
  androidPurchase.obfuscatedAccountIdAndroid =
154
195
  nitroPurchase.obfuscatedAccountIdAndroid
155
196
  androidPurchase.obfuscatedProfileIdAndroid =
156
197
  nitroPurchase.obfuscatedProfileIdAndroid
198
+
199
+ // Use the common isAutoRenewing field from NitroPurchase
200
+ purchase.isAutoRenewing = nitroPurchase.isAutoRenewing
201
+
202
+ // Map numeric Android purchase state to common PurchaseState
203
+ switch (nitroPurchase.purchaseStateAndroid) {
204
+ case 1:
205
+ purchase.purchaseState = PurchaseState.purchased
206
+ break
207
+ case 2:
208
+ purchase.purchaseState = PurchaseState.pending
209
+ break
210
+ case 0:
211
+ default:
212
+ purchase.purchaseState = PurchaseState.unknown
213
+ break
214
+ }
157
215
  }
158
216
 
159
217
  return purchase as Purchase
@@ -169,6 +227,9 @@ export function convertNitroPurchaseToPurchase(
169
227
  * Validate that a NitroProduct has all required fields for conversion
170
228
  */
171
229
  export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
230
+ if (!nitroProduct || typeof nitroProduct !== 'object') {
231
+ return false
232
+ }
172
233
  const required = ['id', 'title', 'description', 'type', 'platform']
173
234
  for (const field of required) {
174
235
  if (
@@ -189,6 +250,9 @@ export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
189
250
  * Validate that a NitroPurchase has all required fields for conversion
190
251
  */
191
252
  export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
253
+ if (!nitroPurchase || typeof nitroPurchase !== 'object') {
254
+ return false
255
+ }
192
256
  const required = ['id', 'productId', 'transactionDate', 'platform']
193
257
  for (const field of required) {
194
258
  if (