Aula 94 - Django - Ecommerce - Confirmação e Processamento de Pagamento

Código da aula: Github

Nesta aula, vamos implementar a lógica de confirmação e processamento de pagamento no nosso projeto de e-commerce usando o Stripe.

Certifique-se que está logado no Stripe

Faça login com sua conta Stripe stripe login Encaminhe eventos ao seu webhook stripe listen --forward-to localhost:4242/api/webhook Acione eventos com a CLI stripe trigger payment_intent.succeeded Vamos adicionar as views de sucesso e falha de pagamento, ajustar as URLs e atualizar o JavaScript para tratar essas situações.

Boas práticas no Github

Crie a branch da aula, vou chamar a minha de: feature/payment-confirmation-and-processing Usar feature/... para branches no GitHub, ajuda a organizar e identificar rapidamente branches dedicadas ao desenvolvimento de novas funcionalidades. Prefixos como feature/, bugfix/, hotfix/, e release/ são amplamente aceitos como boas práticas na comunidade de desenvolvimento e ajudam a manter um fluxo de trabalho consistente e organizado.

Atualizações no código

Views de Sucesso e Falha de Pagamento

Primeiramente, vamos criar duas novas views no arquivo views.py do billing, para renderizar as páginas de sucesso e falha de pagamento.

django_ecommerce/e_commerce/billing/views.py


from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
import stripe
import json

# Configure a chave da API do Stripe
stripe.api_key = settings.STRIPE_API_KEY

# View para renderizar a página de sucesso do pagamento
def payment_success_view(request):
    return render(request, 'billing/payment-success.html')

# View para renderizar a página de falha do pagamento
def payment_failed_view(request):
    return render(request, 'billing/payment-failed.html')

# View para renderizar a página de método de pagamento
def payment_method_view(request):
    # Adicione um log para verificar se a chave está sendo passada (opcional)
    # print(f"Publish Key na view: {settings.STRIPE_PUB_KEY}")
    context = {'publish_key': settings.STRIPE_PUB_KEY}
    return render(request, 'billing/payment-method.html', context)

# View para criar o PaymentIntent do Stripe
@csrf_exempt
@require_POST
def create_payment_intent(request):
    data = json.loads(request.body)
    try:
        # Calcular o valor com base nos itens enviados
        # Substitua esta função pela sua lógica de cálculo de preços
        amount = calculate_order_amount(data['items'])

        intent = stripe.PaymentIntent.create(
            amount=amount,
            currency='usd',
            payment_method_types=['card'],
        )
        return JsonResponse({'clientSecret': intent.client_secret})
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=400)

# Função para calcular o valor do pedido
def calculate_order_amount(items):
    # Substitua esta função pela sua lógica de cálculo de preços
    return 1400  # preço de exemplo

Atualização das URLs

Agora vamos adicionar as novas URLs no nosso arquivo urls.py.

e_commerce/e_commerce/urls.py


from django.conf import settings
from django.conf.urls.static import static

from django.contrib import admin
from django.contrib.auth.views import LogoutView 
from django.urls import path, include
from django.views.generic import TemplateView
from carts.views import cart_detail_api_view
from accounts.views import LoginView, RegisterView, LogoutView, guest_register_view
from addresses.views import checkout_address_create_view, checkout_address_reuse_view
from billing.views import create_payment_intent, payment_method_view, payment_success_view, payment_failed_view
from .views import (home_page,  
                    about_page, 
                    contact_page
)

urlpatterns = [
    path('', home_page, name='home'),
    path('about/', about_page, name='about'),
    path('contact/', contact_page, name='contact'),
    path('cart/', include("carts.urls", namespace="cart")),
    path('checkout/address/create/', checkout_address_create_view, name='checkout_address_create'),
    path('checkout/address/reuse/', checkout_address_reuse_view, name='checkout_address_reuse'),
    path('api/cart/', cart_detail_api_view, name='api-cart'),
    path('login/', LoginView.as_view(), name='login'),
    path('create-payment-intent', create_payment_intent, name='create-payment-intent'),
    path('billing/payment-method/', payment_method_view, name='billing-payment-method'),
    path('billing/payment-success/', payment_success_view, name='payment-success'),
    path('billing/payment-failed/', payment_failed_view, name='payment-failed'),
    path('register/guest/', guest_register_view, name='guest_register'),
    path('logout/', LogoutView.as_view(), name='logout'),
    path('register/', RegisterView.as_view(), name='register'),
    path('bootstrap/', TemplateView.as_view(template_name='bootstrap/example.html')),
    path('search/', include("search.urls", namespace="search")),
    path('products/', include("products.urls", namespace="products")),
    path('admin/', admin.site.urls),
]

if settings.DEBUG:
    urlpatterns = urlpatterns + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns = urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Atualização do JavaScript

Vamos agora atualizar o arquivo JavaScript stripe_payment.js para redirecionar para as páginas de sucesso e falha de pagamento, dependendo do resultado do processamento do pagamento.

static_local/js/stripe_payment.js

$(document).ready(function () {
  console.log("Documento pronto");
  function initializeStripe() {
    const stripeKeyElement = $("#stripe-key");
    if (stripeKeyElement.length === 0) {
      console.error("Elemento stripe-key não encontrado");
      return;
    }
    const publishKey = stripeKeyElement.data("publishKey");
    if (!publishKey) {
      console.error("Chave pública do Stripe não encontrada");
      return;
    }
    const stripe = Stripe(publishKey);
    let elements,
      cardElement,
      clientSecret = null;
    function initialize() {
      $.ajax({
        url: "/create-payment-intent",
        method: "POST",
        contentType: "application/json",
        headers: {
          "X-CSRFToken": getCookie("csrftoken"),
        },
        data: JSON.stringify({ items: [{ id: "xl-tshirt", price: 2000 }] }),
        success: function (data) {
          if (data.error) {
            console.error("Falha ao criar o intent de pagamento:", data.error);
            return;
          }
          clientSecret = data.clientSecret;

          elements = stripe.elements();
          cardElement = elements.create("card");
          cardElement.mount("#payment-element");
          console.log("Elemento do cartão montado");
        },
        error: function (xhr, status, error) {
          console.error("Erro ao inicializar os elementos do Stripe:", error);
        },
      });
    }
    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();
          if (cookie.substring(0, name.length + 1) === name + "=") { 
            cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 
            break; 
          } 
        } 
      } 
      return cookieValue; 
    } 
    const paymentForm = $("#payment-form"); 
    if (paymentForm.length > 0) {
      paymentForm.on("submit", function (event) {
        event.preventDefault();
        if (!elements || !cardElement) {
          showMessage(
            "Elemento do cartão não inicializado corretamente.",
            true
          );
          return;
        }
        setLoading(true);
        stripe
          .confirmCardPayment(clientSecret, {
            payment_method: {
              card: cardElement,
              billing_details: {
                // Inclua os detalhes de faturamento se necessário
              },
            },
          })
          .then(function (result) {
            if (result.error) {
              showMessage(result.error.message, true);
              setTimeout(function () {
                window.location.href = "/billing/payment-failed/";
              }, 3000);
            } else if (
              result.paymentIntent &&
              result.paymentIntent.status === "succeeded"
            ) {
              showMessage("Pagamento realizado com sucesso!", false);
              setTimeout(function () {
                window.location.href = "/billing/payment-success/";
              }, 3000);
            } else {
              showMessage(
                "Pagamento não foi bem-sucedido. Tente novamente.",
                true
              );
              setTimeout(function () {
                window.location.href = "/billing/payment-failed/";
              }, 3000);
            }
            setLoading(false);
          })
          .catch(function (error) {
            showMessage("Ocorreu um erro inesperado.", true);
            setTimeout(function () {
              window.location.href = "/billing/payment-failed/";
            }, 3000);
            setLoading(false);
          });
      });
    } else {
      console.error("Formulário de pagamento não encontrado");
    }
    function showMessage(messageText, isError) {
      const messageContainer = $("#payment-message");
      messageContainer.text(messageText).removeClass("hidden");
      if (isError) {
        messageContainer.addClass("error-message");
      } else {
        messageContainer.removeClass("error-message");
      }
      setTimeout(function () {
        messageContainer.addClass("hidden").text("");
      }, 4000);
    }
    function setLoading(isLoading) {
      const submitButton = $("#submit");
      submitButton.prop("disabled", isLoading);
      $("#spinner").toggleClass("hidden", !isLoading);
      $("#button-text").toggleClass("hidden", isLoading);
    }
    initialize();
  }
  // Inicializa o Stripe diretamente se o elemento estiver presente
  const stripeKeyElement = $("#stripe-key");
  if (stripeKeyElement.length > 0) {
    initializeStripe();
  }
});


Mudanças

  1. Adição do parâmetro isError na função showMessage para distinguir entre mensagens de sucesso e erro.
  2. Atualização das chamadas para showMessage para incluir o novo parâmetro false,showMessage("Pagamento realizado com sucesso!", false);, indicando que esta é uma mensagem de sucesso e não um erro.
  3. Simplificação do redirecionamento pós-pagamento para URLs fixas e claras, com uma redução no tempo de espera.
  4. Implementação de redirecionamento para a página de falha de pagamento em caso de erro durante a confirmação do pagamento.
Isso melhora a clareza, usabilidade e feedback visual para o usuário, bem como simplifica a lógica do código.

Templates de Sucesso e Falha de Pagamento

Crie os arquivos de templates para as páginas de sucesso e falha de pagamento. Template de Sucesso (billing/templates/billing/payment-success.html):

billing/templates/billing/payment-success.html


<!-- Começo do template -->
{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h1>Pagamento realizado com sucesso!</h1>
    <p>Seu pedido foi processado com sucesso. Obrigado por sua compra!</p>
    <a href="{% url 'home' %}" class="btn btn-primary">Voltar para a página inicial</a>
</div>
{% endblock %}
<!-- Fim do template -->

Template de Falha (billing/templates/billing/payment-failed.html):

billing/templates/billing/payment-failed.html


<!-- Começo do template -->
{% extends 'base.html' %}
{% block content %}
<div class="container">
    <h1>Falha no pagamento</h1>
    <p>Ocorreu um problema ao processar seu pagamento. Por favor, tente novamente.</p>
    <a href="{% url 'billing-payment-method' %}" class="btn btn-primary">Tentar novamente</a>
</div>
{% endblock %}
<!-- Fim do template -->

Testando

Suba o servidor

python manage.py runserver Acesse: 127.0.0.1:8000/billing/payment-method Nesta aula, implementamos a lógica de confirmação e processamento de pagamento no nosso projeto de e-commerce usando Django e Stripe. Adicionamos views para sucesso e falha de pagamento, ajustamos as URLs e atualizamos o JavaScript para tratar essas situações. Com isso, agora seu sistema de pagamento está completo e você pode testar o fluxo de pagamento no seu ambiente de desenvolvimento. Na próxima aula, vamos aprimorar nosso sistema de pagamento implementando a lógica para calcular o valor total do pedido com base nos itens do carrinho. Além disso, vamos criar uma API para obter os itens do carrinho em tempo real e integrar essa funcionalidade ao frontend. Isso permitirá que o valor do pedido seja calculado dinamicamente, oferecendo uma experiência mais precisa e eficiente para os usuários.

Nos vemos na próxima aula! Bons estudos!

Nos vemos na próxima aula!

Bons estudos!