The "Basics" of adding PayPal payments to Django, server and client-side

The "Basics" of adding PayPal payments to Django, server and client-side

in this article will show a basic way on how to implement the Paypal payments using the checkout API on your Django project, for this I will create an e-learning website where the user can purchase courses

first clone this codebase from GitHub click here, because I want to focus on Paypal payments setup in this post

installing the requirements

go to your command line and run

pip install Django==3.0.7
pip install Pillow==7.1.2
pip install paypal-checkout-serversdk
pip install django-environ==0.4.5

create a sandbox account

first of all, click here and create a sandbox account after creating this account successfully you will be redirected to your dashboard and click on My Apps & Cardntials

after this click on default application or you can create a new one sandbox default

this will redirect you to this page where you will see a public key, the client key is public so you know anyone can see it

but don't let anyone see the private/secret key here is the page of the client and secret key client and secret key

update settings

now you can copy these two keys and past them, in line 26, and 27 in the settings.py file

#paypallpay/settings.py

#replace  this 
# 26 PAYPAL_CLIENT_ID  = env('PAYPAL_CLIENT_ID')
# 27 PAYPAL_SECRET_ID  =  env('PAYPAL_SECRET_ID')
#with this
PAYPAL_CLIENT_ID  = "client id here"
PAYPAL_SECRET_ID  =  "secret id here"

get client id from server-side

now let's add Paypal js sdk to our pay.html template, but the first thing that we need to do is to add a client_add variable to our pay in the views.py and assign the Paypal client id to it

#learn/views.py

def pay(request, pk):
    #line 10
    client_id = settings.PAYPAL_CLIENT_ID
    course = get_object_or_404(Course, pk=pk)
    return render(request, 'pay.html', {'course':course, 'client_id':client_id })

go to the learn/templates/pay.html, and scroll to the bottom and replace SB_CLIENT_ID with {{client_id}}, here is how you do it

{% block extrascript %}
#line 23
    <script src="https://www.paypal.com/sdk/js?client-id={{client_id}}&disable-funding=credit,card" > </script> 
    <script src="{% static  'learn/js/main.js' %}" ></script>
{% endblock %}

I added "&disable-funding=credit,card" because I just wan work with Paypal button , so I need to disable credit card button

client side setup

go to paypal server side smart buttons you will see something like this button container

copy

<!-- Set up a container element for the button -->
    <div id="paypal-button-container"></div>

got to learn/tempaltes/pay.html and replace pay button with ""

""

<!-- delete this  -->
 <a href="{% url 'pay' course.pk %}" class="btn btn-warning 
font-weight-bold btn-lg col-12">Pay</a>

<!-- and replace it with this -->
    <div id="paypal-button-container"></div>

now go to the main.js file write or copy/paste ✨ this code

  paypal.Buttons({

            // Call your server to set up the transaction
            createOrder: function(data, actions) {
                return fetch('/paypal/create/'+course_id+'/', {
                    method: 'post',
                    headers: {"X-CSRFToken": csrftoken}
                }).then(function(res) {
                    return res.json();
                }).then(function(orderData) {
                    return orderData.id;
                });
            },

            // Call your server to finalize the transaction
            onApprove: function(data, actions) {
                return fetch('/paypal/' + data.orderID + '/capture/'+course_id+'/', {
                    method: 'post',
                    headers: {"X-CSRFToken": csrftoken}
                }).then(function(res) {
                    return res.json();
                }).then(function(orderData) {
                    // Three cases to handle:
                    //   (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
                    //   (2) Other non-recoverable errors -> Show a failure message
                    //   (3) Successful transaction -> Show a success / thank you message

                    // Your server defines the structure of 'orderData', which may differ
                    var errorDetail = Array.isArray(orderData.details) && orderData.details[0];

                    if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
                        // Recoverable state, see: "Handle Funding Failures"
                        // https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
                        return actions.restart();
                    }

                    if (errorDetail) {
                        var msg = 'Sorry, your transaction could not be processed.';
                        if (errorDetail.description) msg += '\n\n' + errorDetail.description;
                        if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
                        // Show a failure message
                        return alert(msg);
                    }

                    // Show a success message to the buyer
                    alert('Transaction completed by ' + orderData.payer.name.given_name);
                });
            }


        }).render('#paypal-button-container');

on the top of main.js add this lines of code

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');

why we add this because csrftokens is important to secure our requests against csrf attacks

but before we execute this we need to edit the learn/urls.py, here is how you do it replace the old URLs patterns with this one

#learn/urls.py
urlpatterns = [
    path('', index, name="index"),
    path('paypal/pay/<pk>/', pay, name="pay"),
    path('paypal/create/<id>/', create, name="paypal-create"),
    path('paypal/<order_id>/capture/<id>/', capture, name="paypal-capture"), #changed #
     path('paypal/client-id/', getClientId , name="client-id")
]

we need to update our capture and create views to match our urls, go to learn/views.py

#learn/views.py
#line 18
def capture(request,order_id,id):
......

#server side create an order

we start by importing create and capture request from python Paypal checkout SDK, add these lines on the top of learn/views.py

from paypalcheckoutsdk.orders import OrdersCreateRequest
from paypalcheckoutsdk.orders import OrdersCaptureRequest
from paypalcheckoutsdk.core import SandboxEnvironment

in our views , lets go to the create view, and setup the sandbox environment

#learn/views.py
def create(request):
    environment = SandboxEnvironment(client_id=settings.PAYPAL_CLIENT_ID, client_secret=settings.PAYPAL_SECRET_ID)
    client = PayPalHttpClient(environment)
.......

its time to create an order add an item and get the response from the Paypal api

 #order            
    create_order.request_body (
        {
            "intent": "CAPTURE",
            "purchase_units": [
                {
                    "amount": {
                        "currency_code": "USD",
                        "value": course.price,
                        "breakdown": {
                            "item_total": {
                                "currency_code": "USD",
                                "value": course.price
                            }
                            },
                        },                               


                }
            ]
        }
    )


#get the response
    response = client.execute(create_order)

#get the data dictionary  from the response  
    data = response.result.__dict__['_dict']

now we can return a json response to our client-side in the main js our create view will look like this now

def create(request,id):
    if request.method =="POST":
        environment = SandboxEnvironment(client_id=settings.PAYPAL_CLIENT_ID, client_secret=settings.PAYPAL_SECRET_ID)
        client = PayPalHttpClient(environment)

        course = Course.objects.get(pk=id)
        create_order = OrdersCreateRequest()

        #order            
        create_order.request_body (
            {
                "intent": "CAPTURE",
                "purchase_units": [
                    {
                        "amount": {
                            "currency_code": "USD",
                            "value": course.price,
                            "breakdown": {
                                "item_total": {
                                    "currency_code": "USD",
                                    "value": course.price
                                }
                                },
                            },                               


                    }
                ]
            }
        )

        response = client.execute(create_order)
        data = response.result.__dict__['_dict']
        return JsonResponse(data)
    else:
        return JsonResponse({'details': "invalide request"})

before we can run this we need to add a course id so go to the pay.html and create a hidden input and add course id to it

#learn/templates/pay.html
<div class="card-footer bg-white"> 
   <input id="course-id"  value={{course.id}} hidden/>         
   <div id="paypal-button-container"></div>
</div>

and in the top of main.js let's add a client_id const , which contain the value of the course id

#learn/static/learn/js/main.js
const course_id = document.getElementById('course-id').value

Server-side capture the order id and finish the payment

it's simple we just need to send a request containing the order id to the paypal API to check if this order exists and then we get a response and send it to the client-side when everything is working fine the javascript code will send an alert to our end-user that the payment completed successfully, our capture view will look like this now

#learn/views
def capture(request,order_id,id):
    if request.method =="POST":
        capture_order = OrdersCaptureRequest(order_id)
        environment = SandboxEnvironment(client_id=settings.PAYPAL_CLIENT_ID, client_secret=settings.PAYPAL_SECRET_ID)
        client = PayPalHttpClient(environment)

        response = client.execute(capture_order)
        data = response.result.__dict__['_dict']

        return JsonResponse(data)
    else:
        return JsonResponse({'details': "invalide request"})

everything is ready now go to you sandbox dashboard and go to accounts u will see two email addresses the first one is personal and the other one is for business, personal for payer and business email for the receiver sandbox account emails

💀Note: this two email addresses and keys is for testing if you want real keys and emails you need to upgrade your account upgrade

if you want to run this app in your localhost check this GitHub repository click here you just need to replace

SECRET_KEY = 'SECRET_KEY' #your secret key here
PAYPAL_CLIENT_ID = 'PAYPAL_CLIENT_ID' #paypal client id here
PAYPAL_SECRET_ID = 'PAYPAL_SECRET_ID' #paypal secret id here

#for further explorations

  • check the Paypal API here
  • and the Paypal checkout repository it helped me a lot to understand how things work here