Search Unity

Modifying ProcessPurchase with Receipt validation

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

  1. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    I use IAP scripting. With MyIAPManager.cs demo class.
    I want to modify it so I have receipt validation.
    1. Is the right place to verify it is inside ProcessPurchase ()?
    2. In my code (and Unity's documentation) I assume that as long as "Catch" isn't activated, validPurchase stays true, which means only then I can unlock the content, is this OK?
    3. Unity says "you should make decisions based on the product IDs." How and where would you recommend to add this extra check in my code?
    The reason I'm asking is because I can't test it on Unity's editor and I want to make sure you approve this approach.
    Thanks
    Code (CSharp):
    1.   public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    2.         {
    3.           test_product = e.purchasedProduct;
    4.  
    5.         //new code:
    6.         bool validPurchase = true; // Presume valid for platforms with no R.V.
    7.    
    8.         // Unity IAP's validation logic is only included on these platforms.
    9. #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    10.         // Prepare the validator with the secrets we prepared in the Editor
    11.         // obfuscation window.
    12.         var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    13.             AppleTangle.Data(), Application.identifier);
    14.    
    15.         try
    16.         {
    17.             // On Google Play, result has a single product ID.
    18.             // On Apple stores, receipts contain multiple products.
    19.             var result = validator.Validate(e.purchasedProduct.receipt);
    20.             // For informational purposes, we list the receipt(s)
    21.             Debug.Log("Receipt is valid. Contents:");
    22.             foreach (IPurchaseReceipt productReceipt in result)
    23.             {
    24.                 Debug.Log(productReceipt.productID);
    25.                 Debug.Log(productReceipt.purchaseDate);
    26.                 Debug.Log(productReceipt.transactionID);
    27.  
    28.               //  if (productReceipt.productID != productId)
    29.                // { validPurchase = false; }
    30.  
    31.                 GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    32.                 if (null != google)
    33.                 {
    34.                     // This is Google's Order ID.
    35.                     // Note that it is null when testing in the sandbox
    36.                     // because Google's sandbox does not provide Order IDs.
    37.                     Debug.Log(google.transactionID);
    38.                     Debug.Log(google.purchaseState);
    39.                     Debug.Log(google.purchaseToken);
    40.                 }
    41.  
    42.                 AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    43.                 if (null != apple)
    44.                 {
    45.                     Debug.Log(apple.originalTransactionIdentifier);
    46.                     Debug.Log(apple.subscriptionExpirationDate);
    47.                     Debug.Log(apple.cancellationDate);
    48.                     Debug.Log(apple.quantity);
    49.                 }
    50.  
    51.  
    52.                 var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    53.                 // Get a reference to IAppleConfiguration during IAP initialization.
    54.                 var appleConfig = builder.Configure<IAppleConfiguration>();
    55.                 var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
    56.                 AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);
    57.  
    58.                 Debug.Log(receipt.bundleID);
    59.                 Debug.Log(receipt.receiptCreationDate);
    60.                 foreach (AppleInAppPurchaseReceipt productReceipt2 in receipt.inAppPurchaseReceipts)
    61.                 {
    62.                     Debug.Log(productReceipt2.originalTransactionIdentifier);
    63.                     Debug.Log(productReceipt2.productID);
    64.                 }
    65.  
    66.  
    67.  
    68.             }
    69.  
    70.         }
    71.         catch (IAPSecurityException)
    72.         {
    73.             Debug.Log("Invalid receipt, not unlocking content");
    74.             validPurchase = false;
    75.         }
    76.  
    77.         if (validPurchase)
    78.         { Debug.Log("Valid receipt, unlocking content"); }
    79. #endif
    80.  
    81.        //end new code
    82.  
    83.         if (return_complete)
    84.         {
    85.                 Debug.Log(string.Format("ProcessPurchase: Complete. Product:" + e.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
    86.                 return PurchaseProcessingResult.Complete;
    87.             }
    88.             else
    89.         {
    90.    
    91.             Debug.Log(string.Format("ProcessPurchase: Pending. Product:" + e.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
    92.                 return PurchaseProcessingResult.Pending;
    93.             }
    94.  
    95.   }
     
    Last edited: Jul 24, 2019
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Take a look at IAPDemo.cs it has this code included. In your code, you are not showing where google and apple are defined, and so would not be expected to compile as is. And you probably don't need test_product, its use is described in the forum post.
     
  3. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    Hey thanks for your answer
    "you are not showing where google and apple are defined"
    Here is my Awake function (as copied from IAPDemo)-
    Code (CSharp):
    1.    public void Awake()
    2.     {  
    3.     var module = StandardPurchasingModule.Instance();
    4.     module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
    5.     var builder = ConfigurationBuilder.Instance(module);
    6.     builder.Configure<IMicrosoftConfiguration>().useMockBillingSystem = false;
    7.     m_IsGooglePlayStoreSelected =
    8.     Application.platform == RuntimePlatform.Android && module.appStore == AppStore.GooglePlay;
    9.     builder.Configure<ISamsungAppsConfiguration>().SetMode(SamsungAppsMode.AlwaysSucceed);
    10.     builder.Configure<IUnityChannelConfiguration>().fetchReceiptPayloadOnPurchase =      m_FetchReceiptPayloadOnPurchase;
    11.     m_IsSamsungAppsStoreSelected =
    12.     Application.platform == RuntimePlatform.Android && module.appStore == AppStore.SamsungApps;
    13.  
    14.     }
    And here is my updated ProcessPurchase function + validation. (notice where my content unlocking is placed). Is it OK now?
    Code (CSharp):
    1.  public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    2.     {
    3.         string prodID = "";
    4.         prodID = e.purchasedProduct.definition.id;
    5.         Debug.Log("Purchase OK: " + e.purchasedProduct.definition.id);
    6.  
    7.         //new code:
    8.         bool validPurchase = true; // Presume valid for platforms with no R.V.
    9.  
    10.         // Unity IAP's validation logic is only included on these platforms.
    11. #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    12.         // Prepare the validator with the secrets we prepared in the Editor
    13.         // obfuscation window.
    14.  
    15.         if (m_IsGooglePlayStoreSelected ||
    16.            (m_IsUnityChannelSelected && m_FetchReceiptPayloadOnPurchase) ||
    17.            Application.platform == RuntimePlatform.IPhonePlayer ||
    18.            Application.platform == RuntimePlatform.OSXPlayer ||
    19.            Application.platform == RuntimePlatform.tvOS)
    20.         {
    21.             try
    22.             {
    23.                 var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    24.                AppleTangle.Data(), Application.identifier);
    25.                 // On Google Play, result has a single product ID.
    26.                 // On Apple stores, receipts contain multiple products.
    27.                 var result = validator.Validate(e.purchasedProduct.receipt);
    28.                 // For informational purposes, we list the receipt(s)
    29.                 Debug.Log("Receipt is valid. Contents:");
    30.                 foreach (IPurchaseReceipt productReceipt in result)
    31.                 {
    32.                     Debug.Log(productReceipt.productID);
    33.                     Debug.Log(productReceipt.purchaseDate);
    34.                     Debug.Log(productReceipt.transactionID);
    35.  
    36.                     //  if (productReceipt.productID != productId)
    37.                     // { validPurchase = false; }
    38.  
    39.                     GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    40.                     if (null != google)
    41.                     {
    42.                         // This is Google's Order ID.
    43.                         // Note that it is null when testing in the sandbox
    44.                         // because Google's sandbox does not provide Order IDs.
    45.                         Debug.Log(google.transactionID);
    46.                         Debug.Log(google.purchaseState);
    47.                         Debug.Log(google.purchaseToken);
    48.                     }
    49.  
    50.                     AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    51.                     if (null != apple)
    52.                     {
    53.                         Debug.Log(apple.originalTransactionIdentifier);
    54.                         Debug.Log(apple.subscriptionExpirationDate);
    55.                         Debug.Log(apple.cancellationDate);
    56.                         Debug.Log(apple.quantity);
    57.                     }
    58.  
    59.                     UnityChannelReceipt unityChannel = productReceipt as UnityChannelReceipt;
    60.                     if (null != unityChannel)
    61.                     {
    62.                         Debug.Log(unityChannel.productID);
    63.                         Debug.Log(unityChannel.purchaseDate);
    64.                         Debug.Log(unityChannel.transactionID);
    65.                     }
    66.                     var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    67.                     // Get a reference to IAppleConfiguration during IAP initialization.
    68.                     var appleConfig = builder.Configure<IAppleConfiguration>();
    69.                     var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
    70.                     AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);
    71.  
    72.                     Debug.Log(receipt.bundleID);
    73.                     Debug.Log(receipt.receiptCreationDate);
    74.                     foreach (AppleInAppPurchaseReceipt productReceipt2 in receipt.inAppPurchaseReceipts)
    75.                     {
    76.                         Debug.Log(productReceipt2.originalTransactionIdentifier);
    77.                         Debug.Log(productReceipt2.productID);
    78.                     }
    79.                 }
    80.             }
    81.             catch (IAPSecurityException)
    82.             {
    83.                 Debug.Log("Invalid receipt, not unlocking content");
    84.                 validPurchase = false;
    85.             }
    86. #endif
    87.             if (validPurchase)
    88.             {
    89. //  unlocking content
    90.             }
    91.             //end new code
    92.         }
    93.             if (return_complete)
    94.             {
    95.                 return PurchaseProcessingResult.Complete;
    96.             }
    97.             else
    98.             {
    99.                 return PurchaseProcessingResult.Pending;
    100.             }
    101.  
    102.  
    103.     }
     
  4. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I would not recommend your additional google/apple/unityChannel checking code, it is not necessary. Your unlocking code looks correct.
     
  5. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    Are you talking about
    Code (CSharp):
    1.      if (m_IsGooglePlayStoreSelected ||
    2.            (m_IsUnityChannelSelected && m_FetchReceiptPayloadOnPurchase) ||
    3.            Application.platform == RuntimePlatform.IPhonePlayer ||
    4.            Application.platform == RuntimePlatform.OSXPlayer ||
    5.            Application.platform == RuntimePlatform.tvOS)
    OR
    #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
     
  6. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    No, just the variables that I mentioned such as in if null != apple
     
  7. swifter14

    swifter14

    Joined:
    Mar 2, 2017
    Posts:
    165
    OK Thanks, and for my other question about verifying productID, they also mention it in IAPDemo, right before the catch in ProcessPurchase function. -
    // For improved security, consider comparing the signed
    // IPurchaseReceipt.productId, IPurchaseReceipt.transactionID, and other data
    // embedded in the signed receipt objects to the data which the game is using
    // to make this purchase.

    should I just add before the catch-
    Code (CSharp):
    1. if (!IPurchaseReceipt.productId.Equals(productToBuy)) //productToBuy is a private variable with the productId string, it's intilizied before the purchase begins
    2. validPurchase = false;
    Or should I compare it with all my productId strings like-
    Code (CSharp):
    1. if (!IPurchaseReceipt.productId.Equals("gold50")&&!IPurchaseReceipt.productId.Equals("gold100") &&!IPurchaseReceipt.productId.Equals("subscription"))
    2. validPurchase = false;
    They also mention it on documantion-
    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.
     
    Last edited: Jul 24, 2019
  8. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I would not recommend to make any changes until you get the existing code working as expected. Then add your customization. I do similar logic with test_product in the Sample app, so your code looks correct at first glance. Just be sure to test first.