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 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
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
returnValue : refer to GetReturnValuetoJSON
When verification is successful(normal product)
statusCode : 201
returnValue : {"usedDate":"2018-10-15T05:17:49Z"}
Error cases
Expired receipt token
statusCode : 400
errorCode : BadParameterException
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
Already used receipt token
statusCode : 409
errorCode : UsedReceipt
When the receipt for an already used or canceled subscription product is verified
statusCode : 409
errorCode : DuplicatedParameterException
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())
{
// 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
returnValue : refer to GetReturnValuetoJSON
When verification is successful(normal product)
statusCode : 201
returnValue : {"usedDate":"2018-10-15T05:17:49Z"}
Error cases
Expired receipt token
statusCode : 400
errorCode : BadParameterException
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
Already used receipt token
statusCode : 409
errorCode : UsedReceipt
When the receipt for an already used or canceled subscription product is verified
statusCode : 409
errorCode : DuplicatedParameterException
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
}