장고 - 결제 시스템 연동
- 이번 포스트에서는 온라인 쇼핑몰 사이트를 예로 들어, 결제 시스템을 어떻게 연동하는지에 대해 알아볼 것이다.
- 여기에서는 iamport를 사용하여 결제 시스템을 연동할 것이다.
iamport 결제 관리 시스템 이용
1. iamport 사이트 접속
- https://www.iamport.kr
- 우측 상단 ‘대사보드’ 클릭
- 회원가입 및 로그인
- 시스템설정 > PG설정(일반결제 및 정기결제)
- PG사 : KG이니시스(웹표준결제창) 선택
- 테스트모드 : ON
2. 장고 프로젝트에 IAMPORT 관련 정보 설정
- 경로 : config(프로젝트) > settings.py
1
2
3
4...
# iamport 사이트 > 시스템설정 > 내정보에서 IAMPORT_KEY, IAMPORT_SECRET 정보 입력
IAMPORT_KEY = 'REST API 키'
IAMPORT_SECRET = 'REST API secret'
3. 장바구니 페이지에서 결제 버튼 클릭 시, 결제페이지로 이동되도록 코드 작성
- 경로 : cart > templates > cart > cart.detail.html
1
2
3
4
5
6
7
8
9
10
11...
<!-- 클릭 시, 결제페이지로 이동되는 버튼 부분 -->
<tr>
<td colspan="7">
<!-- 이전 페이지 이동 -->
<a href="{{continue_url}}" class="float-left btn btn-lg btn-primary">Continue Shopping</a>
<!-- 결제페이지로 이동 -->
<a href="{% url 'order_create' %}" class="float-right btn btn-lg btn-info">Checkout</a>
</td>
</tr>
{% endblock %}
4. 결제 페이지로 이동할 경로 작성
- 경로 : order > urls.py
1
2
3
4
5
6from django.urls import path
from .views import *
urlpatterns = [
path('create/', order_create, name='order_create'),
]
5. 결제 페이지 코드 작성
경로 : order > views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from django.shortcuts import render
from cart.cart import Cart
from .models import OrderItem
from .forms import OrderForm
def order_create(request):
# 장바구니에 존재하는 상품 정보 받아오기
cart = Cart(request)
# 주문 정보가 입력 완료된 상황(결제 폼 작성 완료하여 POST 형태로 request를 받은 상황)
if request.method == "POST":
form = OrderForm(request.POST)
# 작성한 폼 validation 진행
if form.is_valid():
order = form.save()
for item in cart:
# 장바구니에 들어있는 모든 상품 정보를 OrderItem 모델에 저장
OrderItem.objects.create(order=order, product=item['product'], price=item['price'], quantity=item['quantity'])
return render(request, 'order/order_created.html', {'order': order})
# 주문 정보가 입력되지 않은 상황(처음 결제페이지로 이동한 상황)
else:
form = OrderForm()
return render(request, 'order/order_create.html', {'form':form})경로 : order > templates > order > order_create.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35{% extends 'base.html' %}
{% block title %}
Order Checkout
{% endblock %}
{% block content %}
<div class="alert alert-info mt-3">
Please enter your order information.
</div>
<form action="" method="post" class="order-form">
{% csrf_token %}
{{form.as_p}}
<input type="hidden" name="pre_order_id" value="">
<input type="hidden" name="amount" value="{{cart.get_total_price}}">
<input type="submit" value="Payment" class="btn btn-lg btn-success">
</form>
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
csrf_token = '{{csrf_token}}';
order_create_url = '{% url 'order_create_ajax' %}';
order_checkout_url = '{% url 'order_checkout' %}';
order_validation_url = '{% url 'order_validation' %}';
order_complete_url = '{% url 'order_complete' %}';
</script>
<!-- iamport 사이트에서 해당 스크립트 복사 붙여넣기-->
<script src="https://cdn.iamport.kr/js/iamport.payment-1.1.5.js" type="text/javascript"></script>
<!-- 결제창 띄우기 위한 자바스크립트 실행 -->
{% load staticfiles %}
<script src="{% static 'js/checkout.js' %}" type="text/javascript"></script>
{% endblock %}
6. 결제창 실행시키기 위해 필요한 자바스크립트 관련 view의 경로 설정
- 경로 : order > urls.py
1
2
3
4
5
6
7
8
9from django.urls import path
from .views import *
urlpatterns = [
...
path('create_ajax/', OrderCreateAjaxView.as_view(), name='order_create_ajax'),
path('checkout/', OrderCheckoutAjaxView.as_view(), name='order_checkout'),
path('validation/', OrderImpAjaxView.as_view(), name='order_validation'),
]
7. 결제창 실행시키기 위해 필요한 자바스크립트 관련 view 작성
- 경로 : order > views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80from cart.cart import Cart
from .models import OrderItem
from .forms import OrderForm
from django.views.generic.base import View
from django.http import JsonResponse
class OrderCreateAjaxView(View):
def post(self, request, *args, **kwargs):
cart = Cart(request)
form = OrderForm(request.POST)
if form.is_valid():
order = form.save()
for item in cart:
OrderItem.objects.create(order=order, product=item['product'], price=item['price'], quantity=item['quantity'])
data = {
"order_id":order.id
}
return JsonResponse(data)
return JsonResponse({}, status=401)
from .models import OrderTransaction
class OrderCheckoutAjaxView(View):
def post(self, request, *args, **kwargs):
order_id = request.POST.get('order_id')
order = Order.objects.get(id=order_id)
amount = request.POST.get('amount')
try:
merchant_order_id = OrderTransaction.objects.create_new(order=order, amount=amount)
except:
merchant_order_id = None
if merchant_order_id is not None:
data = {
'works':True,
'merchant_id':merchant_order_id
}
return JsonResponse(data)
else:
return JsonResponse({}, status=401)
class OrderImpAjaxView(View):
def post(self, request, *args, **kwargs):
order_id = request.POST.get('order_id')
merchant_id = request.POST.get('merchant_id')
imp_id = request.POST.get('imp_id')
amount = request.POST.get('amount')
order = Order.objects.filter(pk=order_id)
if not order.exists():
return JsonResponse({}, status=401)
order = order[0]
transaction = OrderTransaction.objects.filter(order=order, merchant_order_id=merchant_id, amount=amount)
if not transaction.exists():
return JsonResponse({}, status=401)
# 결제 정보 수정
try:
exact_transaction = transaction[0]
exact_transaction.transaction_id = imp_id
exact_transaction.success = True
exact_transaction.save()
# 주문 정보 - 결제 완료로 변경
order.paid = True
order.save()
data = {
'works':True
}
cart = Cart(request)
cart.clear()
return JsonResponse(data)
except Exception as e:
print("transaction error", e)
return JsonResponse({"message":str(e)}, status=401)
8. iamport API와 통신하는 함수 작성
- 경로 : order > iamport.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85# iamport 가입 - 설정
# iamport API와 통신하는 함수 만들기
# views.py API함수를 이용한 결제 진행
import requests
from django.conf import settings
# pip install requests
def get_token():
access_data = {
'imp_key':settings.IAMPORT_KEY,
'imp_secret':settings.IAMPORT_SECRET
}
url = 'https://api.iamport.kr/users/getToken'
req = requests.post(url, data=access_data)
data = req.json()
if data['code'] is 0:
return data['response']['access_token']
else:
return None
# iamport에 사전 정보를 보내서 결제 준비
def payment_prepare(order_id, amount, *args, **kwargs):
access_token = get_token()
if access_token:
access_data = {
'merchant_uid':order_id,
'amount':amount
}
url = "https://api.iamport.kr/payments/prepare"
headers = {
'Authorization':access_token
}
req = requests.post(url, data=access_data, headers=headers)
data = req.json()
if data['code'] is not 0:
raise ValueError("API 통신 오류")
else:
raise ValueError("토큰 오류")
# 결제 이후에 해당 하는 주문 번호와 결제 금액으로 진행된 결제가 있는지 찾아주는 함수
def find_transaction(order_id, *args, **kwargs):
access_token = get_token()
if access_token:
url = "https://api.iamport.kr/payments/find/"+order_id
headers = {'Authorization':access_token}
req = requests.post(url, headers=headers)
data = req.json()
if data['code'] is 0:
imp_data = data['response']
context = {
'imp_id': imp_data['imp_uid'],
'merchant_order_id': imp_data['merchant_uid'],
'amount':imp_data['amount'],
'status':imp_data['status'],
'type':imp_data['pay_method'],
'receipt_url':imp_data['receipt_url']
}
return context
else:
return None
else:
raise ValueError("토큰 오류")
def cancel_transaction(transaction_id, *args, **kwargs):
access_token = get_token()
if access_token:
url = "https://api.iamport.kr/payments/cancel"
headers = {'Authorization':access_token}
access_data = {
'imp_uid': transaction_id
}
print("data", access_data)
req = requests.post(url, data=access_data, headers=headers)
data = req.json()
if data['code'] is 0:
return data
else:
return None
else:
raise ValueError("토큰 오류")
9. 결제창 띄우기 위한 자바스크립트 코드 작성
- 경로 : order > static > js > checkout.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134$(function () {
var IMP = window.IMP;
IMP.init('iamport 사이트에서 가맹점 식별코드 입력');
$('.order-form').on('submit', function (e) {
var amount = parseFloat($('.order-form input[name="amount"]').val().replace(',', ''));
var type = $('.order-form input[name="type"]:checked').val();
// 폼 데이터를 기준으로 주문 생성
var order_id = AjaxCreateOrder(e);
if (order_id == false) {
alert('주문 생성 실패\n다시 시도해주세요.');
return false;
}
// 결제 정보 생성
var merchant_id = AjaxStoreTransaction(e, order_id, amount, type);
// 결제 정보가 만들어졌으면 iamport로 실제 결제 시도
if (merchant_id !== '') {
IMP.request_pay({
merchant_uid: merchant_id,
name: 'E-Shop product',
buyer_name:$('input[name="first_name"]').val()+" "+$('input[name="last_name"]').val(),
buyer_email:$('input[name="email"]').val(),
amount: amount
}, 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;
// 결제가 완료되었으면 비교해서 디비에 반영
ImpTransaction(e, order_id, rsp.merchant_uid, rsp.imp_uid, rsp.paid_amount);
} else {
var msg = '결제에 실패하였습니다.';
msg += '에러내용 : ' + rsp.error_msg;
console.log(msg);
}
});
}
return false;
});
});
// 폼 데이터를 기준으로 주문 생성
function AjaxCreateOrder(e) {
e.preventDefault();
var order_id = '';
var request = $.ajax({
method: "POST",
url: order_create_url,
async: false,
data: $('.order-form').serialize()
});
request.done(function (data) {
if (data.order_id) {
order_id = data.order_id;
}
});
request.fail(function (jqXHR, textStatus) {
if (jqXHR.status == 404) {
alert("페이지가 존재하지 않습니다.");
} else if (jqXHR.status == 403) {
alert("로그인 해주세요.");
} else {
alert("문제가 발생했습니다. 다시 시도해주세요.");
}
});
return order_id;
}
// 결제 정보 생성
function AjaxStoreTransaction(e, order_id, amount, type) {
e.preventDefault();
var merchant_id = '';
var request = $.ajax({
method: "POST",
url: order_checkout_url,
async: false,
data: {
order_id : order_id,
amount: amount,
type: type,
csrfmiddlewaretoken: csrf_token,
}
});
request.done(function (data) {
if (data.works) {
merchant_id = data.merchant_id;
}
});
request.fail(function (jqXHR, textStatus) {
if (jqXHR.status == 404) {
alert("페이지가 존재하지 않습니다.");
} else if (jqXHR.status == 403) {
alert("로그인 해주세요.");
} else {
alert("문제가 발생했습니다. 다시 시도해주세요.");
}
});
return merchant_id;
}
// iamport에 결제 정보가 있는지 확인 후 결제 완료 페이지로 이동
function ImpTransaction(e, order_id,merchant_id, imp_id, amount) {
e.preventDefault();
var request = $.ajax({
method: "POST",
url: order_validation_url,
async: false,
data: {
order_id:order_id,
merchant_id: merchant_id,
imp_id: imp_id,
amount: amount,
csrfmiddlewaretoken: csrf_token
}
});
request.done(function (data) {
if (data.works) {
$(location).attr('href', location.origin+order_complete_url+'?order_id='+order_id)
}
});
request.fail(function (jqXHR, textStatus) {
if (jqXHR.status == 404) {
alert("페이지가 존재하지 않습니다.");
} else if (jqXHR.status == 403) {
alert("로그인 해주세요.");
} else {
console.log(jqXHR);
alert("문제가 발생했습니다. 다시 시도해주세요.");
}
});
}
Posted