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 Character | Description |
---|---|
' | Single quotation mark |
\ | Backslash |
Valid special characters
Special Character | Description |
---|---|
. | 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
Value | Type | Description | default |
---|---|---|---|
receipt | string | Purchasing.PurchaseEventArgs.purchasedProduct.receipt | - |
receiptDescription | string | Additional details to be stored | - |
isSubscription | bool | Whether 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
Value | Type | Description | default |
---|---|---|---|
productId | string | productId of the product to be purchase | - |
token | string | Receipt token issued after purchasing | - |
receiptDescription | string | Additional details to be stored | - |
isSubscription | bool | Whether 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
}