I have two small questions about receipt validation and how the flow should work. I have the latest samples from this post Sample IAP Project
Note : I have not touched these methods.
The first method ProcessPurchase has this commented out code.
//var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
// var result = validator.Validate(args.purchasedProduct.receipt);
// foreach (IPurchaseReceipt productReceipt in result)
// {
// MyDebug("Valid receipt for " + productReceipt.productID.ToString());
// }
What is this for? It seems like it is validating the product with a receipt. Do I need to check when purchasing or restoring a purchase? Where in this method do I unlock the item? In this part of the code?
if(returnComplete)
{
MyDebug(string.Format("ProcessPurchase: Complete. Product:" + args.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
return PurchaseProcessingResult.Complete;
}
The second method is OnInitialized
I see this code:
Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();
foreach(UnityEngine.Purchasing.Product item in controller.products.all)
{
if(item.receipt != null)
{
string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];
}
}
This is where I am assuming every time I launch the game and the initialization is complete, I check if the receipt is valid and then unlock the item? Is this assumption correct?
Yes, when you validate the receipt during purchase or restore, that is when you award the product to the user. You unlock the product in ProcessPurchase, before returning Complete.
JeffDUnity3D:
Yes, when you validate the receipt during purchase or restore, that is when you award the product to the user. You unlock the product in ProcessPurchase, before returning Complete.
I uncommented the code and tried to purchase the item, but I get an error in the editor.
I already generated the apple/google tangle files
var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
var result = validator.Validate(args.purchasedProduct.receipt);
foreach(IPurchaseReceipt productReceipt in result)
{
MyDebug("Valid receipt for " + productReceipt.productID.ToString());
}
MyDebug("Validate = " + result.ToString());
NotImplementedException: The method or operation is not implemented.
UnityEngine.Purchasing.Security.AppleTangle.Data () (at Assets/Scripts/UnityPurchasing/generated/AppleTangle.cs:15)
IAPManager.ProcessPurchase (UnityEngine.Purchasing.PurchaseEventArgs args) (at Assets/Scripts/IAPManager.cs:242)
UnityEngine.Purchasing.StoreListenerProxy.ProcessPurchase (UnityEngine.Purchasing.PurchaseEventArgs e) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Purchasing/StoreListenerProxy.cs:34)
UnityEngine.Purchasing.PurchasingManager.ProcessPurchaseIfNew (UnityEngine.Purchasing.Product product) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Purchasing/PurchasingManager.cs:258)
UnityEngine.Purchasing.PurchasingManager.OnPurchaseSucceeded (System.String id, System.String receipt, System.String transactionId) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Purchasing/PurchasingManager.cs:111)
UnityEngine.Purchasing.JSONStore.OnPurchaseSucceeded (System.String id, System.String receipt, System.String transactionID) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Stores/BaseStore/JSONStore.cs:165)
UnityEngine.Purchasing.FakeStore.<>n__0 (System.String id, System.String receipt, System.String transactionID) (at :0)
UnityEngine.Purchasing.FakeStore+<>c__DisplayClass15_0.b__0 (System.Boolean allow, UnityEngine.Purchasing.PurchaseFailureReason failureReason) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Stores/FakeStore/FakeStore.cs:143)
UnityEngine.Purchasing.FakeStore.FakePurchase (UnityEngine.Purchasing.ProductDefinition product, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Stores/FakeStore/FakeStore.cs:162)
UnityEngine.Purchasing.FakeStore.Purchase (System.String productJSON, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Stores/FakeStore/FakeStore.cs:128)
UnityEngine.Purchasing.JSONStore.Purchase (UnityEngine.Purchasing.ProductDefinition product, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Stores/BaseStore/JSONStore.cs:140)
UnityEngine.Purchasing.PurchasingManager.InitiatePurchase (UnityEngine.Purchasing.Product product, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Purchasing/PurchasingManager.cs:60)
UnityEngine.Purchasing.PurchasingManager.InitiatePurchase (UnityEngine.Purchasing.Product product) (at Library/PackageCache/com.unity.purchasing@4.1.3/Runtime/Purchasing/PurchasingManager.cs:38)
IAPManager.BuyProductID (System.String productId) (at Assets/Scripts/IAPManager.cs:207)
IAPManager.BuyAlbertTier () (at Assets/Scripts/IAPManager.cs:129)
ModalManager.OnOpenModalComplete (UnityEngine.UIElements.VisualElement focusThisButton, System.Action onModalOpened) (at Assets/Scripts/ModalManager.cs:242)
ModalManager+<>c__DisplayClass31_0.b__0 () (at Assets/Scripts/ModalManager.cs:219)
UnityEngine.UIElements.Experimental.ValueAnimation1[T].Stop () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/ValueAnimation.cs:173) UnityEngine.UIElements.Experimental.ValueAnimation
1[T].UnityEngine.UIElements.Experimental.IValueAnimationUpdate.Tick (System.Int64 currentTimeMs) (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/ValueAnimation.cs:241)
UnityEngine.UIElements.VisualElementAnimationSystem.Update () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/VisualTreeAnimationUpdater.cs:77)
UnityEngine.UIElements.VisualTreeUpdater.UpdateVisualTreePhase (UnityEngine.UIElements.VisualTreeUpdatePhase phase) (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/VisualTreeUpdater.cs:111)
UnityEngine.UIElements.Panel.UpdateAnimations () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/Panel.cs:957)
UnityEngine.UIElements.BaseVisualElementPanel.Update () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/Panel.cs:561)
UnityEngine.UIElements.RuntimePanel.Update () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/GameObjects/RuntimePanel.cs:47)
UnityEngine.UIElements.UIElementsRuntimeUtility.UpdateRuntimePanels () (at /Users/bokken/buildslave/unity/build/ModuleOverrides/com.unity.ui/Core/UIElementsRuntimeUtility.cs:218)
UnityEngine.UIElements.UIElementsRuntimeUtilityNative.UpdateRuntimePanels () (at /Users/bokken/buildslave/unity/build/Modules/UIElementsNative/UIElementsUtility.bindings.cs:26)
I there an error in the sample project?
@TomTheMan59 Are you testing on an Android or iOS device? Your error mentions the fake store, so you are likely running in the Editor. If so, then you’ll want to add a Try/Catch block (and is why the code was commented out) or run this code only on a device.
That worked, thanks.
My last question is the results are very weird. I don’t know how to validate the receipt with the provided code from unity.
The MyDebug() prints an array. If it is not empty then the receipt is valid?
#if UNITY_IOS
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
IPurchaseReceipt[] result = validator.Validate(args.purchasedProduct.receipt);
MyDebug("Validate = " + result.ToString());
#endif
TomTheMan59:
That worked, thanks.
My last question is the results are very weird. I don’t know how to validate the receipt with the provided code from unity.
The MyDebug() prints an array. If it is not empty then the receipt is valid?
#if UNITY_IOS
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
IPurchaseReceipt[] result = validator.Validate(args.purchasedProduct.receipt);
MyDebug("Validate = " + result.ToString());
#endif
I believe result will be either true or false for each array item (receipt)
It doesn’t seem like it has a .isValid boolean. I did some more research and found this, but I think that it is old documentation since your new code doesn’t use it?
Or can I use this code with the try catch block and do what it says by checking the product id vs the apple product id which I set in the apple developer website?
https://docs.unity3d.com/Manual/UnityIAPValidatingReceipts.html
“These receipts are genuine and do pass validation, so you should make decisions based on the product IDs parsed by the CrossPlatformValidator.”
@TomTheMan59 Oh that’s right, it throws an exception that you’ll want to catch.
Thanks!
I have one more question to make sure I understand the receipt validation for OnInitialized().
This was the code provided. Is this correct for detecting fake receipts?
foreach(UnityEngine.Purchasing.Product item in controller.products.all)
{
if(item.receipt == null)
{
// Receipt is FAKE. Remove the purchased item!
}
}
For anyone interested, this is how you do the receipt validation for ProcessPurchase() method.
#if UNITY_IOS
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
try
{
IPurchaseReceipt[] result = validator.Validate(args.purchasedProduct.receipt);
isValidPurchase = true;
}
catch
{
isValidPurchase = false;
}
#endif
TomTheMan59:
Thanks!
I have one more question to make sure I understand the receipt validation for OnInitialized().
This was the code provided. Is this correct for detecting fake receipts?
foreach(UnityEngine.Purchasing.Product item in controller.products.all)
{
if(item.receipt == null)
{
// Receipt is FAKE. Remove the purchased item!
}
}
For anyone interested, this is how you do the receipt validation for ProcessPurchase() method.
#if UNITY_IOS
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
try
{
IPurchaseReceipt[] result = validator.Validate(args.purchasedProduct.receipt);
isValidPurchase = true;
}
catch
{
isValidPurchase = false;
}
#endif
The first loop doesn’t check the receipt itself. The comment is incorrect. If there is no receipt, it simply means the user has not purchased that item yet.
I cannot find any documentation on how this works. You seem to be saying:
If the receipt == null, that means the user hasn’t purchased.
If the receipt != null, the user has purchased.
Maybe I am confusing as to this code and it isn’t for checking if the receipt is real?
1 and 2 are both correct. If the receipt is not null, you are welcome to validate it.
I think I finally understand. I made this method for both ProcessPurchase and OnInitialized methods to check if the receipt is valid.
Is this correct?
private bool IsReceiptValid(string receipt)
{
#if UNITY_IOS
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
try
{
IPurchaseReceipt[] result = validator.Validate(receipt);
}
catch
{
return false;
}
#endif
return true;
}
@TomTheMan59 Yes! Your code is correct, and would be a very handy helper method.
1 Like
Thanks! Just wanted to make sure 100% I was doing it right as I have no idea how to fake a receipt and test if that code works.
I would rather error on the side of caution by allowing those who didn’t pay still get the item just to make sure it doesn’t take it away from those who did pay accidently.
TomTheMan59:
Thanks! Just wanted to make sure 100% I was doing it right as I have no idea how to fake a receipt and test if that code works.
I would rather error on the side of caution by allowing those who didn’t pay still get the item just to make sure it doesn’t take it away from those who did pay accidently.
Sorry I don’t follow your last statement. You don’t want to give a product to people who don’t pay. There are no accidental purchases, that never happens. Try the string “hello world” as a receipt and see how it behaves. But ensure it works in your actual store testing. Configure your app for Closed Testing on Google Play, and add your testers.