2

I want to add In-App purchases (subscriptions) to my react-native app. I use react-native-iap. I added subscriptions in https://appstoreconnect.apple.com/ Features -> Subscriptions, I have 1 group with 2 subscriptions. enter image description here I added In-App purchase capability in Xcode. enter image description here My subscriptions screen:

import { useEffect } from 'react';
import { ImageBackground, Platform, SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native';
import { PurchaseError, requestSubscription, useIAP, withIAPContext } from 'react-native-iap';
import { ParamListBase, useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';

import AppButton from 'components/AppButton';
import CustomIcon from 'components/CustomIcon/CustomIcon';
import InstitutionExpiredModal from 'components/InstitutionExpiredModal';
import Tabs, { Tab } from 'components/Tabs';
import { COLORS } from 'constants/colors';

const subscriptionSkus = Platform.select({
  ios: ['sub...', 'sub...'],
  android: [],
}) as string[];

function MembershipScreen() {
  const navigation = useNavigation<NativeStackNavigationProp<ParamListBase>>();

  const options = [
    'Text',
    'Text',
    'Text',
    'Text',
    'Text',
    'Text',
  ];

  const { connected, subscriptions, getSubscriptions, currentPurchase, finishTransaction } = useIAP();
  console.log(subscriptions);
  const handleBuySubscription = async (productId: string) => {
    try {
      await requestSubscription({
        sku: productId,
      });
    } catch (error) {
      if (error instanceof PurchaseError) {
        console.error(error.code, error.message);
      } else {
        console.error('handleBuySubscription', JSON.stringify(error));
      }
    }
  };

  useEffect(() => {
    getSubscriptions({ skus: subscriptionSkus });
  }, []);

  const tabs: Tab[] = [
    {
      title: 'Monthly',
      Content: () =>
        subscriptions.length ? (
          <View className="mt-2">
            <Text className="mb-[42px] text-center font-blender-bold text-[26px] lowercase text-black-100">
              ${subscriptions[0].price}/ {subscriptions[0].subscriptionPeriodUnitIOS}
            </Text>
            <AppButton onPress={() => handleBuySubscription(subscriptions[0].productId)} text="Subscribe" />
          </View>
        ) : null,
    },
    {
      title: 'Annual',
      Content: () =>
        subscriptions.length ? (
          <View className="mt-2">
            <Text className="mb-2 text-center font-blender-bold text-[26px] lowercase text-black-100">
              ${subscriptions[1].price}/ {subscriptions[1].subscriptionPeriodUnitIOS}
            </Text>
            <AppButton onPress={() => {}} text="Subscribe" />
          </View>
        ) : null,
    },
  ];

  return (
    <SafeAreaView className="h-full bg-white">
      <StatusBar barStyle="dark-content" />
      <ImageBackground
        source={require('../../../assets/images/mainPattern.png')}
        resizeMode="contain"
        className="absolute left-0 top-0 mt-24 h-full w-full flex-1 grow"
      />
      <ScrollView className="px-6" contentContainerStyle={styles.scrollContent} alwaysBounceVertical={false}>
        <View className="relative mb-9 mt-4 flex-row items-center justify-center">
          <CustomIcon name="back-button" size={23} className="absolute left-0" onPress={navigation.goBack} />
          <Text className="font-blender-heavy text-[24px] uppercase text-black-100">Membership</Text>
        </View>
        <Text className="mb-2 text-center font-blender-bold text-[20px] text-black-100">You will get</Text>
        {options.map((option, index) => {
          return (
            <View key={index} className="mt-4 flex-row items-center">
              <CustomIcon name="check-circle" color={COLORS.pink} size={18} />
              <Text className="ml-6 font-blender-medium text-[16px] text-black-100">{option}</Text>
            </View>
          );
        })}
        <Text className="mb-5 mt-12 text-center font-blender-bold text-[20px] text-black-100">Choose your plan</Text>
        <View style={[styles.card, Platform.OS === 'ios' ? styles.shadowIOS : styles.shadowAndroid]}>
          <Tabs tabs={tabs} containerClassName="flex-none" />
        </View>
        <Text className="mb-2 mt-auto text-center font-blender-bold text-[14px] text-black-100 underline">
          Terms of service
        </Text>
      </ScrollView>
      <InstitutionExpiredModal canShow />
    </SafeAreaView>
  );
}

I successfully get an array of my subscriptions:

[
  {
    "countryCode": "USA",
    "currency": "USD",
    "description": "...",
    "discounts": [],
    "introductoryPrice": "",
    "introductoryPriceAsAmountIOS": "",
    "introductoryPriceNumberOfPeriodsIOS": "",
    "introductoryPricePaymentModeIOS": "",
    "introductoryPriceSubscriptionPeriodIOS": "",
    "localizedPrice": "10.99 $",
    "platform": "ios",
    "price": "10.99",
    "productId": "sub...",
    "subscriptionPeriodNumberIOS": "1",
    "subscriptionPeriodUnitIOS": "MONTH",
    "title": "...",
    "type": "subs"
  },
  {
    "countryCode": "USA",
    "currency": "USD",
    "description": "...",
    "discounts": [],
    "introductoryPrice": "",
    "introductoryPriceAsAmountIOS": "",
    "introductoryPriceNumberOfPeriodsIOS": "",
    "introductoryPricePaymentModeIOS": "",
    "introductoryPriceSubscriptionPeriodIOS": "",
    "localizedPrice": "28.99 $",
    "platform": "ios",
    "price": "28.99",
    "productId": "sub...",
    "subscriptionPeriodNumberIOS": "1",
    "subscriptionPeriodUnitIOS": "YEAR",
    "title": "...",
    "type": "subs"
  }
]

But when I try to make subscribe via this function:

const handleBuySubscription = async (productId: string) => {
    try {
      await requestSubscription({
        sku: productId,
      });
    } catch (error) {
      if (error instanceof PurchaseError) {
        console.error(error.code, error.message);
      } else {
        console.error('handleBuySubscription', JSON.stringify(error));
      }
    }
  };

I get next error:

{"code":"E_UNKNOWN","message":"An unknown error occurred","domain":"SKErrorDomain","userInfo":{"NSUnderlyingError":{"code":"500","message":"underlying error","domain":"ASDErrorDomain","userInfo":{"NSUnderlyingError":{"code":"100","message":"underlying error","domain":"AMSErrorDomain","userInfo":{"NSLocalizedFailureReason":"The authentication failed.","NSLocalizedDescription":"Authentication Failed","NSMultipleUnderlyingErrorsKey":[null,null]}....

How can I fix it? I try to do subscribe by sandbox test user enter image description here

My package.json:

{
  "name": "...",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest",
    "start::clearCache": "yarn start --reset-cache"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.0.0",
    "@invertase/react-native-apple-authentication": "^2.2.2",
    "@react-native-async-storage/async-storage": "^1.17.12",
    "@react-native-firebase/app": "^17.5.0",
    "@react-native-firebase/messaging": "^17.5.0",
    "@react-native-google-signin/google-signin": "^10.0.1",
    "@react-native-picker/picker": "^2.4.9",
    "@react-navigation/bottom-tabs": "^6.5.7",
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/native-stack": "^6.9.12",
    "@shopify/react-native-skia": "^0.1.185",
    "@tanstack/react-query": "^4.28.0",
    "axios": "^1.3.4",
    "classnames": "^2.3.2",
    "d3": "^7.8.4",
    "date-fns": "^2.29.3",
    "jotai": "^2.0.3",
    "nativewind": "^2.0.11",
    "react": "18.2.0",
    "react-hook-form": "^7.43.9",
    "react-native": "^0.71.5",
    "react-native-biometrics": "^3.0.1",
    "react-native-email-link": "^1.14.5",
    "react-native-gesture-handler": "^2.9.0",
    "react-native-haptic-feedback": "^2.0.2",
    "react-native-iap": "^12.10.5",
    "react-native-localize": "^2.2.6",
    "react-native-plaid-link-sdk": "^10.1.0",
    "react-native-reanimated": "^3.1.0",
    "react-native-restart": "^0.0.27",
    "react-native-safe-area-context": "^4.5.0",
    "react-native-screens": "^3.20.0",
    "react-native-splash-screen": "^3.3.0",
    "react-native-uuid": "^2.0.1",
    "react-native-vector-icons": "^9.2.0",
    "react-native-webview": "^12.0.2",
    "zod": "^3.21.4"
  },
  "devDependencies": {
    "@babel/core": "^7.21.4",
    "@babel/preset-env": "^7.21.4",
    "@babel/runtime": "^7.21.0",
    "@react-native-community/eslint-config": "^3.2.0",
    "@total-typescript/ts-reset": "^0.4.2",
    "@tsconfig/react-native": "^2.0.2",
    "@types/d3": "^7.4.0",
    "@types/jest": "^29.5.0",
    "@types/react": "^18.0.24",
    "@types/react-native-vector-icons": "^6.4.13",
    "@types/react-test-renderer": "^18.0.0",
    "babel-jest": "^29.5.0",
    "babel-plugin-module-resolver": "^5.0.0",
    "eslint": "^8.37.0",
    "eslint-plugin-ft-flow": "^2.0.3",
    "eslint-plugin-simple-import-sort": "^10.0.0",
    "jest": "^29.5.0",
    "metro-react-native-babel-preset": "0.73.8",
    "prettier": "^2.8.7",
    "prettier-plugin-tailwindcss": "^0.2.7",
    "react-native-dotenv": "^3.4.8",
    "react-test-renderer": "18.2.0",
    "tailwindcss": "^3.3.1",
    "typescript": "^5.0.3"
  },
  "jest": {
    "preset": "react-native"
  }
}

0 Answers0