Skip to content

Commit 9cc05cf

Browse files
committed
환불 로직 상상 코딩
1 parent fe6936e commit 9cc05cf

File tree

5 files changed

+128
-26
lines changed

5 files changed

+128
-26
lines changed

payment/clients.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import requests
2+
import constance
3+
4+
5+
class PortOneClient:
6+
# TODO: 상황에 맞는 Error로 변경하기: 지금은 모두 ValueError
7+
def __init__(self):
8+
self.url = "https://api.iamport.kr"
9+
10+
# TODO: 잘 말아서 Singleton 패턴으로 만들기
11+
def get_access_token(self) -> str:
12+
endpoint = self.url + "/users/getToken"
13+
14+
if constance.config["imp_key"] is None or constance.config["imp_secret"] is None:
15+
raise ValueError("Access Token 발급 실패: imp_key 또는 imp_secret을 찾을 수 없습니다.")
16+
17+
request_dto = {
18+
"imp_key": constance.config["imp_key"],
19+
"imp_secret": constance.config["imp_secret"]
20+
}
21+
22+
response = requests.post(
23+
endpoint,
24+
request_dto
25+
)
26+
27+
if not response.ok:
28+
raise ValueError("Access Token 발급에 실패했습니다.")
29+
30+
return response.json().get("access_token")
31+
32+
def find_payment_info(self, payment_key: str):
33+
endpoint = self.url + "/payments/{}"
34+
35+
if payment_key is None or payment_key == "":
36+
raise ValueError("payment_key (merchant_uid)는 필수값입니다.")
37+
38+
request_header = {
39+
"Authorization": self.get_access_token()
40+
}
41+
42+
response = requests.get(endpoint.format(payment_key), headers=request_header)
43+
44+
if not response.ok:
45+
raise ValueError("결제 정보 조회에 실패했습니다.")
46+
47+
return response.json()
48+
49+
def req_cancel_payment(self, payment_key: str, price: int, reason: str = ""):
50+
endpoint = self.url + "/payments/cancel"
51+
52+
if payment_key is None or payment_key == "":
53+
raise ValueError("payment_key (merchant_uid)는 필수값입니다.")
54+
55+
if price is None or price == 0:
56+
raise ValueError("금액은 필수값입니다.")
57+
58+
request_header = {
59+
"Authorization": self.get_access_token()
60+
}
61+
62+
request_dto = {
63+
"merchant_uid": payment_key,
64+
"amount": price,
65+
"checksum": price # 지금은 전액환불하는 케이스만 존재함 -> 환불요청금액과 결제 건의 환불가능금액은 동일해야함
66+
}
67+
68+
if reason is not None and reason != "":
69+
request_dto["reason"] = reason
70+
71+
response = requests.post(
72+
endpoint,
73+
request_dto,
74+
headers=request_header
75+
)
76+
77+
if not response.ok:
78+
raise ValueError("Portone에서 비정상 응답: {}".format(payment_key))
79+
80+
response_data = response.json()
81+
82+
if response_data.get("code") is None or response_data.get("code") != 0:
83+
raise ValueError("환불 처리 실패: {}".format(payment_key))
84+
85+
return True

payment/logic.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,14 @@ def _save_history(payment_key: str, status: int):
4444
)
4545

4646
new_payment_history.save()
47+
48+
49+
@transaction.atomic
50+
def cancel_payment(payment: Payment):
51+
payment.status = PaymentStatus.REFUND_SUCCESS.value
52+
payment.save()
53+
54+
payment_history = PaymentHistory(
55+
payment_key=payment.payment_key,
56+
status=PaymentStatus.REFUND_SUCCESS.value
57+
)

payment/utils.py

Lines changed: 0 additions & 23 deletions
This file was deleted.

payment/views.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
from rest_framework.views import APIView
77

88
from payment import enum
9+
from payment.clients import PortOneClient
910
from ticket.models import TicketType, Ticket
10-
from payment.logic import generate_payment_key
11+
from payment.logic import generate_payment_key, cancel_payment
1112
from payment.models import Payment, PaymentHistory
1213

1314
from django.conf import settings
@@ -95,3 +96,31 @@ def post__generate_payment_key(request):
9596
}
9697

9798
return Response(response_data)
99+
100+
101+
@api_view(["POST"])
102+
@transaction.atomic
103+
def post__cancel_payment(request):
104+
portone_client = PortOneClient()
105+
106+
target_payment = Payment.objects.get(payment_key=request.data["payment_key"])
107+
target_ticket = Ticket.objects.get(payment=target_payment)
108+
109+
portone_client.req_cancel_payment(
110+
target_payment.payment_key,
111+
target_payment.money,
112+
"구매자의 환불요청"
113+
)
114+
115+
cancel_payment(target_payment)
116+
117+
target_ticket.is_refunded = True
118+
target_ticket.refunded_at = datetime.datetime.now()
119+
target_ticket.save()
120+
121+
dto = {
122+
"msg": "ok"
123+
}
124+
125+
return Response(dto)
126+

ticket/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.views import View
1010
from django.views.decorators.csrf import csrf_exempt
1111

12-
import payment.utils
12+
import payment.logic
1313
from program.models import CONFERENCE, TUTORIAL, SPRINT
1414
from .models import Ticket, TicketType
1515
from .requests import (
@@ -181,7 +181,7 @@ def get__ticket_list(request):
181181
class TicketDetailView(View):
182182
def get(self, request, item_id: int):
183183
ticket_type = TicketType.objects.get(id=item_id)
184-
payment_key = payment.utils.generate_payment_key(request.user, ticket_type=ticket_type)
184+
payment_key = payment.logic.generate_payment_key(request.user, ticket_type=ticket_type)
185185
user = request.user
186186

187187
dto = {

0 commit comments

Comments
 (0)