react-native-iap 10.1.0 → 10.1.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/src/iap.ts CHANGED
@@ -14,12 +14,12 @@ import {
14
14
  Product,
15
15
  ProductPurchase,
16
16
  ProductType,
17
- ProrationModesAndroid,
18
17
  Purchase,
19
18
  PurchaseResult,
19
+ RequestPurchase,
20
+ RequestSubscription,
20
21
  Sku,
21
22
  Subscription,
22
- SubscriptionOffer,
23
23
  SubscriptionPurchase,
24
24
  } from './types';
25
25
  import {InstallSourceAndroid, PurchaseStateAndroid} from './types';
@@ -81,13 +81,43 @@ export const getNativeModule = ():
81
81
 
82
82
  /**
83
83
  * Init module for purchase flow. Required on Android. In ios it will check whether user canMakePayment.
84
- * @returns {Promise<boolean>}
84
+ * ## Usage
85
+
86
+ ```tsx
87
+ import React, {useEffect} from 'react';
88
+ import {View} from 'react-native';
89
+ import {initConnection} from 'react-native-iap';
90
+
91
+ const App = () => {
92
+ useEffect(() => {
93
+ void initConnection();
94
+ }, []);
95
+
96
+ return <View />;
97
+ };
98
+ ```
85
99
  */
86
100
  export const initConnection = (): Promise<boolean> =>
87
101
  getNativeModule().initConnection();
88
102
 
89
103
  /**
90
- * End module for purchase flow.
104
+ * Disconnects from native SDK
105
+ * Usage
106
+ * ```tsx
107
+ import React, {useEffect} from 'react';
108
+ import {View} from 'react-native';
109
+ import {endConnection} from 'react-native-iap';
110
+
111
+ const App = () => {
112
+ useEffect(() => {
113
+ return () => {
114
+ void endConnection();
115
+ };
116
+ }, []);
117
+
118
+ return <View />;
119
+ };
120
+ ```
91
121
  * @returns {Promise<void>}
92
122
  */
93
123
  export const endConnection = (): Promise<boolean> =>
@@ -103,8 +133,47 @@ export const flushFailedPurchasesCachedAsPendingAndroid =
103
133
 
104
134
  /**
105
135
  * Get a list of products (consumable and non-consumable items, but not subscriptions)
106
- * @param {string[]} skus The item skus
107
- * @returns {Promise<Product[]>}
136
+ ## Usage
137
+
138
+ ```ts
139
+ import React, {useState} from 'react';
140
+ import {Platform} from 'react-native';
141
+ import {getProducts, Product} from 'react-native-iap';
142
+
143
+ const skus = Platform.select({
144
+ ios: ['com.example.consumableIos'],
145
+ android: ['com.example.consumableAndroid'],
146
+ });
147
+
148
+ const App = () => {
149
+ const [products, setProducts] = useState<Product[]>([]);
150
+
151
+ const handleProducts = async () => {
152
+ const items = await getProducts({skus});
153
+
154
+ setProducts(items);
155
+ };
156
+
157
+ useEffect(() => {
158
+ void handleProducts();
159
+ }, []);
160
+
161
+ return (
162
+ <>
163
+ {products.map((product) => (
164
+ <Text key={product.productId}>{product.productId}</Text>
165
+ ))}
166
+ </>
167
+ );
168
+ };
169
+ ```
170
+
171
+ Just a few things to keep in mind:
172
+
173
+ - You can get your products in `componentDidMount`, `useEffect` or another appropriate area of your app.
174
+ - 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.
175
+ - 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.
176
+
108
177
  */
109
178
  export const getProducts = ({
110
179
  skus,
@@ -134,8 +203,24 @@ export const getProducts = ({
134
203
 
135
204
  /**
136
205
  * Get a list of subscriptions
137
- * @param {string[]} skus The item skus
138
- * @returns {Promise<Subscription[]>}
206
+ * ## Usage
207
+
208
+ ```tsx
209
+ import React, {useCallback} from 'react';
210
+ import {View} from 'react-native';
211
+ import {getSubscriptions} from 'react-native-iap';
212
+
213
+ const App = () => {
214
+ const subscriptions = useCallback(
215
+ async () =>
216
+ await getSubscriptions(['com.example.product1', 'com.example.product2']),
217
+ [],
218
+ );
219
+
220
+ return <View />;
221
+ };
222
+ ```
223
+
139
224
  */
140
225
  export const getSubscriptions = ({
141
226
  skus,
@@ -165,7 +250,26 @@ export const getSubscriptions = ({
165
250
 
166
251
  /**
167
252
  * Gets an inventory of purchases made by the user regardless of consumption status
168
- * @returns {Promise<(ProductPurchase | SubscriptionPurchase)[]>}
253
+ * ## Usage
254
+
255
+ ```tsx
256
+ import React, {useCallback} from 'react';
257
+ import {View} from 'react-native';
258
+ import {getPurchaseHistory} from 'react-native-iap';
259
+
260
+ const App = () => {
261
+ const history = useCallback(
262
+ async () =>
263
+ await getPurchaseHistory([
264
+ 'com.example.product1',
265
+ 'com.example.product2',
266
+ ]),
267
+ [],
268
+ );
269
+
270
+ return <View />;
271
+ };
272
+ ```
169
273
  */
170
274
  export const getPurchaseHistory = (): Promise<
171
275
  (ProductPurchase | SubscriptionPurchase)[]
@@ -195,7 +299,81 @@ export const getPurchaseHistory = (): Promise<
195
299
 
196
300
  /**
197
301
  * Get all purchases made by the user (either non-consumable, or haven't been consumed yet)
198
- * @returns {Promise<(ProductPurchase | SubscriptionPurchase)[]>}
302
+ * ## Usage
303
+
304
+ ```tsx
305
+ import React, {useCallback} from 'react';
306
+ import {View} from 'react-native';
307
+ import {getAvailablePurchases} from 'react-native-iap';
308
+
309
+ const App = () => {
310
+ const availablePurchases = useCallback(
311
+ async () => await getAvailablePurchases(),
312
+ [],
313
+ );
314
+
315
+ return <View />;
316
+ };
317
+ ```
318
+
319
+ ## Restoring purchases
320
+
321
+ You can use `getAvailablePurchases()` to do what's commonly understood as "restoring" purchases.
322
+
323
+ :::note
324
+ For debugging you may want to consume all items, you have then to iterate over the purchases returned by `getAvailablePurchases()`.
325
+ :::
326
+
327
+ :::warning
328
+ 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.
329
+ :::
330
+
331
+ ```tsx
332
+ import React from 'react';
333
+ import {Button} from 'react-native';
334
+ import {getAvailablePurchases,finishTransaction} from 'react-native-iap';
335
+
336
+ const App = () => {
337
+ handleRestore = async () => {
338
+ try {
339
+ const purchases = await getAvailablePurchases();
340
+ const newState = {premium: false, ads: true};
341
+ let titles = [];
342
+
343
+ await Promise.all(purchases.map(async purchase => {
344
+ switch (purchase.productId) {
345
+ case 'com.example.premium':
346
+ newState.premium = true;
347
+ titles.push('Premium Version');
348
+ break;
349
+
350
+ case 'com.example.no_ads':
351
+ newState.ads = false;
352
+ titles.push('No Ads');
353
+ break;
354
+
355
+ case 'com.example.coins100':
356
+ await finishTransaction(purchase.purchaseToken);
357
+ CoinStore.addCoins(100);
358
+ }
359
+ })
360
+
361
+ Alert.alert(
362
+ 'Restore Successful',
363
+ `You successfully restored the following purchases: ${titles.join(', ')}`,
364
+ );
365
+ } catch (error) {
366
+ console.warn(error);
367
+ Alert.alert(error.message);
368
+ }
369
+ };
370
+
371
+ return (
372
+ <Button title="Restore purchases" onPress={handleRestore} />
373
+ )
374
+ };
375
+ ```
376
+ *
199
377
  */
200
378
  export const getAvailablePurchases = (): Promise<
201
379
  (ProductPurchase | SubscriptionPurchase)[]
@@ -225,14 +403,70 @@ export const getAvailablePurchases = (): Promise<
225
403
 
226
404
  /**
227
405
  * Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
228
- * @param {string} sku The product's sku/ID
229
- * @param {string} [applicationUsername] The purchaser's user ID
230
- * @param {boolean} [andDangerouslyFinishTransactionAutomaticallyIOS] You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.
231
- * @param {string} [obfuscatedAccountIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
232
- * @param {string} [obfuscatedProfileIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
233
- * @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
234
- * @param {boolean} [isOfferPersonalized] Defaults to false, Only for Android V5
235
- * @returns {Promise<ProductPurchase>}
406
+ * Request a purchase for a product (consumables or non-consumables).
407
+
408
+ The response will be received through the `PurchaseUpdatedListener`.
409
+
410
+ :::note
411
+ `andDangerouslyFinishTransactionAutomatically` defaults to false. We recommend
412
+ always keeping at false, and verifying the transaction receipts on the server-side.
413
+ :::
414
+
415
+ ## Signature
416
+
417
+ ```ts
418
+ requestPurchase(
419
+ The product's sku/ID
420
+ sku,
421
+
422
+
423
+ * You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user.
424
+ * @default false
425
+
426
+ andDangerouslyFinishTransactionAutomaticallyIOS = false,
427
+
428
+ /** Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
429
+ obfuscatedAccountIdAndroid,
430
+
431
+ Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
432
+ obfuscatedProfileIdAndroid,
433
+
434
+ The purchaser's user ID
435
+ applicationUsername,
436
+ ): Promise<ProductPurchase>;
437
+ ```
438
+
439
+ ## Usage
440
+
441
+ ```tsx
442
+ import React, {useCallback} from 'react';
443
+ import {Button} from 'react-native';
444
+ import {requestPurchase, Product, Sku, getProducts} from 'react-native-iap';
445
+
446
+ const App = () => {
447
+ const products = useCallback(
448
+ async () => getProducts(['com.example.product']),
449
+ [],
450
+ );
451
+
452
+ const handlePurchase = async (sku: Sku) => {
453
+ await requestPurchase({sku});
454
+ };
455
+
456
+ return (
457
+ <>
458
+ {products.map((product) => (
459
+ <Button
460
+ key={product.productId}
461
+ title="Buy product"
462
+ onPress={() => handlePurchase(product.productId)}
463
+ />
464
+ ))}
465
+ </>
466
+ );
467
+ };
468
+ ```
469
+
236
470
  */
237
471
 
238
472
  export const requestPurchase = ({
@@ -243,16 +477,7 @@ export const requestPurchase = ({
243
477
  obfuscatedProfileIdAndroid,
244
478
  skus,
245
479
  isOfferPersonalized,
246
- }: {
247
- sku?: Sku;
248
- andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
249
- applicationUsername?: string;
250
- obfuscatedAccountIdAndroid?: string;
251
- obfuscatedProfileIdAndroid?: string;
252
- /** For Google Play Billing Library 5 https://developer.android.com/google/play/billing/integrate#personalized-price */
253
- skus?: Sku[];
254
- isOfferPersonalized?: boolean;
255
- }): Promise<ProductPurchase | void> =>
480
+ }: RequestPurchase): Promise<ProductPurchase | void> =>
256
481
  (
257
482
  Platform.select({
258
483
  ios: async () => {
@@ -302,15 +527,80 @@ export const requestPurchase = ({
302
527
 
303
528
  /**
304
529
  * Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
305
- * @param {string} [sku] The product's sku/ID
306
- * @param {string} [applicationUsername] The purchaser's user ID
307
- * @param {boolean} [andDangerouslyFinishTransactionAutomaticallyIOS] You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.
308
- * @param {string} [purchaseTokenAndroid] purchaseToken that the user is upgrading or downgrading from (Android).
309
- * @param {ProrationModesAndroid} [prorationModeAndroid] UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY, IMMEDIATE_WITH_TIME_PRORATION, IMMEDIATE_AND_CHARGE_PRORATED_PRICE, IMMEDIATE_WITHOUT_PRORATION, DEFERRED
310
- * @param {string} [obfuscatedAccountIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
311
- * @param {string} [obfuscatedProfileIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
312
- * @param {SubscriptionOffers[]} [subscriptionOffers] Array of SubscriptionOffers. Every sku must be paired with a corresponding offerToken
313
- * @returns {Promise<SubscriptionPurchase | null>} Promise resolves to null when using proratioModesAndroid=DEFERRED, and to a SubscriptionPurchase otherwise
530
+ * Request a purchase for a subscription.
531
+
532
+ The response will be received through the `PurchaseUpdatedListener`.
533
+
534
+ :::note
535
+ `andDangerouslyFinishTransactionAutomatically` defaults to false. We recommend
536
+ always keeping at false, and verifying the transaction receipts on the server-side.
537
+ :::
538
+
539
+ ## Signature
540
+
541
+ ```ts
542
+ requestSubscription(
543
+ The product's sku/ID
544
+ sku,
545
+
546
+
547
+ * You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user.
548
+ * @default false
549
+
550
+ andDangerouslyFinishTransactionAutomaticallyIOS = false,
551
+
552
+ purchaseToken that the user is upgrading or downgrading from (Android).
553
+ purchaseTokenAndroid,
554
+
555
+ UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY, IMMEDIATE_WITH_TIME_PRORATION, IMMEDIATE_AND_CHARGE_PRORATED_PRICE, IMMEDIATE_WITHOUT_PRORATION, DEFERRED
556
+ prorationModeAndroid = -1,
557
+
558
+ /** Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
559
+ obfuscatedAccountIdAndroid,
560
+
561
+ Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
562
+ obfuscatedProfileIdAndroid,
563
+
564
+ The purchaser's user ID
565
+ applicationUsername,
566
+ ): Promise<SubscriptionPurchase>
567
+ ```
568
+
569
+ ## Usage
570
+
571
+ ```tsx
572
+ import React, {useCallback} from 'react';
573
+ import {Button} from 'react-native';
574
+ import {
575
+ requestSubscription,
576
+ Product,
577
+ Sku,
578
+ getSubscriptions,
579
+ } from 'react-native-iap';
580
+
581
+ const App = () => {
582
+ const subscriptions = useCallback(
583
+ async () => getSubscriptions(['com.example.subscription']),
584
+ [],
585
+ );
586
+
587
+ const handlePurchase = async (sku: Sku) => {
588
+ await requestSubscription({sku});
589
+ };
590
+
591
+ return (
592
+ <>
593
+ {subscriptions.map((subscription) => (
594
+ <Button
595
+ key={subscription.productId}
596
+ title="Buy subscription"
597
+ onPress={() => handlePurchase(subscription.productId)}
598
+ />
599
+ ))}
600
+ </>
601
+ );
602
+ };
603
+ ```
314
604
  */
315
605
  export const requestSubscription = ({
316
606
  sku,
@@ -322,19 +612,7 @@ export const requestSubscription = ({
322
612
  obfuscatedAccountIdAndroid,
323
613
  obfuscatedProfileIdAndroid,
324
614
  isOfferPersonalized = undefined,
325
- }: {
326
- sku?: Sku;
327
- andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
328
- applicationUsername?: string;
329
- purchaseTokenAndroid?: string;
330
- prorationModeAndroid?: ProrationModesAndroid;
331
- /** For Google Play Billing Library 5 */
332
- subscriptionOffers?: SubscriptionOffer[];
333
- obfuscatedAccountIdAndroid?: string;
334
- obfuscatedProfileIdAndroid?: string;
335
- /** For Google Play Billing Library 5 https://developer.android.com/google/play/billing/integrate#personalized-price */
336
- isOfferPersonalized?: boolean;
337
- }): Promise<SubscriptionPurchase | null | void> =>
615
+ }: RequestSubscription): Promise<SubscriptionPurchase | null | void> =>
338
616
  (
339
617
  Platform.select({
340
618
  ios: async () => {
@@ -405,10 +683,22 @@ export const requestPurchaseWithQuantityIOS = ({
405
683
  * Call this after you have persisted the purchased state to your server or local data in your app.
406
684
  * `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.**
407
685
  * Android: it will consume purchase for consumables and acknowledge purchase for non-consumables.
408
- * @param {object} purchase The purchase that you would like to finish.
409
- * @param {boolean} isConsumable Checks if purchase is consumable. Has effect on `android`.
410
- * @param {string} developerPayloadAndroid Android developerPayload.
411
- * @returns {Promise<string | void> }
686
+ *
687
+ ```tsx
688
+ import React from 'react';
689
+ import {Button} from 'react-native';
690
+ import {finishTransaction} from 'react-native-iap';
691
+
692
+ const App = () => {
693
+ const handlePurchase = async () => {
694
+ // ... handle the purchase request
695
+
696
+ const result = finishTransaction(purchase);
697
+ };
698
+
699
+ return <Button title="Buy product" onPress={handlePurchase} />;
700
+ };
701
+ ```
412
702
  */
413
703
  export const finishTransaction = ({
414
704
  purchase,
@@ -603,7 +893,10 @@ export const validateReceiptIos = async ({
603
893
  ? 'https://sandbox.itunes.apple.com/verifyReceipt'
604
894
  : 'https://buy.itunes.apple.com/verifyReceipt';
605
895
 
606
- return await enhancedFetch<ReceiptValidationResponse>(url);
896
+ return await enhancedFetch<ReceiptValidationResponse>(url, {
897
+ method: 'POST',
898
+ body: receiptBody,
899
+ });
607
900
  };
608
901
 
609
902
  /**
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from './eventEmitter';
4
4
  export * from './hooks/useIAP';
5
5
  export * from './hooks/withIAPContext';
6
6
  export * from './purchaseError';
7
+ export * from './modules';
@@ -4,6 +4,8 @@ import type {
4
4
  IosModuleProps,
5
5
  } from '../modules';
6
6
 
7
+ import type * as Apple from './apple';
8
+
7
9
  export type Sku = string;
8
10
 
9
11
  export enum ProrationModesAndroid {
@@ -45,7 +47,7 @@ export enum ProductType {
45
47
 
46
48
  export interface ProductCommon {
47
49
  type: 'subs' | 'sub' | 'inapp' | 'iap';
48
- productId: string;
50
+ productId: string; //iOS
49
51
  productIds?: string[];
50
52
  title: string;
51
53
  description: string;
@@ -109,15 +111,19 @@ export interface Discount {
109
111
  subscriptionPeriod: string;
110
112
  }
111
113
 
112
- export interface Product extends ProductCommon {
114
+ export interface ProductAndroid extends ProductCommon {
113
115
  type: 'inapp' | 'iap';
114
- // Android V5
115
116
  oneTimePurchaseOfferDetails?: {
116
117
  priceCurrencyCode: string;
117
118
  formattedPrice: string;
118
119
  priceAmountMicros: string;
119
120
  };
120
121
  }
122
+ export interface ProductIOS extends ProductCommon {
123
+ type: 'inapp' | 'iap';
124
+ }
125
+
126
+ export type Product = ProductAndroid & ProductIOS;
121
127
 
122
128
  // Android V5
123
129
  export interface SubscriptionAndroid extends ProductCommon {
@@ -145,7 +151,6 @@ export interface SubscriptionAndroid extends ProductCommon {
145
151
 
146
152
  export interface SubscriptionIOS extends ProductCommon {
147
153
  type: 'subs';
148
-
149
154
  discounts?: Discount[];
150
155
  introductoryPrice?: string;
151
156
  introductoryPriceAsAmountIOS?: string;
@@ -167,6 +172,28 @@ export interface SubscriptionIOS extends ProductCommon {
167
172
  }
168
173
 
169
174
  export type Subscription = SubscriptionAndroid & SubscriptionIOS;
175
+ export interface RequestPurchaseBaseAndroid {
176
+ obfuscatedAccountIdAndroid?: string;
177
+ obfuscatedProfileIdAndroid?: string;
178
+ isOfferPersonalized?: boolean; // For AndroidBilling V5 https://developer.android.com/google/play/billing/integrate#personalized-price
179
+ }
180
+
181
+ export interface RequestPurchaseAndroid extends RequestPurchaseBaseAndroid {
182
+ skus?: Sku[];
183
+ }
184
+
185
+ export interface RequestPurchaseIOS {
186
+ sku?: Sku;
187
+ andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
188
+ /**
189
+ * UUID representing user account
190
+ */
191
+ applicationUsername?: string;
192
+ quantity?: number;
193
+ withOffer?: Apple.PaymentDiscount;
194
+ }
195
+
196
+ export type RequestPurchase = RequestPurchaseAndroid & RequestPurchaseIOS;
170
197
 
171
198
  /**
172
199
  * In order to purchase a new subscription, every sku must have a selected offerToken
@@ -177,6 +204,17 @@ export interface SubscriptionOffer {
177
204
  offerToken: string;
178
205
  }
179
206
 
207
+ export interface RequestSubscriptionAndroid extends RequestPurchaseBaseAndroid {
208
+ purchaseTokenAndroid?: string;
209
+ prorationModeAndroid?: ProrationModesAndroid;
210
+ subscriptionOffers?: SubscriptionOffer[]; // For AndroidBilling V5
211
+ }
212
+
213
+ export type RequestSubscriptionIOS = RequestPurchaseIOS;
214
+
215
+ export type RequestSubscription = RequestSubscriptionAndroid &
216
+ RequestSubscriptionIOS;
217
+
180
218
  declare module 'react-native' {
181
219
  interface NativeModulesStatic {
182
220
  RNIapIos: IosModuleProps;