react-native-iap 9.0.4 → 10.0.2
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/android/src/amazon/java/com/dooboolab/RNIap/RNIapAmazonModule.kt +2 -1
- package/android/src/play/java/com/dooboolab/RNIap/RNIapModule.kt +2 -1
- package/ios/RNIapIos.swift +848 -848
- package/lib/commonjs/eventEmitter.js +54 -0
- package/lib/commonjs/eventEmitter.js.map +1 -0
- package/lib/commonjs/hooks/useIAP.js +33 -15
- package/lib/commonjs/hooks/useIAP.js.map +1 -1
- package/lib/commonjs/hooks/withIAPContext.js +9 -7
- package/lib/commonjs/hooks/withIAPContext.js.map +1 -1
- package/lib/commonjs/iap.js +127 -197
- package/lib/commonjs/iap.js.map +1 -1
- package/lib/commonjs/index.js +28 -15
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/internal/enhancedFetch.js +30 -0
- package/lib/commonjs/internal/enhancedFetch.js.map +1 -0
- package/lib/commonjs/internal/fillProductsWithAdditionalData.js +47 -0
- package/lib/commonjs/internal/fillProductsWithAdditionalData.js.map +1 -0
- package/lib/commonjs/internal/index.js +45 -0
- package/lib/commonjs/internal/index.js.map +1 -0
- package/lib/commonjs/internal/platform.js +19 -0
- package/lib/commonjs/internal/platform.js.map +1 -0
- package/lib/commonjs/purchaseError.js +47 -0
- package/lib/commonjs/purchaseError.js.map +1 -0
- package/lib/commonjs/types/index.js +1 -23
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/module/eventEmitter.js +36 -0
- package/lib/module/eventEmitter.js.map +1 -0
- package/lib/module/hooks/useIAP.js +34 -16
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/hooks/withIAPContext.js +6 -5
- package/lib/module/hooks/withIAPContext.js.map +1 -1
- package/lib/module/iap.js +118 -185
- package/lib/module/iap.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal/enhancedFetch.js +21 -0
- package/lib/module/internal/enhancedFetch.js.map +1 -0
- package/lib/module/internal/fillProductsWithAdditionalData.js +37 -0
- package/lib/module/internal/fillProductsWithAdditionalData.js.map +1 -0
- package/lib/module/internal/index.js +4 -0
- package/lib/module/internal/index.js.map +1 -0
- package/lib/module/internal/platform.js +8 -0
- package/lib/module/internal/platform.js.map +1 -0
- package/lib/module/purchaseError.js +38 -0
- package/lib/module/purchaseError.js.map +1 -0
- package/lib/module/types/index.js +0 -21
- package/lib/module/types/index.js.map +1 -1
- package/lib/typescript/eventEmitter.d.ts +17 -0
- package/lib/typescript/hooks/useIAP.d.ts +15 -9
- package/lib/typescript/hooks/withIAPContext.d.ts +4 -3
- package/lib/typescript/iap.d.ts +80 -41
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/internal/enhancedFetch.d.ts +6 -0
- package/lib/typescript/internal/fillProductsWithAdditionalData.d.ts +5 -0
- package/lib/typescript/internal/index.d.ts +3 -0
- package/lib/typescript/internal/platform.d.ts +3 -0
- package/lib/typescript/purchaseError.d.ts +26 -0
- package/lib/typescript/types/index.d.ts +1 -48
- package/package.json +1 -1
- package/src/eventEmitter.ts +49 -0
- package/src/hooks/useIAP.ts +38 -35
- package/src/hooks/withIAPContext.tsx +12 -13
- package/src/iap.ts +151 -232
- package/src/index.ts +2 -4
- package/src/internal/enhancedFetch.ts +25 -0
- package/src/internal/fillProductsWithAdditionalData.ts +43 -0
- package/src/internal/index.ts +3 -0
- package/src/internal/platform.ts +7 -0
- package/src/purchaseError.ts +35 -0
- package/src/types/index.ts +1 -59
package/src/iap.ts
CHANGED
|
@@ -1,47 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EmitterSubscription,
|
|
3
|
-
Linking,
|
|
4
|
-
NativeEventEmitter,
|
|
5
|
-
NativeModules,
|
|
6
|
-
Platform,
|
|
7
|
-
} from 'react-native';
|
|
1
|
+
import {Linking, NativeModules, Platform} from 'react-native';
|
|
8
2
|
|
|
9
3
|
import type * as Amazon from './types/amazon';
|
|
10
4
|
import type * as Android from './types/android';
|
|
11
5
|
import type * as Apple from './types/apple';
|
|
12
6
|
import {ReceiptValidationStatus} from './types/apple';
|
|
7
|
+
import {
|
|
8
|
+
enhancedFetch,
|
|
9
|
+
fillProductsWithAdditionalData,
|
|
10
|
+
isAmazon,
|
|
11
|
+
isAndroid,
|
|
12
|
+
} from './internal';
|
|
13
13
|
import type {
|
|
14
|
-
InAppPurchase,
|
|
15
14
|
Product,
|
|
16
|
-
ProductCommon,
|
|
17
15
|
ProductPurchase,
|
|
18
|
-
|
|
16
|
+
ProrationModesAndroid,
|
|
19
17
|
PurchaseResult,
|
|
20
|
-
|
|
21
|
-
RequestSubscription,
|
|
18
|
+
Sku,
|
|
22
19
|
Subscription,
|
|
20
|
+
SubscriptionOffer,
|
|
23
21
|
SubscriptionPurchase,
|
|
24
22
|
} from './types';
|
|
25
|
-
import {
|
|
26
|
-
IAPErrorCode,
|
|
27
|
-
InstallSourceAndroid,
|
|
28
|
-
PurchaseStateAndroid,
|
|
29
|
-
} from './types';
|
|
23
|
+
import {InstallSourceAndroid, PurchaseStateAndroid} from './types';
|
|
30
24
|
|
|
31
25
|
const {RNIapIos, RNIapModule, RNIapAmazonModule} = NativeModules;
|
|
32
|
-
const isAndroid = Platform.OS === 'android';
|
|
33
|
-
const isAmazon = isAndroid && !!RNIapAmazonModule;
|
|
34
|
-
const isIos = Platform.OS === 'ios';
|
|
35
26
|
const ANDROID_ITEM_TYPE_SUBSCRIPTION = 'subs';
|
|
36
27
|
const ANDROID_ITEM_TYPE_IAP = 'inapp';
|
|
37
28
|
|
|
38
|
-
export class IapError implements PurchaseError {
|
|
39
|
-
constructor(public code?: string, public message?: string) {
|
|
40
|
-
this.code = code;
|
|
41
|
-
this.message = message;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
29
|
export const getInstallSourceAndroid = (): InstallSourceAndroid => {
|
|
46
30
|
return RNIapModule
|
|
47
31
|
? InstallSourceAndroid.GOOGLE_PLAY
|
|
@@ -58,11 +42,13 @@ export const setAndroidNativeModule = (
|
|
|
58
42
|
|
|
59
43
|
const checkNativeAndroidAvailable = (): void => {
|
|
60
44
|
if (!RNIapModule && !RNIapAmazonModule) {
|
|
61
|
-
throw new Error(
|
|
45
|
+
throw new Error('IAP_NOT_AVAILABLE');
|
|
62
46
|
}
|
|
63
47
|
};
|
|
64
48
|
|
|
65
|
-
const getAndroidModule = ():
|
|
49
|
+
export const getAndroidModule = ():
|
|
50
|
+
| typeof RNIapModule
|
|
51
|
+
| typeof RNIapAmazonModule => {
|
|
66
52
|
checkNativeAndroidAvailable();
|
|
67
53
|
|
|
68
54
|
return androidNativeModule
|
|
@@ -74,17 +60,17 @@ const getAndroidModule = (): typeof RNIapModule | typeof RNIapAmazonModule => {
|
|
|
74
60
|
|
|
75
61
|
const checkNativeIOSAvailable = (): void => {
|
|
76
62
|
if (!RNIapIos) {
|
|
77
|
-
throw new Error(
|
|
63
|
+
throw new Error('IAP_NOT_AVAILABLE');
|
|
78
64
|
}
|
|
79
65
|
};
|
|
80
66
|
|
|
81
|
-
const getIosModule = (): typeof RNIapIos => {
|
|
67
|
+
export const getIosModule = (): typeof RNIapIos => {
|
|
82
68
|
checkNativeIOSAvailable();
|
|
83
69
|
|
|
84
70
|
return RNIapIos;
|
|
85
71
|
};
|
|
86
72
|
|
|
87
|
-
const getNativeModule = ():
|
|
73
|
+
export const getNativeModule = ():
|
|
88
74
|
| typeof RNIapModule
|
|
89
75
|
| typeof RNIapAmazonModule
|
|
90
76
|
| typeof RNIapIos => {
|
|
@@ -113,52 +99,16 @@ export const flushFailedPurchasesCachedAsPendingAndroid = (): Promise<
|
|
|
113
99
|
string[]
|
|
114
100
|
> => getAndroidModule().flushFailedPurchasesCachedAsPending();
|
|
115
101
|
|
|
116
|
-
/**
|
|
117
|
-
* Fill products with additional data
|
|
118
|
-
* @param {Array<ProductCommon>} products Products
|
|
119
|
-
*/
|
|
120
|
-
const fillProductsAdditionalData = async (
|
|
121
|
-
products: Array<ProductCommon>,
|
|
122
|
-
): Promise<Array<ProductCommon>> => {
|
|
123
|
-
// Amazon
|
|
124
|
-
if (RNIapAmazonModule) {
|
|
125
|
-
// On amazon we must get the user marketplace to detect the currency
|
|
126
|
-
const user = await RNIapAmazonModule.getUser();
|
|
127
|
-
|
|
128
|
-
const currencies = {
|
|
129
|
-
CA: 'CAD',
|
|
130
|
-
ES: 'EUR',
|
|
131
|
-
AU: 'AUD',
|
|
132
|
-
DE: 'EUR',
|
|
133
|
-
IN: 'INR',
|
|
134
|
-
US: 'USD',
|
|
135
|
-
JP: 'JPY',
|
|
136
|
-
GB: 'GBP',
|
|
137
|
-
IT: 'EUR',
|
|
138
|
-
BR: 'BRL',
|
|
139
|
-
FR: 'EUR',
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const currency =
|
|
143
|
-
currencies[user.userMarketplaceAmazon as keyof typeof currencies];
|
|
144
|
-
|
|
145
|
-
// Add currency to products
|
|
146
|
-
products.forEach((product) => {
|
|
147
|
-
if (currency) {
|
|
148
|
-
product.currency = currency;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return products;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
102
|
/**
|
|
157
103
|
* Get a list of products (consumable and non-consumable items, but not subscriptions)
|
|
158
104
|
* @param {string[]} skus The item skus
|
|
159
105
|
* @returns {Promise<Product[]>}
|
|
160
106
|
*/
|
|
161
|
-
export const getProducts = (
|
|
107
|
+
export const getProducts = ({
|
|
108
|
+
skus,
|
|
109
|
+
}: {
|
|
110
|
+
skus: string[];
|
|
111
|
+
}): Promise<Array<Product>> =>
|
|
162
112
|
(
|
|
163
113
|
Platform.select({
|
|
164
114
|
ios: async () => {
|
|
@@ -175,7 +125,7 @@ export const getProducts = (skus: string[]): Promise<Array<Product>> =>
|
|
|
175
125
|
skus,
|
|
176
126
|
);
|
|
177
127
|
|
|
178
|
-
return
|
|
128
|
+
return fillProductsWithAdditionalData(products);
|
|
179
129
|
},
|
|
180
130
|
}) || Promise.resolve
|
|
181
131
|
)();
|
|
@@ -185,7 +135,11 @@ export const getProducts = (skus: string[]): Promise<Array<Product>> =>
|
|
|
185
135
|
* @param {string[]} skus The item skus
|
|
186
136
|
* @returns {Promise<Subscription[]>}
|
|
187
137
|
*/
|
|
188
|
-
export const getSubscriptions = (
|
|
138
|
+
export const getSubscriptions = ({
|
|
139
|
+
skus,
|
|
140
|
+
}: {
|
|
141
|
+
skus: string[];
|
|
142
|
+
}): Promise<Subscription[]> =>
|
|
189
143
|
(
|
|
190
144
|
Platform.select({
|
|
191
145
|
ios: async () => {
|
|
@@ -202,17 +156,17 @@ export const getSubscriptions = (skus: string[]): Promise<Subscription[]> =>
|
|
|
202
156
|
skus,
|
|
203
157
|
);
|
|
204
158
|
|
|
205
|
-
return
|
|
159
|
+
return fillProductsWithAdditionalData(subscriptions);
|
|
206
160
|
},
|
|
207
161
|
}) || Promise.resolve
|
|
208
162
|
)();
|
|
209
163
|
|
|
210
164
|
/**
|
|
211
165
|
* Gets an inventory of purchases made by the user regardless of consumption status
|
|
212
|
-
* @returns {Promise<(
|
|
166
|
+
* @returns {Promise<(ProductPurchase | SubscriptionPurchase)[]>}
|
|
213
167
|
*/
|
|
214
168
|
export const getPurchaseHistory = (): Promise<
|
|
215
|
-
(
|
|
169
|
+
(ProductPurchase | SubscriptionPurchase)[]
|
|
216
170
|
> =>
|
|
217
171
|
(
|
|
218
172
|
Platform.select({
|
|
@@ -239,10 +193,10 @@ export const getPurchaseHistory = (): Promise<
|
|
|
239
193
|
|
|
240
194
|
/**
|
|
241
195
|
* Get all purchases made by the user (either non-consumable, or haven't been consumed yet)
|
|
242
|
-
* @returns {Promise<(
|
|
196
|
+
* @returns {Promise<(ProductPurchase | SubscriptionPurchase)[]>}
|
|
243
197
|
*/
|
|
244
198
|
export const getAvailablePurchases = (): Promise<
|
|
245
|
-
(
|
|
199
|
+
(ProductPurchase | SubscriptionPurchase)[]
|
|
246
200
|
> =>
|
|
247
201
|
(
|
|
248
202
|
Platform.select({
|
|
@@ -276,18 +230,27 @@ export const getAvailablePurchases = (): Promise<
|
|
|
276
230
|
* @param {string} [obfuscatedProfileIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
|
|
277
231
|
* @param {string[]} [skus] Product Ids to purchase. Note that this is only for Android. iOS only uses a single SKU. If not provided, it'll default to using [sku] for backward-compatibility
|
|
278
232
|
* @param {boolean} [isOfferPersonalized] Defaults to false, Only for Android V5
|
|
279
|
-
* @returns {Promise<
|
|
233
|
+
* @returns {Promise<ProductPurchase>}
|
|
280
234
|
*/
|
|
281
235
|
|
|
282
236
|
export const requestPurchase = ({
|
|
283
237
|
sku,
|
|
284
238
|
andDangerouslyFinishTransactionAutomaticallyIOS = false,
|
|
239
|
+
applicationUsername,
|
|
285
240
|
obfuscatedAccountIdAndroid,
|
|
286
241
|
obfuscatedProfileIdAndroid,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
242
|
+
skus,
|
|
243
|
+
isOfferPersonalized,
|
|
244
|
+
}: {
|
|
245
|
+
sku?: Sku;
|
|
246
|
+
andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
|
|
247
|
+
applicationUsername?: string;
|
|
248
|
+
obfuscatedAccountIdAndroid?: string;
|
|
249
|
+
obfuscatedProfileIdAndroid?: string;
|
|
250
|
+
/** For Google Play Billing Library 5 https://developer.android.com/google/play/billing/integrate#personalized-price */
|
|
251
|
+
skus?: Sku[];
|
|
252
|
+
isOfferPersonalized?: boolean;
|
|
253
|
+
}): Promise<ProductPurchase> =>
|
|
291
254
|
(
|
|
292
255
|
Platform.select({
|
|
293
256
|
ios: async () => {
|
|
@@ -337,14 +300,26 @@ export const requestPurchase = ({
|
|
|
337
300
|
export const requestSubscription = ({
|
|
338
301
|
sku,
|
|
339
302
|
andDangerouslyFinishTransactionAutomaticallyIOS = false,
|
|
303
|
+
applicationUsername,
|
|
340
304
|
purchaseTokenAndroid,
|
|
341
305
|
prorationModeAndroid = -1,
|
|
306
|
+
subscriptionOffers,
|
|
342
307
|
obfuscatedAccountIdAndroid,
|
|
343
308
|
obfuscatedProfileIdAndroid,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
309
|
+
isOfferPersonalized = undefined,
|
|
310
|
+
}: {
|
|
311
|
+
sku?: Sku;
|
|
312
|
+
andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
|
|
313
|
+
applicationUsername?: string;
|
|
314
|
+
purchaseTokenAndroid?: string;
|
|
315
|
+
prorationModeAndroid?: ProrationModesAndroid;
|
|
316
|
+
/** For Google Play Billing Library 5 */
|
|
317
|
+
subscriptionOffers?: SubscriptionOffer[];
|
|
318
|
+
obfuscatedAccountIdAndroid?: string;
|
|
319
|
+
obfuscatedProfileIdAndroid?: string;
|
|
320
|
+
/** For Google Play Billing Library 5 https://developer.android.com/google/play/billing/integrate#personalized-price */
|
|
321
|
+
isOfferPersonalized?: boolean;
|
|
322
|
+
}): Promise<SubscriptionPurchase | null> =>
|
|
348
323
|
(
|
|
349
324
|
Platform.select({
|
|
350
325
|
ios: async () => {
|
|
@@ -390,10 +365,13 @@ export const requestSubscription = ({
|
|
|
390
365
|
* @param {string} sku The product's sku/ID
|
|
391
366
|
* @returns {Promise<void>}
|
|
392
367
|
*/
|
|
393
|
-
export const requestPurchaseWithQuantityIOS = (
|
|
394
|
-
sku
|
|
395
|
-
quantity
|
|
396
|
-
|
|
368
|
+
export const requestPurchaseWithQuantityIOS = ({
|
|
369
|
+
sku,
|
|
370
|
+
quantity,
|
|
371
|
+
}: {
|
|
372
|
+
sku: Sku;
|
|
373
|
+
quantity: number;
|
|
374
|
+
}): Promise<ProductPurchase> =>
|
|
397
375
|
getIosModule().buyProductWithQuantityIOS(sku, quantity);
|
|
398
376
|
|
|
399
377
|
/**
|
|
@@ -408,11 +386,15 @@ export const requestPurchaseWithQuantityIOS = (
|
|
|
408
386
|
* @param {string} developerPayloadAndroid Android developerPayload.
|
|
409
387
|
* @returns {Promise<string | void> }
|
|
410
388
|
*/
|
|
411
|
-
export const finishTransaction = (
|
|
412
|
-
purchase
|
|
413
|
-
isConsumable
|
|
414
|
-
developerPayloadAndroid
|
|
415
|
-
|
|
389
|
+
export const finishTransaction = ({
|
|
390
|
+
purchase,
|
|
391
|
+
isConsumable,
|
|
392
|
+
developerPayloadAndroid,
|
|
393
|
+
}: {
|
|
394
|
+
purchase: ProductPurchase | ProductPurchase;
|
|
395
|
+
isConsumable?: boolean;
|
|
396
|
+
developerPayloadAndroid?: string;
|
|
397
|
+
}): Promise<string | void> => {
|
|
416
398
|
return (
|
|
417
399
|
Platform.select({
|
|
418
400
|
ios: async () => {
|
|
@@ -468,10 +450,13 @@ export const clearProductsIOS = (): Promise<void> =>
|
|
|
468
450
|
* @param {string} token The product's token (on Android)
|
|
469
451
|
* @returns {Promise<PurchaseResult | void>}
|
|
470
452
|
*/
|
|
471
|
-
export const acknowledgePurchaseAndroid = (
|
|
472
|
-
token
|
|
473
|
-
developerPayload
|
|
474
|
-
|
|
453
|
+
export const acknowledgePurchaseAndroid = ({
|
|
454
|
+
token,
|
|
455
|
+
developerPayload,
|
|
456
|
+
}: {
|
|
457
|
+
token: string;
|
|
458
|
+
developerPayload?: string;
|
|
459
|
+
}): Promise<PurchaseResult | void> => {
|
|
475
460
|
return getAndroidModule().acknowledgePurchase(token, developerPayload);
|
|
476
461
|
};
|
|
477
462
|
|
|
@@ -480,9 +465,11 @@ export const acknowledgePurchaseAndroid = (
|
|
|
480
465
|
* @param {string} sku The product's SKU (on Android)
|
|
481
466
|
* @returns {Promise<void>}
|
|
482
467
|
*/
|
|
483
|
-
export const deepLinkToSubscriptionsAndroid = async (
|
|
484
|
-
sku
|
|
485
|
-
|
|
468
|
+
export const deepLinkToSubscriptionsAndroid = async ({
|
|
469
|
+
sku,
|
|
470
|
+
}: {
|
|
471
|
+
sku: Sku;
|
|
472
|
+
}): Promise<void> => {
|
|
486
473
|
checkNativeAndroidAvailable();
|
|
487
474
|
|
|
488
475
|
return Linking.openURL(
|
|
@@ -506,42 +493,26 @@ export const getPromotedProductIOS = (): Promise<Product | null> =>
|
|
|
506
493
|
export const buyPromotedProductIOS = (): Promise<void> =>
|
|
507
494
|
getIosModule().buyPromotedProduct();
|
|
508
495
|
|
|
509
|
-
const fetchJsonOrThrow = async (
|
|
510
|
-
url: string,
|
|
511
|
-
receiptBody: Record<string, unknown>,
|
|
512
|
-
): Promise<Apple.ReceiptValidationResponse | false> => {
|
|
513
|
-
const response = await fetch(url, {
|
|
514
|
-
method: 'POST',
|
|
515
|
-
headers: {
|
|
516
|
-
Accept: 'application/json',
|
|
517
|
-
'Content-Type': 'application/json',
|
|
518
|
-
},
|
|
519
|
-
body: JSON.stringify(receiptBody),
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
if (!response.ok) {
|
|
523
|
-
throw Object.assign(new Error(response.statusText), {
|
|
524
|
-
statusCode: response.status,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return response.json();
|
|
529
|
-
};
|
|
530
|
-
|
|
531
496
|
const requestAgnosticReceiptValidationIos = async (
|
|
532
497
|
receiptBody: Record<string, unknown>,
|
|
533
498
|
): Promise<Apple.ReceiptValidationResponse | false> => {
|
|
534
|
-
const response = await
|
|
499
|
+
const response = await enhancedFetch<Apple.ReceiptValidationResponse>(
|
|
535
500
|
'https://buy.itunes.apple.com/verifyReceipt',
|
|
536
|
-
|
|
501
|
+
{
|
|
502
|
+
method: 'POST',
|
|
503
|
+
body: receiptBody,
|
|
504
|
+
},
|
|
537
505
|
);
|
|
538
506
|
|
|
539
507
|
// Best practice is to check for test receipt and check sandbox instead
|
|
540
508
|
// https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
|
|
541
509
|
if (response && response.status === ReceiptValidationStatus.TEST_RECEIPT) {
|
|
542
|
-
const testResponse = await
|
|
510
|
+
const testResponse = await enhancedFetch<Apple.ReceiptValidationResponse>(
|
|
543
511
|
'https://sandbox.itunes.apple.com/verifyReceipt',
|
|
544
|
-
|
|
512
|
+
{
|
|
513
|
+
method: 'POST',
|
|
514
|
+
body: receiptBody,
|
|
515
|
+
},
|
|
545
516
|
);
|
|
546
517
|
|
|
547
518
|
return testResponse;
|
|
@@ -565,11 +536,16 @@ const requestAgnosticReceiptValidationIos = async (
|
|
|
565
536
|
* @param {number} withOffer.timestamp The timestamp of the signature
|
|
566
537
|
* @returns {Promise<void>}
|
|
567
538
|
*/
|
|
568
|
-
export const requestPurchaseWithOfferIOS = (
|
|
569
|
-
sku
|
|
570
|
-
forUser
|
|
571
|
-
withOffer
|
|
572
|
-
|
|
539
|
+
export const requestPurchaseWithOfferIOS = ({
|
|
540
|
+
sku,
|
|
541
|
+
forUser,
|
|
542
|
+
withOffer,
|
|
543
|
+
}: {
|
|
544
|
+
sku: Sku;
|
|
545
|
+
forUser: string;
|
|
546
|
+
withOffer: Apple.PaymentDiscount;
|
|
547
|
+
}): Promise<void> =>
|
|
548
|
+
getIosModule().buyProductWithOffer(sku, forUser, withOffer);
|
|
573
549
|
|
|
574
550
|
/**
|
|
575
551
|
* Validate receipt for iOS.
|
|
@@ -577,10 +553,13 @@ export const requestPurchaseWithOfferIOS = (
|
|
|
577
553
|
* @param {boolean} isTest whether this is in test environment which is sandbox.
|
|
578
554
|
* @returns {Promise<Apple.ReceiptValidationResponse | false>}
|
|
579
555
|
*/
|
|
580
|
-
export const validateReceiptIos = async (
|
|
581
|
-
receiptBody
|
|
582
|
-
isTest
|
|
583
|
-
|
|
556
|
+
export const validateReceiptIos = async ({
|
|
557
|
+
receiptBody,
|
|
558
|
+
isTest,
|
|
559
|
+
}: {
|
|
560
|
+
receiptBody: Record<string, unknown>;
|
|
561
|
+
isTest?: boolean;
|
|
562
|
+
}): Promise<Apple.ReceiptValidationResponse | false> => {
|
|
584
563
|
if (isTest == null) {
|
|
585
564
|
return await requestAgnosticReceiptValidationIos(receiptBody);
|
|
586
565
|
}
|
|
@@ -589,9 +568,7 @@ export const validateReceiptIos = async (
|
|
|
589
568
|
? 'https://sandbox.itunes.apple.com/verifyReceipt'
|
|
590
569
|
: 'https://buy.itunes.apple.com/verifyReceipt';
|
|
591
570
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
return response;
|
|
571
|
+
return await enhancedFetch<Apple.ReceiptValidationResponse>(url);
|
|
595
572
|
};
|
|
596
573
|
|
|
597
574
|
/**
|
|
@@ -605,13 +582,19 @@ export const validateReceiptIos = async (
|
|
|
605
582
|
* @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.
|
|
606
583
|
* @returns {Promise<object>}
|
|
607
584
|
*/
|
|
608
|
-
export const validateReceiptAndroid = async (
|
|
609
|
-
packageName
|
|
610
|
-
productId
|
|
611
|
-
productToken
|
|
612
|
-
accessToken
|
|
613
|
-
isSub
|
|
614
|
-
|
|
585
|
+
export const validateReceiptAndroid = async ({
|
|
586
|
+
packageName,
|
|
587
|
+
productId,
|
|
588
|
+
productToken,
|
|
589
|
+
accessToken,
|
|
590
|
+
isSub,
|
|
591
|
+
}: {
|
|
592
|
+
packageName: string;
|
|
593
|
+
productId: string;
|
|
594
|
+
productToken: string;
|
|
595
|
+
accessToken: string;
|
|
596
|
+
isSub?: boolean;
|
|
597
|
+
}): Promise<Android.ReceiptType> => {
|
|
615
598
|
const type = isSub ? 'subscriptions' : 'products';
|
|
616
599
|
|
|
617
600
|
const url =
|
|
@@ -619,20 +602,7 @@ export const validateReceiptAndroid = async (
|
|
|
619
602
|
`/${packageName}/purchases/${type}/${productId}` +
|
|
620
603
|
`/tokens/${productToken}?access_token=${accessToken}`;
|
|
621
604
|
|
|
622
|
-
|
|
623
|
-
method: 'GET',
|
|
624
|
-
headers: {
|
|
625
|
-
'Content-Type': 'application/json',
|
|
626
|
-
},
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
if (!response.ok) {
|
|
630
|
-
throw Object.assign(new Error(response.statusText), {
|
|
631
|
-
statusCode: response.status,
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return response.json();
|
|
605
|
+
return await enhancedFetch<Android.ReceiptType>(url);
|
|
636
606
|
};
|
|
637
607
|
|
|
638
608
|
/**
|
|
@@ -645,75 +615,21 @@ export const validateReceiptAndroid = async (
|
|
|
645
615
|
* @param {boolean} useSandbox Defaults to true, use sandbox environment or production.
|
|
646
616
|
* @returns {Promise<object>}
|
|
647
617
|
*/
|
|
648
|
-
export const validateReceiptAmazon = async (
|
|
649
|
-
developerSecret
|
|
650
|
-
userId
|
|
651
|
-
receiptId
|
|
652
|
-
useSandbox
|
|
653
|
-
|
|
618
|
+
export const validateReceiptAmazon = async ({
|
|
619
|
+
developerSecret,
|
|
620
|
+
userId,
|
|
621
|
+
receiptId,
|
|
622
|
+
useSandbox = true,
|
|
623
|
+
}: {
|
|
624
|
+
developerSecret: string;
|
|
625
|
+
userId: string;
|
|
626
|
+
receiptId: string;
|
|
627
|
+
useSandbox: boolean;
|
|
628
|
+
}): Promise<Amazon.ReceiptType> => {
|
|
654
629
|
const sandBoxUrl = useSandbox ? 'sandbox/' : '';
|
|
655
630
|
const url = `https://appstore-sdk.amazon.com/${sandBoxUrl}version/1.0/verifyReceiptId/developer/${developerSecret}/user/${userId}/receiptId/${receiptId}`;
|
|
656
631
|
|
|
657
|
-
|
|
658
|
-
method: 'GET',
|
|
659
|
-
headers: {
|
|
660
|
-
'Content-Type': 'application/json',
|
|
661
|
-
},
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
if (!response.ok) {
|
|
665
|
-
throw Object.assign(new Error(response.statusText), {
|
|
666
|
-
statusCode: response.status,
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return response.json();
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Add IAP purchase event
|
|
675
|
-
* @returns {callback(e: InAppPurchase | ProductPurchase)}
|
|
676
|
-
*/
|
|
677
|
-
export const purchaseUpdatedListener = (
|
|
678
|
-
listener: (event: InAppPurchase | SubscriptionPurchase) => void,
|
|
679
|
-
): EmitterSubscription => {
|
|
680
|
-
const emitterSubscription = new NativeEventEmitter(
|
|
681
|
-
getNativeModule(),
|
|
682
|
-
).addListener('purchase-updated', listener);
|
|
683
|
-
|
|
684
|
-
if (isAndroid) {
|
|
685
|
-
getAndroidModule().startListening();
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return emitterSubscription;
|
|
689
|
-
};
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Add IAP purchase error event
|
|
693
|
-
* @returns {callback(e: PurchaseError)}
|
|
694
|
-
*/
|
|
695
|
-
export const purchaseErrorListener = (
|
|
696
|
-
listener: (errorEvent: PurchaseError) => void,
|
|
697
|
-
): EmitterSubscription =>
|
|
698
|
-
new NativeEventEmitter(getNativeModule()).addListener(
|
|
699
|
-
'purchase-error',
|
|
700
|
-
listener,
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* Add IAP promoted subscription event
|
|
705
|
-
* Only available on iOS
|
|
706
|
-
*/
|
|
707
|
-
export const promotedProductListener = (
|
|
708
|
-
listener: (productId?: string) => void,
|
|
709
|
-
): EmitterSubscription | null => {
|
|
710
|
-
if (isIos) {
|
|
711
|
-
return new NativeEventEmitter(getIosModule()).addListener(
|
|
712
|
-
'iap-promoted-product',
|
|
713
|
-
listener,
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
return null;
|
|
632
|
+
return await enhancedFetch<Amazon.ReceiptType>(url);
|
|
717
633
|
};
|
|
718
634
|
|
|
719
635
|
/**
|
|
@@ -729,8 +645,11 @@ export const getPendingPurchasesIOS = async (): Promise<ProductPurchase[]> =>
|
|
|
729
645
|
* @param {forceRefresh?:boolean}
|
|
730
646
|
* @returns {Promise<string>}
|
|
731
647
|
*/
|
|
732
|
-
export const getReceiptIOS = async (
|
|
733
|
-
|
|
648
|
+
export const getReceiptIOS = async ({
|
|
649
|
+
forceRefresh,
|
|
650
|
+
}: {
|
|
651
|
+
forceRefresh?: boolean;
|
|
652
|
+
}): Promise<string> => getIosModule().requestReceipt(forceRefresh ?? false);
|
|
734
653
|
|
|
735
654
|
/**
|
|
736
655
|
* Launches a modal to register the redeem offer code in IOS.
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface OverwrittenRequestInit extends Omit<RequestInit, 'body'> {
|
|
2
|
+
body: Record<string, any>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const enhancedFetch = async <T = any>(
|
|
6
|
+
url: string,
|
|
7
|
+
init?: OverwrittenRequestInit,
|
|
8
|
+
) => {
|
|
9
|
+
const response = await fetch(url, {
|
|
10
|
+
method: init?.method ?? 'GET',
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: 'application/json',
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
},
|
|
15
|
+
...(init?.body ? {body: JSON.stringify(init.body)} : {}),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw Object.assign(new Error(response.statusText), {
|
|
20
|
+
statusCode: response.status,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return response.json() as Promise<T>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {NativeModules} from 'react-native';
|
|
2
|
+
|
|
3
|
+
import type {ProductCommon} from '../types';
|
|
4
|
+
|
|
5
|
+
const {RNIapAmazonModule} = NativeModules;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fill products with additional data
|
|
9
|
+
*/
|
|
10
|
+
export const fillProductsWithAdditionalData = async <T extends ProductCommon>(
|
|
11
|
+
items: T[],
|
|
12
|
+
) => {
|
|
13
|
+
if (RNIapAmazonModule) {
|
|
14
|
+
// On amazon we must get the user marketplace to detect the currency
|
|
15
|
+
const user = await RNIapAmazonModule.getUser();
|
|
16
|
+
|
|
17
|
+
const currencies = {
|
|
18
|
+
CA: 'CAD',
|
|
19
|
+
ES: 'EUR',
|
|
20
|
+
AU: 'AUD',
|
|
21
|
+
DE: 'EUR',
|
|
22
|
+
IN: 'INR',
|
|
23
|
+
US: 'USD',
|
|
24
|
+
JP: 'JPY',
|
|
25
|
+
GB: 'GBP',
|
|
26
|
+
IT: 'EUR',
|
|
27
|
+
BR: 'BRL',
|
|
28
|
+
FR: 'EUR',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const currency =
|
|
32
|
+
currencies[user.userMarketplaceAmazon as keyof typeof currencies];
|
|
33
|
+
|
|
34
|
+
// Add currency to items
|
|
35
|
+
items.forEach((item) => {
|
|
36
|
+
if (currency) {
|
|
37
|
+
item.currency = currency;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return items;
|
|
43
|
+
};
|