Cómo integrar PayPal en Django paso a paso

Video thumbnail

Integrar PayPal en Django puede parecer complicado al principio, pero la realidad es que se trata simplemente de realizar peticiones HTTP limpias y bien estructuradas.
En esta guía te muestro cómo hacerlo sin depender de SDKs externos, usando únicamente el paquete requests de Python, el mismo que seguramente ya usas en tu día a día. Y esto es lo bonito que tenemos con PayPal, el primer secreto es que, podemos integrar PayPal web desde CUALQUIER tecnología que permita hacer peticiones HTTP.

Por qué integrar PayPal en tu proyecto Django

PayPal es una de las pasarelas de pago más confiables y universales. Integrarla en un proyecto Django permite ofrecer una experiencia de compra segura, rápida y profesional.

Qué ventajas ofrece PayPal frente a otras pasarelas

No necesitas manejar tarjetas directamente: el pago lo gestiona PayPal.

Ofrece entorno sandbox para pruebas sin dinero real.

Soporta pagos únicos, suscripciones y devoluciones.

Fácil de implementar con simples peticiones HTTP.

Qué necesitas antes de empezar

Un proyecto Django funcionando (puedes buscar en mi blog, en el apartado de Django, allí mostramos como crear un proyecto en Django).

Cuenta en PayPal Developer.

Tu Client ID y Secret.

Y, por supuesto, tener instalado el paquete que usaremos para las peticiones HTTP:

$ pip install requests

Generar claves de acceso y usuarios de prueba

Para poder integrar PayPal en nuestra aplicación, debemos de tener una cuenta en PayPal, una vez conseguido, si vamos al sitio de desarrollo de PayPal:

https://developer.paypal.com/home

Damos click sobre la opción que dice "API Credentials" y crear una aplicación, para ello, presionamos el botón de "Create App" y aparecerá un diálogo como el siguiente:

Diálogo para crear una app en PayPal

Puedes dejar la opción de "Merchant" y "Create App".

Creada las credenciales para poder emplear la API de PayPal, aparecerán listadas en la parte de abajo:

Credenciales de PayPal

Tenemos una clave secreta que la usaremos en el servidor y una pública que la usaremos en el cliente, por lo tanto, quedará expuesta para cualquier persona que vea el código fuente de la página desde su navegador, a su vez, tenemos acceso a unas claves de prueba, con las cuales podemos ir realizando las peticiones a una cuenta de prueba o sandbox.

Aparte de las claves, se generan usuarios de prueba que emplearemos para realizar la conexiones a la API de pruebas de PayPal disponibles en "Sandbox accounts":

Cuenta de sandbox

⚙️ Configurar tu entorno Django y claves de PayPal

Crea una aplicación en el panel de desarrollador de PayPal y copia tus credenciales:

# settings.py
PAYPAL_CLIENT_ID = '<YOUR_CLIENT_ID>'
PAYPAL_SECRET = '<YOUR_SECRET>'
PAYPAL_BASE_URL = 'https://api-m.sandbox.paypal.com'

Usa siempre el entorno sandbox para pruebas.
Cuando pases a producción, cambia la URL base a https://api-m.paypal.com.

Crear una clase modular para manejar pagos en Django

La clave está en modularizar. En lugar de llenar las vistas con código repetido, creamos una clase PayPalPayment que se encargue de todas las operaciones.

import requests
from django.conf import settings
class PayPalPayment:
   def __init__(self):
       self.client_id = settings.PAYPAL_CLIENT_ID
       self.secret = settings.PAYPAL_SECRET
       self.base_url = settings.PAYPAL_BASE_URL

Generar el token de acceso (get_access_token)

PayPal exige un token para autenticar las peticiones. Lo obtenemos así:

class PayPalPayment:
***
def get_access_token(self):
   url = f"{self.base_url}/v1/oauth2/token"
   headers = {
       "Accept": "application/json",
       "Content-Type": "application/x-www-form-urlencoded",
   }
   data = {"grant_type": "client_credentials"}
   response = requests.post(url, auth=(self.client_id, self.secret), data=data, headers=headers)
   return response.json().get("access_token") if response.status_code == 200 else None

Para el servidor, PayPal es simplemente una API HTTP; solo hay que armar bien los headers y el token.

Capturar la orden (capture_order)

Una vez aprobada la orden por el cliente, la procesamos:

def capture_order(self, order_id):
   access_token = self.get_access_token()
   if not access_token:
       return {"error": "No se pudo obtener el token"}
   url = f"{self.base_url}/v2/checkout/orders/{order_id}/capture"
   headers = {
       "Content-Type": "application/json",
       "Authorization": f"Bearer {access_token}",
   }
   response = requests.post(url, json={}, headers=headers)
   return response.json()

Como puntos importantes tenemos que, agregamos la URL a la cual debemos de realizar la petición:

url = f"{self.base_url}/v1/oauth2/token"

Los headers, para indicar que devuelva una respuesta en formato JSON y que la petición es enviada NO como una petición JSON si no como si fuera un formulario:

headers = {
   "Accept": "application/json",
    "Content-Type": "application/x-www-form-urlencoded",
}

Indicamos que la petición es para generar el token de acceso:

data = {"grant_type": "client_credentials"}

Armamos la petición que es de tipo POST y suministramos los datos anteriores:

response = requests.post(url, auth=(self.client_id, self.secret), data=data, headers=headers)

Con:

return response.json()

Obtenemos toda la respuesta, que es formato JSON según configuramos anteriormente, nos interesa es solo el token que es registrado en:

return response.json().get("access_token")

También, verificamos que el estatus de la petición sea 200 que indica que el token fue generado exitosamente, cualquier otro status, indica que NO se pudo generar el token:

response.status_code

El token luce similar a:

A21AAJmqsBwihAFF236Y6pnisFJWK***hy3Qr6SsZNr0BsWPFIwEkXqu45bkJHlDgxkknDUkoSA

Y es el que tenemos que suministrar en lugar del client y el secret al momento de aprobar la orden; toda la estructura de los datos son los estipulados por PayPal.

️ Integrar PayPal con el cliente (JavaScript SDK)

Ya conocido qué es lo que tenemos que hacer para obtener las claves de PayPal para Django, es decir el modo desarrollador en PayPal tal cual te mostraba en el anterior video aquí para continuar en esta clase tienes que tener tanto el client como el Secret sobre todo ahorita el Client que es el que vamos a utilizar el Secret lo empleamos más adelante entonces para poder ampliar esta integración vamos a utilizar un plugin que viene siendo el que voy a dejar en alguna parte de la vida.

https://www.npmjs.com/package/@paypal/paypal-js

Es posible emplearlo mediante Node o la CDN que es la que vamos a emplear

<script src="https://www.paypal.com/sdk/js?client-id=test"></script>

Aclarado esto un poco vamos a comenzar qué es lo que vamos a hacer primero para separar las cosas voy a colocarla aquí en la de edita vamos a crear una nueva nuevo template una nueva plantilla que lo voy a colocar:

mystore\templates\partials\paypal.html

<div id="buttonPayPal"></div>

<script>
   paypal.Buttons().render('#buttonPayPal');
</script>

Y en la página de detalle, cargamos el JS:

{% block content %}
<script src="https://www.paypal.com/sdk/js?client-id=<YOURCLIENTID>"></script>
***
<div class="card-body">
   {% include "partials/paypal.html" %}
***

En donde dice <YOURCLIENTID> pudieras poner tu clave provista por PayPal, pero, vamos a crear un esquema más modular en el siguiente apartado.

Pero, con el código anterior, deberías de poder ver los botones de PayPal desde tu web.

Ejemplo completo

Para mostrar los botones de pago en el frontend, basta con cargar el SDK oficial de PayPal:

<script src="https://www.paypal.com/sdk/js?client-id={{paypal_client_id}}"></script>
<div id="buttonPayPal"></div>
<script>
   paypal.Buttons({
       createOrder: function(data, actions){
           return actions.order.create({
               purchase_units: [{ amount: { value: '{{ element.price }}' } }]
           });
       },
       onApprove: function(data, actions){
           console.log(data);
           window.location.href = '/store/capture-payment/' + data.orderID;
       }
   }).render('#buttonPayPal');
</script>

La redirección hacia otra página mediante el objeto windows evita que el usuario recargue la página y procese por duplicado la orden.

Puedes colocar otras opciones como el payer, el item, entre otros.

Aquí también es importante que entres en contexto recuerda que te había comentado que son dos pasos:

  1. Por una parte lo que es que es la parte del cliente que simplemente es una especie de autorización es una especie de promesa en la cual el cliente quiere comprar ese producto
  2. En el servidor que la integración que vamos a ver más adelante no se aprueba la compra o no se finaliza la compra es decir no se cobra el monto del producto que está comprando del cliente desde su PayPal aquí simplemente es una especie de promesa por así decirlo en la cual una intención en la cual luego vamos a autorizar.

Procesar la orden y mostrar resultados

En el servidor, recibimos el orderID y finalizamos el proceso:

from django.shortcuts import render
from .paypal import PayPalPayment
def capture_payment(request, order_id):
   paypal = PayPalPayment()
   res = paypal.capture_order(order_id)
   if res:
       return render(request, 'elements/capture_payment.html', {
           'res': res,
           'id': res['id'],
           'status': res['status'],
           'price': res['purchase_units'][0]['payments']['captures'][0]['amount']['value'],
       })
   return render(request, 'elements/error.html')

Y en la plantilla:

<ul>
 <li>ID: {{ id }}</li>
 <li>Status: {{ status }}</li>
 <li>Price: {{ price }}</li>
</ul>
<pre>{{ res }}</pre>

Recomiendo guardar la traza completa de la respuesta en la base de datos para depurar si algo falla.

Esos son los datos que recomiendo guardar de manera básica, puedes guardar otros, pero, estos son obligatorios:

  • El estatus eso es lo que es lo primero que nosotros vamos a necesitar la respuesta también nos viene un JSON.
  • Monto
  • ID

Debido a que, con esto, puedes verificar el pago exitoso a nivel del sistema.

Buenas prácticas y errores comunes al integrar PayPal

  • Guardar trazas y respuestas
    • Almacenar el JSON completo de PayPal en un campo aparte te puede salvar de dolores de cabeza si hay discrepancias o fallos intermitentes.
  • Tokens expirados o sandbox vencido
    • Los tokens duran unos minutos y las órdenes pueden expirar al cabo de un día. Si obtienes errores 401 o 404, probablemente el token o la orden ya no sean válidos.
  • Entorno de pruebas vs producción
    • Sandbox: ideal para desarrollo.
    • Producción: usa URL segura https://api-m.paypal.com.
    • Mantén tus credenciales fuera del repositorio (usa variables de entorno).

Conclusión

Integrar PayPal en Django no requiere SDKs ni librerías externas complejas.
Con una clase modular, unas pocas peticiones HTTP y el SDK de PayPal en el frontend, puedes implementar un flujo de pago profesional y seguro.

En mi experiencia, implementar PayPal en Django es básicamente hacer peticiones HTTP limpias y estructuradas, nada más.

Preguntas frecuentes (FAQs)

  1. ¿Se puede usar Django Rest Framework con PayPal?
    Sí, puedes exponer los endpoints de creación y captura de ordenes vía DRF para integraciones SPA o móviles.
  2. ¿Cómo generar el token de acceso desde Django?
    Usa el endpoint /v1/oauth2/token con tu client y secret, como en el método get_access_token().
  3. ¿Qué debo guardar en la base de datos?
    ID de orden, estado, monto y la traza JSON completa del pago.
  4. ¿Cómo pasar de sandbox a producción?
    Solo cambia la URL base y las credenciales en tus variables de entorno.

Acepto recibir anuncios de interes sobre este Blog.

Veremos paso a paso como integrar PayPal en Django, desde crear las credenciales en el sitio de desarrolladores, instalar dependencias necesarias, crear las clases, métodos de ayuda como el de generar el token, procesar la orden e integración en el cliente.

| 👤 Andrés Cruz

🇺🇸 In english