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
@@ -7,9 +7,10 @@
7
7
  * Purpose: Prevent type fragmentation between native (Nitro) and TypeScript sides
8
8
  */
9
9
 
10
- import type { NitroProduct, NitroPurchase } from '../specs/RnIap.nitro'
11
- import type { Product, Purchase, SubscriptionProduct } from '../types'
12
- import { Platform } from 'react-native'
10
+ import type {NitroProduct, NitroPurchase} from '../specs/RnIap.nitro';
11
+ import type {Product, Purchase, SubscriptionProduct} from '../types';
12
+ import {PurchaseState, ProductTypeIOS} from '../types';
13
+ import {Platform} from 'react-native';
13
14
 
14
15
  // ============================================================================
15
16
  // PRODUCT CONVERSION
@@ -20,7 +21,7 @@ import { Platform } from 'react-native'
20
21
  * This ensures all fields are properly mapped and accessible
21
22
  */
22
23
  export function convertNitroProductToProduct(
23
- nitroProduct: NitroProduct
24
+ nitroProduct: NitroProduct,
24
25
  ): Product {
25
26
  // Create base product with common fields, handling platform casting
26
27
  const product: any = {
@@ -33,62 +34,92 @@ export function convertNitroProductToProduct(
33
34
  currency: nitroProduct.currency || '',
34
35
  price: nitroProduct.price,
35
36
  platform: nitroProduct.platform as 'ios' | 'android',
36
- }
37
+ };
37
38
 
38
39
  // Add platform-specific fields based on current platform
39
40
  if (Platform.OS === 'ios') {
40
41
  // Map iOS fields from Nitro to TypeScript types
41
- const iosProduct = product as any // Temporarily cast to access iOS fields
42
- iosProduct.isFamilyShareable = nitroProduct.isFamilyShareable
43
- iosProduct.jsonRepresentation = nitroProduct.jsonRepresentation
42
+ const iosProduct = product as any; // Temporarily cast to access iOS fields
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
- nitroProduct.subscriptionPeriodUnitIOS
65
+ nitroProduct.subscriptionPeriodUnitIOS;
46
66
  iosProduct.subscriptionPeriodNumberIOS =
47
- nitroProduct.subscriptionPeriodNumberIOS
48
- iosProduct.introductoryPriceIOS = nitroProduct.introductoryPriceIOS
67
+ nitroProduct.subscriptionPeriodNumberIOS;
68
+ iosProduct.introductoryPriceIOS = nitroProduct.introductoryPriceIOS;
49
69
  iosProduct.introductoryPriceAsAmountIOS =
50
- nitroProduct.introductoryPriceAsAmountIOS
70
+ nitroProduct.introductoryPriceAsAmountIOS;
51
71
  iosProduct.introductoryPricePaymentModeIOS =
52
- nitroProduct.introductoryPricePaymentModeIOS
72
+ nitroProduct.introductoryPricePaymentModeIOS;
53
73
  iosProduct.introductoryPriceNumberOfPeriodsIOS =
54
- nitroProduct.introductoryPriceNumberOfPeriodsIOS
74
+ nitroProduct.introductoryPriceNumberOfPeriodsIOS;
55
75
  iosProduct.introductoryPriceSubscriptionPeriodIOS =
56
- nitroProduct.introductoryPriceSubscriptionPeriodIOS
76
+ nitroProduct.introductoryPriceSubscriptionPeriodIOS;
57
77
  } else if (Platform.OS === 'android') {
58
78
  // Map Android fields from Nitro to TypeScript types
59
- 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
79
+ const androidProduct = product as any; // Temporarily cast to access Android fields
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) {
73
102
  try {
74
103
  androidProduct.subscriptionOfferDetailsAndroid = JSON.parse(
75
- nitroProduct.subscriptionOfferDetailsAndroid
76
- )
104
+ nitroProduct.subscriptionOfferDetailsAndroid,
105
+ );
77
106
  } catch (e) {
78
- console.warn('Failed to parse subscription offer details:', e)
79
- androidProduct.subscriptionOfferDetailsAndroid = null
107
+ console.warn('Failed to parse subscription offer details:', e);
108
+ androidProduct.subscriptionOfferDetailsAndroid = null;
80
109
  }
81
110
  }
82
111
 
83
112
  // Create flattened offer fields for easier access in example code
84
113
  androidProduct.oneTimePurchaseOfferFormattedPrice =
85
- nitroProduct.displayPrice
86
- androidProduct.oneTimePurchaseOfferPriceAmountMicros =
87
- nitroProduct.originalPriceAmountMicros
88
- androidProduct.oneTimePurchaseOfferPriceCurrencyCode = nitroProduct.currency
114
+ nitroProduct.displayPrice;
115
+ androidProduct.oneTimePurchaseOfferPriceAmountMicros = (
116
+ nitroProduct as any
117
+ ).originalPriceAmountMicrosAndroid;
118
+ androidProduct.oneTimePurchaseOfferPriceCurrencyCode =
119
+ nitroProduct.currency;
89
120
  }
90
121
 
91
- return product as Product
122
+ return product as Product;
92
123
  }
93
124
 
94
125
  // Note: Use nitroProducts.map(convertNitroProductToProduct) instead of a separate function
@@ -97,19 +128,19 @@ export function convertNitroProductToProduct(
97
128
  * Convert Product to SubscriptionProduct (type-safe casting)
98
129
  */
99
130
  export function convertProductToSubscriptionProduct(
100
- product: Product
131
+ product: Product,
101
132
  ): SubscriptionProduct {
102
133
  if (product.type !== 'subs') {
103
134
  console.warn(
104
135
  'Converting non-subscription product to SubscriptionProduct:',
105
- product.id
106
- )
136
+ product.id,
137
+ );
107
138
  }
108
139
  // Since SubscriptionProduct is now an intersection type, we need to cast properly
109
140
  return {
110
141
  ...product,
111
142
  type: 'subs' as const,
112
- } as SubscriptionProduct
143
+ } as SubscriptionProduct;
113
144
  }
114
145
 
115
146
  // ============================================================================
@@ -120,7 +151,7 @@ export function convertProductToSubscriptionProduct(
120
151
  * Convert NitroPurchase (from native) to TypeScript Purchase (for library consumers)
121
152
  */
122
153
  export function convertNitroPurchaseToPurchase(
123
- nitroPurchase: NitroPurchase
154
+ nitroPurchase: NitroPurchase,
124
155
  ): Purchase {
125
156
  // Create base purchase with common fields
126
157
  const purchase: any = {
@@ -130,33 +161,59 @@ export function convertNitroPurchaseToPurchase(
130
161
  transactionReceipt: '', // Will be set by native layer
131
162
  purchaseToken: nitroPurchase.purchaseToken,
132
163
  platform: nitroPurchase.platform as 'ios' | 'android',
133
- }
164
+ // Common fields from NitroPurchase
165
+ quantity: nitroPurchase.quantity || 1,
166
+ purchaseState: nitroPurchase.purchaseState as PurchaseState || PurchaseState.unknown,
167
+ isAutoRenewing: nitroPurchase.isAutoRenewing || false,
168
+ };
134
169
 
135
170
  // Add platform-specific fields
136
171
  if (Platform.OS === 'ios') {
137
- const iosPurchase = purchase as any
138
- iosPurchase.quantityIOS = nitroPurchase.quantityIOS
172
+ const iosPurchase = purchase as any;
173
+ iosPurchase.quantityIOS = nitroPurchase.quantityIOS;
139
174
  iosPurchase.originalTransactionDateIOS =
140
- nitroPurchase.originalTransactionDateIOS
175
+ nitroPurchase.originalTransactionDateIOS;
141
176
  iosPurchase.originalTransactionIdentifierIOS =
142
- nitroPurchase.originalTransactionIdentifierIOS
143
- iosPurchase.appAccountToken = nitroPurchase.appAccountToken
177
+ nitroPurchase.originalTransactionIdentifierIOS;
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
- const androidPurchase = purchase as any
146
- androidPurchase.purchaseTokenAndroid = nitroPurchase.purchaseTokenAndroid
147
- androidPurchase.dataAndroid = nitroPurchase.dataAndroid
148
- androidPurchase.signatureAndroid = nitroPurchase.signatureAndroid
149
- androidPurchase.autoRenewingAndroid = nitroPurchase.autoRenewingAndroid
150
- androidPurchase.purchaseStateAndroid = nitroPurchase.purchaseStateAndroid
151
- androidPurchase.isAcknowledgedAndroid = nitroPurchase.isAcknowledgedAndroid
152
- androidPurchase.packageNameAndroid = nitroPurchase.packageNameAndroid
184
+ const androidPurchase = purchase as any;
185
+ androidPurchase.purchaseTokenAndroid = nitroPurchase.purchaseTokenAndroid;
186
+ androidPurchase.dataAndroid = nitroPurchase.dataAndroid;
187
+ androidPurchase.signatureAndroid = nitroPurchase.signatureAndroid;
188
+ // Support both old and new field names for backward compatibility
189
+ androidPurchase.autoRenewingAndroid = nitroPurchase.autoRenewingAndroid ?? nitroPurchase.isAutoRenewing;
190
+ // no longer surface purchaseStateAndroid on TS side
191
+ androidPurchase.isAcknowledgedAndroid = nitroPurchase.isAcknowledgedAndroid;
192
+ androidPurchase.packageNameAndroid = nitroPurchase.packageNameAndroid;
153
193
  androidPurchase.obfuscatedAccountIdAndroid =
154
- nitroPurchase.obfuscatedAccountIdAndroid
194
+ nitroPurchase.obfuscatedAccountIdAndroid;
155
195
  androidPurchase.obfuscatedProfileIdAndroid =
156
- nitroPurchase.obfuscatedProfileIdAndroid
196
+ nitroPurchase.obfuscatedProfileIdAndroid;
197
+
198
+ // Use the common isAutoRenewing field from NitroPurchase
199
+ purchase.isAutoRenewing = nitroPurchase.isAutoRenewing;
200
+
201
+ // Map numeric Android purchase state to common PurchaseState
202
+ switch (nitroPurchase.purchaseStateAndroid) {
203
+ case 1:
204
+ purchase.purchaseState = PurchaseState.purchased;
205
+ break;
206
+ case 2:
207
+ purchase.purchaseState = PurchaseState.pending;
208
+ break;
209
+ case 0:
210
+ default:
211
+ purchase.purchaseState = PurchaseState.unknown;
212
+ break;
213
+ }
157
214
  }
158
215
 
159
- return purchase as Purchase
216
+ return purchase as Purchase;
160
217
  }
161
218
 
162
219
  // Note: Use nitroPurchases.map(convertNitroPurchaseToPurchase) instead of a separate function
@@ -169,7 +226,10 @@ export function convertNitroPurchaseToPurchase(
169
226
  * Validate that a NitroProduct has all required fields for conversion
170
227
  */
171
228
  export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
172
- const required = ['id', 'title', 'description', 'type', 'platform']
229
+ if (!nitroProduct || typeof nitroProduct !== 'object') {
230
+ return false;
231
+ }
232
+ const required = ['id', 'title', 'description', 'type', 'platform'];
173
233
  for (const field of required) {
174
234
  if (
175
235
  !(field in nitroProduct) ||
@@ -177,19 +237,22 @@ export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
177
237
  ) {
178
238
  console.error(
179
239
  `NitroProduct missing required field: ${field}`,
180
- nitroProduct
181
- )
182
- return false
240
+ nitroProduct,
241
+ );
242
+ return false;
183
243
  }
184
244
  }
185
- return true
245
+ return true;
186
246
  }
187
247
 
188
248
  /**
189
249
  * Validate that a NitroPurchase has all required fields for conversion
190
250
  */
191
251
  export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
192
- const required = ['id', 'productId', 'transactionDate', 'platform']
252
+ if (!nitroPurchase || typeof nitroPurchase !== 'object') {
253
+ return false;
254
+ }
255
+ const required = ['id', 'productId', 'transactionDate', 'platform'];
193
256
  for (const field of required) {
194
257
  if (
195
258
  !(field in nitroPurchase) ||
@@ -197,12 +260,12 @@ export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
197
260
  ) {
198
261
  console.error(
199
262
  `NitroPurchase missing required field: ${field}`,
200
- nitroPurchase
201
- )
202
- return false
263
+ nitroPurchase,
264
+ );
265
+ return false;
203
266
  }
204
267
  }
205
- return true
268
+ return true;
206
269
  }
207
270
 
208
271
  // ============================================================================
@@ -214,10 +277,10 @@ export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
214
277
  * This function can be run in development to detect type mismatches
215
278
  */
216
279
  export function checkTypeSynchronization(): {
217
- isSync: boolean
218
- issues: string[]
280
+ isSync: boolean;
281
+ issues: string[];
219
282
  } {
220
- const issues: string[] = []
283
+ const issues: string[] = [];
221
284
 
222
285
  try {
223
286
  // Simple test: can we convert between types?
@@ -229,19 +292,19 @@ export function checkTypeSynchronization(): {
229
292
  platform: 'ios',
230
293
  displayPrice: '$1.00',
231
294
  currency: 'USD',
232
- }
295
+ };
233
296
 
234
- const converted = convertNitroProductToProduct(testNitroProduct)
297
+ const converted = convertNitroProductToProduct(testNitroProduct);
235
298
 
236
299
  if (!converted.id || !converted.title) {
237
- issues.push('Type conversion failed')
300
+ issues.push('Type conversion failed');
238
301
  }
239
302
  } catch (error) {
240
- issues.push(`Type conversion error: ${error}`)
303
+ issues.push(`Type conversion error: ${error}`);
241
304
  }
242
305
 
243
306
  return {
244
307
  isSync: issues.length === 0,
245
308
  issues,
246
- }
309
+ };
247
310
  }