Search Unity

[Closed] RestorePurchase on iOS not return ProcessPurchase callback

Discussion in 'Unity IAP' started by Loharatanavisit, Mar 16, 2016.

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

    Loharatanavisit

    Joined:
    Oct 6, 2015
    Posts:
    8
    This happen on iOS with Unity 5.3.3f1

    When I try to buy product, call storeController(product); And confirm to buy product

    The message popup appear below

    "This In-App purchase has already been bought. It will be restore for free"

    After I press confirm to restore, nothing happen, there is no callback (That usually have to send back to ProcessPurchase(PurchaseEventArgs args) like android) and not even any error log appear.

    I try calling RestorePurchase() method like this

    Code (CSharp):
    1.  // Restore purchases previously made by this customer. Some platforms automatically restore purchases. Apple currently requires explicit purchase restoration for IAP.
    2.         public void RestorePurchases()
    3.         {
    4.             // If Purchasing has not yet been set up ...
    5.             if (!IsInitialized())
    6.             {
    7.                 // ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
    8.                 Debug.Log("RestorePurchases FAIL. Not initialized.");
    9.                 return;
    10.             }
    11.            
    12.             // If we are running on an Apple device ...
    13.             if (Application.platform == RuntimePlatform.IPhonePlayer ||
    14.                 Application.platform == RuntimePlatform.OSXPlayer)
    15.             {
    16.                 // ... begin restoring purchases
    17.                 Debug.Log("RestorePurchases started ...");
    18.                
    19.                 // Fetch the Apple store-specific subsystem.
    20.                 var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
    21.                 // Begin the asynchronous process of restoring purchases. Expect a confirmation response in the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
    22.                 apple.RestoreTransactions((result) => {
    23.                     // The first phase of restoration. If no more responses are received on ProcessPurchase then no purchases are available to be restored.
    24.                     Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
    25.                 });
    26.             }
    27.             // Otherwise ...
    28.             else
    29.             {
    30.                 // We are not running on an Apple device. No work is necessary to restore purchases.
    31.                 Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
    32.             }
    33.         }
    The result boolean is true... but nothing happens after that, no any callback too

    Is this a bug or did I miss something? It's work fine on Android as it will automatically restore by calling ProcessPurchase with the purchased receipt.
     
  2. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    If you are not getting a callback it is likely that your Application has already processed that purchase and confirmed it. You can check this by looking at the XCode log, which will show 'already recorded transaction'. If you uninstall and reinstall the app, then restore, you should get the transaction.

    If you want to see what a user owns you should update to the latest store packages and use our new receipt validation and parsing library.
     
    Loharatanavisit likes this.
  3. Loharatanavisit

    Loharatanavisit

    Joined:
    Oct 6, 2015
    Posts:
    8
    @Banderous

    Sorry for the late respond, for now we still cannot get the ProcessPurchase callback from restoring transaction for iOS.

    Right now the shop shows the all the Consumable products to be purchased correctly from iTunes (around 5-6 ProductIDs).

    Sorry, that I didn't mention that they were Consumable products

    And according to this link http://docs.unity3d.com/Manual/UnityIAPiOSMAS.html

    We try the following to RestoreTansaction, the callback of this function return "True" , but there is nothing return to the ProcessPurchase or OnPurchaseFailed.

    Reinstalling application is surely not the solution as we always rebuild the code when testing.

    And there are also not any 'already recorded transaction' text appear in XCode log.

    So I'm not sure that is the RestoreTransaction function is unable to Consumable product or not?

    (We also try to get the appreceipt from RefreshAppReceipt function, it's show the appreceipt but we cant get that which transaction it belongs to.)

    When trying to purchase the purchase directly (If it wasn't struck at Pending, the callback ProcessPurchase working correctly.)

    But if it is in pending status, the result still show confirm dialog with same message

    "This In-App purchase has already been bought. It will be restore for free"

    And when confirm, there is no any callback return. Absolutely nothing in XCode log (except active screen method after dialog gone.)

    Do you still have any ideas can suggest? Or is it some kind of bugs?.

    We awaits for your feedback, please help and thanks in advanced!

    :confused::confused::confused::confused:

    Ps.We didn't use Validator in your reference library as we validate the receipt through server and wait callback to ConfirmPendingProduct. So I have no idea to get what user owns with that.
     
  4. Loharatanavisit

    Loharatanavisit

    Joined:
    Oct 6, 2015
    Posts:
    8
    Or is there anyway to get which Consumable product is in Pending status?, as they can only get hasReceipt of Non Consumable and Subscription only. So that we can implement restoring purchase function by ourselves.
     
  5. Loharatanavisit

    Loharatanavisit

    Joined:
    Oct 6, 2015
    Posts:
    8
    *Update

    Now we able to get TransactionID, productId from iOS appReceipt and be able to send the neccesary data to confirm with the server but

    The problem is that once we try to call

    controller.ConfirmPendingPurchase(controller.products.WithID(productId));

    It's appear to be an error with cString null;

    Uncaught exception: NSInvalidArgumentException: *** +[NSString stringWithUTF8String:]: NULL cString

    Is the reason that it's got null error is because we try to confirmPendingProduct directly from controller product list?, because that mostly that I search, it's usually confirm within ProcessPurchase (PurchaseEventArgs args) with args.purchasedProduct

    And, is the args.purchasedProduct have different attributes from controller.products.WithID(productId) so that it cannot be confirmed?
     
  6. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    RestoreTransactions is not able to restore Consumable products. Only non consumables can be restored, you need to implement your own backup and restore if you want to preserve consumable purchases.
     
  7. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    I have the same problem.

    on iOS (tested on 9.3.1):
    1. purchase a consumable product
    2. in the ProcessPurchase callback, I start receipt validation (via server) and return PurchaseProcessingResult.Pending
    3. before validation completes (and call to ConfirmPendingPurchase), quit the app
    4. relaunch the app
    expected result:
    • after Initialize, ProcessPurchase is called again with the pending product, so I can re-validate the receipt and ConfirmPendingPurchase
    actual result:
    • ProcessPurchase is never called
    • if I purchase again the same product, iOS displays the "already bought" dialog, and when I return to the app neither ProcessPurchase nor OnPurchaseFailed is called. nothing is logged
    I updated to the latest UnityPurchasing package (1.5.0)
     
  8. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    @M_R Are you instantiating multiple ConfigurationBuilders? Otherwise, please attach an xcode trace from when you restart the app and reinitialise Unity IAP.
     
  9. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    no I use the singleton pattern on my store listener to ensure only one exists, and do its stuff in the constructor. I use multiple PurchasingModule though
    I'll send it to you via MP

    the strange thing is, I tried to create an empty project to try to reproduce the bug, built it with the same bundleId, and it restored my product instead. but in my main project it is still stuck without callbacks (on that specific id, other products work) and ios ask for itunes password at startup, before I even call iap init.
    what I did:
    1. bug report attempt said it restored the product
    2. reinstalled main app (attached to xcode)
    3. purchased bugged product
    4. app stuck forever waiting for callback, ios said thanks for purchase [sandbox]
    5. stopped app from xcode
    6. restarted app from xcode
    7. purchased normal product, it worked
    8. purchased bugged product
    9. ios said already bought, no callback fired
     
  10. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    The problem is with a specific product/user account combination?

    If you create a new itunes test user can you buy the product without issue?
     
  11. Nicolas1212

    Nicolas1212

    Joined:
    Dec 18, 2014
    Posts:
    139
  12. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    @Banderous with another user I managed to purchase the product, but iOS prompted me for the old user password at startup

    I forgot to tell that I delay IAP initialization after asset bundle download (to fetch products and associated coins I should give) and the password prompt is before I call Initialize (all this worked when I first implemented UnityIAP in January with 5.3.1)
     
  13. Nicolas1212

    Nicolas1212

    Joined:
    Dec 18, 2014
    Posts:
    139
    Just to add to @M_R's reply - in our case, initialisation is immediate with hardcoded IDs; we just hold off processing in ProcessPurchase() if we haven't connected/received our products from our server yet.

    I also get prompted for the password, but judging by the logs it doesn't seem to hold anything up. UnityPurchasing.m sends a message to unity with the subject "OnProductsRetrieved", the apple products as json payload, and the receipt containing the unfinished transaction as the receipt (verified by decoding the base64'd binary). See http://forum.unity3d.com/threads/un...e-only-after-app-restart.403671/#post-2638145 to have an Xcode log of the startup process.

    I've built a stand-alone with nothing but UnityIAP in it and it seems to work, so what could cause a callback being delayed/not called? Apart from UnityPurchasing.m, Unity IAP is a black box, so it's hard to debug what's going on inside it
     
  14. Nicolas1212

    Nicolas1212

    Joined:
    Dec 18, 2014
    Posts:
    139
    If I run the UnityIAP standalone project, with the same bundle ID so it picks up on the blocked transaction, the transaction passes and Unity is notified almost before the popup to enter your password to connect to the iTunes Store is shown, so I'm not even sure what the point of that one is
     
  15. Nicolas1212

    Nicolas1212

    Joined:
    Dec 18, 2014
    Posts:
    139
    Sorry, I tell a lie. If I run the UnityIAP standalone project, I see that I have a transaction waiting, but no callback is ever called (see attached log)
     

    Attached Files:

  16. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    adding again:
    • the same account in another device works
    • another account in the same device works
    • logging in again with the problematic account/device deadlocks
    • when asked for password, actually logging in or pressing cancel does no difference
    • in the empty/IAP project, the password prompt happens after ProcessPurchase call, and pressing cancel restores the product anyways
    • if I Debug.Log(builder.Configure<IAppleConfiguration>().appReceipt); i have an actual receipt containing the pending product
     
  17. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    @M_R I have occasionally observed problematic device/account combinations in the Apple sandbox which have resolved themselves after several days. I suggest you move to a new sandbox test user.
     
  18. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    @Banderous our app is live and we received a report from one user that is affected by this problem. I hope he isn't using a sandbox account :p

    EDIT: i replicated the problem with the other test account (purchase -> kill app -> open app -> purchase again says restored and no callback)
     
    Last edited: May 18, 2016
  19. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    I suggest you file a TSI with Apple.
     
  20. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    with
    Code (csharp):
    1.  
    2. -(void) addTransactionObserver {
    3.     [[SKPaymentQueuedefaultQueue] addTransactionObserver:self];
    4.     NSLog(@"Debug UnityIAP: force update transactions");
    5.     [selfpaymentQueue:[SKPaymentQueuedefaultQueue] updatedTransactions:[SKPaymentQueuedefaultQueue].transactions];
    6. }
    7.  
    I managed to get the ProcessPurchase after init, but I don't know if it's safe for the normal case
     
    nicholasr likes this.
  21. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    logging the transaction count in paymentQueue:updatedTransactions and addTransactionObserver i found that
    • in the empty app i get 0 transactions in addTransactionObserver and then ios immediately fires a paymentQueue:updatedTransactions with the pending payment
    • in the main app i get 1 transaction already added in addTransactionObserver, so no event is fired
      • if I force the update, it is notified by unity after (more than?) a frame, so I cannot distinguish anymore the case "init complete, no purchases pending" from "init complete with x purchases pending", so I have to change my app flow
     
  22. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    Unity IAP adds a single transaction observer during initialisation and leaves it in place for the lifetime of the process.

    If storekit is not notifying that transaction observer, as it appears from the log you sent, then that is an issue to take up with Apple. Unity IAP cannot add an additional transaction observer on the chance that storekit will send it transactions that it should already be receiving.
     
  23. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    I am not adding another observer. i simply force a paymentQueue:updatedTransactions: when unityIAP adds itself to the SKPaymentQueue.
     
  24. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    Is it possible that another plugin in your project would be adding a transaction observer? You mentioned previously that a transaction already exists when Unity IAP adds its transaction observer. SKPaymentQueue.transactions should be undefined until the first observer is added.
     
  25. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    that what I was investigating right now. I do have native plugins in my project but some of them are compiled (.h + .a files) ant the ones for which I have the source do not add observers (except for reign native, which we used for wp8 before unityIAP and was the first thing I commented out and replaced with nslogs)

    I managed to set a symbolic breakpoint in xcode for "-[SKPaymentQueue addTransactionObserver:]" and it hits before our splash screen, with this stack trace
    Code (csharp):
    1.  
    2. (lldb) bt
    3.  
    4. * thread #1: tid = 0xbcbade, 0x000000018d177294 StoreKit`-[SKPaymentQueue addTransactionObserver:], queue = 'com.apple.main-thread', activity = 'starting resolver activity', 2 messages, stop reason = breakpoint 1.1
    5.  
    6.   * frame #0: 0x000000018d177294 StoreKit`-[SKPaymentQueue addTransactionObserver:]
    7.  
    8.     frame #1: 0x000000010131472c BurracoOnline`___lldb_unnamed_function2935$$BurracoOnline + 84
    9.  
    10.     frame #2: 0x00000001012e7b20 BurracoOnline`___lldb_unnamed_function1610$$BurracoOnline + 264
    11.  
    12.     frame #3: 0x00000001012e746c BurracoOnline`___lldb_unnamed_function1602$$BurracoOnline + 112
    13.  
    14.     frame #4: 0x0000000182d59558 Foundation`-[__NSObserver _doit:] + 308
    15.  
    16.     frame #5: 0x0000000182410eac CoreFoundation`__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
    17.  
    18.     frame #6: 0x00000001824106cc CoreFoundation`_CFXRegistrationPost + 396
    19.  
    20.     frame #7: 0x000000018241044c CoreFoundation`___CFXNotificationPost_block_invoke + 60
    21.  
    22.     frame #8: 0x0000000182479494 CoreFoundation`-[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1532
    23.  
    24.     frame #9: 0x000000018234e788 CoreFoundation`_CFXNotificationPost + 368
    25.  
    26.     frame #10: 0x0000000182d5689c Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 68
    27.  
    28.     frame #11: 0x000000018785633c UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 3584
    29.  
    30.     frame #12: 0x000000018785a500 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1684
    31.  
    32.     frame #13: 0x0000000187857674 UIKit`-[UIApplication workspaceDidEndTransaction:] + 168
    33.  
    34.     frame #14: 0x0000000183e0f7ac FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
    35.  
    36.     frame #15: 0x0000000183e0f618 FrontBoardServices`-[FBSSerialQueue _performNext] + 168
    37.  
    38.     frame #16: 0x0000000183e0f9c8 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 56
    39.  
    40.     frame #17: 0x0000000182425124 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    41.  
    42.     frame #18: 0x0000000182424bb8 CoreFoundation`__CFRunLoopDoSources0 + 540
    43.  
    44.     frame #19: 0x00000001824228b8 CoreFoundation`__CFRunLoopRun + 724
    45.  
    46.     frame #20: 0x000000018234cd10 CoreFoundation`CFRunLoopRunSpecific + 384
    47.  
    48.     frame #21: 0x000000018761f834 UIKit`-[UIApplication _run] + 460
    49.  
    50.     frame #22: 0x0000000187619f70 UIKit`UIApplicationMain + 204
    51.  
    52.     frame #23: 0x00000001000be15c BurracoOnline`main(argc=<unavailable>, argv=<unavailable>) + 156 at main.mm:32 [opt]
    53.  
    54.     frame #24: 0x0000000181eea8b8 libdyld.dylib`start + 4
    55.  
     
  26. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    It looks like the transaction observer is being added from a listener added to NSNotificationCenter. Can you set a breakpoint to try to catch what is adding the notification listener?

    Alternatively you could binary search through your installed plugins until you find the one that's adding it.
     
  27. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    that seems a better approach than my current "(lldb) p *(id*)($sp)" attempts...
    I'll try to look into NSNotificationCenter stuff.

    what I could use to do the binary search?
     
  28. Banderous

    Banderous

    Joined:
    Dec 25, 2011
    Posts:
    669
    By binary search I meant:

    1. Remove half the plugins
    2. Retest. If the observer is still added it must be one of the plugins you did not remove, otherwise it is in the half you removed.
    3. Resume from step 1 using whichever half of the plugins is known to contain the plugin which adds the transaction observer
     
  29. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    13 hits for -[NSNotificationCenter addObserverForName:eek:bject:queue:usingBlock:]
    146 hits for -[NSNotificationCenter addObserver:selector:name:eek:bject:]

    it seems almost every plugin I have does something with NSNotificationCenter
    then I tried removing StoreKit and let the linker complain...
    (found a rogue Prime31 static constructor doing an extern call into it's libStoreKit.a but I don't reference it anymore and nothing changed when i commented out that line of code - I put a log in there to be sure, it doesn't get called)
     
  30. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    can't do that because they are too much entangled with our code - we were unable to completely remove Prime31 and Reign from the project because it was a mess to refactor (for P31, we still use its "static OnActivityResult" on Android, and Reign is here for WP8 Facebook integration)

    I was hoping for a way to scan the various .a for references to SKPaymentQueue calls...
     
  31. Nicolas1212

    Nicolas1212

    Joined:
    Dec 18, 2014
    Posts:
    139
    I think it's Facebook. I removed Facebook from our app and successfully got notified of a pending purchase on app restart.

    If you're on SDK 3.22 or higher, it's set to automatically log IAP (https://developers.facebook.com/docs/app-events/ios/#purchase), though it's not clear if you need to explicitly call ActivateApp() or if it's done automatically for you on iOS. I guess to do the logging, they're adding a listener, and it's either because they don't do it on Android, or the fact that iOS needs a password to restore transactions which lets them get in there first.

    I'm trying a build right now where IAP logging is turned off in the developers.facebook.com settings to see if that makes a difference, or if I need to jump into the code to stop the ActivateApp() call
     
    erika_d likes this.
  32. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    found the culprit (I hope is the only one)
    Code (csharp):
    1. (lldb) p *(id*)($sp+16)
    2. (GADInAppPurchaseTransactionReporter *) $1 = 0x000000013fab29d0
    apparently admob thinks it should interfere with the payment flow
     
    erika_d likes this.
  33. Nicolas1212

    Nicolas1212

    Joined:
    Dec 18, 2014
    Posts:
    139
    Can confirm that turning off the automatic in-app purchasing on Facebook settings (developers.facebook.com > App > Settings > Basic > iOS > iOS Only: Log In-App Purchase Events Automatically (Recommended)) unblocks unity purchasing notifications. ActivateApp() is called automatically on iOS.

    (@M_R - we're not using AdMob - FB is pretty much the only plugin, aside from iOS Native)

    As to *when* this change propagates to Unity, I'm not sure. After it worked, I reset logging of purchase events through the site, and it continued to work, so I'm not sure if it's order issue (including Unity IAP before Facebook), or a cache issue (FB hadn't propagated the re-change back), etc. At this point, after a week of sitting through half hour builds, I don't really care :)
     
    divillysausages and nicholasr like this.
  34. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    @Nicolas1212 we do have facebook but that setting is off - and it will remain off -

    now I'm trying if there is a similar setting for admob
     
    Nicolas1212 likes this.
  35. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    found it!
    Code (Obj-c):
    1. [GADMobileAds disableAutomatedInAppPurchaseReporting];
    in application:didFinishLaunchingWithOptions:
     
    Last edited: May 19, 2016
  36. nicholasr

    nicholasr

    Joined:
    Aug 15, 2015
    Posts:
    183
    Awesome finds @Nicolas1212 and @M_R. :cool:

    I'm noting these in our FAQ and we're considering whether a code-change could address this "first registered observer gets all the callbacks" issue.
     
    M_R and erika_d like this.
  37. amjaliks

    amjaliks

    Joined:
    Jul 11, 2015
    Posts:
    159
    Is the any way to add this line to Unity project? Or only way is to remember to edit exported Xcode project every time?
     
  38. amjaliks

    amjaliks

    Joined:
    Jul 11, 2015
    Posts:
    159
    I get it. Just need to subclass UnityAppController. :)

    Code (Obj-c):
    1. // AppController.h
    2.  
    3. #import "UnityAppController.h"
    4.  
    5. @interface AppController : UnityAppController
    6. @end
    7.  
    8. IMPL_APP_CONTROLLER_SUBCLASS(AppController)
    9.  
    10.  
    11. // App Controller.mm
    12.  
    13. #import "AppController.h"
    14. #import <GoogleMobileAds/GoogleMobileAds.h>
    15.  
    16. @implementation AppController
    17.  
    18. -(BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
    19. {
    20.     [GADMobileAds disableAutomatedInAppPurchaseReporting];
    21.     return [super application:application willFinishLaunchingWithOptions:launchOptions];
    22. }
    23.  
    24. @end
     
    nicholasr and erika_d like this.
  39. Martin_k

    Martin_k

    Joined:
    Feb 19, 2015
    Posts:
    6
    Hi,

    I have issue with "This In-App purchase has already been bought. It will be restore for free".

    If I buy some consumable product, then verify, but not consume (with ConfirmPendingPurchase), and after that, again try to buy the same product, I have this message, and no any callback is called.
    This is problem, because I disable all buttons and dont have any callback to enable it.

    it is not related with addObserver.

    Any ideas ?
     
  40. ap-unity

    ap-unity

    Unity Technologies

    Joined:
    Aug 3, 2016
    Posts:
    1,519
Thread Status:
Not open for further replies.