Hello Stack Overflow community,
I am currently working on a React Native application with a Node.js backend, targeting both Android and iOS platforms. Specifically, I am integrating in-app purchase functionality for auto-renewable subscriptions on the iOS platform. To achieve this, I am utilizing the react-native-iap library, and my implementation is in JavaScript.
I have successfully added subscriptions in the Apple Developer Console, and I am able to fetch them into my app. However, I am encountering an issue during the subscription process on iOS. After clicking on the product in the sandbox UI, a popup with subscription details, including a subscribe button and price, appears. Upon clicking the subscribe button, the system prompts for sign-in, and after entering valid credentials, the transaction does not complete.
Additionally, I am facing challenges in obtaining the purchase receipt. As a newcomer to both React Native and in-app purchases, I would greatly appreciate any guidance or insights on how to overcome these issues.
Below is the relevant snippet of my code:
import React, { useEffect, useState } from 'react';import { View, Text, Button, StyleSheet, Alert, Platform } from 'react-native';import { useIAP, validateReceiptIos, finishTransaction, purchaseErrorListener, purchaseUpdatedListener, clearTransactionIOS,} from 'react-native-iap';import axios from 'axios';import { ITUNES_SHARED_SECRET } from "@env";const SubscriptionScreen = ({ navigation }) => { const { requestSubscription, getSubscriptions, currentPurchase, purchaseHistory, } = useIAP(); const [subscriptionStatus, setSubscriptionStatus] = useState(false); const [loading, setLoading] = useState(false); useEffect(() => { handleGetSubscriptions(); setupPurchaseListeners(); // Setup purchase listeners on mount }, []); useEffect(() => { if (purchaseHistory && purchaseHistory.length > 0) { const isSubscribed = purchaseHistory.some((purchase) => subscriptionSkus.includes(purchase.productId) ); if (isSubscribed) { setSubscriptionStatus(true); } } }, [purchaseHistory]); const subscriptionSkus = Platform.select({# ios: ['subscriptionProductID'], }); const handleGetSubscriptions = async () => { try { await getSubscriptions({ skus: subscriptionSkus }); } catch (error) { console.error('Error getting subscriptions:', error); } }; const handleBuySubscription = async (productId) => { try { setLoading(true); // Request subscription const purchase = await requestSubscription({ sku: productId, ...(Platform.OS === 'ios'&& { andDangerouslyFinishTransactionAutomaticallyIOS: false, }), }); // Note: finishTransaction should be handled within purchaseUpdatedListener if (purchase.transactionState === 'Purchased') { const receipt = purchase.transactionReceipt; await handleValidateReceipt(receipt); navigation.navigate('IndexPage'); } else { console.warn('Transaction state:', purchase.transactionState); } } catch (err) { console.error(err.code, err.message); } finally { setLoading(false); } }; const handleValidateReceipt = async (receipt) => { try { const isTestEnvironment = false; const response = await validateReceiptIos( {'receipt-data': receipt, password: ITUNES_SHARED_SECRET, }, isTestEnvironment ); if (response && response.status === 0) { const serverValidationResult = await validateReceiptOnServer(receipt); if (serverValidationResult.valid) { setSubscriptionStatus(true); Alert.alert('Subscription Purchased', 'Thank you for subscribing!'); } else { console.warn('Server validation failed'); } } else { console.warn('Receipt validation failed'); } } catch (error) { console.error('Error during receipt validation:', error); } }; const validateReceiptOnServer = async (receipt) => { try { const response = await axios.post('YOUR_SERVER_ENDPOINT', { receipt }); return response.data; } catch (error) { console.error('Error validating receipt on server:', error); return { valid: false }; } }; const setupPurchaseListeners = () => { // Listen for purchase errors const purchaseErrorSubscription = purchaseErrorListener((error) => { console.warn('purchaseErrorListener', error); }); // Listen for purchase updates const purchaseUpdateSubscription = purchaseUpdatedListener((purchase) => { console.log('purchaseUpdatedListener', purchase); if (purchase.transactionState === 'Purchased') { const receipt = purchase.transactionReceipt; handleValidateReceipt(receipt); finishTransaction({ purchase, isConsumable: false }); } }); return () => { // Remove the listeners on unmount purchaseErrorSubscription.remove(); purchaseUpdateSubscription.remove(); }; }; useEffect(() => { const checkCurrentPurchase = async () => { if (currentPurchase) { const receipt = currentPurchase.transactionReceipt; if (receipt) { await handleValidateReceipt(receipt); try { await finishTransaction({ purchase: currentPurchase, isConsumable: false, }); } catch (err) { console.warn(err.code, err.message); } } } }; checkCurrentPurchase(); }, [currentPurchase, finishTransaction]); const handleClearTransactions = async () => { try { await clearTransactionIOS(); console.log('Cleared pending transactions.'); } catch (error) { console.error('Error while clearing transactions:', error); } }; return (<View style={styles.container}><Text>{subscriptionStatus ? 'Subscribed' : 'Not Subscribed'}</Text> {subscriptionSkus.map((sku) => (<Button key={sku} title={`Purchase ${sku}`} onPress={() => handleBuySubscription(sku)} /> ))}<Button title="Clear Transactions" onPress={handleClearTransactions} /></View> );};const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', },});export default SubscriptionScreen;I am currently facing challenges with the submission of my application, even after removing payment and subscription functionalities. I have attempted to submit the app without any payment-related features, yet it continues to face rejection.coz in app purchase business payment apple guidelines