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.
- package/NitroIap.podspec +2 -0
- package/README.md +9 -9
- package/android/build.gradle +2 -1
- package/android/consumer-rules.pro +7 -0
- package/app.plugin.js +1 -1
- package/ios/HybridRnIap.swift +380 -1192
- package/lib/module/helpers/subscription.js.map +1 -1
- package/lib/module/hooks/useIAP.js +74 -58
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +20 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +8 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/utils/errorMapping.js +33 -0
- package/lib/module/utils/errorMapping.js.map +1 -0
- package/lib/module/utils/type-bridge.js +19 -0
- package/lib/module/utils/type-bridge.js.map +1 -1
- package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +4 -4
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +19 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/error.d.ts.map +1 -1
- package/lib/typescript/src/utils/errorMapping.d.ts +5 -0
- package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -0
- package/lib/typescript/src/utils/type-bridge.d.ts +3 -2
- package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
- package/package.json +5 -2
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/helpers/subscription.ts +30 -30
- package/src/hooks/useIAP.ts +252 -230
- package/src/index.ts +366 -340
- package/src/types.ts +21 -0
- package/src/utils/error.ts +19 -19
- package/src/utils/errorMapping.ts +44 -0
- package/src/utils/type-bridge.ts +127 -93
- package/ios/ErrorUtils.swift +0 -153
- package/ios/ProductStore.swift +0 -43
- package/ios/reactnativeiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/reactnativeiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/plugin/build/src/withIAP.d.ts +0 -3
- package/plugin/build/src/withIAP.js +0 -81
- 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
|
// ============================================================================
|
package/src/utils/error.ts
CHANGED
|
@@ -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
|
|
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
|
-
)
|
|
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
|
+
}
|
package/src/utils/type-bridge.ts
CHANGED
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
* Purpose: Prevent type fragmentation between native (Nitro) and TypeScript sides
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
}
|