I use IAP scripting. With MyIAPManager.cs demo class.
I want to modify it so I have receipt validation.
Is the right place to verify it is inside ProcessPurchase ()?
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?
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.
//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;
}
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.