구분 | IP | 비고 |
---|---|---|
테스트 | 211.193.35.11 | STG |
운영 - 결제 | 211.193.35.20 | |
운영 - 재통보 | 211.193.35.215 | 재통보 서비스 사용시 |
발급 정책 | 옵션 |
---|---|
동일 카드 등록여부 | 거절/기등록 빌링키 정보 전달/삭제 후 신규 등록/미삭제 신규 등록 |
추가 카드 등록여부 | 거절/삭제 후 신규 등록/미삭제 신규 등록 |
Bill-Key 발급 정책 | 회원 기준 1개 발급/회원 기준 다수개 발급 |
본인인증 사용여부 |
미사용/휴대폰 인증 - 본인인증 미사용 옵션 시 타인의 카드 정보를 등록하여 결제 가능한 점 유의해주시길 바랍니다. |
빌링 키 발급 파라미터
파라미터 | 항목명 | 길이(char) | 필수여부 | 설명 |
---|---|---|---|---|
PayMethod | 결제수단 | 10 | 필수 | CARD: 신용카드 |
Mid | 상점 ID | 10 | 필수 | |
Moid | 상품주문번호 | 40 | 필수 | |
MallUserId | 회원사 고객 ID | 20 | 필수 | 회원가입 시 고객 구분값으로 사용 |
MallIp | 상점 IP | 20 | 선택 | 상점서버 IP |
UserIp | 고객 IP | 20 | 선택 | 고객 IP |
ReturnUrl | 상점 인증결과 전송 URL | 100 | 필수 | |
VerifyValue | 위·변조 검증값 | 가변 | 필수 | SHA256 해시 암호화 |
SspMallId | 결제MALL ID | 10 | 필수 | 발급받은 SspMallId |
EdiDate | 결제요청일시 | 14 | 필수 | |
GoodsName | 거래 상품명 | 40 | 옵션 | 값이 없는 경우 플러그인에 "빌링키인증" 이라는 문자로 안내 |
BuyerName | 구매자명 | 30 | 선택 | |
BuyerTel | 구매자연락처 | 40 | 선택 | |
BuyerEmail | 구매자메일주소 | 60 | 선택 | |
EncodingType | 응답 Charset | 6 | 선택 | utf8(기본값), euckr |
MallReserved | 상점 예비필드 | 500 | 선택 | Base64 인코딩필요
(평문으로 전달시 한글 및 특수문자가 정상적으로 전달되지 않음) |
SkinColor | 플러그인 SKIN | 10 | 선택 | RED(기본값), GREEN, BLUE, PURPLE |
RtnUrlEncUse | 응답 데이터 UrlEncoding 처리여부 | 1 | 선택 | Y: 사용, N(기본값): 미사용 |
IsPwdPass | 결제 비밀번호 등록 Skip 여부 | 1 | 선택 | Y(기본값): 비밀번호 설정 미사용, N: 비밀번호 설정 사용 |
UseIfrmYn | Iframe 사용 여부 | 1 | 선택 | Y: Iframe 사용 , N(기본값): Iframe 미사용 |
파라미터 | 항목명 | 길이(char) | 설명 |
---|---|---|---|
PayMethod | 결제수단 | 10 | CARD: 신용카드 |
Mid | 상점 ID | 10 | |
BuyerName | 구매자명 | 30 | 본인확인서비스를 통하여 인증된 사용자명 |
Moid | 주문번호 | 40 | |
Tid | 거래 ID | 40 | |
BillTokenKey | 고정식 토큰(빌링키) | 가변 | AES 암호화되어 전달됩니다. Step1-3 발급 결과 받기를 참고하여 복호화 후
이용해주시길 바랍니다. (암호화키=상점키) |
MallUserId | 회원사 고객 ID | 20 | 회원가입 시 고객 구분값으로 사용 |
ResultCode | 등록 결과 | 4 | 성공: 3001, 실패: 그 외 |
ResultMsg | 등록 결과 메시지 | 가변 | |
MallReserved | 상점 예비필드 | 500 | 요청한 값 응답 |
VerifyValue | 위·변조 검증값 | 50 | SHA256 해시 암호화 |
SspMallId | 간편결제 가맹점 ID | 10 | |
IssuerCardCd | 카드사코드 | 2 | |
IssuerCardNm | 카드사명 | 10 | |
DisplayCardNo | 표시용 카드번호 | 20 | 실 카드번호 마스킹 처리 |
CardExpire | 빌키 유효기간 | 4 | 카드의 유효기간이 아닌 빌키의 유효기간(YYMM) 입니다 |
EncodingType | 응답 Charset | 6 | utf8(기본값), euckr |
RtnUrlEncUse | 응답 데이터 UrlEncoding 처리여부 | 1 | Y: 사용, N(기본값): 미사용 |
옵션 | 비고 |
---|---|
미사용 | 본인인증 성공 여부만 체크(default) |
성명 일치 | 본인인증 성명과 구매자명(BuyerName) 일치여부 확인 |
휴대폰번호 일치 | 본인인증 휴대폰 번호와 구매자 휴대폰번호(BuyerTel) 일치여부 확인 |
성명&휴대폰번호 일치 |
본인인증 성명과 구매자명(BuyerName) 일치 & 본인인증 휴대폰 번호와 구매자 휴대폰번호(BuyerTel) 일치 확인 |
String Mid = ""; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
String MerchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
String EdiDate = getyyyyMMddHHmmss();
String EncryptData = encodeSHA256Base64(EdiDate + Mid + Moid + "SMARTRO!@#");
String today = getyyyyMMddHHmm(); // 현재일자. 캐시방지용으로 사용
/* SHA256 암호화 */
public static final String encodeSHA256Base64(String strPW) {
String passACL = null;
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (Exception e) {
e.printStackTrace();
}
md.update(strPW.getBytes());
byte[] raw = md.digest();
byte[] encodedBytes = Base64.encodeBase64(raw);
passACL = new String(encodedBytes);
return passACL;
}
/* 현재일자 */
public static final String getyyyyMMddHHmmss() {
SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss");
return yyyyMMddHHmmss.format(new Date());
}
/* 현재일자 */
public static final String getyyyyMMddHHmm() {
SimpleDateFormat yyyyMMddHHmm = new SimpleDateFormat("yyyyMMddHHmm");
return yyyyMMddHHmm.format(new Date());
}
$Mid = ""; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
$MerchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
$EdiDate = date("YmdHis");
$EncryptData = base64_encode(hash('sha256', $EdiDate.$Mid.$Moid."SMARTRO!@#", true));
$today = date("YmdHi"); // 현재일자. 캐시방지용으로 사용
Mid = "" ' 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
MerchantKey = "" ' 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
EdiDate = Year(Now()) & right("0"&Month(Now()),2) & right("0"&Day(Now()),2) & right("0"&Hour(Now()),2) & right("0"&Minute(Now()),2) & right("0"&Second(Now()),2)
EncryptData = CryptoUtil.Sha256Encrypt(EdiDate & Mid & Moid & "SMARTRO!@#"))
today = Year(Now()) & right("0"&Month(Now()),2) & right("0"&Day(Now()),2) & right("0"&Hour(Now()),2) & right("0"&Minute(Now()),2) ' 현재일자. 캐시방지용으로 사용
// DLL 암호화 소스
using System;
using System.EnterpriseServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace SmartropayLib
{
public class CryptoUtil : ServicedComponent
{
///
/// SHA256 암호화
///
/// 문자열
/// 암호화 문자열
public string Sha256Encrypt(string text)
{
SHA256Managed sha256Managed = new SHA256Managed();
byte[] encryptBytes = sha256Managed.ComputeHash(Encoding.UTF8.GetBytes(text));
return Convert.ToBase64String(encryptBytes);
}
}
}
SmartroPAY 연동가이드
String tempVerifyValue = null;
boolean result = false;
String BillTokenKey = request.getParameter("BillTokenKey");
String Mid = request.getParameter("Mid");
String DisplayCardNo = request.getParameter("DisplayCardNo");
String ResultCode = request.getParameter("ResultCode");
String VerifyValue = request.getParameter("VerifyValue");
try {
String merchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
String mKey = merchantKey.substring(0,32);
BillTokenKey = AESDecode(BillTokenKey, mKey); // AESDecode 후 가맹점DB에 빌링키 저장
String tempVerifyValueString = Mid + BillTokenKey + DisplayCardNo + ResultCode + "SMARTRO!@#";
tempVerifyValue = encodeSHA256Base64(tempVerifyValueString);
result = tempVerifyValue.equals(VerifyValue) ? true : false;
}catch(Exception e) {
e.printStackTrace();
}
// Decode 함수
public static byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static String AESDecode(String str, String key) throws Exception {
//byte[] textBytes = Base64.decodeBase64(str.getBytes());
String result = null;
byte[] textBytes = Base64.decodeBase64(str);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
result = new String(cipher.doFinal(textBytes), "UTF-8");
return result;
}
빌링결제 파라미터
파라미터 | 항목명 | 길이(char) | 필수여부 | 설명 |
---|---|---|---|---|
PayMethod | 지불수단 | 20 | 필수 | CARD |
GoodsCnt | 결제상품 품목 개수 | 2 | 필수 | 기본값: 1 |
GoodsName | 거래 상품명 | 40 | 필수 | |
Amt | 거래 금액 | 12 | 필수 | 숫자만 가능, 문장부호 제외 |
Moid | 상품주문번호 | 40 | 필수 | 특수문자 포함 불가 |
MallUserId | 회원사 고객 ID | 20 | 필수 | 빌링 결제 요청시 고객 구분값으로 사용 |
Mid | 상점아이디 | 10 | 필수 | |
BillTokenKey | 고정식 토큰(빌링키) | 30 | 필수 | 복호화하여 전송 |
BuyerName | 구매자명 | 30 | 필수 | |
BuyerTel | 구매자연락처 | 40 | 필수 | |
BuyerEmail | 구매자메일주소 | 60 | 필수 | |
CardQuota | 할부 개월 | 2 | 필수 | 00: 일시불(기본값), 02, 03, ... |
VerifyValue | 위·변조 검증값 | 50 | 필수 | SHA256 해시 암호화 |
MallIp | 상점 IP | 20 | 선택 | 상점서버 IP |
UserIp | 고객 IP | 20 | 선택 | 고객 IP |
Currency | 통화구분 | 3 | 선택 | KRW(기본값) |
TaxAmt | 과세 | 9 | 선택 | 숫자만 가능, 문장부호 제외 |
TaxFreeAmt | 비과세 | 9 | 선택 | 숫자만 가능, 문장부호 제외 |
VatAmt | 부가세 | 9 | 선택 | 숫자만 가능, 문장부호 제외 |
CardPoint | 포인트 사용여부 | 1 | 선택 | 신용카드 카드포인트 사용여부 |
파라미터 | 항목명 | 길이(char) | 설명 |
---|---|---|---|
Tid | 거래번호 | 30 | |
AuthDate | 승인일자 | 12 | YYMMDDHHMMSS(형식) |
AuthCode | 승인번호 | 30 | |
ResultCode | 결과코드 | 4 | |
ResultMsg | 결과메시지 | 100 | |
AppCardCode | 발급사코드 | 2 | |
AppCardName | 발급사명 | 20 | |
CardNum | 카드번호 | 20 | 실 카드번호 마스킹 처리 |
CardInterest | 무이자여부 | 1 | 0: 일반, 1: 카드사 무이자, 2: 상점 무이자 |
Currency | 결제통화 | 3 | |
VerifyValue | 위·변조 검증값 | 50 | SHA256 해시 암호화 / URL Decoding 필요 |
MallReserved | 상점예비정보 | 500 | 결제 요청 시 설정한 값 전달 |
String url = "https://tapproval.smartropay.co.kr/payment/approval/approvalUnionBill.do"; // 테스트
// String url = "https://approval.smartropay.co.kr/payment/approval/approvalUnionBill.do"; // 운영
JSONObject body = new JSONObject();
JSONObject paramData = new JSONObject();
String merchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
String Mid = ""; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
String EdiDate = getyyyyMMddHHmmss();
String Moid = "";
String BillTokenKey = ""; // 빌링키 발급을 통해 생성된 키
String Amt = "1004";
// 검증값 SHA256 암호화(빌링키 + Mid + 금액)
String VerifyValue = encodeSHA256Base64(BillTokenKey + Mid + Amt);
// 요청 파라미터 (각 값들은 가맹점 환경에 맞추어 설정해 주세요.)
paramData.put("UserIp" ,"10.0.0.1");
paramData.put("MallIp" ,"10.0.0.1");
paramData.put("Mid" ,Mid);
paramData.put("BillTokenKey" ,BillTokenKey);
paramData.put("Moid" ,Moid);
paramData.put("EdiDate" ,EdiDate);
paramData.put("BuyerName" ,"구매자명");
paramData.put("BuyerTel" ,"01022223333");
paramData.put("BuyerEmail" ,"noname@smartro.co.kr");
paramData.put("CardQuota" ,"00");
paramData.put("CardPoint" ,"0");
paramData.put("GoodsCnt" ,"1");
paramData.put("GoodsName" ,"Peach Macarong");
paramData.put("Amt" ,Amt);
paramData.put("SvcAmt" ,"");
paramData.put("VatAmt" ,"");
paramData.put("TaxAmt" ,"");
paramData.put("TaxFreeAmt" ,"");
paramData.put("MallUserId" ,"");
paramData.put("VerifyValue", VerifyValue);
// 날짜 생성 함수
public static String getyyyyMMddHHmmss() {
/** yyyyMMddHHmmss Date Format */
SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss");
return yyyyMMddHHmmss.format(new Date());
}
// 암호화 함수
public static final synchronized String encodeSHA256Base64(String strPW) {
String passACL = null;
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (Exception e) {
e.printStackTrace();
}
md.update(strPW.getBytes());
byte[] raw = md.digest();
byte[] encodedBytes = Base64.encodeBase64(raw);
passACL = new String(encodedBytes);
return passACL;
}
$url = "https://tapproval.smartropay.co.kr/payment/approval/approvalUnionBill.do"; // 테스트
//$url = "https://approval.smartropay.co.kr/payment/approval/approvalUnionBill.do"; // 운영
$MerchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
$Mid = ""; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
$EdiDate = date("YmdHis");
$Moid = "smartro_".$EdiDate;
$BillTokenKey = ""; // 발급받은 빌링키
$Amt = "1004";
// 검증값 SHA256 암호화(빌링키 + Mid + 금액)
$VerifyValue = base64_encode(hash('sha256', BillTokenKey.$Mid.$Amt, true));
// 요청 파라미터 (각 값들은 가맹점 환경에 맞추어 설정해 주세요.)
$paramData = array(
'UserIp' => '10.0.0.1',
'MallIp' => '10.0.0.1',
'Mid' => $Mid,
'BillTokenKey' => $BillTokenKey,
'Moid' => $Moid,
'EdiDate' => $EdiDate,
'BuyerName' => '구매자명',
'BuyerTel' => '01022223333',
'BuyerEmail' => 'noname@smartro.co.kr',
'CardQuota' => '00',
'CardPoint' => '0',
'GoodsCnt' => '1',
'GoodsName' => 'Peach Macarong',
'Amt' => $Amt,
'SvcAmt' => '',
'VatAmt' => '',
'TaxAmt' => '',
'TaxFreeAmt' => '',
'MallUserId' => '',
'VerifyValue' => $VerifyValue
);
url = "https://tapproval.smartropay.co.kr/payment/approval/approvalUnionBill.do" ' 테스트
'url = "https://approval.smartropay.co.kr/payment/approval/approvalUnionBill.do" ' 운영
merchantKey = "" ' 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
Mid = "" ' 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
EdiDate = Year(Now()) & right("0"&Month(Now()),2) & right("0"&Day(Now()),2) & right("0"&Hour(Now()),2) & right("0"&Minute(Now()),2) & right("0"&Second(Now()),2)
Moid = "smartro_" & EdiDate
BillTokenKey = "" ' 발급받은 빌링키
Amt = "1004"
' 검증값 SHA256 암호화(빌링키 + Mid + 금액)
VerifyValue = CryptoUtil.Sha256Encrypt(BillTokenKey & Mid & Amt)
' 요청 파라미터 (각 값들은 가맹점 환경에 맞추어 설정해 주세요.)
Set paramData = jsObject()
paramData("UserIp") = "10.0.0.1"
paramData("MallIp") = "10.0.0.1"
paramData("Mid") = Mid
paramData("BillTokenKey") = BillTokenKey
paramData("Moid") = Moid
paramData("EdiDate") = EdiDate
paramData("BuyerName") = "구매자명"
paramData("BuyerTel") = "01022223333"
paramData("BuyerEmail") = "noname@smartro.co.kr"
paramData("CardQuota") = "00"
paramData("CardPoint") = "0"
paramData("GoodsCnt") = "1"
paramData("GoodsName") = "Peach Macarong"
paramData("Amt") = Amt
paramData("SvcAmt") = ""
paramData("VatAmt") = ""
paramData("TaxAmt") = ""
paramData("TaxFreeAmt") = ""
paramData("MallUserId") = ""
paramData("VerifyValue") = VerifyValue
// json 데이터 AES256 암호화
try {
body.put("EncData", AES256Cipher.AES_Encode(paramData.toString(), merchantKey.substring(0,32)));
body.put("Mid", Mid);
} catch(Exception e){
e.printStackTrace();
}
HashMap<String, Object> result = callApi(body, url);
public HashMap<String, Object> callApi(JSONObject json, String callUrl) {
StringBuilder responseBody = null;
HashMap<String, Object> result = new HashMap<>();
// http urlCall 승인 요청 및 TrAuthKey 유효성 검증
int connectTimeout = 1000;
int readTimeout = 10000; // 가맹점에 맞게 TimeOut 조절
URL url = null;
HttpsURLConnection connection = null;
try {
SSLContext sslCtx = SSLContext.getInstance("TLSv1.2");
sslCtx.init(null, null, new SecureRandom());
url = new URL(callUrl);
System.out.println(" url " + url.toString());
connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory(sslCtx.getSocketFactory());
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
OutputStreamWriter osw = new OutputStreamWriter(new BufferedOutputStream(connection.getOutputStream()) , "utf-8" );
char[] bytes = json.toString().toCharArray();
osw.write(bytes,0,bytes.length);
osw.flush();
osw.close();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String line = null;
responseBody = new StringBuilder();
while ((line = br.readLine()) != null) {
System.out.println(" response " + line);
responseBody.append(line);
}
br.close();
// 결제결과
result = new ObjectMapper().readValue(responseBody.toString(), HashMap.class);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
// json 데이터 AES256 암호화
$EncData = base64_encode(openssl_encrypt(json_encode($paramData), 'aes-256-cbc', substr($MerchantKey,0,32), true, str_repeat(chr(0), 16)))
$body = array(
'EncData' => $EncData,
'Mid' => $Mid
);
// json data
$body = json_encode($body);
$ret = Curl($url, $body, $http_status);
function Curl($url, $post_data, $http_status, $header = null) {
$ch=curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
// post_data
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'Content-Type: application/json'));
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$body = null;
// error
if (!$response) {
$body = curl_error($ch);
// HostNotFound, No route to Host, etc Network related error
$http_status = -1;
Log::error("CURL Error: = " . $body);
} else {
//parsing http status code
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (!is_null($header)) {
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
} else {
$body = $response;
}
}
curl_close($ch);
return $body;
}
' json 데이터 AES256 암호화
EncData = CryptoUtil.Aes256Encrypt(paramData, left(merchantKey, 32))
Set body = jsObject()
body("EncData") = EncData
body("Mid") = Mid
result = sendPost(url, body)
Function sendPost(url, objJson)
Set httpRequest = Server.CreateObject("MSXML2.ServerXMLHTTP")
httpRequest.open "POST", url, False
httpRequest.setRequestHeader "Content-type","application/json"
httpRequest.setRequestHeader "Accept","application/json"
httpRequest.send objJson.jsString
postResponse = httpRequest.ResponseText
sendPost = postResponse
End Function
// DLL 암호화 소스
using System;
using System.EnterpriseServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace SmartropayLib
{
public class CryptoUtil : ServicedComponent
{
///
/// AES256 암호화
///
/// 문자열
/// 키
/// 암호화 문자열
public string Aes256Encrypt(string text, string key)
{
RijndaelManaged aes = new RijndaelManaged();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] xBuff = null;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
{
byte[] xXml = Encoding.UTF8.GetBytes(text);
cs.Write(xXml, 0, xXml.Length);
}
xBuff = ms.ToArray();
}
return Convert.ToBase64String(xBuff);
}
///
/// AES256 복호화
///
/// 암호화 문자열
/// 키
/// 복호화 문자열
public string Aes256Decrypt(string text, string key)
{
RijndaelManaged aes = new RijndaelManaged();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
var decrypt = aes.CreateDecryptor();
byte[] xBuff = null;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
{
byte[] xXml = Convert.FromBase64String(text);
cs.Write(xXml, 0, xXml.Length);
}
xBuff = ms.ToArray();
}
return Encoding.UTF8.GetString(xBuff);
}
}
}
결제결과 시 전달받는
VerifyValue
파라미터는 아래와 같은 규칙으로 생성됩니다.
String Mid = request.getParameter("Mid");
String AuthDate = request.getParameter("AuthDate");
String AuthCode = request.getParameter("AuthCode");
String Amt = request.getParameter("Amt");
String BillTokenKey = request.getParameter("BillTokenKey");
String VerifyValue = encodeSHA256Base64(Mid + AuthDate + AuthCode + Amt + BillTokenKey);
$Mid = $_REQUEST['Mid']==null?"":$_REQUEST['Mid'];
$AuthDate = $_REQUEST['AuthDate']==null?"":$_REQUEST['AuthDate'];
$AuthCode = $_REQUEST['AuthCode']==null?"":$_REQUEST['AuthCode'];
$Amt = $_REQUEST['Amt']==null?"":$_REQUEST['Amt'];
$BillTokenKey = $_REQUEST['BillTokenKey']==null?"":$_REQUEST['BillTokenKey'];
$VerifyValue = base64_encode(hash('sha256', $Mid.$AuthDate.$AuthCode.$Amt.$BillTokenKey,true));
Mid = request("Mid")
AuthDate = request("AuthDate")
AuthCode = request("AuthCode")
Amt = request("Amt")
BillTokenKey = request("BillTokenKey")
VerifyValue = CryptoUtil.Sha256Encrypt(Mid & AuthDate & AuthCode & Amt & BillTokenKey)
빌링키 삭제 파라미터
파라미터 | 항목명 | 길이(char) | 필수여부 | 설명 |
---|---|---|---|---|
Mid | 상점ID | 10 | 필수 | |
SspMallId | 결제MALL ID | 10 | 필수 | |
BillTokenKey | 고정식 토큰(빌링키) | 50 | 필수 | |
ServiceType | 삭제 유형 | 2 | 선택 | CD(기본값): 카드 삭제 |
MallUserId | 회원사 고객 ID | 20 | 필수 | |
MallReserved | 상점예비정보 | 100 | 선택 | Base64 인코딩필요
(평문으로 전달시 한글 및 특수문자가 정상적으로 전달되지 않음) |
VerifyValue | 위·변조 검증값 | 50 | 필수 | SHA256 해시 암호화 |
파라미터 | 항목명 | 길이(char) | 설명 |
---|---|---|---|
ResultCode | 결과코드 | 10 | 성공: PD00, 실패: 그 외 |
ResultMsg | 결과메시지 | 10 | |
Tid | 삭제거래 ID | 30 | |
MallReserved | 상점예비정보 | 100 |
String url = "https://tapproval.smartropay.co.kr/payment/approval/ssbdel.do"; // 테스트
// String url = "https://approval.smartropay.co.kr/payment/approval/ssbdel.do"; // 운영
JSONObject body = new JSONObject();
JSONObject paramData = new JSONObject();
String merchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
String Mid = ""; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
String SspMallId = "SMTSSPAY0p"; // 테스트용 SspMallId
String BillTokenKey = ""; // 빌링키 발급을 통해 생성된 키
// 검증값 SHA256 암호화(Mid + SspMallId + 빌링키)
String VerifyValue = encodeSHA256Base64(Mid +SspMallId+ BillTokenKey);
// 요청 파라미터 (각 값들은 가맹점 환경에 맞추어 설정해 주세요.)
paramData.put("ServiceType" ,"CD");
paramData.put("Mid" ,Mid);
paramData.put("BillTokenKey" ,BillTokenKey);
paramData.put("SspMallId" ,SspMallId);
paramData.put("MallUserId" ,"");
paramData.put("VerifyValue" ,VerifyValue);
$url = "https://tapproval.smartropay.co.kr/payment/approval/ssbdel.do"; // 테스트
//$url = "https://approval.smartropay.co.kr/payment/approval/ssbdel.do"; // 운영
$MerchantKey = ""; // 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
$Mid = ""; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
$SspMallId = "SMTSSPAY0p"; // 테스트용 SspMallId
$BillTokenKey = ""; // 발급받은 빌링키
// SHA256 암호화(Mid + SspMallId + 빌링키)
$VerifyValue = base64_encode(hash('sha256', $Mid.$SspMallId.$BillTokenKey, true));
$paramData = array(
'ServiceType' => 'CD',
'Mid' => $Mid,
'BillTokenKey' => $BillTokenKey,
'SspMallId' => $SspMallId,
'MallUserId' => '',
'VerifyValue' => $VerifyValue
);
url = "https://tapproval.smartropay.co.kr/payment/approval/ssbdel.do" ' 테스트
'url = "https://approval.smartropay.co.kr/payment/approval/ssbdel.do" ' 운영
merchantKey = "" ' 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
Mid = "" ' 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
SspMallId = "SMTSSPAY0p" ' 테스트용 SspMallId
BillTokenKey = "" ' 발급받은 빌링키
' SHA256 암호화(Mid + SspMallId + 빌링키)
VerifyValue = CryptoUtil.Sha256Encrypt(Mid & SspMallId & BillTokenKey)
Set paramData = jsObject()
paramData("ServiceType") = ""
paramData("Mid") = Mid
paramData("BillTokenKey") = BillTokenKey
paramData("SspMallId") = SspMallId
paramData("MallUserId") = ""
paramData("VerifyValue") = VerifyValue
// json 데이터 AES256 암호화
try {
body.put("EncData", AES256Cipher.AES_Encode(paramData.toString(), merchantKey.substring(0,32)));
body.put("Mid", Mid);
} catch(Exception e){
e.printStackTrace();
}
HashMap<String, Object> result = callApi(body, url);
public HashMap<String, Object> callApi(JSONObject json, String callUrl) {
StringBuilder responseBody = null;
HashMap<String, Object> result = new HashMap<>();
// http urlCall 승인 요청 및 TrAuthKey 유효성 검증
int connectTimeout = 1000;
int readTimeout = 10000; // 가맹점에 맞게 TimeOut 조절
URL url = null;
HttpsURLConnection connection = null;
try {
SSLContext sslCtx = SSLContext.getInstance("TLSv1.2");
sslCtx.init(null, null, new SecureRandom());
url = new URL(callUrl);
System.out.println(" url " + url.toString());
connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory(sslCtx.getSocketFactory());
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
OutputStreamWriter osw = new OutputStreamWriter(new BufferedOutputStream(connection.getOutputStream()) , "utf-8" );
char[] bytes = json.toString().toCharArray();
osw.write(bytes,0,bytes.length);
osw.flush();
osw.close();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String line = null;
responseBody = new StringBuilder();
while ((line = br.readLine()) != null) {
System.out.println(" response " + line);
responseBody.append(line);
}
br.close();
// 결제결과
result = new ObjectMapper().readValue(responseBody.toString(), HashMap.class);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
// json 데이터 AES256 암호화
$EncData = base64_encode(openssl_encrypt(json_encode($paramData), 'aes-256-cbc', substr($MerchantKey,0,32), true, str_repeat(chr(0), 16)))
$body = array(
'EncData' => $EncData,
'Mid' => $Mid
);
// json data
$body = json_encode($body);
$ret = Curl($url, $body, $http_status);
function Curl($url, $post_data, $http_status, $header = null) {
$ch=curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
// post_data
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'Content-Type: application/json'));
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$body = null;
// error
if (!$response) {
$body = curl_error($ch);
// HostNotFound, No route to Host, etc Network related error
$http_status = -1;
Log::error("CURL Error: = " . $body);
} else {
//parsing http status code
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (!is_null($header)) {
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
} else {
$body = $response;
}
}
curl_close($ch);
return $body;
}
' json 데이터 AES256 암호화
EncData = CryptoUtil.Aes256Encrypt(paramData, left(merchantKey, 32))
Set body = jsObject()
body("EncData") = EncData
body("Mid") = Mid
result = sendPost(url, body)
Function sendPost(url, objJson)
Set httpRequest = Server.CreateObject("MSXML2.ServerXMLHTTP")
httpRequest.open "POST", url, False
httpRequest.setRequestHeader "Content-type","application/json"
httpRequest.setRequestHeader "Accept","application/json"
httpRequest.send objJson.jsString
postResponse = httpRequest.ResponseText
sendPost = postResponse
End Function
// DLL 암호화 소스
using System;
using System.EnterpriseServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace SmartropayLib
{
public class CryptoUtil : ServicedComponent
{
///
/// AES256 암호화
///
/// 문자열
/// 키
/// 암호화 문자열
public string Aes256Encrypt(string text, string key)
{
RijndaelManaged aes = new RijndaelManaged();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] xBuff = null;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
{
byte[] xXml = Encoding.UTF8.GetBytes(text);
cs.Write(xXml, 0, xXml.Length);
}
xBuff = ms.ToArray();
}
return Convert.ToBase64String(xBuff);
}
///
/// AES256 복호화
///
/// 암호화 문자열
/// 키
/// 복호화 문자열
public string Aes256Decrypt(string text, string key)
{
RijndaelManaged aes = new RijndaelManaged();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
var decrypt = aes.CreateDecryptor();
byte[] xBuff = null;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
{
byte[] xXml = Convert.FromBase64String(text);
cs.Write(xXml, 0, xXml.Length);
}
xBuff = ms.ToArray();
}
return Encoding.UTF8.GetString(xBuff);
}
}
}
SS-Pay 회원 삭제 파라미터
파라미터 | 항목명 | 길이(char) | 필수여부 | 설명 |
---|---|---|---|---|
MID | 상점 아이디 | 10 | 필수 | |
SspMallId | 결제MALL ID | 10 | 필수 | 발급받은 SspMallId |
MallUserId | 회원사 고객 ID | 20 | 필수 | 고객 구분값 |
ServiceType | 서비스 타입 | 2 | 필수 | 회원 삭제 : MD |
VerifyValue | 위·변조 검증값 | 가변 | 필수 | SHA256 해시 암호화 |
MallReserved | 상점예비정보 | 100 | 선택 | Base64 인코딩필요
(평문으로 전달시 한글 및 특수문자가 정상적으로 전달되지 않음) |
파라미터 | 항목명 | 길이(char) | 설명 |
---|---|---|---|
SspMallId | 결제MALL ID | 10 | |
ResultCode | 결과코드 | 10 | 성공: PD00, PS40 실패: 그 외 PD00 : 회원정보와 등록된 카드 정보 삭제 PS40 : 등록된 카드 정보가 없는 경우 회원 정보 삭제 |
ResultMsg | 결과메시지 | 80 | |
MallReserved | 상점 예비필드 | 500 | 요청한 값 응답 |
String url = "https://tapproval.smartropay.co.kr/payment/approval/ssbMemberDel.do"; // 테스트
// String url = "https://approval.smartropay.co.kr/payment/approval/ssbMemberDel.do"; // 운영
JSONObject body = new JSONObject();
JSONObject paramData = new JSONObject();
String merchantKey =" "; /// 발급받은 MerchantKey
String Mid = "SMTPAY006m"; // 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
String SspMallId = " "; // 발급받은 SspMallId
String MallUserID =" "; // 발급받은 MallUserID
String VerifyValue = encodeSHA256Base64(Mid +SspMallId+MallUserID);
// 요청 파라미터 (각 값들은 가맹점 환경에 맞추어 설정해 주세요.)
paramData.put("Mid" ,Mid);
paramData.put("SspMallId" ,SspMallId);
paramData.put("MallUserId" ,MallUserID);
paramData.put("VerifyValue" ,VerifyValue);
paramData.put("ServiceType" ,"MD");
// json 데이터 AES256 암호화
try {
body.put("EncData", AES256Cipher.AES_Encode(paramData.toString(), merchantKey.substring(0,32)));
body.put("Mid", Mid);
} catch(Exception e){
e.printStackTrace();
}
HashMap result = callApi(body, url);
public static HashMap callApi(JSONObject json, String callUrl) {
StringBuilder responseBody = null;
HashMap result = new HashMap<>();
// http urlCall 승인 요청 및 TrAuthKey 유효성 검증
int connectTimeout = 1000;
int readTimeout = 10000; // 가맹점에 맞게 TimeOut 조절
URL url = null;
HttpsURLConnection connection = null;
try {
SSLContext sslCtx = SSLContext.getInstance("TLSv1.2");
sslCtx.init(null, null, new SecureRandom());
url = new URL(callUrl);
System.out.println(" url " + url.toString());
connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory(sslCtx.getSocketFactory());
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
OutputStreamWriter osw = new OutputStreamWriter(new BufferedOutputStream(connection.getOutputStream()) , "utf-8" );
char[] bytes = json.toString().toCharArray();
osw.write(bytes,0,bytes.length);
osw.flush();
osw.close();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String line = null;
responseBody = new StringBuilder();
while ((line = br.readLine()) != null) {
System.out.println(" response " + line);
responseBody.append(line);
}
br.close();
// 결제결과
result = new ObjectMapper().readValue(responseBody.toString(), HashMap.class);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
Illegal key size
예외가 발생했을 경우
여기를 클릭해 주세요.
import org.apache.commons.codec.binary.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
public class AES256Cipher {
public static byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static String AES_Encode(String str, String key) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] textBytes = str.getBytes("UTF-8");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = null;
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return Base64.encodeBase64String(cipher.doFinal(textBytes));
}
public static String AES_Decode(String str, String key, byte[] ivBytes) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] textBytes = Base64.decodeBase64(str.getBytes());
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(textBytes), "UTF-8");
}
public static String AES_Decode(String str, String key) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return AES_Decode(str, key, ivBytes);
}
}
과세 및 비과세 필드를 직접 계산하는 가맹점일 경우 아래와 같은 방식으로 계산하세요.
// 총금액(Amt) = 과세금액(TaxAmt) + 부가세(VatAmt) + 비과세(TaxFreeAmt)
// 비과세(TaxFreeAmt) 금액이 0원일 경우
tmp = Amt / 1.1;
VatAmt = (long)Math.floor(Amt - tmp); // 부가세 절사
TaxAmt = Amt - VatAmt
/** 예시 : 총금액(Amt:5000) = 과세금액(TaxAmt:4546) + 부가세(VatAmt:454) **/
// 비과세(TaxFreeAmt) 금액이 0원 이상일 경우
tmp = (Amt- TaxFreeAmt) / 1.1;
VatAmt = (long)Math.floor(Amt - tmp - TaxFreeAmt); // 부가세 절사
TaxAmt = Amt - VatAmt - TaxFreeAmt
/** 예시 : 총금액(Amt:5000) = 과세금액(TaxAmt:4364) + 부가세(VatAmt:436)+ 비과세(TaxFreeAmt:200) **/
(참고) AES256 암호화 중 예외 발생 시 조치사항
'java.security.InvalidKeyException: Illegal key size' 라는 예외가 발생했을 경우 아래와 같이 해결해 주세요.
1. 현재 사용중인 Java 버전에 맞춰 Unlimited Strength 정책 파일을 다운받습니다.
2. 다운받은 파일의 local_policy.jar, US_export_policy.jar 파일을 <JAVA_HOME>/jre/lib/security/ 폴더로 옮겨 기존 정책을 덮어씌웁니다.
그러면 JCE로 사용 가능한 모든 암호화의 키 길이 제한이 해제됩니다.
※ 8u151 Release Notes에서는 해당 버전부터 별도의 다운로드 없이 Unlimited Strength 정책을 설정할 수 있게 추가 번들이 같이 제공됩니다.
해당 버전에서 정책 파일은 <JAVA_HOME>/jre/lib/security/policy 경로에 limited와 unlimited 폴더로 구성되며,
unlimited 설정은 <JAVA_HOME>/jre/lib/security/java.security 파일을 열어
crypto.policy=unlimited
부분의 주석 처리를 지워주면 제한 해제된 정책을 사용할 수 있습니다.
※ 변경된 기본 정책
2018년 1월 업데이트된 Java8u161 Release Notes에 따르면, Java8u161 버전부터는 JCE 기본 정책이 unlimited이며,
길이를 제한하고 싶다면 crypto.policy=unlimited
부분을 주석처리하면 됩니다.
Unlimited를 기본으로 사용하는 Java 버전은 'JDK-8170157 : Enable unlimited cryptographic policy by default in Oracle JDK builds'에서 확인할 수 있습니다.