react-native-iap 10.1.0 → 10.1.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/README.md +0 -1
- package/ios/RNIapIos.swift +3 -3
- package/lib/commonjs/iap.js +344 -30
- package/lib/commonjs/iap.js.map +1 -1
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/module/iap.js +344 -30
- package/lib/module/iap.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types/index.js.map +1 -1
- package/lib/typescript/iap.d.ts +345 -55
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/types/index.d.ts +33 -1
- package/package.json +5 -2
- package/src/iap.ts +348 -55
- package/src/index.ts +1 -0
- package/src/types/index.ts +43 -4
package/README.md
CHANGED
|
@@ -19,7 +19,6 @@ Read the [documentation](https://react-native-iap.dooboolab.com). See the [troub
|
|
|
19
19
|
Please [fund the project](https://opencollective.com/react-native-iap) if you are willing the maintainers to make the repository sustainable.
|
|
20
20
|
|
|
21
21
|
- [andresesfm](https://github.com/andresesfm)
|
|
22
|
-
- [jeremybarbet](https://github.com/jeremybarbet)
|
|
23
22
|
|
|
24
23
|
> The fund goes to maintainers.
|
|
25
24
|
|
package/ios/RNIapIos.swift
CHANGED
|
@@ -390,7 +390,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
|
|
|
390
390
|
) {
|
|
391
391
|
requestReceiptData(withBlock: refresh) { [self] receiptData, error in
|
|
392
392
|
if error == nil {
|
|
393
|
-
resolve(receiptData?.base64EncodedString(options: []))
|
|
393
|
+
resolve(receiptData?.base64EncodedString(options: [.endLineWithCarriageReturn]))
|
|
394
394
|
} else {
|
|
395
395
|
reject(standardErrorCode(9), "Invalid receipt", nil)
|
|
396
396
|
}
|
|
@@ -423,7 +423,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
|
|
|
423
423
|
"transactionId": item.transactionIdentifier,
|
|
424
424
|
"productId": item.payment.productIdentifier,
|
|
425
425
|
"quantity": "\(item.payment.quantity)",
|
|
426
|
-
"transactionReceipt": receipt.base64EncodedString(options: [])
|
|
426
|
+
"transactionReceipt": receipt.base64EncodedString(options: [.endLineWithCarriageReturn])
|
|
427
427
|
]
|
|
428
428
|
|
|
429
429
|
output.append(purchase)
|
|
@@ -907,7 +907,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
|
|
|
907
907
|
"transactionDate": transaction.transactionDate?.millisecondsSince1970String,
|
|
908
908
|
"transactionId": transaction.transactionIdentifier,
|
|
909
909
|
"productId": transaction.payment.productIdentifier,
|
|
910
|
-
"transactionReceipt": receiptData?.base64EncodedString(options: [])
|
|
910
|
+
"transactionReceipt": receiptData?.base64EncodedString(options: [.endLineWithCarriageReturn])
|
|
911
911
|
]
|
|
912
912
|
|
|
913
913
|
// originalTransaction is available for restore purchase and purchase of cancelled/expired subscriptions
|
package/lib/commonjs/iap.js
CHANGED
|
@@ -63,7 +63,21 @@ const getNativeModule = () => {
|
|
|
63
63
|
};
|
|
64
64
|
/**
|
|
65
65
|
* Init module for purchase flow. Required on Android. In ios it will check whether user canMakePayment.
|
|
66
|
-
*
|
|
66
|
+
* ## Usage
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import React, {useEffect} from 'react';
|
|
70
|
+
import {View} from 'react-native';
|
|
71
|
+
import {initConnection} from 'react-native-iap';
|
|
72
|
+
|
|
73
|
+
const App = () => {
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
void initConnection();
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
return <View />;
|
|
79
|
+
};
|
|
80
|
+
```
|
|
67
81
|
*/
|
|
68
82
|
|
|
69
83
|
|
|
@@ -71,7 +85,23 @@ exports.getNativeModule = getNativeModule;
|
|
|
71
85
|
|
|
72
86
|
const initConnection = () => getNativeModule().initConnection();
|
|
73
87
|
/**
|
|
74
|
-
*
|
|
88
|
+
* Disconnects from native SDK
|
|
89
|
+
* Usage
|
|
90
|
+
* ```tsx
|
|
91
|
+
import React, {useEffect} from 'react';
|
|
92
|
+
import {View} from 'react-native';
|
|
93
|
+
import {endConnection} from 'react-native-iap';
|
|
94
|
+
|
|
95
|
+
const App = () => {
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
return () => {
|
|
98
|
+
void endConnection();
|
|
99
|
+
};
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
102
|
+
return <View />;
|
|
103
|
+
};
|
|
104
|
+
```
|
|
75
105
|
* @returns {Promise<void>}
|
|
76
106
|
*/
|
|
77
107
|
|
|
@@ -90,8 +120,47 @@ exports.endConnection = endConnection;
|
|
|
90
120
|
const flushFailedPurchasesCachedAsPendingAndroid = () => getAndroidModule().flushFailedPurchasesCachedAsPending();
|
|
91
121
|
/**
|
|
92
122
|
* Get a list of products (consumable and non-consumable items, but not subscriptions)
|
|
93
|
-
|
|
94
|
-
|
|
123
|
+
## Usage
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import React, {useState} from 'react';
|
|
127
|
+
import {Platform} from 'react-native';
|
|
128
|
+
import {getProducts, Product} from 'react-native-iap';
|
|
129
|
+
|
|
130
|
+
const skus = Platform.select({
|
|
131
|
+
ios: ['com.example.consumableIos'],
|
|
132
|
+
android: ['com.example.consumableAndroid'],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const App = () => {
|
|
136
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
137
|
+
|
|
138
|
+
const handleProducts = async () => {
|
|
139
|
+
const items = await getProducts({skus});
|
|
140
|
+
|
|
141
|
+
setProducts(items);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
void handleProducts();
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<>
|
|
150
|
+
{products.map((product) => (
|
|
151
|
+
<Text key={product.productId}>{product.productId}</Text>
|
|
152
|
+
))}
|
|
153
|
+
</>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Just a few things to keep in mind:
|
|
159
|
+
|
|
160
|
+
- You can get your products in `componentDidMount`, `useEffect` or another appropriate area of your app.
|
|
161
|
+
- Since a user may start your app with a bad or no internet connection, preparing/getting the items more than once may be a good idea.
|
|
162
|
+
- If the user has no IAPs available when the app starts first, you may want to check again when the user enters your IAP store.
|
|
163
|
+
|
|
95
164
|
*/
|
|
96
165
|
|
|
97
166
|
|
|
@@ -114,8 +183,24 @@ const getProducts = _ref => {
|
|
|
114
183
|
};
|
|
115
184
|
/**
|
|
116
185
|
* Get a list of subscriptions
|
|
117
|
-
*
|
|
118
|
-
|
|
186
|
+
* ## Usage
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
import React, {useCallback} from 'react';
|
|
190
|
+
import {View} from 'react-native';
|
|
191
|
+
import {getSubscriptions} from 'react-native-iap';
|
|
192
|
+
|
|
193
|
+
const App = () => {
|
|
194
|
+
const subscriptions = useCallback(
|
|
195
|
+
async () =>
|
|
196
|
+
await getSubscriptions(['com.example.product1', 'com.example.product2']),
|
|
197
|
+
[],
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return <View />;
|
|
201
|
+
};
|
|
202
|
+
```
|
|
203
|
+
|
|
119
204
|
*/
|
|
120
205
|
|
|
121
206
|
|
|
@@ -138,7 +223,26 @@ const getSubscriptions = _ref2 => {
|
|
|
138
223
|
};
|
|
139
224
|
/**
|
|
140
225
|
* Gets an inventory of purchases made by the user regardless of consumption status
|
|
141
|
-
*
|
|
226
|
+
* ## Usage
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
import React, {useCallback} from 'react';
|
|
230
|
+
import {View} from 'react-native';
|
|
231
|
+
import {getPurchaseHistory} from 'react-native-iap';
|
|
232
|
+
|
|
233
|
+
const App = () => {
|
|
234
|
+
const history = useCallback(
|
|
235
|
+
async () =>
|
|
236
|
+
await getPurchaseHistory([
|
|
237
|
+
'com.example.product1',
|
|
238
|
+
'com.example.product2',
|
|
239
|
+
]),
|
|
240
|
+
[],
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return <View />;
|
|
244
|
+
};
|
|
245
|
+
```
|
|
142
246
|
*/
|
|
143
247
|
|
|
144
248
|
|
|
@@ -160,7 +264,81 @@ const getPurchaseHistory = () => (_reactNative.Platform.select({
|
|
|
160
264
|
}) || (() => Promise.resolve([])))();
|
|
161
265
|
/**
|
|
162
266
|
* Get all purchases made by the user (either non-consumable, or haven't been consumed yet)
|
|
163
|
-
*
|
|
267
|
+
* ## Usage
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import React, {useCallback} from 'react';
|
|
271
|
+
import {View} from 'react-native';
|
|
272
|
+
import {getAvailablePurchases} from 'react-native-iap';
|
|
273
|
+
|
|
274
|
+
const App = () => {
|
|
275
|
+
const availablePurchases = useCallback(
|
|
276
|
+
async () => await getAvailablePurchases(),
|
|
277
|
+
[],
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
return <View />;
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Restoring purchases
|
|
285
|
+
|
|
286
|
+
You can use `getAvailablePurchases()` to do what's commonly understood as "restoring" purchases.
|
|
287
|
+
|
|
288
|
+
:::note
|
|
289
|
+
For debugging you may want to consume all items, you have then to iterate over the purchases returned by `getAvailablePurchases()`.
|
|
290
|
+
:::
|
|
291
|
+
|
|
292
|
+
:::warning
|
|
293
|
+
Beware that if you consume an item without having recorded the purchase in your database the user may have paid for something without getting it delivered and you will have no way to recover the receipt to validate and restore their purchase.
|
|
294
|
+
:::
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
import React from 'react';
|
|
298
|
+
import {Button} from 'react-native';
|
|
299
|
+
import {getAvailablePurchases,finishTransaction} from 'react-native-iap';
|
|
300
|
+
|
|
301
|
+
const App = () => {
|
|
302
|
+
handleRestore = async () => {
|
|
303
|
+
try {
|
|
304
|
+
const purchases = await getAvailablePurchases();
|
|
305
|
+
const newState = {premium: false, ads: true};
|
|
306
|
+
let titles = [];
|
|
307
|
+
|
|
308
|
+
await Promise.all(purchases.map(async purchase => {
|
|
309
|
+
switch (purchase.productId) {
|
|
310
|
+
case 'com.example.premium':
|
|
311
|
+
newState.premium = true;
|
|
312
|
+
titles.push('Premium Version');
|
|
313
|
+
break;
|
|
314
|
+
|
|
315
|
+
case 'com.example.no_ads':
|
|
316
|
+
newState.ads = false;
|
|
317
|
+
titles.push('No Ads');
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'com.example.coins100':
|
|
321
|
+
await finishTransaction(purchase.purchaseToken);
|
|
322
|
+
CoinStore.addCoins(100);
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
Alert.alert(
|
|
327
|
+
'Restore Successful',
|
|
328
|
+
`You successfully restored the following purchases: ${titles.join(', ')}`,
|
|
329
|
+
);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.warn(error);
|
|
332
|
+
Alert.alert(error.message);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<Button title="Restore purchases" onPress={handleRestore} />
|
|
338
|
+
)
|
|
339
|
+
};
|
|
340
|
+
```
|
|
341
|
+
*
|
|
164
342
|
*/
|
|
165
343
|
|
|
166
344
|
|
|
@@ -182,14 +360,70 @@ const getAvailablePurchases = () => (_reactNative.Platform.select({
|
|
|
182
360
|
}) || (() => Promise.resolve([])))();
|
|
183
361
|
/**
|
|
184
362
|
* Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
|
|
185
|
-
*
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
363
|
+
* Request a purchase for a product (consumables or non-consumables).
|
|
364
|
+
|
|
365
|
+
The response will be received through the `PurchaseUpdatedListener`.
|
|
366
|
+
|
|
367
|
+
:::note
|
|
368
|
+
`andDangerouslyFinishTransactionAutomatically` defaults to false. We recommend
|
|
369
|
+
always keeping at false, and verifying the transaction receipts on the server-side.
|
|
370
|
+
:::
|
|
371
|
+
|
|
372
|
+
## Signature
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
requestPurchase(
|
|
376
|
+
The product's sku/ID
|
|
377
|
+
sku,
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
* You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user.
|
|
381
|
+
* @default false
|
|
382
|
+
|
|
383
|
+
andDangerouslyFinishTransactionAutomaticallyIOS = false,
|
|
384
|
+
|
|
385
|
+
/** Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
|
|
386
|
+
obfuscatedAccountIdAndroid,
|
|
387
|
+
|
|
388
|
+
Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
|
|
389
|
+
obfuscatedProfileIdAndroid,
|
|
390
|
+
|
|
391
|
+
The purchaser's user ID
|
|
392
|
+
applicationUsername,
|
|
393
|
+
): Promise<ProductPurchase>;
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Usage
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
import React, {useCallback} from 'react';
|
|
400
|
+
import {Button} from 'react-native';
|
|
401
|
+
import {requestPurchase, Product, Sku, getProducts} from 'react-native-iap';
|
|
402
|
+
|
|
403
|
+
const App = () => {
|
|
404
|
+
const products = useCallback(
|
|
405
|
+
async () => getProducts(['com.example.product']),
|
|
406
|
+
[],
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const handlePurchase = async (sku: Sku) => {
|
|
410
|
+
await requestPurchase({sku});
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<>
|
|
415
|
+
{products.map((product) => (
|
|
416
|
+
<Button
|
|
417
|
+
key={product.productId}
|
|
418
|
+
title="Buy product"
|
|
419
|
+
onPress={() => handlePurchase(product.productId)}
|
|
420
|
+
/>
|
|
421
|
+
))}
|
|
422
|
+
</>
|
|
423
|
+
);
|
|
424
|
+
};
|
|
425
|
+
```
|
|
426
|
+
|
|
193
427
|
*/
|
|
194
428
|
|
|
195
429
|
|
|
@@ -236,15 +470,80 @@ const requestPurchase = _ref3 => {
|
|
|
236
470
|
};
|
|
237
471
|
/**
|
|
238
472
|
* Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
|
|
239
|
-
*
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
473
|
+
* Request a purchase for a subscription.
|
|
474
|
+
|
|
475
|
+
The response will be received through the `PurchaseUpdatedListener`.
|
|
476
|
+
|
|
477
|
+
:::note
|
|
478
|
+
`andDangerouslyFinishTransactionAutomatically` defaults to false. We recommend
|
|
479
|
+
always keeping at false, and verifying the transaction receipts on the server-side.
|
|
480
|
+
:::
|
|
481
|
+
|
|
482
|
+
## Signature
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
requestSubscription(
|
|
486
|
+
The product's sku/ID
|
|
487
|
+
sku,
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
* You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user.
|
|
491
|
+
* @default false
|
|
492
|
+
|
|
493
|
+
andDangerouslyFinishTransactionAutomaticallyIOS = false,
|
|
494
|
+
|
|
495
|
+
purchaseToken that the user is upgrading or downgrading from (Android).
|
|
496
|
+
purchaseTokenAndroid,
|
|
497
|
+
|
|
498
|
+
UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY, IMMEDIATE_WITH_TIME_PRORATION, IMMEDIATE_AND_CHARGE_PRORATED_PRICE, IMMEDIATE_WITHOUT_PRORATION, DEFERRED
|
|
499
|
+
prorationModeAndroid = -1,
|
|
500
|
+
|
|
501
|
+
/** Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
|
|
502
|
+
obfuscatedAccountIdAndroid,
|
|
503
|
+
|
|
504
|
+
Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
|
|
505
|
+
obfuscatedProfileIdAndroid,
|
|
506
|
+
|
|
507
|
+
The purchaser's user ID
|
|
508
|
+
applicationUsername,
|
|
509
|
+
): Promise<SubscriptionPurchase>
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Usage
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import React, {useCallback} from 'react';
|
|
516
|
+
import {Button} from 'react-native';
|
|
517
|
+
import {
|
|
518
|
+
requestSubscription,
|
|
519
|
+
Product,
|
|
520
|
+
Sku,
|
|
521
|
+
getSubscriptions,
|
|
522
|
+
} from 'react-native-iap';
|
|
523
|
+
|
|
524
|
+
const App = () => {
|
|
525
|
+
const subscriptions = useCallback(
|
|
526
|
+
async () => getSubscriptions(['com.example.subscription']),
|
|
527
|
+
[],
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const handlePurchase = async (sku: Sku) => {
|
|
531
|
+
await requestSubscription({sku});
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<>
|
|
536
|
+
{subscriptions.map((subscription) => (
|
|
537
|
+
<Button
|
|
538
|
+
key={subscription.productId}
|
|
539
|
+
title="Buy subscription"
|
|
540
|
+
onPress={() => handlePurchase(subscription.productId)}
|
|
541
|
+
/>
|
|
542
|
+
))}
|
|
543
|
+
</>
|
|
544
|
+
);
|
|
545
|
+
};
|
|
546
|
+
```
|
|
248
547
|
*/
|
|
249
548
|
|
|
250
549
|
|
|
@@ -314,10 +613,22 @@ const requestPurchaseWithQuantityIOS = _ref5 => {
|
|
|
314
613
|
* Call this after you have persisted the purchased state to your server or local data in your app.
|
|
315
614
|
* `react-native-iap` will continue to deliver the purchase updated events with the successful purchase until you finish the transaction. **Even after the app has relaunched.**
|
|
316
615
|
* Android: it will consume purchase for consumables and acknowledge purchase for non-consumables.
|
|
317
|
-
*
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
616
|
+
*
|
|
617
|
+
```tsx
|
|
618
|
+
import React from 'react';
|
|
619
|
+
import {Button} from 'react-native';
|
|
620
|
+
import {finishTransaction} from 'react-native-iap';
|
|
621
|
+
|
|
622
|
+
const App = () => {
|
|
623
|
+
const handlePurchase = async () => {
|
|
624
|
+
// ... handle the purchase request
|
|
625
|
+
|
|
626
|
+
const result = finishTransaction(purchase);
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
return <Button title="Buy product" onPress={handlePurchase} />;
|
|
630
|
+
};
|
|
631
|
+
```
|
|
321
632
|
*/
|
|
322
633
|
|
|
323
634
|
|
|
@@ -495,7 +806,10 @@ const validateReceiptIos = async _ref10 => {
|
|
|
495
806
|
}
|
|
496
807
|
|
|
497
808
|
const url = isTest ? 'https://sandbox.itunes.apple.com/verifyReceipt' : 'https://buy.itunes.apple.com/verifyReceipt';
|
|
498
|
-
return await (0, _internal.enhancedFetch)(url
|
|
809
|
+
return await (0, _internal.enhancedFetch)(url, {
|
|
810
|
+
method: 'POST',
|
|
811
|
+
body: receiptBody
|
|
812
|
+
});
|
|
499
813
|
};
|
|
500
814
|
/**
|
|
501
815
|
* Validate receipt for Android. NOTE: This method is here for debugging purposes only. Including
|