react-native-iap 14.3.2 → 14.3.3

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 (47) hide show
  1. package/lib/module/helpers/subscription.js +2 -2
  2. package/lib/module/helpers/subscription.js.map +1 -1
  3. package/lib/module/hooks/useIAP.js +14 -8
  4. package/lib/module/hooks/useIAP.js.map +1 -1
  5. package/lib/module/index.js +76 -23
  6. package/lib/module/index.js.map +1 -1
  7. package/lib/module/types.js +90 -190
  8. package/lib/module/types.js.map +1 -1
  9. package/lib/module/utils/error.js +4 -4
  10. package/lib/module/utils/error.js.map +1 -1
  11. package/lib/module/utils/errorMapping.js +34 -10
  12. package/lib/module/utils/errorMapping.js.map +1 -1
  13. package/lib/module/utils/type-bridge.js +217 -173
  14. package/lib/module/utils/type-bridge.js.map +1 -1
  15. package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
  16. package/lib/typescript/src/hooks/useIAP.d.ts +8 -11
  17. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  18. package/lib/typescript/src/index.d.ts +11 -10
  19. package/lib/typescript/src/index.d.ts.map +1 -1
  20. package/lib/typescript/src/specs/RnIap.nitro.d.ts +2 -2
  21. package/lib/typescript/src/types.d.ts +606 -518
  22. package/lib/typescript/src/types.d.ts.map +1 -1
  23. package/lib/typescript/src/utils/errorMapping.d.ts +2 -1
  24. package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -1
  25. package/lib/typescript/src/utils/type-bridge.d.ts +13 -14
  26. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  27. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +4 -4
  28. package/nitrogen/generated/android/c++/{JNitroAndroidReceiptValidationOptions.hpp → JNitroReceiptValidationAndroidOptions.hpp} +9 -9
  29. package/nitrogen/generated/android/c++/JNitroReceiptValidationParams.hpp +5 -5
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/{NitroAndroidReceiptValidationOptions.kt → NitroReceiptValidationAndroidOptions.kt} +3 -3
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroReceiptValidationParams.kt +1 -1
  32. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +10 -10
  33. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -3
  34. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +3 -3
  35. package/nitrogen/generated/ios/swift/{NitroAndroidReceiptValidationOptions.swift → NitroReceiptValidationAndroidOptions.swift} +5 -5
  36. package/nitrogen/generated/ios/swift/NitroReceiptValidationParams.swift +9 -9
  37. package/nitrogen/generated/shared/c++/{NitroAndroidReceiptValidationOptions.hpp → NitroReceiptValidationAndroidOptions.hpp} +10 -10
  38. package/nitrogen/generated/shared/c++/NitroReceiptValidationParams.hpp +8 -8
  39. package/package.json +1 -1
  40. package/src/helpers/subscription.ts +8 -9
  41. package/src/hooks/useIAP.ts +52 -47
  42. package/src/index.ts +123 -35
  43. package/src/specs/RnIap.nitro.ts +2 -2
  44. package/src/types.ts +651 -616
  45. package/src/utils/error.ts +4 -4
  46. package/src/utils/errorMapping.ts +47 -19
  47. package/src/utils/type-bridge.ts +308 -204
@@ -1,10 +1,8 @@
1
1
  /**
2
2
  * Type Bridge Utilities
3
3
  *
4
- * This file provides conversion utilities between Nitro types (simple primitives)
5
- * and TypeScript union types (complex types.ts definitions).
6
- *
7
- * Purpose: Prevent type fragmentation between native (Nitro) and TypeScript sides
4
+ * Converts the loose Nitro shapes coming from native into the strongly typed
5
+ * structures that our generated TypeScript types expect.
8
6
  */
9
7
 
10
8
  import type {
@@ -12,258 +10,367 @@ import type {
12
10
  NitroPurchase,
13
11
  NitroSubscriptionStatus,
14
12
  } from '../specs/RnIap.nitro';
13
+ import {
14
+ Platform as IapPlatform,
15
+ PaymentModeIOS,
16
+ ProductType,
17
+ ProductTypeIOS,
18
+ PurchaseState,
19
+ SubscriptionPeriodIOS,
20
+ } from '../types';
15
21
  import type {
16
22
  Product,
23
+ ProductSubscription,
17
24
  Purchase,
18
- SubscriptionProduct,
19
25
  SubscriptionStatusIOS,
20
26
  } from '../types';
21
- import {PurchaseState, ProductTypeIOS} from '../types';
22
- import {Platform} from 'react-native';
23
27
 
24
- // ============================================================================
25
- // PRODUCT CONVERSION
26
- // ============================================================================
28
+ const PLATFORM_IOS = 'ios';
29
+ const PRODUCT_TYPE_SUBS = 'subs';
30
+ const PURCHASE_STATE_DEFERRED = 'deferred';
31
+ const PURCHASE_STATE_FAILED = 'failed';
32
+ const PURCHASE_STATE_PENDING = 'pending';
33
+ const PURCHASE_STATE_PURCHASED = 'purchased';
34
+ const PURCHASE_STATE_RESTORED = 'restored';
35
+ const DEFAULT_JSON_REPR = '{}';
36
+
37
+ type Nullable<T> = T | null | undefined;
38
+
39
+ function normalizePlatform(value?: Nullable<string>): IapPlatform {
40
+ return value?.toLowerCase() === PLATFORM_IOS
41
+ ? IapPlatform.Ios
42
+ : IapPlatform.Android;
43
+ }
44
+
45
+ function normalizeProductType(value?: Nullable<string>): ProductType {
46
+ return value?.toLowerCase() === PRODUCT_TYPE_SUBS
47
+ ? ProductType.Subs
48
+ : ProductType.InApp;
49
+ }
50
+
51
+ function normalizeProductTypeIOS(value?: Nullable<string>): ProductTypeIOS {
52
+ switch ((value ?? '').toLowerCase()) {
53
+ case 'consumable':
54
+ return ProductTypeIOS.Consumable;
55
+ case 'nonconsumable':
56
+ case 'non_consumable':
57
+ case 'non-consumable':
58
+ return ProductTypeIOS.NonConsumable;
59
+ case 'autorenewablesubscription':
60
+ case 'auto_renewable_subscription':
61
+ case 'autorenewable':
62
+ return ProductTypeIOS.AutoRenewableSubscription;
63
+ case 'nonrenewingsubscription':
64
+ case 'non_renewing_subscription':
65
+ return ProductTypeIOS.NonRenewingSubscription;
66
+ default:
67
+ if (value) {
68
+ console.warn(
69
+ `[react-native-iap] Unknown iOS product type "${value}", defaulting to NonConsumable.`,
70
+ );
71
+ }
72
+ return ProductTypeIOS.NonConsumable;
73
+ }
74
+ }
75
+
76
+ function normalizePaymentMode(value?: Nullable<string>): PaymentModeIOS | null {
77
+ switch ((value ?? '').toUpperCase()) {
78
+ case 'FREE_TRIAL':
79
+ case 'FREETRIAL':
80
+ return PaymentModeIOS.FreeTrial;
81
+ case 'PAY_AS_YOU_GO':
82
+ case 'PAYASYOUGO':
83
+ return PaymentModeIOS.PayAsYouGo;
84
+ case 'PAY_UP_FRONT':
85
+ case 'PAYUPFRONT':
86
+ return PaymentModeIOS.PayUpFront;
87
+ default:
88
+ return PaymentModeIOS.Empty;
89
+ }
90
+ }
91
+
92
+ function normalizeSubscriptionPeriod(
93
+ value?: Nullable<string>,
94
+ ): SubscriptionPeriodIOS | null {
95
+ switch ((value ?? '').toUpperCase()) {
96
+ case 'DAY':
97
+ return SubscriptionPeriodIOS.Day;
98
+ case 'WEEK':
99
+ return SubscriptionPeriodIOS.Week;
100
+ case 'MONTH':
101
+ return SubscriptionPeriodIOS.Month;
102
+ case 'YEAR':
103
+ return SubscriptionPeriodIOS.Year;
104
+ default:
105
+ return SubscriptionPeriodIOS.Empty;
106
+ }
107
+ }
108
+
109
+ function normalizePurchaseState(state: unknown): PurchaseState {
110
+ if (typeof state === 'string') {
111
+ switch (state.toLowerCase()) {
112
+ case PURCHASE_STATE_PURCHASED:
113
+ return PurchaseState.Purchased;
114
+ case PURCHASE_STATE_PENDING:
115
+ return PurchaseState.Pending;
116
+ case PURCHASE_STATE_FAILED:
117
+ return PurchaseState.Failed;
118
+ case PURCHASE_STATE_RESTORED:
119
+ return PurchaseState.Restored;
120
+ case PURCHASE_STATE_DEFERRED:
121
+ return PurchaseState.Deferred;
122
+ default:
123
+ return PurchaseState.Unknown;
124
+ }
125
+ }
126
+
127
+ if (typeof state === 'number') {
128
+ switch (state) {
129
+ case 1:
130
+ return PurchaseState.Purchased;
131
+ case 2:
132
+ return PurchaseState.Pending;
133
+ default:
134
+ return PurchaseState.Unknown;
135
+ }
136
+ }
137
+
138
+ return PurchaseState.Unknown;
139
+ }
140
+
141
+ function toNullableString(value: unknown): string | null {
142
+ if (value == null) return null;
143
+ return String(value);
144
+ }
145
+
146
+ function toNullableNumber(value: unknown): number | null {
147
+ if (value == null) return null;
148
+ const num = Number(value);
149
+ return Number.isFinite(num) ? num : null;
150
+ }
151
+
152
+ function toNullableBoolean(value: unknown): boolean | null {
153
+ if (value == null) return null;
154
+ if (typeof value === 'boolean') return value;
155
+ if (typeof value === 'number') return value !== 0;
156
+ if (typeof value === 'string') return value.toLowerCase() === 'true';
157
+ return null;
158
+ }
159
+
160
+ function parseSubscriptionOffers(value?: Nullable<string>) {
161
+ if (!value) return undefined;
162
+ try {
163
+ const parsed = JSON.parse(value);
164
+ if (Array.isArray(parsed)) {
165
+ return parsed;
166
+ }
167
+ } catch (error) {
168
+ console.warn('Failed to parse subscriptionOfferDetailsAndroid:', error);
169
+ }
170
+ return undefined;
171
+ }
27
172
 
28
173
  /**
29
- * Convert NitroProduct (from native) to TypeScript Product (for library consumers)
30
- * This ensures all fields are properly mapped and accessible
174
+ * Convert NitroProduct (from native) to generated Product type
31
175
  */
32
176
  export function convertNitroProductToProduct(
33
177
  nitroProduct: NitroProduct,
34
178
  ): Product {
35
- // Create base product with common fields, handling platform casting
36
- const product: any = {
179
+ const platform = normalizePlatform(nitroProduct.platform);
180
+ const type = normalizeProductType(nitroProduct.type);
181
+
182
+ const base: any = {
37
183
  id: nitroProduct.id,
38
184
  title: nitroProduct.title,
39
185
  description: nitroProduct.description,
40
- type: nitroProduct.type as 'inapp' | 'subs',
41
- displayName: nitroProduct.displayName,
42
- displayPrice: nitroProduct.displayPrice || '',
43
- currency: nitroProduct.currency || '',
44
- price: nitroProduct.price,
45
- platform: nitroProduct.platform as 'ios' | 'android',
186
+ type,
187
+ displayName: nitroProduct.displayName ?? null,
188
+ displayPrice: nitroProduct.displayPrice ?? '',
189
+ currency: nitroProduct.currency ?? '',
190
+ price: toNullableNumber(nitroProduct.price),
191
+ debugDescription: null,
192
+ platform,
46
193
  };
47
194
 
48
- // Add platform-specific fields based on current platform
49
- if (Platform.OS === 'ios') {
50
- // Map iOS fields from Nitro to TypeScript types
51
- const iosProduct = product as any; // Temporarily cast to access iOS fields
52
- iosProduct.isFamilyShareable = (nitroProduct as any).isFamilyShareableIOS;
53
- iosProduct.jsonRepresentation = (nitroProduct as any).jsonRepresentationIOS;
54
- // Detailed iOS product type - directly from the native field
55
- const typeIOSValue: string | undefined = (nitroProduct as any).typeIOS;
56
-
57
- switch (typeIOSValue) {
58
- case 'consumable':
59
- iosProduct.typeIOS = ProductTypeIOS.consumable;
60
- break;
61
- case 'nonConsumable':
62
- iosProduct.typeIOS = ProductTypeIOS.nonConsumable;
63
- break;
64
- case 'autoRenewableSubscription':
65
- iosProduct.typeIOS = ProductTypeIOS.autoRenewableSubscription;
66
- break;
67
- case 'nonRenewingSubscription':
68
- iosProduct.typeIOS = ProductTypeIOS.nonRenewingSubscription;
69
- break;
70
- default:
71
- iosProduct.typeIOS = undefined;
72
- }
73
- iosProduct.subscriptionPeriodUnitIOS =
74
- nitroProduct.subscriptionPeriodUnitIOS;
75
- iosProduct.subscriptionPeriodNumberIOS =
76
- nitroProduct.subscriptionPeriodNumberIOS;
77
- iosProduct.introductoryPriceIOS = nitroProduct.introductoryPriceIOS;
78
- iosProduct.introductoryPriceAsAmountIOS =
79
- nitroProduct.introductoryPriceAsAmountIOS;
80
- iosProduct.introductoryPricePaymentModeIOS =
81
- nitroProduct.introductoryPricePaymentModeIOS;
82
- iosProduct.introductoryPriceNumberOfPeriodsIOS =
83
- nitroProduct.introductoryPriceNumberOfPeriodsIOS;
195
+ if (platform === IapPlatform.Ios) {
196
+ const iosProduct: any = {
197
+ ...base,
198
+ displayNameIOS: nitroProduct.displayName ?? nitroProduct.title,
199
+ isFamilyShareableIOS: Boolean(
200
+ (nitroProduct as any).isFamilyShareableIOS ?? false,
201
+ ),
202
+ jsonRepresentationIOS:
203
+ (nitroProduct as any).jsonRepresentationIOS ?? DEFAULT_JSON_REPR,
204
+ typeIOS: normalizeProductTypeIOS((nitroProduct as any).typeIOS),
205
+ subscriptionInfoIOS: undefined,
206
+ };
207
+
208
+ iosProduct.introductoryPriceAsAmountIOS = toNullableString(
209
+ (nitroProduct as any).introductoryPriceAsAmountIOS,
210
+ );
211
+ iosProduct.introductoryPriceIOS = toNullableString(
212
+ (nitroProduct as any).introductoryPriceIOS,
213
+ );
214
+ iosProduct.introductoryPriceNumberOfPeriodsIOS = toNullableString(
215
+ (nitroProduct as any).introductoryPriceNumberOfPeriodsIOS,
216
+ );
217
+ iosProduct.introductoryPricePaymentModeIOS = normalizePaymentMode(
218
+ (nitroProduct as any).introductoryPricePaymentModeIOS,
219
+ );
84
220
  iosProduct.introductoryPriceSubscriptionPeriodIOS =
85
- nitroProduct.introductoryPriceSubscriptionPeriodIOS;
86
- } else if (Platform.OS === 'android') {
87
- // Map Android fields from Nitro to TypeScript types
88
- const androidProduct = product as any; // Temporarily cast to access Android fields
89
- androidProduct.originalPrice = (nitroProduct as any).originalPriceAndroid;
90
- androidProduct.originalPriceAmountMicros = (
91
- nitroProduct as any
92
- ).originalPriceAmountMicrosAndroid;
93
- androidProduct.introductoryPriceValue = (
94
- nitroProduct as any
95
- ).introductoryPriceValueAndroid;
96
- androidProduct.introductoryPriceCycles = (
97
- nitroProduct as any
98
- ).introductoryPriceCyclesAndroid;
99
- androidProduct.introductoryPricePeriod = (
100
- nitroProduct as any
101
- ).introductoryPricePeriodAndroid;
102
- androidProduct.subscriptionPeriod = (
103
- nitroProduct as any
104
- ).subscriptionPeriodAndroid;
105
- androidProduct.freeTrialPeriod = (
106
- nitroProduct as any
107
- ).freeTrialPeriodAndroid;
108
-
109
- // Map subscription offer details (parse from JSON string)
110
- if (nitroProduct.subscriptionOfferDetailsAndroid) {
111
- try {
112
- androidProduct.subscriptionOfferDetailsAndroid = JSON.parse(
113
- nitroProduct.subscriptionOfferDetailsAndroid,
114
- );
115
- } catch (e) {
116
- console.warn('Failed to parse subscription offer details:', e);
117
- androidProduct.subscriptionOfferDetailsAndroid = null;
118
- }
119
- }
221
+ normalizeSubscriptionPeriod(
222
+ (nitroProduct as any).introductoryPriceSubscriptionPeriodIOS,
223
+ );
224
+ iosProduct.subscriptionPeriodNumberIOS = toNullableString(
225
+ (nitroProduct as any).subscriptionPeriodNumberIOS,
226
+ );
227
+ iosProduct.subscriptionPeriodUnitIOS = normalizeSubscriptionPeriod(
228
+ (nitroProduct as any).subscriptionPeriodUnitIOS,
229
+ );
120
230
 
121
- // Create flattened offer fields for easier access in example code
122
- androidProduct.oneTimePurchaseOfferFormattedPrice =
123
- nitroProduct.displayPrice;
124
- androidProduct.oneTimePurchaseOfferPriceAmountMicros = (
125
- nitroProduct as any
126
- ).originalPriceAmountMicrosAndroid;
127
- androidProduct.oneTimePurchaseOfferPriceCurrencyCode =
128
- nitroProduct.currency;
231
+ return iosProduct as Product;
129
232
  }
130
233
 
131
- return product as Product;
132
- }
234
+ const androidProduct: any = {
235
+ ...base,
236
+ nameAndroid: (nitroProduct as any).nameAndroid ?? nitroProduct.title,
237
+ oneTimePurchaseOfferDetailsAndroid: (nitroProduct as any)
238
+ .oneTimePurchaseOfferDetailsAndroid,
239
+ subscriptionOfferDetailsAndroid: parseSubscriptionOffers(
240
+ (nitroProduct as any).subscriptionOfferDetailsAndroid,
241
+ ),
242
+ };
243
+
244
+ if (type === ProductType.Subs) {
245
+ if (!Array.isArray(androidProduct.subscriptionOfferDetailsAndroid)) {
246
+ androidProduct.subscriptionOfferDetailsAndroid = [];
247
+ }
248
+ }
133
249
 
134
- // Note: Use nitroProducts.map(convertNitroProductToProduct) instead of a separate function
250
+ return androidProduct as Product;
251
+ }
135
252
 
136
253
  /**
137
- * Convert Product to SubscriptionProduct (type-safe casting)
254
+ * Convert Product to ProductSubscription (type-safe casting helper)
138
255
  */
139
- export function convertProductToSubscriptionProduct(
256
+ export function convertProductToProductSubscription(
140
257
  product: Product,
141
- ): SubscriptionProduct {
142
- if (product.type !== 'subs') {
258
+ ): ProductSubscription {
259
+ if (product.type !== ProductType.Subs) {
143
260
  console.warn(
144
- 'Converting non-subscription product to SubscriptionProduct:',
261
+ 'Converting non-subscription product to ProductSubscription:',
145
262
  product.id,
146
263
  );
147
264
  }
148
- // Since SubscriptionProduct is now an intersection type, we need to cast properly
149
- return {
150
- ...product,
151
- type: 'subs' as const,
152
- } as SubscriptionProduct;
153
- }
154
265
 
155
- // ============================================================================
156
- // PURCHASE CONVERSION
157
- // ============================================================================
266
+ const output: any = {...(product as any)};
267
+
268
+ if (output.platform === IapPlatform.Android) {
269
+ if (!Array.isArray(output.subscriptionOfferDetailsAndroid)) {
270
+ output.subscriptionOfferDetailsAndroid = [];
271
+ }
272
+ }
273
+
274
+ return output;
275
+ }
158
276
 
159
277
  /**
160
- * Convert NitroPurchase (from native) to TypeScript Purchase (for library consumers)
278
+ * Convert NitroPurchase (from native) to generated Purchase type
161
279
  */
162
280
  export function convertNitroPurchaseToPurchase(
163
281
  nitroPurchase: NitroPurchase,
164
282
  ): Purchase {
165
- // Create base purchase with common fields
283
+ const platform = normalizePlatform(nitroPurchase.platform);
166
284
  const purchase: any = {
167
285
  id: nitroPurchase.id,
168
286
  productId: nitroPurchase.productId,
169
- transactionDate: nitroPurchase.transactionDate,
170
- // Unified token (iOS JWS, Android purchaseToken)
171
- purchaseToken: nitroPurchase.purchaseToken,
172
- platform: nitroPurchase.platform as 'ios' | 'android',
173
- // Common fields from NitroPurchase
174
- quantity: nitroPurchase.quantity || 1,
175
- purchaseState:
176
- (nitroPurchase.purchaseState as PurchaseState) || PurchaseState.unknown,
177
- isAutoRenewing: nitroPurchase.isAutoRenewing || false,
287
+ transactionDate: nitroPurchase.transactionDate ?? Date.now(),
288
+ purchaseToken:
289
+ nitroPurchase.purchaseToken ?? nitroPurchase.purchaseTokenAndroid ?? null,
290
+ platform,
291
+ quantity: nitroPurchase.quantity ?? 1,
292
+ purchaseState: normalizePurchaseState(
293
+ nitroPurchase.purchaseState ?? nitroPurchase.purchaseStateAndroid,
294
+ ),
295
+ isAutoRenewing: Boolean(nitroPurchase.isAutoRenewing),
178
296
  };
179
297
 
180
- // Add platform-specific fields
181
- if (Platform.OS === 'ios') {
182
- const iosPurchase = purchase as any;
183
- iosPurchase.quantityIOS = nitroPurchase.quantityIOS;
184
- iosPurchase.originalTransactionDateIOS =
185
- nitroPurchase.originalTransactionDateIOS;
186
- iosPurchase.originalTransactionIdentifierIOS =
187
- nitroPurchase.originalTransactionIdentifierIOS;
188
- iosPurchase.appAccountToken = nitroPurchase.appAccountToken;
189
- // Fill common quantity from iOS-specific quantity when available
190
- if (typeof nitroPurchase.quantityIOS === 'number') {
191
- purchase.quantity = nitroPurchase.quantityIOS;
192
- }
193
- } else if (Platform.OS === 'android') {
194
- const androidPurchase = purchase as any;
195
- androidPurchase.purchaseTokenAndroid = nitroPurchase.purchaseTokenAndroid;
196
- androidPurchase.dataAndroid = nitroPurchase.dataAndroid;
197
- androidPurchase.signatureAndroid = nitroPurchase.signatureAndroid;
198
- // Support both old and new field names for backward compatibility
199
- androidPurchase.autoRenewingAndroid =
200
- nitroPurchase.autoRenewingAndroid ?? nitroPurchase.isAutoRenewing;
201
- // no longer surface purchaseStateAndroid on TS side
202
- androidPurchase.isAcknowledgedAndroid = nitroPurchase.isAcknowledgedAndroid;
203
- androidPurchase.packageNameAndroid = nitroPurchase.packageNameAndroid;
204
- androidPurchase.obfuscatedAccountIdAndroid =
205
- nitroPurchase.obfuscatedAccountIdAndroid;
206
- androidPurchase.obfuscatedProfileIdAndroid =
207
- nitroPurchase.obfuscatedProfileIdAndroid;
208
-
209
- // Use the common isAutoRenewing field from NitroPurchase
210
- purchase.isAutoRenewing = nitroPurchase.isAutoRenewing;
211
-
212
- // Map numeric Android purchase state to common PurchaseState
213
- switch (nitroPurchase.purchaseStateAndroid) {
214
- case 1:
215
- purchase.purchaseState = PurchaseState.purchased;
216
- break;
217
- case 2:
218
- purchase.purchaseState = PurchaseState.pending;
219
- break;
220
- case 0:
221
- default:
222
- purchase.purchaseState = PurchaseState.unknown;
223
- break;
224
- }
298
+ if (
299
+ purchase.purchaseState === PurchaseState.Unknown &&
300
+ nitroPurchase.purchaseStateAndroid != null
301
+ ) {
302
+ purchase.purchaseState = normalizePurchaseState(
303
+ nitroPurchase.purchaseStateAndroid,
304
+ );
225
305
  }
226
306
 
227
- return purchase as Purchase;
228
- }
307
+ if (platform === IapPlatform.Ios) {
308
+ const iosPurchase: any = purchase;
309
+ iosPurchase.quantityIOS = toNullableNumber(nitroPurchase.quantityIOS);
310
+ iosPurchase.originalTransactionDateIOS = toNullableNumber(
311
+ nitroPurchase.originalTransactionDateIOS,
312
+ );
313
+ iosPurchase.originalTransactionIdentifierIOS = toNullableString(
314
+ nitroPurchase.originalTransactionIdentifierIOS,
315
+ );
316
+ iosPurchase.appAccountToken = toNullableString(
317
+ nitroPurchase.appAccountToken,
318
+ );
319
+ return iosPurchase as Purchase;
320
+ }
229
321
 
230
- // Note: Use nitroPurchases.map(convertNitroPurchaseToPurchase) instead of a separate function
322
+ const androidPurchase: any = purchase;
323
+ androidPurchase.autoRenewingAndroid = toNullableBoolean(
324
+ nitroPurchase.autoRenewingAndroid ?? nitroPurchase.isAutoRenewing,
325
+ );
326
+ androidPurchase.dataAndroid = toNullableString(nitroPurchase.dataAndroid);
327
+ androidPurchase.signatureAndroid = toNullableString(
328
+ nitroPurchase.signatureAndroid,
329
+ );
330
+ androidPurchase.isAcknowledgedAndroid = toNullableBoolean(
331
+ nitroPurchase.isAcknowledgedAndroid,
332
+ );
333
+ androidPurchase.packageNameAndroid = toNullableString(
334
+ nitroPurchase.packageNameAndroid,
335
+ );
336
+ androidPurchase.obfuscatedAccountIdAndroid = toNullableString(
337
+ nitroPurchase.obfuscatedAccountIdAndroid,
338
+ );
339
+ androidPurchase.obfuscatedProfileIdAndroid = toNullableString(
340
+ nitroPurchase.obfuscatedProfileIdAndroid,
341
+ );
231
342
 
232
- // ============================================================================
233
- // SUBSCRIPTION STATUS CONVERSION (iOS)
234
- // ============================================================================
343
+ return androidPurchase as Purchase;
344
+ }
235
345
 
346
+ /**
347
+ * Convert Nitro subscription status (iOS) to generated type
348
+ */
236
349
  export function convertNitroSubscriptionStatusToSubscriptionStatusIOS(
237
350
  nitro: NitroSubscriptionStatus,
238
351
  ): SubscriptionStatusIOS {
239
352
  return {
240
- state: nitro.state,
241
- platform: 'ios',
353
+ state: String(nitro.state ?? ''),
242
354
  renewalInfo: nitro.renewalInfo
243
355
  ? {
244
- autoRenewStatus: nitro.renewalInfo.autoRenewStatus,
245
- autoRenewPreference: nitro.renewalInfo.autoRenewPreference,
246
- expirationReason: nitro.renewalInfo.expirationReason,
247
- gracePeriodExpirationDate:
248
- nitro.renewalInfo.gracePeriodExpirationDate,
249
- currentProductID: nitro.renewalInfo.currentProductID,
250
- platform: 'ios',
356
+ autoRenewPreference: toNullableString(
357
+ nitro.renewalInfo.autoRenewPreference,
358
+ ),
359
+ jsonRepresentation: JSON.stringify(nitro.renewalInfo),
360
+ willAutoRenew: Boolean(nitro.renewalInfo.autoRenewStatus),
251
361
  }
252
362
  : undefined,
253
363
  };
254
364
  }
255
365
 
256
- // ============================================================================
257
- // TYPE VALIDATION
258
- // ============================================================================
259
-
260
366
  /**
261
- * Validate that a NitroProduct has all required fields for conversion
367
+ * Validate that a NitroProduct has the expected shape
262
368
  */
263
369
  export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
264
370
  if (!nitroProduct || typeof nitroProduct !== 'object') {
265
371
  return false;
266
372
  }
373
+
267
374
  const required = ['id', 'title', 'description', 'type', 'platform'];
268
375
  for (const field of required) {
269
376
  if (
@@ -277,16 +384,18 @@ export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
277
384
  return false;
278
385
  }
279
386
  }
387
+
280
388
  return true;
281
389
  }
282
390
 
283
391
  /**
284
- * Validate that a NitroPurchase has all required fields for conversion
392
+ * Validate that a NitroPurchase has the expected shape
285
393
  */
286
394
  export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
287
395
  if (!nitroPurchase || typeof nitroPurchase !== 'object') {
288
396
  return false;
289
397
  }
398
+
290
399
  const required = ['id', 'productId', 'transactionDate', 'platform'];
291
400
  for (const field of required) {
292
401
  if (
@@ -300,16 +409,12 @@ export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
300
409
  return false;
301
410
  }
302
411
  }
412
+
303
413
  return true;
304
414
  }
305
415
 
306
- // ============================================================================
307
- // TYPE SYNCHRONIZATION HELPERS
308
- // ============================================================================
309
-
310
416
  /**
311
- * Check if Nitro types and TypeScript types are synchronized
312
- * This function can be run in development to detect type mismatches
417
+ * Development helper to check that type conversions stay valid
313
418
  */
314
419
  export function checkTypeSynchronization(): {
315
420
  isSync: boolean;
@@ -318,24 +423,23 @@ export function checkTypeSynchronization(): {
318
423
  const issues: string[] = [];
319
424
 
320
425
  try {
321
- // Simple test: can we convert between types?
322
426
  const testNitroProduct: NitroProduct = {
323
427
  id: 'test',
324
428
  title: 'Test',
325
- description: 'Test',
429
+ description: 'Test product',
326
430
  type: 'inapp',
327
- platform: 'ios',
431
+ platform: PLATFORM_IOS,
328
432
  displayPrice: '$1.00',
329
433
  currency: 'USD',
434
+ price: 1,
330
435
  };
331
436
 
332
437
  const converted = convertNitroProductToProduct(testNitroProduct);
333
-
334
438
  if (!converted.id || !converted.title) {
335
439
  issues.push('Type conversion failed');
336
440
  }
337
441
  } catch (error) {
338
- issues.push(`Type conversion error: ${error}`);
442
+ issues.push(`Type conversion error: ${String(error)}`);
339
443
  }
340
444
 
341
445
  return {