Search Unity

Modifying IAPButton with Receipt validation

Discussion in 'Unity IAP' started by swifter14, Jul 22, 2019.

  1. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    I use a codeless IAP.
    I attached the IAP button script to my gameobject, and inserted a private function to call after On Purchase Complete.
    Everything works fine, but now I want to modify it so I have receipt validation.
    1. First thing I did was to move
    Code (CSharp):
    1.  onPurchaseComplete.Invoke(e.purchasedProduct);
    in IAPButton.cs to the end- after I confirm that validation is true, and after I check that product ID is correct. So only then "On Purchase Complete" will start. Is it OK?
    2. Unity says "you should make decisions based on the product IDs"
    So I added 2 code lines-
    Code (CSharp):
    1.   if (productReceipt.productID!= productId)
    2.                     { validPurchase = false; }
    My question is, is my code correct now?
    Will it fail if validation is not right (and won't start "On Purchase Complete") and succeed when valid? It's hard to test it since I only test it on editor

    My code in IAPButton (not sure if it's right):
    Code (CSharp):
    1. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    2.         {
    3.             Debug.Log(string.Format("BB IAPButton.ProcessPurchase(PurchaseEventArgs {0} - {1})", e,
    4.                 e.purchasedProduct.definition.id));
    5.  
    6.             //NEW CODE:
    7.  
    8.             bool validPurchase = true; // Presume valid for platforms with no R.V.
    9.               // Unity IAP's validation logic is only included on these platforms.
    10. #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    11.             // Prepare the validator with the secrets we prepared in the Editor
    12.             // obfuscation window.
    13.             var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    14.                 AppleTangle.Data(), Application.identifier);
    15.        
    16.             try
    17.             {
    18.                 // On Google Play, result has a single product ID.
    19.                 // On Apple stores, receipts contain multiple products.
    20.                 var result = validator.Validate(e.purchasedProduct.receipt);
    21.                 // For informational purposes, we list the receipt(s)
    22.                 Debug.Log("Receipt is valid. Contents:");
    23.                 foreach (IPurchaseReceipt productReceipt in result)
    24.                 {
    25.                     Debug.Log(productReceipt.productID);
    26.                     Debug.Log(productReceipt.purchaseDate);
    27.                     Debug.Log(productReceipt.transactionID);
    28.  
    29.                     if (productReceipt.productID!= productId)
    30.                     { validPurchase = false; }
    31.  
    32.                         GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    33.                     if (null != google)
    34.                     {
    35.                         // This is Google's Order ID.
    36.                         // Note that it is null when testing in the sandbox
    37.                         // because Google's sandbox does not provide Order IDs.
    38.                         Debug.Log(google.transactionID);
    39.                         Debug.Log(google.purchaseState);
    40.                         Debug.Log(google.purchaseToken);
    41.                     }
    42.  
    43.                     AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    44.                     if (null != apple)
    45.                     {
    46.                         Debug.Log(apple.originalTransactionIdentifier);
    47.                         Debug.Log(apple.subscriptionExpirationDate);
    48.                         Debug.Log(apple.cancellationDate);
    49.                         Debug.Log(apple.quantity);
    50.                     }
    51.  
    52.  
    53.                     var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    54.                     // Get a reference to IAppleConfiguration during IAP initialization.
    55.                     var appleConfig = builder.Configure<IAppleConfiguration>();
    56.                     var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
    57.                     AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);
    58.  
    59.                     Debug.Log(receipt.bundleID);
    60.                     Debug.Log(receipt.receiptCreationDate);
    61.                     foreach (AppleInAppPurchaseReceipt productReceipt2 in receipt.inAppPurchaseReceipts)
    62.                     {
    63.                         Debug.Log(productReceipt2.originalTransactionIdentifier);
    64.                         Debug.Log(productReceipt2.productID);
    65.                     }
    66.  
    67.  
    68.  
    69.                 }
    70.  
    71.             }
    72.             catch (IAPSecurityException)
    73.             {
    74.                 Debug.Log("Invalid receipt, not unlocking content");
    75.                 validPurchase = false;
    76.             }
    77. #endif
    78.  
    79.             if (validPurchase)
    80.             {
    81.                    onPurchaseComplete.Invoke(e.purchasedProduct);
    82.                 // Unlock the appropriate content here.
    83.             }
    84.                 return PurchaseProcessingResult.Complete;
    85.  
    86.             Debug.Log("111");
    87.             //END NEW CODE
    88.  
    89.             return (consumePurchase) ? PurchaseProcessingResult.Complete : PurchaseProcessingResult.Pending;
    90.         }
    91.  
    92.         /**
     
    Last edited: Jul 23, 2019
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Does this code compile? I don't see where you are setting the value of productId anywhere. Also, I'm not sure where we say "you should make decisions based on the product IDs". At any rate, I would not recommend that you change the code as you suggest. I would also not recommend codeless IAP for you, but use scripting instead.
     
  3. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    It's the IAPButton class. Do you know it?
    1. Yes this code compile
    2. The productID is declared in the class, you choose it on the editor, inside the IAP button script component.
    3. "Also, I'm not sure where we say ". Check Unity's documentation https://docs.unity3d.com/Manual/UnityIAPValidatingReceipts.html
    "it is important you check not just that the receipt is valid, but also what information it contains. A common technique by users attempting to access content without purchase is to supply receipts from other products or applications. These receipts are genuine and do pass validation, so you should make decisions based on the product IDs parsed by the CrossPlatformValidator."
    4. To say I don't recommend codeless IAP is not an answer because there's nothing wrong with it, codeless IAP uses code anyway, and that's what I'm trying to edit
     
  4. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    No problem, I will ask the same question again with scripting
     
  5. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please look at IAPDemo.cs, it uses scripting and has this implemented already.
     
  6. JoshExterna

    JoshExterna

    Joined:
    Apr 26, 2019
    Posts:
    2
    i dont get why we have codeless if apple requires validation which takes more scripting. so basically dont use codelss for apple because you'll have to script for validation anyways. right?
     
  7. richardzzzarnold

    richardzzzarnold

    Joined:
    Aug 2, 2012
    Posts:
    140
    I couldn't agree more. I think both the unity ads and the UNITY IAP are a goddamned mess. Instead of just one way to do things there are multiple ways. None of which seem to work.
    Why for gods sake would you make and release a codeless IAP system if it doesn't work!
    FFS.

    Heres an idea Unity Team, why not just make the codeless IAP system function properly. Just a thought.
     
  8. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I'm not certain that Apple requires receipt validation? I believe that is for you as the developer to guard against purchase fraud. You can certainly edit the Codeless scripts to handle this customization, but at that point, it would probably be easier to move to scripted IAP.
     
  9. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, this would the correct approach to edit the IAPButton class, I had assumed you were adding this code on button click.
     
  10. richardzzzarnold

    richardzzzarnold

    Joined:
    Aug 2, 2012
    Posts:
    140
    Whenever i submit to the AppStore now the reviewers require receipt validation. Its possible that this is related to autorenewing subscription.

    It seems that if you are purchasing consumable/non-consumable products, then codeless IAP is sufficient, but if you are purchasing subscriptions then codelessIAP will not work. Not sure why this is not just clearly explained in Unity docs.
     
  11. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, it will work, but you need to modify IAPButton.cs. We will look to fix this.
     
  12. shahin2019

    shahin2019

    Joined:
    Mar 17, 2019
    Posts:
    19
    this code does not working for me on google play in unity 2019.1.9, why?
    Code (CSharp):
    1.  
    2.     public class ShopManager2 : MonoBehaviour, IStoreListener
    3.     {
    4.  
    5.         public static ShopManager2 Instance{ set; get;}
    6.         private static IStoreController m_StoreController;          // The Unity Purchasing system.
    7.         private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.
    8.     public static string PRODUCT_30_level = "com.dezi.waronfrozenland2.ss2222";
    9.     public static string PRODUCT_Removeadss = "com.dezi.waronfrozenland2.xxxxxxx";
    10.         private void Awake(){
    11.  
    12.             Instance = this;
    13.         }
    14.  
    15.         private    void Start()
    16.         {
    17.             // If we haven't set up the Unity Purchasing reference
    18.             if (m_StoreController == null)
    19.             {
    20.                 // Begin to configure our connection to Purchasing
    21.                 InitializePurchasing();
    22.             }
    23.         }
    24.  
    25.         public void InitializePurchasing()
    26.         {
    27.             // If we have already connected to Purchasing ...
    28.             if (IsInitialized())
    29.             {
    30.                 return;
    31.             }
    32.  
    33.            
    34.             var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    35.  
    36.         builder.AddProduct(PRODUCT_30_level, ProductType.Consumable);
    37.         builder.AddProduct(PRODUCT_Removeadss, ProductType.Consumable);
    38.  
    39.             UnityPurchasing.Initialize(this, builder);
    40.         }
    41.  
    42.  
    43.         private bool IsInitialized()
    44.         {
    45.             return m_StoreController != null && m_StoreExtensionProvider != null;
    46.         }
    47.  
    48.         public void levelss09_30_level()
    49.         {
    50.         BuyProductID(PRODUCT_30_level);
    51.         }
    52.  
    53.     public void removeadsss()
    54.     {
    55.         BuyProductID(PRODUCT_Removeadss);
    56.     }
    57.  
    58.  
    59.         private    void BuyProductID(string productId)
    60.         {
    61.             if (IsInitialized())
    62.             {
    63.            
    64.                 Product product = m_StoreController.products.WithID(productId);
    65.  
    66.                 if (product != null && product.availableToPurchase)
    67.                 {
    68.                     Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
    69.                     m_StoreController.InitiatePurchase(product);
    70.                 }
    71.                 else
    72.                 {
    73.                     Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
    74.                 }
    75.             }
    76.             else
    77.             {
    78.                
    79.                 Debug.Log("BuyProductID FAIL. Not initialized.");
    80.             }
    81.         }
    82.  
    83.  
    84.         public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    85.         {
    86.             Debug.Log("OnInitialized: PASS");
    87.  
    88.             m_StoreController = controller;
    89.             m_StoreExtensionProvider = extensions;
    90.         }
    91.  
    92.  
    93.         public void OnInitializeFailed(InitializationFailureReason error)
    94.         {
    95.             Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    96.         }
    97.  
    98.  
    99.         public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    100.         {
    101.  
    102.         if (String.Equals(args.purchasedProduct.definition.id,PRODUCT_30_level, StringComparison.Ordinal))
    103.             {
    104.                 shopping.buy30 =5000;
    105.  
    106.     }
    107.  
    108.         else if (String.Equals(args.purchasedProduct.definition.id,PRODUCT_Removeadss, StringComparison.Ordinal))
    109.         {
    110.             buy_Remove_Ads.buy_ads =5000;
    111.  
    112.     }
    113.  
    114.             else
    115.             {
    116.                 Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
    117.             }
    118.  
    119.             return PurchaseProcessingResult.Complete;
    120.         }
    121.  
    122.  
    123.         public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    124.         {
    125.         Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    126.         }
    127.     }
    128.  
    129.  
    130.  
    131.  
    you can see my game ion google play for checking :
    https://play.google.com/store/apps/details?id=com.dezi.waronfrozenland2
    please what is wrong in this code answer me
     
    Last edited: Apr 9, 2020
  13. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    This thread refers to Codeless IAP, you are using scripting. Please open a new topic. When you open the new thread, please attach the device logs, they are essential for debugging https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/