> 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/eng/tools/webshop/billing.md).

# Webshop Payment Integration

{% hint style="info" %}
To integrate ONE webshop payments, both PNS reception and Confirm Purchases must be implemented.

* [**PNS(Payment Notification Service)** ](#pns)
  * When a user completes a ONE webshop item purchase, the Webshop sends the payment result and related information to the developer’s server URL via PNS.
  * Use this information to deliver the item to the user.
* [**Confirm Purchases**](#purchase)
  * Notifies the ONE store server that the item has been successfully delivered to the user.
  * Depending on your purpose, use consumePurchase or acknowledgePurchase.
  * If Confirm Purchases is not completed within 3 days, the purchase is considered undelivered and is automatically canceled.
    {% endhint %}

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

When a payment or cancellation occurs, ONE store sends a notification to the developer’s server.

{% hint style="info" %}
PNS uses the same specification as In‑App Payment API V7 (Reference :  [Using PNS (Push Notification Service](https://onestore-dev.gitbook.io/dev/tools/billing/v21/pns))
{% endhint %}

### **1.1** Configure PNS Reception Server URL <a href="#id-07.pns-pushnotificationservice-pns-url" id="id-07.pns-pushnotificationservice-pns-url"></a>

{% hint style="warning" %}
**Notice**\
According to the ONE webshop security policy, only the **HTTPS protocol and port 443** are supported.\
Using any other configuration may result in improper operation.
{% endhint %}

You can configure the PNS server URL in: `Developer Center > Webshop > Integration Mgmt.`

For detailed setup instructions, refer to the [Integration Management](/dev/eng/webshop/integration.md) Guide.

### 1.2 PNS Detailed Specifications

**HTTP TYPE :** HTTPS

**URI** : Payment Notification URL set in the Developer Center

**Method** : POST

**Request Parameters** : N/A

**Request Header** :&#x20;

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

**Request Body** : JSON format

<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><p>Message version</p><ul><li>Development (Sandbox): 3.1.0D</li><li>Commercial (commercial test): 3.1.0</li></ul></td></tr><tr><td><code>clientId</code></td><td>String</td><td>Webshop Title ID</td></tr><tr><td><code>productId</code></td><td>String</td><td>Webshop Item ID</td></tr><tr><td><code>messageType</code></td><td>String</td><td>Fix SINGLE_PAYMENT_TRANSACTION</td></tr><tr><td><code>purchaseId</code></td><td>String</td><td>Purchase ID</td></tr><tr><td><code>developerPayload</code></td><td>String</td><td>Identifier managed by the developer to identify purchases </td></tr><tr><td><code>purchaseTimeMillis</code></td><td>Long</td><td>Time (ms) when the billing is completed in ONE store billing system</td></tr><tr><td><code>purchaseState</code></td><td>String</td><td><p>COMPLETED: billing completed</p><p>CANCELED: canceled</p></td></tr><tr><td><code>price</code></td><td>String</td><td>Payment amount</td></tr><tr><td><code>priceCurrencyCode</code></td><td>String</td><td>Currency code for payment amount (KRW, USD, ...)</td></tr><tr><td><code>productName</code></td><td>String</td><td>It is transmitted when the developer sets up the customized in-app title at the time of purchase request</td></tr><tr><td><code>paymentTypeList[]</code></td><td>List</td><td>List of payment information</td></tr><tr><td><code>paymentTypeList[].paymentMethod</code></td><td>String</td><td>Payment method (for details, refer to the definition of paymentMethod)</td></tr><tr><td><code>paymentTypeList[].amount</code></td><td>String</td><td>Amount per payment method</td></tr><tr><td><code>billingKey</code></td><td>String</td><td>Billing key for extended function</td></tr><tr><td><code>isTestMdn</code></td><td>Boolean</td><td>Test phone or not(true: test phone, false: not test phone)</td></tr><tr><td><code>purchaseToken</code></td><td>String</td><td>Purchase token</td></tr><tr><td><code>environment</code></td><td>String</td><td><p>Test environment</p><ul><li>Development (Sandbox): SANDBOX</li><li>Commercial:COMMERCIAL</li></ul></td></tr><tr><td><code>marketCode</code></td><td>String</td><td><p>Market identification code (MKT_ONE: ONE store, MKT_GLB: Global ONE store)</p><ul><li>For Webshop, the value is fixed as MKT_ONE</li></ul></td></tr><tr><td><code>signature</code></td><td>String</td><td>Signature for this message</td></tr><tr><td><code>serviceUserId</code></td><td>String</td><td>User's Game ID</td></tr><tr><td><code>serviceServerId</code></td><td>String</td><td>User's Server ID</td></tr></tbody></table>

\
**Example**

```
{
    "msgVersion" : "3.1.0"
    "clientId":"0999999999",
    "productId":"0900001234",
    "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"
}
```

<br>

#### **Definition of paymentMethod (ONE store payment method)** <a href="#id-07.pns-pushnotificationservice-paymentmethod" id="id-07.pns-pushnotificationservice-paymentmethod"></a>

<table><thead><tr><th width="249.33333333333331">paymentMethod</th><th>Payment method name</th><th>Description</th></tr></thead><tbody><tr><td>DCB</td><td>Mobile phone payment</td><td>Charged as the 'information use fee' item in the telecommunication bill by mobile carriers</td></tr><tr><td>PHONEBILL</td><td>Mobile phone micropayment</td><td>Charged as the 'micropayment' item in the telecommunication bill by mobile carriers</td></tr><tr><td>ONEPAY</td><td>ONE pay</td><td>Easy pay provided by ONE store</td></tr><tr><td>CREDITCARD</td><td>Credit card</td><td>Typical credit card payment</td></tr><tr><td>11PAY</td><td>11Pay</td><td>Simple mobile phone payment provided by SK planet</td></tr><tr><td>NAVERPAY</td><td>N pay</td><td>Naver pay's payment provided by Naver</td></tr><tr><td>CULTURELAND</td><td>컬쳐캐쉬</td><td>Culture cash payment provided by Korea Culture Promotion Inc.</td></tr><tr><td>OCB</td><td>OK cashbag</td><td>OK cashback payment provided by SK planet</td></tr><tr><td>ONESTORECASH</td><td>ONE store cash</td><td>ONE store cash payment </td></tr><tr><td>COUPON</td><td>ONE store coupon</td><td>ONE store coupon payment</td></tr><tr><td>POINT</td><td>ONE store point</td><td>ONE store point payment</td></tr><tr><td>TELCOMEMBERSHIP</td><td>Mobile carrier membership </td><td>Membership payment provided by the mobile carrier</td></tr><tr><td>EWALLET</td><td>e-Wallet</td><td>e-Wallet Payment</td></tr><tr><td>BANKACCT</td><td>Account Payment</td><td>Regular Account Payment</td></tr><tr><td>PAYPAL</td><td>Paypal </td><td>Payment provided by PayPal</td></tr><tr><td>MYCARD</td><td>My Card</td><td>Payment with MyCard provided by Soft World</td></tr></tbody></table>

#### &#x20;How to review the signature <a href="#id-07.pns-pushnotificationservice-signature" id="id-07.pns-pushnotificationservice-signature"></a>

You can check whether the signature has been forced or falsified by using the code below.

* The PublicKey in the code refers to the license key provided in the `Developer Center > Webshop > Intergration Mgmt. > Settings for Licensing` menu.

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

```java
import java.security.PublicKey;
   
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 {
        // Extract the signature from the JSON message.
        JsonNode root = mapper.readTree(rawMsg);
        String signature = root.get("signature").getValueAsText();
        ((ObjectNode)root).remove("signature");
          
        // Verify that the extracted signature is correct.
        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
<?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" %}

```python
# -*- 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 Transmission Policy

ONE store's PNS server shall send notification to the developer's server through HTTP(S) requests.At this time, the developer's server must respond with the HTTP Status Code as 200 to indicate that the notification has been normally received.

If the HTTP Status Code fails to be received as 200 (as the response due to the loss of notification caused by delays in the network or due to the failure in the developer's server), the PNS server determines that the notification transmission has failed, and thereby will perform up to 30 rounds of retransmission during 3 days.

The retransmission of the notification will be performed after certain delays as seen in the example below, and more retransmission rounds will lead to more delays.

**Example**

| Round     | delay (s) | Retransmission Time |
| --------- | --------- | ------------------- |
| 0 (first) | 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 |
| ...       | ...       | ...                 |

## 2. Confirm Purchases <a href="#purchase" id="purchase"></a>

To ensure that items are delivered correctly, the Confirm Purchases process must be completed.

{% hint style="warning" %}
OAuth authentication is required to use ONE store Open APIs.

For details, refer to the [ONE store OAuth Guide](/dev/eng/tools/billing/v21/serverapi.md#onestoreiapserverapi-apiv7-onestoreoauth).
{% endhint %}

{% hint style="success" %}
**You can retrieve purchases with unprocessed confirmation using the `getUnconfirmedPurchases` API.**

We recommend using this API to ensure stable payment processing.

* For details, refer to the [getUnconfirmedPurchases](/dev/eng/tools/billing/v21/serverapi.md#getunconfirmedpurchases) documentation.
  {% endhint %}

### 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>(Commercial) <br>https://iap-apis.onestore.net/v7/apps/{clientId}/purchases/inapp/products/{productId}/{purchaseToken}/consume<br><br>(Development) <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>Changes the state of the purchased managed in-app product to consumed. (Only applicable to consumable products.)</p><ul><li>After processing, the <strong>user can repurchase the same item.</strong></li></ul></li><li>Error Code: Refer to the <a href="https://onestore-dev.gitbook.io/dev/eng/tools/billing/v21/web-payment/uiapw#standard-error-response">Standard response codes</a></li></ul></td></tr></tbody></table>

* **Method :** POST

* **Request Parameter :** Path Variable format

  * String clientId : Client ID of the app that calls API (Data Size : 128)
  * String productId : Product ID (Data Size : 150)
  * String purchaseToken : Purchase token (Data Size : 20)

* **Request Header:**&#x20;

  | Parameter Name | Data Type | Required | Description                                           |
  | -------------- | --------- | -------- | ----------------------------------------------------- |
  | Authorization  | String    | true     | The access\_token issued through the Access Token API |
  | Content-Type   | String    | true     | application/json                                      |
  | x-market-code  | String    | false    | Market classification code                            |

* **Example**&#x20;

  ```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 Format

  The response is returned in the following format to allow for a more intuitive determination of the process completion when API processing is successful. However, when the API processing fails, it returns the standard error response.

  | Element Name | Data Type | Data Size | Description      |
  | ------------ | --------- | --------- | ---------------- |
  | code         | String    | -         | Response code    |
  | message      | String    | -         | Response message |
  | 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."
      }
  }
  ```

### 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>(Commercial) <br>https://iap-apis.onestore.net/v7/apps/{clientId}/purchases/all/products/{productId}/{purchaseToken}/acknowledge<br><br>(Development) <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>Changes the purchased in-app product to the purchase confirmation status. (Supported for both consumable and subscription products.)</p><ul><li>After processing, <strong>the user cannot repurchase the same item.</strong></li></ul></li><li>Error Code: Refer to the <a href="https://onestore-dev.gitbook.io/dev/eng/tools/billing/v21/web-payment/uiapw#standard-error-response">Standard response codes</a></li></ul></td></tr></tbody></table>

* **Method :** POST
* **Request Parameter :** Path Variable format
  * String clientId : Client ID of the app that calls API (Data Size : 128)
  * String productId : Product ID (Data Size : 150)
  * String purchaseToken : Purchase token (Data Size : 20)
* **Request Header:**&#x20;

  | Parameter Name | Data Type | Required | Description                                           |
  | -------------- | --------- | -------- | ----------------------------------------------------- |
  | Authorization  | String    | true     | The access\_token issued through the Access Token API |
  | Content-Type   | String    | true     | application/json                                      |
  | x-market-code  | String    | false    | Market classification code                            |
* **Example**&#x20;

  ```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 format \
  The response is returned in the following format to allow for a more intuitive determination of the process completion when API processing is successful. However, when the API processing fails, it returns the standard error response.

  | Element Name | Data Type | Data Size | Description      |
  | ------------ | --------- | --------- | ---------------- |
  | code         | String    | -         | Response code    |
  | message      | String    | -         | Response message |
  | 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."
      }
  }
  ```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://onestore-dev.gitbook.io/dev/eng/tools/webshop/billing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
