Integración de una pasarela de pago
Una pasarela de pago es una tecnología utilizada por los comercios para procesar pagos de clientes en línea. Al utilizar una pasarela de pago, puedes gestionar los pedidos de los clientes y delegar el procesamiento de pagos a un tercero confiable y seguro. Al usar una pasarela de pago confiable, no tendrás que preocuparte por la complejidad técnica, de seguridad y regulatoria de procesar tarjetas de crédito en tu propio sistema.
Gestión de Pagos y Pedidos
Existen varios proveedores de pasarelas de pago para elegir. Vamos a integrar Stripe, que es una pasarela de pago muy popular utilizada por servicios en línea como Shopify, Uber, Twitch y GitHub, entre otros.
Stripe proporciona una Interfaz de Programación de Aplicaciones (API) que te permite procesar pagos en línea con varios métodos de pago, como tarjeta de crédito, Google Pay y Apple Pay. Puedes obtener más información sobre Stripe en https://www.stripe.com/.
Stripe ofrece diferentes productos relacionados con el procesamiento de pagos. Puede gestionar pagos únicos, pagos recurrentes para servicios de suscripción, pagos multiparte para plataformas y mercados, entre otros.
Stripe ofrece diferentes métodos de integración, desde formularios de pago alojados por Stripe hasta flujos de pago totalmente personalizables. Vamos a integrar el producto Stripe Checkout, que consiste en una página de pago optimizada para la conversión. Los usuarios podrán pagar fácilmente con tarjeta de crédito u otros métodos de pago por los artículos que ordenen. Recibiremos notificaciones de pago de Stripe. Puedes ver la documentación de Stripe Checkout en https://stripe.com/docs/payments/checkout.
Al aprovechar Stripe Checkout para procesar pagos, confías en una solución que es segura y cumple con los requisitos de la Industria de Tarjetas de Pago (PCI). Podrás recopilar pagos de Google Pay, Apple Pay, Afterpay, Alipay, débitos directos SEPA, débitos directos Bacs, débitos directos BECS, iDEAL, Sofort, GrabPay, FPX y otros métodos de pago.
Creación de una cuenta en Stripe
Necesitas una cuenta en Stripe para integrar la pasarela de pago en tu sitio. Creemos una cuenta para probar la API de Stripe. Abre https://dashboard.stripe.com/register en tu navegador. Verás un formulario como el siguiente:
Rellena el formulario con tus datos y haz clic en Crear Cuenta. Recibirás un email con un link para verificar tu correo electrónico. El correo será similar a este:
Abre el email y haz clic en el enlace que se adjunta para verificar tu cuenta. Serás redirigido al panel de control de Stripe que tiene un aspecto parecido a este:En la esquina superior derecha de la pantalla, puedes ver que el modo de prueba está activado. Stripe te proporciona un entorno de prueba y un entorno de producción. Si tienes un negocio o eres autónomo, puedes agregar los detalles de tu empresa para activar la cuenta y obtener acceso para procesar pagos reales. Sin embargo, esto no es necesario para implementar y probar pagos a través de Stripe, ya que estaremos trabajando en el entorno de prueba.
Necesitas agregar un nombre de cuenta para procesar pagos. Abre https://dashboard.stripe.com/settings/account en tu navegador. Verás la siguiente pantalla:
En nombre de la cuenta, introduce un nombre y haz clic en guardar. Vuelve de nuevo al panel de control de Stripe. Verás como el nombre que hayas introducido aparece ahora en la parte superior izquierda de la página.
Lo siguiente va a ser instalar el Stipe Python SDK y añadir Stripe a nuestro proyecto de Django.
Instalando la libreria Stripe de Python.
Instala la librería Stripe desde el shell usando el siguiente comando:
pip install stripe
Añadiendo Stripe a nuestro proyecto.
Abre https://dashboard.stripe.com/test/apikeys en tu navegador. También puedes acceder a esta página desde el panel de Stripe haciendo clic en 'Desarrolladores' y luego en 'Claves API'. Verás la siguiente pantalla:
Stripe proporciona un par de claves para dos entornos diferentes, pruebas y producción. Hay una clave pública y una clave secreta para cada entorno. Las claves públicas del modo de prueba tienen el prefijo pk_test_ y las claves públicas del modo en vivo tienen el prefijo pk_live_. Las claves secretas del modo de prueba tienen el prefijo sk_test_ y las claves secretas del modo en vivo tienen el prefijo sk_live_.
Necesitarás esta información para autenticar las solicitudes a la API de Stripe. Siempre debes mantener tu clave privada en secreto y almacenarla de forma segura. La clave pública se puede utilizar en el código del lado del cliente, como scripts de JavaScript. Puedes obtener más información sobre las claves de la API de Stripe en https://stripe.com/docs/keys.
Añade la siguiente configuración al archivo settings.py de tu proyecto:
# Stripe settings
STRIPE_PUBLISHABLE_KEY = '' # Publishable key
STRIPE_SECRET_KEY = '' # Secret key
STRIPE_API_VERSION = '2023-10-16'
Reemplaza los valores STRIPE_PUBLISHABLE_KEY y STRIPE_SECRET_KEY con la clave pública de prueba y la clave secreta proporcionadas por Stripe. Utilizarás la versión 2023-10-16 de la API de Stripe. Puedes ver las notas de lanzamiento para esta versión de la API en https://stripe.com/docs/upgrades#2023-10-16.
Estás utilizando las claves del entorno de prueba para el proyecto. Una vez que vayas en vivo y valides tu cuenta de Stripe, obtendrás las claves del entorno de producción. Más adelante veremos cómo configurar ajustes para múltiples entornos.
Integremos la pasarela de pago en el proceso de pago. Puedes encontrar la documentación en Python para Stripe en https://stripe.com/docs/api?lang=python.
Construyendo el proceso de pago
El proceso de pago funcionará de la siguiente manera:
- Agregar artículos al carrito de compras
- Pagar el carrito de compras
- Ingresar detalles de la tarjeta de crédito y pagar
Vamos a crear una nueva aplicación para gestionar los pagos. Crea una nueva aplicación en tu proyecto usando el siguiente comando:
python manage.py startapp payment
Edita el archivo settings.py del proyecto y añade la nueva aplicación:
PracticaDjango/PracticaDjango/settings.py
#... 'Carro.apps.CarroConfig', 'Autentificacion.apps.AutentificacionConfig', 'Orders.apps.OrdersConfig', # Aplicaciones de terceros 'django_bootstrap5', 'social_django', 'django_extensions', 'easy_thumbnails', 'debug_toolbar', 'Payment.apps.PaymentConfig', # Mapa del Sitio 'django.contrib.sites', # add sites to installed_apps 'django.contrib.sitemaps', # add Django sitemaps to installed app # PostgreSQL 'django.contrib.postgres', # Aplicaciones por defecto
La aplicación de pagos está ahora activa en el proyecto.
Actualmente, los usuarios pueden realizar pedidos pero no pueden pagarlos. Después de que los clientes realicen un pedido, necesitamos redirigirlos al proceso de pago.
Edita el archivo views.py de la aplicación de pedidos (Orders) e incluye las siguientes importaciones:
PracticaDjango/Orders/views.py
from django.shortcuts import render from .models import OrderItem from .forms import OrderCreateForm from Carro.carro import Carro # Crea una tarea asincronica al finalizar la orden from .tasks import order_created # Para la gestion de la pasarela de pago from django.urls import reverse from django.shortcuts import redirect # Create your views here.
# launch asynchronous task
order_created.delay(order.id)
return render(request, "Orders/order/created.html", {"order": order})
y sustitúyelas por esta otra:
# launch asynchronous task
order_created.delay(order.id)
# guarda el pedido en la sesión
request.session['order_id'] = order.id
# redirecciona para hacer el pago
return redirect(reverse('payment:process'))
Ahora el archivo de las vista debería parecerse a esto:
PracticaDjango/Orders/views.py
from django.shortcuts import render from .models import OrderItem from .forms import OrderCreateForm from Carro.carro import Carro # Crea una tarea asincronica al finalizar la orden from .tasks import order_created # Para la gestion de la pasarela de pago from django.urls import reverse from django.shortcuts import redirect # Create your views here. def order_create(request): carro = Carro(request) if request.method == "POST": form = OrderCreateForm(request.POST) if form.is_valid(): order = form.save() for item in carro: OrderItem.objects.create( order=order, product=item["producto"], price=item["precio"], quantity=item["cantidad"], ) # Limpia el carro carro.clear() # launch asynchronous task order_created.delay(order.id) # guarda el pedido en la sesión request.session['order_id'] = order.id # redirecciona para hacer el pago return redirect(reverse('payment:process')) else: form = OrderCreateForm() return render(request, "Orders/order/create.html", {"cart": carro, "form": form})
Integrando Stripe Checkout
El proceso completo de pago funcionará de la siguiente manera:
1. Después de que se cree un pedido, el usuario es redirigido a la vista payment_process. Se le presenta al usuario un resumen del pedido y un botón para proceder con el pago.
2. Cuando el usuario procede a pagar, se crea una sesión de pago de Stripe. La sesión de pago incluye la lista de artículos que el usuario comprará, una URL para redirigir al usuario después de un pago exitoso y una URL para redirigir al usuario si el pago es cancelado.
3. La vista redirige al usuario a la página de pago alojada por Stripe. Esta página incluye el formulario de pago. El cliente ingresa los detalles de su tarjeta de crédito y envía el formulario.
4. Stripe procesa el pago y redirige al cliente a la vista payment_completed. Si el cliente no completa el pago, Stripe en su lugar redirige al cliente a la vista payment_canceled.
Comencemos a construir las vistas de pago. Edita el archivo views.py de la aplicación de pagos y agrega el siguiente código:
PracticaBlog/PracticaDjango/Payment/views.py
from django.shortcuts import render, redirect, reverse, get_object_or_404 from decimal import Decimal import stripe from django.conf import settings from Orders.models import Order # Creamos la instancia de Stripe stripe.api_key = settings.STRIPE_SECRET stripe.api_version = settings.STRIPE_API_VERSION def payment_process(request): order_id = request.session.get('order_id', None) order = get_object_or_404(Order, id=order_id) if request.method == 'POST': success_url = request.build_absolute_uri(reverse('payment:completed')) cancel_url = request.build_absolute_uri(reverse('payment:canceled')) # Stripe checkout session data session_data = { 'mode': 'payment', 'client_reference_id': order.id, 'success_url': success_url, 'cancel_url': cancel_url, 'line_items': [] } # create Stripe checkout session session = stripe.checkout.Session.create(**session_data) # redirect to Stripe payment form return redirect(session.url, code=303) else: return render(request, 'payment/process.html', locals())
En el código anterior, se importa el módulo "stripe" y se establece la clave de la API de Stripe usando el valor del ajuste "STRIPE_SECRET_KEY". También se establece la versión de la API que se va a utilizar usando el valor del ajuste "STRIPE_API_VERSION".
La vista "payment_process" realiza las siguientes tareas:
1. Se recupera el objeto de Pedido actual de la base de datos utilizando la clave de sesión "order_id", que fue almacenada previamente en la sesión por la vista "order_create".
2. Se recupera el objeto de Pedido para el ID dado. Al utilizar la función de atajo "get_object_or_404()", se genera una excepción Http404 (página no encontrada) si no se encuentra ningún pedido con el ID dado.
3. Si la vista se carga con una solicitud GET, se renderiza y devuelve la plantilla "payment/process.html". Esta plantilla incluirá el resumen del pedido y un botón para proceder con el pago, que generará una solicitud POST a la vista.
4. Si la vista se carga con una solicitud POST, se crea una sesión de pago de Stripe con "stripe.checkout.Session.create()" utilizando los siguientes parámetros:
- "mode": El modo de la sesión de pago. Usamos "payment" para un pago único. Puedes ver los diferentes valores aceptados para este parámetro en https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-mode.
- "client_reference_id": La referencia única para este pago. Usaremos esto para conciliar la sesión de pago de Stripe con nuestro pedido. Al pasar el ID del pedido, vinculamos los pagos de Stripe a los pedidos en nuestro sistema, y podremos recibir notificaciones de pago de Stripe para marcar los pedidos como pagados.
- "success_url": La URL a la que Stripe redirigirá al usuario si el pago es exitoso. Usamos "request.build_absolute_uri()" para generar un URI absoluto a partir de la ruta URL. Puedes ver la documentación para este método en https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.build_absolute_uri.
- "cancel_url": La URL a la que Stripe redirigirá al usuario si se cancela el pago.
- "line_items": Esta es una lista vacía. A continuación, la poblaremos con los elementos del pedido que se van a comprar.
5. Después de crear la sesión de pago, se devuelve una redirección HTTP con el código de estado 303 para redirigir al usuario a Stripe. El código de estado 303 se recomienda para redirigir aplicaciones web a un nuevo URI después de haber realizado una solicitud HTTP POST.
Puedes ver todos los parámetros para crear un objeto de sesión de Stripe en https://stripe.com/docs/api/checkout/sessions/create.
La función `locals()` en Python retorna un diccionario que representa las variables locales en el contexto actual. En el fragmento de código que hemos utilizado, se utiliza `locals()` dentro de la función 'render()'` para pasar todas las variables locales del contexto de la vista a la plantilla de renderizado.
```
return render(request, 'Payment/process.html', locals())
```
Lo que hace es tomar todas las variables locales en ese punto de la función 'payment_process' y las pasa a la plantilla como contexto. Esto significa que cualquier variable definida antes de la llamada a 'render()' estará disponible en la plantilla 'Payment/process.html'. Esta es una forma rápida de pasar todas las variables locales al contexto de la plantilla sin necesidad de enumerarlas explícitamente.
¡Vamos a poblar la lista "line_items" con los elementos del pedido para crear la sesión de pago! Cada elemento contendrá el nombre del artículo, la cantidad a cobrar, la moneda a utilizar y la cantidad comprada. Agrega el siguiente código resaltado en azul a la vista "payment_process":
PracticaBlog/PracticaDjango/Payment/views.py
from django.shortcuts import render, redirect, reverse, get_object_or_404
from decimal import Decimal
import stripe
from django.conf import settings
from Orders.models import Order
# Creamos la instancia de Stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
stripe.api_version = settings.STRIPE_API_VERSION
def payment_process(request):
order_id = request.session.get('order_id', None)
order = get_object_or_404(Order, id=order_id)
if request.method == 'POST':
success_url = request.build_absolute_uri(reverse('payment:completed'))
cancel_url = request.build_absolute_uri(reverse('payment:canceled'))
# Stripe checkout session data
session_data = {
'mode': 'payment',
'client_reference_id': order.id,
'success_url': success_url,
'cancel_url': cancel_url,
'line_items': []
}
# add order items to the Stripe checkout session
for item in order.items.all():
session_data['line_items'].append({
'price_data': {
'unit_amount': int(item.price * Decimal('100')),
'currency': 'eur',
'product_data': {
'name': item.product.name,
},
},
'quantity': item.quantity,
})
# create Stripe checkout session
session = stripe.checkout.Session.create(**session_data)
# redirect to Stripe payment form
return redirect(session.url, code=303)
else:
return render(request, 'Payment/process.html', locals())
Utilizamos la siguiente información para cada artículo:
- price_data: Información relacionada con el precio.
- unit_amount: La cantidad en centimos que se recogerá con el pago. Este es un número entero positivo que representa cuánto cobrar en la unidad de moneda más pequeña sin lugares decimales. Por ejemplo, para cobrar 10.00€, esto serían 1000 (es decir, 1,000 céntimos). El precio del artículo, item.price, se multiplica por Decimal(‘100’) para obtener el valor en céntimos y luego se convierte en un entero.
- currency: La moneda a utilizar en formato ISO de tres letras. Utilizamos 'eur' para euros. Puedes ver una lista de monedas admitidas en https://stripe.com/docs/currencies.
- product_data: Información relacionada con el producto.
- name: El nombre del producto.
- quantity: La cantidad de unidades a comprar.
La vista payment_process está ahora lista. Creemos vistas simples para las páginas de éxito y cancelación de pago.
Agrega el siguiente código al archivo views.py de la aplicación de pagos:
PracticaBlog/PracticaDjango/Payment/views.py
#... def payment_completed(request): return render(request, 'Payment/completed.html') def payment_canceled(request): return render(request, 'Payment/canceled.html')
Crea un nuevo archivo dentro del directorio de la aplicación y llámale urls.py. Añádele el siguiente código:
PracticaBlog/PracticaDjango/Payment/urls.py
from django.urls import path from . import views app_name = 'payment' urlpatterns = [ path('process/', views.payment_process, name='process'), path('completed/', views.payment_completed, name='completed'), path('canceled/', views.payment_canceled, name='canceled'), ]
Estas son las URL para el flujo de pago. Hemos incluido los siguientes patrones de URL:
• process: La vista que muestra el resumen del pedido al usuario, crea la sesión de pago de Stripe y redirige al usuario al formulario de pago alojado por Stripe.
• completed: La vista a la que Stripe redirige al usuario si el pago se realiza con éxito.
• canceled: La vista a la que Stripe redirige al usuario si se cancela el pago.
Edita el archivo urls.py principal del proyecto PracticaDjano e incluye los patrones de URL para la aplicación de pago, de la siguiente manera:
PracticaBlog/PracticaDjango/Payment/urls.py
#...
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('Proyecto_web_app.urls')),
path('servicios/', include('Servicios.urls')),
path('blog/', include('Blog.urls')),
path('contacto/', include('Contacto.urls')),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
path('payment/', include('Payment.urls', namespace='payment')),
path('tienda/', include('Tienda.urls')),
path('carro/', include('Carro.urls')),
path('cuenta/', include('Autentificacion.urls')),
path('social-auth/', include('social_django.urls', namespace='social')),
path('orders/', include('Orders.urls', namespace='orders')),
path('__debug__/', include('debug_toolbar.urls')),
]
#...
Hemos colocado la nueva ruta antes del patrón tienda.urls y caroo.urls para evitar una coincidencia no deseada con un patrón definido en ambas. Recuerda que Django recorre cada patrón de URL en orden y se detiene en el primero que coincide con la URL solicitada.
Construyamos una plantilla para cada vista. Crea la siguiente estructura de archivos dentro del directorio de la aplicación de pago:
templates/
Payment/
process.html
completed.html
canceled.html
Edita la plantilla process.html y añade el siguiente código:
PracticaDjango/Payment/templates/Payment/process.html
<!--Cargamos la plantilla base--> {% extends "Proyecto_web_app/base.html" %} {% load thumbnail %} <!-- Establecemos el titulo de la página --> {% block title %}Pagar el Pedido{% endblock %} <!-- Definimos su contenido --> {% block content %} <div class="container"> <h1>Resumen del Pedido</h1> <table class="table table-dark table-striped"> <thead> <tr> <th>Imagen</th> <th>Producto</th> <th>Precio</th> <th>Cantidad</th> <th>Total</th> </tr> </thead> <tbody> {% for item in order.items.all %} <tr> <td> <img src="{% thumbnail item.product.imagen 100x0 %}"> </td> <td>{{ item.product.nombre }}</td> <td class="num">{{ item.price }}€</td> <td class="num">{{ item.quantity }}</td> <td class="num">{{ item.get_cost }}€</td> </tr> {% endfor %} <tr> <td>Total</td> <td colspan="3"></td> <td class="num">{{ order.get_total_cost }} €</td> </tr> </tbody> </table> <div class="d-flex justify-content-end"> <p class="text-right"> <form action="{% url 'payment:process' %}" method="post"> <input type="submit" value="Pagar"> {% csrf_token %} </form> </p> </div> </div> {% endblock %}
Este es el formato para mostrar el resumen del pedido al usuario y permitir al cliente proceder con el pago. Incluye un formulario y un botón 'Pagar ahora' para enviarlo mediante POST. Cuando se envía el formulario, la vista payment_process crea la sesión de pago de Stripe y redirige al usuario al formulario de pago alojado por Stripe.
Edita la plantilla Payment/completed.html y añade el siguiente código a ella:
PracticaDjango/Payment/templates/Payment/completed.html
{% extends "Proyecto_web_app/base.html" %} {% block title %}Payment successful{% endblock %} {% block content %} <h1>Pago Realizado.</h1> <p>Su pago ha sido realizado correctamente.</p> <p>¡Muchas Gracias!</p> {% endblock %}
Esta es la plantilla de la página a la que el usuario será redirigido si el proceso de pago se efectúa correctamente.
Edita la plantilla Payment/canceled.html y añade el siguiente código a ella:
PracticaDjango/Payment/templates/Payment/canceled.html
{% extends "Proyecto_web_app/base.html" %} {% block title %}Payment canceled{% endblock %} {% block content %} <h1>Pago NO REALIZADO</h1> <p>Ha habido un problema al procesar el pago.</p> {% endblock %}
Esta es la plantilla de la página a la que el usuario será redirigido si el proceso de pago no se efectúa correctamente.
Con esto hemos implementado las vistas necesarias para realizar el proceso de pago, asi como los patrones de sus URLS asociadas. Es hora de probar si todo funciona.
Prueba del proceso de pago
Ejecuta el siguiente comando en la terminal para iniciar el servidor RabbitMQ con Docker:
sudo docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management-alpine
Esto ejecutará RabbitMQ en el puerto 5672 y la interfaz de gestión basada en web en el puerto 15672.
Abre otra terminal y inicia el worker de Celery desde el directorio de tu proyecto con el siguiente comando:
celery -A PracticaDjango worker -l info
Abre una terminal más y inicia el servidor de desarrollo desde el directorio de tu proyecto con este comando:
python manage.py runserver
Abre http://127.0.0.1:8000/ en tu navegador, agrega algunos productos al carrito de compras y completa el formulario de pago. Haz clic en el botón Realizar pedido. El pedido se guardará en la base de datos, el ID del pedido se guardará en la sesión actual y serás redirigido a la página de proceso de pago.
El aspecto de la página de proceso de pago será el siguiente:
En esta página, podrás ver un resumen del pedido y un botón de Pagar ahora. Haz clic en Pagar ahora. La vista del proceso de pago creará una sesión de pago en Stripe y serás redirigido al formulario de pago alojado por Stripe. Verás la siguiente página:
Utilizando tarjetas de crédito de prueba
Stripe proporciona diferentes tarjetas de crédito de prueba de diferentes emisores y países, lo que te permite simular pagos para probar todos los escenarios posibles (pago exitoso, pago rechazado, etc.). La siguiente tabla muestra algunas de las tarjetas que puedes usar para diferentes escenarios:
Puedes encontrar la lista completa de tarjetas de crédito para pruebas en https://stripe.com/docs/testing.
Vamos a usar la tarjeta de prueba 4242 4242 4242 4242, que es una tarjeta Visa que devuelve una compra exitosa. Utilizaremos el CVC 123 y cualquier fecha de vencimiento futura, como por ejemplo 12/29. Ingresa los detalles de la tarjeta de crédito en el formulario de pago de la siguiente manera:
Después de un par de segundos si todo ha ido correctamente, Stripe te redirigirá a la página que antes habíamos definido para una compra exitosa.
Verificando la información del pago en el panel de control de Stripe
Accede al panel de control de Stripe en https://dashboard.stripe.com/test/payments. Bajo 'Pagos', podrás ver el pago como se muestra en la siguiente imagen:
El estado del pago es 'Succeeded' (Exitoso). La descripción del pago incluye el ID del intento de pago que comienza con 'pi_'. Cuando se confirma una sesión de pago, Stripe crea un intento de pago asociado con la sesión. Un intento de pago se utiliza para recopilar un pago del usuario. Stripe registra todos los intentos de pago como intentos de pago. Cada intento de pago tiene un ID único y encapsula los detalles de la transacción, como los métodos de pago admitidos, el monto a cobrar y la moneda deseada. Haz clic en la transacción para acceder a los detalles del pago.
Verás la siguiente pantalla:
Puedes encontrar mucha más información en los enlaces de la página.
Usar webhooks para recibir notificaciones de pago
Creación de un punto final de webhook
En esta misma pantalla haz clic en "Probar en un entorno local". Verás la siguiente imagen:
/PracticaDjango/Payment/webhooks.py
import stripe from django.conf import settings from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from Orders.models import Order @csrf_exempt def stripe_webhook(request): payload = request.body sig_header = request.META['HTTP_STRIPE_SIGNATURE'] event = None try: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET) except ValueError as e: # Invalid payload return HttpResponse(status=400) except stripe.error.SignatureVerificationError as e: # Invalid signature return HttpResponse(status=400) return HttpResponse(status=200)
/PracticaDjango/Payment/webhooks.py
import stripe from django.conf import settings from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from Orders.models import Order @csrf_exempt def stripe_webhook(request): payload = request.body sig_header = request.META['HTTP_STRIPE_SIGNATURE'] event = None try: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET) except ValueError as e: # Invalid payload return HttpResponse(status=400) except stripe.error.SignatureVerificationError as e: # Invalid signature return HttpResponse(status=400) if event.type == 'checkout.session.completed': session = event.data.object if session.mode == 'payment' and session.payment_status == 'paid': try: order = Order.objects.get(id=session.client_reference_id) except Order.DoesNotExist: return HttpResponse(status=404) # marca el pedido como pagado order.paid = True order.save() return HttpResponse(status=200)
PracticaDjango/Payment/urls.py
from django.urls import path from . import views from . import webhooks app_name = 'payment' urlpatterns = [ path('process/', views.payment_process, name='process'), path('completed/', views.payment_completed, name='completed'), path('canceled/', views.payment_canceled, name='canceled'), path('webhook/', webhooks.stripe_webhook, name='stripe-webhook'), ]
Realizando una prueba de las notificaciones mediante webhooks.
bash
brew install stripe/stripe-cli/stripe
bash
stripe login
bash
# Crea un volumen para almacenar la configuración de Stripe CLI
sudo docker volume create stripe_config
# Ejecuta el contendor con el volumen montado
sudo docker run -it --rm -v stripe_config:/root stripe/stripe-cli login
Your pairing code is: xxxx-yyyy-zzzz-oooo This pairing code verifies your authentication with Stripe. Press Enter to open the browser or visit https://dashboard.stripe.com/ stripecli/confirm_auth?t=....
Your pairing code is: xxxx-yyyy-zzzz-oooo
This pairing code verifies your authentication with Stripe.
Press Enter to open the browser or visit https://dashboard.stripe.com/
stripecli/confirm_auth?t=....
Done! The Stripe CLI is configured for unikgame with account id acct_XXXXXXXXXXXXXXXXX
Please note: this key will expire after 90 days, at which point you'll need
to re-authenticate.
bash
stripe listen --forward-to localhost:8000/payment/webhook/
bash
# Después de crear el volumen, hacer login, puedes ejecutar el comando stripe listen
sudo docker run --network="host" -it --rm -v stripe_config:/root stripe/stripe-cli listen --forward-to localhost:8000/payment/webhook/
bash
stripe_config:/root stripe/stripe-cli listen --forward-to localhost:8000/payment/webhook/ > Ready! You are using Stripe API Version [2023-10-16]. Your webhook signing secret is XXXXXXXXXXXXXXXXXXXX (^C to quit)
bash
stripe_config:/root stripe/stripe-cli listen --forward-to localhost:8000/payment/webhook/
> Ready! You are using Stripe API Version [2023-10-16].
Your webhook signing secret is XXXXXXXXXXXXXXXXXXXX (^C to quit)
024-01-11 18:08:05 --> charge.succeeded [evt_...]
2024-01-11 18:08:05 --> payment_intent.succeeded [evt_...]
2024-01-11 18:08:05 <-- [200] POST http://localhost:8000/payment/webhook/ [evt_...]
2024-01-11 18:08:05 --> payment_intent.created [evt_...]
2024-01-11 18:08:05 <-- [200] POST http://localhost:8000/payment/webhook/ [evt_...]
2024-01-11 18:08:05 <-- [200] POST http://localhost:8000/payment/webhook/ [evt_...]
2024-01-11 18:08:05 --> checkout.session.completed [evt_...]
2024-01-11 18:08:05 <-- [200] POST http://localhost:8000/payment/webhook/ [evt_...]
Haciendo referencia a los pagos de Stripe en los pedidos
PracticaDjango/Orders/models.py
class Order(models.Model):
#...
stripe_id = models.CharField(max_length=250, blank=True)
PracticaDjango/Payment/webhooks.py
#...
@csrf_exempt
def stripe_webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
event = None
try:
event = stripe.Webhook.construct_event(
payload,
sig_header,
settings.STRIPE_WEBHOOK_SECRET)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
if event.type == 'checkout.session.completed':
session = event.data.object
if session.mode == 'payment' and session.payment_status == 'paid':
try:
order = Order.objects.get(id=session.client_reference_id)
except Order.DoesNotExist:
return HttpResponse(status=404)
# marca el pedido como pagado
order.paid = True
# store Stripe payment ID
order.stripe_id = session.payment_intent
order.save()
return HttpResponse(status=200)
PracticaDjango/Orders/models.py
from django.db import models from Tienda.models import Producto from django.conf import settings # Create your models here. class Order(models.Model): #... class Meta: #... def __str__(self): return f"Orden {self.id}" def get_total_cost(self): return sum(item.get_cost() for item in self.items.all()) def get_stripe_url(self): if not self.stripe_id: # no payment associated return '' if '_test_' in settings.STRIPE_SECRET_KEY: # Stripe path for test payments path = '/test/' else: # Stripe path for real payments path = '/' return f'https://dashboard.stripe.com{path}payments/{self.stripe_id}'
PracticaDjango/Orders/models.py
from django.contrib import admin from .models import Order, OrderItem from django.utils.safestring import mark_safe # Register your models here. def order_payment(obj): url = obj.get_stripe_url() if obj.stripe_id: html = f'<a href="{url}" target="_blank">{obj.stripe_id}</a>' return mark_safe(html) return '' order_payment.short_description = 'Stripe payment' class OrderItemInline(admin.TabularInline): model = OrderItem raw_id_fields = ['product'] @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ['id', 'first_name', 'last_name', 'email', 'address', 'postal_code', 'city', 'paid', order_payment, 'created', 'updated'] list_filter = ['paid', 'created', 'updated'] inlines = [OrderItemInline] #...
No hay comentarios:
Publicar un comentario