Skip to content

requestPurchase always uses first base plan regardless of provided offerToken #3096

@eden170

Description

@eden170

Hi,

After making an Android requestPurchase request, not only the purchase data received in onSuccess, but also the activeSubscriptions data obtained via getActiveSubscriptions after the purchase, returns the first subscription plan within the same subscription group, rather than the subscription plan corresponding to the offerToken I passed when calling requestPurchase.

It seems that a similar issue existed in expo-iap and was resolved there. I would really appreciate it if you could confirm whether this fix has also been applied to react-native-iap.

(Actually, I also brought up this issue in the expo-iap discussions, but didn’t receive a clear response. I’ve now switched to react-native-iap and am running tests, so I would really appreciate it if you could take a look.)

Just for your information, I checked the order management on google play console, and the plan I requested was successfully logged not like the purchase return on onPurchaseSuccess and the return from the getActiveSubscriptions.

environment
"react-native": "0.79.6"
"react-native-iap": "14.4.44"

example code

import React, { useCallback, useEffect, useState } from 'react';
import { TouchableOpacity, Text, View, StyleSheet } from 'react-native';
import { useIAP } from 'react-native-iap';
import { ios } from '@constants/os';
import { subscriptionItemSkus } from '@constants/sku';

const TestPurchaseScreen = () => {
  const {
    fetchProducts,
    subscriptions: subscriptionProducts,
    requestPurchase,
    getActiveSubscriptions,
    activeSubscriptions,
    finishTransaction,
  } = useIAP({
    onPurchaseSuccess: async (purchase: any) => {
      console.log('Purchase:', purchase);
    },
  });


  const fetchIAPProducts = async () => {
    try {
      await fetchProducts({ skus: subscriptionItemSkus, type: 'subs' });
    } catch (error) {
      console.error('IAP product get error:', error);
    }
  };

  useEffect(() => {
    fetchIAPProducts();
  }, []);

  useEffect(() => {
    if (!connected) {
      console.log('IAP not connected');
      return;
    }
  }, [connected]);

  useEffect(() => {
    if (connected) {
      const fetchPurchaseData = async () => {
        try {
          await fetchIAPProducts();
          await getActiveSubscriptions();
        } catch (error) {
          console.error('Purchase data fetch failed:', error);
        }
      };
      fetchPurchaseData();
    }
  }, [connected]);

  const handleTestPurchase = async (productId: string) => {
    try {
      if (ios) {
        await requestPurchase({
          request: { ios: { sku: productId }, android: { skus: [productId] } },
          type: 'subs',
        });
      } else {
        const groupId = productId.startsWith('a') ? 'group_a' : 'group_b'; //subscription group id
        const productGroup = subscriptionProducts.find(
          (group: any) => group.id === groupId
        );
        const offerToken = productGroup?.subscriptionOfferDetailsAndroid?.find(
          (offer: any) => !offer.offerId && offer.basePlanId === productId
        )?.offerToken;

        await requestPurchase({
          request: {
            ios: { sku: productId },
            android: {
              skus: [groupId],
              subscriptionOffers: [{ sku: groupId, offerToken }],
            },
          },
          type: 'subs',
        });
      }
    } catch (error) {
      console.error('Purchase failed:', error);
    }
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.button}
        onPress={() => handleTestPurchase('a_1wk')}>
        <Text style={styles.buttonText}>a 1week</Text>
      </TouchableOpacity>
      <TouchableOpacity
        style={styles.button}
        onPress={() => handleTestPurchase('a_1mo')}>
        <Text style={styles.buttonText}>a 1month</Text>
      </TouchableOpacity>
      <TouchableOpacity
        style={styles.button}
        onPress={() => handleTestPurchase('b_1wk')}>
        <Text style={styles.buttonText}>b 1week</Text>
      </TouchableOpacity>
      <TouchableOpacity
        style={styles.button}
        onPress={() => handleTestPurchase('b_1mo')}>
        <Text style={styles.buttonText}>b 1month</Text>
      </TouchableOpacity>
    </View>
  );
};

export default TestPurchaseScreen;

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions