在django-oscar中集成包含重定向的付款方法

7

我正在使用django-oscar框架开发一个购物网站,实际上我正在使用他们的沙箱网站。 我想在结账过程中添加付款功能,但问题是,我完全困惑了!

我阅读了这个链接:"Oscar's payment integration docs"

我理解了大致内容。 我还阅读了checkout应用程序中的views.py文件,但我有一些问题在网上找不到答案。

我的问题是,我应该重写或创建哪些方法/类来处理以下过程:

用户请求付款后,我应该向银行发送请求并提供所需的参数(代码中的pay_request_parameters)。

然后,他们将发送一个Id,确认我的访问,然后我应该将该Id发布到地址并将用户重定向到银行的网页。

用户成功向银行支付后,他们将通过在第一步提供的回调URL进行POST通知我。

通过这些信息,如果付款成功,我应该要求银行结算,以向我发送款项。

现在我拥有的代码执行前两个步骤,但我不知道如何处理沙箱中重定向后的过程。 这是我的代码:

from oscar.apps.checkout import views
from oscar.apps.payment import models
from oscar.apps.payment.exceptions import *
import requests
import datetime

mellat_services_url = 'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl'
start_pay_url = 'https://bpm.shaparak.ir/pgwchannel/startpay.mellat'
terminal_id = 'xxx'
username = 'xxx'
password = 'xxx'

# Subclass the core Oscar view so we can customise
class PaymentDetailsView(views.PaymentDetailsView):

    def handle_payment(self, order_number, total, **kwargs):
        # Talk to payment gateway.  If unsuccessful/error, raise a
        # PaymentError exception which we allow to percolate up to be caught
        # and handled by the core PaymentDetailsView.

        # mellat cycle start 
        local_date = str(datetime.date.today())[0:4] + str(datetime.date.today())[5:7] + str(datetime.date.today())[8:10]
        local_time = str(datetime.datetime.now().time())[0:2] + str(datetime.datetime.now().time())[3:5] + str(datetime.datetime.now().time())[6:8]
        # call bpPayRequest and get refId
        pay_request_parameters = {'terminalId': terminal_id, 'userName': username, 
                                'userPassword': password, 'orderId': order_number, 
                                'amount': total.incl_tax, 'localDate': local_date,
                                'localTime': local_time, 'additionalData': ""
                                'callBackUrl': 'mysite.com/checkout/preview/'} 

        pay_request_answer = requests.post(mellat_services_url, pay_request_parameters)

        if not pay_request_answer.split(",")[0] == 0:
            response_code = pay_request_answer.split(",")[0]
            if response_code[0] == '1':
                raise UnableToTakePayment()
            else: 
                raise PaymentError()

        requests.post(start_pay_url, pay_request_answer.split(",")[1])
        raise RedirectRequired(start_pay_url)

        # post the refId to bank and then redirect customer to the bank
        # apparently wait for the bank ( like for 10 mins ) to get the payment status
        # if the bank responded with success, the you verify the payment with a post to the bank
        # if everything was verified, tell the bank for a settlement

        # mellat cycle end

#The rest should be implemented but I dont know where I should put this
#All I know is that it should be done after the verification with the data
#sent from the bank. 

        reference = gateway.pre_auth(order_number, total.incl_tax, kwargs['bankcard'])

        # Payment successful! Record payment source
        source_type, __ = models.SourceType.objects.get_or_create(
            name="SomeGateway")
        source = models.Source(
            source_type=source_type,
            amount_allocated=total.incl_tax,
            reference=reference)
        self.add_payment_source(source)

        # Record payment event
        self.add_payment_event('pre-auth', total.incl_tax)

thanks in advance.

3个回答

3

我有一个类似的问题,我的解决方法是让银行的回调URL重定向到一个视图,该视图实现类似于以下内容:

class CustomCheckoutDone(OrderPlacementMixin, RedirectView):
"""
here we verify payment was done and place the actual order
then redirect to thank you page
"""
permanent = False

def get_redirect_url(self, pk):
    basket = Basket.objects.get(pk=self.checkout_session.get_submitted_basket_id())
    basket.strategy = CustomStrategy()
    order_number = self.checkout_session.get_order_number()
    shipping_address = self.get_shipping_address(basket)
    shipping_method = self.get_shipping_method(basket, shipping_address)
    shipping_charge = shipping_method.calculate(basket)
    billing_address = self.get_billing_address(shipping_address)
    order_total = self.get_order_totals(basket, shipping_charge=shipping_charge)
    order_kwargs = {}
    # make sure payment was actually paid
    CustomPayment.objects.get(order_number=order_number, payed_sum=str(float(order_total.incl_tax)))
    user = self.request.user
    if not user.is_authenticated():
        order_kwargs['guest_email'] = self.checkout_session.get_guest_email()
    self.handle_order_placement(
        order_number, user, basket, shipping_address, shipping_method,
        shipping_charge, billing_address, order_total, **order_kwargs
    )
    return '/checkout/thank-you/'

1
在@ori Hoch的代码基础上,您需要使用Applicator().apply(basket, request.user, request)将优惠应用于购物篮。 - baher

3

好的,我遇到了同样的问题,并通过以下解决方案:

当我们重定向到支付网关时,应该有批准、拒绝和取消的回调URL。

首先,我们应该将这些添加到settings.py中。

SESSION_COOKIE_SAMESITE = None
SESSION_COOKIE_DOMAIN = 'localhost' #change it to your domain in prod env.

根据文档,我们需要在付款完成后进行一些额外的操作:
请设置回调URL:
path('gateway/', PaymentReturnURL.as_view(), name="gateway") 以上内容均正确。请参考:https://django-oscar.readthedocs.io/en/2.0.4/howto/how_to_integrate_payment.html
from django.shortcuts import render
from oscar.apps.checkout.mixins import OrderPlacementMixin
from django.views.generic.base import TemplateView
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.shortcuts import redirect
from django.urls.base import reverse_lazy
from oscar.core.loading import  get_model
from datetime import datetime
from oscar.apps.payment import models
from xml.etree import ElementTree


Basket = get_model('basket', 'Basket')


@method_decorator(csrf_exempt, name='dispatch')
class PaymentReturnURL(OrderPlacementMixin,TemplateView):
    success_url = reverse_lazy('checkout:thank-you')
    basket_url = reverse_lazy('basket:summary')
    

    def get_success_url(self):
        return self.success_url

    def get_order_details(self):
       
        basket = Basket.objects.get(pk=self.checkout_session.get_submitted_basket_id())
        basket.strategy = self.request.strategy
        order_number = self.checkout_session.get_order_number()
        shipping_address = self.get_shipping_address(basket)
        shipping_method = self.get_shipping_method(basket, shipping_address)
        shipping_charge = shipping_method.calculate(basket)
        billing_address = self.get_billing_address(shipping_address)
        order_total = self.get_order_totals(basket, shipping_charge=shipping_charge)
        order_kwargs = {}

       
        return {
            "basket": basket,
            "order_number": order_number,
            "shipping_address": shipping_address,
            "shipping_method": shipping_method,
            "shipping_charge": shipping_charge,
            "billing_address": billing_address,
            "order_total": order_total,
            "order_kwargs": order_kwargs,
        }

    def get(self, request, *args, **kwargs):

        return redirect(self.get_success_url())

    def post(self, request, *args, **kwargs):
       
        
        #for my case it was XML I needed to parse, after proceding from bank, banks posts the data to your callback url
        context = {}
        data = self.request.POST.get("xmlmsg")
        xml_response = ElementTree.fromstring(data)
        for i in xml_response.iter("*"):
            context[i.tag] = i.text

         
        status = context.get("OrderStatus")
        if status == "APPROVED":
            # Payment successful! Record payment source

            user = self.request.user
            print("the user",user)
            order_details = self.get_order_details()

            
            source_type, __ = models.SourceType.objects.get_or_create(name="Name of Payment")
            source = models.Source(
                source_type=source_type,
                amount_allocated=context.get("PurchaseAmountScr"),
                reference=context.get("OrderID")) 
            
            self.add_payment_source(source)

            # Record payment event
           
            self.add_payment_event('pre-auth', float(context.get("PurchaseAmountScr")))

           
            return self.handle_order_placement(
                order_details['order_number'], user, order_details['basket'], order_details['shipping_address'],
                order_details['shipping_method'], order_details['shipping_charge'],
                order_details['billing_address'], order_details['order_total'], **order_details['order_kwargs']
            )

        #for cancel situation
        elif status == "CANCELED":
            self.restore_frozen_basket()
            return redirect("basket:summary")
         #for decline stiuation
        elif status == "DECLINED":
            self.restore_frozen_basket()
            return redirect("basket:summary")
    def restore_frozen_basket(self):
        """
        Restores a frozen basket as the sole OPEN basket.  Note that this also
        merges in any new products that have been added to a basket that has
        been created while payment.
        """
        try:
           
            fzn_basket = self.get_submitted_basket()
        except Basket.DoesNotExist:
           
            # Strange place.  The previous basket stored in the session does
            # not exist.
            pass
        else:
            fzn_basket.thaw()
            if self.request.basket.id != fzn_basket.id:
                fzn_basket.merge(self.request.basket)
                # Use same strategy as current request basket
                fzn_basket.strategy = self.request.basket.strategy
                self.request.basket = fzn_basket


提交后,您将被重定向到感谢页面。

0
另一种方法:使用支付服务的网络钩子(IPN等)来验证(并在oscar系统中添加)实际付款,并返回URL以完成订单放置过程,然后重定向到感谢页面。
因此,即使用户不返回返回URL(当我们关闭“您将在一秒钟内重定向到商家”页面时会发生!),订单也将被正确放置,因为我们也可以在网络钩子中检查订单状态,并在需要时先完成订单放置。反之亦然(先webhook,returnURL不再需要执行任何操作)。
我故意没有添加太多真正的代码,因为其他人已经提供了这些。我仍在研究如何尽可能地使用DRY来实现所有这些。

urls.py

path('payment-capture/', PaymentCaptureView.as_view(), name="payment-capture")
path('payment-return/', PaymentReturnView.as_view(), name="payment-return")

views.py

class PaymentCaptureView(OrderPlacementMixin, RedirectView):
    """
    check for successfull payment,
    finish order placement, if needed
    register payment
    notify payment service with 'ok' ;-)=
    """
    permanent = False

    def post(self, pk):
        # check if we actually really received the payment, abort otherwise
        # check if the order is already placed (via return url?)
        # if not, place the order
        # and finally add a the payment in oscar
        return 'ok'


class PaymentReturnView(OrderPlacementMixin, RedirectView):
    """
    finish order placement, redirect to thank you
    """
    permanent = False

    def get_redirect_url(self, pk):
        # check if the order is already placed
        # if not, place the order
        return reverse('checkout:thank-you')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接