> For the complete documentation index, see [llms.txt](https://onestore-dev.gitbook.io/dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://onestore-dev.gitbook.io/dev/tools/webshop/billing.md).

# ONEshop 결제 연동

{% hint style="info" %}
ONEshop 결제를 연동하려면 PNS 수신과 구매확인 처리를 반드시 진행해야 합니다.

* [**PNS(Payment Notification Service)**](https://onestore-dev.gitbook.io/dev/tools/webshop/billing#pns)
  * 사용자가 ONEshop 아이템 결제를 완료하면, 결제 결과를 개발사 서버 URL로 전달합니다.
  * 전달된 정보를 참고하여 사용자에게 아이템을 지급해 주세요.
* [**구매확인**](https://onestore-dev.gitbook.io/dev/tools/webshop/billing#purchase)
  * 사용자에게 아이템을 정상적으로 지급되었음을 원스토어 서버에 전달합니다.
  * 목적에 따라 consumePurchase 또는 acknowledgePurchase를 활용합니다.
  * 구매확인이 3일 이내 완료되지 않으면, 아이템이 정상적으로 지급되지 않은 것으로 판단하여 해당 구매는 자동 취소됩니다. 자동 취소되면 `purchaseState`가 `CANCELED`인 PNS가 전송됩니다.
    {% endhint %}

### 1. PNS <a href="#pns" id="pns"></a>

아이템 결제 또는 결제취소가 발생하면 원스토어가 개발사 서버로 알림을 전송합니다.

{% hint style="info" %}
PNS는 인앱결제 API V7과 동일한 규격으로 제공합니다. 자세한 내용은 [PNS 이용하기](https://onestore-dev.gitbook.io/dev/tools/billing/v21/pns)를 참고해 주세요.
{% endhint %}

#### **1.1 PNS 수신 서버 URL 설정** <a href="#id-07.pns-pushnotificationservice-pns-url" id="id-07.pns-pushnotificationservice-pns-url"></a>

{% hint style="warning" %}
**유의사항**

ONEshop 보안 정책에 따라 **HTTPS 프로토콜 및 443 Port**만 지원합니다. 그 외 규격은 정상 동작하지 않을 수 있습니다.
{% endhint %}

PNS를 수신 받을 개발사 서버의 URL은 `ONEconsole > Webshop > 개발자 > 서버 연동` 메뉴에서 등록할 수 있습니다.

자세한 방법은 [서버 연동](https://onestore-dev.gitbook.io/dev/webshop/integration)을 참고해 주세요.

#### 1.2 PNS 상세 규격

**HTTP Type :** HTTPS

**URI** : `ONEconsole > Webshop > 개발자 > 서버 연동`에서 설정한 결제 알림(PNS) URL

**Method** : POST

**Request Parameters** : N/A

**Request Header** :

| Name         | Type   | Value              |
| ------------ | ------ | ------------------ |
| Content-Type | String | `application/json` |

**Request Body** : JSON 형식

<table><thead><tr><th width="328.740478515625">Name</th><th width="155.9791259765625">Type</th><th width="303.3809814453125">Description</th></tr></thead><tbody><tr><td><code>msgVersion</code></td><td>String</td><td><p>메시지 버전</p><ul><li>개발(Sandbox) : 3.1.0D</li><li>상용(상용테스트) : 3.1.0</li></ul></td></tr><tr><td><code>clientId</code></td><td>String</td><td>ONEshop 타이틀 ID (예: <code>WS00000001</code>)</td></tr><tr><td><code>productId</code></td><td>String</td><td>ONEshop 아이템 ID</td></tr><tr><td><code>messageType</code></td><td>String</td><td>SINGLE_PAYMENT_TRANSACTION 고정</td></tr><tr><td><code>purchaseId</code></td><td>String</td><td>구매 ID</td></tr><tr><td><code>developerPayload</code></td><td>String</td><td>구매 건을 식별하기 위해 개발사에서 관리하는 식별자. 유효성 검증 응답 시 전달한 값이 그대로 포함됩니다.</td></tr><tr><td><code>purchaseTimeMillis</code></td><td>Long</td><td><p>구매 상태가 변경된 시간(ms)</p><ul><li><code>COMPLETED</code>: 결제가 완료된 시간</li><li><code>CANCELED</code>: 결제가 취소된 시간</li><li>최초 결제 시간은 구매 상세 조회로 확인할 수 있습니다.</li></ul></td></tr><tr><td><code>purchaseState</code></td><td>String</td><td><p><code>COMPLETED</code>: 결제 완료</p><p><code>CANCELED</code>: 결제 완료 후 환불 또는 구매 취소된 주문. 수신하면 지급한 아이템을 회수하는 등 필요한 처리를 진행해 주세요.</p><ul><li>구매확인이 3일 이내에 처리되지 않아 자동 취소된 경우에도 <code>CANCELED</code>로 전송됩니다.</li></ul></td></tr><tr><td><code>price</code></td><td>String</td><td>결제 금액</td></tr><tr><td><code>priceCurrencyCode</code></td><td>String</td><td>결제 금액 통화코드(KRW, USD, ...)</td></tr><tr><td><code>productName</code></td><td>String</td><td>구매요청 시 개발사가 customized 인앱상품 제목을 설정한 경우 전달</td></tr><tr><td><code>paymentTypeList[]</code></td><td>List</td><td>결제 정보 목록</td></tr><tr><td><code>paymentTypeList[].paymentMethod</code></td><td>String</td><td>결제 수단 (상세 내용은 아래 paymentMethod 정의 참고)</td></tr><tr><td><code>paymentTypeList[].amount</code></td><td>String</td><td>결제 수단 별 금액</td></tr><tr><td><code>billingKey</code></td><td>String</td><td>확장 기능용 결제 키</td></tr><tr><td><code>isTestMdn</code></td><td>Boolean</td><td>시험폰 여부(true : 시험폰, false : 시험폰 아님)</td></tr><tr><td><code>purchaseToken</code></td><td>String</td><td>구매토큰</td></tr><tr><td><code>environment</code></td><td>String</td><td><p>결제환경</p><ul><li>개발(샌드박스) : SANDBOX</li><li>상용 :COMMERCIAL</li></ul></td></tr><tr><td><code>marketCode</code></td><td>String</td><td><p>마켓 구분코드 ( MKT_ONE : 원스토어, MKT_GLB : 원스토어 글로벌 )</p><ul><li>웹샵의 경우, MKT_ONE 고정</li></ul></td></tr><tr><td><code>signature</code></td><td>String</td><td>본 메시지에 대한 signature</td></tr><tr><td><code>serviceUserId</code></td><td>String</td><td>사용자의 게임 ID</td></tr><tr><td><code>serviceServerId</code></td><td>String</td><td>사용자의 서버 ID (플레이어 식별 필드에 서버 리스트 타입을 설정한 경우)</td></tr><tr><td><code>serviceCustomFields</code></td><td>String (JSON String)</td><td>사용자 커스텀 필드. 플레이어 식별 필드에서 추가한 커스텀 필드의 <code>{"필드 키": "입력값"}</code> (<a href="https://onestore-dev.gitbook.io/dev/webshop/player-id-fields">플레이어 식별 필드</a> 참고)</td></tr><tr><td><code>quantity</code></td><td>Integer</td><td>수량</td></tr><tr><td><code>productPrice</code></td><td>String</td><td>아이템 가격</td></tr></tbody></table>

\
**Example**

```json
{
    "msgVersion":"3.1.0",
    "clientId":"WS00000001",
    "productId":"item1000",
    "messageType":"SINGLE_PAYMENT_TRANSACTION",
    "purchaseId":"SANDBOX3000000004564",
    "developerPayload":"OS_000211234",
    "purchaseTimeMillis":24431212233,
    "purchaseState":"COMPLETED",
    "price":"10000",
    "priceCurrencyCode":"KRW",
    "productName":"GOLD100(+20)",
    "paymentTypeList":[
        {
            "paymentMethod":"ONEPAY",
            "amount":"3000"
        },
        {
            "paymentMethod":"ONESTORECASH",
            "amount":"7000"
        }
    ],
      "billingKey": "36FED4C6E4AC9E29ADAF356057DB98B5CB92126B1D52E8757701E3A261AF49CCFBFC49F5FEF6E277A7A10E9076B523D839E9D84CE9225498155C5065529E22F5",
      "isTestMdn": true,
      "purchaseToken": "TOKEN",
      "environment": "SANDBOX",
      "marketCode": "MKT_ONE",
      "signature": "ajkfl;askfjkladfjksl",
      "serviceUserId": "user1234",
      "serviceServerId": "server01",
      "serviceCustomFields": "{\"characterId\":\"char_1001\",\"guildId\":\"guild_77\"}",
      "quantity": 1,
      "productPrice": "10000"
}
```

<br>

**paymentMethod(원스토어 결제수단) 정의**

<table><thead><tr><th width="249.33333333333331">paymentMethod</th><th>결제수단 명칭</th><th>설명</th></tr></thead><tbody><tr><td>DCB</td><td>휴대폰결제</td><td>통신사 요금청구서에 '정보이용료' 항목으로 청구</td></tr><tr><td>PHONEBILL</td><td>휴대폰 소액결제</td><td>통신사 요금청구서에 '소액결제' 항목으로 청구</td></tr><tr><td>ONEPAY</td><td>ONE pay</td><td>원스토어가 제공하는 간편결제</td></tr><tr><td>CREDITCARD</td><td>신용카드</td><td>일반 신용카드 결제</td></tr><tr><td>11PAY</td><td>11Pay</td><td>11번가에서 제공하는 신용카드 간편결제</td></tr><tr><td>NAVERPAY</td><td>N pay</td><td>네이버에서 제공하는 네이버페이 결제</td></tr><tr><td>CULTURELAND</td><td>컬쳐캐쉬</td><td>한국문화진흥에서 제공한는 컬쳐캐쉬 결제</td></tr><tr><td>OCB</td><td>OK cashbag</td><td>SK플래닛이 제공하는 OK캐쉬백 결제</td></tr><tr><td>ONESTORECASH</td><td>원스토어 캐쉬</td><td>원스토어 캐쉬 결제</td></tr><tr><td>COUPON</td><td>원스토어 쿠폰</td><td>원스토어 쿠폰 결제</td></tr><tr><td>POINT</td><td>원스토어 포인트</td><td>원스토어 포인트 결제</td></tr><tr><td>TELCOMEMBERSHIP</td><td>통신사멤버십</td><td>통신사에서 제공하는 멤버십 결제</td></tr><tr><td>EWALLET</td><td>e-Wallet</td><td>e-Wallet 결제</td></tr><tr><td>BANKACCT</td><td>계좌결제</td><td>일반 계좌결제</td></tr><tr><td>PAYPAL</td><td>페이팔</td><td>페이팔이 제공하는 결제</td></tr><tr><td>MYCARD</td><td>마이카드</td><td>소프트월드에서 제공하는 마이카드 결제</td></tr></tbody></table>

**Signature 검증 방법**

아래 코드를 사용하면 signature에 대한 위변조 여부를 확인할 수 있습니다.

* 코드 내 PublicKey는 `ONEconsole > Webshop > 개발자 > 서버 연동` 메뉴 라이선스 탭의 라이선스 키를 의미합니다.

{% tabs %}
{% tab title="Java" %}

```
import java.security.PublicKey;
import java.security.Signature;
   
import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
   
   
public class SignatureVerifier {
   
    private static final String SIGN_ALGORITHM = "SHA512withRSA";
    private ObjectMapper mapper = new ObjectMapper();
   
   
    boolean verify(String rawMsg, PublicKey key) throws Exception {
        // JSON 메시지에서 signature를 추출한다.
        JsonNode root = mapper.readTree(rawMsg);
        String signature = root.get("signature").getValueAsText();
        ((ObjectNode)root).remove("signature");
          
        // 추출한 signature가 올바른 값인지 검증한다.
        Signature sign = Signature.getInstance(SIGN_ALGORITHM);
        sign.initVerify(key);
        sign.update(root.toString().getBytes("UTF-8"));
        return sign.verify(Base64.decodeBase64(signature));
    }
}
```

{% endtab %}

{% tab title="PHP" %}

```
<?php
function formatPublicKey($publicKey) {
 $BEGIN= "-----BEGIN PUBLIC KEY-----";
 $END = "-----END PUBLIC KEY-----";
  
 $pem = $BEGIN . "\n";
 $pem .= chunk_split($publicKey, 64, "\n");
 $pem .= $END . "\n";
  
 return $pem;
}
  
function formatSignature($signature) {
 return base64_decode(chunk_split($signature, 64, "\n"));
}
  
// Sample message
$sampleMessage = '{"msgVersion":"3.1.0D","purchaseId":"SANDBOX3000000004564","developerPayload":"OS_000211234","clientId":"0000000001","productId":"0900001234","messageType":"SINGLE_PAYMENT_TRANSACTION","purchaseMillis":24431212233,"purchaseState":"COMPLETED","price":20000,"productName":"한글은?GOLD100(+20)","paymentTypeList":[{"paymentMethod":"DCB","amount":3000},{"paymentMethod":"ONESTORECASH","amount":7000}],"billingKey":"36FED4C6E4AC9E29ADAF356057DB98B5CB92126B1D52E8757701E3A261AF49CCFBFC49F5FEF6E277A7A10E9076B523D839E9D84CE9225498155C5065529E22F5","isTestMdn":true,"signature":"MNxIl32ws+yYWpUr7om+jail4UQxBUXdNX5yw5PJKlqW2lurfvhiqF0p4XWa+fmyV6+Ot63w763Gnx2+7Zp2Wgl73TWru5kksBjqVJ3XqyjUHDDaF80aq0KvoQdLAHfKze34cJXKR/Qu8dPHK65PDH/Vu6MvPVRB8TvCJpkQrqg="}';
  
// Sample public key
$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMzpWJoK1GSOrr4juma5+sREYjdCW8/xSd9+6z6PAkUH5af97wy8ecfkLtP9LK5VskryfDlcOjfu0BgmHYntAqKT7B4KWk8jWbJ8VHUpp30H95UbcnCRFDqpEtwYzNA5gNMYKtAdbL41K8Fbum0Xqxo65pPEI4UC3MAG96O7X1WQIDAQAB";
  
  
// Parse JSON message
$jsonArr = json_decode($sampleMessage, true);
  
// Extract and remove signature
$signature = $jsonArr["signature"];
unset($jsonArr["signature"]);
$originalMessage = json_encode($jsonArr, JSON_UNESCAPED_UNICODE);
  
// Veify
$formattedKey = formatPublicKey($publicKey);
$formattedSign = formatSignature($signature);
$hash_algorithm = 'sha512';
  
$success = openssl_verify($originalMessage, $formattedSign, $formattedKey, $hash_algorithm);
if ($success == 1) {
 echo "verified";
}
else {
 echo "unverified";
}
?>
```

{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" %}

```
# -*- coding: utf-8 -*-
  
import json
from base64 import b64decode
from collections import OrderedDict
  
from Crypto.Hash import SHA512
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
  
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
  
hash = "SHA-512"
  
  
def verify(message, signature, pub_key):
    signer = PKCS1_v1_5.new(pub_key)
    digest = SHA512.new()
    digest.update(message)
    return signer.verify(digest, signature)
  
  
jsonData = json.loads(rawMsg, encoding='utf-8', object_pairs_hook=OrderedDict)
signature = jsonData['signature']
del jsonData['signature']
originalMessage = json.dumps(jsonData, ensure_ascii=False, encoding='utf-8', separators=(',', ':'))
  
RSA.importKey(publickey).publickey()
print(verify(originalMessage, b64decode(signature), RSA.importKey(publickey).publickey()))
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### 1.3 Notification 전송 정책

원스토어의 PNS 서버는 HTTP(S) 요청을 통하여 개발사 서버로 notification을 전송합니다.

이때 개발사 서버는 notification를 정상적으로 수신했다는 의미로 HTTP Status Code를 200으로 응답하여야 합니다.

만약 네트워크 지연으로 인한 유실 또는 개발사 서버의 비정상적인 상황으로 HTTP Status Code를 200으로 응답받지 못한 경우, PNS 서버는 notification 전송이 실패했다고 판단하고 3일간 최대 30회의 재전송을 수행하게 됩니다.

notification의 재전송은 아래의 예시와 같이 일정한 delay를 가진 후 실행되며, 재시도 회수가 많아지면 delay가 점차 늘어나는 구조로 되어 있습니다.

**Example**

| 회차     | delay (초) | 재전송 시간              |
| ------ | --------- | ------------------- |
| 0 (최초) | 0         | 2020-05-17 13:10:00 |
| 1      | 30        | 2020-05-17 13:10:30 |
| 2      | 120       | 2020-05-17 13:12:30 |
| 3      | 270       | 2020-05-17 13:17:00 |
| 4      | 480       | 2020-05-17 13:25:00 |
| ...    | ...       | ...                 |

{% hint style="warning" %}
**유의사항**

* 재전송 때문에 같은 PNS를 여러 번 받을 수 있습니다. `purchaseState`가 `COMPLETED`인 알림은 `purchaseId` 기준으로 이미 지급한 구매 건인지 확인해 아이템이 중복 지급되지 않게 해 주세요.
* PNS에는 새 필드가 추가될 수 있습니다. 정의되지 않은 필드가 있어도 오류로 응답하지 말고 무시한 뒤 HTTP 200으로 응답해 주세요. 서명 검증은 추가된 필드를 포함한 수신 원문 기준으로 합니다.
  {% endhint %}

### 2. 구매확인 <a href="#purchase" id="purchase"></a>

아이템이 정상적으로 지급되었음을 확인하기 위해 구매확인 처리는 반드시 진행해야 합니다.

{% hint style="warning" %}
**구매확인은 원스토어 서버 Open API로 연동을 위해선 반드시 OAuth 인증이 필요합니다.**

* 자세한 내용은 [원스토어 OAuth](https://onestore-dev.gitbook.io/dev/tools/billing/v21/serverapi#id-06.-api-apiv7-oauth)를 참고해 주세요.
  {% endhint %}

{% hint style="success" %}
**`getUnconfirmedPurchases` API를 통해 구매확인이 처리되지 않은 내역을 조회할 수 있습니다.**

안정적인 결제 처리를 위해 해당 API 활용을 권장드립니다.

* 경로: `GET https://{host}/v7/apps/{clientId}/unconfirmed-purchases` (`{host}`: 아래 결제 환경별 호스트, `{clientId}`: ONEshop 타이틀 ID)
* 조회 기간은 현재 기준 최근 1개월까지이며, 한 번에 최대 100건을 반환합니다. 남은 건이 있으면 응답의 `continuationKey`로 이어서 조회해 주세요.
* 자세한 내용은 [getUnconfirmedPurchases](https://onestore-dev.gitbook.io/dev/tools/billing/v21/serverapi#getunconfirmedpurchases)를 참고해 주세요.
  {% endhint %}

**결제 환경별 호스트**

구매확인 API와 Access Token 발급 API는 PNS의 `environment` 값과 같은 환경의 호스트로 호출해 주세요.

| PNS `environment` | PNS `msgVersion` | 호스트                             |
| ----------------- | ---------------- | ------------------------------- |
| `SANDBOX`         | `3.1.0D`         | `https://sbpp.onestore.net`     |
| `COMMERCIAL`      | `3.1.0`          | `https://iap-apis.onestore.net` |

{% hint style="warning" %}
**유의사항**

결제 환경과 다른 호스트로 구매확인 API를 호출하면 구매확인에 실패합니다. 구매확인이 3일 이내에 처리되지 않으면 해당 구매는 자동 취소되므로, 호스트를 섞어 쓰지 않도록 주의해 주세요.
{% endhint %}

**Access Token 발급**

구매확인 API를 호출하려면 먼저 Access Token을 발급받아야 합니다.

| 항목       | 내용                                                                            |
| -------- | ----------------------------------------------------------------------------- |
| URI      | `https://{host}/v7/oauth/token` (`{host}`: 위 결제 환경별 호스트)                      |
| Method   | POST                                                                          |
| Header   | `Content-Type: application/x-www-form-urlencoded`, `x-market-code` (마켓 구분 코드) |
| Body     | `grant_type=client_credentials`, `client_id`, `client_secret`                 |
| Response | `access_token`, `token_type`(bearer), `expires_in`(초), `scope`                |

* `client_id`와 `client_secret`은 `ONEconsole > Webshop > 개발자 > 서버 연동` 메뉴 라이선스 탭의 OAuth Client ID와 Client Secret을 사용합니다.
* 개발(Sandbox)과 상용의 Access Token은 따로 관리됩니다. 환경별로 Access Token을 분리해 관리해 주세요.
* 구매확인 API 경로의 `{clientId}`에는 ONEshop 타이틀 ID(`WS`로 시작)를 넣습니다.
* 발급받은 `access_token`은 구매확인 API의 `Authorization` 헤더에 `Bearer {access_token}` 형식으로 넣습니다.

#### 2.1 consumePurchase

<table data-header-hidden><thead><tr><th width="129.44921875"></th><th></th></tr></thead><tbody><tr><td><strong>Path</strong></td><td>(상용)<br>https://iap-apis.onestore.net/v7/apps/{clientId}/purchases/inapp/products/{productId}/{purchaseToken}/consume<br><br>(개발)<br>https://sbpp.onestore.net/v7/apps/{clientId}/purchases/inapp/products/{productId}/{purchaseToken}/consume</td></tr><tr><td><strong>Description</strong></td><td><ul><li><p>구매한 아이템(관리형 상품)을 소비한 상태로 변경합니다.</p><ul><li>처리 후 <strong>사용자는 동일 상품을 다시 구매할 수 있습니다.</strong></li></ul></li><li>오류 코드 : <a href="https://onestore-dev.gitbook.io/dev/tools/billing/v21/serverapi#id-06.-api-apiv7-1">표준 응답코드</a> 참조</li></ul></td></tr></tbody></table>

* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : ONEshop 타이틀 ID (Data Size : 128)
  * String productId : ONEshop 아이템 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
* **Request Header:**

  | 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    | 마켓 구분 코드. PNS로 전달받은 `marketCode` 값을 그대로 입력 |
* **Example**

  ```java
  Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
  Request.setHeader("Content-Type", "application/json");
  Request.setHeader("x-market-code", "MKT_ONE");
  ```
* **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 :**

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

#### 2.2 acknowledgePurchase

<table data-header-hidden><thead><tr><th width="125.921875"></th><th></th></tr></thead><tbody><tr><td><strong>Path</strong></td><td>(상용)<br>https://iap-apis.onestore.net/v7/apps/{clientId}/purchases/all/products/{productId}/{purchaseToken}/acknowledge<br><br>(개발)<br>https://sbpp.onestore.net/v7/apps/{clientId}/purchases/all/products/{productId}/{purchaseToken}/acknowledge</td></tr><tr><td><strong>Description</strong></td><td><ul><li><p>구매한 아이템(관리형 상품)을 구매확인 상태로 변경합니다.</p><ul><li>처리 후 <strong>사용자는 동일 상품을 재구매할 수 없습니다.</strong></li></ul></li><li>오류 코드 : <a href="https://onestore-dev.gitbook.io/dev/tools/billing/v21/serverapi#id-06.-api-apiv7-1">표준 응답코드</a> 참조</li></ul></td></tr></tbody></table>

* **Method :** POST
* **Request Parameter :** Path Variable 형식
  * String clientId : ONEshop 타이틀 ID (Data Size : 128)
  * String productId : ONEshop 아이템 ID (Data Size : 150)
  * String purchaseToken : 구매 토큰 (Data Size : 20)
* **Request Header:**

  | 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    | 마켓 구분 코드. PNS로 전달받은 `marketCode` 값을 그대로 입력 |
* **Example**

  ```java
  Request.setHeader("Authorization", "Bearer 680b3621-1234-1234-1234-8adfaef561b4");
  Request.setHeader("Content-Type", "application/json");
  Request.setHeader("x-market-code", "MKT_ONE");
  ```
* **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 :**

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