Subscriptions - getting started

Hi

We’d like to add support for subscriptions on iOS and Android, and I’ve seen a lot of adhoc discussions about what Unity IAP handles for you vs various platform-specific APIs that need to be called manually.

Is this information summarised anywhere?

This should help Unity - Manual: Subscription Product support and Improved Support for Subscription Products

How about refunded / voided / cancelled subscriptions?

It should work, please test and see for yourself. There should be no additional action necessary. If you run into any issues, please report here.

1 Like

@JeffDUnity3D is it possible to provide a limited subscription available to say the first 1000 users then and then after that they can keep it while everyone new has to pay more than the 1000 subscribers and then if they cancel the subscription they can not get it back?

I hope you can make sense of that.

This is something you would need to do yourself, we don’t have anything built in that would handle this. I’m not sure how you would determine “first 1,000 subscribers”, you would need to keep an eye on your Google developer dashboard for the number of sales, I’m not sure if there is a Google API for that. I might suggest to get the basics working first.

Well i don’t require it is exactly 1000 users get the discounted rate as much as I want to be able to change it for subscribers at some point but honor the lower price for the first comers … sounds like this is doable but is there any sorta guide to making this happen?

I might suggest you get the basics working first, there is no specific guide. This might help https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/implementing_introductory_offers_in_your_app and https://help.apple.com/app-store-connect/#/deve1d49254f and https://developer.android.com/google/play/billing/subscriptions

Hi, I’m having trouble checking if a subscription is active, especially with Google Play.

On iPhone, the subscriptionInfo.isExpired() and subscriptionInfo.isSubscribed() seems to be updated automatically when the subscription is expired. It doesn’t have auto-renew.

But on Android, the subscriptionInfo doesn’t seems to update at all, even when its renewed or cancelled. subscriptionInfo.isExpired() remained false, and subscriptionInfo.isSubscribed() remained true.

I verified via the Play Store app, the subscription has cancelled and expired.
Tested with auto-renew as well, the expiry time just seems to stuck at the first expiry time, and doesn’t update even if I restart the app. Same for when cancelled and expired.

Note that, on both devices, they were tested without exiting the app. Only unfocused and focused, except when I was trying to reset the status.

Here’s my code, in case you need it.

        public SubscriptionManager GetSubscriptionManager(string productId)
        {
            if (!IsInitialized()) return null;
            var product = _storeController.products.WithID(productId);
            if (product == null) return null;
            if (product.definition.type != ProductType.Subscription) return null;
            if (!product.hasReceipt) return null;

            string introJson = GetIntroductoryInfo(productId);
            return new SubscriptionManager(product, introJson);
        }
        public bool IsExpired(string productId)
        {
            var subscriptionManager = GetSubscriptionManager(productId);
            if (subscriptionManager == null) return false;

            var subscriptionInfo = subscriptionManager.getSubscriptionInfo();

            Debug.Logger.Write($"IsExpired: {productId}, {subscriptionInfo.isExpired()}, {subscriptionInfo.isSubscribed()}");

            return subscriptionInfo.isExpired() == Result.True;
        }

This is for iOS. The version for GooglePlay, will just return a null.

        protected override string GetIntroductoryInfo(string productId)
        {
            var introductoryInfoDict = _appleExtensions.GetIntroductoryPriceDictionary();
            if (introductoryInfoDict != null && introductoryInfoDict.ContainsKey(productId))
            {
                return introductoryInfoDict[productId];
            }

            return null;
        }

@s-tang Which version of IAP are you using? I will test here also.

Thanks for the reply. Apologize for forgetting to mention the version.

I’m using version 2.2.1 for the core (the one from Package Manager), and version 2.2.4 for the package (the one from Services). Using them on Unity 2019.2.21f1.

Did more testing on Android.

It seems that the Product.hasReceipt will return false, after a certain time has passed after expired, and only if the app is restarted. Its tested with the same build as when I reported the issue, so there’s no change in code or anything.
If I remember correctly, it didn’t reset to false, even after 10 mins, when I reported the issue.
But now I’m seeing it updated as earlier as 3min after expired.

I wonder if it take awhile for Google Play to update the subscription status, or if there’s a cache, somewhere for the product, in the device.

Also, I noticed the expiry time given from the subscription info is different from the one given in Google Play app, even on the first purchase of the subscription. Hence I added more logs to show more subscription infos and noticed that the expire date has the same date and time with the purchase date.

[EDIT start]
Ignore my comment on the expiry time. I didn’t noticed that the month and year value is different.

But the expiry time still remained the time, even after auto-renewed. I’m expecting it to update to the new expiry time after renewed. Do correct me if that shouldn’t be the case.
[EDIT End]

Here’s my code for the log

        public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
        {
            // Overall Purchasing system, configured with products for this application.
            _storeController = controller;
            // Store specific subsystem, for accessing device-specific store features.
            _storeExtensionProvider = extensions;
            _transactionHistoryExtensions = extensions.GetExtension<ITransactionHistoryExtensions>();

            foreach(var product in _storeController.products.all)
            {
                var subscriptionManager = GetSubscriptionManager(product.definition.id);
                if (subscriptionManager == null) continue;
                var info = subscriptionManager.getSubscriptionInfo();

                Debug.Logger.Write($"ZR Product: {info.getProductId()}, " +
                    $"\ngetCancelDate: {info.getCancelDate()}" +
                    $"\ngetExpireDate: {info.getExpireDate()}" +
                    $"\ngetFreeTrialPeriodString: {info.getFreeTrialPeriodString()}" +
                    $"\ngetIntroductoryPrice: {info.getIntroductoryPrice()}" +
                    $"\ngetIntroductoryPricePeriod: {info.getIntroductoryPricePeriod()}" +
                    $"\ngetIntroductoryPricePeriodCycles: {info.getIntroductoryPricePeriodCycles()}" +
                    $"\ngetPurchaseDate: {info.getPurchaseDate()}" +
                    $"\ngetRemainingTime: {info.getRemainingTime()}" +
                    $"\ngetSkuDetails: {info.getSkuDetails()}" +
                    $"\ngetSubscriptionInfoJsonString: {info.getSubscriptionInfoJsonString()}" +
                    $"\ngetSubscriptionPeriod: {info.getSubscriptionPeriod()}" +
                    $"\nisAutoRenewing: {info.isAutoRenewing()}" +
                    $"\nisCancelled: {info.isCancelled()}" +
                    $"\nisExpired: {info.isExpired()}" +
                    $"\nisFreeTrial: {info.isFreeTrial()}" +
                    $"\nisIntroductoryPricePeriod: {info.isIntroductoryPricePeriod()}" +
                    $"\nisSubscribed: {info.isSubscribed()}" +
                    $"");
            }
        }

And the output from the log. (It got cut off after the “isFreeTrial” log…)

2020-12-16 12:12:02.619 9894-9940/com.(…omitted…) I/Unity: ZR Product: (…omitted…).ads.plan1,
getCancelDate: 01/01/0001 00:00:00
getExpireDate: 01/16/2021 03:10:57
getFreeTrialPeriodString:
getIntroductoryPrice: not available
getIntroductoryPricePeriod: 00:00:00
getIntroductoryPricePeriodCycles: 0
getPurchaseDate: 12/16/2020 03:10:57
getRemainingTime: 30.23:58:55.0462350
getSkuDetails: (…omitted…)
getSubscriptionInfoJsonString: (…omitted…)
getSubscriptionPeriod: 31.00:00:00
isAutoRenewing: True
isCancelled: False
isExpired: False
isFreeTrial: False
isIntr

Upon cancelled, and app restarted, isCancelled is showing true, while getCancelDate is returning a default value.

2020-12-16 12:35:20.592 11719-11761/com.(…omitted…) I/Unity: ZR Product: (…omitted…).ads.plan1,
getCancelDate: 01/01/0001 00:00:00
getExpireDate: 01/16/2021 03:10:57
getFreeTrialPeriodString:
getIntroductoryPrice: not available
getIntroductoryPricePeriod: 00:00:00
getIntroductoryPricePeriodCycles: 0
getPurchaseDate: 12/16/2020 03:10:57
getRemainingTime: 30.23:35:37.0726970
getSkuDetails: (…omitted…)
getSubscriptionInfoJsonString: (…omitted…)
getSubscriptionPeriod: 31.00:00:00
isAutoRenewing: False
isCancelled: True
isExpired: False
isFreeTrial: False
is

@s-tang We will investigate.