# Google Play 유저의 구매 완료 요청에 대한 검증

## 기본 정보 <a href="#undefined" id="undefined"></a>

1. Google Play에서 유저가 구매한 결제 건을 검증합니다.
2. <mark style="color:red;">**영수증 검증**</mark> 및 기본적인 어뷰징 체크가 진행됩니다.
   1. 영수증 변조 여부 확인
   2. [Google 서버에 결제 상태 조회](https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products/get?hl=en)

<table><thead><tr><th width="204">항목</th><th>내용</th><th>비고</th></tr></thead><tbody><tr><td>호출주체</td><td>게임서버(s2s API)</td><td> </td></tr><tr><td>도메인</td><td><a href="../../api#inlineextension-api">각 환경별 도메인</a></td><td> </td></tr><tr><td>인증방식</td><td><a href="../../api#inlineextension">HTTP 헤더 인증 정보</a></td><td> </td></tr><tr><td>HTTP 메소드</td><td>POST</td><td> </td></tr><tr><td>Content-Type</td><td>application/json</td><td> </td></tr></tbody></table>

## Request <a href="#request" id="request"></a>

### HTTP Request end point <a href="#http-request-end-point" id="http-request-end-point"></a>

```
POST https://각 환경별 빌링시스템 도메인/billing/api-game/v1/purchase/google/play/consumable/verify
```

### &#x20;HTTP Request Header

* 아래의 HTTP Header들을 API 요청에 포함시켜야합니다.

| 이름                | 값      | 비고                         |
| ----------------- | ------ | -------------------------- |
| X-Req-Pjid        | 프로젝트ID | 기술 PM 등에게 전달받은 프로젝트ID      |
| X-Auth-Access-Key | 인증용 키  | 기술 PM 등에게 전달받은 해당 게임용 인증 키 |

&#x20;

### HTTP Request Parameter <a href="#http-request-parameter" id="http-request-parameter"></a>

* 아래의 공통 파라미터 외에 하단의 각 결제 스토어 특화 파라미터들도 같이 요청해야 합니다.
* Content-Type: **application/json** 으로 요청되어야 합니다.
  * 영수증 관련 정보들의 길이 및 escape 등의 문제 때문

<table data-full-width="true"><thead><tr><th width="179">이름</th><th width="158">데이터 타입</th><th width="84">필수 Y/N</th><th>설명</th><th>예</th></tr></thead><tbody><tr><td>reqId</td><td>String(100)</td><td>Y</td><td>중복 요청을 막거나 요청 추적을 위한 값</td><td>userId_UUID 등과 같은 방식으로 생성</td></tr><tr><td>pjid</td><td>String(20)</td><td>Y</td><td><p>프로젝트ID</p><ul><li>동일 게임이라면 서비스ID가 여러개일 수 있지만 프로젝트ID는 같음</li></ul></td><td>1004</td></tr><tr><td>boid</td><td>String(20)</td><td>Y</td><td>빌링 시스템 구매 예약 API를 통해서 생성된 ID</td><td>1234</td></tr><tr><td>productId</td><td>String(200)</td><td><mark style="color:red;"><strong>N</strong></mark></td><td>구매 진행하는 상품ID(SKU와 같은 값)<br> - 옵션 값으로 전달되는 경우에만 boid의 예약 시점의 상품과 동일한지 추가 검사 진행</td><td></td></tr><tr><td>playerId</td><td>String(50)</td><td>Y</td><td>주문 완료를 진행하는 유저ID </td><td> </td></tr><tr><td>microPrice</td><td>long</td><td>Y</td><td><p>구매 금액 <mark style="color:red;"><strong>micro단위</strong></mark></p><ul><li>사용측의 부동 소수점 오차등을 방지하기 위해서 micro단위 사용</li></ul></td><td>구매 금액 micro단위(예. $0.99는 990,000 µ$. 100만을 곱함)</td></tr><tr><td>currency</td><td>String(10)</td><td>Y</td><td><p>구매 금액의 화폐 단위</p><ul><li>3글자 포맷의 <a href="https://ko.wikipedia.org/wiki/ISO_4217">ISO-4217</a></li></ul></td><td>USD</td></tr></tbody></table>

#### 결제 스토어 특화 파라미터

<table data-full-width="true"><thead><tr><th>이름</th><th width="162">데이터 타입</th><th width="393">설명</th><th>예시</th></tr></thead><tbody><tr><td>productDetailsJson</td><td>String</td><td><p>구매하는 상품의 구글 상세 내용 json</p><ul><li>앱에서 구현시 <a href="https://developer.android.com/reference/com/android/billingclient/api/ProductDetails">참고 링크</a></li></ul></td><td> </td></tr><tr><td>purchaseOriginalJson</td><td>String</td><td><p>구매 원본 Json</p><ul><li>앱에서 구현시 <a href="https://developer.android.com/reference/com/android/billingclient/api/Purchase#getOriginalJson()">참고 링크</a></li><li>base64 디코딩된 receiptData</li></ul></td><td> {"orderId":"GPA.3339-8889-4444-64541","packageName":"com.hybeim.블라블라","productId":"google_com.hybeim.블라블라_gem55000\ .... 블라블라</td></tr><tr><td>purchaseSignature</td><td>String</td><td><p>구매 Signature</p><ul><li>앱에서 구현시 <a href="https://developer.android.com/reference/com/android/billingclient/api/Purchase#getSignature()">참고 링크</a></li></ul></td><td> </td></tr></tbody></table>

## Response <a href="#response" id="response"></a>

응답은 JSON형태로 전달됩니다.

### Sample <a href="#hardbreak-success-sample" id="hardbreak-success-sample"></a>

#### Success sample <a href="#hardbreak-success-sample" id="hardbreak-success-sample"></a>

* 성공으로 응답하면 게임 서버는 **유저에게 아이템 및 재화를 지급**하고, 이후 Google 소모처리와 빌링 완료처리 API를 호출해서 최종 완료해주시면 됩니다.
  * 게임 서버에서는 필요 로그를 게임DB 또는 로그 DB에 저장해주시는게 좋습니다.

```json
{
    "resultCode": "SUCCESS",
    "resultMessage": "success. boid:'1'. You must process product(or items) give to user 'playerId' and request completeWithConsume billing API."
}
```

#### Error sample <a href="#error-sample" id="error-sample"></a>

* 다른 boid의 데이터를 검증 시도(동일 영수증 중복 사용 요청)
  * 에러코드 ALREADY\_EXIST\_DATA로 응답하며 existPurchaseInfo가 추가로 응답됩니다.
    * 게임 서버에서 추가로 기능 구현할 때 사용 가능합니다.

| key            | data type   | description                                                                                                         | example                                                                             |
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| boid           | String(20)  | 빌링 시스템의 주문ID                                                                                                        | 4                                                                                   |
| purchaseStatus | String(30)  | 빌링시스템에 구매 상태                                                                                                        | [코드 표 참고](https://bighitcorp.atlassian.net/wiki/spaces/HYBIMINFRA/pages/3660448276) |
| imid           | String(40)  | <p>IMID</p><ul><li>하이브IM의 IMID로 로그인 후 얻을 수 있음</li><li>PJID 내 사용자를 식별하는 고유 Key</li></ul>                             | `aaaabbbb-ccccddd-fffccc-tttggg`                                                    |
| playerId       | String(50)  | <p>구매를 진행하는 유저의 ID</p><ul><li><p>게임에서 결제 스콥을 관리하고 싶은 id값으로 사용</p><ul><li>하이브 IM의 imid값이 될 수도 있음</li></ul></li></ul> |                                                                                     |
| paymentOrderId | String(100) | <p>결제가 진행된 payment의 주문ID</p><ul><li>스토어/마켓에서 발급한 주문ID</li></ul>                                                     |                                                                                     |
| productId      | String(200) | <p>구매 진행하는 상품ID</p><ul><li>SKU와 같은 값</li></ul>                                                                      |                                                                                     |

```json
{
   "resultCode":"ALREADY_EXIST_DATA",
   "resultMessage":"This receipt information has already been used. | paymentOrderId:'GPA.3322-9740-8007-41997' | request boid:'646' | exist boid:'645' | exist boid purchaseStatus:'VERIFY_SUCCESS'",
   "traceId":"b_4766e9364f9b",
   "resultData":{
      "existPurchaseInfo":{
         "boid":"645",
         "purchaseStatus":"VERIFY_SUCCESS",
         "imid":"EXCDQKKTBS5NYLTL53R3",
         "playerId":"EXCDQKKTBS5NYLTL53R3",
         "paymentOrderId":"GPA.3322-9740-8007-41997",
         "productId":"google_com.hybeim.platform_test003"
      }
   }
}
```

* 검증 성공한 boid에 다시 검증 요청

{% code overflow="wrap" %}

```json
{
    "resultCode": "INVALID_PARAMETER",
    "resultMessage": "'boid: 1' purchaseStatus is not RESERVED. The current value is 'VERIFY_SUCCESS'"
}
```

{% endcode %}

* 변조된 영수증 데이터(purchaseOriginalJson)으로 요청

{% code overflow="wrap" %}

```json
{
    "resultCode": "NOT_VALID_RECEIPT",
    "resultMessage": "The check for alteration of the receipt(purchaseOriginalJson) failed(not valid). reserved userId:'userId' | purchaseOriginalJson:'{\"1orderId\":\"GPA.3323-4443-4781-49315\",\"packageName\":\"com.hybeim.intheseom\",\"productId\":\"seom_tany_100022\",\"purchaseTime\":1698116965733,\"purchaseState\":0,\"purchaseToken\":\"bkdcnncmkeegcekfcmjaeppj.AO-J1OxMzEQ0nV5GJvj1ytipiX90uRpWtyyn4cgozqFYeRp1euFxm2lFbH3XR8mPBPWeXgIIsxhkpMvDeey1_lRJ7WLmFMJdbg\",\"quantity\":1,\"acknowledged\":false}'"
}
```

{% endcode %}

* 이미 완료처리에 사용했던 영수증 데이터로 재 완료 요청(영수증 재 사용 중복요청 어뷰징)

{% code overflow="wrap" %}

```json
{
    "resultCode": "NOT_ALLOW_PURCHASE",
    "resultMessage": "This receipt information has already been used. | storeOrderId:'GPA.3323-4443-4781-49315' | request boid:'6' | exist boid:'1'"
}
```

{% endcode %}

&#x20;

### 에러 코드 정의 <a href="#undefined" id="undefined"></a>

* 아래와 같은 에러코드가 resultCode에 응답될 수 있습니다. ([참고 링크](https://developers.hybeim.com/api/billing/api/errorcode))
* 아래의 에러코드들은 모든 빌링 API에서 발생 가능한 에러 코드들입니다.
  * 해당 에러코드 외에 각 API에서만 발생가능한 에러코드들도 있습니다.
  * 각 에러 코드를 바탕으로 필요시 비즈니스 로직을 작성하시면 됩니다.

<table><thead><tr><th width="287">에러코드</th><th>비고</th></tr></thead><tbody><tr><td>SYSTEM_ERROR</td><td><p>빌링 API에서 발생한 시스템 에러</p><ul><li>HTTP 상태코드 500 리턴</li></ul></td></tr><tr><td>NOT_ALLOW_AUTH</td><td>API 인증 헤더 누락이거나 기타 권한이 없는 요청에서 발생</td></tr></tbody></table>

* 아래 에러 코드들은 해당 API에서 발생 가능한 추가 에러 코드들입니다.

<table><thead><tr><th width="286">에러코드</th><th>비고</th></tr></thead><tbody><tr><td>INVALID_PARAMETER</td><td> </td></tr><tr><td>ALREADY_EXIST_DATA</td><td><p>미 데이터가 존재</p><ul><li>동일 영수증 정보로 이미 데이터가 존재와 같은 케이스</li></ul></td></tr><tr><td>NOT_ALLOW_PURCHASE</td><td><p>구매 기능을 사용할 수 없는 상황</p><ul><li>빌링 시스템에 스토어 관련 설정 누락</li><li>다른 유저의 결제 예약건을 완료 처리 시도</li></ul></td></tr><tr><td>NOT_VALID_RECEIPT</td><td><p>구매 영수증 검증</p><ul><li>영수증 변조 여부 검사와 같은 어뷰징 방지 검사</li><li>구글 API로부터 구매의 현재 상태를 검증</li></ul></td></tr><tr><td>EXTERNAL_API_ERROR</td><td><p>빌링 API가 호출하는 외부 API가 에러인 경우</p><ul><li>예) 구글 API 장애로 구매의 상태 정보를 받아오지 못하는 경우</li><li>게임 서버는 지수 백오프 알고리즘으로 재 시도하거나 클라이언트의 미 처리 영수증 처리 기능으로 처리할 수 있음</li></ul></td></tr></tbody></table>

### &#x20;요청 curl 샘플 <a href="#hardbreak-curl" id="hardbreak-curl"></a>

* 도메인 및 인증 헤더 등은 변경 필요

```bash
curl --location 'https://billing-game-api-dev.pub-dev.hybegames.io/billing/api-game/v1/purchase/google/play/consumable/verify' \
--header 'X-Req-Pjid: 9001' \
--header 'X-Auth-Access-Key: test-auth-key' \
--header 'Content-Type: application/json' \
--data '{
   "reqId":"userId_verify_170083bf-8d85-433f-8bd2-67674b7482db-2023-11-24-04",
   "pjid":"9001",   
   "boid":"1",
   "microPrice":2200000000,
   "currency":"JPY",   
   "playerId":"playerId",
   "purchaseSignature":"Nk2aSXeM17GUiTBNax0dWhNb5nJEjvk1VTBzlfRh8tm5gEl78sof96oIFlrqCw9pFnZAxbVo7NjZyOBs9hzYWZDpMBicFADngJLYKcGY+7ni4+4YkmzDBHwlxWOTiJMmrAmCLHKDAOUWpZo9m3EH9BH3sZGoLsJiSLkgPv8p9D17lQi/U50bhG1NNDmiyX0TxC/sT6JuFWoLSFTQYno0Up/WcgqATK8fTJ+8A7yP7eOxKO+50gFOZ1XkF1fKUhcczi0t1yUKk8CjA+ZawghcNKhy1/pDBTLfU93b3/pr+x0j3GyJT45S/Yf1PxXrF2PjcUvXyV5TBdY6ztk6C1vwAg==",
   "purchaseOriginalJson":"{\"orderId\":\"GPA.3323-4443-4781-42315\",\"packageName\":\"com.hybeim.intheseom\",\"productId\":\"seom_tany_100022\",\"purchaseTime\":1698116965733,\"purchaseState\":0,\"purchaseToken\":\"bkdcnncmkeegcekfcmjaeppj.AO-J1OxMzEQ0nV5GJvj1ytipiX90uRpWtyyn4cgozqFYeRp1euFxm2lFbH3XR8mPBPWeXgIIsxhkpMvDeey1_lRJ7WLmFMJdbg\",\"quantity\":1,\"acknowledged\":false}",
   "productDetailsJson":"{\"some\":\"블라블라\"}"
}'
```
