거래취소


취소요청

거래취소 에서는 상점에서 NHN KCP의 거래 상태를 취소하는 방법에 대해 설명합니다.

취소데이터를 API URL로 승인요청 하세요.

API Request

<!-- API 호출 -->

1https://spl.kcp.co.kr/gw/mod/v1/cancel

테스트 환경 : https://stg-spl.kcp.co.kr/gw/mod/v1/cancel


서비스 인증서

NHN KCP 발급 인증서 내 데이터 값 추출,
가맹점 인증을 위해 KCP로부터 발급 받은 인증서 정보를 text 형식으로 전달해주셔야 하며,
인증서의 text 값을 데이터 직렬화하여 kcp_cert_info 의 value 값으로 전달하시기 바랍니다.

kcp_cert_info는 결제 승인 취소 거래등록 시에 필요합니다.

KCP 서비스 인증서 관련 상세 내용은 서버 인증서 페이지를 참고 부탁 드립니다.


kcp_sign_data

kcp_sign_data 는 거래취소 시에 필요합니다.

kcp_sign_data는 생성시개인키(PRIVATE KEY) 가 필요하며 테스트 개인키는 다운로드 자료실을 확인해주세요.

kcp_sign_data 생성예시

public class MakeSplParam
  {
    static{ Security.addProvider( new BouncyCastleProvider() ); }

      public static void main(String[] args)
      {
          PrivateKey priKey       = loadSplMctPrivateKeyPKCS8( "../splPrikeyPKCS8.pem", "changeit" ); // (개인키 경로, 개인키 비밀번호)
          String signData   = makeSignatureData( priKey, "T0000^20210719000000^PACA" );	// 서명데이터 생성
          System.out.println( "\n[서명데이터(kcp_sign_data)] : " + signData );
      }
      public static PrivateKey loadSplMctPrivateKeyPKCS8( String filePath, String privateKeyPassword )
      {
          PrivateKey priKey = null;
      Path path = Paths.get( filePath );
      String strPriKeyData = Files.readAllLines( path )
          .stream()
          .filter( line -> !line.startsWith( "-----BEGIN" ) && !line.startsWith( "-----END" ) )
          .collect( Collectors.joining() );

      byte[] btArrPriKey   = Base64.getDecoder().decode( strPriKeyData );

      ASN1Sequence derSeq = ASN1Sequence.getInstance( btArrPriKey );
      PKCS8EncryptedPrivateKeyInfo encPkcs8PriKeyInfo = new PKCS8EncryptedPrivateKeyInfo( org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance( derSeq ) );
      JcaPEMKeyConverter pemKeyConverter = new JcaPEMKeyConverter();
      InputDecryptorProvider decProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().build( privateKeyPassword.toCharArray() );
      PrivateKeyInfo priKeyInfo          = encPkcs8PriKeyInfo.decryptPrivateKeyInfo( decProvider );
      priKey                             = pemKeyConverter.getPrivateKey( priKeyInfo );

          return priKey;
      }
      public static String makeSignatureData(PrivateKey priKey, String targetData)
      {
          String signData = null;
          byte[] btArrTargetData = targetData.getBytes( StandardCharsets.UTF_8 );

      Signature sign = Signature.getInstance( "SHA256WithRSA" );
      sign.initSign( priKey );
      sign.update( btArrTargetData );

      byte[] btArrSignData = sign.sign();
      signData = Base64.getEncoder().encodeToString( btArrSignData );

      return signData;
      }
  }
<?php
  $cancel_target_data = "T0000^22284971100001^STSC";
  // 결제 취소 (cancel) = site_cd^KCP거래번호^취소유형

  $key_data = file_get_contents('../splPrikeyPKCS8.pem');
  // 개인키 경로 ("splPrikeyPKCS8.pem" 은 테스트용 개인키)
  $pri_key = openssl_pkey_get_private($key_data,'changeit');
  // 개인키 비밀번호

  openssl_sign($cancel_target_data, $signature, $pri_key, 'sha256WithRSAEncryption');
  // 결제 취소 signature 생성
  echo "cancel_signature :".base64_encode($signature)."<br><br>";
?>
namespace kcp_sign_data_sample
{
    class Program
    {
        static void Main(string[] args)
        {
            // PKCS#8 PEM READ
            StreamReader sr = new StreamReader("../splPrikeyPKCS8.pem"); // 개인키 경로 ("splPrikeyPKCS8.pem" 은 테스트용 개인키)
            String privateKeyText = sr.ReadToEnd();
            // 개인키 비밀번호
            string privateKeyPass = "changeit"; // 개인키 비밀번호 ("changeit" 은 테스트용 개인키 비밀번호)
            // 개인키정보 READ
            StringReader stringReader = new StringReader(privateKeyText);
            PemReader pemReader = new PemReader(stringReader, new PasswordFinder(privateKeyPass));
            RsaPrivateCrtKeyParameters keyParams = (RsaPrivateCrtKeyParameters)pemReader.ReadObject();

            byte[] tmpSource = Encoding.ASCII.GetBytes("T0000^22671971380028^PACA");

            ISigner sign = SignerUtilities.GetSigner(PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id);
            sign.Init(true, keyParams);
            sign.BlockUpdate(tmpSource, 0, tmpSource.Length);

            var kcp_sign_data = sign.GenerateSignature();
            // Console 출력
            Console.WriteLine("kcp_sign_data:" + Convert.ToBase64String(kcp_sign_data));
            Console.ReadKey();
        }

        private class PasswordFinder : IPasswordFinder
        {
            private string password;
            public PasswordFinder(string pwd)
            {
              password = pwd;
            }
            public char[] GetPassword()
            {
              return password.ToCharArray();
            }
        }
    }
}
<%
    textToSign = "T0000^22671971380028^PACA" '서명대상데이터(site_cd^tno^mod_type)
    set kcpSign = server.createobject("kcp_sign_data_lib.GenSign") '서명데이터생성 예제 lib
    kcp_sign_data = kcpSign.Sign("../splPrikeyPKCS8.pem", "changeit", textToSign) '(테스트용 개인키,테스트용 개인키 비밀번호)

    response.write "kcp_sign_data : " + kcp_sign_data + "<br/>"
%>

  const data = "T0000^22296971511092^PACA";
  const crypto = require('crypto');
  const fs = require('fs');
  // 서명데이터 생성 함수 호출
  const signature = getSignatureToVerify(data);
  // 서명데이터 생성을 위한 개인키 READ
  function getPrivateKeySomehow()
  {
      const pKey = fs.readFileSync('../splPrikeyPKCS8.pem', 'utf8' ); // "splPrikeyPKCS8.pem" 은 테스트용 개인키
      const pKeyObj = crypto.createPrivateKey({
          key : pKey,
          passphrase : "changeit", // "changeit" 은 테스트용 개인키 비밀번호
          format : "pem",
          type : "pkcs8"
          })
      const pKeyStr = pKeyObj.export({
           format: 'pem',
            type: 'pkcs8'
          }).toString();
      return pKeyStr;
  }

  // 서명데이터 생성 함수
  function getSignatureToVerify(data)
  {
      const privateKey = getPrivateKeySomehow();
      const sign = crypto.createSign("SHA256");
      sign.update(data);
      const signature = sign.sign(privateKey, "base64");
      console.log(">>> Signature:\n\n" + signature);
      return signature;
  }

  import OpenSSL
  from OpenSSL import crypto
  import base64

  data = 'T0000^22671971380028^PACA'
  # "splPrikeyPKCS8.pem" 은 테스트용 개인키
  key_file = open('../splPrikeyPKCS8.pem', 'r')
  key = key_file.read()
  key_file.close()
  # "changeit" 은 테스트용 개인키비밀번호
  password = 'changeit'.encode('utf-8')
  pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key, password)
  # 서명데이터생성
  sign = OpenSSL.crypto.sign(pkey, data, 'sha256')
  kcp_sign_data = base64.b64encode(sign)
  print("kcp_sign_data : ", kcp_sign_data)

취소요청 파라미터 가이드

요청 파라미터

string5

site_cd

KCP발급 사이트(상점)코드
테스트코드 : T0000


string14

tno

NHN KCP 거래 고유번호


string가변

kcp_cert_info

KCP 인증서정보(직렬화)
관리자 페이지에서 발급받은 인증서 정보(직렬화 하여 요청)


string가변

kcp_sign_data

KCP 암호화데이터
site_cd + "^" + tno + "^" + mod_type


string4

mod_type

전체 승인취소 - STSC / 부분취소 - STPC


number12

mod_mny

부분취소일 경우 부분취소금액


number12

rem_mny

부분취소일 경우 남은 원거래 금액


string100

mod_desc

취소사유

취소요청 예시

<!-- KCP 전체 취소요청 데이터 -->

1<!-- 취소요청 데이터 -->
2{
3 "site_cd":"T0000",
4 "kcp_cert_info":"-----BEGIN CERTIFICATE-----MIID3DCCAsSgAwIBAgIJAMzLXkRXpY3KMA0GCSqGSIb3DQEBCwU...bpE1aPTjDDQsuUduNaCu1jYuBALO+LelrFA...VNeequGLUZSx1il+tJU=-----END CERTIFICATE-----",
5 "kcp_sign_data":"QdwMF6y3GU1JTVkSv7Yn20CCCTeFrKkjvrdZOjShiFibFo...cA0nyX+4HEUZ4Fy3U+htmkZqAfJljeujC1KAL5Flnzqbp5Tst5p5SvZ...0qH7NSq0c6BpedDZb04w==",
6 "mod_type":"STSC",
7 "tno":"2099123112345"
8}

<!-- KCP 부분 취소요청 데이터 -->

1<!-- 취소요청 데이터 -->
2{
3  "site_cd":"T0000",
4  "kcp_cert_info":"-----BEGIN CERTIFICATE-----MIID3DCCAsSgAwIBAgIJAMzLXkRXpY3KMA0GCSqGSIb3DQEBCwU...bpE1aPTjDDQsuUduNaCu1jYuBALO+LelrFA...VNeequGLUZSx1il+tJU=-----END CERTIFICATE-----",
5  "kcp_sign_data":"QdwMF6y3GU1JTVkSv7Yn20CCCTeFrKkjvrdZOjShiFibFo...cA0nyX+4HEUZ4Fy3U+htmkZqAfJljeujC1KAL5Flnzqbp5Tst5p5SvZ...0qH7NSq0c6BpedDZb04w==",
6  "mod_type":"STPC",
7  "tno":"2099123112345",
8  "mod_mny":"5000",
9  "rem_mny":"10000",
10 "mod_desc":"취소 사유를 입력하세요"
11}

Json String 전송 / 취소 응답 값은 동일한 Json 형태로 리턴됩니다.


취소응답 파라미터 가이드

응답 파라미터

string4

res_cd

결과코드
정상 승인이 이루어졌을 경우 ‘0000’ 값 리턴


string100

res_msg

결과메세지


string14

tno

NHN KCP 거래 고유번호
※ 거래고유번호 전체로 사용 하시기 바랍니다. (임의의 숫자나 파싱하여 사용 불가)


number14

canc_time

취소시각


number12

mod_mny

부분취소일 경우 부분취소금액


number12

rem_mny

부분취소일 경우 남은 원거래 금액


number14

mod_pacn_seq_no

부분취소 일련번호


number12

card_mod_mny

카드취소금액(부분취소일 경우)
부분취소금액 중 카드취소 금액


number12

coupon_mod_mny

쿠폰취소금액(부분취소일 경우)
부분취소금액 중 쿠폰취소 금액

취소처리 샘플 예시

target_URL = "https://stg-spl.kcp.co.kr/gw/mod/v1/cancel";  // 취소 개발서버

json_req = new JSONObject();
json_req.put("site_cd", site_cd);
json_req.put("kcp_cert_info", kcp_cert_info);
json_req.put("kcp_sign_data", kcp_sign_data); // 가맹점에 맞게 생성 필요
json_req.put("tno", tno);
json_req.put("mod_type", "STSC");
json_req.put("mod_desc", "취소");

temp_req_data = json_req.toString();
req_data = temp_req_data.replace(",",",\r\n");
inputLine = null;
outResult = new StringBuffer();

// API REQ
URL url = new URL(target_URL);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept-Charset", "UTF-8");

OutputStream os = conn.getOutputStream();
os.write(req_data.getBytes("UTF-8"));
os.flush();

// API RES
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
while ((inputLine = in.readLine()) != null)
{
  outResult.append(inputLine);
}
conn.disconnect();

temp_result = outResult.toString();
res_data = temp_result.replace(",",",\r\n");
// RES JSON DATA Parsing
parser = new JSONParser();
json_res = (JSONObject)parser.parse(temp_result);

res_cd  = f_get_parm((String)json_res.get("res_cd"));
res_msg = f_get_parm((String)json_res.get("res_msg"));
$target_URL = "https://stg-spl.kcp.co.kr/gw/mod/v1/cancel"; // 취소 개발서버

$data = [
    'site_cd'        => $site_cd,
    'kcp_cert_info'  => $kcp_cert_info,
    'kcp_sign_data'  => $kcp_sign_data,
    'tno'            => $tno,
    'mod_type'       => 'STSC',
    'mod_desc'       => '취소',
];

$req_data = json_encode($data);

$header_data = array( "Content-Type: application/json", "charset=utf-8" );

// API REQ
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_URL);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header_data);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $req_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// API RES
$res_data  = curl_exec($ch);

// RES JSON DATA Parsing
$json_res = json_decode($res_data, true);

$res_cd = $json_res["res_cd"];
$res_msg = $json_res["res_msg"];

curl_close($ch);
target_URL = "https://stg-spl.kcp.co.kr/gw/mod/v1/cancel"; // 취소 개발서버

req_data = "{\"site_cd\" : \"" + site_cd + "\"," +
                      "\"kcp_cert_info\":\"" + KCP_CERT_INFO + "\"," +
                      "\"kcp_sign_data\":\"" + kcp_sign_data + "\"," +
                      "\"tno\":\"" + tno + "\"," +
                      "\"mod_type\":\"STSC\"," +
                      "\"mod_desc\":\"취소\"}";

// API REQ
req = (HttpWebRequest)WebRequest.Create(target_URL);
req.Method = "POST";
req.ContentType = "application/json";

byte_req = Encoding.UTF8.GetBytes(req_data);
req.ContentLength = byte_req.Length;

st = req.GetRequestStream();
st.Write(byte_req, 0, byte_req.Length);
st.Close();

// API RES
res = (HttpWebResponse)req.GetResponse();
st_read = new StreamReader(res.GetResponseStream(), Encoding.GetEncoding("utf-8"));
res_data = st_read.ReadToEnd();

st_read.Close();
res.Close();

// RES JSON DATA Parsing
json_data = JObject.Parse(res_data);
res_cd = json_data["res_cd"].ToString();
res_msg = json_data["res_msg"].ToString();
' 취소요청 API
target_URL = "https://stg-spl.kcp.co.kr/gw/mod/v1/cancel" ' 취소요청 API 개발환경

req_data = "{""site_cd"":""" & site_cd & """,""kcp_cert_info"":""" & kcp_cert_info & """,""kcp_sign_data"":""" & kcp_sign_data & """,""tno"":""" & tno & """,""mod_type"":""STSC"",""mod_desc"":""취소""}"

' API REQ
set cancel_req = Server.CreateObject("MSXML2.ServerXMLHTTP")
cancel_req.open "POST", target_URL, false
cancel_req.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
cancel_req.send req_data

' API RES
'요청 성공인 경우
if cancel_req.status = 200 then
    res_data = cancel_req.ResponseText
'요청 실패인 경우
else
    res_data = "http error code : " & cancel_req.status
end if

set cancel_req = nothing

'' RES JSON DATA Parsing
set json_cancel_data = JSON.parse(res_data)

res_cd = json_cancel_data.res_cd
res_msg = json_cancel_data.res_msg
req_data = {
    site_cd : site_cd,
    tno : tno,
    kcp_cert_info : KCP_CERT_INFO,
    kcp_sign_data : kcp_sign_data,
    mod_type : mod_type,
    mod_desc : ''
  };
  // 취소 API URL
  fetch("https://stg-spl.kcp.co.kr/gw/mod/v1/cancel", {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(req_data),
  })
  // 취소 API RES
  .then(response => {
    return response.json();
  })
  // RES JSON DATA Parsing
  .then(data => {
    res.render('kcp_api_pay', {
      req_data : JSON.stringify(req_data),
      res_data : JSON.stringify(data),
    });
  })
# 취소요청 API
target_URL = 'https://stg-spl.kcp.co.kr/gw/mod/v1/cancel' # 취소요청 API 개발환경
tno = res_data['tno']
mod_type = 'STSC' # 전체취소

req_data = {
  'site_cd' : site_cd,
  'tno' : tno,
  'kcp_cert_info' : KCP_CERT_INFO,
  'kcp_sign_data' : kcp_sign_data,
  'mod_type' : mod_type,
  'mod_desc' : '취소'
}
res = requests.post(target_URL, headers=headers, data=json.dumps(req_data, ensure_ascii=False, indent="\t").encode('utf8'))