Modifying ProcessPurchase with Receipt validation

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
  public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
        {
          test_product = e.purchasedProduct;

        //new code:
        bool validPurchase = true; // Presume valid for platforms with no R.V.
   
        // Unity IAP's validation logic is only included on these platforms.
#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
        // Prepare the validator with the secrets we prepared in the Editor
        // obfuscation window.
        var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
            AppleTangle.Data(), Application.identifier);
    
        try
        {
            // On Google Play, result has a single product ID.
            // On Apple stores, receipts contain multiple products.
            var result = validator.Validate(e.purchasedProduct.receipt);
            // For informational purposes, we list the receipt(s)
            Debug.Log("Receipt is valid. Contents:");
            foreach (IPurchaseReceipt productReceipt in result)
            {
                Debug.Log(productReceipt.productID);
                Debug.Log(productReceipt.purchaseDate);
                Debug.Log(productReceipt.transactionID);

              //  if (productReceipt.productID != productId)
               // { validPurchase = false; }

                GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
                if (null != google)
                {
                    // This is Google's Order ID.
                    // Note that it is null when testing in the sandbox
                    // because Google's sandbox does not provide Order IDs.
                    Debug.Log(google.transactionID);
                    Debug.Log(google.purchaseState);
                    Debug.Log(google.purchaseToken);
                }

                AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
                if (null != apple)
                {
                    Debug.Log(apple.originalTransactionIdentifier);
                    Debug.Log(apple.subscriptionExpirationDate);
                    Debug.Log(apple.cancellationDate);
                    Debug.Log(apple.quantity);
                }


                var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
                // Get a reference to IAppleConfiguration during IAP initialization.
                var appleConfig = builder.Configure<IAppleConfiguration>();
                var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
                AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);

                Debug.Log(receipt.bundleID);
                Debug.Log(receipt.receiptCreationDate);
                foreach (AppleInAppPurchaseReceipt productReceipt2 in receipt.inAppPurchaseReceipts)
                {
                    Debug.Log(productReceipt2.originalTransactionIdentifier);
                    Debug.Log(productReceipt2.productID);
                }



            }

        }
        catch (IAPSecurityException)
        {
            Debug.Log("Invalid receipt, not unlocking content");
            validPurchase = false;
        }

        if (validPurchase)
        { Debug.Log("Valid receipt, unlocking content"); }
#endif

       //end new code

        if (return_complete)
        {
                Debug.Log(string.Format("ProcessPurchase: Complete. Product:" + e.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
                return PurchaseProcessingResult.Complete;
            }
            else
        {
   
            Debug.Log(string.Format("ProcessPurchase: Pending. Product:" + e.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
                return PurchaseProcessingResult.Pending;
            }

  }

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.

Hey thanks for your answer
“you are not showing where google and apple are defined”
Here is my Awake function (as copied from IAPDemo)-
```csharp

  • public void Awake()
    {
    var module = StandardPurchasingModule.Instance();
    module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
    var builder = ConfigurationBuilder.Instance(module);
    builder.Configure().useMockBillingSystem = false;
    m_IsGooglePlayStoreSelected =
    Application.platform == RuntimePlatform.Android && module.appStore == AppStore.GooglePlay;
    builder.Configure().SetMode(SamsungAppsMode.AlwaysSucceed);
    builder.Configure().fetchReceiptPayloadOnPurchase = m_FetchReceiptPayloadOnPurchase;
    m_IsSamsungAppsStoreSelected =
    Application.platform == RuntimePlatform.Android && module.appStore == AppStore.SamsungApps;

    }*
    * *And here is my updated ProcessPurchase function + validation. (notice where my content unlocking is placed). Is it OK now?* *csharp

  • public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    {
    string prodID = “”;
    prodID = e.purchasedProduct.definition.id;
    Debug.Log("Purchase OK: " + e.purchasedProduct.definition.id);

      //new code:
      bool validPurchase = true; // Presume valid for platforms with no R.V.
    
      // Unity IAP's validation logic is only included on these platforms.
    

#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
// Prepare the validator with the secrets we prepared in the Editor
// obfuscation window.

    if (m_IsGooglePlayStoreSelected ||
       (m_IsUnityChannelSelected && m_FetchReceiptPayloadOnPurchase) ||
       Application.platform == RuntimePlatform.IPhonePlayer ||
       Application.platform == RuntimePlatform.OSXPlayer ||
       Application.platform == RuntimePlatform.tvOS)
    {
        try
        {
            var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
           AppleTangle.Data(), Application.identifier);
            // On Google Play, result has a single product ID.
            // On Apple stores, receipts contain multiple products.
            var result = validator.Validate(e.purchasedProduct.receipt);
            // For informational purposes, we list the receipt(s)
            Debug.Log("Receipt is valid. Contents:");
            foreach (IPurchaseReceipt productReceipt in result)
            {
                Debug.Log(productReceipt.productID);
                Debug.Log(productReceipt.purchaseDate);
                Debug.Log(productReceipt.transactionID);

                //  if (productReceipt.productID != productId)
                // { validPurchase = false; }

                GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
                if (null != google)
                {
                    // This is Google's Order ID.
                    // Note that it is null when testing in the sandbox
                    // because Google's sandbox does not provide Order IDs.
                    Debug.Log(google.transactionID);
                    Debug.Log(google.purchaseState);
                    Debug.Log(google.purchaseToken);
                }

                AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
                if (null != apple)
                {
                    Debug.Log(apple.originalTransactionIdentifier);
                    Debug.Log(apple.subscriptionExpirationDate);
                    Debug.Log(apple.cancellationDate);
                    Debug.Log(apple.quantity);
                }

                UnityChannelReceipt unityChannel = productReceipt as UnityChannelReceipt;
                if (null != unityChannel)
                {
                    Debug.Log(unityChannel.productID);
                    Debug.Log(unityChannel.purchaseDate);
                    Debug.Log(unityChannel.transactionID);
                }
                var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
                // Get a reference to IAppleConfiguration during IAP initialization.
                var appleConfig = builder.Configure<IAppleConfiguration>();
                var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
                AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);

                Debug.Log(receipt.bundleID);
                Debug.Log(receipt.receiptCreationDate);
                foreach (AppleInAppPurchaseReceipt productReceipt2 in receipt.inAppPurchaseReceipts)
                {
                    Debug.Log(productReceipt2.originalTransactionIdentifier);
                    Debug.Log(productReceipt2.productID);
                }
            }
        }
        catch (IAPSecurityException)
        {
            Debug.Log("Invalid receipt, not unlocking content");
            validPurchase = false;
        }

#endif
if (validPurchase)
{
// unlocking content
}
//end new code
}
if (return_complete)
{
return PurchaseProcessingResult.Complete;
}
else
{
return PurchaseProcessingResult.Pending;
}

}*

```

I would not recommend your additional google/apple/unityChannel checking code, it is not necessary. Your unlocking code looks correct.

Are you talking about

     if (m_IsGooglePlayStoreSelected ||
           (m_IsUnityChannelSelected && m_FetchReceiptPayloadOnPurchase) ||
           Application.platform == RuntimePlatform.IPhonePlayer ||
           Application.platform == RuntimePlatform.OSXPlayer ||
           Application.platform == RuntimePlatform.tvOS)

OR
#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX

No, just the variables that I mentioned such as in if null != apple

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-

if (!IPurchaseReceipt.productId.Equals(productToBuy)) //productToBuy is a private variable with the productId string, it's intilizied before the purchase begins
validPurchase = false;

Or should I compare it with all my productId strings like-

if (!IPurchaseReceipt.productId.Equals("gold50")&&!IPurchaseReceipt.productId.Equals("gold100") &&!IPurchaseReceipt.productId.Equals("subscription"))
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.

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.