인프런 커뮤니티 질문&답변

bluebamus님의 프로필 이미지

작성한 질문수

파이썬/장고로 결제 시작하기 (Feat. 아임포트) - 기본편

admin 주문결제 취소 구현

cancle에 대해 문의 드립니다.

작성

·

57

0

코드의 내용을 보자면 admin에서 action을 이용해 여러개의 쿼리셑을 받아 각각을 한번씩 처리합니다.

@admin.display(description=f"지정 주문결제를 취소합니다.")
    def make_cancel(self, request, queryset):
        for order in queryset:
            order.cancel("관리자가 주문결제를 취소했습니다.")
        self.message_user(request, f"{queryset.count()}개의 주문결제를 취소했습니다.")

그런데 Order의 cancel()에서 orderpayment_set, 즉 manytomany 중계 테이블내 모든 관련한 결제 내역을 가져와 cancel을 시도합니다.

 

결제 가능 여부를 확인하는 코드에서 보자면

def can_pay(self) -> bool:
        return self.status in (self.Status.REQUESTED, self.Status.FAILED_PAYMENT)

두 가지 조건만 있습니다. 실패했거나, 성공한 경우 입니다. 때문에 DB에는 관련한 주문이 여러간의 실패 + 1건의 성공의 이력인데 굳이 모든 이력을 가져와 cancel을 하는 이유가 무엇인지 알고 싶습니다.

 

 

 

 

 

 

답변 3

0

이진석님의 프로필 이미지
이진석
지식공유자

설명하신 내용이 맞습니다.

말씀하신 것처럼 결제가 된 건에 대해서만 취소 요청을 하실 수도 있습니다.

order.orderpayment_set.all() 은 한 order 내의 모든 결제시도(payment) 목록을 조회합니다. 아래의 코드는 한 order 내의 모든 결제시도에 대해서, 별도의 상태조회없이 cancel을 시도합니다.

for payment in self.orderpayment_set.all():
payment.cancel(reason=reason)

위와 같이 코드를 작성한 이유는 포트원 측에서는 결제가 되었는 데 네트워크 이슈, 비정상 종료 등의 이유로 웹훅을 받지 못해서, 우리 서비스에서는 결제완료 상태로 갱신되지 않았을 가능성이 아주 적은 확률이지만 있을 수도 있다고 생각했기 때문입니다. 그래서 러프하게 모든 결제시도 건에 대해서 cancel을 요청한 것이었습니다.

다수의 cancel api 요청으로 인해, 요청건수에 비례해서 처리시간이 늘어날 수 있는 데요. 이때 병렬로 요청을 처리하시면 처리시간을 대폭 줄이실 수 있는 데요. asyncio를 활용하실 수도 있구요. 그런데 이는 ASGI 방식으로 장고 구동 방식을 변경해야하구요. 기존의 WSGI 방식 구동에서는 gevent 라이브러리를 활용해서 병렬로 요청을 처리하실 수 있습니다.

0

이진석님의 프로필 이미지
이진석
지식공유자

안녕하세요.

admin의 action은 모든 Row에 대해서 cancel을 시도하는 것이 아니라, 아래처럼 선택된 레코드 목록만이 queryset 으로 전달됩니다. 그러니 선택된 항목에 대해서만 포트원 측으로 결제취소 요청을 보냅니다. 포트원 측에서 해당 결제건을 취소하면 주문상태는 cancelled 로 변경됩니다.

image.png

결제 상태는 아래 7가지이구요. 강의에서는 유저가 초기 결제 시에 "결제 가능 여부"를 결정하기 위해, can_pay 메서드를 통해 주문요청/결제실패 상태에서만 결제를 허용토록 했습니다. 이는 예시일 뿐 절대적인 것은 아니구요. 서비스에 맞춰 변경하시면 됩니다. :-)

image.png

살펴보시고, 댓글 남겨주세요.

화이팅입니다. :-)

bluebamus님의 프로필 이미지
bluebamus
질문자

말씀해 주신 부분은 알고 있습니다.

제가 이해하고 있는게 혹 잘못된 것인지 알고 싶어 다시 제 궁금증을 정리해 말씀 드립니다.

make_cancel에서 여러개 선택된 항목들은

def make_cancel(self, request, queryset):
        for order in queryset:
            order.cancel("관리자가 주문결제를 취소했습니다.")
        self.message_user(request, f"{queryset.count()}개의 주문결제를 취소했습니다.")


를 통해 각각의 주문을 cancel 시도 합니다.

 

order 모델에 있는 cancel() 함수에서

 def cancel(self, reason=""):
        for payment in self.orderpayment_set.all():
            payment.cancel(reason=reason)

위 코드와 같이 호출을 하는데, 하나의 주문에 대해

왜 self.orderpayment_set.all()를 사용해 모든 하나의 주문에 대한 모든 결제시도 내역을 가져오는지 이해가 잘 되지 않습니다.

코드를 확인하니 orderpay 모델은 order_pay 뷰에 의해 만들어지며, 주문요청과 결제실패 상태에서만 생성될 수 있습니다.

그렇다면 orderpayment_set.all() 코드는 cancel()에서 한 주문에 대한 성공 및 결제실패에 대한 상태도 가져와 cancel을 시도하게 되는것인가요?

 

아무리 생각해봐도

def cancel(self, reason=""):
        for payment in self.orderpayment_set.all():
            payment.cancel(reason=reason)

 

위 코드에서 orderpayment_set.all()을 사용하여 모든 결과에 대해 cancel을 시도하는 목적이 이해가 잘 되지 않습니다.

 

0

안녕하세요, 인프런 AI 인턴입니다. bluebamus님.

주문 결제 취소 로직에서 모든 결제 내역을 가져와 취소를 시도하는 이유는 다음과 같은 몇 가지 이유가 있을 수 있습니다:

  1. 일관성 유지: 여러 번의 결제 시도가 있었던 경우, 일부 방식에서는 성공하지 않았던 결제가 주문의 일부분으로 인식될 수 있습니다. 따라서 모든 결제 정보를 명시적으로 취소하는 것이 데이터의 일관성을 확보하는 방법이 될 수 있습니다.

  2. 미래의 확장 가능성: 예를 들어, 향후 기능 확장을 통해 결제 시스템이 여러 개의 결제 수단을 지원하게 되면, 각각의 결제 이력을 개별적으로 관리하는 것이 필요해질 수 있습니다. 이 경우를 대비하여 모든 내역을 취소하도록 구현하는 것은 설계의 유연성을 높이는 방법일 수 있습니다.

  3. 보안 및 기록 관리: 모든 결제 시도에 대해 명확하게 취소 기록을 남기면, 나중에 발생할 수 있는 분쟁 상황에서 확실한 증거를 제공할 수 있습니다. 이는 고객 서비스 측면에서도 긍정적인 영향을 미칠 수 있습니다.

혹시라도 주문 취소 기능과 관련하여 더 구체적인 구현이나 의문이 있으시면, 강의에서 제공하는 내용이나 관련 문서를 참고해주세요.


여러 유사한 질문과 답변을 추가로 참조할 수 있습니다. 관련 문의가 있다면 아래 링크도 도움이 될 수 있습니다:

  1. 결제 취소 로직 관련 질문
  2. 다중 결제 시스템에서의 처리 방식

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.