Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

환불 로직 작성 #95

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions payment/clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import requests
import constance


class PortOneClient:
# TODO: 상황에 맞는 Error로 변경하기: 지금은 모두 ValueError
def __init__(self):
self.url = "https://api.iamport.kr"

# TODO: 잘 말아서 Singleton 패턴으로 만들기
def get_access_token(self) -> str:
endpoint = self.url + "/users/getToken"

if constance.config["imp_key"] is None or constance.config["imp_secret"] is None:
raise ValueError("Access Token 발급 실패: imp_key 또는 imp_secret을 찾을 수 없습니다.")

request_dto = {
"imp_key": constance.config["imp_key"],
"imp_secret": constance.config["imp_secret"]
}

response = requests.post(
endpoint,
request_dto
)

if not response.ok:
raise ValueError("Access Token 발급에 실패했습니다.")

return response.json().get("access_token")

def find_payment_info(self, payment_key: str):
endpoint = self.url + "/payments/{}"

if payment_key is None or payment_key == "":
raise ValueError("payment_key (merchant_uid)는 필수값입니다.")

request_header = {
"Authorization": self.get_access_token()
}

response = requests.get(endpoint.format(payment_key), headers=request_header)

if not response.ok:
raise ValueError("결제 정보 조회에 실패했습니다.")

return response.json()

def req_cancel_payment(self, payment_key: str, price: int, reason: str = ""):
endpoint = self.url + "/payments/cancel"

if payment_key is None or payment_key == "":
raise ValueError("payment_key (merchant_uid)는 필수값입니다.")

if price is None or price == 0:
raise ValueError("금액은 필수값입니다.")

request_header = {
"Authorization": self.get_access_token()
}

request_dto = {
"merchant_uid": payment_key,
"amount": price,
"checksum": price # 지금은 전액환불하는 케이스만 존재함 -> 환불요청금액과 결제 건의 환불가능금액은 동일해야함
}

if reason is not None and reason != "":
request_dto["reason"] = reason

response = requests.post(
endpoint,
request_dto,
headers=request_header
)

if not response.ok:
raise ValueError("Portone에서 비정상 응답: {}".format(payment_key))

response_data = response.json()

if response_data.get("code") is None or response_data.get("code") != 0:
raise ValueError("환불 처리 실패: {}".format(payment_key))

return True
11 changes: 11 additions & 0 deletions payment/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,14 @@ def _save_history(payment_key: str, status: int):
)

new_payment_history.save()


@transaction.atomic
def cancel_payment(payment: Payment):
payment.status = PaymentStatus.REFUND_SUCCESS.value
payment.save()

payment_history = PaymentHistory(
payment_key=payment.payment_key,
status=PaymentStatus.REFUND_SUCCESS.value
)
23 changes: 0 additions & 23 deletions payment/utils.py

This file was deleted.

31 changes: 30 additions & 1 deletion payment/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from rest_framework.views import APIView

from payment import enum
from payment.clients import PortOneClient
from ticket.models import TicketType, Ticket
from payment.logic import generate_payment_key
from payment.logic import generate_payment_key, cancel_payment
from payment.models import Payment, PaymentHistory

from django.conf import settings
Expand Down Expand Up @@ -95,3 +96,31 @@ def post__generate_payment_key(request):
}

return Response(response_data)


@api_view(["POST"])
@transaction.atomic
def post__cancel_payment(request):
portone_client = PortOneClient()

target_payment = Payment.objects.get(payment_key=request.data["payment_key"])
target_ticket = Ticket.objects.get(payment=target_payment)

portone_client.req_cancel_payment(
target_payment.payment_key,
target_payment.money,
"구매자의 환불요청"
)

cancel_payment(target_payment)

target_ticket.is_refunded = True
target_ticket.refunded_at = datetime.datetime.now()
target_ticket.save()

dto = {
"msg": "ok"
}

return Response(dto)

4 changes: 2 additions & 2 deletions ticket/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.views import View
from django.views.decorators.csrf import csrf_exempt

import payment.utils
import payment.logic
from program.models import CONFERENCE, TUTORIAL, SPRINT
from .models import Ticket, TicketType
from .requests import (
Expand Down Expand Up @@ -181,7 +181,7 @@ def get__ticket_list(request):
class TicketDetailView(View):
def get(self, request, item_id: int):
ticket_type = TicketType.objects.get(id=item_id)
payment_key = payment.utils.generate_payment_key(request.user, ticket_type=ticket_type)
payment_key = payment.logic.generate_payment_key(request.user, ticket_type=ticket_type)
user = request.user

dto = {
Expand Down