구글 인앱 결제 서버 검증 java - gugeul in-aeb gyeolje seobeo geomjeung java

알림: 2022년 8월 2일부터 모든 신규 앱은 결제 라이브러리 버전 4 이상을 사용해야 합니다. 2022년 11월 1일부터는 기존 앱의 모든 업데이트에도 결제 라이브러리 버전 4 이상이 요구됩니다. 자세히 알아보기

  • Google Play 결제 시스템
  • Google Play
  • Play 결제

개발자 페이로드컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

개발자 페이로드는 과거에 사기를 예방하고 구매에 기여한 사용자를 정확하게 분석하는 등 다양한 목적으로 사용되었습니다. Google Play 결제 라이브러리 버전 2.2 이상에서는 이전에 개발자 페이로드에 의존하도록 의도된 사용 사례가 이제 라이브러리의 다른 부분에서 완전히 지원됩니다.

이러한 지원이 제공되는 가운데 Google Play 결제 라이브러리 버전 2.2부터 개발자 페이로드는 지원이 중단됩니다. 개발자 페이로드와 연결된 메서드는 버전 2.2에서 지원 중단되었으며 버전 3.0에서 삭제되었습니다. 앱은 라이브러리의 이전 버전 또는 AIDL을 사용하여 발생한 구매의 개발자 페이로드를 계속 가져올 수 있습니다.

자세한 변경사항 목록은 Google Play 결제 라이브러리 2.2 출시 노트 및 Google Play 결제 라이브러리 3.0 출시 노트를 참조하세요.

참고: 아래에서 다루지 않은 사용 사례가 있다면 버그로 신고하세요.

구매 확인

구매가 신뢰할 수 있고, 위조되거나 재생되지 않았다는 것을 보장하기 위해 Google Play Developer API와 함께 구매 토큰(Purchase 객체의 getPurchaseToken() 메서드에서 가져옴)을 사용하여 구매가 신뢰할 수 있는 것인지 확인하는 것이 좋습니다. 자세한 내용은 사기 및 악용 방지를 참조하세요.

구매 기여 분석

많은 앱, 특히 게임은 구매를 시작한 인게임 캐릭터/아바타 또는 인앱 사용자 프로필이 해당 구매에 기여한 것을 정확하게 확인해야 합니다. Google Play 결제 라이브러리 2.2부터 앱은 구매 대화상자를 시작할 때 Google에 난독화된 계정과 프로필 식별자를 전달하고 구매를 회수할 때 반환하도록 할 수 있습니다.

BillingFlowParamssetObfuscatedAccountId()setObfuscatedProfileId() 매개변수를 사용하고 Purchase 객체에서 getAccountIdentifiers() 메서드를 사용하여 회수합니다.

참고 : 라이브러리 이전 버전의 setAccountId()(이름이 setObfuscatedAccountId()로 바뀜)를 사용하여 발생한 구매는 getAccountIdentifiers()로 반환되지 않습니다.

메타데이터를 구매에 연결

구매에 관한 메타데이터는 개발자가 유지하는 안전한 백엔드 서버에 저장하는 것이 좋습니다. 이러한 구매 메타데이터는 Purchase 객체의 getPurchaseToken()1 메서드를 사용하여 가져온 구매 토큰과 연결되어야 합니다. 구매가 성공적으로 이루어진 후에 getPurchaseToken()2가 호출될 때 구매 토큰과 메타데이터를 백엔드에 전달하여 이 데이터를 유지할 수 있습니다.

구매 흐름에 방해가 있을 때 메타데이터의 연결을 보장하려면 구매 대화상자를 실행하고 메타데이터를 사용자의 계정 ID, 구매 중인 SKU 및 현재 타임스탬프와 연결하기 전에 개발자의 백엔드 서버에 저장하는 것이 좋습니다.

getPurchaseToken()2가 호출되기 전에 구매 흐름이 중단되면 앱이 다시 시작되어 getPurchaseToken()4를 호출할 때 구매를 감지합니다. 그런 다음 Purchase 객체의 getPurchaseToken()6, getPurchaseToken()7 및 getPurchaseToken() 메서드에서 가져온 값을 백엔드 서버에 전송하여 메타데이터를 조회하고 구매 토큰과 연결하여 구매를 계속 처리할 수 있습니다.

최근에 DeveloperPayload(개발자가 임의로 만든 payload값) 기능이 구글에서 없어졌습니다. 때문에 해당 부분을 삭제했습니다. (2021-1-8)

 

 

서버 검증을 위해, 구글에 계정 만들고, 개발자 페이지에서 p12 파일 만들고 그러는 거는 검색해 보시면 쉽게 보실 수 있어요.
아래 페이지에 잘 정리 돼 있더라구요.
http://theeye.pe.kr/archives/2570

여기서는 c#으로 서버를 개발 했을 경우, 서버 검증하는 코드를 넣었습니다.
서버 검증은 3가지 방법이 있는데,
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#oauth-2.0-protocol

여기서는 [ServiceAccountCredential]을 이용한 방법입니다.

 

1. 클라이언트에서 서버로 결과 보내기

 아래 클라이언트 부분의 코드는 유니티엔진의 인앱 라이브러리를 이용한 것 입니다.

// 유저 인앱 완료 시, 실행되는 이벤트
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    //결과를 저장하기 위한 클래스 객체
    IapResultData result_data = new IapResultData();

    try
    {
        //인앱 구매 전, 게임 서버로부터 받은 payload값
        JObject json_object = JObject.Parse(e.purchasedProduct.receipt);            
        string payload = (string)json_object["Payload"];

        //결과값에 파싱에 문제되는 문자들이 섞여있습니다. 아래같이 직접 없애주세요.
        payload.Replace("\\", "");            

        JObject payload_object = JObject.Parse(payload);
        string payload_json = (string)payload_object["json"];


        JObject payload_json_object = JObject.Parse(payload_json);


        string payload_json_purchaseToken = (string)payload_json_object["purchaseToken"];
   
        
        result_data.m_data_id = m_curr_shop_data.m_id;
        result_data.m_pid = m_curr_shop_data.m_pid;
        result_data.m_token = payload_json_purchaseToken;

        //위 값들을 서버로 전송합니다~
        NetworkManager.Instance.SendBuyIapItemFinishReq(result_data);

    }
    catch (Exception ex)
    {
        return PurchaseProcessingResult.Complete;
    }
    return PurchaseProcessingResult.Complete;
}

 

 

2. 서버에서 받은 데이터로 검증하기

//클라이언트로 받은 결과를 저장하는 클래스 입니다.
public class IapResultData
{
    public int m_data_id;        //제 겜에서 사용하는 상품 데이터 id
    public string m_pid;        //구글 상품 id
    public string m_token;        //구글 인앱 결과에 있습니다.
}

//스레드로 처리하려다 보니 따로 request 객체를 만들어 사용했습니다. 몰라도 돼요
public class IapVerifyRequestData
{
    public IapResultData m_iap_result_data { get; set; }
    public jdUserToken m_user_token { get; set; }

    public IapVerifyRequestData()
    {
    }


    public IapVerifyRequestData(IapResultData data, jdUserToken user_token)
    {
        m_iap_result_data   = data;
        m_user_token        = user_token;
    }
}


//클라이언트로 받은 영수증 정보로 서버 검증을 합니다.
private void processRequest(IapVerifyRequestData iap_data)
{

    try
    {
    	//구글api 계정
        String serviceAccountEmail = "[email protected]";

    	//위 계정에서 만든 key.p12 파일.
        var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);

        ServiceAccountCredential credential = new ServiceAccountCredential(
           new ServiceAccountCredential.Initializer(serviceAccountEmail)
           {
               Scopes = new[] { AndroidPublisherService.Scope.Androidpublisher }
           }.FromCertificate(certificate));


        AndroidPublisherService android_service = new AndroidPublisherService(
            new BaseClientService.Initializer
            {
                HttpClientInitializer = credential,
                ApplicationName = "com.jd.games.BattleGame",
            }
        );


    	//검증 요청
        var request = android_service.Purchases.Products.Get("com.xxx.앱패키지명",
                                                        iap_data.m_iap_result_data.m_pid,
                                                        iap_data.m_iap_result_data.m_token);
        var purchase_state = request.Execute();



        IapVerifyResultData result_data = new IapVerifyResultData();

      //결과 예시 입니다.
      //{
      //    "consumptionState" : 1,    // 0 아직 컨슘 안됨, 1 컨슘됨
      //  "developerPayload" : "",
      //  "kind" : "androidpublisher#productPurchase",
      //  "purchaseState" : 0,    // 0 구매완료, 1 취소
      //  "purchaseTimeMillis" : "1454502702978"
      //}

        //유니티에서 인앱 구매하는 경우 자동으로 컨슘까지 됩니다.
        // 페이로드 값이 제대로 왔는지 확인합니다.
        // 페이로드 체크 안하시면 안해도 됩니다.

        result_data.m_shop_data_id      = iap_data.m_iap_result_data.m_data_id;
        result_data.m_purchase_state    = (int)purchase_state.PurchaseState;
        result_data.m_consume_state     = (int)purchase_state.ConsumptionState;                
        result_data.m_fail_reason       = FailReason.FAIL_REASON_NONE;




        //인앱 결과를 처리...

    }
    catch (Exception exc)
    {
        //문제 발생 시 처리
    }
}

공유하기

게시글 관리

구독하기겨울팥죽 여름빙수

'게임을 만들자 > 게임 서버(C#)' 카테고리의 다른 글

c#, SocketAsyncEventArgs 메모리 릭 현상  (0)2021.03.20C#, 외부 프로세스 실행  (0)2021.03.16윈도우 서버 TCPNoDelay, TcpAckFrequency 설정  (1)2020.12.23c# 각도, 라디안, 벡터 간 변환  (0)2020.10.24c#, 점 - 선분 간의 거리 구하기  (0)2020.10.13