├── .gitignore ├── README.md └── oscar_payments ├── __init__.py ├── apps ├── __init__.py └── checkout │ ├── __init__.py │ ├── app.py │ └── views.py ├── modules ├── __init__.py └── payment │ ├── __init__.py │ ├── base │ ├── __init__.py │ ├── app.py │ └── views.py │ ├── dummy │ ├── __init__.py │ ├── app.py │ ├── forms.py │ └── views.py │ └── paypal │ ├── __init__.py │ ├── app.py │ └── views.py └── templates ├── basket └── basket.html └── checkout ├── bankcard_billing_form.html ├── bankcard_billing_preview.html ├── paypal └── choice_button.html └── select_payment.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | .idea 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-oscar-payments 2 | ===================== 3 | 4 | pluggable payments plugin for django-oscar 5 | 6 | This module is supposed to be used to override oscar's checkout app, and it 7 | supports various pluggable reusable payment systems, that have to be based off 8 | `oscar_overrides.modules.payment.base.app`. The 9 | `oscar_overrides.modules.payment.base.views` provides some basic mixins from 10 | which to build your plugins' views. 11 | 12 | **DISCLAIMER** 13 | This code comes from a private framework I'm working on for 14 | http://www.sologroup.gs/ As such it has some things that might be sub-ideal for 15 | a standalone reusable app (such as the `OSCAR_OVERRIDES_PACKAGE` setting). I'm 16 | more than open to accepting fixes these (and bugfixes, and any reusable 17 | plugins you might want to create), and bringing the package to level. 18 | 19 | Settings 20 | -------- 21 | * `OSCAR_PAYMENT_MODULES`. A list of `(url, module name)` tuples. This is where 22 | you configure your active payment modules. An example that uses paypal, 23 | and the dummy would be this: 24 | 25 | ``` 26 | BOILER_PAYMENT_MODULES = [ 27 | (r'^paypal/', 'boilerplate.apps.store.modules.payment.paypal'), 28 | ] 29 | if DEBUG: 30 | BOILER_PAYMENT_MODULES.append( 31 | (r'^dummy/', 'boilerplate.apps.store.modules.payment.dummy')) 32 | ``` 33 | 34 | * `OSCAR_OVERRIDES_PACKAGE`. This is used by the checkout root view to find the 35 | 36 | Example Plugins 37 | --------------- 38 | There's a couple of example payment modules: 39 | 40 | * `dummy`. This module asks you to introduce test credit card data, and marks the 41 | order as paid. To be used during development. 42 | * `paypal`. This is a pluggable payment plugin for django-oscar-paypal. It 43 | requires django-oscar-paypal if you'll use it, and it uses it's urls, and 44 | templates 45 | 46 | -------------------------------------------------------------------------------- /oscar_payments/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | import importlib 12 | from django.conf import settings 13 | 14 | def try_import(module): 15 | try: 16 | return importlib.import_module(module) 17 | except ImportError: 18 | return None 19 | 20 | def shop_app_module_name(appname): 21 | module_name = '%s.%s' % (settings.OSCAR_OVERRIDES_PACKAGE, 22 | appname) 23 | module = try_import(module_name) 24 | if module is not None: 25 | return module_name 26 | module_name = 'oscar_payments.apps.%s' % appname 27 | module = try_import(module_name) 28 | if module is not None: 29 | return module_name 30 | 31 | def import_shop_app(appname, submodule=''): 32 | module_name = shop_app_module_name(appname) 33 | if module_name is not None: 34 | if submodule: 35 | module_name = "%s.%s" % (module_name, submodule) 36 | return try_import(module_name) 37 | -------------------------------------------------------------------------------- /oscar_payments/apps/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.apps 5 | :platform: Unix 6 | :synopsis: containing module for oscar app overrides 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /oscar_payments/apps/checkout/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.apps.checkout 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /oscar_payments/apps/checkout/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.apps.checkout.app 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from collections import OrderedDict 12 | import logging 13 | from django.conf import settings 14 | from django.conf.urls import url, include, patterns 15 | from oscar.apps.checkout import app 16 | from oscar_payments import try_import 17 | from oscar_payments.apps.checkout.views import PaymentDetailsView 18 | 19 | class CheckoutApplication(app.CheckoutApplication): 20 | payment_details_view = PaymentDetailsView 21 | 22 | def __init__(self, *args, **kwargs): 23 | super(CheckoutApplication, self).__init__(*args, **kwargs) 24 | self.log = logging.getLogger("%s.%s" % (self.__class__.__module__, 25 | self.__class__.__name__)) 26 | # 27 | self._modules = None 28 | 29 | def get_urls(self): 30 | base_urls = super(CheckoutApplication, self).get_urls() 31 | args = [] 32 | for module in self.modules.values(): 33 | args.append(url(module['url'], include(module['app'].urls))) 34 | return patterns('', *args) + base_urls 35 | 36 | @property 37 | def modules(self): 38 | """ 39 | Populate and return registered payment modules 40 | 41 | :return: An OrderedDict like { module_name -> { 'url': url, 42 | 'app': application }} 43 | """ 44 | if self._modules is None: 45 | self._modules = OrderedDict() 46 | for root, appname in settings.OSCAR_PAYMENT_MODULES: 47 | app = try_import(appname + '.app') 48 | if app is not None: 49 | application = app.application 50 | self._modules[application.name] = {'url': root, 51 | 'app': application} 52 | else: 53 | self.log.warning("Oscar misconfigured! %s.app cannot be " 54 | "imported", appname) 55 | 56 | return self._modules 57 | 58 | 59 | 60 | application = CheckoutApplication() 61 | -------------------------------------------------------------------------------- /oscar_payments/apps/checkout/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: 5 | :platform: Unix 6 | :synopsis: containing module for oscar app overrides 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from django import forms 12 | from django.shortcuts import redirect 13 | from django.views.generic.edit import FormMixin 14 | from oscar.apps.checkout import views 15 | 16 | from oscar_payments import import_shop_app 17 | 18 | class PaymentDetailsView(FormMixin, views.PaymentDetailsView): 19 | """ 20 | We need to redefine post to acommodate both the FormView and 21 | PaymentDetailView parts of this view. 22 | 23 | In our case, this view should just render a form to choose the payment 24 | method, but Oscar expects this view to handle order / payment details 25 | preview as well 26 | """ 27 | 28 | #: We change the normal template name because we want this to be a 29 | #: "select payment" view 30 | 31 | template_name = "checkout/select_payment.html" 32 | def __init__(self, *args, **kwargs): 33 | super(PaymentDetailsView, self).__init__(*args, **kwargs) 34 | self.app = import_shop_app('checkout', 'app').application 35 | 36 | def get_success_url(self): 37 | """ 38 | This needs to be overriden because FormMixin is before in the MRO and it 39 | raises if not overriden 40 | :return: `oscar.apps.checkout.views.PaymentDetailsView.get_success_url` 41 | """ 42 | return views.PaymentDetailsView.get_success_url(self) 43 | 44 | def get_context_data(self, **kwargs): 45 | form_class = self.get_form_class() 46 | form = self.get_form(form_class) 47 | return super(PaymentDetailsView, self).get_context_data(form=form, 48 | **kwargs) 49 | 50 | def post(self, request, *args, **kwargs): 51 | # oscar bit 52 | error_response = self.get_error_response() 53 | if error_response: 54 | return error_response 55 | if self.preview: 56 | if request.POST.get('action', '') == 'place_order': 57 | return self.submit(request.basket) 58 | return self.render_preview(request, **kwargs) 59 | 60 | # FormView bit 61 | form_class = self.get_form_class() 62 | form = self.get_form(form_class) 63 | if form.is_valid(): 64 | return self.form_valid(form) 65 | else: 66 | return self.form_invalid(form) 67 | 68 | def get_form_class(self): 69 | class PaymentModuleForm(forms.Form): 70 | module = forms.ChoiceField( 71 | label='', # no label for this field 72 | choices=[m['app'].get_choice() 73 | for m in self.app.modules.values()], 74 | widget=forms.RadioSelect) 75 | return PaymentModuleForm 76 | 77 | def form_valid(self, form): 78 | module_name = form.cleaned_data['module'] 79 | module = self.app.modules[module_name]['app'] 80 | return redirect(module.get_root_url()) 81 | -------------------------------------------------------------------------------- /oscar_payments/modules/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.modules 5 | :platform: Unix 6 | :synopsis: containing module for oscar app specific modules for certain use 7 | cases. Project-specific overrides can use these 8 | 9 | .. moduleauthor:: Tomas Neme 10 | """ 11 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.modules.payment 5 | :platform: Unix 6 | :synopsis: base package for base-case payment modules 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/base/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.modules.payment.base 5 | :platform: Unix 6 | :synopsis: Base for custom payment modules for usage with the checkout app 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/base/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from django.conf.urls import patterns, url 12 | from django.utils.translation import ugettext_noop as _ 13 | from oscar.core.application import Application 14 | from oscar.apps.payment.exceptions import PaymentError 15 | 16 | from oscar_payments.modules.payment.base.views import BaseRootView 17 | 18 | class PaymentModule(Application): 19 | """ 20 | Base payment module. Tries to provide reasonable default behaviors 21 | 22 | To create a payment module,subclass this. 23 | """ 24 | #: This is used from the checkout app and needs to be defined in each 25 | #: subclass. It is also the namespace for each module's urls 26 | name = None 27 | #: Human-readable name for the payment app. Should be internationalized if 28 | #: required 29 | verbose_name = _('UNDEFINED') 30 | 31 | #: Root view. It's mapped to r'$^' by default 32 | root_view = BaseRootView 33 | #: Preview view. It defaults to the same as `root_view` 34 | preview_view = None 35 | 36 | def __init__(self, *args, **kwargs): 37 | if self.name is None: 38 | raise PaymentError, "%s.%s: name property cannot be None" % ( 39 | self.__class__.__module__, self.__class__.__name__) 40 | if self.preview_view is None: 41 | self.preview_view = self.root_view 42 | super(PaymentModule, self).__init__(*args, **kwargs) 43 | 44 | def get_choice(self): 45 | """ 46 | This is used in the select payment form to generate the list of choices. 47 | By default it returns (name, verbose_name) 48 | 49 | :return: a Tuple as expected by forms.ChoiceFields' choices parameter 50 | """ 51 | return (self.name, self.verbose_name) 52 | 53 | def get_root_url(self): 54 | """ 55 | Get this module's namespaced root url name for use in returning 56 | HTTPResponseRedirect 57 | 58 | :return: checkout:: 59 | """ 60 | return ":".join(['checkout', self.name, self.root_url_name()]) 61 | 62 | def get_preview_url(self): 63 | """ 64 | Get this module's namespaced preview url name for use in url reversing 65 | 66 | :return: checkout:: 67 | """ 68 | return ":".join(['checkout', self.name, self.preview_url_name()]) 69 | 70 | def root_url_name(self): 71 | """ 72 | Root url name to use in url registration. WITHOUT the namespace 73 | """ 74 | return 'index' 75 | 76 | def preview_url_name(self): 77 | """ 78 | Preview url name to use in url registration. WITHOUT the namespace 79 | """ 80 | return 'preview' 81 | 82 | def get_urls(self): 83 | """ 84 | Add default root and preview urls and 85 | """ 86 | base_urls = super(PaymentModule, self).get_urls() 87 | urlpatterns = patterns('', 88 | url(r'^$', self.root_view.as_view(module=self), name=self.root_url_name()), 89 | url(r'^preview/$', self.root_view.as_view(module=self), 90 | kwargs=dict(preview=True), name=self.preview_url_name()) 91 | ) 92 | return urlpatterns + base_urls 93 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/base/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from oscar.apps.checkout import views 12 | from oscar.apps.payment import forms 13 | from oscar.apps.payment.exceptions import PaymentError 14 | 15 | class BaseRootMixin(object): 16 | """ 17 | Add base behavior for root views: 18 | 19 | * allows for `template_name` and `template_name_preview` to be lists instead 20 | of single strings 21 | * adds `preview_url` and `method_index_url` variables to the context 22 | """ 23 | 24 | #: for internal usage. This is why 25 | #: oscar_payments.modules.payment.base.app.PaymentModule 26 | #: sets module=self in every view 27 | module = None 28 | 29 | def get_template_names(self): 30 | """ 31 | Allow for definition of multiple template names in template_name_preview 32 | and template_name. These MUST be lists (not tuples) 33 | """ 34 | if self.preview: 35 | if isinstance(self.template_name_preview, list): 36 | return self.template_name_preview 37 | else: 38 | return [self.template_name_preview] 39 | else: 40 | if isinstance(self.template_name, list): 41 | return self.template_name 42 | else: 43 | return [self.template_name] 44 | 45 | def get_context_data(self, **kwargs): 46 | ctx = { 47 | 'preview_url': self.module.get_preview_url(), 48 | 'payment_module_root': self.module.get_root_url(), 49 | } 50 | ctx.update(super(BaseRootMixin, self).get_context_data(**kwargs)) 51 | return ctx 52 | 53 | class BankcardBillcardMixin(object): 54 | """ 55 | Mixin object that sets the template names and adds the forms to the context 56 | """ 57 | #: default bankcard/billing forms collect template 58 | template_name = "checkout/bankcard_billing_form.html" 59 | #: bankcard form class 60 | bankcard_form_class = forms.BankcardForm 61 | #: default bankcard/billing hidden forms preview template 62 | template_name_preview = "checkout/bankcard_billing_preview.html" 63 | #: billing address form class 64 | billing_address_form_class = forms.BillingAddressForm 65 | 66 | def get_context_data(self, **kwargs): 67 | """ 68 | Add the 69 | """ 70 | ctx = { 71 | 'bankcard_form': self.get_form('bankcard'), 72 | 'billing_address_form': self.get_form('billing_address'), 73 | } 74 | ctx.update(super(BankcardBillcardMixin, 75 | self).get_context_data(**kwargs)) 76 | return ctx 77 | 78 | def get_form(self, type=None, *args, **kwargs): 79 | """ 80 | Return the requested form with the given arguments 81 | 82 | :param type: REQUIRED: ('bankcard'|'billing_address') 83 | :param args: positional arguments for the form constructor 84 | :param kwargs: keyword arguments for the form constructor 85 | :return: The form instance 86 | """ 87 | if type not in ('bankcard', 'billing_address'): 88 | raise PaymentError, "Form type parameter is required" 89 | return getattr(self, '{}_form_class'.format(type))(*args, **kwargs) 90 | 91 | def post(self, request, *args, **kwargs): 92 | """ 93 | Handle posting of the collect and preview forms. This method doesn't 94 | call super() so beware when overriding 95 | """ 96 | 97 | # this is set by the preview template on the form. This should be more 98 | # elegantly handled, I think 99 | if request.POST.get('action', '') == 'place_order': 100 | return self.do_place_order(request) 101 | bankcard_form = self.get_form('bankcard', request.POST) 102 | billing_address_form = self.get_form('billing_address', request.POST) 103 | if not all([bankcard_form.is_valid(), billing_address_form.is_valid()]): 104 | self.preview = False 105 | ctx = self.get_context_data( 106 | bankcard_form=bankcard_form, 107 | billing_address_form=billing_address_form) 108 | return self.render_to_response(ctx) 109 | self.preview = True 110 | return self.render_preview(request, 111 | bankcard_form=bankcard_form, 112 | billing_address_form=billing_address_form) 113 | 114 | 115 | class BaseRootView(BaseRootMixin, views.PaymentDetailsView): 116 | """ 117 | Base class for payment modules "root" view. 118 | """ 119 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/dummy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.modules.dummy 5 | :platform: Unix 6 | :synopsis: dummy payment module for oscar. Receives Credit Card data and 7 | billing address. This is the list of valid test credit card numbers: 8 | 9 | CREDIT_CARDS = { 10 | 'American Express': [ 11 | '378282246310005', 12 | '371449635398431', 13 | ], 14 | 'MasterCard': [ 15 | '5555555555554444', 16 | '5105105105105100', 17 | ], 18 | 'Visa': [ 19 | '4111111111111111', 20 | '4012888888881881', 21 | # Note : Even though this number has a different character count than 22 | # the other test numbers, it is the correct and functional number. 23 | '4222222222222' 24 | ] 25 | } 26 | 27 | .. moduleauthor:: Tomas Neme 28 | 29 | """ 30 | 31 | # Test Credit Card Account Numbers 32 | CREDIT_CARDS = { 33 | 'American Express': [ 34 | '378282246310005', 35 | '371449635398431', 36 | ], 37 | 'MasterCard': [ 38 | '5555555555554444', 39 | '5105105105105100', 40 | ], 41 | 'Visa': [ 42 | '4111111111111111', 43 | '4012888888881881', 44 | # Note : Even though this number has a different character count than 45 | # the other test numbers, it is the correct and functional number. 46 | '4222222222222' 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/dummy/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.modules.payment.dummy.app 5 | :platform: Unix 6 | :synopsis: oscar application for the dummy payment module 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from django.conf.urls import patterns, url 12 | from django.utils.translation import ugettext_lazy as _ 13 | from oscar_payments.modules.payment.base.app import PaymentModule 14 | from oscar_payments.modules.payment.dummy import views 15 | 16 | class DummyPaymentApplication(PaymentModule): 17 | name = 'dummy' 18 | verbose_name = _('Credit Cards') 19 | root_view = views.CollectBillingInfo 20 | 21 | application = DummyPaymentApplication() 22 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/dummy/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. :module: oscar_payments.modules.payment.dummy.forms 5 | :platform: Unix 6 | :synopsis: 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from django.utils.translation import ugettext_lazy as _ 12 | from django import forms 13 | from oscar.apps.payment import forms as payment_forms 14 | 15 | from oscar_payments.modules.payment.dummy import CREDIT_CARDS 16 | 17 | 18 | class BankcardForm(payment_forms.BankcardForm): 19 | """ 20 | Update the BankcardForm to accept only our test credit cards 21 | """ 22 | card_type = forms.ChoiceField(choices=((cc, cc) for cc in CREDIT_CARDS), 23 | label=_('Card Type'),) 24 | 25 | def __init__(self, *args, **kwargs): 26 | super(BankcardForm, self).__init__(*args, **kwargs) 27 | 28 | # move the card_type field to the top 29 | card_type = self.fields.pop('card_type') 30 | self.fields.insert(0, 'card_type', card_type) 31 | 32 | # set up card number help text 33 | self.fields['number'].help_text = _('Valid numbers: %s') % str(CREDIT_CARDS) 34 | 35 | def clean(self): 36 | card_type = self.cleaned_data['card_type'] 37 | if not self.cleaned_data['number'] in CREDIT_CARDS[card_type]: 38 | self._errors['number'] = self.error_class([_("Please enter a valid " 39 | "credit card number")]) 40 | return self.cleaned_data 41 | 42 | BillingAddressForm = payment_forms.BillingAddressForm 43 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/dummy/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: oscar_payments.modules.payment.dummy.views 5 | :platform: Unix 6 | :synopsis: Views to collect and render 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from oscar_payments.modules.payment.base import views 12 | from oscar_payments.modules.payment.dummy import forms 13 | 14 | class CollectBillingInfo(views.BankcardBillcardMixin, views.BaseRootView): 15 | """ 16 | Collect billing info and pass it on. 17 | """ 18 | bankcard_form_class = forms.BankcardForm 19 | billing_address_form_class = forms.BillingAddressForm 20 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/paypal/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/paypal/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from django.utils.translation import ugettext_lazy as _ 12 | from oscar_payments.modules.payment.base.app import PaymentModule 13 | from oscar_payments.modules.payment.paypal import views 14 | from django.template.loader import render_to_string 15 | from django.utils.safestring import mark_safe 16 | 17 | class PaypalPaymentModule(PaymentModule): 18 | name = 'paypal' 19 | verbose_name = _('Paypal') 20 | root_view = views.PaypalRootView 21 | 22 | #: Template to generate the "paypal" button for the choice field. PayPal 23 | #: recommends this to be taken from here: 24 | #: https://www.paypal.com/express-checkout-buttons 25 | choice_template_name = "checkout/paypal/choice_button.html" 26 | 27 | def get_urls(self): 28 | from paypal.express import urls 29 | base_urls = super(PaypalPaymentModule, self).get_urls() 30 | return urls.urlpatterns + base_urls 31 | 32 | def get_choice(self): 33 | text = render_to_string(self.choice_template_name) 34 | return (self.name, mark_safe(text)) 35 | 36 | application = PaypalPaymentModule() 37 | -------------------------------------------------------------------------------- /oscar_payments/modules/payment/paypal/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | .. module:: 5 | :platform: Unix 6 | :synopsis: TODO 7 | 8 | .. moduleauthor:: Tomas Neme 9 | 10 | """ 11 | from django.views.generic import RedirectView 12 | from oscar_payments.modules.payment.base.views import BaseRootMixin 13 | 14 | class PaypalRootView(BaseRootMixin, RedirectView): 15 | """ 16 | By default we don't add the payflow-pro stuff, so all this really needs to 17 | do is change the render template. 18 | """ 19 | # DO NOT FORGET: this is paypal express checkout, their site will take care 20 | # of letting the user review their order 21 | 22 | url = 'paypal-redirect' 23 | 24 | def get_redirect_url(self, **kwargs): 25 | from django.core.urlresolvers import reverse 26 | return reverse(self.url) 27 | -------------------------------------------------------------------------------- /oscar_payments/templates/basket/basket.html: -------------------------------------------------------------------------------- 1 | {% extends 'oscar/basket/basket.html' %} 2 | 3 | {% block formactions %} 4 |
5 | {% if anon_checkout_allowed or request.user.is_authenticated %} 6 | 7 | {% endif %} 8 | Proceed to checkout 9 |
10 | {% endblock formactions %} -------------------------------------------------------------------------------- /oscar_payments/templates/checkout/bankcard_billing_form.html: -------------------------------------------------------------------------------- 1 | {% extends "oscar/checkout/payment_details.html" %} 2 | {% load i18n %} 3 | {% load url from future %} 4 | 5 | {% block payment_details %} 6 |
{% trans "Fill in your details" %}
7 |
8 | {% csrf_token %} 9 |

{% trans "Bankcard" %}

10 | {% include "partials/form_fields.html" with form=bankcard_form %} 11 |

{% trans "Billing address" %}

12 | {% include "partials/form_fields.html" with form=billing_address_form %} 13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /oscar_payments/templates/checkout/bankcard_billing_preview.html: -------------------------------------------------------------------------------- 1 | {% extends 'oscar/checkout/preview.html' %} 2 | {% load currency_filters %} 3 | 4 | {% block payment_method %} 5 |
6 |
7 |

Payment

8 |
9 |
10 |

{{ order_total_incl_tax|currency }} will be debited from your bankcard.

11 | 14 |
15 |
16 | {% endblock %} 17 | 18 | {% block place_order %} 19 |

Please review the information below, then click "Place Order"

20 |
21 | {% csrf_token %} 22 | 23 | {# Store payment forms but hidden so they get resubmitted #} 24 |
25 | 26 | {{ bankcard_form.as_p }} 27 | {{ billing_address_form.as_p }} 28 |
29 | 30 |
31 | 32 |
33 |
34 | {% endblock place_order %} 35 | -------------------------------------------------------------------------------- /oscar_payments/templates/checkout/paypal/choice_button.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | PayPal 3 | {% trans "The safer, easier way to pay." %} 4 | -------------------------------------------------------------------------------- /oscar_payments/templates/checkout/select_payment.html: -------------------------------------------------------------------------------- 1 | {% extends 'oscar/checkout/payment_details.html' %} 2 | {% load i18n %} 3 | 4 | {% block payment_details %} 5 |
6 |

How would you like to pay?

7 |
8 |
9 | {% csrf_token %} 10 | {{ form }} 11 | 12 |
13 | {% endblock %} 14 | --------------------------------------------------------------------------------