배프의 오지랖 online shop 결제파트 로직 정리

SINHOLEE·2020년 8월 21일
1

책을 읽으면서 유독 결재파트의 코드와 로직의 설명이 불친절하고, 중간중간 빠진로직, 등이 있어 이를 내식대로 수정하고 이해하며 정리한 것을 기록한 것. 개인의 생각과 배프의 코드가 혼재되어있음을 미리 알림

1판기준 317p~340p의 내용에 관한 글.

내기준에서 web application 설계에 있어서 대 전제

  • 어떤데이터를 생성하거나, 등록할때마다, 에러가 나타나는 구간이 있는지 검사 후 적절한 에러 작업해야함
  • 외부데이터를 연결해서 사용할 경우에도 같은맥락으로 연결되어있는 데이터와 내가 사용하는 db에서의 데이터가 잘 연동되어 있는 것인지 확인하는 작업이 필요
  • 변수명, 함수명은 플랫폼이 변경되더라도 최대한 통일하여 헷갈리지 않게 제어하는것이 좋다.

결재파트 본문

  1. 카트정보화면에서 check-out버튼을 클릭한다.

  2. GET method로 접근하고, order/order_create view를 실행한다.

  3. formcart 인스턴스를 생성하고 created.html에 전달한다.

  4. formOrderCreateForm이므로 order table을 생성할 데이터를 input으로 받는다.

  5. place order버튼(order/created.html)을 누르면 form에 등록되어있는 submit event가 발생한다.

  6. submit event 를 바인딩 하던 checkout.js가 발동하여 ajax function을 실행한다.

    1. AjaxCreateOrder 함수를 실행하여 order_id를 반환한다.

      • order/urls.py에서 order_create_url으로 바인딩 된 AjaxCreateOrder class를 찾는다.

      • OrderCreateAjaxView클래스의 post method로 들어온 요청을 수행한다.

        • requestuser가 로그인 상태인지 검사한다.

        • cart 인스턴스에 담겨있는 장바구니 정보 불러오기

        • OrercreateForm에서 받아온 form정보 인스턴스화 하기

        • formvalidation checkorder table에 등록

          order = {
          	first_name,
          	last_name,
          	email,
          	address,
          	postal_code,
          	city,
          	created, // form에 없음
          	updated, // form에 없음
          	paid, // form에 없음
          	coupon, // form에 없음, cart에서 받음
          	discount, // form에 없음, cart에서 받음
          }
        • 만약 cartcoupon정보가 있다면, order에 저장한다. 이때 db에 지연저장을 위해 아래와 같이 처리한다.

           if form.is_valid():
                      order = form.save(commit=False) # 아직 커밋 하지 말라! 
                      if cart.coupon:
                          order.coupon = cart.coupon
                          order.discount = cart.coupon.amount
                      order = form.save()
    - `order` table에 생성된 `order` instance와 관계테이블로 구성된 `OrderItem `table에 `cart`정보(`order_id`, `product`, `price`, `quantity`) 등록

      ```python
      OrderItem.objects.bulk_create(
                      [OrderItem(
                          order=order, # order instance를 등록할시 알아서 id로 적용
                          product=item['product'], 
                          price=item['price'], 
                          qunatity=item['quantity']) for item in cart]
                      )
      ```

    - `cart` 인스턴스 삭제 

    - `order_id`를 반환하므로써 `http`통신에 전달하는 데이터 양을 줄이고, 해당 `key`로 저장했던 모든 데이터를 불러올 수 있도록 설계함
  1. 1의 과정에서 order_id를 제대로 반환받았으면, form에 없었던 amountpay_methodhtml selector를 이용하여 받아온 뒤 AjaxStoreTransaction(e, order_id, amount, pay_method)을 실행한다.

    • order/urls.pyorder_checkout으로 바인딩된 OrderCheckoutAjaxView 클래스를 실행한다.

      • requestuser가 로그인 상태인지 검사한다.

      • order_id 를 통해, order 객체를 불러온다.

      • order 객체를 통해 amount 값을 찾는다.

      • order, amount를 기준으로 OrderTransaction table에 등록한다. 이때 추가로 등록되는 데이터는 merchant_order_id로, iamport에서 결제에 대한 get요청을 할때 필요한 key값이라고 생각하면 된다.

      • 등록할때는 orderTransaction.objects.create_new()함수를 이용한다.

         merchant_order_id = OrderTransaction.objects.create_new(
                        order=order,
                        amount=amount,
                        pay_method=pay_method
                    )
      • order/models.py에는 OrderTransactionManager클래스를 이용하여 orderTransaction model의 objects 객체에 추가적인 함수를 등록하도록 한다.

        # OrderTransactionManager 전문 
        class OrderTransactionManager(models.Manager):
            def create_new(self, order, amount, pay_method, success=None, transaction_status=None):
                if not order:
                    raise ValueError('주문 오류')
                order_hash = hashlib.sha1(str(order.id).encode('utf-8')).hexdigest()  # 16진법으로 바꾼 order.id를 utf-8로 인코딩한 해시값
                email_hash = str(order.email).split('@')[0]  # 이메일의 아이디 부분만 사용
                final_hash = hashlib.sha1((order_hash + email_hash).encode('utf-8')).hexdigest()[:10]  # 0~9index까지의 16진수만 받자...
                merchant_order_id = '%s' % final_hash
        
                # validation기능의 함수 실행
                payments_prepare(merchant_order_id, amount)
        
                transaction = self.model(
                    order=order,
                    merchant_order_id=merchant_order_id,
                    amount=int(amount),
                    pay_method=pay_method,
                )
        
                if success is not None:
                    transaction.success = success
                    transaction.transaction_status = transaction_status
        
                try:
                    transaction.save()
                except Exception as e:
                    print('save error: ', e)
                return transaction.merchant_order_id        
        
            def get_transaction(self, merchant_order_id):
                result = find_transcaction(merchant_order_id)
                if result['status'] == 'paid':
                    return result
                return None
        
      • OrderTransaction에 저장되는 데이터는 다음과 같다.

        orderTransaction = {
        	order_id, // foreginKey
        	merchant_order_id,
        	amount,
        	pay_method,
        	
        	transaction_id, // imp_id?? 결재 SDK에서 제공?하는 id인듯 아직 이시점에서는 null값으로 유지된다. OrderImAjaxView에서 등록되는 로직
        	created, // 생성날짜
        }
      • 아직 transaction_id가 생성되지 않은 상태에서 db등록과정은 일단락 하고, merchant_order_id를 반환한다.

    • 현재 submit event로 진행된 후 가지고 있는 데이터는 merchant_uid(merchant_order_id), name, order_id, buyer_name, buyer_email, amount가 있다. 이 데이터로 iamport API 를 이용할 수 있다.

  2. IMP.request_pay()함수를 실행하면 브라우저에 결제화면이 켜지면서 결제를 하면 된다.

  3. 결제가 완료되면, promise객체를 callback한다.

  4. promise에 담겨져 있는 정보와, 우리 db에 있는 정보가 동일한지 체크하는 작업을 한다.

  5. ImpTransaction(e, order_id, response.merchant_uid, response.imp_uid, response.paid_amount); 를 실행한다.

    • order/urls.py에서 order_validation_url으로 바인딩 된 OrderImpAjaxView class를 찾는다

      class OrderImpAjaxView(View):
          def post(self, request, *args, **kwargs):
              if not request.user.is_authenticated:
                  return JsonResponse({'autenticated':False}, status=403)
              
              order_id = request.POST.get('order_id')
              order = Order.objects.get(id=order_id)
              merchant_id = request.POST.get('merchant_id')
              imp_id = request.POST.get('imp_id')
              amount = request.POST.get('amount')

      여기서 변수에 할당된 값들은 iamportpromise에서 반환된 값들이다.

    • 해당 정보로부터 transaction 인스턴스를 불러온 뒤, transaction_idimp_id를 저장시킨다.

       try:
                  trans = OrderTransaction.objects.get(
                      order=order,
                      merchant_order_id=merchant_id,
                      amount=amount
                  )
              except:
                  trans = None
      
              if trans is not None:
                  trans.transaction_id = imp_id
                  trans.success = True
                  trans.save()
                  order.paid = True
                  order.save()
      

      이때, trans.save() 로 인해 order/models.py/order_payment_validation()가 발동되어 자동적으로 거래에 대한 validation을 평가한다.

      • 결제가 완료 되었는지 확인
      • 결제가 된 거래라면, iamport에서 가지고 있는 결과와, 내가 가지고 있는 결과가 똑같은지 확인
    • 문제가 없다면, $(location).attr('href', location.origin+order_complete_url+'?order_id='+order_id) 이 코드로 리다이렉션이 되어야 한다.

  6. false를 반환하며 submit event에 바인딩된 작업이 종료된다.

이상한 점

  1. file: order/models.py ,class: OderTransactionManager / function: create_new에서 인자로 successsuccess_statusNone으로 설정했는데, 코드에서는 쓰질 않는다. 이때 이 인자의 역할이 무엇인지 궁금하다. 설명도 없다.
  2. pay_method도 table에 저장 안했네 책 퀄리티 좋던데 왜 이 부분만 유독 부족한거같지?
profile
엔지니어로 거듭나기

0개의 댓글