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

URGENT: iOS Receipt not being parsed in live version only

Discussion in 'Unity IAP' started by NSBryant, Oct 8, 2018.

  1. NSBryant

    NSBryant

    Joined:
    Jul 16, 2017
    Posts:
    8
    Hello,

    We've just updated our app from a paid app to a free app with an in-app upgrade to the full version. We set up the game to grant access to users who previously purchased the game, but this is not working in the live version of the game.

    We are validating the purchase of the full version of the game using receipt validation and checking for original_application_version, and letting the user bypass the IAP if this shows a version less than 2.7.0 (the free version). In a sandbox environment (both TestFlight and from Xcode), this shows 1.0, which I understand is correct for all sandbox environments. Thus, I figured that the code was correct.

    Unfortunately, our code does NOT get the correct original_application_version in our live build. I'm not sure if this is because we are parsing the version number incorrectly (I have not been able to find any documentation on exactly what this string should look like), or if we're parsing the receipt incorrectly (we are using the Unity IAP plugin, and once again the receipt is parsed correctly in a test environment), or if the receipt is not getting refreshed for some reason. This is the SAME build that I've tried on TestFlight. So currently, our legacy customers have to buy the IAP to play the game, essentially forcing them to buy the game twice.

    Since I can't see Debug.Log from our live version, I don't really have a way of debugging this.

    Our code is more or less as follows:


    public IEnumerator Start()
    {
    while(!CodelessIAPStoreListener.initializationComplete)
    {
    yield return null;
    }
    CodelessIAPStoreListener.iosExtensionProvider.GetExtension().RefreshAppReceipt(receipt =>
    {
    byte[] receiptData = System.Convert.FromBase64String(receiptString);
    AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);

    //_boughtVersion is what displays on the options menu.
    //This System.Version value is parsed from the original_application_version string.
    //The default value is '0.0.0', which is what shows up in the live build, while '1.0' shows up in the same build on TestFlight.
    _boughtVersion = new Version(receipt.originalApplicationVersion);
    });
    }


    Any thoughts on why this is happening?
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    When you updated your app, did you use the same package name? That is, is it the same game as previously on TestFlight? We have no control of what is in the Apple receipt. When users reinstall and Restore products, do they get a ProcessPurchase for this product? If so, then there is no need to check the receipt as you know they purchased previously. If they are purchasing again, it is likely a brand new receipt. Why would you believe a new receipt would have the previous purchase information?
     
  3. NSBryant

    NSBryant

    Joined:
    Jul 16, 2017
    Posts:
    8
    I'm trying to find out if the user paid for the original game, which is not an in-app purchase.

    We've released five versions of our app:
    1.0 - $4.99
    2.6.3 - $4.99
    2.6.4 - $4.99
    2.7.0 - Free, but blocks the user from continuing after a certain point until the purchase a separate IAP.
    2.7.1 - Also free with IAP

    So the bundle identifier (package name?) is the same: com.NightSchool.Oxenfree.

    And yes: the build in TestFlight is the exact same build that I submitted and is on the store now.

    I understand that you don't have control over what's in the receipt, but Apple refuses to work with me on my support ticket just because I'm using Unity and not Xcode. I'm working on finding a dev there that can help directly, but also figure that this could be an issue with how I implemented my Unity code, or a bug in Unity.

    Note that the actual IAP mechanism works just fine. If the user buys the IAP, the game unlocks properly. The problem I'm having is allowing the user to bypass this IAP check if they already paid for, say, version 2.6.3. That is what I understand "original_application_version" refers to.

    So just to be clear about what's currently happening:
    1. "_boughtVersion" is getting set on the sandbox (TestFlight and Xcode) build
    2. "_boughtVersion" is NOT getting set in the live version (same build, different environment)

    And to be clear about where the problem potentially lies:
    1. It could be that the System.Version constructor is throwing an exception because "original_application_version" is not in the expected format
    2. It could be that no receipt is found

    Hope this helps.
     
  4. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Sorry, what do you mean by expected format, is this in your code? IAP has no information on previous app purchases, only In App Purchasing. If you are parsing a receipt expecting something other than IAP purchases, we would not be able to support this.
     
  5. NSBryant

    NSBryant

    Joined:
    Jul 16, 2017
    Posts:
    8
    There are two classes mentioned in the Unity Docs (although there is no actual script reference that I can find, frustratingly):

    • AppleReceipt
    • AppleInAppPurchaseReceipt

    AppleReceipt contains an array of AppleInAppPurchaseReceipts. The AppleReceipt has fields consistent with the AppleReceipt described on this Apple Developer page:

    https://developer.apple.com/library...teAppStoreReceipt/Chapters/ReceiptFields.html

    This is the documentation on the Unity side I'm referring to (the two sections at the bottom, specifically):

    https://docs.unity3d.com/Manual/UnityIAPValidatingReceipts.html

    ...and the function I'm using to refresh the receipt is here:

    https://docs.unity3d.com/Manual/UnityIAPStoreExtensions.html

    So in Apple's case, it looks like we do have information on the purchase as a whole (it sounds like Google Play, by the way, does not have an equivalent type; only GooglePlayReceipt). The field I am specifically needing to use this information is "Original Application Version", which is shown in one of those Unity links. This is the value that I can't find a format for, and getting an answer from Apple is proving difficult. I'd think that my game's version would show up as '2.7.1', but I can't confirm it because it always returns '1.0' in a test environment (once again, Unity does correctly return '1.0').

    If this value is not for the application itself, what is it for? I've seen Apple developer posts about this parameter being added to the receipt to facilitate premium-to-freemium transitions, so I'm pretty sure I'm not totally off here.
     
  6. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You would want to reach out to Apple. We do not handle application purchase transactions, even if the info may be present in the Apple receipt.
     
  7. NSBryant

    NSBryant

    Joined:
    Jul 16, 2017
    Posts:
    8
    So it looks like it is indeed an Apple issue, and while this functionality is documented, the sandbox functionality makes this value misleading.

    For anyone else who runs into this problem:

    Note that "original_application_version" refers to the CFBundleVersion at the time of purchase or app download. This is the build number, not the application version. Thus, it should be a single integer, such as 1, 2, 100, etc (the number that's in parentheses when you archive your build). The behavior of this value in a sandbox environment, though, is to only show "1.0". This is a pretty alarming discrepancy between test and live environments, in my opinion.

    I realized this when I came across this thread, which references an old tech guideline that apparently doesn't exist any more: https://forums.developer.apple.com/thread/88574

    Apparently this guideline described how to use the build number: the build number should increase each time a new build is made, and reset to 1 when the version number increases. This, clearly, is in opposition to how "original_application_version" is apparently meant to be used: to check to see if the user bought an old version of the game in case of a premium-to-freemium transition. This is because, under this guideline, this number is not a unique identifier. Maybe this is why that guideline no longer exists, but it's a subtle difference that threw us off based on how we've always done things.

    The solution? If you are making this transition, you can increase your build number higher than any other versions you've released--we are using "100"--and check against that to see if the user bought your paid version or downloaded a free IAP-enabled version.

    Luckily, our previously released versions all line up in a way that all of our old versions have a build number of "1", and all of our releases with IAP functionality were released with something other than "1". If that wasn't the case, I think we would have been out of luck.

    Hope that helps. Thanks for your help, Jeff!
     
    edwardrowe and JonBFS like this.
  8. NSBryant

    NSBryant

    Joined:
    Jul 16, 2017
    Posts:
    8
    I'll quickly add that after additional research, CFBundleVersion can indeed be a dotted version, which I was not aware of. I don't think I'm alone because the forum post I linked to above shows another person who was confused by this, and potentially an old guideline that suggested that the build number should be an integer. Thus, moving forward, I intend to show the long-form build version in CFBundleVersion (for example, CFBundleShortVersionString is 2.7.0, and CFBundleVersion is 2.7.0.1 or 2.7.0f1).

    More importantly, Apple does not force CFBundleVersion to be unique, so I advise enforcing that yourself. I've responded to Apple suggesting that this should be forced to be unique in App Store Connect, but I don't expect any action on this (there may be a good reason I'm missing).
     
  9. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    Hey, So im trying the same thing and having a similar issue. In sandbox mode it works fine but once it goes live the receipt refresh fails. Do I need to have the in-app purchases live on the app store for this to work?
     
  10. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Do you get an error? Can you elaborate what you mean by "live on the app store"? Yes, your products need to be defined and available.
     
  11. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    This is the error I get when I try to refresh and parse the app receipt:


    [B]2019-05-30 09:54:43.678658+0100 santatrackerpro[5377:1421417] UnityIAP: RefreshReceipt status 0[/B]

    [B]2019-05-30 09:54:45.686427+0100 santatrackerpro[5377:1421417] UnityIAP: No App Receipt found[/B]

    [B]ArgumentException: Failed to load data.[/B]

    [B] at LipingShare.LCLib.Asn1Processor.Asn1Parser.LoadData (System.IO.Stream stream) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.Purchasing.Security.AppleReceiptParser.Parse (System.Byte[] receiptData, UnityEngine.Purchasing.Security.PKCS7& receipt) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.Purchasing.Security.AppleValidator.Validate (System.Byte[] receiptData) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at StoreTest.CheckReceipt () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.Events.UnityAction.Invoke () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.Events.InvokableCall.Invoke () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.UI.Button.Press () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1].Invoke (T1 handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at InControl.InControlInputModule.ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, System.Boolean pressed, System.Boolean released) [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at InControl.InControlInputModule.ProcessTouchEvents () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at InControl.InControlInputModule.Process () [0x00000] in <00000000000000000000000000000000>:0 [/B]

    [B] at UnityEngine.EventSystems.EventSystem.Update () [0x00000] in <00000000000000000000000000000000>:0 [/B]




    This only happens on accounts that are not added as testers in iTunes Connect. This is what happens when using an account that is also added as a Testflight tester:


    [B]2019-05-30 09:58:48.190311+0100 santatrackerpro[5394:1424797] UnityIAP: Refresh app receipt[/B]

    [B]2019-05-30 09:58:49.878144+0100 santatrackerpro[5394:1424797] UnityIAP: RefreshReceipt status 1[/B]

    [B]ORIGINAL APP VERSION: 1.0[/B]

    [B]UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)[/B]

    [B]UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])[/B]

    [B]UnityEngine.Logger:Log(LogType, Object)[/B]

    [B]UnityEngine.Debug:Log(Object)[/B]

    [B]<>c:<RefreshReceipts>b__11_0(String)[/B]

    [B]System.Action`1:Invoke(T)[/B]

    [B]UnityEngine.Purchasing.AppleStoreImpl:OnAppReceiptRetrieved(String)[/B]

    [B]UnityEngine.Purchasing.AppleStoreImpl:ProcessMessage(String, String, String, String)[/B]

    [B]UnityEngine.Purchasing.<>c__DisplayClass36_0:<MessageCallback>b__0()[/B]

    [B]System.Action:Invoke()[/B]

    [B]UnityEngine.Purchasing.Extension.UnityUtil:Update()[/B]



    [B](Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 48)[/B]


    [B]ORIGINAL PURCHASE DATE: 30/05/2019 08:58:49[/B]

    [B]UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)[/B]

    [B]UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])[/B]

    [B]UnityEngine.Logger:Log(LogType, Object)[/B]

    [B]UnityEngine.Debug:Log(Object)[/B]

    [B]<>c:<RefreshReceipts>b__11_0(String)[/B]

    [B]System.Action`1:Invoke(T)[/B]

    [B]UnityEngine.Purchasing.AppleStoreImpl:OnAppReceiptRetrieved(String)[/B]

    [B]UnityEngine.Purchasing.AppleStoreImpl:ProcessMessage(String, String, String, String)[/B]

    [B]UnityEngine.Purchasing.<>c__DisplayClass36_0:<MessageCallback>b__0()[/B]

    [B]System.Action:Invoke()[/B]

    [B]UnityEngine.Purchasing.Extension.UnityUtil:Update()[/B]



    [B](Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 48)[/B]


    [B]APP VERSION: 4[/B]

    [B]UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)[/B]

    [B]UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])[/B]

    [B]UnityEngine.Logger:Log(LogType, Object)[/B]

    [B]UnityEngine.Debug:Log(Object)[/B]

    [B]<>c:<RefreshReceipts>b__11_0(String)[/B]

    [B]System.Action`1:Invoke(T)[/B]

    [B]UnityEngine.Purchasing.AppleStoreImpl:OnAppReceiptRetrieved(String)[/B]

    [B]UnityEngine.Purchasing.AppleStoreImpl:ProcessMessage(String, String, String, String)[/B]

    [B]UnityEngine.Purchasing.<>c__DisplayClass36_0:<MessageCallback>b__0()[/B]

    [B]System.Action:Invoke()[/B]

    [B]UnityEngine.Purchasing.Extension.UnityUtil:Update()[/B]



    [B](Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 48)[/B]




    I figured that because I'm trying to access the app receipt rather than the in-app purchase receipt it should work regardless of whether in-app purchases are available, but this doesn't seem to be the case. I'm just trying to test if app receipt validation would work for a transition from a paid app to free with in-app purchase...
     
  12. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, you'll see "No App Receipt Found" if you look at the logs. You'll want to check for that.
     
  13. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    So is there any way to check the app receipt without in-app purchases being live on the App Store?
     
  14. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What do you mean live in the app store? If the user already made a purchase, whether in TestFlight or the App Store, the receipt will be available.
     
  15. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    So the app receipt is different from the in-app purchase receipt. However the app receipt only seems to be available when theres in-app purchases Ready for Sale in iTunes Connect (or in Sandbox mode). This is the code Im using to access the app receipt:

    Code (CSharp):
    1.         extensions.GetExtension<IAppleExtensions>().RefreshAppReceipt(receiptString =>
    2.  
    3.         {
    4.             byte[] receiptData = System.Convert.FromBase64String(receiptString);
    5.  
    6.             AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);
    7.  
    8.             Debug.Log("ORIGINAL APP VERSION: " + receipt.originalApplicationVersion);
    9.             Debug.Log("ORIGINAL PURCHASE DATE: " + receipt.receiptCreationDate);
    10.             Debug.Log("APP VERSION: " + receipt.appVersion);
    11.  
    12.  
    13.  
    14.  
    15.         }, () =>
    16.         {
    17.      
    18.         });
     
  16. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    As expected. Why would you want to call it otherwise? Perhaps I am missing something.
     
  17. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    I want to test that app receipt validation works as expected before implementing a store front and changing the app from paid to free. I need to know the original purchased version for each user so I can unlock content appropriately for users who have paid for the app rather than downloaded it for free.

    What if I wanted to check the app receipt is valid as an anti-piracy measure? Is there no way to do that without in-app purchases being live?
     
  18. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What do you mean by live? Unity IAP only supports In-App Purchases, not app sales, if that is what you are referring to. As the name implies. Naturally to implement IAP, you need the products configured on the Apple Dashboard, whether in TestFlight or in a released app. Is there any reason you haven't configured your products yet? IAP won't work without them.
     
  19. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    Yeah I guess that makes sense. UnityIAP does expose the app sales receipt, I guess the catch 22 is that you have to have in-app purchases available. Is there no way to initialise UnityIAP without products?

    I have the in-app products configured, and they work in Sandbox mode. When I say live I mean passed Apple review. Apple won't pass them through the review without being able to access them. So I'm in this situation where I can't really test a before launching...
     
  20. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    So you are testing with a user that has made an IAP purchase already (or is able to make a purchase, after successful IAP initialization)? You seem to be providing conflicting information. First you ask "Is there no way to initialise UnityIAP without products?" then say "I have the in-app products configured, and they work in Sandbox mode." Everything with Unity IAP that you can do before or after the Apple Review process should also work in their Sandbox if you have IAP properly configured. If you are seeing differences, you would want to contact Apple. And yes, you need to have products configured properly for IAP to initialize. Also, you said earlier "transition from a paid app to free", are you planning on refunding their app purchase?
     
    Last edited: May 30, 2019
  21. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    Sorry Im not explaining myself well. Thank you for sticking with me :) I am trying to verify that the app receipt that Unity receives has the correct data within it. When used in Sandbox mode the field receipt.originalApplicationVersion always returns the value 1.0, which is expected behavior and is described in Apple's own documentation. The entire process of moving the app from paid to free relies on this information, so if it is wrong the move cannot be done.

    I cannot add in-app purchases until I can verify this works. I cannot test it in Sandbox as it always returns the value 1.0. I cannot check it on a live build because Apple will not approve an app that has in-app purchases within the metadata but not within the app.

    I am asking if there is a way to initialize UnityIAP without products because if I can do this, I can access the app receipt without needing to add in-app purchases to the metadata on the App Store.

    I am not planning on refunding the previous users, I want to unlock the content that will be behind the paywall in this updated free version, as they have already paid for it.
     
  22. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Unity IAP requires the products to be already configured and available in the store, regardless of our understanding. However I don't understand why you cannot just create the products on the store. Products are not added on a "per user" basis. Once configured and added to the store, they are (potentially, and up to you as the developer) available to all users. And once active in the store, they won't be visible and available to the users until you expose them, within your app. So create the products, allow users to launch your app regardless if they have paid for it not. Initialize IAP during app launch, and check the app receipt to confirm. If they paid for it, give the user the products. If not, then display the Purchase buttons. However, as stated, IAP requires products to be available to initialize.
     
  23. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    Did you ever figure this out? I'm in the same position with 1.0 as the Original App Version. There are a whole lotta ways this could go pair shaped if I can't test that out.
     
  24. matimark

    matimark

    Joined:
    Jun 1, 2015
    Posts:
    5
    Really old thread but it is worth a shot.

    My question is about the original_application_version always returning 1.0 in a test build.
    Is it correct for me to think that I actually have to publish my game to the official App Store in order to see if this value changes? Will it always return 1.0 when downloading the game from Testflight?

    It makes me very nervous obviously that I cannot test this in Testflight.

    Thank you