最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Unable to Fetch Subscriptions Data from App Store Connect as Sandbox User on Real Device (React Native IAP) - Stack Overfl

programmeradmin3浏览0评论

I am working on an iOS app using React Native IAP to fetch subscription data from App Store Connect.

When I run the app on a real device using Xcode and a sandbox account, the getOfferings() function does not return any subscription data. However, when I use a StoreKit Configuration file, the subscriptions are fetched successfully.

Setup Details:
Library: react-native-iap (latest version)
Function Used: getOfferings()
Testing Environment: Real iOS device with a Sandbox account
Paid Agreements: Signed
Subscription Status: "Ready to Submit"

What I've Tried:
✅ Verified that the sandbox account is correctly logged into the App Store.
✅ Restarted the device and reinstalled the app.
✅ Ensured that in-app purchases are enabled in App Store Connect.
✅ Checked if the subscriptions are properly set up under "In-App Purchases" in App Store Connect.
✅ Confirmed that react-native-iap is correctly configured in the app.

Source code for fetching subscriptions

import { useEffect, useRef, useState } from "react";
import * as Iap from "react-native-iap";
import { useIAP, validateReceiptIos, getReceiptIOS } from "react-native-iap";
import { SECTIONS, SUBSCRIPTION_DATA_MAPPER } from "@/constants/constants";
import { useSnackBar } from "@/contexts/SnackBarContext";
import { GetActiveSubscriptionPlan } from "@/services/apiServices";
import { getCurrentSubscriptionPlan } from "@/utils/helper";

const iosPaymentService = () => {
  const [subscriptions, setSubscriptions] = useState<Iap.Subscription[]>();
  const [iapLoading, setIapLoading] = useState(false);
  const [activeSection, setActiveSection] = useState<any>();
  const [loading, setLoading] = useState(true);
  const [subscriptionLoading, setSubscriptionLoading] = useState(false);
  const [showLoadingModal, setShowLoadingModal] = useState(false);
  const [currentPlan, setCurrentPlan] = useState({
    currentPlan: "free",
    selection: -1,
    platform: "",
    price: 0,
    index: -1,
  });
  const [showAlert, setShowAlert] = useState(false);
  const [sections, setSections] = useState();
  const { showSnackBar } = useSnackBar();
  const [paymentLoading, setPaymentLoading] = useState(false);
  const map = SUBSCRIPTION_DATA_MAPPER();
  const [selectedIndex, setSelectedIndex] = useState(1);

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

  const getSections = (availableSubscriptions: Iap.Subscription[]) => {
    const groupedSubscriptions = availableSubscriptions.reduce((acc, sub) => {
      const baseProductId = sub.productId.replace(/_(monthly|quarterly)$/, '');
      if (!acc[baseProductId]) {
        acc[baseProductId] = {
          subscription: sub,
          data: {
            title: sub.title,
            index: Object.keys(acc).length + 1,
            content: {
              perks: [sub.description],
              prices: {
                currency: {
                  monthly: '',
                  quarterly: '',
                },
                coins: {
                  monthly: 0,
                  quarterly: 0,
                }
              }
            }
          }
        };
      }
      if (sub.subscriptionPeriodNumberIOS === '1') {
        acc[baseProductId].data.content.prices.currency.monthly = sub.localizedPrice;
      } else if (sub.subscriptionPeriodNumberIOS === '3') {
        acc[baseProductId].data.content.prices.currency.quarterly = sub.localizedPrice;
      }
      return acc;
    }, {});

    const list = Object.values(groupedSubscriptions);
    list.unshift({
      subscription: null,
      data: SECTIONS[0].data,
    });
    // console.log("List II:", list);
    setSections(list.sort((a, b) => a.data.index - b.data.index));
  };

  const initIAP = async () => {
    if (iapLoading) {
      return;
    }
    setIapLoading(true);
    try {
      await Iap.initConnection();
      const skus = [
        "com.chatreal.subscription.premium_pro_quarterly",
        "com.chatreal.subscription.premium_pro_monthly",
        "com.chatreal.subscription.premium_quarterly",
        "com.chatreal.subscription.premium_monthly"
      ];
      console.log("isConnneted", connected);
      const availableSubscriptions = await Iap.getSubscriptions({ skus });
      console.log("Available subscriptions:", JSON.stringify(availableSubscriptions, null, 3));
      setSubscriptions(availableSubscriptions);
      getSections(availableSubscriptions);
    } catch (err) {
      console.warn("Error fetching subscriptions:", err);
      console.log("Error details:", JSON.stringify(err, null, 2));
      showSnackBar(`Failed to load subscriptions: ${err.message || "Unknown error"}`);
    } finally {
      setIapLoading(false);
    }
  };

  const purchaseSubscription = async (sku: string) => {
    console.log("SKU in IOS: ", sku);
    setPaymentLoading(true);
    try {
      if (!sku) {
        showSnackBar("Subscription Unavailable at the moment");
        return;
      }

      const request: Iap.RequestSubscriptionIOS = {
        sku,
        andDangerouslyFinishTransactionAutomaticallyIOS: false, // Recommended to keep false for validation
        appAccountToken: undefined, // Optional: Add user account token if needed
        quantity: 1
      };

      console.log("Request:", request);

      const purchase = await Iap.requestSubscription(request);

      console.log("PURCHASE::", purchase);
      console.log("PURCHASE STRN::", JSON.stringify(purchase, null, 2));

      if (purchase) {
        // Validate the receipt (if required)
        const receipt = purchase.transactionReceipt;
        // const temp = decodeTransactionReceipt(receipt);
        // console.log("Temp:", temp);
        if (receipt) {
          setShowLoadingModal(false); // Close the loading modal
          setSubscriptionLoading(false); // Mark subscription as complete
        } else {
          console.warn("Receipt validation failed");
          showSnackBar("Subscription validation failed");
        }
      } else {
        console.warn("No Purchase found");
        showSnackBar("No Purchase found for the subscription");
      }

    } catch (err) {
      console.warn("Error purchasing subscription:", err);
      showSnackBar("Purchase failed");
    } finally {
      console.log("Finally block");
      setPaymentLoading(false);
    }
  };

  const decodeTransactionReceipt = (receipt: string) => {
    try {
      const decoded = atob(receipt); // Decode Base64 string
      console.log("Decoded Receipt:", decoded);
      return decoded;
    } catch (error) {
      console.error("Error decoding receipt:", error);
      return null;
    }
  };

  const getCurrentSubscription = async () => {
    try {
      setLoading(true);
      const response = await GetActiveSubscriptionPlan();
      if (!response) return;

      const currentPlan = getCurrentSubscriptionPlan(response?.subscription);
      setCurrentPlan(currentPlan ? { ...currentPlan, platform: response?.platform } : undefined);
    } catch (e) {
      showSnackBar("Error fetching current subscription", 2000);
    } finally {
      setLoading(false);
    }
  };

  const initPaymentService = async () => {
    await initIAP();
    getCurrentSubscription();
  };

  return {
    initPaymentService,
    paymentLoading,
    subscriptions,
    showAlert,
    subscriptionLoading,
    setSubscriptionLoading,
    setShowAlert,
    sections,
    activeSection,
    showLoadingModal,
    setShowLoadingModal,
    setActiveSection,
    currentPlan,
    loading,
    setLoading,
    setCurrentPlan,
    selectedIndex,
    setSelectedIndex,
    purchaseSubscription,
  };
};

export default iosPaymentService;

This is what I am getting when running it using xcode and sandbox testing on real device: 'Available subscriptions:', '[]'

This is what I am getting when running it using xcode and storekit config file on simulator or even real device:

Available subscriptions: [
   {
      "introductoryPrice": "",
      "title": "Premium",
      "introductoryPriceSubscriptionPeriodIOS": "",
      "introductoryPriceAsAmountIOS": "",
      "productId": "com.chatreal.subscription.premium_monthly",
      "subscriptionPeriodUnitIOS": "MONTH",
      "introductoryPricePaymentModeIOS": "",
      "currency": "USD",
      "price": "4.99",
      "countryCode": "USA",
      "subscriptionPeriodNumberIOS": "1",
      "type": "subs",
      "localizedPrice": "$4.99",
      "introductoryPriceNumberOfPeriodsIOS": "",
      "discounts": [],
      "description": "Chatreal AI Premium Subscription",
      "platform": "ios"
   },
   {
      "description": "Chatreal AI Premium Pro Subscription",
      "subscriptionPeriodNumberIOS": "1",
      "countryCode": "USA",
      "introductoryPriceNumberOfPeriodsIOS": "",
      "discounts": [],
      "subscriptionPeriodUnitIOS": "MONTH",
      "productId": "com.chatreal.subscription.premium_pro_monthly",
      "title": "Premium Pro",
      "introductoryPriceSubscriptionPeriodIOS": "",
      "introductoryPrice": "",
      "localizedPrice": "$9.99",
      "type": "subs",
      "currency": "USD",
      "introductoryPricePaymentModeIOS": "",
      "price": "9.99",
      "introductoryPriceAsAmountIOS": "",
      "platform": "ios"
   },
   {
      "introductoryPriceAsAmountIOS": "",
      "subscriptionPeriodUnitIOS": "MONTH",
      "productId": "com.chatreal.subscription.premium_pro_quarterly",
      "discounts": [],
      "introductoryPriceNumberOfPeriodsIOS": "",
      "description": "Chatreal AI Premium Pro Subscription",
      "introductoryPrice": "",
      "localizedPrice": "$24.99",
      "type": "subs",
      "price": "24.99",
      "introductoryPricePaymentModeIOS": "",
      "currency": "USD",
      "subscriptionPeriodNumberIOS": "3",
      "countryCode": "USA",
      "introductoryPriceSubscriptionPeriodIOS": "",
      "title": "Premium Pro",
      "platform": "ios"
   },
   {
      "introductoryPriceNumberOfPeriodsIOS": "",
      "discounts": [],
      "introductoryPriceSubscriptionPeriodIOS": "",
      "title": "Premium",
      "introductoryPrice": "",
      "countryCode": "USA",
      "subscriptionPeriodNumberIOS": "3",
      "introductoryPricePaymentModeIOS": "",
      "price": "12.99",
      "currency": "USD",
      "description": "Chatreal AI Premium Subscription",
      "productId": "com.chatreal.subscription.premium_quarterly",
      "subscriptionPeriodUnitIOS": "MONTH",
      "introductoryPriceAsAmountIOS": "",
      "type": "subs",
      "localizedPrice": "$12.99",
      "platform": "ios"
   }
]

I am working on an iOS app using React Native IAP to fetch subscription data from App Store Connect.

When I run the app on a real device using Xcode and a sandbox account, the getOfferings() function does not return any subscription data. However, when I use a StoreKit Configuration file, the subscriptions are fetched successfully.

Setup Details:
Library: react-native-iap (latest version)
Function Used: getOfferings()
Testing Environment: Real iOS device with a Sandbox account
Paid Agreements: Signed
Subscription Status: "Ready to Submit"

What I've Tried:
✅ Verified that the sandbox account is correctly logged into the App Store.
✅ Restarted the device and reinstalled the app.
✅ Ensured that in-app purchases are enabled in App Store Connect.
✅ Checked if the subscriptions are properly set up under "In-App Purchases" in App Store Connect.
✅ Confirmed that react-native-iap is correctly configured in the app.

Source code for fetching subscriptions

import { useEffect, useRef, useState } from "react";
import * as Iap from "react-native-iap";
import { useIAP, validateReceiptIos, getReceiptIOS } from "react-native-iap";
import { SECTIONS, SUBSCRIPTION_DATA_MAPPER } from "@/constants/constants";
import { useSnackBar } from "@/contexts/SnackBarContext";
import { GetActiveSubscriptionPlan } from "@/services/apiServices";
import { getCurrentSubscriptionPlan } from "@/utils/helper";

const iosPaymentService = () => {
  const [subscriptions, setSubscriptions] = useState<Iap.Subscription[]>();
  const [iapLoading, setIapLoading] = useState(false);
  const [activeSection, setActiveSection] = useState<any>();
  const [loading, setLoading] = useState(true);
  const [subscriptionLoading, setSubscriptionLoading] = useState(false);
  const [showLoadingModal, setShowLoadingModal] = useState(false);
  const [currentPlan, setCurrentPlan] = useState({
    currentPlan: "free",
    selection: -1,
    platform: "",
    price: 0,
    index: -1,
  });
  const [showAlert, setShowAlert] = useState(false);
  const [sections, setSections] = useState();
  const { showSnackBar } = useSnackBar();
  const [paymentLoading, setPaymentLoading] = useState(false);
  const map = SUBSCRIPTION_DATA_MAPPER();
  const [selectedIndex, setSelectedIndex] = useState(1);

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

  const getSections = (availableSubscriptions: Iap.Subscription[]) => {
    const groupedSubscriptions = availableSubscriptions.reduce((acc, sub) => {
      const baseProductId = sub.productId.replace(/_(monthly|quarterly)$/, '');
      if (!acc[baseProductId]) {
        acc[baseProductId] = {
          subscription: sub,
          data: {
            title: sub.title,
            index: Object.keys(acc).length + 1,
            content: {
              perks: [sub.description],
              prices: {
                currency: {
                  monthly: '',
                  quarterly: '',
                },
                coins: {
                  monthly: 0,
                  quarterly: 0,
                }
              }
            }
          }
        };
      }
      if (sub.subscriptionPeriodNumberIOS === '1') {
        acc[baseProductId].data.content.prices.currency.monthly = sub.localizedPrice;
      } else if (sub.subscriptionPeriodNumberIOS === '3') {
        acc[baseProductId].data.content.prices.currency.quarterly = sub.localizedPrice;
      }
      return acc;
    }, {});

    const list = Object.values(groupedSubscriptions);
    list.unshift({
      subscription: null,
      data: SECTIONS[0].data,
    });
    // console.log("List II:", list);
    setSections(list.sort((a, b) => a.data.index - b.data.index));
  };

  const initIAP = async () => {
    if (iapLoading) {
      return;
    }
    setIapLoading(true);
    try {
      await Iap.initConnection();
      const skus = [
        "com.chatreal.subscription.premium_pro_quarterly",
        "com.chatreal.subscription.premium_pro_monthly",
        "com.chatreal.subscription.premium_quarterly",
        "com.chatreal.subscription.premium_monthly"
      ];
      console.log("isConnneted", connected);
      const availableSubscriptions = await Iap.getSubscriptions({ skus });
      console.log("Available subscriptions:", JSON.stringify(availableSubscriptions, null, 3));
      setSubscriptions(availableSubscriptions);
      getSections(availableSubscriptions);
    } catch (err) {
      console.warn("Error fetching subscriptions:", err);
      console.log("Error details:", JSON.stringify(err, null, 2));
      showSnackBar(`Failed to load subscriptions: ${err.message || "Unknown error"}`);
    } finally {
      setIapLoading(false);
    }
  };

  const purchaseSubscription = async (sku: string) => {
    console.log("SKU in IOS: ", sku);
    setPaymentLoading(true);
    try {
      if (!sku) {
        showSnackBar("Subscription Unavailable at the moment");
        return;
      }

      const request: Iap.RequestSubscriptionIOS = {
        sku,
        andDangerouslyFinishTransactionAutomaticallyIOS: false, // Recommended to keep false for validation
        appAccountToken: undefined, // Optional: Add user account token if needed
        quantity: 1
      };

      console.log("Request:", request);

      const purchase = await Iap.requestSubscription(request);

      console.log("PURCHASE::", purchase);
      console.log("PURCHASE STRN::", JSON.stringify(purchase, null, 2));

      if (purchase) {
        // Validate the receipt (if required)
        const receipt = purchase.transactionReceipt;
        // const temp = decodeTransactionReceipt(receipt);
        // console.log("Temp:", temp);
        if (receipt) {
          setShowLoadingModal(false); // Close the loading modal
          setSubscriptionLoading(false); // Mark subscription as complete
        } else {
          console.warn("Receipt validation failed");
          showSnackBar("Subscription validation failed");
        }
      } else {
        console.warn("No Purchase found");
        showSnackBar("No Purchase found for the subscription");
      }

    } catch (err) {
      console.warn("Error purchasing subscription:", err);
      showSnackBar("Purchase failed");
    } finally {
      console.log("Finally block");
      setPaymentLoading(false);
    }
  };

  const decodeTransactionReceipt = (receipt: string) => {
    try {
      const decoded = atob(receipt); // Decode Base64 string
      console.log("Decoded Receipt:", decoded);
      return decoded;
    } catch (error) {
      console.error("Error decoding receipt:", error);
      return null;
    }
  };

  const getCurrentSubscription = async () => {
    try {
      setLoading(true);
      const response = await GetActiveSubscriptionPlan();
      if (!response) return;

      const currentPlan = getCurrentSubscriptionPlan(response?.subscription);
      setCurrentPlan(currentPlan ? { ...currentPlan, platform: response?.platform } : undefined);
    } catch (e) {
      showSnackBar("Error fetching current subscription", 2000);
    } finally {
      setLoading(false);
    }
  };

  const initPaymentService = async () => {
    await initIAP();
    getCurrentSubscription();
  };

  return {
    initPaymentService,
    paymentLoading,
    subscriptions,
    showAlert,
    subscriptionLoading,
    setSubscriptionLoading,
    setShowAlert,
    sections,
    activeSection,
    showLoadingModal,
    setShowLoadingModal,
    setActiveSection,
    currentPlan,
    loading,
    setLoading,
    setCurrentPlan,
    selectedIndex,
    setSelectedIndex,
    purchaseSubscription,
  };
};

export default iosPaymentService;

This is what I am getting when running it using xcode and sandbox testing on real device: 'Available subscriptions:', '[]'

This is what I am getting when running it using xcode and storekit config file on simulator or even real device:

Available subscriptions: [
   {
      "introductoryPrice": "",
      "title": "Premium",
      "introductoryPriceSubscriptionPeriodIOS": "",
      "introductoryPriceAsAmountIOS": "",
      "productId": "com.chatreal.subscription.premium_monthly",
      "subscriptionPeriodUnitIOS": "MONTH",
      "introductoryPricePaymentModeIOS": "",
      "currency": "USD",
      "price": "4.99",
      "countryCode": "USA",
      "subscriptionPeriodNumberIOS": "1",
      "type": "subs",
      "localizedPrice": "$4.99",
      "introductoryPriceNumberOfPeriodsIOS": "",
      "discounts": [],
      "description": "Chatreal AI Premium Subscription",
      "platform": "ios"
   },
   {
      "description": "Chatreal AI Premium Pro Subscription",
      "subscriptionPeriodNumberIOS": "1",
      "countryCode": "USA",
      "introductoryPriceNumberOfPeriodsIOS": "",
      "discounts": [],
      "subscriptionPeriodUnitIOS": "MONTH",
      "productId": "com.chatreal.subscription.premium_pro_monthly",
      "title": "Premium Pro",
      "introductoryPriceSubscriptionPeriodIOS": "",
      "introductoryPrice": "",
      "localizedPrice": "$9.99",
      "type": "subs",
      "currency": "USD",
      "introductoryPricePaymentModeIOS": "",
      "price": "9.99",
      "introductoryPriceAsAmountIOS": "",
      "platform": "ios"
   },
   {
      "introductoryPriceAsAmountIOS": "",
      "subscriptionPeriodUnitIOS": "MONTH",
      "productId": "com.chatreal.subscription.premium_pro_quarterly",
      "discounts": [],
      "introductoryPriceNumberOfPeriodsIOS": "",
      "description": "Chatreal AI Premium Pro Subscription",
      "introductoryPrice": "",
      "localizedPrice": "$24.99",
      "type": "subs",
      "price": "24.99",
      "introductoryPricePaymentModeIOS": "",
      "currency": "USD",
      "subscriptionPeriodNumberIOS": "3",
      "countryCode": "USA",
      "introductoryPriceSubscriptionPeriodIOS": "",
      "title": "Premium Pro",
      "platform": "ios"
   },
   {
      "introductoryPriceNumberOfPeriodsIOS": "",
      "discounts": [],
      "introductoryPriceSubscriptionPeriodIOS": "",
      "title": "Premium",
      "introductoryPrice": "",
      "countryCode": "USA",
      "subscriptionPeriodNumberIOS": "3",
      "introductoryPricePaymentModeIOS": "",
      "price": "12.99",
      "currency": "USD",
      "description": "Chatreal AI Premium Subscription",
      "productId": "com.chatreal.subscription.premium_quarterly",
      "subscriptionPeriodUnitIOS": "MONTH",
      "introductoryPriceAsAmountIOS": "",
      "type": "subs",
      "localizedPrice": "$12.99",
      "platform": "ios"
   }
]
Share Improve this question edited Mar 20 at 10:34 Mohit asked Mar 20 at 8:27 MohitMohit 1017 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

You can check if any agreements on App Store Connect are pending acceptance? Failure to accept updated agreements may cause issues when fetching subscription data or processing transactions.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论