Skip to main content
Question

Google Play IAPS Imported as Subscriptions?


Forum|alt.badge.img+3

I went from using react-native-iap, due to losing a lot of money on refunds because of a purchase acknowledgment issue I couldn’t figure out.

Came over to use RevenueCat, heard great things, setup account, it gave me all the check marks. Pulled in all my previous data, even refunded data. (which is EVERY purchase) 

I have a trivia game.

Now products can’t be found.

I have 1 subscription: Unlocks premium access, an “Everything Bundle” which unlocks everything in the app, always, forever, 1 price. Then I have individual IAPs for specific packs.

[RevenueCat] ℹ️ Product not found: everythingbundle1 - Product Type: subs, Reason: PRODUCT_NOT_FOUND, Serialized doc ID: 

 

I imported all my products from Google Play Console, and they are set as Non-Consumable. (Photo for the one above below)

 

But I don’t see why they are coming over as subs, even though they are setup as IAPs in google play console. 

I have offers setup and entitlements (Example of the same one)

Every single IAP & Sub are all not able to be found. 

 

This post has been closed for comments

9 replies

Forum|alt.badge.img+3
  • RevenueCat Staff
  • 49 replies
  • July 25, 2025

Thanks for the details! A few questions to help narrow this down:

  • Are you getting the PRODUCT_NOT_FOUND error for all of your products, or is it just the “Everything Bundle” that’s failing?
  • Are you testing on a physical device or an emulator? (Billing may fail on emulators unless  Play Console is properly signed in.)
  • Are you signed in as a licensed tester on the device?

Forum|alt.badge.img+3
  • Author
  • Member
  • 5 replies
  • July 26, 2025

Hello! Thank you so much for replying.

I am testing on a physical device. All my product are currently not found. 

 

07-26 12:32:29.824 20834 20916 I ReactNativeJS: [RevenueCat] ℹ️ Missing productDetails: UnfetchedProduct{productId='starwars1', productType='subs', statusCode=3}

 

I have double checked all play console setups. 

I have deleted all imported products in RevenueCat and recreated them to ensure they are set to non-consumable. 

 

I now have my ACTUAL subscription working as intended. Also on IOS, all products/subscriptions are working as intended. 

 

I am signed in as a license tester. A Different account than my developer account and signed into the proper account on the physical device. 


Forum|alt.badge.img+3
  • RevenueCat Staff
  • 49 replies
  • July 28, 2025

Thanks for getting back to me. The log you see is an expected log because we actually query for all products as SUBS first and then we would query the INAPP products next. 

If you’re not seeing a log for INAPP, then it should mean that the product was fetched successfully as an INAPP product. 

Can you confirm that you’re not seeing the product? 


Forum|alt.badge.img+3
  • Author
  • Member
  • 5 replies
  • July 28, 2025

I am currently not seeing the product. it just repeats “Purchase error: The Product is not available for purchase” on android but the subscription is working. On IOS, I can purchase each product individually & use the subscription. 

.. oh man, currently on build 54 of attempting multiple different things. 


Forum|alt.badge.img+3
  • RevenueCat Staff
  • 49 replies
  • July 28, 2025

Can you share the code you're using the make the purchase? 


Forum|alt.badge.img+3
  • Author
  • Member
  • 5 replies
  • July 28, 2025

These are from my StoreScreen.js, I can share my RevenueCatManager.js as well if need be, let me know how you would like me to share that file if so. Btw, I this is since I’ve been testing direct purchases and not offer based, I attempted offer based purchases and couldn’t get them to work either. 

 

// Main purchase method - handles both subscriptions and individual IAPs
async purchaseProductWithCallback(productId, onComplete) {
  debugLog('PURCHASE', `Starting DIRECT purchase for: ${productId}`);
  
  try {
    if (!this.isInitialized) {
      const initResult = await this.initialize();
      if (!initResult) {
        onComplete({ success: false, error: 'RevenueCat not initialized', cancelled: false });
        return false;
      }
    }

    // For subscriptions, use offering-based purchase (THESE WORK)
    if (productId === PRODUCT_IDS.SUBSCRIPTION_MONTHLY) {
      debugLog('PURCHASE', 'Subscription detected, using offering-based purchase...');
      return await this.purchaseSubscriptionViaOffering(productId, onComplete);
    }

    // For individual IAPs, use direct product purchase (THESE DON'T WORK ON ANDROID)
    debugLog('PURCHASE', 'Attempting DIRECT product purchase (bypassing offerings)...');
    
    const { customerInfo } = await Purchases.purchaseProduct(productId);
    
    debugLog('PURCHASE', 'Direct purchase successful!', {
      productId,
      newActiveEntitlements: Object.keys(customerInfo.entitlements.active)
    });
    
    this.customerInfo = customerInfo;
    onComplete({ success: true, productId: productId });
    
    return true;

  } catch (error) {
    debugLog('PURCHASE', '❌ Direct purchase failed', {
      productId,
      error: error.message,
      code: error.code,
      underlyingErrorMessage: error.userInfo?.underlyingErrorMessage
    });
    
    const isUserCancelled = error.code === Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED;
    
    onComplete({ 
      success: false, 
      error: error.message,
      cancelled: isUserCancelled,
      productId: productId
    });
    
    return false;
  }
}

 

// Subscription purchase via offerings - WORKS PERFECTLY
async purchaseSubscriptionViaOffering(productId, onComplete) {
  try {
    const offerings = await Purchases.getOfferings();
    
    if (!offerings?.current?.availablePackages) {
      throw new Error('No subscription offerings available');
    }

    const monthlyPackage = offerings.current.availablePackages.find(pkg => 
      pkg.packageType === 'MONTHLY' || pkg.storeProduct?.identifier === productId
    );

    if (!monthlyPackage) {
      throw new Error('Monthly subscription package not found');
    }

    // This works perfectly on Android
    const { customerInfo } = await Purchases.purchasePackage(monthlyPackage);
    
    this.customerInfo = customerInfo;
    onComplete({ success: true, productId: productId });
    
    return true;

  } catch (error) {
    debugLog('PURCHASE', '❌ Subscription purchase failed:', error.message);
    throw error;
  }
}

ProductIDs:
export const PRODUCT_IDS = {
  // Individual IAPs (these don't work on Android with direct purchase)
  PACK_HARRYPOTTER: Platform.select({
    ios: 'HarryPotter1',
    android: 'harrypotter1'
  }),
  PACK_STARWARS: Platform.select({
    ios: 'StarWars1',
    android: 'starwars1'
  }),
  EVERYTHING_BUNDLE: Platform.select({
    ios: 'EverythingBundle1',
    android: 'everythingbundle1'
  }),
  
  // Subscription (this works fine)
  SUBSCRIPTION_MONTHLY: Platform.select({
    ios: 'TriviadareBase',
    android: 'trivia_dare_monthly'
  })
};

How They Are Initiaed in the UI:

const handlePurchase = async (packId, packType = 'trivia') => {
  try {
    setLoadingPurchases(prev => ({ ...prev, [packId]: true }));

    let productId;
    if (packType === 'trivia') {
      productId = revenueCatManager.PACK_TO_PRODUCT_MAP[packId];
    } else if (packType === 'bundle') {
      productId = revenueCatManager.PRODUCT_IDS.EVERYTHING_BUNDLE;
    }

    if (!productId) {
      Alert.alert('Error', 'Product not found');
      return;
    }

    // This calls the main purchase method above
    const success = await revenueCatManager.purchaseProductWithCallback(
      productId,
      (result) => {
        setLoadingPurchases(prev => ({ ...prev, [packId]: false }));
        if (result.success) {
          loadPurchaseStates();
        }
      }
    );

  } catch (error) {
    console.error('Purchase error:', error);
    Alert.alert('Purchase Error', 'Something went wrong. Please try again.');
  }
};

 

RevenueCat Initialization: 

 

async initialize() {
  if (this.isInitialized) {
    return true;
  }

  try {
    const apiKey = Platform.OS === 'ios' 
      ? REVENUECAT_API_KEYS.ios 
      : REVENUECAT_API_KEYS.android;
        
    await Purchases.configure({ apiKey });
    Purchases.addCustomerInfoUpdateListener(this.handleCustomerInfoUpdate.bind(this));
    this.customerInfo = await Purchases.getCustomerInfo();
    this.isInitialized = true;
    
    return true;
  } catch (error) {
    console.error('❌ RevenueCat initialization failed:', error);
    this.isInitialized = false;
    return false;
  }
}


Forum|alt.badge.img+3
  • Author
  • Member
  • 5 replies
  • July 28, 2025

Here is some snippets from my RevenueCatManager.js

 

// DIRECT PURCHASE: Main purchase method using direct product purchases
async purchaseProductWithCallback(productId, onComplete) {
  debugLog('PURCHASE', `Starting DIRECT purchase for: ${productId}`);
  
  try {
    if (!this.isInitialized) {
      debugLog('PURCHASE', 'RevenueCat not initialized, initializing now...');
      const initResult = await this.initialize();
      if (!initResult) {
        debugLog('PURCHASE', '❌ Initialization failed, aborting purchase');
        onComplete({ success: false, error: 'RevenueCat not initialized', cancelled: false });
        return false;
      }
    }

    // For subscriptions, use offering-based purchase (they need special handling)
    if (productId === PRODUCT_IDS.SUBSCRIPTION_MONTHLY) {
      debugLog('PURCHASE', 'Subscription detected, using offering-based purchase...');
      return await this.purchaseSubscriptionViaOffering(productId, onComplete);
    }

    debugLog('PURCHASE', 'Checking if already purchased...');
    if (await this.isPurchased(productId)) {
      debugLog('PURCHASE', '❌ Product already purchased, aborting');
      onComplete({ success: false, error: 'Already purchased', cancelled: false });
      Alert.alert('Already Purchased', 'You have already purchased this content.', [{ text: 'OK' }]);
      return false;
    }

    debugLog('PURCHASE', 'Attempting DIRECT product purchase (bypassing offerings)...');

    // 🎯 THIS IS THE LINE THAT FAILS ON ANDROID FOR INDIVIDUAL IAPS
    const { customerInfo } = await Purchases.purchaseProduct(productId);
    
    debugLog('PURCHASE', 'Direct purchase successful!', {
      productId,
      newActiveEntitlements: Object.keys(customerInfo.entitlements.active),
      newNonSubscriptionTransactions: customerInfo.nonSubscriptionTransactions.length
    });
    
    this.customerInfo = customerInfo;
    await this.trackPurchaseAchievements(productId);
    
    onComplete({ success: true, productId: productId });
    console.log('✅ Purchase successful:', productId);
    
    const successMessage = this.getSuccessMessage(productId);
    Alert.alert('Purchase Successful!', successMessage, [{ text: 'OK' }]);
    
    return true;

  } catch (error) {
    debugLog('PURCHASE', '❌ Direct purchase failed', {
      productId,
      error: error.message,
      code: error.code,
      underlyingErrorMessage: error.userInfo?.underlyingErrorMessage
    });
    
    console.error('❌ Purchase failed:', error);
    
    const isUserCancelled = error.code === Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED;
    
    onComplete({ 
      success: false, 
      error: error.message,
      cancelled: isUserCancelled,
      productId: productId
    });
    
    if (!isUserCancelled) {
      this.handlePurchaseError(error);
    }
    
    return false;
  }
}

 

 

// SUBSCRIPTION PURCHASE: Still use offerings for subscriptions
async purchaseSubscriptionViaOffering(productId, onComplete) {
  debugLog('PURCHASE', `Purchasing subscription via offering: ${productId}`);
  
  try {
    const offerings = await Purchases.getOfferings();
    
    if (!offerings?.current?.availablePackages) {
      throw new Error('No subscription offerings available');
    }

    // Find monthly package
    const monthlyPackage = offerings.current.availablePackages.find(pkg => 
      pkg.packageType === 'MONTHLY' || pkg.storeProduct?.identifier === productId
    );

    if (!monthlyPackage) {
      throw new Error('Monthly subscription package not found');
    }

    debugLog('PURCHASE', 'Found subscription package:', {
      packageId: monthlyPackage.identifier,
      packageType: monthlyPackage.packageType,
      productId: monthlyPackage.storeProduct?.identifier
    });

    // THIS WORKS PERFECTLY ON ANDROID
    const { customerInfo } = await Purchases.purchasePackage(monthlyPackage);
    
    debugLog('PURCHASE', 'Subscription purchase successful!', {
      packageId: monthlyPackage.identifier,
      newActiveEntitlements: Object.keys(customerInfo.entitlements.active)
    });
    
    this.customerInfo = customerInfo;
    await this.trackPurchaseAchievements(productId);
    
    onComplete({ success: true, productId: productId });
    console.log('✅ Subscription purchase successful:', productId);
    Alert.alert('Subscription Activated!', 'You now have full access to all content.', [{ text: 'OK' }]);
    
    return true;

  } catch (error) {
    debugLog('PURCHASE', '❌ Subscription purchase failed:', error.message);
    throw error;
  }
}

 

 

// Product IDs (keeping exact same IDs for compatibility)
export const PRODUCT_IDS = {
  PACK_HARRYPOTTER: Platform.select({
    ios: 'HarryPotter1',
    android: 'harrypotter1'
  }),
  PACK_MARVELCINAMATICUNIVERSE: Platform.select({
    ios: 'MarvelCinematicUniverse1',
    android: 'marvelcinematicuniverse1'
  }),
  PACK_STARWARS: Platform.select({
    ios: 'StarWars1',
    android: 'starwars1'
  }),
  PACK_DISNEYANIMATEDMOVIES: Platform.select({
    ios: 'DisneyAnimatedMovies1',
    android: 'disneyanimatedmovies1'
  }),
  // ... more individual IAP products
  EVERYTHING_BUNDLE: Platform.select({
    ios: 'EverythingBundle1',
    android: 'everythingbundle1'
  }),
  SUBSCRIPTION_MONTHLY: Platform.select({
    ios: 'TriviadareBase',
    android: 'trivia_dare_monthly'
  })
};

 

//PROPER ENTITLEMENT ARCHITECTURE
export const ENTITLEMENTS = {
  // Full access (subscription + everything bundle only)
  FULL_ACCESS: 'TriviaDareFullAccess',
  
  // Individual trivia pack entitlements (create these in RevenueCat Dashboard)
  ANIME: 'Anime',
  FRIENDS: 'Friends',
  HARRY_POTTER: 'Harry Potter',
  MARVEL: 'MarvelCinematicUniverse',
  STAR_WARS: 'Star Wars',
  DISNEY: 'DisneyAnimatedMovies',
  // ... more individual entitlements
};

// Product to Individual Entitlement Mapping
export const PRODUCT_TO_ENTITLEMENT_MAP = {
  // Trivia Packs → Individual Entitlements
  [PRODUCT_IDS.PACK_ANIME]: ENTITLEMENTS.ANIME,
  [PRODUCT_IDS.PACK_FRIENDS]: ENTITLEMENTS.FRIENDS,
  [PRODUCT_IDS.PACK_HARRYPOTTER]: ENTITLEMENTS.HARRY_POTTER,
  [PRODUCT_IDS.PACK_MARVELCINAMATICUNIVERSE]: ENTITLEMENTS.MARVEL,
  [PRODUCT_IDS.PACK_STARWARS]: ENTITLEMENTS.STAR_WARS,
  [PRODUCT_IDS.PACK_DISNEYANIMATEDMOVIES]: ENTITLEMENTS.DISNEY,
  // ... more mappings
};

 

 

// CHECK PURCHASE STATUS: Updated for proper entitlement logic
async isPurchased(productId) {
  debugLog('CHECK', `Checking purchase status for: ${productId}`);
  
  try {
    if (!this.customerInfo) {
      debugLog('CHECK', 'No customer info, fetching...');
      if (!this.isInitialized) {
        await this.initialize();
      }
      this.customerInfo = await Purchases.getCustomerInfo();
    }

    // 1. Check if user has full access (ONLY from subscription or everything bundle)
    const hasFullAccess = this.customerInfo.entitlements.active[ENTITLEMENTS.FULL_ACCESS];
    if (hasFullAccess) {
      debugLog('CHECK', '✅ Product available via full access entitlement (subscription or bundle)');
      return true;
    }

    // 2. Check individual product entitlement
    const individualEntitlement = PRODUCT_TO_ENTITLEMENT_MAP[productId];
    if (individualEntitlement) {
      const hasIndividualAccess = this.customerInfo.entitlements.active[individualEntitlement];
      if (hasIndividualAccess) {
        debugLog('CHECK', `✅ Product available via individual entitlement: ${individualEntitlement}`);
        return true;
      }
    }

    // 3. Fallback: Check direct product purchases (for legacy/edge cases)
    const hasActiveSubscription = this.customerInfo.activeSubscriptions.includes(productId);
    const hasNonSubscriptionTransaction = this.customerInfo.nonSubscriptionTransactions.some(t => t.productId === productId);
    
    if (hasActiveSubscription || hasNonSubscriptionTransaction) {
      debugLog('CHECK', '✅ Product found via direct purchase/subscription (fallback)');
      return true;
    }

    debugLog('CHECK', '❌ Product not purchased', {
      productId,
      checkedFullAccess: !!hasFullAccess,
      checkedIndividualEntitlement: individualEntitlement,
      hasIndividualEntitlement: individualEntitlement ? !!this.customerInfo.entitlements.active[individualEntitlement] : false,
      allActiveEntitlements: Object.keys(this.customerInfo.entitlements.active)
    });
    
    return false;
  } catch (error) {
    debugLog('CHECK', '❌ Error checking purchase status', { 
      productId, 
      error: error.message 
    });
    return false;
  }
}

 

// INITIALIZE: Simple and reliable
async initialize() {
  debugLog('INIT', 'initialize() called', { 
    isInitialized: this.isInitialized,
    platform: Platform.OS 
  });
  
  if (this.isInitialized) {
    debugLog('INIT', 'Already initialized, skipping');
    return true;
  }

  try {
    debugLog('INIT', 'Starting RevenueCat initialization...');
    
    const apiKey = Platform.OS === 'ios' 
      ? REVENUECAT_API_KEYS.ios 
      : REVENUECAT_API_KEYS.android;
        
    debugLog('INIT', 'API Key selected', {
      platform: Platform.OS,
      keyExists: !!apiKey,
      keyLength: apiKey ? apiKey.length : 0,
      keyPrefix: apiKey ? apiKey.substring(0, 10) + '...' : 'none'
    });
        
    if (!apiKey || apiKey.includes('YOUR_') || apiKey.includes('_HERE')) {
      debugLog('INIT', '❌ API key validation failed');
      console.error('❌ RevenueCat API key not configured!');
      return false;
    }
    
    debugLog('INIT', 'Configuring RevenueCat with API key...');
    await Purchases.configure({ apiKey });
    
    debugLog('INIT', 'RevenueCat configured, setting up listener...');
    Purchases.addCustomerInfoUpdateListener(this.handleCustomerInfoUpdate.bind(this));
    
    debugLog('INIT', 'Getting initial customer info...');
    this.customerInfo = await Purchases.getCustomerInfo();
    
    debugLog('INIT', 'Initial customer info retrieved', {
      hasCustomerInfo: !!this.customerInfo,
      originalAppUserId: this.customerInfo?.originalAppUserId,
      activeEntitlements: this.customerInfo ? Object.keys(this.customerInfo.entitlements.active) : [],
      activeSubscriptions: this.customerInfo?.activeSubscriptions || [],
      nonSubscriptionTransactions: this.customerInfo?.nonSubscriptionTransactions?.length || 0
    });
    
    this.isInitialized = true;
    debugLog('INIT', '✅ RevenueCat initialized successfully!');
    
    return true;
  } catch (error) {
    debugLog('INIT', '❌ RevenueCat initialization failed', {
      error: error.message,
      code: error.code
    });
    console.error('❌ RevenueCat initialization failed:', error);
    this.isInitialized = false;
    return false;
  }
}

 

 

 

  • Why does Purchases.purchasePackage(package) work for subscriptions but Purchases.purchaseProduct(productId) fail for individual IAPs on Android?
  • Are there Android-specific requirements for individual IAP products that I'm missing?
  • Is there a difference in how Google Play handles subscriptions vs individual IAPs through RevenueCat?

Forum|alt.badge.img+3
  • Author
  • Member
  • 5 replies
  • July 28, 2025

I shifted to an offering based purchase flow to attempt that way again. 


Forum|alt.badge.img+3
  • RevenueCat Staff
  • 49 replies
  • July 29, 2025

purchaseProduct method is deprecated, but if you do still want to use it, you will need to pass the product type as a parameter: https://revenuecat.github.io/react-native-purchases-docs/9.1.0/classes/default.html#purchaseProduct like this:

1const {customerInfo } = await Purchases.purchaseProduct("product_id", null, PURCHASE_TYPE.INAPP);

Of course you can use the offering based purchase flow, but if you want to avoid doing that, you can use the getProducts method to obtain the relevant PurchasesStoreProduct and then use it in the purchaseStoreProduct method like this:

1const products = await Purchases.getProducts(["YOUR_PRODUCT_ID"], PRODUCT_CATEGORY.SUBSCRIPTION);
2if (products.length > 0) {
3 const { customerInfo } = await Purchases.purchaseStoreProduct(products[0]);
4}

 


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings