Building a Food Delivery App With Django and Python 3: Part 4 Implementing PayPal API With AJAX

LegionScript
7 min readNov 16, 2020

Video Tutorial

Code on Github

In this tutorial we are going to contine building our food delivery application. This time, we are going to implement PayPal payments using a sandbox paypal account. We will give the user an option on the confirmation page to either pay with cash when the food arrives or to pay with PayPal now. We will use AJAX to make a request back to the server after completing the payment through PayPal to mark the order as paid and redirect to a confirmation page.

Let’s start by adding to our OrderModel in our models.py file, we need to add a field to mark the order as paid:

class OrderModel(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=7, decimal_places=2)
items = models.ManyToManyField('MenuItem', related_name='order', blank=True)
name = models.CharField(max_length=50, blank=True)
email = models.EmailField(max_length=50, blank=True)
street = models.CharField(max_length=50, blank=True)
city = models.CharField(max_length=50, blank=True)
state = models.CharField(max_length=15, blank=True)
zip_code = models.IntegerField(blank=True, null=True)
is_paid = models.BooleanField(default=False)
def __str__(self):
return f'Order: {self.created_on.strftime("%b %d %Y %I:%M %p")}'

The is_paid filed will hold this. We will set it to a default of False because when every order is created, it will not have been paid yet. Let’s migrate the changes to the database.

python3 manage.py makemigrations
python3 manage.py migrate

Now that we have our model set up, let’s move the order confirmation from just being rendered at the end of the POST request to being it’s own view that the user is redirected to once they place an order.

class OrderConfirmation(View):
def get(self, request, pk, *args, **kwargs):
order = OrderModel.objects.get(pk=pk)
context = {
'pk': order.pk,
'items': order.items,
'price': order.price
}
return render(request, 'customer/order_confirmation.html', context)

def post(self, request, pk, *args, **kwargs):
print(request.body)

We will move the render line into a get request method. We need to grab the order from the database. We will pass the primary key into the url so we will use that to get the order. We also need to pass in the pk, items, and price attached to this order for the template. Later, we will need a post request so let’s just create that method now as well. We will add to it later, for now let’s print out what is in the post request body. There will eventually be JSON there that will tell Django that the order was paid for.

Since we removed the return render from the post method in our Order class, we will need to replace that. We want to redirect to the view we just created. Once we create a url for it, we will name it ‘order-confirmation’ so let’s import from django.shortcuts redirect and add that line to the bottom of the post method in our Order class.

return redirect('order-confirmation', pk=order.pk)

Now let’s create the url pattern for this:

urlpatterns = [
path('admin/', admin.site.urls),
path('', Index.as_view(), name='index'),
path('about/', About.as_view(), name='about'),
path('order/', Order.as_view(), name='order'),
path('order-confirmation/<int:pk>/', OrderConfirmation.as_view(), name='order-confirmation'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Make sure the name attribute matches what you passed into the redirect method in the Order post method.

Let’s also create a payment confirmation view so that the user gets some feedback that the payment went through successfully.

class OrderPayConfirmation(View):
def get(self, request, *args, **kwargs):
return render(request, 'customer/order_pay_confirmation.html')
{% extends 'customer/base.html' %}{% block content %}
<div class="container mb-5">
<div class="row justify-content-center mt-1">
<div class="col-md-5 col-sm-12 p-4 text-center">
<h1>Payment Submitted!</h1>
<a href="{% url 'index' %}">Go to the homepage</a>
</div>
</div>
</div>
{% endblock content %}
urlpatterns = [
path('admin/', admin.site.urls),
path('', Index.as_view(), name='index'),
path('about/', About.as_view(), name='about'),
path('order/', Order.as_view(), name='order'),
path('order-confirmation/<int:pk>/', OrderConfirmation.as_view(), name='order-confirmation'),
path('payment-confirmation/', OrderPayConfirmation.as_view(), name='payment-confirmation')
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Now that we have all of that set up, let’s set up the PayPal API. First you will need to sign up for a free PayPal accoutn if you do not already have one. if you go to https://developer.paypal.com/developer/applications/ you will be able to create an App. Make sure the sandbox tab is checked

Now with that set up, in the left hand side bar, select accounts.

You should be able to create two accounts from here. You will need one business account and one personal account to test this. The business account will be the one recieving the money and the personal account will be the customer account.

Now with that done, we have all of the information we need to get this implemented. This link has information on the necessary code for this: https://developer.paypal.com/docs/checkout/integrate/#

This is an example of how mine looks, you will need to replace CLIENT_ID with the client ID for your business account. If you go back to the accounts page we were at earlier you should be able to find that information.

<script src="https://www.paypal.com/sdk/js?client-id=API_KEY&currency=USD"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '{{ price }}'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Make post request to pay confirmation
});
}
}).render('#paypal-button-container');
</script>

We will also need to add a div with the paypal-button-container id, we will put this right below our total:

<div class="row justify-content-center mt-5">
<div class="col-md-6 text-center">
<h3>Pay Now or Pay With Cash On Delivery</h3>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 text-center">
<div id="paypal-button-container"></div>
</div>
</div>

With this, our buttons should show up but they won’t do anything yet. Let’s add that functionality now. To do this, we will use AJAX. We need to send a request back to Django after the payment successfully goes through. To accomplish this, we will set up a post method on our order confirmation view.

class OrderConfirmation(View):
def get(self, request, pk, *args, **kwargs):
order = OrderModel.objects.get(pk=pk)
context = {
'pk': order.pk,
'items': order.items,
'price': order.price
}
return render(request, 'customer/order_confirmation.html', context)

def post(self, request, pk, *args, **kwargs):
print request.body

First let’s just print out the data in the request body so that we know that it is working. We will send the post request to this url and we will eventually mark is_paid as True if it receives valid data.

Now let’s send our AJAX request. In our order_confirmation.html we will add this to the onApprove function in our javascript. Once the payment goes through successfully, this function is called. This is what all of the code looks like:

<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '{{ price }}'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Make post request to pay confirmation
$.ajax({
type: 'POST',
url: "{% url 'order-confirmation' pk %}",
beforeSend: function(request) {
request.setRequestHeader("X-CSRFToken", csrftoken)
},
data: JSON.stringify({'isPaid': true}),
success: function(data) {
window.location.href = '/payment-confirmation/'
}
})
});
}
}).render('#paypal-button-container');
</script>

The getCookie function comes straight from the Django documentation on how to get the corrent token. This is needed to avoid getting an error when sending the request. We will send it as a header later. In the actual AJAX request, we need to set the type as POST, and set the url to our order confirmatino url passing in the current pk. Next, we need to send the Django CSRF token that we got in the getCookie function. We can send a header by adding a beforeSend function and then using the setRequestHeader function with our token passed in. Next we need to add our data that we want to send, we will just send a JSON object with a property called isPaid. If that is set to true we know that the order has been paid for. Next we need to redirect the user to the payment confirmation view that we created. We can do this by adding a success function and using window.location.href to redirect the user.

That is all we should need to be able to see the result in the console. If you go through the payment steps you should see isPaid: true in the console.

Now let’s actually check for the isPaid value and if it is set to true we want to change the model field value for is_paid:

import json

def post(self, request, pk, *args, **kwargs):
data = json.loads(request.body)
if data['isPaid']:
order = OrderModel.objects.get(pk=pk)
order.is_paid = True
order.save()
return redirect('payment-confirmation')

We will use the json library to parse the request body. If isPaid is true, we will get the current OrderModel object and set the is_paid field to True. Finally we will save the object and redirect to the payment-confirmation url. It might take a couple seconds for the redirect to take place.

That concludes this tutorial. We now should have a fully functioning payment system integrated into our app. We will come back in the future and add more to this application.

--

--