# 06. 원스토어 인앱결제 서버 API (API V7)

## **개요**  <a href="#id-06.-api-apiv7" id="id-06.-api-apiv7"></a>

원스토어 인앱결제 서버 API란 원스토어에서 결제된 인앱 상품의 데이터를 조회하거나 결제 상태를 변경하기 위한 Open API를 말합니다.

해당 API를 사용하기 위해서는 OAuth 인증이 필요합니다.

### **HOST URL**  <a href="#id-06.-api-apiv7-hosturl" id="id-06.-api-apiv7-hosturl"></a>

본 문서에서 사용되는 Host Domain은 아래와 같습니다.

| 원스토어 환경 | Host Domain                                                    |
| ------- | -------------------------------------------------------------- |
| 검증(개발)  | <mark style="color:blue;">https://sbpp.onestore.net</mark>     |
| 상용      | <mark style="color:blue;">https://iap-apis.onestore.net</mark> |

{% hint style="info" %}
본 문서에서 사용하는 도메인은 모든 국가에서 공용으로 사용할 수 있는 도메인입니다.&#x20;

대한민국에서만 서비스 중인 기존 앱/게임의 도메인은 과거 도메인을 그대로 사용하시면 됩니다.&#x20;

(apis.onestore.co.kr)
{% endhint %}

### 마켓 구분 코드&#x20;

* Request header에 마켓 구분 코드 (x-market-code)가 추가 되었습니다.&#x20;
* 원스토어 글로벌 플랫폼에 서비스 되는 경우 마켓 구분 코드를 헤더에 추가하여 요청해야 합니다.&#x20;
* 마켓 구분 코드가 없는 경우에는 원스토어(기본 값)로 호출됩니다.&#x20;

| 마켓 구분 코드 | 서비스 대상      |
| -------- | ----------- |
| MKT\_ONE | 원스토어 (기본 값) |
| MKT\_GLB | 원스토어 글로벌    |

{% hint style="danger" %}
마켓 구분 코드에 따라 서버 API 응답으로 제공되는 시간의 기준이 다릅니다.&#x20;

MKT\_ONE : UTC+09

MKT\_GLB : UTC+00 &#x20;
{% endhint %}

## **원스토어 OAuth**  <a href="#id-06.-api-apiv7-oauth" id="id-06.-api-apiv7-oauth"></a>

### **OAuth 개요** <a href="#id-06.-api-apiv7-oauth" id="id-06.-api-apiv7-oauth"></a>

원스토어 서버 Open API를 연동하기 위해서는 OAuth 인증이 필요합니다.

* 원스토어 OAuth v2의 이해
  * AccessToken은 원스토어의 Server Open API를 통하여 발급받을 수 있는 값으로 원스토어에서 제공하는 Server Open API 호출시 인증값으로 사용됩니다.
  * AccessToken은 기본적으로 3600초의 유효기간이 있으며, 유효기간이 만료 되거나 600초 미만으로 남은 경우에 getAccessToken()을 호출하면 새로운 AccessToken이 발급됩니다.
    * 기존의 AccessToken도 유효기간이 끝날 때까지는 사용이 가능합니다.
    * 다수의 AccessToken이 발행되는 방식이므로 개발사의 서비스 인스턴스 별로 서로 다른 AccessToken을 취득하고 사용할 수 있는 형태가 됩니다. \
      \

*   일반적인 연동 흐름은 다음과 같습니다.\


    <figure><img src="https://1837360763-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fot0z57AnnXZ02C5qyePV%2Fuploads%2FrewUDEKtBdLoX8gRJs9L%2Fimage.png?alt=media&#x26;token=86dc5393-c873-48cb-af6a-1488ff07b0dd" alt=""><figcaption></figcaption></figure>
* AccessToken을 얻는 과정(1번)은 API 호출 시 인증 오류가 발생했을 때 호출하면 됩니다. \


원스토어의 인앱결제 서버 API의 호출을 위해서는 Authorization Bearer 스킴을 사용하며 호출 샘플은 아래와 같습니다.

```
GET /v7/apps/com.onestore.game.goindol/purchases/inapp/products/product01/SANDBOXT000120004476 HTTP/1.1
Host: sbpp.onestore.net
Authorization: Bearer 680b3621-1234-1234-1234-8adfaef561b4
```

Authorization 헤더에는 위 예제와 같이 Bearer + 공백 + AccessToken이 대소문자 구분하여 정확히 입력되어야 합니다.\
Bearer의 값은 getAccessToken()을 호출하여 발급받은 AccessToken 값입니다.

잘못된 예&#x20;

```
Authorization: 680b3621-1234-1234-1234-8adfaef561b4
Authorization: bearer 680b3621-1234-1234-1234-8adfaef561b4
Authorization: Bearer <680b3621-1234-1234-1234-8adfaef561b4>
Authorization:Bearer680b3621-1234-1234-1234-8adfaef561b4
```

\


### **OAuth API 상세** <a href="#id-06.-api-apiv7-oauthapi" id="id-06.-api-apiv7-oauthapi"></a>

**client\_id 및 client\_secret 확인**

Client\_id와 Client\_secret 값은 "라이선스 관리" 메뉴에서 확인할 수 있습니다.



### **AccessToken 발급**

* **URI :** /v7/oauth/token
* **Method:** POST
*   **Request Parameter:** Form 형식

    | Parameter Name | Description                        | Example                                      |
    | -------------- | ---------------------------------- | -------------------------------------------- |
    | client\_id     | 개발자센터에서 앱 등록 시 발급된 Client id 값     | 0000042301                                   |
    | client\_secret | 개발자센터에서 앱 등록 시 발급된 client secret 값 | vxIMAGcVz3DAx20uDBr/IDWNJAPNHFl7YruF4uxB6BI= |
    | grant\_type    | 고정값                                | client\_credentials                          |
*   **Request Header** :&#x20;

    | Parameter Name | Description                                                             | Example                                         |
    | -------------- | ----------------------------------------------------------------------- | ----------------------------------------------- |
    | Content-Type   | Http 요청시 Content Type으로 반드시 application/x-www-form-urlencoded 로 설정되어야 함 | Content-Type: application/x-www-form-urlencoded |
    | x-market-code  | 마켓 구분 코드                                                                | x-market-code: MKT\_GLB                         |
*   **Example**&#x20;

    ```
    POST /v7/oauth/token HTTP/1.1
    Host: apis.onestore.com
    Content-Type: application/x-www-form-urlencoded;charset=UTF-8
    x-market-code: MKT_GLB

    grant_type=client_credentials&client_id=com.onestore.game.goindol&client_secret=vxIMAGcVz3DAx20uDBr/IDWNJAPNHFl7YruF4uxB6BI=
    ```
*   **Response Body** : JSON 형식

    | Element Name  | Data Type | Data Size | Description                |
    | ------------- | --------- | --------- | -------------------------- |
    | client\_id    | String    | 255       | OAuth 인증 client\_id        |
    | access\_token | String    | 36        | AccessToken                |
    | token\_type   | String    | 6         | bearer 방식만 제공              |
    | expires\_in   | Integer   | 10        | token 만료기한, 단위 : 초(second) |
    | scope         | String    | 1024      | token 사용 범위                |
*   **Example :**&#x20;

    ```json
    {
        "client_id":"0000000001",
        "access_token":"680b3621-1234-1234-1234-8adfaef561b4",
        "token_type":"bearer",
        "expires_in":3010,
        "scope":"DEFAULT"
    }
    ```


*   **발급 예시** :&#x20;

    {% code overflow="wrap" %}
    ```bash
    curl -v -X POST https://sbpp.onestore.net/v7/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "x-market-code: MKT_GLB" \
    -d "grant_type=client_credentials" \
    -d "client_id=0000000001" \
    -d "client_secret=vxIMAGcVz3DAx20uDBr/IDWNJAPNHFl7YruF4uxB6BI="
    > POST /v7/oauth/token HTTP/1.1
    > Host: sbpp.onestore.net
    > User-Agent: curl/7.43.0
    > Accept: */*
    > Content-Type: application/x-www-form-urlencoded;charset=UTF-8
    > Content-Length: 35
    >
    * upload completely sent off: 35 out of 35 bytes
    < HTTP/1.1 200 200
    < Date: Wed, 02 May 2018 02:52:42 GMT
    < Server: Apache
    < Connection: close
    < Transfer-Encoding: chunked
    < Content-Type: application/json;charset=UTF-8
    <
    * Closing connection 0
    {"client_id":"0000000001","access_token":"680b3621-1234-1234-1234-8adfaef561b4","token_type":"bearer","expires_in":3600,"scope":"DEFAULT"}
    ```
    {% endcode %}



검증(개발) 환경과 상용환경의 AccessToken은 독립적으로 관리되므로, 환경 별로 AccessToken을 분리하여 관리하셔야 합니다.

## **서버 API 상세**  <a href="#id-06.-api-apiv7-api" id="id-06.-api-apiv7-api"></a>

{% hint style="info" %}
2025년 3월 20일 개발자센터 개편의 영향으로 서버 API 요청 시 이용되던 packageName은 clientId로 변경되었습니다.&#x20;

* 3월 20일 이전 등록된 앱의 경우 클라이언트 ID 와 패키지명이 동일하므로, 변경에 대한 영향도가 없습니다.&#x20;
* 3월 20일 이후 등록된 앱의 경우 클라이언트 ID 가 PID와 동일하므로 참고 부탁 드립니다.&#x20;
{% endhint %}

### getPurchaseDetails (구매상품 상세조회) <a href="#id-06.-api-apiv7-getpurchasedetails" id="id-06.-api-apiv7-getpurchasedetails"></a>

* **Desc :** 구매한 원스토어 관리형 상품의 상세정보를 조회합니다. 구매완료 시 전달받은 원스토어 purchaseToken(구매 토큰)을 이용하여 조회하여야 합니다. 월정액 상품의 purchaseToken으로 조회 시, 조회 실패(NoSuchData) 응답이 전달됩니다.
* **URI :** /v7/apps/{clientId}/purchases/inapp/products/{productId}/{purchaseToken}
* **Method :** GET
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body** : JSON 형식

    | Element Name     | Data Type | Data Size | Description                                    |
    | ---------------- | --------- | --------- | ---------------------------------------------- |
    | consumptionState | Integer   | 1         | 구매한 상품의 소비상태 (0: 미소비, 1: 소비)                   |
    | developerPayload | String    | 200       | 개발사가 제공한 결제 고유 식별자                             |
    | purchaseState    | Integer   | 1         | 구매상태 ( 0: 구매완료, 1: 취소완료)                       |
    | purchaseTime     | Long      | 13        | 구매시간 (ms)                                      |
    | purchaseId       | String    | 20        | 구매 ID                                          |
    | acknowledgeState | Integer   | 1         | 구매확인 상태( 0: Not Acknowledged, 1: Acknowledged) |
    | quantity         | Integer   | 2         | 구매 수량                                          |
*   **Example:**

    ```
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "consumptionState": 0,
        "developerPayload": "developerPayload",
        "purchaseState": 0,
        "purchaseTime": 1345678900000,
        "purchaseId": "17070421461015116878",
        "acknowledgeState": 0,
    	"quantity": 2
    }
    ```

### getRecurringPurchaseDetails (월정액 상품 구매 상세조회)  <a href="#id-06.-api-apiv7-getrecurringpurchasedetails" id="id-06.-api-apiv7-getrecurringpurchasedetails"></a>

* **Desc :** 구매한 원스토어 월정액 상품의 자동결제 상태와 마지막 구매상태의 상세정보를 조회합니다. 구매완료 시 전달받은 원스토어 purchaseToken(구매 토큰)을 이용하여 조회하여야 합니다. 관리형 상품의 purchaseToken으로 조회 시, 조회 실패(NoSuchData) 응답이 전달됩니다.
* **URI :** /v7/apps/{clientId}/purchases/auto/products/{productId}/{purchaseToken}
* **Method :** GET
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body** : JSON 형식

    | Element Name      | Data Type | Data Size | Description                                            |
    | ----------------- | --------- | --------- | ------------------------------------------------------ |
    | startTime         | Long      | 13        | 구매한 상품의 이용시작시간(ms)                                     |
    | expiryTime        | Long      | 13        | 구매한 상품의 이용종료시간(ms)                                     |
    | nextPaymentTime   | Long      | 13        | 다음 자동결제시간                                              |
    | autoRenewing      | boolean   | -         | 이용종료시간(expiryTime)가 지난 후 자동결제 여부                       |
    | cancelReason      | Integer   | 1         | 해지사유 (0 : 고객요청에 의한 해지, 1 : 기타 시스템 처리로 인한 해지 )          |
    | cancelledTime     | Long      | 13        | 해지시간(ms)                                               |
    | acknowledgeState  | Integer   | 1         | 월정액 상품의 구매확인 상태( 0: Not Acknowledged, 1: Acknowledged) |
    | lastPurchaseId    | String    | 20        | 마지막에 자동결제된 구매ID                                        |
    | lastPurchaseState | Integer   | 1         | 마지막에 자동결제된 구매상태 (0 : 구매완료, 1 : 취소완료)                   |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "startTime": 1345678900000,
        "expiryTime": 1345678999999,
        "nextPaymentTime": 1345688000000,
        "autoRenewing": true,
        "cancelReason": 1,
        "cancelledTime": 1345679000000,
        "acknowledgeState": 0,
        "lastPurchaseId":"15081718460701027851",
        "lastPurchaseState": 0
    }
    ```

    고객이 월정액 상품에 대하여 컨텐츠 사용 권한이 있는지 아래의 조건으로 판단이 가능합니다.

    * 현재시간이 expiryTime(구매한 상품의 이용종료시간)보다 작거나 같고, lastPurchaseState(마지막에 자동결제된 구매상태)가 0(구매완료) 상태인경우\
      Ex) expiryTime >= 현재시간 AND lastPurchaseState == 0

### acknowledgePurchase (구매상품 확인)  <a href="#id-06.-api-apiv7-acknowledgepurchase" id="id-06.-api-apiv7-acknowledgepurchase"></a>

* **Desc :** 구매한 관리형 또는 월정액 상품을 구매확인 상태로 변경한다.
* **URI :** /v7/apps/{clientId}/purchases/all/products/{productId}/{purchaseToken}/acknowledge
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
*   **Request Body** : JSON 형식

    | Element Name     | Data Type | Required | Description |
    | ---------------- | --------- | -------- | ----------- |
    | developerPayload | String    | false    | <p><br></p> |
*   **Example :**

    ```json
    {
        "developerPayload": "your payload"
    }
    ```
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```



원스토어는 3일 이내에 acknowledgePurchase API가 호출되지 않은 구매 건을 자동으로 취소합니다.

이에 따라 개발사는 반드시 해당 API를 호출해야 하며, SDK API 또는 서버 API를 통해 처리할 수 있습니다.

단, consumePurchase API가 호출된 구매 건은 acknowledge가 되었다고 판단하고 구매 취소를 하지 않습니다.

### consumePurchase (구매상품 소비) <a href="#id-06.-api-apiv7-consumepurchase" id="id-06.-api-apiv7-consumepurchase"></a>

* **Desc :** 구매한 관리형 인앱 상품을 소비 상태로 변경한다.
* **URI :** /v7/apps/{clientId}/purchases/inapp/products/{productId}/{purchaseToken}/consume
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
*   **Request Body** : JSON 형식

    | Element Name     | Data Type | Required | Description |
    | ---------------- | --------- | -------- | ----------- |
    | developerPayload | String    | false    | <p><br></p> |
*   **Example :**

    ```json
    {
        "developerPayload": "your payload"
    }
    ```
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```



### cancelRecurringPurchase (자동결제 해지요청)  <a href="#id-06.-api-apiv7-cancelrecurringpurchase" id="id-06.-api-apiv7-cancelrecurringpurchase"></a>

* **Desc :** 월정액 상품의 자동결제 해지를 요청한다. 구독형 상품의 purchaseToken이용 시, 조회 실패(NoSuchData) 응답이 전달됩니다.
* **URI :** /v7/apps/{clientId}/purchases/auto/products/{productId}/{purchaseToken}/cancel
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```

### reactiveRecurringPurchase (자동결제 해지 취소요청)  <a href="#id-06.-api-apiv7-reactiverecurringpurchase" id="id-06.-api-apiv7-reactiverecurringpurchase"></a>

* **Desc :** 월정액 상품의 자동결제 해지요청을 취소한다. 구독형 상품의 purchaseToken이용 시, 조회 실패(NoSuchData) 응답이 전달됩니다.
* **URI :** /v7/apps/{clientId}/purchases/auto/products/{productId}/{purchaseToken}/reactivate
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```

### getVoidedPurchases (구매취소내역 조회)  <a href="#id-06.-api-apiv7-getvoidedpurchases" id="id-06.-api-apiv7-getvoidedpurchases"></a>

* **Desc :** 구매취소내역을 조회한다.
* **URI :** /v7/apps/{clientId}/voided-purchases
* **Method :** GET
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
* **Request Parameter (Optional) :** Query String 형식\

  * String continuationKey : 구매취소 건이 많을 경우 원스토어 서버에서 이 값을 반환합니다. \
    응답에 continuationKey가 있을 경우, getVoidedPurchases를 다시 호출하면서 continuationKey를 전달하면 추가 구매취소 내역을 전달받을 수 있습니다. (Data Size : 41)
  * String startTime : 구매취소일시 검색 시작시간 (milliseconds). \
    현재시간기준 과거 1개월까지만 설정가능하며 startTime 단독으로 사용할 경우 endTime은 startTime기준 미래 1개월로 설정됩니다. (Data Size : 13)
  * String endTime :  구매취소일시 검색 종료시간 (milliseconds). \
    현재시간보다 클 수 없으며 endTime 단독으로 사용할 경우 startTime은 endTime기준 과거 1개월로 설정됩니다. (Data Size : 13)
  * unsigned integer maxResults : 최대조회건수 default 100 (Data Size : 3)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body :**

    | Element Name        | Data Type   | Data Size | Description                                                                         |
    | ------------------- | ----------- | --------- | ----------------------------------------------------------------------------------- |
    | continuationKey     | String      | 41        | 구매취소 건이 최대조회건수보다 많을 경우 반환. 이후 요청시 전달받은 continuationKey 를 셋팅하여 호출하면 이후 내역을 조회 할 수 있음 |
    | voidedPurchaseList  | <p><br></p> | -         | <p><br></p>                                                                         |
    | purchaseId          | String      | 20        | 구매 ID                                                                               |
    | purchaseTime        | Long        | 13        | 구매시간(ms)                                                                            |
    | voidedTime          | Long        | 13        | 구매취소시간(ms)                                                                          |
    | purchaseToken       | String      | 20        | 구매 토큰                                                                               |
    | marketCode          | String      | -         | 마켓 구분 코드                                                                            |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "continuationKey": "continuationKey",
        "voidedPurchaseList ": [
            {
                "purchaseId": "19062709124410111299",
                "purchaseTime": 1345678900000,
                "voidedTime": 1345688900000,
                "purchaseToken": "19062709124410111299",
                "marketCode": "MKT_ONE"
            },
            {
                "purchaseId": "19062709124410111300",
                "purchaseTime": 1345679900000,
                "voidedTime": 1345878900000,
                "purchaseToken": "19062709124410111299",
                "marketCode": "MKT_ONE"
            }
        ]
    }
    ```

### getSubscriptionDetail (구독 상세 조회)  <a href="#id-06.-api-apiv7-getsubscriptiondetail" id="id-06.-api-apiv7-getsubscriptiondetail"></a>

* **Desc :** 구독 상세 내역을 조회한다.
* **URI :** /v7/apps/{clientId}/purchases/subscription/products/{productId}/{purchaseToken}
* **Method :** GET
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body :**

    <table data-header-hidden><thead><tr><th width="205"></th><th></th><th></th><th></th></tr></thead><tbody><tr><td>Element Name</td><td>Data Type</td><td>Data Size</td><td>Description</td></tr><tr><td>acknowledgementState</td><td>Integer</td><td>1</td><td>구매확인 상태( 0: Not Acknowledged, 1: Acknowledged)</td></tr><tr><td>developerPayload</td><td>String</td><td>200</td><td>개발사가 제공한 결제 고유 식별자</td></tr><tr><td>autoRenewing</td><td>boolean</td><td>-</td><td><p>다음결제 갱신여부<br></p><ul><li>구독해지예약,해지,만료 : false</li><li>나머지 : true</li></ul></td></tr><tr><td>paymentState</td><td>Integer</td><td>1</td><td><p>구독결제 수신여부</p><ul><li>null: 만료 상태</li><li>0: 결제가 완료 되지 않은 상태</li><li>1: 결제요청 후 결제완료가 된 상태</li><li>2: 무료기간으로 처리된 상태</li><li>3: 상품 업/다운그레이드 시 DEFERRED옵션으로 처리된 상태</li></ul></td></tr><tr><td>priceAmount</td><td>String</td><td>-</td><td>구매금액</td></tr><tr><td>priceAmountMicros</td><td>Long</td><td>-</td><td>구매금액 * 100만</td></tr><tr><td>nextPriceAmount</td><td>String</td><td>-</td><td>다음구매금액</td></tr><tr><td>nextPriceAmountMicros</td><td>Long</td><td>-</td><td>다음구매금액 * 100만</td></tr><tr><td>nextPaymentTimeMillis</td><td>Long</td><td>13</td><td>다음구매시간(ms)</td></tr><tr><td>priceCurrencyCode</td><td>String</td><td>-</td><td>통화코드(KRW 고정)</td></tr><tr><td>countryCode</td><td>String</td><td>-</td><td>국가코드(KR 고정)</td></tr><tr><td>startTimeMillis</td><td>Long</td><td>13</td><td>구독 시작(첫 결제)시간(ms)</td></tr><tr><td>expiryTimeMillis</td><td>Long</td><td>13</td><td>구독 만료시간(ms)</td></tr><tr><td>pauseStartTimeMillis</td><td>Long</td><td>13</td><td>일시정지 시작일시(ms)-일시정지예약/일시정지 시에만 존재</td></tr><tr><td>pauseEndTimeMillis</td><td>Long</td><td>13</td><td>일시정지 시작일시(ms)-일시정지예약/일시정지 시에만 존재</td></tr><tr><td>autoResumeTimeMillis</td><td>Long</td><td>13</td><td><p>일시정지 후 재구독 시간</p><ul><li>정상 구독 중: null</li><li>일시정지 예약/확정/일시정지중: 다음결제일 + 일시정지일자 </li></ul></td></tr><tr><td>linkedPurchaseToken</td><td>String</td><td>20</td><td>구독상품 변경 시 이전 purchaseToken, 변경한 적이 없으면 null</td></tr><tr><td>lastPurchaseId</td><td>String</td><td>20</td><td>마지막 구매ID</td></tr><tr><td>cancelledTimeMillis<br></td><td>Long</td><td>13</td><td>구독 해지시간(ms)</td></tr><tr><td>cancelReason</td><td>Integer</td><td>1</td><td><p>구독 해지사유 </p><ul><li>0 : 고객요청</li><li>1 : 기타</li></ul></td></tr><tr><td>promotionPrice</td><td>Object</td><td>-</td><td>프로모션 금액 정보</td></tr><tr><td>promotionPrice.promotionPrice</td><td>String</td><td>-</td><td>프로모션 금액</td></tr><tr><td>promotionPrice.promotionPriceMicros</td><td>Long</td><td>-</td><td>프로모션 금액 정보 * 100만</td></tr><tr><td>promotionPrice.promotionPeriod</td><td>Int</td><td>-</td><td>프로모션 적용 회차</td></tr><tr><td>priceChange</td><td>Object</td><td>-</td><td>가격변동정보</td></tr><tr><td>priceChange.seq</td><td>Integer</td><td>-</td><td>가격변동 시퀀스 </td></tr><tr><td>priceChange.previousPrice</td><td>String</td><td>-</td><td>이전 가격 </td></tr><tr><td>priceChange.previousPriceMicros</td><td>Long</td><td>-</td><td>이전 가격 * 100만</td></tr><tr><td>priceChange.newPrice</td><td>String</td><td>-</td><td>신규 가격</td></tr><tr><td>priceChange.newPriceMicros</td><td>Long</td><td>-</td><td>신규 가격 * 100만</td></tr><tr><td>priceChange.applyTimeMillis</td><td>Long</td><td>13</td><td>적용일자(ms)</td></tr><tr><td>priceChange.agreement</td><td>Boolean</td><td>-</td><td>가격 변동 동의 여부</td></tr><tr><td>priceChange.agreementDueDateTimeMillis</td><td>Long</td><td>13</td><td><p>가격 변동 동의 만료 시간(ms)</p><p>(정책 부연 설명)</p><p>Value = 가격변경일 +7+30일<br>사용자는 동의만료일 이후 첫 자동결제 시점까지 동의가능함.</p></td></tr></tbody></table>
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "acknowledgementState":1,
        "autoRenewing":true,
        "paymentState":1,
        "lastPurchaseId": "20202394820938409234"
        "linkedPurchaseToken":null,
        "priceAmount":"100",
        "priceAmountMicros":100000000,
        "nextPriceAmount":"150",
        "nextPriceAmountMicros":150000000,
        "nextPaymentTimeMillis": 1623337199000,
        "priceCurrencyCode":"KRW",
        "countryCode":"KR",
        "startTimeMillis":1623337199000,
        "expiryTimeMillis":1625929199000,
        "pauseStartTimeMillis":1625929199000,
        "pauseEndTimeMillis":1625929199000,
        "autoResumeTimeMillis":1625878800000,
        "cancelledTimeMillis":1625929199000,
        "cancelReason":0,
        "promotionPrice":{
            "promotionPrice":"100",
            "promotionPriceMicros":100000000,
            "promotionPeriod":30
        },
        "priceChange":{
            "seq": 1,
            "previousPrice":"100",
            "previousPriceMicros":100000000,
            "newPrice":"500",
            "newPriceMicros":500000000,
            "applyTimeMillis":1625670000000,
            "agreement":false,
            "agreementDueDateTimeMillis": 1345678920000
        }
    }
    ```

### cancelSubscription (구독결제 해지요청)  <a href="#id-06.-api-apiv7-cancelsubscription" id="id-06.-api-apiv7-cancelsubscription"></a>

* **Desc :** 구독형 상품의 자동결제 해지를 요청한다. 단, 요청 시점에 구독상태가 일시중지, 결제유예, 계정보류일 경우 즉시 해지를 요청한다.
* **URI :** /v7/apps/{clientId}/purchases/subscription/products/{productId}/{purchaseToken}/cancel
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```

### reactivateSubscription (구독결제 해지 취소요청)  <a href="#id-06.-api-apiv7-reactivatesubscription" id="id-06.-api-apiv7-reactivatesubscription"></a>

* **Desc :** 구독형 상품의 자동결제 해지요청을 취소한다. 단, 즉시해지된 경우 해지요청을 취소할 수 없다.
* **URI :** /v7/apps/{clientId}/purchases/subscription/products/{productId}/{purchaseToken}/reactivate
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
* **Request Body :** 없음
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```

### deferSubscription (구독결제 연기요청)  <a href="#id-06.-api-apiv7-defersubscription" id="id-06.-api-apiv7-defersubscription"></a>

* **Desc :** 구독형 상품의 다음 결제일을 연기한다.
* **URI :** /v7/apps/{clientId}/purchases/subscription/products/{productId}/{purchaseToken}/defer
* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : API를 호출하는 앱의 클라이언트 ID (Data Size : 128)
  * String productId : 상품 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
*   **Request Header:**&#x20;

    | Parameter Name | Data Type | Required | Description                             |
    | -------------- | --------- | -------- | --------------------------------------- |
    | Authorization  | String    | true     | Access Token API를 통해 발급받은 access\_token |
    | Content-Type   | String    | true     | application/json                        |
    | x-market-code  | String    | false    | 마켓 구분 코드                                |
*   **Example**&#x20;

    ```java
    Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
    Request.setHeader("Content-Type", "application/json");
    Request.setHeader("x-market-code", "MKT_GLB");
    ```
*   **Request Body :** JSON 형식

    | Parameter Name | Data Type | Required | Description                                                       |
    | -------------- | --------- | -------- | ----------------------------------------------------------------- |
    | deferPeriod    | Integer   | true     | <p>연기 기간</p><ul><li>상용: 일 단위(1~365일)</li><li>샌드박스: 분 단위</li></ul> |
*   **Example :**&#x20;

    ```json
    Content-type: application/json;charset=UTF-8
    {
    	"deferPeriod" : 3
    }
    ```
*   **Response Body :** JSON 형식

    API 처리 성공 시 처리완료를 보다 직관적으로 판단할 수 있도록 아래 형식의 응답를 리턴합니다. 단, API 처리 실패 시에는 표준오류응답을 리턴합니다.

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | result       | Object    | -         | <p><br></p> |
*   **Example :**&#x20;

    ```json
    HTTP/1.1 200 OK
    Content-type: application/json;charset=UTF-8
    {
        "result" : {
            "code" : "Success",
            "message" : "Request has been completed successfully."
        }
    }
    ```

## 표준응답규격 <a href="#id-06.-api-apiv7" id="id-06.-api-apiv7"></a>

### 표준응답코드 <a href="#id-06.-api-apiv7" id="id-06.-api-apiv7"></a>

| Code                       | Message                                                                               | Description                              | HTTP Status Code                      | 대상 API                                                                                                  |
| -------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| AccessBlocked              | The request was blocked.                                                              | 요청이 차단되었습니다.                             | 403 - Fobidden                        | 공통                                                                                                      |
| AccessTokenExpired         | Access token has expired.                                                             | Access 토큰이 만료되었습니다.                      | 401 - Unauthorized                    | 공통                                                                                                      |
| BadRequest                 | The request are invalid.                                                              | 잘못된 요청입니다.                               | 400 - Bad Request                     | 공통                                                                                                      |
| DeveloperPayloadNotMatch   | The request developerPayload does not match the value passed in the purchase request. | 구매요청 시 전달된 developerPayload값과 일치하지 않습니다. | 400 - Bad Request                     | <p>acknowledgePurchase<br>consumePurchase</p>                                                           |
| InternalError              | An undefined error has occurred.                                                      | 정의되지 않은 오류가 발생하였습니다.                     | 500 - Internal Server Error           | 공통                                                                                                      |
| InvalidAccessToken         | Access token is invalid.                                                              | Access 토큰이 유효하지 않습니다.                    | 401 - Unauthorized                    | 공통                                                                                                      |
| InvalidAuthorizationHeader | Authorization header is invalid.                                                      | Authorization 헤더의 값이 유효하지 않습니다.          | 400 - Bad Request                     | 공통                                                                                                      |
| InvalidConsumeState        | The purchase consumption status cannot be changed or has already been changed.        | 소비상태 변경이 불가하거나, 이미 변경완료 되었습니다.           | 409 - Conflict                        | consumePurchase                                                                                         |
| InvalidContentType         | The request content-type is invalid.                                                  | 잘못된 Content Type 입니다.                    | 415 - Unsupported Media Type          | 공통                                                                                                      |
| InvalidPurchaseState       | Purchase history does not exist or is not completed.                                  | 구매내역이 존재하지 않거나, 구매완료 상태가 아닙니다.           | 409 - Conflict                        | <p>acknowledgePurchase<br>consumePurchase</p>                                                           |
| InvalidRequest             | Request parameters are invalid. \[ field1, field2, ... ]                              | 입력값이 유효하지 않습니다. \[ field1, field2, ... ] | 400 - Bad Request                     | 공통                                                                                                      |
| MethodNotAllowed           | HTTP method not supported.                                                            | 지원하지 않는 HTTP Method 입니다.                 | 405 - Method Not Allowed              | 공통                                                                                                      |
| NoSuchData                 | The requested data could not be found.                                                | 조회된 결과값이 존재하지 않습니다.                      | 404 - Not Found                       | <p>getPurchaseDetails<br>getRecurringPurchaseDetails</p>                                                |
| RequiredValueNotExist      | Request parameters are required. \[ field1, field2, ... ]                             | 필수값이 존재하지 않습니다. \[ field1, field2, ... ] | 400 - Bad Request                     | 공통                                                                                                      |
| ResourceNotFound           | The requested resource could not be found.                                            | 요청한 자원이 존재하지 않습니다.                       | 404 - Not Found                       | 공통                                                                                                      |
| ServiceMaintenance         | System maintenance is in progress.                                                    | 서비스 점검중입니다.                              | 503 - Service Temporarily Unavailable | 공통                                                                                                      |
| Success                    | The request has been completed successfully.                                          | 정상처리 되었습니다.                              | 200 - Success                         | <p>acknowledgePurchase<br>consumePurchase<br>cancelRecurringPurchase<br>reactivateRecurringPurchase</p> |
| UnauthorizedAccess         | Not authorized to this API.                                                           | 해당 API에 접근권한이 없습니다.                      | 403 - Fobidden                        | 공통                                                                                                      |

### 표준오류 응답규격 <a href="#id-06.-api-apiv7" id="id-06.-api-apiv7"></a>

서버 API에서는 정상적인 응답 외에 오류발생 시, 아래의 Example과 같은 형식의 표준오류응답을 리턴합니다. 아래 내용을 참고하시기 바랍니다.

*   **Response Body :** JSON 형식

    | Element Name | Data Type | Data Size | Description |
    | ------------ | --------- | --------- | ----------- |
    | code         | String    | -         | 응답 코드       |
    | message      | String    | -         | 응답 메시지      |
    | error        | Object    | -         | <p><br></p> |
*   **Example**

    ```json
    HTTP/1.1 400 Bad Request
    Content-type: application/json;charset=UTF-8
    {
        "error" : {
            "code" : "NoSuchData",
            "message" : "The requested data could not be found."
        }
    }
    ```

## **공통 코드**  <a href="#id-06.-api-apiv7" id="id-06.-api-apiv7"></a>

### **상품타입 코드**  <a href="#id-06.-api-apiv7" id="id-06.-api-apiv7"></a>

| Code         | Name   | Description     |
| ------------ | ------ | --------------- |
| inapp        | 관리형 상품 | 소비성/영구성/기간제 상품  |
| auto         | 월정액 상품 | 월 자동결제 상품       |
| subscription | 구독형 상품 | 구독형(자동결제) 상품    |
| all          | 전체 상품  | 관리형 상품 + 월정액 상품 |

