react-native-iap 14.3.2-rc.9 → 14.3.3-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.
- package/lib/index.d.ts +8 -0
- package/lib/index.js +36 -0
- package/lib/module/helpers/subscription.js +2 -2
- package/lib/module/helpers/subscription.js.map +1 -1
- package/lib/module/hooks/useIAP.js +14 -8
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +87 -30
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +90 -190
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js +4 -4
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/utils/errorMapping.js +34 -10
- package/lib/module/utils/errorMapping.js.map +1 -1
- package/lib/module/utils/type-bridge.js +217 -173
- package/lib/module/utils/type-bridge.js.map +1 -1
- package/lib/specs/RnIap.nitro.d.ts +7 -0
- package/lib/specs/RnIap.nitro.js +1 -0
- package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
- package/lib/typescript/src/helpers/subscription.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +8 -11
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -10
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +2 -2
- package/lib/typescript/src/types.d.ts +606 -518
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/errorMapping.d.ts +2 -1
- package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -1
- package/lib/typescript/src/utils/type-bridge.d.ts +13 -14
- package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +4 -4
- package/nitrogen/generated/android/c++/{JNitroAndroidReceiptValidationOptions.hpp → JNitroReceiptValidationAndroidOptions.hpp} +9 -9
- package/nitrogen/generated/android/c++/JNitroReceiptValidationParams.hpp +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/{NitroAndroidReceiptValidationOptions.kt → NitroReceiptValidationAndroidOptions.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroReceiptValidationParams.kt +1 -1
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +10 -10
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -3
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +3 -3
- package/nitrogen/generated/ios/swift/{NitroAndroidReceiptValidationOptions.swift → NitroReceiptValidationAndroidOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/NitroReceiptValidationParams.swift +9 -9
- package/nitrogen/generated/shared/c++/{NitroAndroidReceiptValidationOptions.hpp → NitroReceiptValidationAndroidOptions.hpp} +10 -10
- package/nitrogen/generated/shared/c++/NitroReceiptValidationParams.hpp +8 -8
- package/package.json +1 -1
- package/plugin/src/withIAP.ts +4 -2
- package/src/helpers/subscription.ts +8 -9
- package/src/hooks/useIAP.ts +52 -47
- package/src/index.ts +141 -45
- package/src/specs/RnIap.nitro.ts +2 -2
- package/src/types.ts +651 -616
- package/src/utils/error.ts +4 -4
- package/src/utils/errorMapping.ts +47 -19
- package/src/utils/type-bridge.ts +308 -204
- package/lib/commonjs/index.js +0 -36
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/package.json +0 -1
- package/lib/commonjs/specs/RnIap.nitro.js +0 -6
- package/lib/commonjs/specs/RnIap.nitro.js.map +0 -1
- package/lib/commonjs/types.js +0 -118
- package/lib/commonjs/types.js.map +0 -1
|
@@ -33,21 +33,20 @@ export const getActiveSubscriptions = async (
|
|
|
33
33
|
purchaseToken: purchase.purchaseToken,
|
|
34
34
|
transactionDate: purchase.transactionDate,
|
|
35
35
|
// Platform-specific fields
|
|
36
|
-
expirationDateIOS: iosPurchase.expirationDateIOS
|
|
37
|
-
? new Date(iosPurchase.expirationDateIOS)
|
|
38
|
-
: undefined,
|
|
36
|
+
expirationDateIOS: iosPurchase.expirationDateIOS ?? null,
|
|
39
37
|
autoRenewingAndroid:
|
|
40
38
|
androidPurchase.autoRenewingAndroid ??
|
|
41
39
|
androidPurchase.isAutoRenewing, // deprecated - use isAutoRenewing instead
|
|
42
40
|
environmentIOS: iosPurchase.environmentIOS,
|
|
43
41
|
// Convenience fields
|
|
44
42
|
willExpireSoon: false, // Would need to calculate based on expiration date
|
|
45
|
-
daysUntilExpirationIOS:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
daysUntilExpirationIOS:
|
|
44
|
+
iosPurchase.expirationDateIOS != null
|
|
45
|
+
? Math.ceil(
|
|
46
|
+
(iosPurchase.expirationDateIOS - Date.now()) /
|
|
47
|
+
(1000 * 60 * 60 * 24),
|
|
48
|
+
)
|
|
49
|
+
: undefined,
|
|
51
50
|
};
|
|
52
51
|
});
|
|
53
52
|
|
package/src/hooks/useIAP.ts
CHANGED
|
@@ -16,20 +16,23 @@ import {
|
|
|
16
16
|
getActiveSubscriptions,
|
|
17
17
|
hasActiveSubscriptions,
|
|
18
18
|
restorePurchases as restorePurchasesTopLevel,
|
|
19
|
+
getPromotedProductIOS,
|
|
20
|
+
requestPurchaseOnPromotedProductIOS,
|
|
19
21
|
} from '../';
|
|
20
|
-
import {getPromotedProductIOS, requestPurchaseOnPromotedProductIOS} from '../';
|
|
21
22
|
|
|
22
23
|
// Types
|
|
24
|
+
import {ProductQueryType, ErrorCode} from '../types';
|
|
23
25
|
import type {
|
|
26
|
+
ActiveSubscription,
|
|
24
27
|
Product,
|
|
25
28
|
Purchase,
|
|
26
29
|
PurchaseError,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
RequestPurchaseProps,
|
|
30
|
-
RequestSubscriptionProps,
|
|
31
|
-
ActiveSubscription,
|
|
30
|
+
ProductSubscription,
|
|
31
|
+
PurchaseParams,
|
|
32
32
|
} from '../types';
|
|
33
|
+
import type {FinishTransactionParams} from '../';
|
|
34
|
+
import type {NitroPurchaseResult} from '../specs/RnIap.nitro';
|
|
35
|
+
import {normalizeErrorCodeFromNative} from '../utils/errorMapping';
|
|
33
36
|
|
|
34
37
|
// Types for event subscriptions
|
|
35
38
|
interface EventSubscription {
|
|
@@ -41,7 +44,7 @@ type UseIap = {
|
|
|
41
44
|
products: Product[];
|
|
42
45
|
promotedProductsIOS: Purchase[];
|
|
43
46
|
promotedProductIdIOS?: string;
|
|
44
|
-
subscriptions:
|
|
47
|
+
subscriptions: ProductSubscription[];
|
|
45
48
|
availablePurchases: Purchase[];
|
|
46
49
|
currentPurchase?: Purchase;
|
|
47
50
|
currentPurchaseError?: PurchaseError;
|
|
@@ -52,14 +55,11 @@ type UseIap = {
|
|
|
52
55
|
finishTransaction: ({
|
|
53
56
|
purchase,
|
|
54
57
|
isConsumable,
|
|
55
|
-
}:
|
|
56
|
-
purchase: Purchase;
|
|
57
|
-
isConsumable?: boolean;
|
|
58
|
-
}) => Promise<PurchaseResult | boolean>;
|
|
58
|
+
}: FinishTransactionParams) => Promise<NitroPurchaseResult | boolean>;
|
|
59
59
|
getAvailablePurchases: (skus?: string[]) => Promise<void>;
|
|
60
60
|
fetchProducts: (params: {
|
|
61
61
|
skus: string[];
|
|
62
|
-
type?:
|
|
62
|
+
type?: ProductQueryType | null;
|
|
63
63
|
}) => Promise<void>;
|
|
64
64
|
/**
|
|
65
65
|
* @deprecated Use fetchProducts({ skus, type: 'inapp' }) instead. This method will be removed in version 3.0.0.
|
|
@@ -71,10 +71,7 @@ type UseIap = {
|
|
|
71
71
|
* Note: This method internally uses fetchProducts, so no deprecation warning is shown.
|
|
72
72
|
*/
|
|
73
73
|
getSubscriptions: (skus: string[]) => Promise<void>;
|
|
74
|
-
requestPurchase: (params:
|
|
75
|
-
request: RequestPurchaseProps | RequestSubscriptionProps;
|
|
76
|
-
type?: 'inapp' | 'subs';
|
|
77
|
-
}) => Promise<any>;
|
|
74
|
+
requestPurchase: (params: PurchaseParams) => Promise<any>;
|
|
78
75
|
validateReceipt: (
|
|
79
76
|
sku: string,
|
|
80
77
|
androidOptions?: {
|
|
@@ -109,7 +106,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
109
106
|
const [connected, setConnected] = useState<boolean>(false);
|
|
110
107
|
const [products, setProducts] = useState<Product[]>([]);
|
|
111
108
|
const [promotedProductsIOS] = useState<Purchase[]>([]);
|
|
112
|
-
const [subscriptions, setSubscriptions] = useState<
|
|
109
|
+
const [subscriptions, setSubscriptions] = useState<ProductSubscription[]>([]);
|
|
113
110
|
const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);
|
|
114
111
|
const [currentPurchase, setCurrentPurchase] = useState<Purchase>();
|
|
115
112
|
const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();
|
|
@@ -159,7 +156,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
159
156
|
promotedProductIOS?: EventSubscription;
|
|
160
157
|
}>({});
|
|
161
158
|
|
|
162
|
-
const subscriptionsRefState = useRef<
|
|
159
|
+
const subscriptionsRefState = useRef<ProductSubscription[]>([]);
|
|
163
160
|
|
|
164
161
|
useEffect(() => {
|
|
165
162
|
subscriptionsRefState.current = subscriptions;
|
|
@@ -176,7 +173,10 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
176
173
|
const getProductsInternal = useCallback(
|
|
177
174
|
async (skus: string[]): Promise<void> => {
|
|
178
175
|
try {
|
|
179
|
-
const result = await fetchProducts({
|
|
176
|
+
const result = await fetchProducts({
|
|
177
|
+
skus,
|
|
178
|
+
type: ProductQueryType.InApp,
|
|
179
|
+
});
|
|
180
180
|
setProducts((prevProducts: Product[]) =>
|
|
181
181
|
mergeWithDuplicateCheck(
|
|
182
182
|
prevProducts,
|
|
@@ -194,12 +194,15 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
194
194
|
const getSubscriptionsInternal = useCallback(
|
|
195
195
|
async (skus: string[]): Promise<void> => {
|
|
196
196
|
try {
|
|
197
|
-
const result = await fetchProducts({
|
|
198
|
-
|
|
197
|
+
const result = await fetchProducts({
|
|
198
|
+
skus,
|
|
199
|
+
type: ProductQueryType.Subs,
|
|
200
|
+
});
|
|
201
|
+
setSubscriptions((prevSubscriptions: ProductSubscription[]) =>
|
|
199
202
|
mergeWithDuplicateCheck(
|
|
200
203
|
prevSubscriptions,
|
|
201
|
-
result as
|
|
202
|
-
(subscription:
|
|
204
|
+
result as ProductSubscription[],
|
|
205
|
+
(subscription: ProductSubscription) => subscription.id,
|
|
203
206
|
),
|
|
204
207
|
);
|
|
205
208
|
} catch (error) {
|
|
@@ -212,7 +215,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
212
215
|
const fetchProductsInternal = useCallback(
|
|
213
216
|
async (params: {
|
|
214
217
|
skus: string[];
|
|
215
|
-
type?:
|
|
218
|
+
type?: ProductQueryType | null;
|
|
216
219
|
}): Promise<void> => {
|
|
217
220
|
if (!connectedRef.current) {
|
|
218
221
|
console.warn(
|
|
@@ -222,12 +225,12 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
222
225
|
}
|
|
223
226
|
try {
|
|
224
227
|
const result = await fetchProducts(params);
|
|
225
|
-
if (params.type ===
|
|
226
|
-
setSubscriptions((prevSubscriptions:
|
|
228
|
+
if (params.type === ProductQueryType.Subs) {
|
|
229
|
+
setSubscriptions((prevSubscriptions: ProductSubscription[]) =>
|
|
227
230
|
mergeWithDuplicateCheck(
|
|
228
231
|
prevSubscriptions,
|
|
229
|
-
result as
|
|
230
|
-
(subscription:
|
|
232
|
+
result as ProductSubscription[],
|
|
233
|
+
(subscription: ProductSubscription) => subscription.id,
|
|
231
234
|
),
|
|
232
235
|
);
|
|
233
236
|
} else {
|
|
@@ -296,7 +299,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
296
299
|
}: {
|
|
297
300
|
purchase: Purchase;
|
|
298
301
|
isConsumable?: boolean;
|
|
299
|
-
}): Promise<
|
|
302
|
+
}): Promise<NitroPurchaseResult | boolean> => {
|
|
300
303
|
try {
|
|
301
304
|
return await finishTransactionInternal({
|
|
302
305
|
purchase,
|
|
@@ -322,7 +325,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
322
325
|
);
|
|
323
326
|
|
|
324
327
|
const requestPurchaseWithReset = useCallback(
|
|
325
|
-
async (requestObj:
|
|
328
|
+
async (requestObj: PurchaseParams) => {
|
|
326
329
|
clearCurrentPurchase();
|
|
327
330
|
clearCurrentPurchaseError();
|
|
328
331
|
|
|
@@ -371,23 +374,25 @@ export function useIAP(options?: UseIapOptions): UseIap {
|
|
|
371
374
|
},
|
|
372
375
|
);
|
|
373
376
|
|
|
374
|
-
subscriptionsRef.current.purchaseError = purchaseErrorListener(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
377
|
+
subscriptionsRef.current.purchaseError = purchaseErrorListener((error) => {
|
|
378
|
+
const mappedError: PurchaseError = {
|
|
379
|
+
code: normalizeErrorCodeFromNative(error.code),
|
|
380
|
+
message: error.message,
|
|
381
|
+
productId: undefined,
|
|
382
|
+
};
|
|
383
|
+
// Ignore init error until connected
|
|
384
|
+
if (
|
|
385
|
+
mappedError.code === ErrorCode.InitConnection &&
|
|
386
|
+
!connectedRef.current
|
|
387
|
+
) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
setCurrentPurchase(undefined);
|
|
391
|
+
setCurrentPurchaseError(mappedError);
|
|
392
|
+
if (optionsRef.current?.onPurchaseError) {
|
|
393
|
+
optionsRef.current.onPurchaseError(mappedError);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
391
396
|
|
|
392
397
|
if (Platform.OS === 'ios') {
|
|
393
398
|
subscriptionsRef.current.promotedProductsIOS = promotedProductListenerIOS(
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// External dependencies
|
|
2
2
|
import {Platform} from 'react-native';
|
|
3
|
-
// Side-effect import ensures Nitro installs its dispatcher before IAP is used
|
|
3
|
+
// Side-effect import ensures Nitro installs its dispatcher before IAP is used (no-op in tests)
|
|
4
4
|
import 'react-native-nitro-modules';
|
|
5
|
-
import {NitroModules
|
|
5
|
+
import {NitroModules} from 'react-native-nitro-modules';
|
|
6
6
|
|
|
7
7
|
// Internal modules
|
|
8
8
|
import type {
|
|
@@ -12,22 +12,24 @@ import type {
|
|
|
12
12
|
NitroReceiptValidationResultIOS,
|
|
13
13
|
NitroReceiptValidationResultAndroid,
|
|
14
14
|
} from './specs/RnIap.nitro';
|
|
15
|
+
import {ProductQueryType} from './types';
|
|
15
16
|
import type {
|
|
16
17
|
Product,
|
|
18
|
+
ProductRequest,
|
|
17
19
|
Purchase,
|
|
18
20
|
PurchaseAndroid,
|
|
19
|
-
RequestPurchaseProps,
|
|
20
|
-
RequestSubscriptionProps,
|
|
21
|
-
RequestSubscriptionAndroidProps,
|
|
22
21
|
PurchaseOptions,
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
PurchaseParams,
|
|
23
|
+
PurchaseError,
|
|
25
24
|
ReceiptValidationResultAndroid,
|
|
26
|
-
|
|
25
|
+
ReceiptValidationResultIOS,
|
|
27
26
|
RequestPurchaseAndroidProps,
|
|
27
|
+
RequestPurchaseIosProps,
|
|
28
|
+
RequestPurchasePropsByPlatforms,
|
|
29
|
+
RequestSubscriptionAndroidProps,
|
|
30
|
+
RequestSubscriptionPropsByPlatforms,
|
|
28
31
|
SubscriptionStatusIOS,
|
|
29
32
|
} from './types';
|
|
30
|
-
import type {ProductRequest} from './types';
|
|
31
33
|
import {
|
|
32
34
|
convertNitroProductToProduct,
|
|
33
35
|
convertNitroPurchaseToPurchase,
|
|
@@ -36,6 +38,7 @@ import {
|
|
|
36
38
|
convertNitroSubscriptionStatusToSubscriptionStatusIOS,
|
|
37
39
|
} from './utils/type-bridge';
|
|
38
40
|
import {parseErrorStringToJsonObj} from './utils/error';
|
|
41
|
+
import {normalizeErrorCodeFromNative} from './utils/errorMapping';
|
|
39
42
|
|
|
40
43
|
// Export all types
|
|
41
44
|
export type {
|
|
@@ -47,11 +50,46 @@ export type {
|
|
|
47
50
|
export * from './types';
|
|
48
51
|
export * from './utils/error';
|
|
49
52
|
|
|
50
|
-
//
|
|
53
|
+
// Internal constants/helpers for bridging legacy Nitro expectations
|
|
54
|
+
const NITRO_PRODUCT_TYPE_INAPP = 'inapp';
|
|
55
|
+
const NITRO_PRODUCT_TYPE_SUBS = 'subs';
|
|
56
|
+
|
|
57
|
+
function toNitroProductType(
|
|
58
|
+
type?: ProductQueryType | null,
|
|
59
|
+
): typeof NITRO_PRODUCT_TYPE_INAPP | typeof NITRO_PRODUCT_TYPE_SUBS {
|
|
60
|
+
return type === ProductQueryType.Subs
|
|
61
|
+
? NITRO_PRODUCT_TYPE_SUBS
|
|
62
|
+
: NITRO_PRODUCT_TYPE_INAPP;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isSubscriptionQuery(type?: ProductQueryType | null): boolean {
|
|
66
|
+
return type === ProductQueryType.Subs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeProductQueryType(
|
|
70
|
+
type?: ProductQueryType | string | null,
|
|
71
|
+
): ProductQueryType {
|
|
72
|
+
if (type === ProductQueryType.All || type === 'all') {
|
|
73
|
+
return ProductQueryType.All;
|
|
74
|
+
}
|
|
75
|
+
if (type === ProductQueryType.Subs || type === 'subs') {
|
|
76
|
+
return ProductQueryType.Subs;
|
|
77
|
+
}
|
|
78
|
+
if (type === ProductQueryType.InApp || type === 'inapp') {
|
|
79
|
+
return ProductQueryType.InApp;
|
|
80
|
+
}
|
|
81
|
+
return ProductQueryType.InApp;
|
|
82
|
+
}
|
|
83
|
+
|
|
51
84
|
export interface EventSubscription {
|
|
52
85
|
remove(): void;
|
|
53
86
|
}
|
|
54
87
|
|
|
88
|
+
export type FinishTransactionParams = {
|
|
89
|
+
purchase: Purchase;
|
|
90
|
+
isConsumable?: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
55
93
|
// ActiveSubscription and PurchaseError types are already exported via 'export * from ./types'
|
|
56
94
|
|
|
57
95
|
// Export hooks
|
|
@@ -85,15 +123,23 @@ const IAP = {
|
|
|
85
123
|
get instance(): RnIap {
|
|
86
124
|
if (iapRef) return iapRef;
|
|
87
125
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
// Attempt to create the HybridObject and map common Nitro/JSI readiness errors
|
|
127
|
+
try {
|
|
128
|
+
iapRef = NitroModules.createHybridObject<RnIap>('RnIap');
|
|
129
|
+
} catch (e) {
|
|
130
|
+
const msg = String((e as any)?.message ?? e ?? '');
|
|
131
|
+
if (
|
|
132
|
+
msg.includes('Nitro') ||
|
|
133
|
+
msg.includes('JSI') ||
|
|
134
|
+
msg.includes('dispatcher') ||
|
|
135
|
+
msg.includes('HybridObject')
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'Nitro runtime not installed yet. Ensure react-native-nitro-modules is initialized before calling IAP.',
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
throw e;
|
|
94
142
|
}
|
|
95
|
-
|
|
96
|
-
iapRef = NitroModules.createHybridObject<RnIap>('RnIap');
|
|
97
143
|
return iapRef;
|
|
98
144
|
},
|
|
99
145
|
};
|
|
@@ -142,17 +188,19 @@ export const endConnection = async (): Promise<boolean> => {
|
|
|
142
188
|
*/
|
|
143
189
|
export const fetchProducts = async ({
|
|
144
190
|
skus,
|
|
145
|
-
type =
|
|
191
|
+
type = ProductQueryType.InApp,
|
|
146
192
|
}: ProductRequest): Promise<Product[]> => {
|
|
147
193
|
try {
|
|
148
194
|
if (!skus || skus.length === 0) {
|
|
149
195
|
throw new Error('No SKUs provided');
|
|
150
196
|
}
|
|
151
197
|
|
|
152
|
-
|
|
198
|
+
const normalizedType = normalizeProductQueryType(type);
|
|
199
|
+
|
|
200
|
+
if (normalizedType === ProductQueryType.All) {
|
|
153
201
|
const [inappNitro, subsNitro] = await Promise.all([
|
|
154
|
-
IAP.instance.fetchProducts(skus,
|
|
155
|
-
IAP.instance.fetchProducts(skus,
|
|
202
|
+
IAP.instance.fetchProducts(skus, NITRO_PRODUCT_TYPE_INAPP),
|
|
203
|
+
IAP.instance.fetchProducts(skus, NITRO_PRODUCT_TYPE_SUBS),
|
|
156
204
|
]);
|
|
157
205
|
const allNitro = [...inappNitro, ...subsNitro];
|
|
158
206
|
const validAll = allNitro.filter(validateNitroProduct);
|
|
@@ -164,7 +212,10 @@ export const fetchProducts = async ({
|
|
|
164
212
|
return validAll.map(convertNitroProductToProduct);
|
|
165
213
|
}
|
|
166
214
|
|
|
167
|
-
const nitroProducts = await IAP.instance.fetchProducts(
|
|
215
|
+
const nitroProducts = await IAP.instance.fetchProducts(
|
|
216
|
+
skus,
|
|
217
|
+
toNitroProductType(normalizedType),
|
|
218
|
+
);
|
|
168
219
|
|
|
169
220
|
// Validate and convert NitroProducts to TypeScript Products
|
|
170
221
|
const validProducts = nitroProducts.filter(validateNitroProduct);
|
|
@@ -217,17 +268,53 @@ export const fetchProducts = async ({
|
|
|
217
268
|
* ⚠️ Important: This is an event-based operation, not promise-based.
|
|
218
269
|
* Listen for events through purchaseUpdatedListener or purchaseErrorListener.
|
|
219
270
|
* @param params - Purchase request configuration
|
|
220
|
-
* @param params.
|
|
221
|
-
* @param params.
|
|
271
|
+
* @param params.requestPurchase - Platform-specific purchase parameters (in-app)
|
|
272
|
+
* @param params.requestSubscription - Platform-specific subscription parameters (subs)
|
|
273
|
+
* @param params.type - Type of purchase (defaults to in-app)
|
|
222
274
|
*/
|
|
223
|
-
export const requestPurchase = async (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}: {
|
|
227
|
-
request: RequestPurchaseProps | RequestSubscriptionProps;
|
|
228
|
-
type?: 'inapp' | 'subs';
|
|
229
|
-
}): Promise<void> => {
|
|
275
|
+
export const requestPurchase = async (
|
|
276
|
+
params: PurchaseParams,
|
|
277
|
+
): Promise<void> => {
|
|
230
278
|
try {
|
|
279
|
+
const {requestPurchase: purchaseRequest, requestSubscription} = params;
|
|
280
|
+
const normalizedPurchaseRequest = purchaseRequest ?? undefined;
|
|
281
|
+
const normalizedSubscriptionRequest = requestSubscription ?? undefined;
|
|
282
|
+
|
|
283
|
+
const effectiveType = normalizeProductQueryType(params.type);
|
|
284
|
+
const isSubs = isSubscriptionQuery(effectiveType);
|
|
285
|
+
let request:
|
|
286
|
+
| RequestPurchasePropsByPlatforms
|
|
287
|
+
| RequestSubscriptionPropsByPlatforms
|
|
288
|
+
| undefined;
|
|
289
|
+
|
|
290
|
+
if (isSubs) {
|
|
291
|
+
if (
|
|
292
|
+
__DEV__ &&
|
|
293
|
+
normalizedPurchaseRequest &&
|
|
294
|
+
!normalizedSubscriptionRequest
|
|
295
|
+
) {
|
|
296
|
+
console.warn(
|
|
297
|
+
'[react-native-iap] `requestPurchase` was provided for a subscription request. Did you mean to use `requestSubscription`?',
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
request = normalizedSubscriptionRequest ?? normalizedPurchaseRequest;
|
|
301
|
+
} else {
|
|
302
|
+
if (
|
|
303
|
+
__DEV__ &&
|
|
304
|
+
normalizedSubscriptionRequest &&
|
|
305
|
+
!normalizedPurchaseRequest
|
|
306
|
+
) {
|
|
307
|
+
console.warn(
|
|
308
|
+
'[react-native-iap] `requestSubscription` was provided for an in-app purchase request. Did you mean to use `requestPurchase`?',
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
request = normalizedPurchaseRequest ?? normalizedSubscriptionRequest;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!request) {
|
|
315
|
+
throw new Error('Missing purchase request configuration');
|
|
316
|
+
}
|
|
317
|
+
|
|
231
318
|
// Validate platform-specific requests
|
|
232
319
|
if (Platform.OS === 'ios') {
|
|
233
320
|
const iosRequest = request.ios;
|
|
@@ -253,8 +340,7 @@ export const requestPurchase = async ({
|
|
|
253
340
|
if (Platform.OS === 'ios' && request.ios) {
|
|
254
341
|
const iosReq = request.ios as RequestPurchaseIosProps;
|
|
255
342
|
const autoFinishSubs =
|
|
256
|
-
|
|
257
|
-
iosReq.andDangerouslyFinishTransactionAutomatically == null;
|
|
343
|
+
isSubs && iosReq.andDangerouslyFinishTransactionAutomatically == null;
|
|
258
344
|
unifiedRequest.ios = {
|
|
259
345
|
...iosReq,
|
|
260
346
|
// Align with native SwiftUI flow: auto-finish subscriptions by default
|
|
@@ -265,7 +351,7 @@ export const requestPurchase = async ({
|
|
|
265
351
|
}
|
|
266
352
|
|
|
267
353
|
if (Platform.OS === 'android' && request.android) {
|
|
268
|
-
if (
|
|
354
|
+
if (isSubs) {
|
|
269
355
|
const subsRequest = request.android as RequestSubscriptionAndroidProps;
|
|
270
356
|
unifiedRequest.android = {
|
|
271
357
|
...subsRequest,
|
|
@@ -612,13 +698,20 @@ export const purchaseUpdatedListener = (
|
|
|
612
698
|
* ```
|
|
613
699
|
*/
|
|
614
700
|
export const purchaseErrorListener = (
|
|
615
|
-
listener: (error:
|
|
701
|
+
listener: (error: PurchaseError) => void,
|
|
616
702
|
): EventSubscription => {
|
|
617
|
-
|
|
618
|
-
|
|
703
|
+
const wrapped = (error: NitroPurchaseResult) => {
|
|
704
|
+
listener({
|
|
705
|
+
code: normalizeErrorCodeFromNative(error.code),
|
|
706
|
+
message: error.message,
|
|
707
|
+
productId: undefined,
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
listenerMap.set(listener, wrapped);
|
|
619
712
|
let attached = false;
|
|
620
713
|
try {
|
|
621
|
-
IAP.instance.addPurchaseErrorListener(
|
|
714
|
+
IAP.instance.addPurchaseErrorListener(wrapped as any);
|
|
622
715
|
attached = true;
|
|
623
716
|
} catch (e) {
|
|
624
717
|
const msg = String(e ?? '');
|
|
@@ -633,12 +726,15 @@ export const purchaseErrorListener = (
|
|
|
633
726
|
|
|
634
727
|
return {
|
|
635
728
|
remove: () => {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
729
|
+
const stored = listenerMap.get(listener);
|
|
730
|
+
if (stored) {
|
|
731
|
+
if (attached) {
|
|
732
|
+
try {
|
|
733
|
+
IAP.instance.removePurchaseErrorListener(stored as any);
|
|
734
|
+
} catch {}
|
|
735
|
+
}
|
|
736
|
+
listenerMap.delete(listener);
|
|
640
737
|
}
|
|
641
|
-
listenerMap.delete(listener);
|
|
642
738
|
},
|
|
643
739
|
};
|
|
644
740
|
};
|
|
@@ -1232,7 +1328,7 @@ export {
|
|
|
1232
1328
|
export {
|
|
1233
1329
|
convertNitroProductToProduct,
|
|
1234
1330
|
convertNitroPurchaseToPurchase,
|
|
1235
|
-
|
|
1331
|
+
convertProductToProductSubscription,
|
|
1236
1332
|
validateNitroProduct,
|
|
1237
1333
|
validateNitroPurchase,
|
|
1238
1334
|
checkTypeSynchronization,
|
package/src/specs/RnIap.nitro.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {HybridObject} from 'react-native-nitro-modules';
|
|
|
9
9
|
/**
|
|
10
10
|
* Android-specific receipt validation options
|
|
11
11
|
*/
|
|
12
|
-
export interface
|
|
12
|
+
export interface NitroReceiptValidationAndroidOptions {
|
|
13
13
|
packageName: string;
|
|
14
14
|
productToken: string;
|
|
15
15
|
accessToken: string;
|
|
@@ -21,7 +21,7 @@ export interface NitroAndroidReceiptValidationOptions {
|
|
|
21
21
|
*/
|
|
22
22
|
export interface NitroReceiptValidationParams {
|
|
23
23
|
sku: string;
|
|
24
|
-
androidOptions?:
|
|
24
|
+
androidOptions?: NitroReceiptValidationAndroidOptions;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Purchase request parameters
|