본문 바로가기
API

[아임포트] 일반 결제 API 연동

by 원동호 2021. 3. 18.
반응형

개발 환경 : Laravel Framework, php

 

상세 API 정보는 아래를 참고 하길 바란다.

 

docs.iamport.kr/implementation/payment

 

[결제연동] 일반결제

일반결제 연동하기 해당 가이드는 아임포트 일반 결제 기능을 웹사이트에 설치하고 서버 데이터베이스에 결제 결과 정보를 저장하는 방법을 안내합니다.아임포트의 JavaScript 라이브러리를 삽입

docs.iamport.kr

 

1. 아임포트 라이브러리 추가하기

jQuery 기반이기 때문에 jQuery라이브러리를 추가해줘야 한다.

<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
<!-- iamport.payment.js -->
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.5.js"></script>

 

2. 가맹점 식별코드 추가
let IMP = window.IMP; // <- 생략해도 무방함. 
IMP.init("본인가맹점고유번호"); 

init function 인자로 본인 가맹점 고유번호를 입력한다.

 

3. 결제창 호출 코드 추가하기

IMP.request_pay(param, callback)을 호출한다. 함수의 첫번째 인자인 param에 결제 요청에 필요한 속성과 값을

담음.

 // IMP.request_pay(param, callback) 호출
  IMP.request_pay({ // param
    pg: "html5_inicis",
    pay_method: "card",
    merchant_uid: "주문번호",
    name: "상품명",
    amount: 가격,
    buyer_tel: "휴대폰번호",
    confirm_url : '실제 결제 시도 전 데이터 검증을 할 url'
  },function (rsp) {
    if (rsp.success) {
        // 결제 성공 시 로직,
        
    }else{
        // 결제 실패 시 로직,
    }
  });

 

 

필수로 들어가야 할 parameter은 아래 링크에서 확인 바람.

docs.iamport.kr/tech/imp?lang=ko#param

 

[가이드] 결제 파라미터

pay_methodstring결제수단card(신용카드), trans(실시간계좌이체), vbank(가상계좌), phone(휴대폰소액결제), kakaopay (이니시스, KCP, 나이스페이먼츠를 통한 카카오페이 직접 호출), payco (이니시스, KCP를 통한

docs.iamport.kr

merchant_uid(주문번호)는 DB에 저장하여 검증시 필요하기 때문에 결제 모듈을 호출하기 전에 생성 후 merchant_uid에 값을 넣어 줘야 한다.

 

confirm_url은 document에 없는 내용이므로 아임포트 측에 별도로 신청을 하고 등록이 완료 되어야 사용 할 수 있는 parameter이다. 결제 하기 전 검증이 꼭 필요 하므로 위 parameter을 사용하면 좋을 것 같다.  

예를들어 실제 결제가 이루어 지기 전, 재고가 부족하거나 실 결제 가격과 구매한 제품의 가격이 다를 경우(자바스크립트 위변조)에는 결제 취소를 해야 하기 때문이다.

 

4. confirm_url 을 이용하여 결제 검증하기

아임포트 서버에서 PG사로 최종 결제 요청하기 직전에 confirm_url parameter가 있으면 지정한 url로 HTTP POST요청을 보내게 된다. 지정하지 않은 경우 기존처럼 아임포트->PG사로 바로 결제를 요청한다.

 

confirm_url을 사용하지 않고 결제를 진행할 경우 이미 결제가 이루어진 후, 웹서버측에서 재고와 금액을 검증하게 되면 결제완료 -> 결제 취소로 진행해야 하기 때문에 별도로 결제 취소하는 API를 작성해야 한다. 

 

해당 요청건에 대해서는 5초 이내로 응답을 해야하며 결제진행은 HTTP_STATUS 200, 결제거절은 그 외의 응답을 해주면 되고, 5초 이내로 응답이 없을 시 Timeout이 발생하고 500응답을 받은 것으로 간주하고 결제는 중단된다.

 

요청 parameter는 아래와 같다.

//Content-type : application/json
{
    "imp_uid" : "가맹점고유번호",
    "merchant_uid" : "주문번호",
    "amount" : "PG사에 요청할실제 결제 금액"
}

웹서버에서 위 parameter을 받아 2가지를 검증했다.

1. merchant_uid로 DB에 저장된 주문 테이블을 조회해 parameter로 받은 amount(PG사에 요청할 결제금액)과 주문 테이블에 저장된 결제 금액을 비교해 동일한 값이면 결제를 진행하고(HTTP_STATUS 200) 값이 다를 시 결제를 중단한다.(HTTP_STATUS 500) 

2. 구매한 제품의 재고가 부족할경우 결제를 중단(HTTP_STATUS 500)

 

응답시 본문에 "reason" 이라는 필드가 있으면 해당 정보를 결제 실패 사유로 기록하고 callback함수의 rsp object에도 값을 확인 할 수 있다.

//example
{
    "reason" : "결제 금액 불일치"
}


결제 실패시 function(rsp) callback의 else문을 타므로 로직을 처리해 주면 된다.

 

5. 웹서버에 parameter 전달하기

confirm_url을 이용해서 결제 검증이 완료되면 실제 결제 완료된 건에 대해서 데이터 동기화를 하기 위해 웹서버로 API를 요청한다.

  IMP.request_pay({
    /* ...중략... */
  }, function (rsp) { // callback
  
    /* 결제 성공 시: 결제 승인
     * 아래의 rsp.success는 결제 모듈창에서 성공했을경우 이므로
     * 실제 결제가 됬더라도 ajax호출 후 웹서버측에서 에러가 발생했다면
     * 별도로 처리를 해줘야함
    */
    if (rsp.success) { 
      // jQuery로 HTTP 요청
      $.ajax({
      	  headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')},
          type: 'post',
          url: '본인웹서버 route',
          data: {
                    'imp_uid': rsp.imp_uid,
                    'merchant_uid': rsp.merchant_uid
          },
          success: function(data) {
            if(data.code!=200){
                //결제실패(웹서버측 실패)      
            }else{
                //결제성공(웹서버측 성공)
            }
           },
           //javascript error
           error: function(data) {
           	console.log("error" +data);
           }
      	})
    } else {
      alert("결제에 실패하였습니다. 에러 내용: " +  rsp.error_msg);
    }
  });

 

//controller
function payments(Request $request){
        $result = ["code"=>200, "message"=>"success"];
  
        //아임포트 관리자 페이지의 시스템설정->내정보->REST API 키 값을 입력한다.
        $imp_key = "REST API 키";
        //아임포트 관리자 페이지의 시스템설정->내정보->REST API Secret 값을 입력한다.
        $imp_secret = "REST API Secret";
        //결제 모듈을 호출한 페이지에서 ajax로 넘겨받은 imp_uid값을 저장한다.
        $imp_uid = $request->input('imp_uid');
        //결제 모듈을 호출한 페이지에서 ajax로 넘겨받은 merchant_uid값을 저장한다.
        $merchant_uid = $request->input('merchant_uid;);
          
        try{
            /*주문서조회*/
            $order_list = $this->orderProductService->getById($merchant_uid);   
            
            /*
             * 결제성공한 건에 대해서는 merchant_uid로 아임포트 API 통신을해야 한다.
             * api access_key를 얻기위해 아임포트에서 제공되는 imp_key,imp_secret을 이용하여
             * 아래 api로 token을 얻는다.
             * return 값이 json이므로 decode하여 원하는 값을 들고온다.
            */
            
            //다날 엑세스 토큰 발급
            $getToken  = Http::withHeaders([
                'Content-Type' => 'application/json'
            ])->post('https://api.iamport.kr/users/getToken', [
                'imp_key' => $imp_key,
                'imp_secret' => $imp_secret,
            ]);
            $getTokenJson = json_decode($getToken, true);
            $access_token = $getTokenJson['response']['access_token'];
    
            $getPaymentData = Http::withHeaders([
                'Authorization' => $access_token
            ])->get('https://api.iamport.kr/payments/'.$imp_uid);
    		
            $getPaymentDataJson = json_decode($getPaymentData,true);
    
            
            //아임포트에 요청한 실제 결제 정보
            $responseData = $getPaymentDataJson['response'];
            //아임포트 결제 상태 값 (paid가 정상 결제 된 값)
            $iamport_status = $responseData['status'];
            //아임포트 실제 결제 금액
            $iamport_amount = $responseData['amount'];            
            
            //결제 내역 테이블 update
            $this->orderProductService->success($order_list);
  
            }catch(Exception $e){
                $result = [
                    'code' => 410,
                    'message' => $e->getMessage()
                ];
            }

            return response()->json($result);
    }

 

6. 서버 응답 처리하기

성공할 경우 5번의 응답 값으로 본인이 처리하고 싶은 로직을 추가 하면 된다.

 

실패할 경우에 본인은 5가지로 처리했다.

1. 결제 모듈에서 사용자가 취소한 경우

2. 결제 시도했지만 한도 초과인 경우

3. 결제 시도했지만 잔액이 부족할 경우

4. confirm_url에서 결제금액이 불일치 한 경우(PG사 요청한 금액 != DB에 저장된 주문서의 금액)

5. confirm_url에서 상품의 재고가 부족한 경우

 

IMP.request_pay({
    /* ...중략... */
  }, function (rsp) { // callback
    if (rsp.success) { // 결제 성공 시: 결제 승인
        /* ...중략... */
    } else {
      /* 
      {
        error_msg: "에러메시지"
        imp_uid: "고유번호"
        merchant_uid: "주문번호"
        pay_method: "card"
        pg_provider: "danal_tpay"
        pg_type: "payment"
        success: false(실패시)
      }
      */
    }
  });
  
/*
결제 실패 시 rsp object에 다음와 같은 값이 적용된다.
1. 한도초과, 잔액부족, 사용자 취소 시 에러 메시지가 다날측 적용된 메시지로 적용된다.
	ex)error_msg: "F0004:PG사 결제요청에 실패하여 중단합니다.(imp_123456) 3112, 한도가 초과하였습니다."
2. confirm_url을 이용하여 HTTP_STATUS 500 응답 시 상세 사유에는 응답값으로 reason 필드에 
   있는 값을 출력한다.
-> "가맹점 요청에 의해 결제를 중단합니다.(상세사유 : reason 필드의 value)
	ex)error_msg: "가맹점 요청에 의해 결제를 중단합니다. (상세사유 : 결제 금액 불일치로 결제가 실패 되었습니다.)"
개발자 도구로 필요한 값을 출력해보면 될 것 같다.
 */

 

반응형

댓글