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
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
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
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
💀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