Django Oscar - Payment Gateway - Part 19

In this part, we implement a simulation payment gateway into Django-Oscar.
From here we have a ground to easily apply the code to Stripe, PayPal, or any other payment service.

We assume that the payment gateway provides a way to configure:

  • a cancellation link, if the user decides not to complete the payment
  • a payment link, if the user confirms the transaction

 

Content:

  1. Gateway Simulation
  2. Gateway Redirect View
  3. Cancelation
  4. Payment

 

 

1. Gateway Simulation

 

 

from django.views.generic import TemplateView
from django.shortcuts import get_object_or_404
from oscar.apps.basket.models import Basket

class Gateway(TemplateView):
    template_name = "Store/gateway.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        basket_id = kwargs.get('basket_id') 
        if basket_id:
            context['basket'] = get_object_or_404(Basket, id=basket_id) 
        return context

 

from django.urls import path
from .views import *

urlpatterns = [
	path("gateway/<int:basket_id>/", Gateway.as_view(), name="gateway"),
]

 

...
urlpatterns = [
    ...
	path('', include('Store.urls')),
]

 

{% load static %}
<!DOCTYPE html>
<html lang="en" dir="ltr">
	<head>
		<meta charset="utf-8">
	</head>
    <body style="background-color:DarkSlateGray; color:Cornsilk; margin:20% 20%; font-family:'Segoe UI';">

		<h1>This could be any Payment Gateway!</h1>

        <p>Basket ID: {{basket.id}} </p>

		<a style="color:greenyellow!important;" href="">Cancel Payment</a> <br>
		<a style="color:greenyellow!important;" href="">Complete Payment</a>
		
	</body>
</html>

Later when we create the Cancel and Success views we can complete the href for the anchor tags.
 

 

 

 

2. Gateway Redirect View

 

from django.views.generic import RedirectView
from oscar.apps.checkout.session import CheckoutSessionMixin
from django.urls import reverse
from django.contrib import messages

class GatewayRedirect(CheckoutSessionMixin, RedirectView):

	def get_redirect_url(self, **kwargs):

        self.request.session['payment_method'] = "Gateway"
		basket = self.build_submission()['basket']

        shipping_address = self.get_shipping_address(basket)
        shipping_method = self.get_shipping_method(basket, shipping_address=shipping_address)
        shipping_charge = shipping_method.calculate(basket)

		if basket.is_empty:
			messages.error(self.request, "Your basket is empty")
			return reverse('basket:summary')
		
		else: basket.freeze()

		return f"/gateway/{basket.id}"

 Before the user is redirected to /gateway/ the basket is frozen making it impossible to be modified when the user is in the gateway.
 

urlpatterns = [
    ...
    path("redirect/", GatewayRedirect.as_view(), name="gateway_redirect"),
]

 

...
{% block payment_details %}
    {% block payment_details_content %}
        
    ...
    <li>
        <a href="{% url 'gateway_redirect' %}"> Gateway </a>
    </li><br>


    {% endblock payment_details_content %}
{% endblock payment_details %}

 

 

 

3. Cancelation

 

class GatewayCancel(RedirectView):

    def get_redirect_url(self, **kwargs):
       
        basket = get_object_or_404(Basket, id=kwargs['basket_id'], status=Basket.FROZEN)
        basket.thaw()
        return reverse('basket:summary')	  

 

urlpatterns = [
    ...
    path("cancel/<int:basket_id>/", GatewayCancel.as_view(), name="gateway_cancel"),
]

 

...
<a style="color:greenyellow!important;" href="{% url 'gateway_cancel' basket.id  %}">Cancel Payment</a> <br>

 

 

 

4. Payment

 

from oscar.apps.checkout.views import PaymentDetailsView
from oscar.apps.checkout.mixins import OrderPlacementMixin
from oscar.apps.partner.strategy import Selector 
from oscar.apps.offer.applicator import Applicator 
from oscar.apps.payment.models import Source, SourceType


class GatewaySuccess(PaymentDetailsView, OrderPlacementMixin):
    
    def check_pre_conditions(self, request): 
        pass  

    def check_skip_conditions(self, request): 
        pass  
    
    def get(self, request, *args, **kwargs):
        basket_id = kwargs.get('basket_id') 
        basket = Basket.objects.get(id=basket_id, status=Basket.FROZEN)
        if Selector: basket.strategy = Selector().strategy(self.request)
        Applicator().apply(basket, self.request.user, request=self.request)
        
        return self.submit(**self.build_submission(basket=basket))


    def handle_payment(self, order_number, total, **kwargs):
  
        payment_method = self.request.session.get('payment_method', 'default_value')  

        source_type, _ = SourceType.objects.get_or_create(name=payment_method)
        
        source = Source(source_type=source_type,
                        currency=total.currency,
                        amount_allocated=str(total.incl_tax),
                        amount_debited=str(total.incl_tax),
                        reference="-")
        
        self.add_payment_source(source)
        self.add_payment_event('Settled', str(total.incl_tax), reference="-")



    def handle_order_placement(self, order_number, user, basket, shipping_address, shipping_method, 
                               shipping_charge, billing_address, order_total, surcharges=None, **kwargs):


        order = self.place_order(
            order_number=order_number,
            user=user,
            basket=basket,
            shipping_address=shipping_address,
            shipping_method=shipping_method,
            shipping_charge=shipping_charge,
            order_total=order_total,
            billing_address=billing_address,
            surcharges=surcharges,
            **kwargs,
        )
        basket.submit()
        return self.handle_successful_order(order)

 

urlpatterns = [
    ...
    path('success/<int:basket_id>/', GatewaySuccess.as_view(), name='gateway_success'),
]

 

...
<a style="color:greenyellow!important;" href="{% url 'gateway_success' basket.id  %}">Complete Payment</a> <br>