react-native-iap 14.1.1-rc.1 → 14.2.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 (46) hide show
  1. package/NitroIap.podspec +2 -0
  2. package/README.md +9 -9
  3. package/android/build.gradle +2 -1
  4. package/android/consumer-rules.pro +7 -0
  5. package/app.plugin.js +1 -1
  6. package/ios/HybridRnIap.swift +380 -1192
  7. package/lib/module/helpers/subscription.js.map +1 -1
  8. package/lib/module/hooks/useIAP.js +74 -58
  9. package/lib/module/hooks/useIAP.js.map +1 -1
  10. package/lib/module/index.js +20 -3
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/types.js +8 -0
  13. package/lib/module/types.js.map +1 -1
  14. package/lib/module/utils/error.js.map +1 -1
  15. package/lib/module/utils/errorMapping.js +33 -0
  16. package/lib/module/utils/errorMapping.js.map +1 -0
  17. package/lib/module/utils/type-bridge.js +19 -0
  18. package/lib/module/utils/type-bridge.js.map +1 -1
  19. package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
  20. package/lib/typescript/src/hooks/useIAP.d.ts +4 -4
  21. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  22. package/lib/typescript/src/index.d.ts +7 -3
  23. package/lib/typescript/src/index.d.ts.map +1 -1
  24. package/lib/typescript/src/types.d.ts +19 -0
  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/errorMapping.d.ts +5 -0
  28. package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -0
  29. package/lib/typescript/src/utils/type-bridge.d.ts +3 -2
  30. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  31. package/package.json +5 -2
  32. package/plugin/tsconfig.tsbuildinfo +1 -1
  33. package/src/helpers/subscription.ts +30 -30
  34. package/src/hooks/useIAP.ts +252 -230
  35. package/src/index.ts +366 -340
  36. package/src/types.ts +21 -0
  37. package/src/utils/error.ts +19 -19
  38. package/src/utils/errorMapping.ts +44 -0
  39. package/src/utils/type-bridge.ts +127 -93
  40. package/ios/ErrorUtils.swift +0 -153
  41. package/ios/ProductStore.swift +0 -43
  42. package/ios/reactnativeiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  43. package/ios/reactnativeiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  44. package/plugin/build/src/withIAP.d.ts +0 -3
  45. package/plugin/build/src/withIAP.js +0 -81
  46. package/plugin/build/tsconfig.tsbuildinfo +0 -1
package/src/types.ts CHANGED
@@ -211,6 +211,27 @@ export type PurchaseIOS = PurchaseCommon & {
211
211
  jwsRepresentationIOS?: string
212
212
  }
213
213
 
214
+ /**
215
+ * iOS subscription renewal info
216
+ */
217
+ export interface SubscriptionRenewalInfoIOS {
218
+ autoRenewStatus: boolean
219
+ autoRenewPreference?: string
220
+ expirationReason?: number
221
+ gracePeriodExpirationDate?: number
222
+ currentProductID?: string
223
+ platform: 'ios'
224
+ }
225
+
226
+ /**
227
+ * iOS subscription status entry
228
+ */
229
+ export interface SubscriptionStatusIOS {
230
+ state: number
231
+ platform: 'ios'
232
+ renewalInfo?: SubscriptionRenewalInfoIOS
233
+ }
234
+
214
235
  // ============================================================================
215
236
  // ANDROID TYPES
216
237
  // ============================================================================
@@ -2,15 +2,15 @@
2
2
  * Error utilities for parsing platform-specific error responses
3
3
  */
4
4
 
5
- import {ErrorCode} from '../types';
5
+ import { ErrorCode } from '../types'
6
6
 
7
7
  export interface IapError {
8
- code: string;
9
- message: string;
10
- responseCode?: number;
11
- debugMessage?: string;
12
- productId?: string;
13
- [key: string]: any; // Allow additional platform-specific fields
8
+ code: string
9
+ message: string
10
+ responseCode?: number
11
+ debugMessage?: string
12
+ productId?: string
13
+ [key: string]: any // Allow additional platform-specific fields
14
14
  }
15
15
 
16
16
  /**
@@ -25,11 +25,11 @@ export interface IapError {
25
25
  * @returns Parsed error object with code and message
26
26
  */
27
27
  export function parseErrorStringToJsonObj(
28
- errorString: string | Error | unknown,
28
+ errorString: string | Error | unknown
29
29
  ): IapError {
30
30
  // Handle Error objects
31
31
  if (errorString instanceof Error) {
32
- errorString = errorString.message;
32
+ errorString = errorString.message
33
33
  }
34
34
 
35
35
  // Handle non-string inputs
@@ -37,35 +37,35 @@ export function parseErrorStringToJsonObj(
37
37
  return {
38
38
  code: ErrorCode.E_UNKNOWN,
39
39
  message: 'Unknown error occurred',
40
- };
40
+ }
41
41
  }
42
42
 
43
43
  // Try to parse as JSON first
44
44
  try {
45
- const parsed = JSON.parse(errorString);
45
+ const parsed = JSON.parse(errorString)
46
46
  if (typeof parsed === 'object' && parsed !== null) {
47
47
  // Ensure it has at least code and message
48
48
  return {
49
49
  code: parsed.code || ErrorCode.E_UNKNOWN,
50
50
  message: parsed.message || errorString,
51
51
  ...parsed,
52
- };
52
+ }
53
53
  }
54
54
  } catch {
55
55
  // Not JSON, continue with other formats
56
56
  }
57
57
 
58
58
  // Try to parse "CODE: message" format
59
- const colonIndex = errorString.indexOf(':');
59
+ const colonIndex = errorString.indexOf(':')
60
60
  if (colonIndex > 0 && colonIndex < 50) {
61
61
  // Reasonable position for error code
62
- const potentialCode = errorString.substring(0, colonIndex).trim();
62
+ const potentialCode = errorString.substring(0, colonIndex).trim()
63
63
  // Check if it looks like an error code (starts with E_ or contains uppercase)
64
64
  if (potentialCode.startsWith('E_') || /^[A-Z_]+$/.test(potentialCode)) {
65
65
  return {
66
66
  code: potentialCode,
67
67
  message: errorString.substring(colonIndex + 1).trim(),
68
- };
68
+ }
69
69
  }
70
70
  }
71
71
 
@@ -73,7 +73,7 @@ export function parseErrorStringToJsonObj(
73
73
  return {
74
74
  code: ErrorCode.E_UNKNOWN,
75
75
  message: errorString,
76
- };
76
+ }
77
77
  }
78
78
 
79
79
  /**
@@ -82,16 +82,16 @@ export function parseErrorStringToJsonObj(
82
82
  * @returns true if the error is a user cancellation
83
83
  */
84
84
  export function isUserCancelledError(
85
- error: IapError | string | Error | unknown,
85
+ error: IapError | string | Error | unknown
86
86
  ): boolean {
87
87
  const errorObj =
88
88
  typeof error === 'object' && error !== null && 'code' in error
89
89
  ? (error as IapError)
90
- : parseErrorStringToJsonObj(error);
90
+ : parseErrorStringToJsonObj(error)
91
91
 
92
92
  return (
93
93
  errorObj.code === ErrorCode.E_USER_CANCELLED ||
94
94
  errorObj.code === 'E_USER_CANCELED' || // Alternative spelling
95
95
  errorObj.responseCode === 1
96
- ); // Android BillingClient.BillingResponseCode.USER_CANCELED
96
+ ) // Android BillingClient.BillingResponseCode.USER_CANCELED
97
97
  }
@@ -0,0 +1,44 @@
1
+ import { ErrorCode, type PurchaseError } from '../types'
2
+
3
+ export function isUserCancelledError(error: PurchaseError): boolean {
4
+ return (
5
+ error.code === ErrorCode.E_USER_CANCELLED ||
6
+ error.code === 'E_USER_CANCELED'
7
+ )
8
+ }
9
+
10
+ export function isRecoverableError(error: PurchaseError): boolean {
11
+ const recoverable = new Set<string>([
12
+ ErrorCode.E_NETWORK_ERROR,
13
+ ErrorCode.E_SERVICE_ERROR,
14
+ ErrorCode.E_REMOTE_ERROR,
15
+ ErrorCode.E_CONNECTION_CLOSED,
16
+ ErrorCode.E_SERVICE_DISCONNECTED,
17
+ ErrorCode.E_INIT_CONNECTION,
18
+ ErrorCode.E_SYNC_ERROR,
19
+ ])
20
+ return recoverable.has(error.code)
21
+ }
22
+
23
+ export function getUserFriendlyErrorMessage(error: PurchaseError): string {
24
+ switch (error.code) {
25
+ case ErrorCode.E_USER_CANCELLED:
26
+ return 'Purchase cancelled'
27
+ case ErrorCode.E_NETWORK_ERROR:
28
+ return 'Network connection error'
29
+ case ErrorCode.E_SERVICE_ERROR:
30
+ return 'Store service error'
31
+ case ErrorCode.E_REMOTE_ERROR:
32
+ return 'Remote service error'
33
+ case ErrorCode.E_IAP_NOT_AVAILABLE:
34
+ return 'In-app purchases are not available on this device'
35
+ case ErrorCode.E_DEFERRED_PAYMENT:
36
+ return 'Payment was deferred (pending approval)'
37
+ case ErrorCode.E_TRANSACTION_VALIDATION_FAILED:
38
+ return 'Transaction validation failed'
39
+ case ErrorCode.E_SKU_NOT_FOUND:
40
+ return 'Product not found'
41
+ default:
42
+ return error.message || 'Unknown error occurred'
43
+ }
44
+ }
@@ -7,10 +7,19 @@
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 {PurchaseState, ProductTypeIOS} from '../types';
13
- import {Platform} from 'react-native';
10
+ import type {
11
+ NitroProduct,
12
+ NitroPurchase,
13
+ NitroSubscriptionStatus,
14
+ } from '../specs/RnIap.nitro'
15
+ import type {
16
+ Product,
17
+ Purchase,
18
+ SubscriptionProduct,
19
+ SubscriptionStatusIOS,
20
+ } from '../types'
21
+ import { PurchaseState, ProductTypeIOS } from '../types'
22
+ import { Platform } from 'react-native'
14
23
 
15
24
  // ============================================================================
16
25
  // PRODUCT CONVERSION
@@ -21,7 +30,7 @@ import {Platform} from 'react-native';
21
30
  * This ensures all fields are properly mapped and accessible
22
31
  */
23
32
  export function convertNitroProductToProduct(
24
- nitroProduct: NitroProduct,
33
+ nitroProduct: NitroProduct
25
34
  ): Product {
26
35
  // Create base product with common fields, handling platform casting
27
36
  const product: any = {
@@ -34,92 +43,91 @@ export function convertNitroProductToProduct(
34
43
  currency: nitroProduct.currency || '',
35
44
  price: nitroProduct.price,
36
45
  platform: nitroProduct.platform as 'ios' | 'android',
37
- };
46
+ }
38
47
 
39
48
  // Add platform-specific fields based on current platform
40
49
  if (Platform.OS === 'ios') {
41
50
  // Map iOS fields from Nitro to TypeScript types
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;
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
45
54
  // Detailed iOS product type - directly from the native field
46
- const typeIOSValue: string | undefined = (nitroProduct as any).typeIOS;
55
+ const typeIOSValue: string | undefined = (nitroProduct as any).typeIOS
47
56
 
48
57
  switch (typeIOSValue) {
49
58
  case 'consumable':
50
- iosProduct.typeIOS = ProductTypeIOS.consumable;
51
- break;
59
+ iosProduct.typeIOS = ProductTypeIOS.consumable
60
+ break
52
61
  case 'nonConsumable':
53
- iosProduct.typeIOS = ProductTypeIOS.nonConsumable;
54
- break;
62
+ iosProduct.typeIOS = ProductTypeIOS.nonConsumable
63
+ break
55
64
  case 'autoRenewableSubscription':
56
- iosProduct.typeIOS = ProductTypeIOS.autoRenewableSubscription;
57
- break;
65
+ iosProduct.typeIOS = ProductTypeIOS.autoRenewableSubscription
66
+ break
58
67
  case 'nonRenewingSubscription':
59
- iosProduct.typeIOS = ProductTypeIOS.nonRenewingSubscription;
60
- break;
68
+ iosProduct.typeIOS = ProductTypeIOS.nonRenewingSubscription
69
+ break
61
70
  default:
62
- iosProduct.typeIOS = undefined;
71
+ iosProduct.typeIOS = undefined
63
72
  }
64
73
  iosProduct.subscriptionPeriodUnitIOS =
65
- nitroProduct.subscriptionPeriodUnitIOS;
74
+ nitroProduct.subscriptionPeriodUnitIOS
66
75
  iosProduct.subscriptionPeriodNumberIOS =
67
- nitroProduct.subscriptionPeriodNumberIOS;
68
- iosProduct.introductoryPriceIOS = nitroProduct.introductoryPriceIOS;
76
+ nitroProduct.subscriptionPeriodNumberIOS
77
+ iosProduct.introductoryPriceIOS = nitroProduct.introductoryPriceIOS
69
78
  iosProduct.introductoryPriceAsAmountIOS =
70
- nitroProduct.introductoryPriceAsAmountIOS;
79
+ nitroProduct.introductoryPriceAsAmountIOS
71
80
  iosProduct.introductoryPricePaymentModeIOS =
72
- nitroProduct.introductoryPricePaymentModeIOS;
81
+ nitroProduct.introductoryPricePaymentModeIOS
73
82
  iosProduct.introductoryPriceNumberOfPeriodsIOS =
74
- nitroProduct.introductoryPriceNumberOfPeriodsIOS;
83
+ nitroProduct.introductoryPriceNumberOfPeriodsIOS
75
84
  iosProduct.introductoryPriceSubscriptionPeriodIOS =
76
- nitroProduct.introductoryPriceSubscriptionPeriodIOS;
85
+ nitroProduct.introductoryPriceSubscriptionPeriodIOS
77
86
  } else if (Platform.OS === 'android') {
78
87
  // Map Android fields from Nitro to TypeScript types
79
- const androidProduct = product as any; // Temporarily cast to access Android fields
80
- androidProduct.originalPrice = (nitroProduct as any).originalPriceAndroid;
88
+ const androidProduct = product as any // Temporarily cast to access Android fields
89
+ androidProduct.originalPrice = (nitroProduct as any).originalPriceAndroid
81
90
  androidProduct.originalPriceAmountMicros = (
82
91
  nitroProduct as any
83
- ).originalPriceAmountMicrosAndroid;
92
+ ).originalPriceAmountMicrosAndroid
84
93
  androidProduct.introductoryPriceValue = (
85
94
  nitroProduct as any
86
- ).introductoryPriceValueAndroid;
95
+ ).introductoryPriceValueAndroid
87
96
  androidProduct.introductoryPriceCycles = (
88
97
  nitroProduct as any
89
- ).introductoryPriceCyclesAndroid;
98
+ ).introductoryPriceCyclesAndroid
90
99
  androidProduct.introductoryPricePeriod = (
91
100
  nitroProduct as any
92
- ).introductoryPricePeriodAndroid;
101
+ ).introductoryPricePeriodAndroid
93
102
  androidProduct.subscriptionPeriod = (
94
103
  nitroProduct as any
95
- ).subscriptionPeriodAndroid;
104
+ ).subscriptionPeriodAndroid
96
105
  androidProduct.freeTrialPeriod = (
97
106
  nitroProduct as any
98
- ).freeTrialPeriodAndroid;
107
+ ).freeTrialPeriodAndroid
99
108
 
100
109
  // Map subscription offer details (parse from JSON string)
101
110
  if (nitroProduct.subscriptionOfferDetailsAndroid) {
102
111
  try {
103
112
  androidProduct.subscriptionOfferDetailsAndroid = JSON.parse(
104
- nitroProduct.subscriptionOfferDetailsAndroid,
105
- );
113
+ nitroProduct.subscriptionOfferDetailsAndroid
114
+ )
106
115
  } catch (e) {
107
- console.warn('Failed to parse subscription offer details:', e);
108
- androidProduct.subscriptionOfferDetailsAndroid = null;
116
+ console.warn('Failed to parse subscription offer details:', e)
117
+ androidProduct.subscriptionOfferDetailsAndroid = null
109
118
  }
110
119
  }
111
120
 
112
121
  // Create flattened offer fields for easier access in example code
113
122
  androidProduct.oneTimePurchaseOfferFormattedPrice =
114
- nitroProduct.displayPrice;
123
+ nitroProduct.displayPrice
115
124
  androidProduct.oneTimePurchaseOfferPriceAmountMicros = (
116
125
  nitroProduct as any
117
- ).originalPriceAmountMicrosAndroid;
118
- androidProduct.oneTimePurchaseOfferPriceCurrencyCode =
119
- nitroProduct.currency;
126
+ ).originalPriceAmountMicrosAndroid
127
+ androidProduct.oneTimePurchaseOfferPriceCurrencyCode = nitroProduct.currency
120
128
  }
121
129
 
122
- return product as Product;
130
+ return product as Product
123
131
  }
124
132
 
125
133
  // Note: Use nitroProducts.map(convertNitroProductToProduct) instead of a separate function
@@ -128,19 +136,19 @@ export function convertNitroProductToProduct(
128
136
  * Convert Product to SubscriptionProduct (type-safe casting)
129
137
  */
130
138
  export function convertProductToSubscriptionProduct(
131
- product: Product,
139
+ product: Product
132
140
  ): SubscriptionProduct {
133
141
  if (product.type !== 'subs') {
134
142
  console.warn(
135
143
  'Converting non-subscription product to SubscriptionProduct:',
136
- product.id,
137
- );
144
+ product.id
145
+ )
138
146
  }
139
147
  // Since SubscriptionProduct is now an intersection type, we need to cast properly
140
148
  return {
141
149
  ...product,
142
150
  type: 'subs' as const,
143
- } as SubscriptionProduct;
151
+ } as SubscriptionProduct
144
152
  }
145
153
 
146
154
  // ============================================================================
@@ -151,7 +159,7 @@ export function convertProductToSubscriptionProduct(
151
159
  * Convert NitroPurchase (from native) to TypeScript Purchase (for library consumers)
152
160
  */
153
161
  export function convertNitroPurchaseToPurchase(
154
- nitroPurchase: NitroPurchase,
162
+ nitroPurchase: NitroPurchase
155
163
  ): Purchase {
156
164
  // Create base purchase with common fields
157
165
  const purchase: any = {
@@ -163,61 +171,87 @@ export function convertNitroPurchaseToPurchase(
163
171
  platform: nitroPurchase.platform as 'ios' | 'android',
164
172
  // Common fields from NitroPurchase
165
173
  quantity: nitroPurchase.quantity || 1,
166
- purchaseState: nitroPurchase.purchaseState as PurchaseState || PurchaseState.unknown,
174
+ purchaseState:
175
+ (nitroPurchase.purchaseState as PurchaseState) || PurchaseState.unknown,
167
176
  isAutoRenewing: nitroPurchase.isAutoRenewing || false,
168
- };
177
+ }
169
178
 
170
179
  // Add platform-specific fields
171
180
  if (Platform.OS === 'ios') {
172
- const iosPurchase = purchase as any;
173
- iosPurchase.quantityIOS = nitroPurchase.quantityIOS;
181
+ const iosPurchase = purchase as any
182
+ iosPurchase.quantityIOS = nitroPurchase.quantityIOS
174
183
  iosPurchase.originalTransactionDateIOS =
175
- nitroPurchase.originalTransactionDateIOS;
184
+ nitroPurchase.originalTransactionDateIOS
176
185
  iosPurchase.originalTransactionIdentifierIOS =
177
- nitroPurchase.originalTransactionIdentifierIOS;
178
- iosPurchase.appAccountToken = nitroPurchase.appAccountToken;
186
+ nitroPurchase.originalTransactionIdentifierIOS
187
+ iosPurchase.appAccountToken = nitroPurchase.appAccountToken
179
188
  // Fill common quantity from iOS-specific quantity when available
180
189
  if (typeof nitroPurchase.quantityIOS === 'number') {
181
- purchase.quantity = nitroPurchase.quantityIOS;
190
+ purchase.quantity = nitroPurchase.quantityIOS
182
191
  }
183
192
  } else if (Platform.OS === 'android') {
184
- const androidPurchase = purchase as any;
185
- androidPurchase.purchaseTokenAndroid = nitroPurchase.purchaseTokenAndroid;
186
- androidPurchase.dataAndroid = nitroPurchase.dataAndroid;
187
- androidPurchase.signatureAndroid = nitroPurchase.signatureAndroid;
193
+ const androidPurchase = purchase as any
194
+ androidPurchase.purchaseTokenAndroid = nitroPurchase.purchaseTokenAndroid
195
+ androidPurchase.dataAndroid = nitroPurchase.dataAndroid
196
+ androidPurchase.signatureAndroid = nitroPurchase.signatureAndroid
188
197
  // Support both old and new field names for backward compatibility
189
- androidPurchase.autoRenewingAndroid = nitroPurchase.autoRenewingAndroid ?? nitroPurchase.isAutoRenewing;
198
+ androidPurchase.autoRenewingAndroid =
199
+ nitroPurchase.autoRenewingAndroid ?? nitroPurchase.isAutoRenewing
190
200
  // no longer surface purchaseStateAndroid on TS side
191
- androidPurchase.isAcknowledgedAndroid = nitroPurchase.isAcknowledgedAndroid;
192
- androidPurchase.packageNameAndroid = nitroPurchase.packageNameAndroid;
201
+ androidPurchase.isAcknowledgedAndroid = nitroPurchase.isAcknowledgedAndroid
202
+ androidPurchase.packageNameAndroid = nitroPurchase.packageNameAndroid
193
203
  androidPurchase.obfuscatedAccountIdAndroid =
194
- nitroPurchase.obfuscatedAccountIdAndroid;
204
+ nitroPurchase.obfuscatedAccountIdAndroid
195
205
  androidPurchase.obfuscatedProfileIdAndroid =
196
- nitroPurchase.obfuscatedProfileIdAndroid;
206
+ nitroPurchase.obfuscatedProfileIdAndroid
197
207
 
198
208
  // Use the common isAutoRenewing field from NitroPurchase
199
- purchase.isAutoRenewing = nitroPurchase.isAutoRenewing;
209
+ purchase.isAutoRenewing = nitroPurchase.isAutoRenewing
200
210
 
201
211
  // Map numeric Android purchase state to common PurchaseState
202
212
  switch (nitroPurchase.purchaseStateAndroid) {
203
213
  case 1:
204
- purchase.purchaseState = PurchaseState.purchased;
205
- break;
214
+ purchase.purchaseState = PurchaseState.purchased
215
+ break
206
216
  case 2:
207
- purchase.purchaseState = PurchaseState.pending;
208
- break;
217
+ purchase.purchaseState = PurchaseState.pending
218
+ break
209
219
  case 0:
210
220
  default:
211
- purchase.purchaseState = PurchaseState.unknown;
212
- break;
221
+ purchase.purchaseState = PurchaseState.unknown
222
+ break
213
223
  }
214
224
  }
215
225
 
216
- return purchase as Purchase;
226
+ return purchase as Purchase
217
227
  }
218
228
 
219
229
  // Note: Use nitroPurchases.map(convertNitroPurchaseToPurchase) instead of a separate function
220
230
 
231
+ // ============================================================================
232
+ // SUBSCRIPTION STATUS CONVERSION (iOS)
233
+ // ============================================================================
234
+
235
+ export function convertNitroSubscriptionStatusToSubscriptionStatusIOS(
236
+ nitro: NitroSubscriptionStatus
237
+ ): SubscriptionStatusIOS {
238
+ return {
239
+ state: nitro.state,
240
+ platform: 'ios',
241
+ renewalInfo: nitro.renewalInfo
242
+ ? {
243
+ autoRenewStatus: nitro.renewalInfo.autoRenewStatus,
244
+ autoRenewPreference: nitro.renewalInfo.autoRenewPreference,
245
+ expirationReason: nitro.renewalInfo.expirationReason,
246
+ gracePeriodExpirationDate:
247
+ nitro.renewalInfo.gracePeriodExpirationDate,
248
+ currentProductID: nitro.renewalInfo.currentProductID,
249
+ platform: 'ios',
250
+ }
251
+ : undefined,
252
+ }
253
+ }
254
+
221
255
  // ============================================================================
222
256
  // TYPE VALIDATION
223
257
  // ============================================================================
@@ -227,9 +261,9 @@ export function convertNitroPurchaseToPurchase(
227
261
  */
228
262
  export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
229
263
  if (!nitroProduct || typeof nitroProduct !== 'object') {
230
- return false;
264
+ return false
231
265
  }
232
- const required = ['id', 'title', 'description', 'type', 'platform'];
266
+ const required = ['id', 'title', 'description', 'type', 'platform']
233
267
  for (const field of required) {
234
268
  if (
235
269
  !(field in nitroProduct) ||
@@ -237,12 +271,12 @@ export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
237
271
  ) {
238
272
  console.error(
239
273
  `NitroProduct missing required field: ${field}`,
240
- nitroProduct,
241
- );
242
- return false;
274
+ nitroProduct
275
+ )
276
+ return false
243
277
  }
244
278
  }
245
- return true;
279
+ return true
246
280
  }
247
281
 
248
282
  /**
@@ -250,9 +284,9 @@ export function validateNitroProduct(nitroProduct: NitroProduct): boolean {
250
284
  */
251
285
  export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
252
286
  if (!nitroPurchase || typeof nitroPurchase !== 'object') {
253
- return false;
287
+ return false
254
288
  }
255
- const required = ['id', 'productId', 'transactionDate', 'platform'];
289
+ const required = ['id', 'productId', 'transactionDate', 'platform']
256
290
  for (const field of required) {
257
291
  if (
258
292
  !(field in nitroPurchase) ||
@@ -260,12 +294,12 @@ export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
260
294
  ) {
261
295
  console.error(
262
296
  `NitroPurchase missing required field: ${field}`,
263
- nitroPurchase,
264
- );
265
- return false;
297
+ nitroPurchase
298
+ )
299
+ return false
266
300
  }
267
301
  }
268
- return true;
302
+ return true
269
303
  }
270
304
 
271
305
  // ============================================================================
@@ -277,10 +311,10 @@ export function validateNitroPurchase(nitroPurchase: NitroPurchase): boolean {
277
311
  * This function can be run in development to detect type mismatches
278
312
  */
279
313
  export function checkTypeSynchronization(): {
280
- isSync: boolean;
281
- issues: string[];
314
+ isSync: boolean
315
+ issues: string[]
282
316
  } {
283
- const issues: string[] = [];
317
+ const issues: string[] = []
284
318
 
285
319
  try {
286
320
  // Simple test: can we convert between types?
@@ -292,19 +326,19 @@ export function checkTypeSynchronization(): {
292
326
  platform: 'ios',
293
327
  displayPrice: '$1.00',
294
328
  currency: 'USD',
295
- };
329
+ }
296
330
 
297
- const converted = convertNitroProductToProduct(testNitroProduct);
331
+ const converted = convertNitroProductToProduct(testNitroProduct)
298
332
 
299
333
  if (!converted.id || !converted.title) {
300
- issues.push('Type conversion failed');
334
+ issues.push('Type conversion failed')
301
335
  }
302
336
  } catch (error) {
303
- issues.push(`Type conversion error: ${error}`);
337
+ issues.push(`Type conversion error: ${error}`)
304
338
  }
305
339
 
306
340
  return {
307
341
  isSync: issues.length === 0,
308
342
  issues,
309
- };
343
+ }
310
344
  }