FAQ

Why does InitializeStore return an empty product list?

This usually happens for two reasons:

  • No billing products configured in Essential Kit Settings

  • Platform-specific store issues

iOS Issues

Issue
Solution

Pending Agreements, Tax, or Banking information

Complete all required information in App Store Connect

Latest Apple Developer Program License Agreement not accepted

Accept the agreement in App Store Connect

Paid Agreements not in Active status

Ensure status shows as "Active"

Android Issues

Issue
Solution

Wrong package name or version code

Ensure APK package name and version match Google Play Console

Tester account not opted in

Use the opt-in testing track link to become a valid tester

App not uploaded to testing track

Upload signed APK/AAB to Internal or Closed Testing

Why does InitializeStore return products out of order?

Products can be marked as inactive or deleted after your app is released. If we ordered products by array index, deleting a product in the store console would break older app versions.

Recommended approach:

// Always use GetProductWithId, never array index
var product = BillingServices.GetProductWithId("coins_100");

This ensures your code works regardless of product ordering changes.

On Android, why do purchases from one account appear when logged in with a different account?

This is standard Android behavior. Purchases on Android are linked to the account that installs the app, not the account logged into the Google Play app.

To restore a different account's purchases, uninstall and reinstall the app while logged in with the new account.

Are testers charged for testing?

iOS:

  • Sandbox testers: Never charged

  • TestFlight users: IAP offered free by default, no sandbox account needed

Android:

  • Normal testers (opt-in via test track): Charged but refunded within 14 days

  • License testers: Never charged

How do I implement receipt verification with Appodeal?

IBillingTransaction transaction; // From OnTransactionStateChange

var rawData = (IDictionary)ExternalServiceProvider.JsonServiceProvider.FromJson(transaction.RawData);
var originalTransaction = rawData?["transaction"] as string;
var signature = rawData?["signature"] as string;

#if UNITY_ANDROID
var purchase = new PlayStoreInAppPurchase.Builder(
    transaction.Product.Type == BillingProductType.Subscription
        ? PlayStorePurchaseType.Subs
        : PlayStorePurchaseType.InApp)
    .WithPurchaseTimestamp(new DateTimeOffset(transaction.DateUTC).ToUnixTimeSeconds())
    .WithPurchaseToken(transaction.Receipt)
    .WithPurchaseData(originalTransaction)
    .WithPublicKey(BillingServices.UnitySettings.AndroidProperties.PublicKey)
    .WithSignature(signature)
    .WithCurrency(transaction.Product.Price.Code)
    .WithOrderId(transaction.Id)
    .WithPrice($"{transaction.Product.Price.Value}")
    .WithSku(transaction.Product.PlatformId)
    .Build();

Appodeal.ValidatePlayStoreInAppPurchase(purchase, this);

#elif UNITY_IOS
var purchaseType = transaction.Product.Type == BillingProductType.Subscription
    ? AppStorePurchaseType.AutoRenewableSubscription
    : transaction.Product.Type == BillingProductType.Consumable
        ? AppStorePurchaseType.Consumable
        : AppStorePurchaseType.NonConsumable;

var purchase = new AppStoreInAppPurchase.Builder(purchaseType)
    .WithTransactionId(transaction.Id)
    .WithProductId(transaction.Product.PlatformId)
    .WithCurrency(transaction.Product.Price.Code)
    .WithPrice($"{transaction.Product.Price.Value}")
    .Build();

Appodeal.ValidateAppStoreInAppPurchase(purchase, this);
#endif

Why does IsProductPurchased always return false for consumables?

This is expected behavior. IsProductPurchased() only works for non-consumable products and subscriptions. Consumable products can be purchased multiple times, so there's no permanent ownership state to check.

When should I disable Auto Finish Transactions?

Only disable Auto Finish Transactions if you need server-side receipt verification, typically for:

  • Android apps with significant user base (Google recommends server verification)

  • High-value purchases requiring additional fraud prevention

  • Custom verification flows with your backend

For most games, keep it enabled. iOS uses StoreKit2 with built-in local verification.

My purchases work in sandbox but fail in production. Why?

Common causes:

  • Product not approved or active in production store

  • App Store Connect / Google Play Console agreements not signed

  • Tax and banking information incomplete

  • Product ID mismatch between sandbox and production configurations

Verify all store setup steps are complete and products are live in production.

How do I handle multiple virtual currencies?

Use the Payouts property in product definitions:

// In Essential Kit Settings or runtime configuration
new BillingProductDefinition("mega_pack", BillingProductType.Consumable)
{
    Payouts = new BillingProductPayoutDefinition[]
    {
        new BillingProductPayoutDefinition { Type = "coins", Quantity = 500 },
        new BillingProductPayoutDefinition { Type = "gems", Quantity = 100 }
    }
}

// In your grant content code
var product = BillingServices.GetProductWithId("mega_pack");
foreach (var payout in product.PayoutDefinitions)
{
    switch (payout.Type)
    {
        case "coins": PlayerData.AddCoins(payout.Quantity); break;
        case "gems": PlayerData.AddGems(payout.Quantity); break;
    }
}

What's the difference between forceRefresh true and false in RestorePurchases?

forceRefresh: true

  • Shows interactive login dialog on iOS

  • Contacts server for latest purchase data

  • Use for user-triggered "Restore Purchases" button

forceRefresh: false

  • Silent restore without dialogs

  • Uses cached data

  • Use for automatic restore on app startup

Where can I confirm plugin behavior versus my implementation?

Run the demo scene at Assets/Plugins/VoxelBusters/EssentialKit/Examples/Scenes/BillingServicesDemo.unity.

If the sample works but your scene does not, compare:

  • Product ID configuration in settings

  • Event subscription timing (subscribe in OnEnable, unsubscribe in OnDisable)

  • Error handling in transaction callbacks

  • Transaction finishing logic if Auto Finish is disabled

How do I implement season passes or time-limited products?

Use non-consumable products with custom time tracking:

string currentSeasonId = "season_pass_2024_q1";

if (BillingServices.IsProductPurchased(currentSeasonId))
{
    if (IsSeasonActive(currentSeasonId))
    {
        GrantSeasonBenefits();
    }
}

bool IsSeasonActive(string seasonId)
{
    var seasonConfig = GetSeasonConfig(seasonId);
    DateTime now = DateTime.Now;
    return now >= seasonConfig.StartDate && now <= seasonConfig.EndDate;
}

Each season should have a unique product ID to prevent conflicts.

Can I change product prices after release?

Yes. Change prices in App Store Connect and Google Play Console at any time. New prices appear in product.LocalizedPrice after InitializeStore() completes. Players who already purchased keep their access at the old price.

My transactions stay in pending state. What's wrong?

If you disabled Auto Finish Transactions, you must manually call:

BillingServices.FinishTransactions(new[] { transaction });

Call this after granting purchased content. Transactions in Purchasing or Deferred state cannot be finished until they complete.

How do I test subscription renewals and expirations?

iOS sandbox:

  • Subscriptions renew every few minutes instead of monthly/yearly

  • Fast testing of renewal flows

Android testing:

  • Use Google Play's test card for subscription testing

  • Check product.SubscriptionInfo.ExpiryDate to validate status

Note: Subscription properties may have limited data on Android.

How does Essential Kit track Non-Consumable/Subscription purchases?

For details on how Essential Kit maintains purchase states across platforms, see:

How Non-Consumable and Subscription product statuses are maintained?

Last updated

Was this helpful?