Android Receipt Validation
API for validating Android In-App Purchase (IAP) receipts.
This API uses the service-api.playnanoo.com domain.
API Information
- URL:
https://service-api.playnanoo.com/iap/v20221001/unity/android - Method:
PUT - Authentication Required: Yes
The Req class of this API inherits from DeviceInfo. All properties of DeviceInfo are automatically included.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| receipt | string | Required | Receipt information |
| skuDetails | string | Yes | Android product information |
| signature | string | Yes | Android Signature |
| duplicate_allow | string | Required | Allow duplicate receipt validation (Y/N) |
Response Data
Res Class
| Field | Type | Description |
|---|---|---|
| UserID | string | User ID |
| PackageName | string | Package name |
| OrderID | string | Order ID |
| ProductID | string | Product ID |
| Currency | string | Currency code |
| Quantity | string | Quantity |
| Price | string | Price |
| PurchaseState | string | Validation resultpurchase : Successful paymentwait : Processing in progresscancel : Validation failed |
If the status is wait, please send a re-validation request.
Unity C# Implementation
BaseResponse Class
Base class for all API responses.
public class BaseResponse
{
public string ErrorCode;
public string Message;
public string WithdrawalKey;
public string BlockKey;
}
Field descriptions:
ErrorCode: Error codeMessage: Error messageWithdrawalKey: Key required for recovery when account is in withdrawal grace period (provided only for accounts in withdrawal grace period)BlockKey: Key provided when account is blocked (provided only for blocked accounts)
Android IAP Validation Class
using System;
using System.Collections;
using UnityEngine.Networking;
public class IAPUnityAndroid
{
static string path = "https://service-api.playnanoo.com/iap/v20221001/unity/android";
[Serializable]
public class Req : DeviceInfo
{
//Required
public string receipt; // Receipt information
//Optional
public string skuDetails; // Android product information
//Optional
public string signature; // Android Signature
//Required
public string duplicate_allow; // Allow duplicate receipt validation
public IEnumerator Send(string receipt, string skuDetails, string signature, bool isDuplicateAllow, Action<Res> onSuccess, Action<BaseResponse> onError)
{
if (!string.IsNullOrEmpty(receipt)) this.receipt = receipt;
if (!string.IsNullOrEmpty(skuDetails)) this.skuDetails = skuDetails;
if (!string.IsNullOrEmpty(signature)) this.signature = signature;
this.duplicate_allow = isDuplicateAllow ? "Y" : "N";
yield return HttpClient.Send<Req, Res>(
UnityWebRequest.kHttpVerbPUT,
path,
requireToken: true,
body: this,
onSuccess: onSuccess,
onError: onError
);
}
}
[Serializable]
public class Res : BaseResponse
{
public string UserID;
public string PackageName;
public string OrderID;
public string ProductID;
public string Currency;
public string Quantity;
public string Price;
public string PurchaseState;
}
}
Usage Example
public void ValidateAndroidIAP()
{
IAPUnityAndroid.Req req = new IAPUnityAndroid.Req();
// Google Play payment information
string receipt = "{\"orderId\":\"GPA.1234-5678-9012-34567\",\"packageName\":\"com.example.game\",\"productId\":\"gold_100\",\"purchaseTime\":1234567890,\"purchaseState\":0,\"purchaseToken\":\"abcdefghijklmnop\"}";
string skuDetails = "{\"productId\":\"gold_100\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"100 Gold\",\"description\":\"Get 100 gold coins\"}";
string signature = "Base64EncodedSignature==";
StartCoroutine(req.Send(
receipt: receipt,
skuDetails: skuDetails,
signature: signature,
isDuplicateAllow: false, // Do not allow duplicate receipts
onSuccess: res =>
{
switch (res.PurchaseState)
{
case "purchase":
// Successful payment - Grant item
Debug.Log($"Payment completed: {res.ProductID}");
GiveItemToPlayer(res.ProductID, int.Parse(res.Quantity));
break;
case "wait":
// Processing in progress - Re-validation needed
Debug.Log("Payment is being processed. Re-validation will be requested shortly.");
StartCoroutine(RetryValidation(receipt, skuDetails, signature));
break;
case "cancel":
// Validation failed
Debug.LogError("Receipt validation failed.");
break;
}
},
onError: (error) =>
{
Debug.LogError($"IAP validation failed: [{error.ErrorCode}] [{error.Message}]");
}
));
}
private void GiveItemToPlayer(string productId, int quantity)
{
// Implement actual item granting logic
Debug.Log($"Item granted: {productId} x {quantity}");
}
private IEnumerator RetryValidation(string receipt, string skuDetails, string signature)
{
yield return new WaitForSeconds(3f); // Retry after 3 seconds
ValidateAndroidIAP(); // Call re-validation
}
If isDuplicateAllow is set to false, duplicate validation with the same receipt is not possible. In a test environment, you can set it to true to allow duplicate validation.
The receipt is JSON-formatted receipt information including the purchaseToken provided by the Google Play Billing Library.
Unity IAP Usage Example
When using Unity IAP, you can perform receipt validation in the ProcessPurchase callback.
using UnityEngine.Purchasing;
public class IAPManager : IStoreListener
{
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
// Check Android platform
if (Application.platform == RuntimePlatform.Android)
{
// Call Playnanoo receipt validation API
IAPUnityAndroid.Req req = new IAPUnityAndroid.Req();
StartCoroutine(req.Send(
receipt: args.purchasedProduct.receipt, // Unity IAP full receipt
skuDetails: "", // Optional when using Unity IAP
signature: "", // Optional when using Unity IAP
isDuplicateAllow: false,
onSuccess: res =>
{
switch (res.PurchaseState)
{
case "purchase":
// Successful payment - Grant item
Debug.Log($"Receipt validation successful: {res.ProductID}");
GiveItemToPlayer(res.ProductID, int.Parse(res.Quantity));
break;
case "wait":
// Processing in progress - Re-validation needed
Debug.Log("Payment is being processed. Re-validation will be requested shortly.");
break;
case "cancel":
// Validation failed
Debug.LogError("Receipt validation failed.");
break;
}
},
onError: error =>
{
Debug.LogError($"Receipt validation failed: [{error.ErrorCode}] {error.Message}");
}
));
// Pending until asynchronous validation completes
return PurchaseProcessingResult.Pending;
}
return PurchaseProcessingResult.Complete;
}
private void GiveItemToPlayer(string productId, int quantity)
{
// Implement actual item granting logic
Debug.Log($"Item granted: {productId} x {quantity}");
}
// Remaining IStoreListener methods
public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { }
public void OnInitializeFailed(InitializationFailureReason error) { }
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { }
}
The ProcessPurchase method is synchronous, but receipt validation is processed asynchronously. Return PurchaseProcessingResult.Pending until validation completes, and after validation is complete, call IStoreController.ConfirmPendingPurchase() to finalize the purchase.
If receipt validation fails and you return PurchaseProcessingResult.Pending, Unity IAP will automatically retry on the next app launch. This helps handle temporary issues like network errors.