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 | Name |
---|---|
' | Single quotation mark |
\ | Backslash |
Valid special characters
Special Character | Name |
---|---|
. | 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 | 해당 상품의 구독 상품 여부. true일 경우 구독 상품 | false |
설명
IStoreListener.ProcessPurchase() in the IAP service supported by Unity accepts the receipt for the purchased product and verifies it via the BACKND server.
- 뒤끝은 영수증 자체의 유효성과, 구매한 productId를 검증합니다.
- You must be logged in to BACKND to use its receipt 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 of purchase (optional)
뒤끝 콘솔의 영수증 검증 항목에서 해당 구매 내역의 금액을 표시하고자 할 경우, 인자값 iapPrice와 iapCurrency를 추가해야합니다.
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;
/*
Processing BACKND receipt verification
*/
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(receiptJson, "receiptDescription", false);
// When displaying 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())
{
// Checks the product ID and provides 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; // When displaying the amount in the console, input (must be input with 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 (general 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
1-3번 항목의 JWT 권한의 확인이 필요합니다.\ 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":"ID of the order",
"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 purchased | - |
token | string | Receipt token issued after purchase | - |
receiptDescription | string | Additional details to be stored | - |
isSubscription | bool | 해당 상품의 구독 상품 여부. true일 경우 구독 상품 | 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
}
});
ReturnCase
Success cases
When verification is successful (subscription product)\ statusCode : 200\ message : Success\ returnValue : Refer to GetReturnValuetoJSON
When verification is successful (general 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":"ID of the order",
"purchaseType":0,
"acknowledgementState":1
}