본문 바로가기

SCRIPT/NODE JS

[NODEJS] 결제 API 연동하기 - 아임포트

웹 개발시, 결제가 필요할때, PG사 연동등, 결제 처리에 대한 프로세스가 필요한데, 

아임포트는 이를 지원해주고, 편리하게 사용할 수 있도록 하는 API 제공 서비스이다.

 

먼저, 아임포트 공식 사이트이다. ( https://www.iamport.kr/ )

장점으로는, 다양한 개발 언어를 제공하며, 다양한 PG 사를 지원해준다는 점. 

그리고, 글 작성일자 기준으로, 1개의 PG사 연동은 평생 무료 제공한다는 점이 메리트 있다.

아임포트 홍보글이 아니므로, 여기까지만 적어보도록 하고, 바로 연동을 해보도록 하자.

 

 

먼저, 기본 사용방법 , 환경설정의 경우 아임포트 홈페이지에 잘 알려주고 있으며, 기술지원 속도도 빠른편으로 보인다. 

필요시에는 전문 아임포트 개발자들에게 문의하는게 좋은 방법.

 

 

1. REST API 사용하기 ( 인증 토큰 요청 및 결제 정보 조회 ) 

[ 관리자 모드 - 시스템 설정 화면 ]

아임포트 식별 코드, REST API 사용에 필요한 KEY 

 

위의 정보는, REST API 사용에 필요한 정보들이며, REST API 메뉴얼은 (https://api.iamport.kr/#/) 에서 제공해주고 있다.

예제는, REST API 이용하기 위한 필수사항인, 인증 토근 요청. 결제내역 조회에 대한 예제입니다.

config 파일에 아임포트 시스템 설정에서 복사해온 아임포트 식별코드 , REST API 키, REST API Secret 키를 json 형식으로 작성해 둔 상태이며, 이값을 불러와 아래와 같은 요청을 통해 처리를 합니다.

1-1. 인증 토큰 요청 ( POST ) 

import.js

var unirest = require('unirest');
// 인증 토큰 요청
router.post('/admin/iamport-getToken', function(req,res){
	var imp_key = config.import.imp_key;
	var imp_secret = config.import.imp_secret;
	unirest.post('https://api.iamport.kr/users/getToken')
	.headers({'Accept': 'application/json', 'Content-Type': 'application/json'})
	.send({ "imp_key": imp_key, "imp_secret": imp_secret })
	.end(function (response) {
		res.json({'message' : 'success', "tokenObj" : response.body});
	});
});

키값을 이용해 토큰 값을 Callback 받습니다. 해당 토큰 값은 REST API 요청시에 필수적인 사항이므로, 모듈화 시켜 필요할 때 쓸 수 있도록 개발하는 것을 권장 드립니다.

1-2. 결제 내역 조회 ( GET )

// merchant_uid & token 을 이용한 결제내역 조회
router.post('/admin/iamport-getPayment', function(req,res){
	var merchant_uid = req.body.merchant_uid;
	var tokenId = req.body.tokenId;
			
	unirest.get('https://api.iamport.kr/payments/find/'+merchant_uid+'/?sorting=-started')
	.headers({'Accept': 'application/json', 'Content-Type': 'application/json' , "Authorization" : tokenId})
	.end(function (response) {
		res.json({'message' : 'success', "result" : response.body});
	});
});

Callback 받은 토큰값을 Header Authorization 에 입력하고, 주문ID(merchant_uid , 시스템에서 생성한 ID )를 GET 파람에 실어서 보내, 필요한 정보를 얻을 수 있습니다.

https://api.iamport.kr/payments/find/'+merchant_uid+'/?sorting=-started&_token="asdfasdfasdf" 와 같이 token 값을 Param 값으로 전달하여도 되지만, 해당 부분이 보안 취약점이 될 것으로 판단되어, 다음 버전에서 삭제될 예정이라고 하니, 위의 예제의 방식으로 구현하는 것이 정신건강에 좋을 것 같습니다.

기타 다른 API 사용에도 위와 크게 다른 점이 없으므로, 참고하여 개발하면 됩니다.

 

 

 

2. 결제 요청 및 CALLBACK 처리

결제 요청에 대해서는  https://www.iamport.kr/getstarted 에 자세하게 잘 설명되어 있습니다.

아래는 실제로 결제가 이뤄지는 화면에서의 결제 요청 처리 예시입니다.

먼저, ajax 로 formDATA (주문정보 등..)에 대한 정보를 이용해, 주문정보를 데이터베이스에 INSERT 합니다. 이후, INSERT 성공시, 결제 요청을 진행하도록 합니다. 

IMP.request_pay ~ 부분 부터 결제 창에 필요한 데이터를 파라메터로 넘겨 함수를 호출합니다.

//ejs

var im_port = <%- JSON.stringify(im_port) %> // 라우터에서 im_port 정보를 넘겨받습니다.
IMP.init(im_port); // 가맹점 식별코드를 이용해서 window.IMP 변수를 초기화합니다

var merchant_uid = 'merchant_' + new Date().getTime();
var formData = $('#form').serialize();
jQuery.ajax({
		  url: 포스트 URL,
		  type: 'POST',
	      data: formData,
	      contentType: 'application/x-www-form-urlencoded; charset=UTF-8', 
	      dataType: 'html',
	      success: function (result) {
			var rData = JSON.parse(result);
	     	if(rData.message == 'success'){
		      	IMP.request_pay({
                  pg : 'uplus', // version 1.1.0부터 지원.
                  pay_method : 결제 방식, //'samsung':삼성페이, 'card':신용카드, 'trans':실시간계좌이체, 'vbank':가상계좌, 'phone':휴대폰소액결제
                  merchant_uid : 주문 아이디,
                  name : 상품 명,
                  amount : 가격,
                  buyer_email : 구매자 이메일,
                  buyer_name : 구매자 이름,
                  buyer_tel : 구매자 연락처,
                  buyer_addr : 구매자 주소,
                  buyer_postcode : 구매자 우편번호,
                  ,m_redirect_url : 리다이렉트 url // 실제 도메인 생성되면 수정.
                  }, function(rsp) {
                    if ( rsp.success ) {
                    var msg = '결제가 완료되었습니다.';
                          msg += '고유ID : ' + rsp.imp_uid;
                          msg += '상점 거래ID : ' + rsp.merchant_uid;
                          msg += '결제 금액 : ' + comma(rsp.paid_amount);
                          msg += '카드 승인번호 : ' + rsp.apply_num;
                    } else {
                        var msg = '결제에 실패하였습니다.';
                        msg += '에러내용 : ' + rsp.error_msg;
                    }
                   alert(msg);
	 			 });
	    	}else{
	        alert('결제 진행에 실패하였습니다. 관리자에게 문의하세요.');
	     }
	  },
	  error : function(xhr, status) {
	  	alert('결제 진행에 실패하였습니다. 관리자에게 문의하세요.');
	  }
});

결제 완료후, 정상 결제 완료시, rsp callback 을 이용해, 다음 행동에 대한 부분을 작성하면 됩니다. ( 결제 완료 처리 )

중요한 부분은, 모바일로 결제를 진행할 경우, m_redirect_url 에 설정한 redirect url 로 이동한다는 점,  만약 이를 작성하지 않은 경우, 결제 정보에 대한 창으로 연결됩니다. 모바일의 경우 callback 처리를 하지 않는다는 점을 유의해주십시요.

 

 

3. webhook  

결제 직후 Notification URL 작성

먼저, 아임포트 시스템 설정에서 Notifiaction URL 를 입력해줍니다. 해당 URL 을 통해, 아임포트에서 post 요청을 할 것입니다. 요청 TYPE 은 'application/json' , 'x-www-form-urlencoded' 두개타입입니다.                                             

json : {"imp_uid":"imp_1234567890","merchant_uid":"merchant_1234567890","status":"ready"} 

x-www : imp_uid=imp_1234567890&merchant_uid=merchant_1234567890&status=ready

웹훅은 결제에 대한 재검토를 위한 과정과 가상계좌와 같은 주문자의 입금결과를 얻어 시스템 자동화를 위한 목적으로 사용됩니다.

해당 정보를 이용해서, 아래와 같이 webhook 처리를 해줍니다.

router.post('/iamport-callback', function(req,res){
	
//	결제가 승인되었을 때(모든 결제 수단) - (status : paid)
//	가상계좌가 발급되었을 때 - (status : ready)
//	가상계좌에 결제 금액이 입금되었을 때 - (status : paid)
//	예약결제가 시도되었을 때 - (status : paid or failed)
//	대시보드에서 환불되었을 때 - (status : cancelled)

	var imp_uid = req.body.imp_uid;
	var merchant_uid = req.body.merchant_uid;
	var status = req.body.status;
	var result = '';
	switch (status) {
	case "paid":
//		console.log('결제완료');
		models.Order.updateOne({'merchant_uid' : merchant_uid },{$set :{'payStatus' : 1, 'pgInfo.pg_uid' : imp_uid}},function(err){
			if(err){
				console.log(err);
				res.status(500).json({ error: 'database failure' });
			}else{
				console.log('STATUS UPDATE');
			}
		});
		break;
	case "ready":
//		console.log('가상계좌 입금대기');
		models.Order.updateOne({'merchant_uid' : merchant_uid },{$set :{'payStatus' : 2 ,'pgInfo.pg_uid' : imp_uid}},function(err){
			if(err){
				console.log(err);
				res.status(500).json({ error: 'database failure' });
			}else{
				console.log('STATUS UPDATE');
			}
		});
		
		break;
	case "failed":
//		console.log('예약결제 입금대기');
		models.Order.updateOne({'merchant_uid' : merchant_uid },{$set :{'payStatus' : 3 ,'pgInfo.pg_uid' : imp_uid}},function(err){
			if(err){
				console.log(err);
				res.status(500).json({ error: 'database failure' });
			}else{
				console.log('STATUS UPDATE');
			}
		});
		break;
	case "cancelled":
//		console.log('환불');		
		models.Order.updateOne({'merchant_uid' : merchant_uid },{$set :{'payStatus' : 4 ,'pgInfo.pg_uid' : imp_uid}},function(err){
			if(err){
				console.log(err);
				res.status(500).json({ error: 'database failure' });
			}else{
				console.log('STATUS UPDATE');
			}
		});
		break;
	}
	res.json({'message' : status + ' : ' + common.currentDate()});
});