2018년 12월 31일 월요일

[django] 아임포트 결제 사용 하기

핵심흐름
1. 결제 테스트 페이지 작성 (blank 페이지 하나 만든다)
2. 아임포트 가입
3. 아임포트 관리자 화면에서 PG사 선택
4. 결제 테스트 페이지에 아임포트 라이브러리 추가
5. 결제 요청을위한 javascript 추가 (기본동작확인 완료)
6. 결제 결과를 검증 (아임포트로 REST API를 이용하여 다시 호출 하여 확인)

관련 페이지
https://docs.iamport.kr/
https://www.iamport.kr/getstarted
https://admin.iamport.kr/settings
https://github.com/iamport/iamport-manual/blob/master/%EC%9D%B8%EC%A6%9D%EA%B2%B0%EC%A0%9C/README.md (상세메뉴얼)


1. 결제 테스트 페이지 작성

  • blank 페이지 하나 만든다


2. 아임포트 가입
3. 아임포트 관리자 화면에서 PG사 선택
  • 관련페이지(https://www.iamport.kr/getstarted 등) 를 참고 하여 진행한다.


4. 결제 테스트 페이지에 아임포트 라이브러리 추가

간단한 결제 테스트 페이지 를 만들어 놓고, 아임포트 라이브러리 (javascript cdn) 를 추가 한후

<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-x.y.z.js"></script>


5. 결제 요청을위한 javascript 추가 (기본동작확인 완료)

상세메뉴얼(https://github.com/iamport/iamport-manual/blob/master/%EC%9D%B8%EC%A6%9D%EA%B2%B0%EC%A0%9C/README.md)  2.1의

IMP.request_pay({
    pg : 'html5_inicis',
    pay_method : 'card',
    merchant_uid : 'merchant_' + new Date().getTime(),
    name : '주문명:결제테스트',
    amount : 14000,
    buyer_email : 'iamport@siot.do',
    buyer_name : '구매자이름',
    buyer_tel : '010-1234-5678',
    buyer_addr : '서울특별시 강남구 삼성동',
    buyer_postcode : '123-456'
}, function(rsp) {
    if ( rsp.success ) {
        var msg = '결제가 완료되었습니다.';
        msg += '고유ID : ' + rsp.imp_uid;
        msg += '상점 거래ID : ' + rsp.merchant_uid;
        msg += '결제 금액 : ' + rsp.paid_amount;
        msg += '카드 승인번호 : ' + rsp.apply_num;
    } else {
        var msg = '결제에 실패하였습니다.';
        msg += '에러내용 : ' + rsp.error_msg;
    }

    alert(msg);
});

을 결제 테스트 페이지에 추가 하되,  pg를 관리자를,  3. 아임포트 관리자 화면에서 PG사 선택 에서 선택한데로 적당히 잘 셋팅해줘야 한다.

이렇게 해서 연동 여부 바로 확인. 핵심흐름 5번까지 하는데 큰무리 없음.


6. 결제 결과를 검증 (아임포트로 REST API를 이용하여 다시 호출 하여 확인)

핵심흐름 6번이 문제이면서 잘 안되었던 부분 (아임포트 사이트에 너무 대충 나와있는듯)

6번을 하는 이유는 살펴보자.

내사이트(우리), 아임포트, PG사 세가지 서버가 존재 한다.

아임포트는 우리(내사이트)의 요청을 받으면 그것을 실제 PG사로 릴레이 해주고 결과를 받아서 아임포트 서버에 저장 하고, 다시 우리(내사이트)에게 알려주는 역할을 한다.  다시 말해 아임포트는 단지 연결을 쉽게 해주는 역할을 한다.
그런데 이 연결 과정에서 문제가 생겨 마지막에 내사이트(우리)에서 결제하려고 했던 금액과 틀린 금액이 아임포트에 저장 되면 문제가 되므로, 내사이트(우리)에서 아임포트쪽 서버에 REST API를 통해 잘 저장이 된것지를 호출하여 확인하는 검증 과정이 필요하다.

그러므로, 위의 rsp.success 이후에,



IMP.request_pay({
    pg : 'inicis',
    pay_method : 'card',
    merchant_uid : 'merchant_' + new Date().getTime(),
    name : '주문명:결제테스트',
    amount : 14000,
    buyer_email : 'iamport@siot.do',
    buyer_name : '구매자이름',
    buyer_tel : '010-1234-5678',
    buyer_addr : '서울특별시 강남구 삼성동',
    buyer_postcode : '123-456'
}, function(rsp) {
    if ( rsp.success ) {
     //[1] 서버단에서 결제정보 조회를 위해 jQuery ajax로 imp_uid 전달하기
     jQuery.ajax({
      url: "/payments/complete", //cross-domain error가 발생하지 않도록 동일한 도메인으로 전송
      type: 'POST',
      dataType: 'json',
      data: {
       imp_uid : rsp.imp_uid
       //기타 필요한 데이터가 있으면 추가 전달
      }
     }).done(function(data) {
      //[2] 서버에서 REST API로 결제정보확인 및 서비스루틴이 정상적인 경우
      if ( everythings_fine ) {
       var msg = '결제가 완료되었습니다.';
       msg += '\n고유ID : ' + rsp.imp_uid;
       msg += '\n상점 거래ID : ' + rsp.merchant_uid;
       msg += '\결제 금액 : ' + rsp.paid_amount;
       msg += '카드 승인번호 : ' + rsp.apply_num;

       alert(msg);
      } else {
       //[3] 아직 제대로 결제가 되지 않았습니다.
       //[4] 결제된 금액이 요청한 금액과 달라 결제를 자동취소처리하였습니다.
      }
     });
    } else {
        var msg = '결제에 실패하였습니다.';
        msg += '에러내용 : ' + rsp.error_msg;

        alert(msg);
    }
});


와 같이 큰 흐름으로 구성이 되고,  그 실제 검증 과정을 할 페이지/Json response 페이지는 내사이트(우리) 에서 만들어서 제공 하고 그 이름은 /payments/complete가 된다.

결국 결제 연동인 성공한 경우 (rsp.success) ajax로 호출을 하는데, 이것은 내사이트에 /payments/complete  url을 만들고 ajax response를 만들어 주라는 의미이고, 그 response를 만드는 과정에서, 엑세스 토큰을 만들고, 결제정보를 조회하고, 그걸로 db에 필요한 정보를 넣고, 완료가 되면 다시 결제 테스트 페이지 로 돌아 와서  everythings_fine 이후의 것들을 실행 하는 구조이다.

그러므로 우리는 /payments/complete 를 url의 기능(json response)을 만들어 주어야 한다.
이것을 이해하는데 좀 시간이 걸림.

이것을 하는 과정중,
어려웠던점은 django  csrf 검증 때문에,  url: "/payments/complete"  을 

url: "/payments/complete/"
로 해줘야 했고, template view를 못쓰고(해보려고 했는데 잘 안되서)

urls.py
1
url(r'^payment_test/$', TemplateView.as_view(template_name='payment_test.html'), name='payment_test'),url(r'^payments/complete/$', views.payment_complete, name='payment_complete'),
cs

views.py
1
2
@csrf_exempt
def payment_complete(request):
cs

로 함수로 구성 하고 decorator를 넣어 주었다.


그리고, 나머지는 관련페이지중 https://docs.iamport.kr/implementation/payment 를 참고하여,  django형태에 맞게 아래와 같이 구성하였다. (django를 쓰시는 분들에게는 도움이 되길)

@csrf_exempt
def payment_complete(request):
    if request.method == 'POST' and request.is_ajax():
        imp_uid = request.POST.get('imp_uid')
        # // 액세스 토큰(access token) 발급받기
        data = {
            "imp_key""X071XXXXXX52038",
            "imp_secret""MlXXXXXXnhP7plaPGe6NXXaXXXX9ocqekQAuFXXXwXXXXXgrZ5n9GELvNaIdp24ZwJhfvm"
        }
        response = requests.post('https://api.iamport.kr/users/getToken', data=data)
        data = response.json()
        my_token = data['response']['access_token']
        #  // imp_uid로 아임포트 서버에서 결제 정보 조회
        headers = {"Authorization": my_token}
        response = requests.get('https://api.iamport.kr/payments/'+imp_uid, data=data, headers = headers)
        data = response.json()
        # // DB에서 결제되어야 하는 금액 조회 const
        order_amount = 100
        amountToBePaid = data['response']['amount']  # 아임포트에서 결제후 실제 결제라고 인지 된 금액
        status = data['response']['status']  # 아임포트에서의 상태
        if order_amount==amountToBePaid:
            # DB에 결제 정보 저장
            # await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData}); // DB에
            if status == 'ready':
                # DB에 가상계좌 발급정보 저장
                return HttpResponse(json.dumps({'status'"vbankIssued"'message'"가상계좌 발급 성공"}),
                                    content_type="application/json")
            elif status=='paid':
                return HttpResponse(json.dumps({'status'"success"'message'"일반 결제 성공"}),
                                    content_type="application/json")
            else:
                pass
        else:
            return HttpResponse(json.dumps({'status'"forgery"'message'"위조된 결제시도"}), content_type="application/json")
    else:
        return render_to_response('payment_complete.html', locals())   #수정 필요
cs



그리고, 결제 테스트 페이지에 실제 구성에 done, fail, always등을 같이 구성해서 debugging을 쉽게 하였다.

<script>
    IMP.init('impX2XXXX03');
    IMP.request_pay({
        pg: 'danal_tpay',
        pay_method: 'card',
        merchant_uid: 'merchant_' + new Date().getTime(),
        name'주문명:결제테스트',
        amount: 100,
        buyer_email: 'iamport@siot.do',
        buyer_name: 'yellowdonkey',
        buyer_tel: '010-1234-5678',
        buyer_addr: '서울특별시 강남구 삼성동',
        buyer_postcode: '123-456'
    }, function (rsp) {
        if (rsp.success) {
            //[1] 서버단에서 결제정보 조회를 위해 jQuery ajax로 imp_uid 전달하기
            jQuery.ajax({
                url: "/payments/complete/"//cross-domain error가 발생하지 않도록 동일한 도메인으로 전송
                type: 'POST',
                dataType: 'json',
                data: {
                    imp_uid: rsp.imp_uid
                    //기타 필요한 데이터가 있으면 추가 전달
                },
            }).done(function (data) {
                //[2] 서버에서 REST API로 결제정보확인 및 서비스루틴이 정상적인 경우
                alert("ajax done");               
                console.log("ajax done");
                if (data.status=='success') {
                    var msg = '결제가 완료되었습니다.';
                    msg += '\n고유ID : ' + rsp.imp_uid;
                    msg += '\n상점 거래ID : ' + rsp.merchant_uid;
                    msg += '\결제 금액 : ' + rsp.paid_amount;
                    msg += '카드 승인번호 : ' + rsp.apply_num;
                    alert(msg);
                    console.log(msg);
                } else {
                    //[3] 아직 제대로 결제가 되지 않았습니다.
                    //[4] 결제된 금액이 요청한 금액과 달라 결제를 자동취소처리하였습니다.
                    var msg = '아직 제대로 결제가 되지 않았습니다.';
                    alert(msg);
                    console.log(msg);
                }
            }).fail(function () {
                alert("ajax fail");
                console.log("ajax fail");
            }).always(function () {
                alert("ajax always");
                console.log("ajax always");
            });
        } else {
            var msg = '결제에 실패하였습니다.';
            msg += '에러내용 : ' + rsp.error_msg;
            alert(msg);
        }
    });
</script>
cs

댓글 1개:

  1. 정리가 너무 잘되어 있네요~~
    저도 이것저것 해보고 있는데 잘안되네요ㅠ
    혹시 결제 테스트 하려면, 아임포트에서 사업자 정보로 PG사 가입 해야하나요?

    답글삭제