Skip to main content
Version: 5.15.0

IsValidateGooglePurchase

public BackendReturnObject IsValidateGooglePurchase(string receipt, string receiptDescription);
public BackendReturnObject IsValidateGooglePurchase(string json, string receiptDescription, decimal iapPrice, string iapCurrency);
public BackendReturnObject IsValidateGooglePurchase(string receipt, string receiptDescription , bool isSubscription);
public BackendReturnObject IsValidateGooglePurchase(string receipt, string receiptDescription , bool isSubscription);
public BackendReturnObject IsValidateGooglePurchase(string json, string receiptDescription,bool isSubscription, decimal iapPrice, string iapCurrency);
public BackendReturnObject IsValidateGooglePurchase(string productId, string token, string receiptDescription, bool isSubscription, decimal iapPrice, string iapCurrency);

Error caused by special characters in in-app product names and descriptions

Certain special characters cannot be used in names or descriptions in In-app Products > Product Details configured in Google Play Console.
Please check if the following values exist in the name or description when creating an in-app product.

Invalid special characters

Special CharacterDescription
'Single quotation mark
\ Backslash

Valid special characters

Special CharacterDescription
.Period
,Comma
"Double quotation mark
()Parentheses
!Exclamation mark
?Question mark
~Tilde
@At sign
*Asterisk
+Plus
-Hyphen
/Slash

If you are using special characters that are not listed above, manually check that the receipt verification works properly before applying.

Parameters

ValueTypeDescriptiondefault
receiptstringPurchasing.PurchaseEventArgs.purchasedProduct.receipt-
receiptDescriptionstringAdditional details to be stored-
isSubscriptionboolWhether the product is a subscription product (true means it is)false

Description

Products purchased from IStoreListener.ProcessPurchase() in the IAP service supported by Unity can have their receipts called and verified via the BACKND server.

  • BACKND verifies the validity of the receipt itself and the purchased productId.
  • You must be logged in to BACKND to use its verification function.

To verify Google receipts, BACKND and Google Console must be configured.
For more information, please refer to the Google Payment Console Settings documentation.

Display amount (optional)

If you wish to display the amount of relevant purchase details in the BACKND console's receipt verification field, parameter values iapPrice and iapCurrency must be added.

 private void GetCheckGoogleReceiptWithPrice(PurchaseEventArgs purchaseEvent)
{
string receiptToken = purchaseEvent.purchasedProduct.receipt;
var bro = Backend.Receipt.IsValidateGooglePurchase(
json: receiptToken,
receiptDescription: "Purchased",
isSubscription: false,
iapPrice: purchaseEvent.purchasedProduct.metadata.localizedPrice,
iapCurrency: purchaseEvent.purchasedProduct.metadata.isoCurrencyCode);
}

Example

Synchronous

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
string receiptJson = args.purchasedProduct.receipt;
/*
Process BACKND receipt verification
*/
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(receiptJson, "receiptDescription", false);


// When attempting to display the amount in the console
decimal iapPrice = args.purchasedProduct.metadata.localizedPrice;
string iapCurrency = args.purchasedProduct.metadata.isoCurrencyCode;
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(receiptJson, "receiptDescription", iapPrice, iapCurrency);

// When receipt verification is successful
if(validation.IsSuccess())
{
// Check the product ID and provide rewards accordingly
// A consumable product has been purchased by this user.
if(string.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// The consumable item has been successfully purchased, add 100 coins to the player's in-game score.
ScoreManager.score += 100;
}
// Or ... a non-consumable product has been purchased by this user.
else if(string.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// TODO: The non-consumable item has been successfully purchased, grant this item to the player.
}
// Or ... a subscription product has been purchased by this user.
else if(string.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// TODO: The subscription item has been successfully purchased, grant this to the player.
}
}
// When receipt verification fails
else
{
// Or ... an unknown product has been purchased by this user. Fill in additional products here....
Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
}

// Return a flag indicating whether this product has completely been received, or if the application needs
// to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still
// saving purchased products to the cloud, and when that save is delayed.
return PurchaseProcessingResult.Complete;
}

Asynchronous

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
string receiptJson = args.purchasedProduct.receipt;
decimal iapPrice = args.purchasedProduct.metadata.localizedPrice; // Enter to display the amount in the console (must be entered the same as iapCurrency)
string iapCurrency = args.purchasedProduct.metadata.isoCurrencyCode;

Backend.Receipt.IsValidateGooglePurchase(args.purchasedProduct.receipt, "receiptDescription", false, iapPrice, iapCurrency, (callback) =>
{
// When receipt verification is successful
if(callback.IsSuccess())
{
if(string.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// The consumable item has been successfully purchased, add 100 coins to the player's in-game score.
ScoreManager.score += 100;
}
// Or ... a non-consumable product has been purchased by this user.
else if(string.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// TODO: The non-consumable item has been successfully purchased, grant this item to the player.
}
// Or ... a subscription product has been purchased by this user.
else if(string.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// TODO: The subscription item has been successfully purchased, grant this to the player.
}
}
else
{
// When receipt verification fails
Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
}
});
// Return a flag indicating whether this product has completely been received, or if the application needs
// to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still
// saving purchased products to the cloud, and when that save is delayed.
return PurchaseProcessingResult.Complete;
}

SendQueue

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
SendQueue.Enqueue(Backend.Receipt.IsValidateGooglePurchase, args.purchasedProduct.receipt, "receiptDescription", false, (callback) =>
{
// When receipt verification is successful
if(callback.IsSuccess())
{
if(string.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// The consumable item has been successfully purchased, add 100 coins to the player's in-game score.
ScoreManager.score += 100;
}
// Or ... a non-consumable product has been purchased by this user.
else if(string.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// TODO: The non-consumable item has been successfully purchased, grant this item to the player.
}
// Or ... a subscription product has been purchased by this user.
else if(string.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
{
Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
// TODO: The subscription item has been successfully purchased, grant this to the player.
}
}
else
{
// When receipt verification fails
Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
}
});
// Return a flag indicating whether this product has completely been received, or if the application needs
// to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still
// saving purchased products to the cloud, and when that save is delayed.
return PurchaseProcessingResult.Complete;
}

ReturnCase

Success cases

When verification is successful (subscription product)
statusCode : 200
message : Success
returnValue : refer to GetReturnValuetoJSON

When verification is successful (normal product)
statusCode : 201
message : Success
returnValue : {"usedDate":"2018-10-15T05:17:49Z"}

Error cases

Expired receipt token
statusCode : 400
errorCode : BadParameterException
message : bad token, Invalid token

You must check the JWT permissions in Steps 1 - 3.
If a bad token error occurs even after setting the permissions properly, reissue the JWT, and if an error occurs even after reissuing, create a new JWT and try again.
JWT modification may not be applied immediately, so it is recommended to try again after 1-2 hours.

Receipt of refund/cancellation
statusCode : 402
errorCode : AbnormalReceipt
message : This receipt has changed status. purchaseState: cancelled

Already used receipt token
statusCode : 409
errorCode : UsedReceipt
message : This receipt has already been used. usedDate: 2018-02-15T04:01:50.000Z

When the receipt for an already used or canceled subscription product is verified
statusCode : 409
errorCode : DuplicatedParameterException
message : Duplicated receipt, Duplicated receipt

GetReturnValuetoJSON(Subscription product)

{
"kind":"androidpublisher#subscriptionPurchase",
"startTimeMillis":"1583722491833",
"expiryTimeMillis":"1583722908247",
"autoRenewing":true,
"priceCurrencyCode":"KRW",
"priceAmountMicros":"1000000000",
"countryCode":"KR",
"developerPayload":"{\"developerPayload\":\"\",\"is_free_trial\":false,\"has_introductory_price_trial\":false,\"is_updated\":false}",
"paymentState":1,
"orderId":"order Id",
"purchaseType":0,
"acknowledgementState":1
}

IsValidateGooglePurchase

public BackendReturnObject IsValidateGooglePurchase(string productId, string token, string receiptDescription);
public BackendReturnObject IsValidateGooglePurchase(string productId, string token, string receiptDescription, bool isSubscription = false);

Parameters

ValueTypeDescriptiondefault
productIdstringproductId of the product to be purchase-
tokenstringReceipt token issued after purchasing-
receiptDescriptionstringAdditional details to be stored-
isSubscriptionboolWhether the product is a subscription product (true means it is)false

Description

Even if you do not use the IAP service provided by Unity, you can verify receipts via the BACKND server with the productID and receipt token.

Example

Synchronous

BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(productID , receiptToken , "receiptDescription" , true);
if(validation.IsSuccess())
{
// Processed when receipt verification succeeds
} else {
// Processed when receipt verification fails
}

Asynchronous

Backend.Receipt.IsValidateGooglePurchase(productID, receiptToken, "receiptDescription", false, (callback) => {
if(callback.IsSuccess()) {
// Processed when receipt verification succeeds
} else {
// Processed when receipt verification fails
}
});

SendQueue

SendQueue.Enqueue(Backend.Receipt.IsValidateGooglePurchase, productID, receiptToken, "receiptDescription", false, (callback) => {
if(callback.IsSuccess()) {
// Processed when receipt verification succeeds
} else {
// Processed when receipt verification fails
}
});

ReturnCase

Success cases

When verification is successful (subscription product)
statusCode : 200
message : Success
returnValue : refer to GetReturnValuetoJSON

When verification is successful (normal product)
statusCode : 201
message : Success
returnValue : {"usedDate":"2018-10-15T05:17:49Z"}

Error cases

Expired receipt token
statusCode : 400
errorCode : BadParameterException
message : bad token, Invalid token

You must check the JWT permissions.
If a bad token error occurs even after setting the permissions properly, reissue the JWT, and if an error occurs even after reissuing, create a new JWT and try again.
JWT modification may not be applied immediately, so it is recommended to try again after 1-2 hours.

Receipt of refund/cancellation
statusCode : 402
errorCode : AbnormalReceipt
message : This receipt has changed status. purchaseState: cancelled

Already used receipt token
statusCode : 409
errorCode : UsedReceipt
message : This receipt has already been used. usedDate: 2018-02-15T04:01:50.000Z

When the receipt for an already used or canceled subscription product is verified
statusCode : 409
errorCode : DuplicatedParameterException
message : Duplicated receipt, Duplicated receipt

GetReturnValuetoJSON(Subscription product)

{
"kind":"androidpublisher#subscriptionPurchase",
"startTimeMillis":"1583722491833",
"expiryTimeMillis":"1583722908247",
"autoRenewing":true,
"priceCurrencyCode":"KRW",
"priceAmountMicros":"1000000000",
"countryCode":"KR",
"developerPayload":"{\"developerPayload\":\"\",\"is_free_trial\":false,\"has_introductory_price_trial\":false,\"is_updated\":false}",
"paymentState":1,
"orderId":"order Id",
"purchaseType":0,
"acknowledgementState":1
}