개요
원스토어는 개발자를 위해 두 가지 Push Notification Service를 제공합니다.
인앱상품 결제 또는 결제취소가 발생하면 원스토어가 개발사 서버로 알림을 전송하는 PNS(Payment Notification Service)
구독 상태가 변경되면 개발사 서버로 알림을 전송하는 SNS (Subscription Notifacation Service)
주의사항
Notification은 발송/수신 서버의 상태에 따라 지연 또는 유실될 수 있으므로, notification 수신을 기준으로 상품(서비스)을 제공하는 것은 권장하지 않습니다.
정상적인 결제 건인지 Server to Server로 확인하기를 원하신다면 PNS notification을 이용하는 대신, 관련 서버 API로 조회하는 것을 권장합니다.
원스토어는 검증 및 모니터링 목적으로 결제 테스트를 진행할 수 있으며, 해당 테스트 건들도 결제/결제취소 시 동일하게 notification이 발송됩니다. 원스토어가 진행한 결제 테스트 내역은 주기적으로 원스토어에서 자체 취소 처리합니다.
PNS 수신 서버 URL 설정
PNS를 수신 받을 개발사 서버의 URL은 '개발자센터 > Apps > 상품 선택 > In-App정보' 메뉴에서 'PNS 관리' 버튼을 클릭하면 설정할 수 있습니다.
URL은 Sandbox(개발용) 결제환경 및 상용(상용테스트 포함) 결제환경을 각각 설정할 수 있으며, 개발용/상용 서버가 동일할 경우 동일한 URL을 입력하시면 됩니다.
PNS 상세
Payment Notification 메시지 발송 규격 (원스토어 → 개발사 서버)
URI : 개발자 센터에서 설정한 Payment Notification URL
Example
Copy {
"msgVersion" : "3.0.0"
"packageName":"com.onestore.pns",
"productId":"0900001234",
"messageType":"SINGLE_PAYMENT_TRANSACTION",
"purchaseId":"SANDBOX3000000004564",
"developerPayload":"OS_000211234",
"purchaseTimeMillis":24431212233,
"purchaseState":"COMPLETED",
"price":"10000",
"priceCurrencyCode":"KRW"
"productName":"GOLD100(+20)"
"paymentTypeList":[
{
"paymentMethod":"DCB",
"amount":"3000"
},
{
"paymentMethod":"ONESTORECASH",
"amount":"7000"
}
],
"billingKey" : "36FED4C6E4AC9E29ADAF356057DB98B5CB92126B1D52E8757701E3A261AF49CCFBFC49F5FEF6E277A7A10E9076B523D839E9D84CE9225498155C5065529E22F5",
"isTestMdn" : true,
"purchaseToken" : "TOKEN...",
"environment" : "SANDBOX",
"marketCode" : "MKT_ONE"
"signature" "SIGNATURE..."
}
paymentMethod(원스토어 결제수단) 정의
Signature 검증 방법
아래 코드를 사용하면 signature에 대한 위변조 여부를 확인할 수 있습니다.
코드 내 PublicKey는 '개발자센터 > Apps > In-App정보 > 인증 및 라이선스'에서 제공되는 라이선스 키를 의미합니다.
라이선스 키에 대한 상세한 내용은 인앱결제 적용을 위한 사전준비 페이지 내 '라이선스 키 및 OAuth 인증 정보 확인' 부분을 참고하시기 바랍니다.
JAVA
Copy 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
Copy <?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
Copy # -*- 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()))
Subscription Notification 메시지 발송 규격 (원스토어 → 개발사 서버)
URI : 개발자 센터에서 설정한 Subscription Notification URL
Example
Copy {
"msgVersion":"3.0.0",
"packageName":"com.onestore.pns",
"eventTimeMillis":24431212233000,
"subscriptionNotification": {
"version": "1",
"notificationType" : 1,
"purchaseToken":"TOKEN",
"productId": "com.product.id"
},
"environmenmt": "COMMERCIAL",
"marketCode": "MKT_ONE"
}
구독상태 정의
Notification 전송 정책
원스토어의 PNS 서버는 HTTP(S) 요청을 통하여 개발사 서버로 notification을 전송합니다.
이때 개발사 서버는 notification를 정상적으로 수신했다는 의미로 HTTP Status Code를 200으로 응답하여야 합니다.
만약 네트워크 지연으로 인한 유실 또는 개발사 서버의 비정상적인 상황으로 HTTP Status Code를 200으로 응답받지 못한 경우, PNS서버는 notification 전송이 실패했다고 판단하고 3일간 최대 30회의 재전송을 수행하게 됩니다.
notification의 재전송은 아래의 예시와 같이 일정한 delay를 가진 후 실행되며, 재시도 회수가 많아지면 delay가 점차 늘어나는 구조로 되어 있습니다.
Example