@spicysparks/react-native-iap
Version:
React Native In App Purchase Module.
145 lines (138 loc) • 5.66 kB
JavaScript
import { Linking, NativeModules } from 'react-native';
import { getIosModule, isIosStorekit2 } from '../internal';
const {
RNIapIos
} = NativeModules;
/**
* Get the current receipt base64 encoded in IOS.
* @returns {Promise<ProductPurchase[]>}
*/
export const getPendingPurchasesIOS = async () => getIosModule().getPendingTransactions();
/**
* Get the current receipt base64 encoded in IOS.
*
* The sequence should be as follows:
* Call getReceiptIOS({forceRefresh: false}). That will return the cached receipt that is available on TestFlight and Production.
* In the case of Sandbox the receipt might not be cached, causing it to return nil.
* In that case you might want to let the user that they will to be prompted for credentials.
* If they accept, call it again with `getReceiptIOS({forceRefresh:true}) If it fails or the user declines, assume they haven't purchased any items.
* Reference: https://developer.apple.com/forums/thread/662350
*
* From: https://apphud.com/blog/app-store-receipt-validation#what-is-app-store-receipt
> Q: Does a receipt always exist in the app?
> A: If a user downloaded the app from the App Store – yes. However, in sandbox if your app was installed via Xcode or Testflight, then there won't be a receipt until you make a purchase or restore.
*
## Usage
```tsx
import {getReceiptIOS} from 'react-native-iap';
try{
let receipt = await getReceiptIOS({forceRefresh: false});
if(!receipt){
// Let user know that they might get prompted for credentials
const shouldShowPrompt = // Display UI with details, Did user agree?. this only for Sandbox testing
if(shouldShowPrompt){
receipt = await getReceiptIOS({forceRefresh: true});
}
}
}catch(error:Error){
// error while getting the receipt, it might indicate an invalid receipt of a connection error while trying to get it
}
// If !receipt assume user doesn't own the items
```
* @param {forceRefresh?:boolean} Requests the receipt from Bundle.main.appStoreReceiptURL.
Based on the note above, looks like forceRefresh only makes sense when testing an app not downloaded from the Appstore.
And only afer a direct user action.
* @returns {Promise<string | undefined | null>} The receipt data
*/
export const getReceiptIOS = async ({
forceRefresh
}) => {
if (!isIosStorekit2()) {
return RNIapIos.requestReceipt(forceRefresh ?? false);
} else {
return Promise.reject('Only available on Sk1');
}
};
/**
* Launches a modal to register the redeem offer code in IOS.
* @returns {Promise<null>}
*/
export const presentCodeRedemptionSheetIOS = async () => getIosModule().presentCodeRedemptionSheet();
/**
* Should Add Store Payment (iOS only)
* Indicates the the App Store purchase should continue from the app instead of the App Store.
* @returns {Promise<Product | null>} promoted product
*/
export const getPromotedProductIOS = () => {
if (!isIosStorekit2()) {
return RNIapIos.promotedProduct();
} else {
return Promise.reject('Only available on Sk1');
}
};
/**
* Buy the currently selected promoted product (iOS only)
* Initiates the payment process for a promoted product. Should only be called in response to the `iap-promoted-product` event.
* @returns {Promise<void>}
*/
export const buyPromotedProductIOS = () => getIosModule().buyPromotedProduct();
const fetchJsonOrThrow = async (url, receiptBody) => {
const response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(receiptBody)
});
if (!response.ok) {
throw Object.assign(new Error(response.statusText), {
statusCode: response.status
});
}
return response.json();
};
const TEST_RECEIPT = 21007;
const requestAgnosticReceiptValidationIos = async receiptBody => {
const response = await fetchJsonOrThrow('https://buy.itunes.apple.com/verifyReceipt', receiptBody);
// Best practice is to check for test receipt and check sandbox instead
// https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
if (response && response.status === TEST_RECEIPT) {
const testResponse = await fetchJsonOrThrow('https://sandbox.itunes.apple.com/verifyReceipt', receiptBody);
return testResponse;
}
return response;
};
/**
* Validate receipt for iOS.
* @param {object} receiptBody the receipt body to send to apple server.
* @param {boolean} isTest whether this is in test environment which is sandbox.
* @returns {Promise<Apple.ReceiptValidationResponse | false>}
*/
export const validateReceiptIos = async ({
receiptBody,
isTest
}) => {
if (isTest == null) {
return await requestAgnosticReceiptValidationIos(receiptBody);
}
const url = isTest ? 'https://sandbox.itunes.apple.com/verifyReceipt' : 'https://buy.itunes.apple.com/verifyReceipt';
const response = await fetchJsonOrThrow(url, receiptBody);
return response;
};
/**
* Clear Transaction (iOS only)
* Finish remaining transactions. Related to issue #257 and #801
* link : https://github.com/dooboolab-community/react-native-iap/issues/257
* https://github.com/dooboolab-community/react-native-iap/issues/801
* @returns {Promise<void>}
*/
export const clearTransactionIOS = () => getIosModule().clearTransaction();
/**
* Clear valid Products (iOS only)
* Remove all products which are validated by Apple server.
* @returns {void}
*/
export const clearProductsIOS = () => getIosModule().clearProducts();
export const deepLinkToSubscriptionsIos = () => Linking.openURL('https://apps.apple.com/account/subscriptions');
//# sourceMappingURL=ios.js.map