如何为django-oscar添加一些付款方式

时间:2018-03-18 15:51:41

标签: django django-oscar

我已经将cash_on_delivery付款方式添加到我的项目中。但我想再增加一个方法。我怎么做此时我有这样的结账视图代码:

class PaymentDetailsView(PaymentDetailsView):
template_name = 'checkout/payment-details.html'
template_name_preview = 'checkout/preview.html'

def get_context_data(self, **kwargs):

    ctx = super(PaymentDetailsView, self).get_context_data(**kwargs)
    ctx['signature'] = gateway.hmac_gen(ctx)
    ctx['amount'] = '%s' % ctx['order_total'].incl_tax
    ctx['price'] = '%s' % ctx['basket'].lines.all()[0].price_incl_tax
    ctx['source'] = ctx

    return ctx

# def handle_payment_details_submission(self, request):
#     # Validate the submitted forms
#     shipping_address = self.get_shipping_address(
#         self.request.basket)
#     address_form = BillingAddressForm(shipping_address, request.POST)
#
#     if address_form.is_valid():
#         address_fields = dict(
#             (k, v) for (k, v) in address_form.instance.__dict__.items()
#             if not k.startswith('_') and not k.startswith('same_as_shipping'))
#         self.checkout_session.bill_to_new_address(address_fields)
#         return self.render_preview(request, billing_address_form=address_form)
#
#     # Forms are invalid - show them to the customer along with the
#     # validation errors.
#     return self.render_payment_details(
#         request, billing_address_form=address_form)

def handle_payment(self, order_number, total, **kwargs):
    reference = gateway.create_transaction(order_number, total)
    source_type, is_created = SourceType.objects.get_or_create(
        name='Cash on Delivery')
    source = Source(
        source_type=source_type,
        currency=total.currency,
        amount_allocated=total.incl_tax,
        amount_debited=total.incl_tax
    )
    self.add_payment_source(source)
    self.add_payment_event('Issued', total.incl_tax, reference=reference)

也许我可以通过添加运费方式进行付款?

1 个答案:

答案 0 :(得分:5)

Docdata对我的实施起了很好的启发。

多种付款方式,意味着您首先必须在某处列出所有方法(以便轻松启用/禁用或其他),然后确定适用于给定用户的方法(取决于货币,购物篮大小,位置等)所有这些都可以通过扩展PaymentMethodView

来处理

<强> settings.py

from oscar import get_core_apps as get_oscar_apps

...
INSTALLED_APPS = [
    ...
] + get_oscar_apps([
    'checkout',
]) + [
    'cashondelivery', # https://github.com/ashishnitinpatil/django-oscar-cash-on-delivery
    'custom_payment', # my local app for custom payment gateway
]

OSCAR_PAYMENT_METHODS = (
    ('cod', _('Cash on delivery')),
    ('custom_payment', _('Credit / Debit card')),
)
...

<强>结帐/ forms.py

from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _


class PaymentMethodForm(forms.Form):
    """
    Extra form for the custom payment method.
    """
    payment_method = forms.ChoiceField(
        label=_("Select a payment method"),
        choices=settings.OSCAR_PAYMENT_METHODS,
        widget=forms.RadioSelect()
    )


def get_payment_method_display(payment_method):
    return dict(settings.OSCAR_PAYMENT_METHODS).get(payment_method)

<强>结帐/ views.py

from django.utils import six
from django.conf import settings
from django.shortcuts import redirect
from django.views.generic import FormView
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse, reverse_lazy

from . import forms
from custom_payment import facade
from cashondelivery import gateway as cod_gateway

from oscar.apps.checkout import exceptions
from oscar.core.loading import get_model, get_class


Source = get_model("payment", "Source")
SourceType = get_model("payment", "SourceType")
RedirectRequired = get_class("payment.exceptions", "RedirectRequired")
UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
OscarPaymentMethodView = get_class("checkout.views", "PaymentMethodView")
OscarPaymentDetailsView = get_class("checkout.views", "PaymentDetailsView")
OscarShippingMethodView = get_class("checkout.views", "ShippingMethodView")

# Sample pre-condition
class CheckCountryPreCondition(object):
    """DRY class for check country in session pre_condition"""

    def get_pre_conditions(self, request):
        if 'check_country_in_session' not in self.pre_conditions:
            return self.pre_conditions + ['check_country_in_session']
        return super().get_pre_conditions(request)

    def check_country_in_session(self, request):
        if request.session.get('country', None) is None:
            raise exceptions.FailedPreCondition(
                    url=reverse('checkout:shipping-address'),
                )


# Inspired by https://github.com/django-oscar/django-oscar-docdata/blob/master/sandbox/apps/checkout/views.py
class PaymentMethodView(CheckCountryPreCondition, OscarPaymentMethodView, FormView):
    """
    View for a user to choose which payment method(s) they want to use.

    This would include setting allocations if payment is to be split
    between multiple sources. It's not the place for entering sensitive details
    like bankcard numbers though - that belongs on the payment details view.
    """
    template_name = "checkout/payment_method.html"
    step = 'payment-method'
    form_class = forms.PaymentMethodForm
    success_url = reverse_lazy('checkout:payment-details')

    pre_conditions = [
        'check_basket_is_not_empty',
        'check_basket_is_valid',
        'check_user_email_is_captured',
        'check_shipping_data_is_captured',
        'check_payment_data_is_captured',
    ]
    skip_conditions = ['skip_unless_payment_is_required']

    def get(self, request, *args, **kwargs):
        # if only single payment method, store that
        # and then follow default (redirect to preview)
        # else show payment method choice form
        if len(settings.OSCAR_PAYMENT_METHODS) == 1:
            self.checkout_session.pay_by(settings.OSCAR_PAYMENT_METHODS[0][0])
            return redirect(self.get_success_url())
        else:
            return FormView.get(self, request, *args, **kwargs)

    def get_success_url(self, *args, **kwargs):
        # Redirect to the correct payments page as per the method (different methods may have different views &/or additional views)
        return reverse_lazy('checkout:preview')

    def get_initial(self):
        return {
            'payment_method': self.checkout_session.payment_method(),
        }

    def form_valid(self, form):
        # Store payment method in the CheckoutSessionMixin.checkout_session (a CheckoutSessionData object)
        self.checkout_session.pay_by(form.cleaned_data['payment_method'])
        return super().form_valid(form)

class PaymentDetailsView(CheckCountryPreCondition, OscarPaymentDetailsView):

    def handle_payment(self, order_number, order_total, **kwargs):
        method = self.checkout_session.payment_method()
        if method == 'cod':
            return self.handle_cod_payment(order_number, order_total, **kwargs)
        elif method == 'custom_payment':
            return self.handle_custom_payment_payment(order_number, order_total, **kwargs)
        else:
            raise PaymentError(_('Bad payment method in handle_payment!'))

    def handle_cod_payment(self, order_number, total, **kwargs):
        reference = cod_gateway.create_transaction(order_number, total)
        source_type, is_created = SourceType.objects.get_or_create(
            name='Cash on delivery')
        source = Source(
            source_type=source_type,
            currency=total.currency,
            amount_allocated=total.incl_tax,
            amount_debited=total.incl_tax
        )
        self.add_payment_source(source)
        self.add_payment_event('awaiting-delivery', total.incl_tax, reference=reference)

    def handle_custom_payment_payment(self, order_number, total, **kwargs):
        submission = self.build_submission(order_number=order_number, **kwargs)

        # Save required payment gateway data
        # also validates that we have all we need
        custom_payment_payment_data = facade.get_gateway_url_and_parameters(
            submission, self.request.build_absolute_uri, live=settings.custom_payment['LIVE'])

        # Any raised exceptions are handled by the PaymentDetail.submit() code.
        custom_payment_order = facade.create_order(
            order_number=order_number,
            amount=total,
            payment_data=custom_payment_payment_data
        )
        # record payment data to session to double verify things on success / failure
        self.set_custom_payment_payment_data(custom_payment_payment_data, custom_payment_order)

        source = Source(
            source_type=facade.get_source_type(),
            currency=total.currency,
            amount_allocated=total.incl_tax,   # amount_* field depends on type of transaction.
            reference=custom_payment_order.id
        )
        self.add_payment_source(source)

        # Also record payment event.
        # This will be visible in the Dashboard
        self.add_payment_event('pre-auth', total.incl_tax, reference=custom_payment_order.id)

        # Regardless of whether the order is paid, write it in the database before redirecting.
        # Oscar actually skips this when redirecting the user to the payment provider.
        self._save_order(order_number, submission)

        # Redirect the user to the payment provider's gateway.
        raise RedirectRequired(facade.get_payment_url())

    def _save_order(self, order_number, submission):
        # Finalize the order that PaymentDetailsView.submit() started
        # If all is ok with payment, try and place order
        logger.info("Order #%s: payment started, placing order", order_number)

        try:
            # Call OrderPlacementMixin.handle_order_placement()
            return self.handle_order_placement(
                order_number, submission['user'], submission['basket'],
                submission['shipping_address'], submission['shipping_method'],
                submission['shipping_charge'], submission['billing_address'],
                submission['order_total'], **(submission['order_kwargs'])
            )
        except UnableToPlaceOrder as e:
            # It's possible that something will go wrong while trying to
            # actually place an order.  Not a good situation to be in as a
            # payment transaction may already have taken place, but needs
            # to be handled gracefully.
            logger.error("Order #%s: unable to place order - %s", order_number, e, exc_info=True)
            msg = six.text_type(e)
            self.restore_frozen_basket()
            return self.render_to_response(self.get_context_data(error=msg))

    # Can't update CheckoutSessionMixin nicely without causing trouble,
    # hence dumping the overridden / new methods from that class here
    def check_payment_data_is_captured(self, request):
        # We don't collect payment data by default so we don't have anything to
        # validate here. If your shop requires forms to be submitted on the
        # payment details page, then override this method to check that the
        # relevant data is available. Often just enforcing that the preview
        # view is only accessible from a POST request is sufficient.
        if not self.checkout_session.payment_method():
            raise FailedPreCondition(
                url=reverse('checkout:payment-method'),
                message=_("Please select a payment method for your order!")
            )

    def set_custom_payment_payment_data(self, payment_data, custom_payment_order):
        self.request.session['custom_payment'] = payment_data
        self.request.session['custom_payment_order_id'] = custom_payment_order.id
        self.request.session['ongoing_online_payment'] = True
        self.request.session.pop('custom_payment_error', None)

    def get_context_data(self, **kwargs):
        ctx = super(PaymentDetailsView, self).get_context_data(**kwargs)
        ctx.update({'payment_method': self.checkout_session.payment_method()})
        return ctx

    def send_confirmation_message(self, order, *args, **kwargs):
        # In case of custom_payment, delay sending the order confirmation till payment success!
        if not self.checkout_session.payment_method() == 'custom_payment':
            super(PaymentDetailsView, self).send_confirmation_message(order, *args, **kwargs)

您可能会忽略custom_payment的详细信息,因为这是我的项目/支付网关特有的内容,您可能会对此有不同的要求。我仍然留下了一大块东西,以便你可以相应地思考这些事情。

PS - 请注意我使用的是Python3.6,因此在以前的Python版本(特别是2.7版)中,一些内容(如super)可能无法正常工作。

<强>更新

我的templates/checkout/payment_method.html

{% extends "checkout/checkout.html" %}
{% load i18n %}

{% block checkout_title %}{{ form.payment_method.label }}{% endblock %}

{% block checkout_nav %}
    {% include 'checkout/nav.html' with step=3 %}
{% endblock %}

{% block content %}
    {% if error %}
        <div class="alert alert-error">
            {{ error }}
        </div>
    {% endif %}

    <form action="" method="post">
        {% csrf_token %}

        {% if form.payment_method.errors %}{{ form.payment_method.errors }}{% endif %}
        <div class="radio">
            <label for="id_payment_method_0">
                <input id="id_payment_method_0" name="payment_method" type="radio" value="cod" required="">{% trans "Cash on delivery" %}
            </label>
        </div>
        <div class="radio">
            <label for="id_payment_method_1">
                <input id="id_payment_method_1" name="payment_method" type="radio" value="custom_payment" required="">{% trans "Credit / Debit Card" %}
            </label>
        </div>
        <p><button type="submit" class="btn btn-large btn-primary">{% trans "Continue to confirmation" %}</button>

    </form>
{% endblock %}
相关问题