취소 연동

스마트로페이 결제 취소 연동을 위한 가이드입니다.
- 해당 메뉴얼의 Parameter 들은 가맹점에 통보 없이 응답값이 추가 될 수 있습니다. 응답값 추가도 고려하여 개발 진행하시길 권장합니다.
- 가상계좌 환불의 경우 환불 연동을 별도로 진행해야합니다.
- 입금되지 않은 가상계좌 거래 취소 요청 시 채번 내역이 취소됩니다.
- 온라인 PG 거래 취소 연동 가이드입니다.
- 오프라인 PG 거래 취소 가이드는 이곳에서 확인해주세요.

취소

UrlCall 취소 연동 결제 취소 연동에 대한 안내입니다.

스마트로페이 SERVER IP
스마트로페이 서비스 IP 정보입니다.
구분 IP 비고
테스트 211.193.35.11 STG
운영 - 결제 211.193.35.20
운영 - 재통보 211.193.35.216
211.193.35.217
재통보 서비스 사용시

취소

취소 상세 파라미터

파라미터 항목명 길이(char) 설명
Mid Mid 10 상점 ID
Tid 거래번호 30 취소할 거래의 Tid
CancelPwd 취소 패스워드 20 관리자 페이지에서 설정한 취소 패스워드
CancelAmt 취소금액 12 숫자만 가능, 문장부호 제외
CancelMsg 취소사유 100
CancelSeq 취소차수 3 기본값: 1(부분취소 시마다 차수가 1씩 늘어남. 첫번째 부분취소=1, 두번째 부분취소=2, ...)
CancelTaxAmt 취소 과세 12 부가세 직접 가맹점일 경우 취소 시에도 과세 계산 필요(숫자만 가능, 문장부호 제외)
CancelTaxFreeAmt 취소 비과세 12 부가세 직접 가맹점일 경우 취소 시에도 비과세 계산 필요(숫자만 가능, 문장부호 제외)
CancelVatAmt 취소 부가세 12 부가세 직접 가맹점일 경우 취소 시에도 부가세 계산 필요(숫자만 가능, 문장부호 제외)
PartialCancelCode 부분취소 여부 1 0: 전체, 1: 부분
HashData 검증데이터 가변 SHA256 암호화(Tid + MerchantKey + CancelAmt + PartialCancelCode)
DivideInfo 분할정산 정보 가변 분할정산 가맹점 전용(설정 방법-일반연동 참고)
복합결제 전문 취소
CpxTid 복합결제 취소Tid 30 복합결제 취소Tid (복합결제 취소시 필수)
GreenDeposit 컵보증금 12 컵보증금 거래 취소시 필수
파라미터 항목명 길이(char)
PayMethod 지불수단 10
Tid 거래번호 30
Mid 상점아이디 10
ResultCode 지불수단 별 결과코드 10
ResultMsg 취소메세지 100
CancelDate 취소일자 8
CancelTime 취소시간 6
CancelNum 지불수단 별 취소번호(가상계좌 채번취소시 취소된 가상계좌번호가 응답됩니다.)
CancelSeq 취소차수 3
Moid 주문번호 100
PTid 부분취소 Tid (전체취소 : 원거래 Tid, 부분취소 : 부분취소Tid) 30
CancelAmt 취소금액 12
CancelTaxAmt 취소 과세 12
CancelTaxFreeAmt 취소 비과세 12
CancelVatAmt 취소 부가세 12
RemainAmt 부분 취소 후 남은 총 금액 12
RemainVatAmt 부분 취소 후 남은 부가세 금액 12
RemainTaxAmt 부분 취소 후 남은 과세금액 12
RemainTaxFreeAmt 부분 취소 후 남은 비과세금액 12
복합결제 취소
CpxTid 복합결제 Tid 30
CpxAuthCode 복합결제 승인번호 30
CpxAmt 복합결제 취소금액 12
PayAmt 주결제수단금액 12
InstDiscTid 즉시할인 취소Tid 30
InstDiscAuthCode 즉시할인 승인번호 30
InstDiscAmt 즉시할인 금액 12

샘플 소스
STEP 1 취소 요청 데이터 설정하기.
·JSP
        
String url = "https://tapproval.smartropay.co.kr/payment/approval/cancel.do";  		// 테스트
// String url = "https://approval.smartropay.co.kr/payment/approval/cancel.do"; 	// 운영

JSONObject body = new JSONObject();
JSONObject paramData = new JSONObject()

String Tid = "";			// 취소 요청할 Tid 입력
String Mid = "";			// 발급받은 테스트 Mid 설정(Real 전환 시 운영 Mid 설정)
String CancelAmt = "1004";	// 취소할 거래금액
String CancelSeq = "1";		// 취소차수(기본값: 1, 부분취소 시마다 차수가 1씩 늘어남. 첫번째 부분취소=1, 두번째 부분취소=2, ...)
String PartialCancelCode = "0";		// 0: 전체취소, 1: 부분취소
String MerchantKey = "";  			// 발급받은 테스트 상점키 설정(Real 전환 시 운영 상점키 설정)
// 검증값 SHA256 암호화(Tid + MerchantKey + CancelAmt + PartialCancelCode)
String HashData = encodeSHA256Base64(Tid + MerchantKey + CancelAmt + PartialCancelCode);

// 취소 요청 파라미터 셋팅
paramData.put("SERVICE_MODE", "CL1");
paramData.put("Tid", Tid);   
paramData.put("Mid", Mid);   
paramData.put("CancelAmt", CancelAmt);   
paramData.put("CancelPwd", "");  
paramData.put("CancelMsg", "");
paramData.put("CancelSeq", CancelSeq);    
paramData.put("PartialCancelCode", PartialCancelCode);

// 과세, 비과세, 부가세 셋팅(부가세 직접 계산 가맹점의 경우 각 값을 계산하여 설정해야 합니다.)
paramData.put("CancelTaxAmt", "");
paramData.put("CancelTaxFreeAmt", "");
paramData.put("CancelVatAmt", "");

// 분할정산 사용 가맹점의 경우, DivideInfo 파라미터를 가맹점에 맞게 설정해 주세요. (일반연동 참고)
paramData.put("DivideInfo", "");

// HASH 설정 (필수)
paramData.put("HashData", HashData);  
		
	

STEP 2 취소 요청 및 결과 받기
※ 취소시 전달하는 데이터인 HashData, EncData 확인은 여기를 이용해 주세요. (가맹점 로직으로 생성된 데이터와 일치해야 합니다.)

·JSP
        
// 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;
}
		
	

(추가) AES256 암호화 함수(AES256Cipher)

·JSP
※ 암호화 실행 중 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) **/

		
	
- 위의 계산방식은 일반 연동을 사용하며 부가세를 직접 계산하는 가맹점일 경우에 한합니다.
- 비과세 가맹점인 경우 비과세금액(TaxFreeAmt)이 총금액(Amt)과 일치해야 하며 과세(TaxAmt)와 부가세(VatAmt)는 0 으로 세팅
- 간편 연동을 사용하거나 직접 계산을 하지 않는 경우, 비과세 가맹점에는 해당되지 않습니다.
※ 부가세 계산방식을 자동 계산 방식으로 변경 희망하시는 경우, 영업 담당자에게 문의 부탁드립니다.

(참고) 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'에서 확인할 수 있습니다.