Skip to main content
Question

RevenueCat not exposing Google Play's OneTimePurchaseOfferDetails

  • March 21, 2026
  • 0 replies
  • 1 view

Forum|alt.badge.img

If anyone else is struggling with RevenueCat not exposing Google Play's OneTimePurchaseOfferDetails (like Launch Offers or region-specific discounts for In-App products on Android), here is a bulletproof workaround we implemented in production.

The Problem: Google Play added oneTimePurchaseOfferDetailsList in Billing Library v6 for in-app products, but purchases_flutter (and the underlying purchases-hybrid-common) currently drops this array when serializing the Native ProductDetails into the Dart 

StoreProduct. So storeProduct.price just returns the un-discounted base list price.

The Workaround: Use the official in_app_purchase package strictly as a "read-only scanner" to fetch the real JSON natively, and keep using RevenueCat for the actual purchase.

  1. Add the dependency Add in_app_purchase: ^3.2.0 to your pubspec.yaml. We won't use it to buy things, only to query the Google Play API directly.

  2. Create a quick Extractor Service This queries the Google Play Billing Library and finds the lowest available offer for a specific one-time product.

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';

class GooglePlayOfferExtractor {
static Future<double?> getLowestInAppOffer(String productId) async {
if (!Platform.isAndroid) return null;

try {
final iap = InAppPurchase.instance;
final response = await iap.queryProductDetails({productId});

if (response.error != null || response.productDetails.isEmpty) {
return null;
}

final details = response.productDetails.first;
if (details is GooglePlayProductDetails) {
final rawDetails = details.productDetails;
final offer = rawDetails.oneTimePurchaseOfferDetails; // Native Play Billing v6 offer wrapper

if (offer != null) {
// You now have access to the exact eligible local offer!
return offer.priceAmountMicros.toDouble() / 1000000.0;
}
}
} catch (e) {
debugPrint('Error extracting native Android offer: $e');
}
return null;
}
}
  1. Hook it up in your UI When you call Purchases.getOfferings(), also call the extractor.
final offerings = await Purchases.getOfferings();
final lifetimePackage = offerings.current?.availablePackages.firstWhere((p) => p.packageType == PackageType.lifetime);

double? realOfferPrice;
if (Platform.isAndroid) {
realOfferPrice = await GooglePlayOfferExtractor.getLowestInAppOffer('your_lifetime_id');
}

// In your UI, if realOfferPrice is not null, display IT instead of package.storeProduct.price!
  1. Buy using RevenueCat When the user taps "Buy", just call Purchases.purchasePackage(lifetimePackage). RevenueCat will hand it over to Google Play, and Google Play will automatically charge the discounted Offer price we just fetched, but RevenueCat gets the purchase state properly synced.

It takes 10 minutes to add and solves the discrepancy flawlessly!