Skip to content

Commit

Permalink
feat: 예외 처리 로직 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Yoo117 committed Oct 13, 2024
1 parent a250aa8 commit d4e173c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 12 deletions.
5 changes: 4 additions & 1 deletion payments/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,11 @@ def refund_payment(self, order, payment):
if not payment or payment.payment_status != "completed":
raise ValidationError("해당 주문에 대한 완료된 결제를 찾을 수 없습니다.")

if payment.paid_at is None:
raise ValidationError("결제 완료 시간이 기록되지 않았습니다.")

if timezone.now() - payment.paid_at > timezone.timedelta(days=7):
raise ValidationError("결제 후 7일이 지나 취소할 수 없습니다.")
raise ValidationError("결제 후 7일이 지난 주문은 환불할 수 없습니다.")

try:
self.kakao_pay_service.refund_payment(payment)
Expand Down
46 changes: 44 additions & 2 deletions payments/tests/test_payments_views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pytest
from django.utils import timezone
from django.urls import reverse
from rest_framework import status
from unittest.mock import patch
from payments.models import CartItem


@pytest.mark.django_db
Expand Down Expand Up @@ -41,22 +41,64 @@ def test_주문_생성_실패_빈_장바구니(self, api_client, user):

@pytest.mark.django_db
class Test결제뷰:
@patch("payments.mixins.KakaoPayService.request_payment")
def test_결제_요청(self, mock_request_payment, api_client, user, order):
mock_request_payment.return_value = {
"next_redirect_pc_url": "http://test-redirect-url.com",
"next_redirect_mobile_url": "http://test-redirect-url.com",
"next_redirect_app_url": "http://test-redirect-url.com",
"tid": "test_transaction_id",
}
api_client.force_authenticate(user=user)
url = reverse("payments:payment")
response = api_client.post(url)
print(f"\n결제 요청 테스트")
print(f"URL: {url}")
print(f"응답 상태 코드: {response.status_code}")
print(f"응답 데이터: {response.data}")
assert response.status_code == status.HTTP_201_CREATED
assert "payment" in response.data
assert "next_redirect_pc_url" in response.data

@patch("payments.mixins.KakaoPayService.approve_payment")
def test_결제_승인_성공(self, mock_approve_payment, api_client, user, payment):
api_client.force_authenticate(user=user)
mock_approve_payment.return_value = {"amount": {"total": 10000}}
api_client.force_authenticate(user=user)
url = reverse("payments:payment")
response = api_client.get(url, {"result": "success", "pg_token": "test_token"})
print(f"\n결제 승인 테스트")
print(f"URL: {url}")
print(f"응답 상태 코드: {response.status_code}")
print(f"응답 데이터: {response.data}")
assert response.status_code == status.HTTP_200_OK
assert response.data["detail"] == "결제가 성공적으로 완료되었습니다."

def test_결제_취소(self, api_client, user, payment):
api_client.force_authenticate(user=user)
url = reverse("payments:payment")
response = api_client.get(url, {"result": "cancel"})
print(f"\n결제 취소 테스트")
print(f"URL: {url}")
print(f"응답 상태 코드: {response.status_code}")
print(f"응답 데이터: {response.data}")
assert response.status_code == status.HTTP_200_OK
assert "결제 과정이 취소되었습니다" in response.data["detail"]

@patch("payments.mixins.KakaoPayService.refund_payment")
def test_결제_환불(self, mock_refund_payment, api_client, user, completed_payment):
mock_refund_payment.return_value = {"status": "CANCEL_PAYMENT"}
completed_payment.paid_at = timezone.now()
completed_payment.save()
api_client.force_authenticate(user=user)
url = reverse("payments:payment-cancel", args=[completed_payment.order.id])
response = api_client.delete(url)
print(f"\n결제 환불 테스트")
print(f"URL: {url}")
print(f"응답 상태 코드: {response.status_code}")
print(f"응답 데이터: {response.data}")
assert response.status_code == status.HTTP_200_OK
assert "결제가 성공적으로 환불되었습니다" in response.data["detail"]


@pytest.mark.django_db
class Test영수증뷰:
Expand Down
5 changes: 5 additions & 0 deletions payments/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
PaymentView.as_view(),
name="payment",
),
path(
"payments/<int:order_id>/cancel/",
PaymentView.as_view(),
name="payment-cancel",
),
# 영수증 관련 URLs
path("receipts/", ReceiptView.as_view(), name="receipt-list"),
path(
Expand Down
42 changes: 33 additions & 9 deletions payments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.permissions import IsAuthenticated
from drf_spectacular.utils import extend_schema, extend_schema_view

from .models import CartItem, Order, UserBillingAddress
from .models import CartItem, Order, UserBillingAddress, Payment
from .serializers import (
CartItemSerializer,
CartSerializer,
Expand Down Expand Up @@ -254,7 +254,7 @@ class PaymentView(PaymentMixin, OrderMixin, generics.GenericAPIView):
[POST /payments/]: 현재 진행 중인 주문에 대한 결제를 생성하고 카카오페이 결제를 요청합니다.
[GET /payments/]: 카카오페이 결제 결과를 처리합니다.
[DELETE /payments/]: 결제를 취소하고 환불을 처리합니다.
[DELETE /payments/<order_id>/cancel/]: 결제를 취소하고 환불을 처리합니다.
"""

serializer_class = PaymentSerializer
Expand All @@ -265,7 +265,7 @@ def get_queryset(self):

@transaction.atomic
def post(self, request):
order = self.get_queryset().select_for_update().first()
order = self.get_queryset().filter(order_status="pending").select_for_update().first()
if not order:
return Response(
{"detail": "진행 중인 주문이 없습니다."},
Expand All @@ -290,14 +290,35 @@ def post(self, request):

@transaction.atomic
def get(self, request):
order = self.get_queryset().select_for_update().first()
order = (
self.get_queryset()
.filter(order_status="pending")
.select_for_update()
.first()
)
if not order:
return Response(
{"detail": "진행 중인 주문이 없습니다."},
status=status.HTTP_404_NOT_FOUND,
)

payment = self.get_payment(request.user, order=order)
# 가장 최근의 pending payment를 가져오고 나머지는 취소 처리
payments = list(
Payment.objects.filter(order=order, payment_status="pending").order_by(
"-created_at"
)
)
if not payments:
return Response(
{"detail": "해당 주문에 대한 대기 중인 결제를 찾을 수 없습니다."},
status=status.HTTP_404_NOT_FOUND,
)
payment = payments[0]
# 가장 최근의 pending payment를 제외한 나머지 payment를 취소 처리
if len(payments) > 1:
Payment.objects.filter(id__in=[p.id for p in payments[1:]]).update(
payment_status="cancelled"
)

result = request.GET.get("result")
pg_token = request.GET.get("pg_token")
Expand Down Expand Up @@ -339,11 +360,14 @@ def get(self, request):
)

@transaction.atomic
def delete(self, request):
order = self.get_queryset().select_for_update().first()
if not order:
def delete(self, request, order_id):
try:
order = (
self.get_queryset().filter(order_status="completed").get(id=order_id)
)
except Order.DoesNotExist:
return Response(
{"detail": "진행 중인 주문이 없습니다."},
{"detail": "결제된 주문을 찾을 수 없습니다."},
status=status.HTTP_404_NOT_FOUND,
)

Expand Down

0 comments on commit d4e173c

Please sign in to comment.