Skip to main content

Android Receipt Validation

API for validating Android In-App Purchase (IAP) receipts.

URL Verification

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
DeviceInfo Inheritance

The Req class of this API inherits from DeviceInfo. All properties of DeviceInfo are automatically included.

Request Parameters

ParameterTypeRequiredDescription
receiptstringRequiredReceipt information
skuDetailsstringYesAndroid product information
signaturestringYesAndroid Signature
duplicate_allowstringRequiredAllow duplicate receipt validation (Y/N)

Response Data

Res Class

FieldTypeDescription
UserIDstringUser ID
PackageNamestringPackage name
OrderIDstringOrder ID
ProductIDstringProduct ID
CurrencystringCurrency code
QuantitystringQuantity
PricestringPrice
PurchaseStatestringValidation result
purchase : Successful payment
wait : Processing in progress
cancel : Validation failed
wait Status Handling

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 code
  • Message: Error message
  • WithdrawalKey: 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
}
Duplicate Receipt 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.

Google Play Receipt

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) { }
}
Asynchronous Processing Caution

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.

Retry Logic

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.