Skip to main content
Version: SDK-5.9.6

IsValidateGooglePurchase

public BackendReturnObject IsValidateGooglePurchase(string receipt, string receiptDescription);
public BackendReturnObject IsValidateGooglePurchase(string receipt, string receiptDescription , bool isSubscription);

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

IStoreListener.ProcessPurchase() in the IAP service supported by Unity accepts the receipt for the purchased product and verifies it 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.

Example

Synchronous

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

// 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) 
{
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;
}

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;
}

Return cases

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())
{
// Processing when receipt verification succeeds
}
else
{
// Processing when receipt verification fails
}

Asynchronous

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

SendQueue

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

Return cases

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
}