Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Subscriptions - getting started

Discussion in 'Unity IAP' started by dri_richard, Nov 2, 2020.

  1. dri_richard

    dri_richard

    Joined:
    Mar 10, 2017
    Posts:
    153
    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?
     
  2. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
  3. dri_richard

    dri_richard

    Joined:
    Mar 10, 2017
    Posts:
    153
    How about refunded / voided / cancelled subscriptions?
     
  4. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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.
     
    dri_richard likes this.
  5. CrandellWS

    CrandellWS

    Joined:
    Oct 31, 2015
    Posts:
    177
    @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.
     
  6. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    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.
     
  7. CrandellWS

    CrandellWS

    Joined:
    Oct 31, 2015
    Posts:
    177
    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?
     
  8. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    I might suggest you get the basics working first, there is no specific guide. This might help https://developer.apple.com/documen.../implementing_introductory_offers_in_your_app and https://help.apple.com/app-store-connect/#/deve1d49254f and https://developer.android.com/google/play/billing/subscriptions
     
  9. s-tang

    s-tang

    Joined:
    Jan 8, 2020
    Posts:
    10
    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.
    Code (CSharp):
    1.  
    2.         public SubscriptionManager GetSubscriptionManager(string productId)
    3.         {
    4.             if (!IsInitialized()) return null;
    5.             var product = _storeController.products.WithID(productId);
    6.             if (product == null) return null;
    7.             if (product.definition.type != ProductType.Subscription) return null;
    8.             if (!product.hasReceipt) return null;
    9.  
    10.             string introJson = GetIntroductoryInfo(productId);
    11.             return new SubscriptionManager(product, introJson);
    12.         }

    Code (CSharp):
    1.  
    2.         public bool IsExpired(string productId)
    3.         {
    4.             var subscriptionManager = GetSubscriptionManager(productId);
    5.             if (subscriptionManager == null) return false;
    6.  
    7.             var subscriptionInfo = subscriptionManager.getSubscriptionInfo();
    8.  
    9.             Debug.Logger.Write($"IsExpired: {productId}, {subscriptionInfo.isExpired()}, {subscriptionInfo.isSubscribed()}");
    10.  
    11.             return subscriptionInfo.isExpired() == Result.True;
    12.         }
    This is for iOS. The version for GooglePlay, will just return a null.
    Code (CSharp):
    1.  
    2.         protected override string GetIntroductoryInfo(string productId)
    3.         {
    4.             var introductoryInfoDict = _appleExtensions.GetIntroductoryPriceDictionary();
    5.             if (introductoryInfoDict != null && introductoryInfoDict.ContainsKey(productId))
    6.             {
    7.                 return introductoryInfoDict[productId];
    8.             }
    9.  
    10.             return null;
    11.         }
     
  10. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    @s-tang Which version of IAP are you using? I will test here also.
     
  11. s-tang

    s-tang

    Joined:
    Jan 8, 2020
    Posts:
    10
    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.
     
  12. s-tang

    s-tang

    Joined:
    Jan 8, 2020
    Posts:
    10
    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
    Code (CSharp):
    1.  
    2.         public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    3.         {
    4.             // Overall Purchasing system, configured with products for this application.
    5.             _storeController = controller;
    6.             // Store specific subsystem, for accessing device-specific store features.
    7.             _storeExtensionProvider = extensions;
    8.             _transactionHistoryExtensions = extensions.GetExtension<ITransactionHistoryExtensions>();
    9.  
    10.             foreach(var product in _storeController.products.all)
    11.             {
    12.                 var subscriptionManager = GetSubscriptionManager(product.definition.id);
    13.                 if (subscriptionManager == null) continue;
    14.                 var info = subscriptionManager.getSubscriptionInfo();
    15.  
    16.                 Debug.Logger.Write($"ZR Product: {info.getProductId()}, " +
    17.                     $"\ngetCancelDate: {info.getCancelDate()}" +
    18.                     $"\ngetExpireDate: {info.getExpireDate()}" +
    19.                     $"\ngetFreeTrialPeriodString: {info.getFreeTrialPeriodString()}" +
    20.                     $"\ngetIntroductoryPrice: {info.getIntroductoryPrice()}" +
    21.                     $"\ngetIntroductoryPricePeriod: {info.getIntroductoryPricePeriod()}" +
    22.                     $"\ngetIntroductoryPricePeriodCycles: {info.getIntroductoryPricePeriodCycles()}" +
    23.                     $"\ngetPurchaseDate: {info.getPurchaseDate()}" +
    24.                     $"\ngetRemainingTime: {info.getRemainingTime()}" +
    25.                     $"\ngetSkuDetails: {info.getSkuDetails()}" +
    26.                     $"\ngetSubscriptionInfoJsonString: {info.getSubscriptionInfoJsonString()}" +
    27.                     $"\ngetSubscriptionPeriod: {info.getSubscriptionPeriod()}" +
    28.                     $"\nisAutoRenewing: {info.isAutoRenewing()}" +
    29.                     $"\nisCancelled: {info.isCancelled()}" +
    30.                     $"\nisExpired: {info.isExpired()}" +
    31.                     $"\nisFreeTrial: {info.isFreeTrial()}" +
    32.                     $"\nisIntroductoryPricePeriod: {info.isIntroductoryPricePeriod()}" +
    33.                     $"\nisSubscribed: {info.isSubscribed()}" +
    34.                     $"");
    35.             }
    36.         }
    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
     
    Last edited: Dec 16, 2020
  13. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446