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);
인앱상품 이름 및 설명에 특수문자 포함 시 에러 발생
구글 플레이 콘솔에서 설정하는 인앱 상품 > 상품 세부 정보
의 이름과 설명에는 일부 특수문자 사용이 불가능합니다.
인앱을 생성할 때 이름과 설명에 다음과 같은 값이 포함되어있는지 확인해주세요.
사용 불가능한 특수문자
특수문자 | 한글명 |
---|---|
' | 작은 따옴표 |
\ | 역슬래시 |
사용 가능한 특수문자
특수문자 | 한글명 |
---|---|
. | 마침표 |
, | 쉼표 |
" | 큰따옴표 |
() | 괄호 |
! | 느낌표 |
? | 물음표 |
~ | 물결 |
@ | 골뱅이 |
* | 곱하기 |
+ | 더하기 |
- | 빼기 |
/ | 슬래시 |
표기가 되지 않은 특수문자를 사용할 경우에는 영수증 검증이 정상적으로 진행되는지 확인 후 적용해주세요.
파라미터
Value | Type | Description | default |
---|---|---|---|
receipt | string | Purchasing.PurchaseEventArgs.purchasedProduct.receipt | - |
receiptDescription | string | 추가로 저장하고자 하는 내용 | - |
isSubscription | bool | 해당 상품의 구독 상품 여부. true일 경우 구독 상품 | false |
설명
유니티에서 지원하는 IAP 서비스의 IStoreListener.ProcessPurchase()에서 구매한 상품에 대한 영수증을 받아 뒤끝 서버를 통해 영수증 검증을 받습니다.
- 뒤끝은 영수증 자체의 유효성과, 구매한 productId를 검증합니다.
- 뒤끝 로그인 없이 뒤끝 영수증 검증 기능을 사용하는 것은 불가능합니다.
구글 영수증 검증을 위해서는 뒤끝 및 구글 콘솔 설정이 필요합니다.
자세한 설명은 구글 결제 콘솔 설정 문서을 참고해 주세요.
금액 표시(선택 사항)
뒤끝 콘솔의 영수증 검증 항목에서 해당 구매 내역의 금액을 표시하고자 할 경우, 인자값 iapPrice와 iapCurrency를 추가해야합니다.
private void GetCheckGoogleReceiptWithPrice(PurchaseEventArgs purchaseEvent)
{
string receiptToken = purchaseEvent.purchasedProduct.receipt;
var bro = Backend.Receipt.IsValidateGooglePurchase(
json: receiptToken,
receiptDescription: "구매 했습니다",
isSubscription: false,
iapPrice: purchaseEvent.purchasedProduct.metadata.localizedPrice,
iapCurrency: purchaseEvent.purchasedProduct.metadata.isoCurrencyCode);
}
Example
동기
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
string receiptJson = args.purchasedProduct.receipt;
/*
뒤끝 영수증 검증 처리
*/
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(receiptJson, "receiptDescription", false);
// 금액을 콘솔에서 표시하고자 할 경우
decimal iapPrice = args.purchasedProduct.metadata.localizedPrice;
string iapCurrency = args.purchasedProduct.metadata.isoCurrencyCode;
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(receiptJson, "receiptDescription", iapPrice, iapCurrency);
// 영수증 검증에 성공한 경우
if(validation.IsSuccess())
{
// 구매 성공한 제품에 대한 id 체크하여 그에 맞는 보상
// 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.
}
}
// 영수증 검증에 실패한 경우
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;
}
비동기
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
string receiptJson = args.purchasedProduct.receipt;
decimal iapPrice = args.purchasedProduct.metadata.localizedPrice; // 콘솔에서 금액을 표시하고자 할 경우, 입력(iapCurrency와 같이 입력해야합니다.)
string iapCurrency = args.purchasedProduct.metadata.isoCurrencyCode;
Backend.Receipt.IsValidateGooglePurchase(args.purchasedProduct.receipt, "receiptDescription", false, iapPrice, iapCurrency, (callback) =>
{
// 영수증 검증에 성공한 경우
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
{
// 영수증 검증에 실패한 경우
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) =>
{
// 영수증 검증에 성공한 경우
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
{
// 영수증 검증에 실패한 경우
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
검증에 성공한 경우(구독 상품)
statusCode : 200
message : Success
returnValue : GetReturnValuetoJSON 참조
검증에 성공한 경우(일반 상품)
statusCode : 201
message : Success
returnValue : {"usedDate":"2018-10-15T05:17:49Z"}
Error cases
유효하지 않은 영수증 토큰
statusCode : 400
errorCode : BadParameterException
message : bad token, 잘못된 token 입니다
1-3번 항목의 JWT 권한의 확인이 필요합니다.
권한을 제대로 설정했는데도 bad token 에러가 발생할 경우 JWT를 재발급하고, 재발급 후에도 에러가 발생할 경우 JWT를 새로 생성한 후 다시 시도해야 합니다.
JWT 수정 여부는 바로 적용이 안될 수 있어 1~2시간 후에 다시 시도하는 것을 권장합니다.
환불/취소 영수증
statusCode : 402
errorCode : AbnormalReceipt
message : This receipt has changed status. purchaseState: cancelled
이미 사용한 영수증 토큰
statusCode : 409
errorCode : UsedReceipt
message : This receipt has already been used. usedDate: 2018-02-15T04:01:50.000Z
이미 사용하거나 취소된 구독 상품의 영수증을 검증한 경우
statusCode : 409
errorCode : DuplicatedParameterException
message : Duplicated receipt, 중복된 receipt 입니다
GetReturnValuetoJSON(구독 상품)
{
"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",
"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);
파라미터
Value | Type | Description | default |
---|---|---|---|
productId | string | 구매하고자 하는 productId | - |
token | string | 구매 이후에 발행되는 영수증 토큰 | - |
receiptDescription | string | 추가로 저장하고자 하는 내용 | - |
isSubscription | bool | 해당 상품의 구독 상품 여부. true일 경우 구독 상품 | false |
설명
유니티에서 제공하는 IAP 서비스를 사용하지 않아도 제품 productID와 영수증 token을 알고 있으면 뒤끝 서버를 통해 영수증을 검증받을 수 있습니다.
Example
동기
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase(productID , receiptToken , "receiptDescription" , true);
if(validation.IsSuccess())
{
// 영수증 검증 성공 시 처리
} else {
// 영수증 검증 실패 시 처리
}
비동기
Backend.Receipt.IsValidateGooglePurchase(productID, receiptToken, "receiptDescription", false, (callback) => {
if(callback.IsSuccess()) {
// 영수증 검증 성공 시 처리
} else {
// 영수증 검증 실패 시 처리
}
});
SendQueue
SendQueue.Enqueue(Backend.Receipt.IsValidateGooglePurchase, productID, receiptToken, "receiptDescription", false, (callback) => {
if(callback.IsSuccess()) {
// 영수증 검증 성공 시 처리
} else {
// 영수증 검증 실패 시 처리
}
});
ReturnCase
Success cases
검증에 성공한 경우(구독 상품)
statusCode : 200
message : Success
returnValue : GetReturnValuetoJSON 참조
검증에 성공한 경우(일반 상품)
statusCode : 201
message : Success
returnValue : {"usedDate":"2018-10-15T05:17:49Z"}
Error cases
유효하지 않은 영수증 토큰
statusCode : 400
errorCode : BadParameterException
message : bad token, 잘못된 token 입니다
JWT 권한의 확인이 필요합니다.
권한을 제대로 설정했는데도 bad token 에러가 발생할 경우 JWT를 재발급하고, 재발급 후에도 에러가 발생할 경우 JWT를 새로 생성한 후 다시 시도해야 합니다.
JWT 수정 여부는 바로 적용이 안될 수 있어 1~2시간 후에 다시 시도하는 것을 권장합니다.
환불/취소 영수증
statusCode : 402
errorCode : AbnormalReceipt
message : This receipt has changed status. purchaseState: cancelled
이미 사용한 영수증 토큰
statusCode : 409
errorCode : UsedReceipt
message : This receipt has already been used. usedDate: 2018-02-15T04:01:50.000Z
이미 사용하거나 취소된 구독 상품의 영수증을 검증한 경우
statusCode : 409
errorCode : DuplicatedParameterException
message : Duplicated receipt, 중복된 receipt 입니다
GetReturnValuetoJSON(구독 상품)
{
"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",
"purchaseType":0,
"acknowledgementState":1
}