구글 영수증 검증

구글영수증검증
뒤끝에서는 영수증 검증을 통해 확실한 결제 인증을 합니다.
뒤끝은 영수증 자체의 유효성과, 구매한 productId를 검증합니다.

초기 설정 - JWT(Json Web Token) 받기

영수증 검증을 위해서는 app publisher 권한을 가진 계정정보 획득이 우선 되어야 합니다.
이를 위해 구글 서비스 계정의 JWT를 생성, 뒤끝에 제공하여 영수증 검증을 진행할 수 있습니다.

1. 구글 API 엑세스 프로젝트 생성

구글 API 엑세스 프로젝트 생성
구글 API 엑세스 프로젝트 생성

2. 구글 서비스계정 생성, JWT 다운로드

프로젝트에 연결된 것을 확인하고, JWT를 위한 서비스계정을 생성합니다.

JWT 받기

구글 API 콘솔에 접속하여, 서비스계정을 생성합니다.

JWT 받기
JWT 받기

서비스계정의 비공개키 json 파일을 생성하면, 자동으로 json 파일이 다운됩니다. 다운받은 json파일을 뒤끝콘솔 > 서버설정 > 구글 서비스에 업로드합니다.

JWT 받기

3. 서비스계정 액세스권한 부여

뒤끝 서버의 영수증 검증을 위해, 위에서 생성한 서비스계정의 액세스 권한을 부여(혹은 수정) 합니다.

서비스계정 액세스 권한 설정

영수증 검증을 위해 해당 게임을 추가하고 재무데이터 보기, 주문 관리를 필수로 선택 후, 액세스권한 설정을 마무리합니다.

서비스계정 액세스 권한 설정

뒤끝 영수증 검증

유니티에서 지원하는 IAP 서비스의 IStoreListener.ProcessPurchase() 에서 구매한 상품에 대한 영수증을 받아 뒤끝 서버를 통해 영수증 검증을 받을 수 있습니다.

IsValidateGooglePurchase ( String receipt, String receiptDescription ) -> BackendReturnObject
IsValidateGooglePurchase ( String receipt, String receiptDescription, Function callback) -> void

// example
// unity iap servcie process 
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) 
{
    /*
    뒤끝 영수증 검증 처리
    */
    BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase ( args.purchasedProduct.receipt , "receiptDescription" );
    
    // 영수증 검증에 성공한 경우
    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;
}

Parameters

ValueTypeDescription
receiptStringPurchasing.PurchaseEventArgs.purchasedProduct.receipt
receiptDescriptionString추가로 저장하고자 하는 내용

Return BRO Cases

  • 성공한 경우
    statusCode : 201
    message : Success
    returnValue : {"usedDate":"2018-10-15T05:17:49Z"}

  • 이미 사용한 영수증 토큰
    statusCode : 409
    errorCode : UsedReceipt
    message : This receipt has already been used. usedDate: 2018-02-15T04:01:50.000Z

  • 환불/취소 영수증
    statusCode : 402
    errorCode : AbnormalReceipt
    message : This receipt has changed status. purchaseState: cancelled

  • 유효하지 않은 영수증 토큰
    statusCode : 400
    errorCode : BadParameterException
    message : bad token, 잘못된 token 입니다

    3번 항목의 JWT 권한의 확인이 필요합니다.
    권한을 제대로 설정했는데도 bad token 에러가 발생할 경우 JWT를 재발급하고, 재발급 후에도 에러가 발생할 경우 JWT를 새로 생성한 후 다시 시도해야 합니다.
    JWT 수정여부는 바로 적용이 안될 수 있어 1~2시간 후에 다시 시도하는 것을 권장합니다.

유니티 IAP 서비스를 사용하지 않고 뒤끝 영수증 검증 사용하기

유니티에서 제공하는 IAP 서비스를 사용하지 않아도 제품 productID와 영수증 token을 알고 있으면 뒤끝 서버를 통해 영수증을 검증받을 수 있습니다.

동기

IsValidateGooglePurchase ( String productId, String token, String receiptDescription ) -> BackendReturnObject

// example
BackendReturnObject validation = Backend.Receipt.IsValidateGooglePurchase ( 
productID , receiptToken , "receiptDescription" );
if(validation.IsSuccess())
{
  // 영수증 검증 성공 시 처리
}
else
{
  // 영수증 검증 실패 시 처리
}

비동기

IsValidateGooglePurchase ( String productId, String token, String receiptDescription, Function callback) -> void

// example
Backend.Receipt.IsValidateGooglePurchase ( 
productID , receiptToken , "receiptDescription", callback => {
  if(callback.IsSuccess())
  {
    // 영수증 검증 성공 시 처리
  }
  else
  {
    // 영수증 검증 실패 시 처리
  }
} );