# PNS(Payment Notification Service)활용

인앱상품 결제 또는 결제취소 transaction이 발생하면 원스토어는 개발사 서버로 notification을 전송합니다.

주의사항

* Notification은 발송/수신 서버의 상태에 따라 지연 또는 유실될 수 있으므로, notification 수신을 기준으로 상품(서비스)을 제공해서는 안됩니다.\
  상품은 구매완료 응답(또는 소비 처리완료)을 기준으로 제공되어야 하며, notification은 결제 확인, 취소 시 아이템 회수 등의 목적으로 활용하는 것을 권장합니다.
* 정상적인 결제 건인지 서버-to-서버로 확인하기를 원하신다면 PNS notification을 이용하는 대신, 관련 서버 API (getPurchaseDetail 또는 getPurchaseDetailByProductId)로 조회하는 것을 권장합니다.&#x20;
* 원스토어는 검증 및 모니터링 목적으로 결제 테스트를 진행할 수 있으며, 해당 테스트 건들도 결제/결제취소 시 동일하게 notification이 발송됩니다.원스토어가 진행한 결제 테스트 내역은 주기적으로 원스토어에서 자체 취소 처리합니다.

**PNS(Payment Notification Service) 소개**

인앱상품 결제 또는 결제취소 transaction이 발생하면 원스토어는 개발사 서버로 notification을 전송합니다.

주의사항

* Notification은 발송/수신 서버의 상태에 따라 지연 또는 유실될 수 있으므로, notification 수신을 기준으로 상품(서비스)을 제공해서는 안됩니다.\
  상품은 구매완료 응답(또는 소비 처리완료)을 기준으로 제공되어야 하며, notification은 결제 확인, 취소 시 아이템 회수 등의 목적으로 활용하는 것을 권장합니다.
* 정상적인 결제 건인지 서버-to-서버로 확인하기를 원하신다면 PNS notification을 이용하는 대신, 관련 서버 API (getPurchaseDetail 또는 getPurchaseDetailByProductId)로 조회하는 것을 권장합니다.&#x20;
* 원스토어는 검증 및 모니터링 목적으로 결제 테스트를 진행할 수 있으며, 해당 테스트 건들도 결제/결제취소 시 동일하게 notification이 발송됩니다.원스토어가 진행한 결제 테스트 내역은 주기적으로 원스토어에서 자체 취소 처리합니다.

**PNS(Payment Notification Service) 소개**

![](https://dev.onestore.co.kr/wiki/ko/doc/files/1572938/7340202/1/1593141026000/PNS.PNG)

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

PNS를 수신 받을 개발사 서버의 URL은 '개발자센터 > Apps > 상품 선택 > In-App정보' 메뉴에서 'Payment Notification' 버튼을 클릭하면 설정할 수 있습니다.\
URL은 Sandbox(개발용) 결제환경 및 상용(상용테스트 포함) 결제환경을 각각 설정할 수 있으며, 개발용/상용 서버가 동일할 경우 동일한 URL을 입력하시면 됩니다.

<figure><img src="https://1837360763-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fot0z57AnnXZ02C5qyePV%2Fuploads%2FMrSphupCGKsaXKsLmHnu%2Fimage.png?alt=media&#x26;token=66be4ca5-3cad-45a8-a02e-a79c5e500df8" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1837360763-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fot0z57AnnXZ02C5qyePV%2Fuploads%2FGWwXzP2SWp5IH7ASenKV%2Fimage.png?alt=media&#x26;token=427aa878-5c40-4a69-b38b-06247223a5a8" alt=""><figcaption></figcaption></figure>

### **PNS(Payment Notification Service) message format (원스토어 > 개발사 서버)** <a href="#pns-paymentnotificationservice-pns-paymentnotificationservice-messageformat-greater-than" id="pns-paymentnotificationservice-pns-paymentnotificationservice-messageformat-greater-than"></a>

| Parameter Name:Type         | 필수여부                   | Description                                                                       |                                       |
| --------------------------- | ---------------------- | --------------------------------------------------------------------------------- | ------------------------------------- |
| msgVersion                  | Y                      | <p>메시지 버전<br></p><ul><li>개발(Sandbox) : 2.0.0D</li><li>상용(상용테스트) : 2.0.0</li></ul> |                                       |
| packageName : String        | Y                      | 앱의 패키지 네임                                                                         |                                       |
| productId : String          | Y                      | 인앱상품의 상품 ID                                                                       |                                       |
| messageType : String        | Y                      | "SINGLE\_PAYMENT\_TRANSACTION" 고정                                                 |                                       |
| purchaseId : String         | Y                      | 구매 ID                                                                             |                                       |
| developerPayload : String   | N                      | 구매건을 식별하기 위해 개발사에서 관리하는 식별자                                                       |                                       |
| purchaseTimeMillis : Long   | Y                      | 원스토어 결제 시스템에서 결제가 완료된 시간(timestamp:Long)                                          |                                       |
| purcahseState : enum        | Y                      | <p><br></p><ul><li>COMPLETED : 결제완료 </li><li>CANCELED : 취소</li></ul><p><br></p>   |                                       |
| price : Number              | Y                      | 결제 금액                                                                             |                                       |
| productName : String        | N                      | 구매요청 시 개발사가 customized 인앱상품 제목을 설정한 경우 전달                                         |                                       |
| paymentTypeList : object\[] | Y                      | 결제 정보                                                                             |                                       |
| <p><br></p>                 | paymentMethod : String | Y                                                                                 | 결제 수단 (상세 내용은 아래 paymentMethod 정의 참고) |
| <p><br></p>                 | amount : Number        | Y                                                                                 | 결제 금액                                 |
| billingKey                  | N                      | 확장 기능용 결제 키                                                                       |                                       |
| isTestMdn                   | N                      | 시험폰 여부(true : 시험폰, false : 시험폰 아님)                                                |                                       |
| signature : String          | Y                      | 본 메시지에 대한 signature                                                               |                                       |

#### **PNS(Payment Notification Service) message 예시** <a href="#pns-paymentnotificationservice-pns-paymentnotificationservice-message" id="pns-paymentnotificationservice-pns-paymentnotificationservice-message"></a>

```
{
  "msgVersion": "2.0.0.D",
  "packageName":"com.onestore.pns",
  "productId":"0900001234",
  "messageType":"SINGLE_PAYMENT_TRANSACTION",
  "purchaseId":"SANDBOX3000000004564",
  "developerPayload":"OS_000211234",
  "purchaseTimeMillis":24431212233,
  "purchaseState":"COMPLETED",
  "price":10000,
  "productName":"GOLD100(+20)"
  "paymentTypeList":[
    {
      "paymentMethod":"DCB",
      "amount":3000
    },
    {
      "paymentMethod":"ONESTORECASH",
      "amount":7000
    }
  ],
  "billingKey" : "36FED4C6E4AC9E29ADAF356057DB98B5CB92126B1D52E8757701E3A261AF49CCFBFC49F5FEF6E277A7A10E9076B523D839E9D84CE9225498155C5065529E22F5",
  "isTestMdn" : true,
  "signature":   "BwJdUVT/iFFT1MKIFZTdkD/y5+b8h4hCuVB3zVrYcT7pMf1wuWrXNZK9ZA1FhUlWPa7C10Do4CDr8k28QOejGOCgiit5RYzL1tF5eRjFkY66oD3qfNvexkt5wwVjJP5EYyzqwCDVkbx004eGUX46LzaxVV7i137e4KyUrdk9Q5c="
}
```

<br>

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

| paymentMethod   | 결제수단 명칭          | 설명                         |
| --------------- | ---------------- | -------------------------- |
| DCB             | 휴대폰결제            | 통신사 요금청구서에 '정보이용료' 항목으로 청구 |
| PHONEBILL       | 휴대폰 소액결제         | 통신사 요금청구서에 '소액결제' 항목으로 청구  |
| ONEPAY          | ONE pay          | 원스토어가 제공하는 신용카드 간편결제       |
| ONEPAYBANKACCT  | ONE pay 계좌결제     | 원스토어가 제공하는 계좌 간편결제         |
| ONEPAYDCB       | ONE pay 휴대폰결제    | 원스토어가 제공하는 간편 휴대폰결제        |
| ONEPAYPHONEBILL | ONE pay 휴대폰 소액결제 | 원스토어가 제공하는 간편 소액결제         |
| CREDITCARD      | 신용카드             | 일반 신용카드 결제                 |
| 11PAY           | 11Pay            | SK플래닛이 제공하는 신용카드 간편결제      |
| NAVERPAY        | N pay            | 네이버에서 제공하는 네이버페이 결제        |
| CULTURELAND     | 컬쳐캐쉬             | 한국문화진흥에서 제공한는 컬쳐캐쉬 결제      |
| TMEMBERSHIP     | T멤버십             | SK텔레콤이 제공하는 T멤버십 결제        |
| OCB             | OK cashbag       | SK플래닛이 제공하는 OK캐쉬백 결제       |
| GAMECASH        | 게임캐쉬             | 원스토어 게임캐쉬 결제               |
| ONESTORECASH    | 원스토어 캐쉬          | 원스토어 캐쉬 결제                 |
| ONESTORECOUPON  | 원스토어 쿠폰          | 원스토어 쿠폰 결제                 |
| TMONEY          | 모바일 T Money      | 티모넷이 제공하는 모바일티머니 결제        |
| KTMEMBERSHIP    | KT멤버쉽            | KT 멤버쉽 결제                  |
| LGMEMBERSHIP    | U+멤버쉽            | LG U+ 멤버쉽 결제               |

#### **Signature 검증 방법** <a href="#pns-paymentnotificationservice-signature" id="pns-paymentnotificationservice-signature"></a>

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

* 코드 내 PublicKey는 '개발자센터 > Apps > In-App정보 > 인증 및 라이선스'에서 제공되는 라이선스 키를 의미합니다.\
  라이선스 키에 대한 상세한 내용은 [인앱결제 적용을 위한 사전준비](https://onestore-dev.gitbook.io/dev/tools/billing/old-version/v17/undefined-3) 페이지 내 '라이선스 키 및 OAuth 인증 정보 확인' 부분을 참고하시기 바랍니다.&#x20;

**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 {
        // 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));
    }
}
```

**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":"2.0.0.D","purchaseId":"SANDBOX3000000004564","developerPayload":"OS_000211234","packageName":"com.onestore.pns","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";
}
?>
```

**Python**

```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()))
```

<br>

### &#x20;<a href="#pns-paymentnotificationservice-pns-url" id="pns-paymentnotificationservice-pns-url"></a>
