添加应用程序内购买以使用 expo 反应本机应用程序

问题描述 投票:0回答:1

我有一个正在使用 expo 的 React Native 应用程序。我正在尝试为 IOS 实施 IAP 购买。当用户注册该应用程序时。当我的应用程序通过 expo 以这种方式配置时,是否可以将 IAP 添加到我的应用程序中?我的苹果开发者帐户中也有应用程序设置,所以我知道这不是问题。

我无法在应用程序内进行 IAP 购买,尤其是 Typescript 中的类型

我一直收到此错误,这让我认为这是不可能的。

ERROR  Error: E_IAP_NOT_AVAILABLE

This error is located at:
    in WrapperComponent (created by withDevTools(WrapperComponent))
    in withDevTools(WrapperComponent)
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in main(RootComponent), js engine: hermes

这是我尝试实施 IAP 的当前设置。

.这是注册页面:

import React, { useEffect, useState } from "react";
import {
  View,
  TextInput,
  Image,
  Text,
  StyleSheet,
  KeyboardAvoidingView,
  ActivityIndicator,
  Platform,
  TouchableOpacity,
} from "react-native";
import { FIREBASE_AUTH } from "../../api/firebase";
import { createUserWithEmailAndPassword } from "firebase/auth";
import PrimaryButton from "../../components/PrimaryButton/PrimaryButton";
import CustomProgressBar from "../../components/ProgressBar/ProgressBar";
import SecondaryButton from "../../components/SecondaryButton/SecondaryButton";
import { RouterProps } from "../../App";
import { useValidEmail, useValidPassword } from "../../hooks/useAuthHooks";
import { createUser } from "../../api/user";
import { UserProfile } from "../../models/UserProfile";
import { CheckBox } from "react-native-elements";
import {
  PurchaseError,
  RequestSubscription,
  requestSubscription,
  useIAP,
  validateReceiptIos,
} from "react-native-iap";

export type NewUser = {
  uid?: string;
  firstName?: string;
  lastName?: string;
  email: string;
};

const PLATFORM = Platform.OS;

const subscriptionSkus = Platform.select({
  ios: ["****"],
});

const SignupScreen = ({ navigation }: RouterProps) => {
  const [loading, setLoading] = useState(false);
  const [screen, setScreen] = useState<"signupOne" | "signupTwo">("signupOne");
  const { email, setEmail, emailIsValid } = useValidEmail("");
  const { password, setPassword, passwordIsValid } = useValidPassword("");
  const [isChecked, setChecked] = useState<boolean>(false);

  const {
    password: passwordConfirm,
    setPassword: setPasswordConfirm,
    passwordIsValid: passwordConfirmIsValid,
  } = useValidPassword("");
  const [newUser, setNewUser] = useState<NewUser>({ email: email });

  const createUserInAuth = async (newUser: NewUser, password: string) => {
    try {
      const response = await createUserWithEmailAndPassword(
        FIREBASE_AUTH,
        newUser.email,
        password
      );
      return response;
    } catch (error) {
      console.log(error);
    }
  };

  const handleValidation = async () => {
    setLoading(true);
    if (!newUser.email) {
      setLoading(false);
      return alert("Please enter an Email");
    }

    if (!emailIsValid) {
      setLoading(false);
      return alert("Please enter a valid Email");
    }

    if (!password) {
      setLoading(false);
      return alert("Please enter a Password");
    }

    if (!passwordIsValid) {
      setLoading(false);
      return alert("Please enter a valid Password");
    }

    if (!passwordConfirm) {
      setLoading(false);
      return alert("Please confirm your Password");
    }

    if (!passwordConfirmIsValid) {
      setLoading(false);
      return alert("Please confirm your valid Password");
    }

    if (password !== passwordConfirm) {
      setLoading(false);
      return alert("Passwords don't match");
    }

    if (!newUser.firstName) {
      setLoading(false);
      return alert("Please enter an First Name");
    }

    if (!newUser.lastName) {
      setLoading(false);
      return alert("Please enter an Last Name");
    }

    setScreen("signupTwo");
    setLoading(false);
    return true;
  };

  const {
    connected,
    subscriptions,
    getSubscriptions,
    currentPurchase,
    finishTransaction,
    purchaseHistory,
    getPurchaseHistory,
  } = useIAP();

  const handleBuySubscription = async (subscriptionId: any) => {
    try {
      setLoading(true);
      const purchase = await requestSubscription(subscriptionId as RequestSubscription);
      console.log("Purchase successful:", purchase);
      navigation!.navigate("Signin");
      setLoading(false);
    } catch (error: any) {
      console.warn("Error purchasing:", error.message);
      setLoading(false);
      // Handle purchase failure
    }
  };

  const handleSignUp = async () => {
    setLoading(true);

    if (!isChecked && screen === "signupTwo") {
      setLoading(false);
      return alert("Please accept the Terms and Conditions and Privacy Policy");
    }

    try {
      const response = await createUserInAuth(newUser, password);

      if (newUser.firstName && newUser.lastName && response && subscriptionSkus) {
        const createdUser: UserProfile = {
          uid: response?.user.uid,
          firstName: newUser!.firstName,
          lastName: newUser!.lastName,
          email: email,
        };

        if(PLATFORM === "ios") {
          await handleBuySubscription(subscriptionSkus[0])
        } else if(PLATFORM === "android"){
          return
        }

        await createUser(createdUser)
          .then(() => {
            setLoading(false);
            navigation!.navigate("SignIn");
            console.log("User added to database");
          })
          .catch((error) => {
            console.log("Error signing up:", error);
            alert("Sign up failed: " + error.message);
            setLoading(false);
          });
      }
    } catch (error) {
      console.error("Error adding user: ", error);
    }
  }

  const screens = {
    signupOne: (
      <>
        <TextInput
          style={styles.input}
          placeholder="First name"
          onChangeText={(value) =>
            setNewUser((old) => ({ ...old, firstName: value }))
          }
          value={newUser?.firstName}
          autoCapitalize="words"
        />
        <TextInput
          style={styles.input}
          placeholder="Last name"
          onChangeText={(value) =>
            setNewUser((old) => ({ ...old, lastName: value }))
          }
          value={newUser?.lastName}
          autoCapitalize="words"
        />
        <TextInput
          style={styles.input}
          placeholder="Email"
          onChangeText={(value) => {
            setEmail(value);
            setNewUser((old) => ({ ...old, email: value }));
          }}
          value={email}
          autoCapitalize="none"
        />
        <TextInput
          style={styles.input}
          placeholder="Password"
          onChangeText={setPassword}
          value={password}
          secureTextEntry
          autoCapitalize="none"
        />
        <TextInput
          style={styles.input}
          placeholder="Confirm Password"
          onChangeText={setPasswordConfirm}
          value={passwordConfirm}
          secureTextEntry
          autoCapitalize="none"
        />
      </>
    ),
    signupTwo: (
      <>
        <Text style={styles.cost}>Annual cost of $60</Text>
        <View style={[styles.flexed, { width: 300 }]}>
          <CheckBox
            checked={isChecked}
            onPress={() => (isChecked ? setChecked(false) : setChecked(true))}
            containerStyle={styles.checkbox}
          />
          <Text
            style={styles.hereText}
            onPress={() => navigation!.navigate("TermsAndConditions")}
          >
            I accept the Terms and Conditions and Privacy Policy
          </Text>
        </View>
      </>
    ),
  };

  return (
    <View style={styles.container}>
      <KeyboardAvoidingView behavior="padding" style={styles.contentContainer}>
        <CustomProgressBar
          progress={loading ? 1 : screen === "signupOne" ? 0.33 : 0.66}
        />
        <Image
          source={require("../../assets/connection.png")}
          style={styles.logo}
        />
        {screens[screen]}
        {loading ? (
          <ActivityIndicator size="large" color="#0000ff" />
        ) : (
          <PrimaryButton
            title={screen === "signupOne" ? "Next" : "Create"}
            onPress={() => {
              screen === "signupOne" ? handleValidation() : handleSignUp();
            }}
          />
        )}
        <SecondaryButton
          title="Back"
          onPress={() => {
            screen === "signupOne"
              ? navigation!.navigate("SignIn")
              : setScreen("signupOne");
          }}
        />
      </KeyboardAvoidingView>
    </View>
  );
};

const styles = StyleSheet.create({
  cost: {
    borderColor: "#7FB3D5",
    borderRadius: 10,
    paddingVertical: 15,
    paddingHorizontal: 40,
    marginTop: 10,
    color: "black",
    borderWidth: 1,
    backgroundColor: "transparent",
  },
  checkbox: {
    alignSelf: "center",
  },
  logo: {
    width: 100,
    height: 100,
    marginBottom: 20,
    marginTop: 20,
  },
  box: {
    margin: 10,
    marginBottom: 5,
    padding: 10,
    backgroundColor: "white",
    borderRadius: 7,
    shadowColor: "rgba(0, 0, 0, 0.45)",
    shadowOffset: { height: 16, width: 0 },
    shadowOpacity: 0.1,
    shadowRadius: 12,
  },
  button: {
    alignItems: "center",
    backgroundColor: "mediumseagreen",
    borderRadius: 8,
    padding: 10,
  },
  buttonText: {
    fontSize: 16,
    fontWeight: "bold",
    color: "white",
    textTransform: "uppercase",
  },
  specialTag: {
    color: "white",
    backgroundColor: "crimson",
    width: 125,
    padding: 4,
    fontWeight: "bold",
    fontSize: 12,
    borderRadius: 7,
    marginBottom: 2,
  },
  flexed: {
    flexDirection: "row",
    alignItems: "center",
  },
  hereText: {
    marginLeft: 5,
    textDecorationLine: "underline",
    color: "blue",
  },
  signupContainer: {
    marginTop: 80,
    alignItems: "center",
  },
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#D9D9D9",
  },
  contentContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    width: 300,
  },
  title: {
    fontSize: 24,
    marginBottom: 20,
  },
  input: {
    width: 300,
    height: 40,
    backgroundColor: "white",
    borderColor: "black",
    borderWidth: 1,
    padding: 5,
    marginBottom: 10,
  },
  errorText: {
    color: "red",
    fontSize: 14,
    marginBottom: 10,
  },
});

export default SignupScreen;

这是 App.tsx:

import React, { useState, useEffect } from "react";
import {
  NavigationContainer,
  NavigationProp,
  RouteProp,
} from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { FIREBASE_AUTH } from "./api/firebase";
import { onAuthStateChanged, User } from "firebase/auth";
import SettingsPage from "./pages/SettingsPage";
import { AppRegistry } from "react-native";
import Calculator from "./pages/Calculator";
import ChangePassword from "./pages/ChangePassword";
import { QueryClient, QueryClientProvider } from "react-query";
import UpdateUser from "./pages/UpdateUser";
import HelpPage from "./pages/HelpPage";
import ForgotPassword from "./pages/ForgotPassword"
import PresetValues from "./pages/PresetValues";
// import { InAppUtils } from 'react-native-iap';
import TermsAndConditions from "./pages/auth/TermsAndConditions";
import HomeScreen from "./pages/HomeScreen";
import WorkerPage from "./pages/WorkerPage";
import BayCalculator from "./pages/BayCalculator";
import TeamPage from "./pages/TeamPage";
import LoginScreen from "./pages/auth/Login";
import SignupScreen from "./pages/auth/Signup";
import LoadingScreen from "./pages/Loading";
import { withIAPContext } from "react-native-iap";

AppRegistry.registerComponent("bay-calculator", () => App);

// InAppUtils.ios.consumePurchase('your-subscription-product-id');

const queryClient = new QueryClient();

export interface RouterProps {
  navigation?: NavigationProp<any, any>;
  route?: RouteProp<any, any>;
}

const Stack = createStackNavigator();
const InsideStack = createStackNavigator();
const AuthStack = createStackNavigator();

function App() {
  const [isLoading, setIsLoading] = useState(true);
  const [user, setUser] = useState<User | null>(null);
  
  function InsideLayout({ navigation }: { navigation: any }) {
    return (
      <InsideStack.Navigator>
        <InsideStack.Screen
          name="Home"
          component={HomeScreen}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="WorkerPage"
          component={WorkerPage}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="SettingsPage"
          component={SettingsPage}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="BayCalculator"
          component={BayCalculator}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="Calculator"
          component={Calculator}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="TeamPage"
          component={TeamPage}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="ChangePassword"
          component={ChangePassword}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="UpdateUser"
          component={UpdateUser}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="HelpPage"
          component={HelpPage}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="PresetValues"
          component={PresetValues}
          options={{ headerShown: false }}
        />
      </InsideStack.Navigator>
    );
  }

  function AuthLayout() {
    return (
      <AuthStack.Navigator>
        <Stack.Screen
          name="SignIn"
          component={LoginScreen}
          options={{ headerShown: false }}
        />
        <Stack.Screen
          name="SignUp"
          component={SignupScreen}
          options={{ headerShown: false }}
        />
        <Stack.Screen
          name="ForgotPassword"
          component={ForgotPassword}
          options={{ headerShown: false }}
        />
        <InsideStack.Screen
          name="TermsAndConditions"
          component={TermsAndConditions}
          options={{ headerShown: false }}
        />
      </AuthStack.Navigator>
    );
  }

  useEffect(() => {
    try {
      onAuthStateChanged(FIREBASE_AUTH, (user) => {
        setUser(user);
        setIsLoading(false);
      });
    } catch (err) {
      console.log(err);
    }
  }, []);

  return (
    <QueryClientProvider client={queryClient}>
      <NavigationContainer>
        <Stack.Navigator initialRouteName="SignIn">
          {isLoading ? (
            <Stack.Screen
              name="Loading"
              component={LoadingScreen}
              options={{ headerShown: false }}
            />
          ) : user ? (
            <Stack.Screen
              name="Inside"
              component={InsideLayout}
              options={{ headerShown: false }}
            />
          ) : (
            <Stack.Screen
              name="Auth"
              component={AuthLayout}
              options={{ headerShown: false }}
            />
          )}
        </Stack.Navigator>
      </NavigationContainer>
    </QueryClientProvider>
  );
}

export default withIAPContext(App)
ios typescript react-native in-app-purchase
1个回答
0
投票

遗憾的是,expo 本地开发服务器不支持 IAP。我面临着同样的问题。试图找出一种好方法来测试这些 IAP 功能,而无需迭代部署到应用商店。你找到解决办法了吗?

© www.soinside.com 2019 - 2024. All rights reserved.