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

Question IAP Local Receipt validation on iOS working?

Discussion in 'Unity IAP' started by pistoleta, Jun 1, 2022.

  1. pistoleta

    pistoleta

    Joined:
    Sep 14, 2017
    Posts:
    539
    Hello, on the last release we decided to implement the Local receipt validation as it is provided in the examples of the IAP 4.1.5 for our iOS game.

    However, I still see in the database some 'suspicious' purchases, that im afraid are not legitimate.
    Also I've set reports to tell me when someone is trying to fraud but still didn't get anything so I'm starting to wonder if maybe I've missed something in the setup.

    Note: all our purchases are non-consumable, not sure if is worth to mention.

    I'll paste my code by parts:

    Code (CSharp):
    1.  
    2. void InitializeValidator()
    3.         {
    4.             LogMgr.Print(LogsConfigSO.LogElem.purchases,"Initialize validator");
    5.             if (IsCurrentStoreSupportedByValidator())
    6.             {
    7.                 LogMgr.Print(LogsConfigSO.LogElem.purchases,"Supported validator");
    8.                 try
    9.                 {
    10.                     m_Validator = new CrossPlatformValidator(null,AppleTangle.Data(),"com.myappbundleId.id");
    11.                 }
    12.                 catch (Exception e)
    13.                 {
    14.                     LogMgr.PrintError(LogsConfigSO.LogElem.purchases,"Not Supported validator");
    15.                 }
    16.             }
    17.             else
    18.             {
    19.                 LogMgr.PrintError(LogsConfigSO.LogElem.purchases, "Current store not supported by validator "+StandardPurchasingModule.Instance().appStore);
    20.             }
    21.         }
    22.  

    Code (CSharp):
    1.  
    2. static bool IsCurrentStoreSupportedByValidator()
    3. {
    4.     if (!ServiceLocator.Instance.GetService<ConfigManager>().myRemoteConfig.fraudControl) return true;
    5.     //The CrossPlatform validator only supports the GooglePlayStore and Apple's App Stores.
    6.     return IsGooglePlayStoreSelected() || IsAppleAppStoreSelected();
    7. }
    8. static bool IsGooglePlayStoreSelected()
    9. {
    10.     var currentAppStore = StandardPurchasingModule.Instance().appStore;
    11.     return currentAppStore == AppStore.GooglePlay;
    12. }
    13.  
    14. static bool IsAppleAppStoreSelected()
    15. {
    16.     var currentAppStore = StandardPurchasingModule.Instance().appStore;
    17.     return currentAppStore == AppStore.AppleAppStore ||
    18.            currentAppStore == AppStore.MacAppStore;
    19. }
    20.  

    Code (CSharp):
    1.  
    2. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    3. {
    4.     if (!IsPurchaseValid(args.purchasedProduct))
    5.     {
    6.         LogMgr.PrintError(LogsConfigSO.LogElem.purchases, "Fraud attempt");
    7.         Utils.ExitAppInSeconds(2);
    8.         return PurchaseProcessingResult.Pending;
    9.     }
    10. .....
    11. ....
    12. }
    13.  
    14. bool IsPurchaseValid(Product product)
    15. {
    16.     if (IsCurrentStoreSupportedByValidator())
    17.     {
    18.         try
    19.         {
    20.             var result = m_Validator.Validate(product.receipt);
    21.         }
    22.         catch (IAPSecurityException reason)
    23.         {
    24.             LogMgr.PrintError(LogsConfigSO.LogElem.purchases,$"Invalid receipt: {reason}");
    25.             return false;
    26.         }
    27.     }
    28.     else
    29.     {
    30.         LogMgr.PrintError(LogsConfigSO.LogElem.purchases,$"Validator not supported");
    31.         return false;
    32.     }
    33.  
    34.     return true;
    35. }
    36.  
    37. public CrossPlatformValidator(byte[] googlePublicKey, byte[] appleRootCert, string appBundleId)
    38. {
    39.     throw new NotImplementedException();
    40. }
    41. public IPurchaseReceipt[] Validate(string unityIAPReceipt)
    42. {
    43.     throw new NotImplementedException();
    44. }
    45.  

    The not implemented methods in CrossPlatformValidator need to be implemented or they should work out of the box?

    Btw legit purchases keep working with this setup, is just I didnt receive any report of fraud which is quite suspicious, because I've seen suspicious transaction numbers. Do you see something missing?

    Thanks a lot in advance.
    Juan
     
    Last edited: Jun 1, 2022
  2. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,256
    Hi Juan, you will not want to implement the CrossPlatformValidator constructor and Validate method yourself and should remove it. When keeping them, it looks like your validation part will not do something at all.

    Other notes:
    - when passing in the bundle identifier in new CrossPlatformValidator, you could use Application.identifier instead of hardcoding it, so you do not forget to change it if the bundle id changes at some point
    - if validation fails, you can still return PurchaseProcessingResult.Complete so the transaction is not validated an endless amount of times again when restarting the app. Returning pending will leave it open forever, however it will eventually time out after a few days, at least on Google Play.

    Note: local receipt validation is not 100% secure. Unity IAP's local validation only checks the incoming receipt for the matching bundle identifier and valid receipt - not its contents. Users can share receipts with each other to unlock the same content, use receipts multiple times (in case of consumables) or simply use a receipt hacking app that generates a receipt.

    If you would like to have absolutely safe IAPs, you would have to use server-side validation. If you are interested in an asset extensions for this, please check out this one.
     
    JackTruong likes this.