Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Solved] Unity IAP - Subscription Expiration returns always wrong date

Discussion in 'Unity IAP' started by Deathfate, Mar 29, 2016.

Thread Status:
Not open for further replies.
  1. Deathfate

    Deathfate

    Joined:
    Sep 10, 2012
    Posts:
    46
    Hello, I am trying to implement renewing subscriptions with unity IAP, I managed to get the appReceipt and I want to see if the user is currently subscribed or not, to do that I do something like this:

    Code (CSharp):
    1. ConfigurationBuilder builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    2.         // Get a reference to IAppleConfiguration during IAP initialization.
    3.         var appleConfig = builder.Configure<IAppleConfiguration>();
    4.         if (!string.IsNullOrEmpty (appleConfig.appReceipt)) {
    5.             var receiptData = System.Convert.FromBase64String (appleConfig.appReceipt);
    6.             AppleReceipt receipt = new AppleValidator (AppleTangle.Data ()).Validate (receiptData);
    7.             foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts) {
    8.                 Debug.Log ("PRODUCTID: " + productReceipt.productID);
    9.                 Debug.Log ("PURCHASE DATE: " + productReceipt.purchaseDate);
    10.                 Debug.Log ("EXPIRATION DATE: " + productReceipt.subscriptionExpirationDate);
    11.                 Debug.Log ("CANCELDATE DATE: " + productReceipt.cancellationDate);
    12.             }
    13.         }
    The thing is that the productID and purchaseDate are correct, but subscriptionExpirationDate and cancellationDate always return
    01/01/0001 00:00:00
    , with the cancellationDate i assume it can be normal as the value may not be present if the user didn't cancelled the subscription but If im not mistaken the expirationDate should always be there (and be a higher value than purchaseDate).

    I'm using testing account, maybe the values get real once the purchase is real?

    I'm stuck with this, thank you for your time.
     
    siddharth3322 likes this.
  2. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    Have you tried sending it to Apple's receipt validation API to see what information it returns?

    If you attach a sample receipt to this thread I will take a look.
     
    nicholasr likes this.
  3. Deathfate

    Deathfate

    Joined:
    Sep 10, 2012
    Posts:
    46
    Is it safe to post a debug.log of my ios receipt (it looks like a bunch of letters as it is encoded with PCKS7 os something like that). If not I can send it to you via PM.

    Does unity provide any function to validate it with apple's receipt validation API?
     
  4. MochiBits

    MochiBits

    Joined:
    Mar 11, 2014
    Posts:
    30
    Deathfate, you need to validate receipts as a separate process using your own server. You send the base64 encoded receipt (received after purchasing) to the apple URL in the link below, and it sends you back a JSON with new UPDATED receipt info for all your purchases. You can parse that JSON to get the current status of any subscription or non-consumable IAP.

    https://developer.apple.com/library...html#//apple_ref/doc/uid/TP40010573-CH104-SW1

    The issue with subscription receipts is that the one returned after purchase will never include updated information. So if the user cancel's the subscription you won't know unless you get a new receipt.
     
  5. Deathfate

    Deathfate

    Joined:
    Sep 10, 2012
    Posts:
    46
    @MochiBits I know there is another way of validation
    As far as I'm concerned I can validate the receipts locally and don't need to do it through apple's API, in fact I think thats the only validation that unity IAP supports (if i am not mistaken). In my case I get the 6 subscription receipts (of the sandbox enviroment, as it renews a maximum of 6 times every 3 minutes) and I don't use the validation through apple's api so I don't think that I have to do anything else.

    The problem is that the expiration date and the cancellation date values are not correct.
     
  6. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    @Deathfate you are correct that you do not need to submit the receipt to apple's RVS API. That API should never be used directly from your Application. Feel free to DM me the receipt.

    Edit - @MochiBits is correct to point out that the latest status of a receipt can be retrieved from Apple's API. Subscriptions commonly involve a server that dispenses the paywalled content, like DLC or multiplayer, which is where you should generally place your validation.
     
    Last edited: Apr 4, 2016
    nicholasr and Deathfate like this.
  7. Deathfate

    Deathfate

    Joined:
    Sep 10, 2012
    Posts:
    46
    @Banderous I have started a private conversation with you with the receipt that unity returns to me and also with the output that gives me when validating it.

    Thank you so much.
     
  8. VoxStudio

    VoxStudio

    Joined:
    Jan 12, 2016
    Posts:
    16
    I'm having the same issue here... Please let me know if you guys manage to solve this!!!
     
    Deathfate likes this.
  9. Deathfate

    Deathfate

    Joined:
    Sep 10, 2012
    Posts:
    46
    I sent the receipt but I don't have an answer yet, will reply as soon as I get one :)
     
  10. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    This is fixed in version 1.3.2 of our store package; reimport from the IAP cloud service window to get the latest version.

    But please note that you should not generally enforce subscription expiration on your client unless the subscription entitles the user to content that is bundled as part of your App. It's pretty straight forward to change the date on a device, for instance.

    If you are offering content delivered by a server in some way, like multiplayer or DLC, the server is the place to put the validation.
     
    nicholasr likes this.
  11. Deathfate

    Deathfate

    Joined:
    Sep 10, 2012
    Posts:
    46
    Thank you so much, tested it and works flawlessly.

    Thanks again :)
     
  12. VoxStudio

    VoxStudio

    Joined:
    Jan 12, 2016
    Posts:
    16
    The expiration date works fine now here too!!! Thx guys!

    I have another question related to the cancellation date field. When debugging it I got "
    cancellationDate: 01/01/0001 00:00:00". I did NOT cancelled the subscription, so I guess this is the default value when no cancellation action was taken, and when the subscription is cancelled then this date will have a correct date value. Am I correct? I just want to be sure of that...
     
  13. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    Correct this field defaults to DateTime.MinValue if not cancelled. See Apple's docs here.
     
    nicholasr and VoxStudio like this.
  14. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Hi, I'm having some trouble with the Auto-Renew Subscription purchaseProcessed function. Everything appears to be working properly except when I make a purchase, the purchaseProcessed function does not seem to be called, however every time I launch the app on a test account that has previously purchased the subscription (active or expired,) the purchaseProcessed function is called and the purchase is reported successful and the receipt is up to date. I'm not sure why this is being called when the app launches but its actually pretty convenient. The problem now, is for one that I down know why this is happening this way, and two that when the user initially purchases the subscription, I get no feedback from apple and there for the app does not know how to proceed.

    Here is my purchasing code Specific:

    Code (CSharp):
    1.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    2.     {
    3.         // A consumable product has been purchased by this user.
    4.         if (String.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
    5.         {
    6.             Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));// The consumable item has been successfully purchased, add 100 coins to the player's in-game score.
    7.         }
    8.         // Or ... a non-consumable product has been purchased by this user.
    9.         else if (String.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
    10.         {
    11.             Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));// TODO: The non-consumable item has been successfully purchased, grant this item to the player.
    12.         }
    13.         // Or ... a subscription product has been purchased by this user.
    14.         else if (String.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
    15.         {
    16.             debugObj.GetComponent<Text>().text = "Subscription Purchase was Successful!";
    17.             Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));// TODO: The subscription item has been successfully purchased, grant this to the player.
    18.             InstantiateDebugText(DebugInfoPanel, string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
    19.             CheckIfSubscriptionIsActive ();
    20.         }
    21.         // Or ... an unknown product has been purchased by this user. Fill in additional products here....
    22.         else
    23.         {
    24.            
    25.             Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));}
    26.         InstantiateDebugText (DebugInfoPanel, string.Format ("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
    27.         // Return a flag indicating whether this product has completely been received, or if the application needs
    28.         // to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still
    29.         // saving purchased products to the cloud, and when that save is delayed.
    30.         return PurchaseProcessingResult.Complete;
    31.     }
    32.  
    33.     public void CheckIfSubscriptionIsActive(){
    34.         ConfigurationBuilder builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    35.         // Get a reference to IAppleConfiguration during IAP initialization.
    36.         IAppleConfiguration appleConfig = builder.Configure<IAppleConfiguration>();
    37.         if (!string.IsNullOrEmpty (appleConfig.appReceipt)) {
    38.             Debug.Log (appleConfig.appReceipt);
    39. //            InstantiateDebugText (DebugInfoPanel, "APP Receipt Base64 " + appleConfig.appReceipt);
    40.             var receiptData = System.Convert.FromBase64String (appleConfig.appReceipt);
    41. //            InstantiateDebugText (DebugInfoPanel, "receipt Data "+ receiptData);
    42.             AppleReceipt receipt = new AppleValidator (AppleTangle.Data ()).Validate (receiptData);
    43.             InstantiateDebugText (DebugInfoPanel, "Apple receipt " + receipt);
    44.             foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts) {
    45.                 Debug.Log ("PRODUCTID: " + productReceipt.productID);
    46.                 Debug.Log ("PURCHASE DATE: " + productReceipt.purchaseDate);
    47.                 Debug.Log ("EXPIRATION DATE: " + productReceipt.subscriptionExpirationDate);
    48.                 Debug.Log ("CANCELDATE DATE: " + productReceipt.cancellationDate);
    49.  
    50.                 InstantiateDebugText (DebugInfoPanel, "PRODUCTID: " + productReceipt.productID);
    51.                 InstantiateDebugText (DebugInfoPanel, "PURCHASE DATE: " + productReceipt.purchaseDate);
    52.                 InstantiateDebugText (DebugInfoPanel, "EXPIRATION DATE: " + productReceipt.subscriptionExpirationDate);
    53.                 InstantiateDebugText (DebugInfoPanel, "CANCELDATE DATE: " + productReceipt.cancellationDate);
    54.             }
    55.         }
    56.     }
    and Generic:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Purchasing;
    5. using UnityEngine.UI;
    6.  
    7. // Placing the Purchaser class in the CompleteProject namespace allows it to interact with ScoreManager,
    8. // one of the existing Survival Shooter scripts.
    9.  
    10.     // Deriving the Purchaser class from IStoreListener enables it to receive messages from Unity Purchasing.
    11. using UnityEngine.Purchasing.Security;
    12. using System.Collections;
    13.  
    14.  
    15. public class IAPurchaser : MonoBehaviour, IStoreListener
    16. {
    17.     public GameObject DebugText;
    18.     public GameObject debugObj;
    19.     public GameObject DebugInfoPanel;
    20.     private static IStoreController m_StoreController;          // The Unity Purchasing system.
    21.     private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.
    22.  
    23.     // Product identifiers for all products capable of being purchased:
    24.     // "convenience" general identifiers for use with Purchasing, and their store-specific identifier
    25.     // counterparts for use with and outside of Unity Purchasing. Define store-specific identifiers
    26.     // also on each platform's publisher dashboard (iTunes Connect, Google Play Developer Console, etc.)
    27.  
    28.     // General product identifiers for the consumable, non-consumable, and subscription products.
    29.     // Use these handles in the code to reference which product to purchase. Also use these values
    30.     // when defining the Product Identifiers on the store. Except, for illustration purposes, the
    31.     // kProductIDSubscription - it has custom Apple and Google identifiers. We declare their store-
    32.     // specific mapping to Unity Purchasing's AddProduct, below.
    33.     public static string kProductIDConsumable =    "consumable";  
    34.     public static string kProductIDNonConsumable = "nonconsumable";
    35.     public static string kProductIDSubscription =  "subscription";
    36.  
    37.     // Apple App Store-specific product identifier for the subscription product.
    38.     private static string kProductNameAppleSubscription =  "com.RobotnikGameLab.iHypnosis.fullSubscription";
    39.  
    40.     // Google Play Store-specific product identifier subscription product.
    41.     private static string kProductNameGooglePlaySubscription =  "com.unity3d.subscription.original";
    42.  
    43.     void Start()
    44.     {
    45.         // If we haven't set up the Unity Purchasing reference
    46.         if (m_StoreController == null)
    47.         {
    48.             // Begin to configure our connection to Purchasing
    49.             InitializePurchasing();
    50.         }
    51.     }
    52.  
    53.     public void InitializePurchasing()
    54.     {
    55.         // If we have already connected to Purchasing ...
    56.         if (IsInitialized())
    57.         {
    58.             // ... we are done here.
    59.             return;
    60.         }
    61.  
    62.         // Create a builder, first passing in a suite of Unity provided stores.
    63.         var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    64.  
    65.         // Add a product to sell / restore by way of its identifier, associating the general identifier
    66.         // with its store-specific identifiers.
    67.         builder.AddProduct(kProductIDConsumable, ProductType.Consumable);
    68.         // Continue adding the non-consumable product.
    69.         builder.AddProduct(kProductIDNonConsumable, ProductType.NonConsumable);
    70.         // And finish adding the subscription product. Notice this uses store-specific IDs, illustrating
    71.         // if the Product ID was configured differently between Apple and Google stores. Also note that
    72.         // one uses the general kProductIDSubscription handle inside the game - the store-specific IDs
    73.         // must only be referenced here.
    74.         builder.AddProduct(kProductIDSubscription, ProductType.Subscription, new IDs(){{ kProductNameAppleSubscription, AppleAppStore.Name },{ kProductNameGooglePlaySubscription, GooglePlay.Name },});
    75.  
    76.         // Kick off the remainder of the set-up with an asynchrounous call, passing the configuration
    77.         // and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
    78.         UnityPurchasing.Initialize(this, builder);
    79.     }
    80.  
    81.  
    82.     private bool IsInitialized()
    83.     {
    84.         // Only say we are initialized if both the Purchasing references are set.
    85.         return m_StoreController != null && m_StoreExtensionProvider != null;
    86.     }
    87.  
    88.  
    89.     public void BuyConsumable()
    90.     {
    91.         // Buy the consumable product using its general identifier. Expect a response either
    92.         // through ProcessPurchase or OnPurchaseFailed asynchronously.
    93.         BuyProductID(kProductIDConsumable);
    94.     }
    95.  
    96.  
    97.     public void BuyNonConsumable()
    98.     {
    99.         // Buy the non-consumable product using its general identifier. Expect a response either
    100.         // through ProcessPurchase or OnPurchaseFailed asynchronously.
    101.         BuyProductID(kProductIDNonConsumable);
    102.     }
    103.  
    104.  
    105.     public void BuySubscription()
    106.     {
    107.         // Buy the subscription product using its the general identifier. Expect a response either
    108.         // through ProcessPurchase or OnPurchaseFailed asynchronously.
    109.         // Notice how we use the general product identifier in spite of this ID being mapped to
    110.         // custom store-specific identifiers above.
    111.         BuyProductID(kProductIDSubscription);
    112.     }
    113.  
    114.  
    115.     void BuyProductID(string productId)
    116.     {
    117.         // If Purchasing has been initialized ...
    118.         if (IsInitialized())
    119.         {
    120.             // ... look up the Product reference with the general product identifier and the Purchasing
    121.             // system's products collection.
    122.             Product product = m_StoreController.products.WithID(productId);
    123.  
    124.             // If the look up found a product for this device's store and that product is ready to be sold ...
    125.             if (product != null && product.availableToPurchase)
    126.             {
    127.                 Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));// ... buy the product. Expect a response either through ProcessPurchase or OnPurchaseFailed
    128.                 // asynchronously.
    129.                 m_StoreController.InitiatePurchase(product);
    130.             }
    131.             // Otherwise ...
    132.             else
    133.             {
    134.                 // ... report the product look-up failure situation
    135.                 Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
    136.             }
    137.         }
    138.         // Otherwise ...
    139.         else
    140.         {
    141.             // ... report the fact Purchasing has not succeeded initializing yet. Consider waiting longer or
    142.             // retrying initiailization.
    143.             Debug.Log("BuyProductID FAIL. Not initialized.");
    144.         }
    145.     }
    146.  
    147.  
    148.     // Restore purchases previously made by this customer. Some platforms automatically restore purchases, like Google.
    149.     // Apple currently requires explicit purchase restoration for IAP, conditionally displaying a password prompt.
    150.     public void RestorePurchases()
    151.     {
    152.         // If Purchasing has not yet been set up ...
    153.         if (!IsInitialized())
    154.         {
    155.             // ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
    156.             Debug.Log("RestorePurchases FAIL. Not initialized.");
    157.             return;
    158.         }
    159.  
    160.         // If we are running on an Apple device ...
    161.         if (Application.platform == RuntimePlatform.IPhonePlayer ||
    162.             Application.platform == RuntimePlatform.OSXPlayer)
    163.         {
    164.             // ... begin restoring purchases
    165.             Debug.Log("RestorePurchases started ...");
    166.  
    167.             // Fetch the Apple store-specific subsystem.
    168.             var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
    169.             // Begin the asynchronous process of restoring purchases. Expect a confirmation response in
    170.             // the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
    171.             apple.RestoreTransactions((result) => {
    172.                 // The first phase of restoration. If no more responses are received on ProcessPurchase then
    173.                 // no purchases are available to be restored.
    174.                 Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
    175.             });
    176.         }
    177.         // Otherwise ...
    178.         else
    179.         {
    180.             // We are not running on an Apple device. No work is necessary to restore purchases.
    181.             Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
    182.         }
    183.     }
    184.  
    185.  
    186.     //
    187.     // --- IStoreListener
    188.     //
    189.  
    190.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    191.     {
    192.         // Purchasing has succeeded initializing. Collect our Purchasing references.
    193.         Debug.Log("OnInitialized: PASS");
    194.  
    195.         // Overall Purchasing system, configured with products for this application.
    196.         m_StoreController = controller;
    197.         // Store specific subsystem, for accessing device-specific store features.
    198.         m_StoreExtensionProvider = extensions;
    199.     }
    200.  
    201.  
    202.     public void OnInitializeFailed(InitializationFailureReason error)
    203.     {
    204.         // Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
    205.         Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    206.     }
    207.  
    208.  
    209.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    210.     {
    211.         // A consumable product has been purchased by this user.
    212.         if (String.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
    213.         {
    214.             Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));// The consumable item has been successfully purchased, add 100 coins to the player's in-game score.
    215.         }
    216.         // Or ... a non-consumable product has been purchased by this user.
    217.         else if (String.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
    218.         {
    219.             Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));// TODO: The non-consumable item has been successfully purchased, grant this item to the player.
    220.         }
    221.         // Or ... a subscription product has been purchased by this user.
    222.         else if (String.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
    223.         {
    224.             debugObj.GetComponent<Text>().text = "Subscription Purchase was Successful!";
    225.             Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));// TODO: The subscription item has been successfully purchased, grant this to the player.
    226.             InstantiateDebugText(DebugInfoPanel, string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
    227.             CheckIfSubscriptionIsActive ();
    228.         }
    229.         // Or ... an unknown product has been purchased by this user. Fill in additional products here....
    230.         else
    231.         {
    232.            
    233.             Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));}
    234.         InstantiateDebugText (DebugInfoPanel, string.Format ("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
    235.         // Return a flag indicating whether this product has completely been received, or if the application needs
    236.         // to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still
    237.         // saving purchased products to the cloud, and when that save is delayed.
    238.         return PurchaseProcessingResult.Complete;
    239.     }
    240.  
    241.     public void CheckIfSubscriptionIsActive(){
    242.         ConfigurationBuilder builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    243.         // Get a reference to IAppleConfiguration during IAP initialization.
    244.         IAppleConfiguration appleConfig = builder.Configure<IAppleConfiguration>();
    245.         if (!string.IsNullOrEmpty (appleConfig.appReceipt)) {
    246.             Debug.Log (appleConfig.appReceipt);
    247. //            InstantiateDebugText (DebugInfoPanel, "APP Receipt Base64 " + appleConfig.appReceipt);
    248.             var receiptData = System.Convert.FromBase64String (appleConfig.appReceipt);
    249. //            InstantiateDebugText (DebugInfoPanel, "receipt Data "+ receiptData);
    250.             AppleReceipt receipt = new AppleValidator (AppleTangle.Data ()).Validate (receiptData);
    251.             InstantiateDebugText (DebugInfoPanel, "Apple receipt " + receipt);
    252.             foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts) {
    253.                 Debug.Log ("PRODUCTID: " + productReceipt.productID);
    254.                 Debug.Log ("PURCHASE DATE: " + productReceipt.purchaseDate);
    255.                 Debug.Log ("EXPIRATION DATE: " + productReceipt.subscriptionExpirationDate);
    256.                 Debug.Log ("CANCELDATE DATE: " + productReceipt.cancellationDate);
    257.  
    258.                 InstantiateDebugText (DebugInfoPanel, "PRODUCTID: " + productReceipt.productID);
    259.                 InstantiateDebugText (DebugInfoPanel, "PURCHASE DATE: " + productReceipt.purchaseDate);
    260.                 InstantiateDebugText (DebugInfoPanel, "EXPIRATION DATE: " + productReceipt.subscriptionExpirationDate);
    261.                 InstantiateDebugText (DebugInfoPanel, "CANCELDATE DATE: " + productReceipt.cancellationDate);
    262.             }
    263.         }
    264.     }
    265.  
    266.     public void InstantiateDebugText(GameObject DebugGrid, string debugMessage){
    267.         GameObject debugText = Instantiate (DebugText, Vector3.zero, Quaternion.identity) as GameObject;
    268.         debugText.transform.SetParent (DebugGrid.transform);
    269.         debugText.GetComponent<Text> ().text = debugMessage;
    270.         debugText.GetComponent<RectTransform> ().localPosition = new Vector3 (0, 0, 0);
    271.         debugText.GetComponent<RectTransform> ().localScale = new Vector3 (1, 1, 1);
    272.     }
    273.  
    274.     public void ClearDebugText(){
    275.         for(int i = 0; i < DebugInfoPanel.transform.childCount; i++){
    276.             Destroy (DebugInfoPanel.transform.GetChild(i).gameObject);
    277.         }
    278.     }
    279.  
    280.  
    281.     public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    282.     {
    283.         debugObj.GetComponent<Text> ().text = "Subscription Purchase Failed";
    284.         // A product purchase attempt did not succeed. Check failureReason for more detail. Consider sharing
    285.         // this reason with the user to guide their troubleshooting actions.
    286.         Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));}
    287. }
     
  15. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    Please attach the logs from XCode from when you run your app to when you make the purchase.

    Note that on Apple's sandbox subscription periods are artificially shortened to facilitate testing. Each renewal will generate a ProcessPurchase event.

    You should use the App receipt to determine whether or not a user is entitled to a subscription, validating and parsing it either locally or on your server.
     
  16. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Im not sure how to do this Banderous. I was under the impression that the only way to properly test IAP in my app is to upload a new archived build to iTunes Connect and then download it on my phone through internal or external test flight (I'm using internal currently.) How do I access the logs through this process or will it only generate the logs if I'm running the app directly from Xcode onto my iPhone?

    Access to the logs would be fantastic for troubleshooting!

    Thanks B!
     
  17. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Alright, I believe I found what you are referring to. Let me know if I shouldn't post this info for all to see and ill edit it out. This is the log from when I first launch the app:

    Jul 13 12:06:33 Ian-Robinsons-iPhone SpringBoard[58] <Error>: SecTrustEvaluate [leaf IssuerCommonName SubjectCommonName]
    Jul 13 12:06:33 Ian-Robinsons-iPhone SpringBoard[58] <Error>: SecTrustEvaluate [leaf IssuerCommonName SubjectCommonName]
    Jul 13 12:06:33 Ian-Robinsons-iPhone kernel[0] <Notice>: xpcproxy[587] Container: /private/var/mobile/Containers/Data/Application/19E37165-0E77-4F56-80AD-526069548FBA (sandbox)
    Jul 13 12:06:33 Ian-Robinsons-iPhone com.apple.xpc.launchd[1] <Error>: assertion failed: 13F69: launchd + 116796 [9F6284CF-8A17-36CC-9DB5-85D510A21F14]: 0x3
    Jul 13 12:06:33 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: -> registered mono modules 0x100dca6e0
    Jul 13 12:06:33 Ian-Robinsons-iPhone locationd[64] <Notice>: Gesture EnabledForTopClient: 1 (SiriCalled)
    Jul 13 12:06:33 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
    Jul 13 12:06:34 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: UnityIAP:Requesting 3 products
    Jul 13 12:06:34 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: UnityIAP:Requesting product data...
    Jul 13 12:06:34 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: UnityIAP:Received 1 products
    Jul 13 12:06:34 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: UnityIAP:addTransactionObserver
    Jul 13 12:06:34 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: UnityIAP:UpdatedTransactions
    Jul 13 12:06:35 Ian-Robinsons-iPhone iHypnosis[587] <Warning>: UnityIAP:Finishing transaction 1000000223276451

    And this is the log from when I make the purchase:

    Jul 13 12:11:40 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDAccount: no account
    Jul 13 12:11:40 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDBootAccount: no account (null)
    Jul 13 12:11:40 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDPIMAccount: no account (null)
    Jul 13 12:11:40 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDAlwaysOnAccount: no account (null)
    Jul 13 12:12:09 Ian-Robinsons-iPhone iHypnosis[591] <Warning>: UnityIAP:purchaseProduct: com.RobotnikGameLab.iHypnosis.fullSubscription
    Jul 13 12:12:09 Ian-Robinsons-iPhone iHypnosis[591] <Warning>: UnityIAP:UpdatedTransactions
    Jul 13 12:12:31 Ian-Robinsons-iPhone akd[116] <Warning>: success
    Jul 13 12:12:32 Ian-Robinsons-iPhone akd[116] <Warning>: success
    Jul 13 12:12:32 Ian-Robinsons-iPhone accountsd[93] <Warning>: AIDA Notification plugin running
    Jul 13 12:12:32 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDAccount: no account
    Jul 13 12:12:32 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDBootAccount: no account (null)
    Jul 13 12:12:32 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDPIMAccount: no account (null)
    Jul 13 12:12:32 Ian-Robinsons-iPhone syncdefaultsd[595] <Notice>: (Note ) SYDAlwaysOnAccount: no account (null)
    Jul 13 12:12:45 Ian-Robinsons-iPhone iHypnosis[591] <Warning>: UnityIAP:UpdatedTransactions
     
  18. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    Are you attempting to purchase the same subscription again that you already own?
     
  19. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    No, that log is from a purchase attempt initiated after the previous purchase expired. I've attempted on multiple test accounts and get the same behavior even if it's the very first time that account has attempted the purchase.

    This is driving me nuts.
     
  20. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    So I uninstalled the app, created a new test account, signed out of my Apple ID on my phone, reinstalled the app, launched it, navigated to purchase button, initiated purchase, signed in with new Apple ID, confirmed purchase, nothing. Close app, relaunch the app, and sure enough ProcessPurchase is called and my app receipt data is displayed as its supposed to after confirming purchase. I just don't understand, am I missing something obvious or is Apples Sandbox environment this messed up?

    Here is the log straight through starting app for first time after reinstall to making purchase:

    Jul 14 04:54:17 Ian-Robinsons-iPhone SpringBoard[58] <Error>: SecTrustEvaluate [leaf IssuerCommonName SubjectCommonName]
    Jul 14 04:54:18 Ian-Robinsons-iPhone SpringBoard[58] <Error>: SecTrustEvaluate [leaf IssuerCommonName SubjectCommonName]
    Jul 14 04:54:18 Ian-Robinsons-iPhone kernel[0] <Notice>: xpcproxy[1355] Container: /private/var/mobile/Containers/Data/Application/05F78211-ED09-4A8A-84A5-D071E55E2A1B (sandbox)
    Jul 14 04:54:18 Ian-Robinsons-iPhone com.apple.xpc.launchd[1] <Error>: assertion failed: 13F69: launchd + 116796 [9F6284CF-8A17-36CC-9DB5-85D510A21F14]: 0x3
    Jul 14 04:54:18 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: -> registered mono modules 0x100db2550
    Jul 14 04:54:18 Ian-Robinsons-iPhone locationd[64] <Notice>: Gesture EnabledForTopClient: 1 (SiriCalled)
    Jul 14 04:54:18 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:Requesting 3 products
    Jul 14 04:54:18 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:Requesting product data...
    Jul 14 04:54:18 Ian-Robinsons-iPhone locationd[64] <Notice>: Gesture EnabledForTopClient: 1 (SiriCalled)
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:Received 1 products
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:No App Receipt found
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:addTransactionObserver
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:UpdatedTransactions
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:No App Receipt found
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:No App Receipt found
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:No App Receipt found
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:No App Receipt found
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:No App Receipt found
    Jul 14 04:54:19 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:Finishing transaction 1000000223467709
    Jul 14 04:54:26 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDAccount: no account
    Jul 14 04:54:26 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDBootAccount: no account (null)
    Jul 14 04:54:26 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDPIMAccount: no account (null)
    Jul 14 04:54:26 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDAlwaysOnAccount: no account (null)
    Jul 14 04:55:05 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: You are using download over http. Currently unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon. Please consider updating to https.
    Jul 14 04:55:06 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: You are using download over http. Currently unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon. Please consider updating to https.
    Jul 14 04:55:07 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: You are using download over http. Currently unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon. Please consider updating to https.
    Jul 14 04:55:07 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: You are using download over http. Currently unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon. Please consider updating to https.
    Jul 14 04:55:16 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:purchaseProduct: com.RobotnikGameLab.iHypnosis.fullSubscription
    Jul 14 04:55:16 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:UpdatedTransactions
    Jul 14 04:55:16 Ian-Robinsons-iPhone itunescloudd[141] <Warning>: Updating media purchase history for initial load...
    Jul 14 04:55:48 Ian-Robinsons-iPhone akd[116] <Warning>: success
    Jul 14 04:55:49 Ian-Robinsons-iPhone akd[116] <Warning>: success
    Jul 14 04:55:49 Ian-Robinsons-iPhone accountsd[93] <Warning>: AIDA Notification plugin running
    Jul 14 04:55:49 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDAccount: no account
    Jul 14 04:55:49 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDBootAccount: no account (null)
    Jul 14 04:55:49 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDPIMAccount: no account (null)
    Jul 14 04:55:49 Ian-Robinsons-iPhone syncdefaultsd[1357] <Notice>: (Note ) SYDAlwaysOnAccount: no account (null)
    Jul 14 04:55:49 Ian-Robinsons-iPhone itunescloudd[141] <Warning>: Updating media purchase history for initial load...
    Jul 14 04:55:49 Ian-Robinsons-iPhone itunescloudd[141] <Warning>: Updating media purchase history for initial load...
    Jul 14 04:56:02 Ian-Robinsons-iPhone iHypnosis[1355] <Warning>: UnityIAP:UpdatedTransactions
    Jul 14 04:56:26 Ian-Robinsons-iPhone syncdefaultsd[1359] <Notice>: (Note ) SYDAccount: no account
    Jul 14 04:56:26 Ian-Robinsons-iPhone syncdefaultsd[1359] <Notice>: (Note ) SYDBootAccount: no account (null)
    Jul 14 04:56:26 Ian-Robinsons-iPhone syncdefaultsd[1359] <Notice>: (Note ) SYDPIMAccount: no account (null)
    Jul 14 04:56:26 Ian-Robinsons-iPhone syncdefaultsd[1359] <Notice>: (Note ) SYDAlwaysOnAccount: no account (null)
    Jul 14 04:56:43 Ian-Robinsons-iPhone SpringBoard[58] <Warning>: BSXPCMessage received error for message: Connection interrupted
    Jul 14 04:56:43 Ian-Robinsons-iPhone mediaserverd[28] <Notice>: '' com.RobotnikGameLab.iHypnosis(pid = 1355) setting DiscoveryMode = DiscoveryMode_None, currentDiscoveryMode = DiscoveryMode_None
    Jul 14 04:56:43 Ian-Robinsons-iPhone SpringBoard[58] <Warning>: Application 'UIKitApplication:com.RobotnikGameLab.iHypnosis[0x9ac]' exited voluntarily.
    Jul 14 04:56:43 Ian-Robinsons-iPhone UserEventAgent[26] <Warning>: 1399777688729: id=com.RobotnikGameLab.iHypnosis pid=1355, state=0
    Jul 14 04:56:50 Ian-Robinsons-iPhone locationd[64] <Notice>: Gesture EnabledForTopClient: 1 (SiriCalled)
    Jul 14 04:56:50 Ian-Robinsons-iPhone locationd[64] <Notice>: Gesture EnabledForTopClient: 1 (SiriCalled)



    Im going to bed now, if anyone can suggest which of the errors in the logs need the most attention and may be effecting Auto Renew IAP, ill focus on your suggestions in the morning. Thanks!
     
  21. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    @erika_@Banderous

    Thanks guys! This is now working properly, The problem was that I combined two scripts and didn't implement them properly. Both scripts included code that instantiate builders so my app was instantiating two builders. I also reimport my IAP plugin through the services window in the unity editor.

    Thanks again guys!
     
    erika_d likes this.
Thread Status:
Not open for further replies.