Unity IAP v5.2 — How to purchase a specific Base Plan inside a container Subscription? (GoogleProductMetadata wraps SkuDetails, not ProductDetails)

Background — Google Play’s new subscription model

Google Play now separates Subscriptions from Base Plans. The hierarchy looks like this:

Subscription (e.g. "premium_main_sub")
 └── Base Plan: "weekly_prepaid"    ← 7-day, prepaid
 └── Base Plan: "monthly_prepaid"   ← 1-month, prepaid
 └── Base Plan: "yearly_prepaid"    ← 1-year, prepaid

Previously (GPBL v3 / SkuDetails era) each subscription was a flat product with one billing period. Now Google encourages grouping duration variants under a single container Subscription, where each duration is a Base Plan. To purchase a specific Base Plan, the native GPBL v5+ flow requires:

  1. Call queryProductDetailsAsync() on the container subscription ID
  2. From the returned ProductDetails, call getSubscriptionOfferDetails() → gets a List<ProductDetails.SubscriptionOfferDetails>
  3. Each entry has getBasePlanId() and getOfferToken()
  4. Pass the chosen offerToken into BillingFlowParams.ProductDetailsParams when calling launchBillingFlow()

In native Java/Kotlin this is well-documented. The question is how to do it through Unity IAP v5.2.


What we investigated in Unity IAP v5.2

We went through the public API surface carefully, including the official docs and package source. Here is what we found:


GoogleProductMetadata — confirmed public properties

From: Class GoogleProductMetadata | In-App Purchasing | 5.2.0-pre.2

public string freeTrialPeriod { get; }
public string introductoryPrice { get; }
public int    introductoryPriceCycles { get; }
public string introductoryPricePeriod { get; }
public string subscriptionPeriod { get; }
public string originalJson { get; }   // raw SKU JSON

No SubscriptionOfferDetails. No BasePlanId. No OfferToken.

Despite the docs describing GoogleProductMetadata as “a representation of ProductDetails”, the actual properties make it clear this wraps the legacy SkuDetails object (GPBL v3), not the modern ProductDetails (GPBL v5+). The originalJson field is the old SkuDetails JSON format, which predates the Base Plan system entirely and will not contain subscriptionOfferDetails.


IGooglePlayStoreExtendedPurchaseService — confirmed public methods

From uploaded source (IGooglePlayStoreExtendedPurchaseService.cs):

void UpgradeDowngradeSubscription(Product oldProduct, Product newProduct);
void UpgradeDowngradeSubscription(Product oldProduct, Product newProduct, GooglePlayProrationMode);
void UpgradeDowngradeSubscription(Product oldProduct, Product newProduct, GooglePlayReplacementMode);
void UpgradeDowngradeSubscription(Order currentOrder, Product newProduct, GooglePlayReplacementMode);
bool IsOrderDeferred(Order order);
GooglePurchaseState? GetPurchaseState(Order order);
string? GetObfuscatedAccountId(Order order);
string? GetObfuscatedProfileId(Order order);
event Action<DeferredPaymentUntilRenewalDateOrder> OnDeferredPaymentUntilRenewalDate;

No offer-token or base-plan selection here either. This service only handles switching between existing subscriptions (upgrade/downgrade).


IGooglePlayStoreExtendedService — confirmed via StoreController source

Accessible via storeController.GooglePlayStoreExtendedService. Does not expose base-plan purchase methods.


UnityEngine.Purchasing.Models namespace

Only contains GoogleBillingResponseCode (an enum mapping to BillingClient.BillingResponseCode). Nothing subscription-offer related.


UnityEngine.Purchasing.GoogleBilling.Models namespace

Only contains ExternalBillingProgramClient and supporting types for Google’s external billing program compliance. Nothing subscription-offer related.


UnityEngine.Purchasing.Security namespace

Contains GooglePlayReceipt (purchaseToken, orderID, productID, packageName, purchaseDate, purchaseState) and CrossPlatformValidator. No offer token.


IGooglePlayStoreExtensions (legacy, still present)

Listed in UnityEngine.Purchasing namespace. Marked [Obsolete]. This is the old v4 interface — not the path forward for new code.


Summary of what is and isn’t possible in v5.2

Goal Possible in Unity IAP v5.2?
Purchase a subscription (Option A — one sub per plan) :white_check_mark: Yes — storeController.PurchaseProduct(productId)
Switch between subscriptions (upgrade/downgrade) :white_check_mark: Yes — IGooglePlayStoreExtendedPurchaseService.UpgradeDowngradeSubscription()
Get subscription metadata (price, period, trial) :white_check_mark: Yes — GoogleProductMetadata
Get SubscriptionOfferDetails from ProductDetails :cross_mark: No — GoogleProductMetadata wraps SkuDetails, not ProductDetails
Select a specific Base Plan by ID :cross_mark: No — no offer-token API exposed in managed C#
Purchase a specific Base Plan in a container Subscription :cross_mark: No — not possible through public Unity IAP v5.2 API

What we’re trying to achieve

We have a Unity project using StoreController (IAP v5.2) and we want to support the modern Google Play subscription model — one container subscription (premium_main_sub) with multiple base plans (3day_prepaid, weekly_prepaid, monthly_prepaid, yearly_prepaid).

Our current workaround is Option A — creating separate Google Play Subscriptions per duration — which works fine. But we’d prefer the modern approach for cleaner Play Console management and because Google now recommends it for new apps.


Questions

  1. Is there a public API in Unity IAP v5.2 (or a pre-release 5.x build) that allows purchasing a specific Base Plan by offer token? We may have missed something.

  2. Is GoogleProductMetadata planned to be updated to wrap ProductDetails instead of SkuDetails? The current class exposes only legacy SkuDetails fields.

  3. Is the recommended path for Base Plan purchasing still Option A (separate Subscription per duration) from Unity IAP’s perspective? Or is there official guidance on Option B (container sub + base plans)?

  4. If a native Android plugin / AndroidJavaObject bridge is the expected approach, is there any official Unity sample or documentation showing how to integrate a custom native billing flow with Unity IAP’s entitlement and receipt validation pipeline?


Our setup

  • Unity IAP: 5.2.0-pre.2
  • Unity: 6 (6000.x)
  • Target platform: Android (Google Play)
  • StoreController v5 API (not legacy IStoreListener)

Any help or official clarification would be greatly appreciated. If the answer is “use Option A for now”, that’s a perfectly valid answer — we just want to confirm we haven’t missed a v5.2 API surface. Thanks!

@Laurie-Unity @jacobbev @Yannick_D @SashaM

Unity-In-App-Purchases
Monetization-and-Growth-Unity-In-App-Purchases

Hello @HajiyevEl

We still don’t support subscription plans yet, so Option A is still the way to go.

Hi! First of all thanks for taking your time to reply!

My second question: is it possible programmatically “consume” a Non-Consumable (One-time) product for testing?

I am currently using Unity IAP v5 on Android and have a specific testing hurdle regarding promo codes and non-consumable products.

The Setup:
I have created a One-time product (Non-Consumable) with the ID promo_sub_003_days_prepaid. This product is linked to a promotion called promo-sub-003-days-prepaid. In my app logic, this acts as a 3-day access pass.

The Problem:
When I redeem a promo code for this non-consumable item, the purchase is successfully confirmed (Confirmed=1). However, to test the flow again with a new code, I currently have to go into the Google Play Console and manually “Refund” the $0.00 transaction to revoke the entitlement. This process is slow due to Console sync delays (or idk why but redeemed promo code purchases are not showing up in “Order Management” tab).

In older versions of Unity IAP, there was a “hack” to treat a non-consumable as a consumable during initialization or via the purchase processor to effectively “consume” it on the Google Play side. (Similar issue)

My Questions:

  1. Does Unity IAP v5 still support a way to force-consume a product that Google Play sees as a “One-time product” (non-consumable)?
  2. Is there a specific method in IGooglePlayStoreExtensions (or another v5 interface) that can signal Google Play to release the entitlement for a non-consumable so it can be re-purchased/re-redeemed immediately?
  3. If this is no longer possible via code, is there a faster way to reset these entitlements for a License Tester account without waiting for the Order Management dashboard to update?

Any help or modern workarounds for IAP v5 would be greatly appreciated!

The workaround should still work, by changing it from a consumable to a non-consumable.

This is a good suggestion and I believe it would be possible to add a function to confirm a non-consumable for testing purposes. I have created a ticket for us to explore this option.

If you use the Google APIs, you could confirm the purchase by sending the request, but without this, I don’t believe there are other ways to do this faster.

Are there any plans to support subscription plans anytime soon?