Art*_*Goh 2 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)
Run Code Online (Sandbox Code Playgroud)
也许我可以像增加运输方式那样付款?
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')),
)
...
Run Code Online (Sandbox Code Playgroud)
checkout / 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)
Run Code Online (Sandbox Code Playgroud)
checkout / 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)
Run Code Online (Sandbox Code Playgroud)
您可能会忽略详细信息,custom_payment因为那是我的项目/付款网关所特有的,您可能对此有不同的要求。我仍然保留了其中的大部分内容,以便您可以相应地考虑这些内容。
PS-请注意,我使用的是Python3.6,因此super在以前的Python版本(特别是2.7)中,有些事情(如)可能无法按此处所述进行操作
更新资料
我的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 %}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1284 次 |
| 最近记录: |