Compra retiro en línea en la tienda: Bonjour Meal - Parte 3 - Integración con un procesador de pagos

1. Introducción

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Última actualización: 13/09/2021

Recopilar pagos

Recopilar pagos en Business Messages tiene un nuevo mundo de oportunidades de negocios en la plataforma conversacional. Imagine que un cliente potencial le envía una consulta sobre un producto y desea obtener más información. Una vez que tengan las respuestas a sus preguntas, podrás cerrar el acuerdo con ellos mediante una puerta de enlace de pago dentro de la conversación.

Fe0c6754fb69d708.png

¿Qué aspectos ofrecen una buena experiencia de pago?

Una buena experiencia de pago es aquella en la que los usuarios pueden pagar de la forma en la que están acostumbrados.

Los usuarios tienen preferencias con respecto a la forma de pago, y las diferentes formas de pago son más comunes que otras en diferentes partes del mundo. Con Business Messages puedes integrar en más de un procesador de pagos para lograr el mayor nivel de conveniencia para los usuarios.

Una vez que un usuario completa un flujo de pago, debes informarle que recibiste su pago correctamente. La mayoría de los procesadores de pagos incluyen una devolución de llamada exitosa o con errores que envía una solicitud HTTP a la URL que elijas cuando se completa el flujo de pago.

Qué compilarás

En la sección anterior de la serie de codelab, extendiste el agente de comida Bonjour para presentar un catálogo de artículos, crear un carrito de compras que permite a los usuarios agregar y quitar artículos, y calcular el precio total de las compras. carrito. En esta sección, extenderás aún más el agente para que pueda procesar pagos según el contenido del carrito de compras.

En este codelab, tu app

  • Integrar con la puerta de enlace de pago de Stripe
  • Permitir que un usuario complete el flujo de pagos en función del precio del carrito
  • Enviar una notificación de vuelta a la superficie de conversación para informar al usuario sobre el estado del pago

ba08a4d2f8c09c0e.png

Actividades

  • Integra el procesador de pagos de Stripe.
  • Envía una solicitud a Stripe para iniciar una sesión de pago.
  • Maneja las respuestas de éxito o error de pagos de Stripe.

Requisitos

  • Un proyecto de GCP que esté registrado y aprobado para su uso con Business Messages
  • Consulte nuestro sitio para desarrolladores a fin de obtener instrucciones sobre cómo hacerlo
  • Un dispositivo con Android 5 o versiones posteriores O un dispositivo iOS con la app de Google Maps
  • Tiene experiencia en programación de aplicaciones web.
  • Una conexión a Internet

2. Cómo agregar dependencias

Cómo actualizar requirements.txt

Dado que realizaremos la integración con el procesador de pagos de Stripe, podemos usar la biblioteca cliente de Stripe para Python. Agrega stripe a requirements.txt sin una versión para obtener la última versión de la dependencia.

Esto es necesario para que el entorno de ejecución de Python de Google Cloud App Engine incluya el módulo de Python con franjas.

requirements.txt

...
stripe
...

Cómo preparar bopis/views.py

En la parte superior de bopis/views.py, importa render desde django.shortcuts y JsonResponse desde django.http. Además, necesitaremos importar stripe a fin de admitir las llamadas a la biblioteca cliente de Stripe para Python.

...
from django.shortcuts import render
from django.http import JsonResponse
import stripe
...

3. Trabajar con Stripe

Crea una cuenta en Stripe.com

En este codelab, acabamos de usar Stripe, pero puedes integrarlo con cualquier procesador que admita la integración web. Crea una cuenta en stripe.com. Usaremos este perfil para realizar pruebas y con fines educativos a fin de aprender a integrarlo directamente con cualquier procesador de pagos de terceros.

6731d123c56feb67.png

Una vez que haya creado una cuenta y haya accedido, debería ver un panel como este.

6d9d165d2d1fbb8c.png

Asegúrate de estar operando en modo de prueba y haz clic en el botón Developers como se muestra en la captura de pantalla anterior para buscar tus claves de API. Debería ver dos conjuntos de claves de API: una clave publicable y una clave secreta. Necesitarás ambas claves para facilitar las transacciones de pago con Stripe.

Actualiza bopis/views.py

Tu aplicación necesita ambos conjuntos de claves, así que debes actualizarlos en views.py.

Puedes establecer la clave secreta directamente en la propiedad Stripe.api_key y asignarle el valor de la clave secreta que se encuentra en el panel para desarrolladores de Stripe. Luego, crea una variable global llamada STRIPE_PUBLIC_KEY y establécela en la clave publicable.

Además, Stripe necesita redireccionar a una página web que administres, por lo que crearemos una variable global adicional para incluir el dominio accesible de forma pública de tu aplicación.

Al final de estas modificaciones, tendrá algo como lo siguiente:

stripe.api_key = 'sk_test_abcde-12345'
STRIPE_PUBLIC_KEY = 'pk_test_edcba-54321'
YOUR_DOMAIN = 'https://<GCP_PROJECT_ID>.appspot.com'

Eso es todo lo que necesitas hacer para la configuración de Stripe.

4. Funcionalidad de confirmación de la compra

Actualiza la función de precio total del carrito de compras

Actualmente, la función send_shopping_cart_total_price solo envía un mensaje que especifica el precio del carrito de compras. Agreguemos una acción sugerida para abrir una URL a la página de confirmación de compras.

def send_shopping_cart_total_price(conversation_id):
  """Sends shopping cart price to the user through Business Messages.

  Args:
    conversation_id (str): The unique id for this user and agent.
  """
  cart_price = get_cart_price(conversation_id)

  message_obj = BusinessMessagesMessage(
      messageId=str(uuid.uuid4().int),
      representative=BOT_REPRESENTATIVE,
      text=f'Your cart\'s total price is ${cart_price}.',
      suggestions=[
          BusinessMessagesSuggestion(
              action=BusinessMessagesSuggestedAction(
                  text='Checkout',
                  postbackData='checkout',
                  openUrlAction=BusinessMessagesOpenUrlAction(
                      url=f'{YOUR_DOMAIN}/checkout/{conversation_id}'))),
      ]
    )

  send_message(message_obj, conversation_id)

Cuando el usuario presiona esta acción sugerida, se lo dirige a una página web que muestra el precio total y un botón para iniciar el pago con Stripe.

Creemos una página web simple que admita este flujo.

En el código fuente del proyecto, busca el directorio llamado bopis. Crea un directorio nuevo dentro de bopis llamado templates y, dentro de las plantillas, crea otro directorio llamado bopis. Este es un patrón de diseño de npm para especificar el nombre de la app dentro del directorio de plantillas. Además, ayuda a reducir la confusión de plantillas entre las apps de JobScheduler.

Ahora deberías tener un directorio con una ruta de acceso en bopis/templates/bopis/. Puedes crear archivos HTML en este directorio para entregar páginas web. ⌘ se refiere a ellas como plantillas que se renderizan en el navegador. Comencemos con checkout.html.

En este directorio, crea checkout.html. En el siguiente fragmento de código, se muestra un botón de confirmación de la compra y el precio del carrito. También incluye JavaScript para iniciar la confirmación de la compra en Stripe.

{% load static %}

<!DOCTYPE html>
<html>
  <head>
    <title>Purchase from Bonjour Meal</title>

    <script src="https://js.stripe.com/v3/"></script>
    <style>
      .description{
        font-size: 4em;
      }
      button {
        color: red;
        padding: 40px;
        font-size: 4em;
      }
    </style>
  </head>
  <body>
    <section>
      <img
        src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
        alt="Bonjour Meal Restaurant"
      />
      <div class="product">
        <div class="description">
          <h3>Your Bonjour Meal Total</h3>
          <h5>${{cart_price}}</h5>
        </div>
      </div>
      <button type="button" id="checkout-button">Checkout</button>
    </section>
  </body>
  <script type="text/javascript">
    // Create an instance of the Stripe object with your publishable API key
    var stripe = Stripe("{{stripe_public_key}}");
    var checkoutButton = document.getElementById("checkout-button");

    checkoutButton.addEventListener("click", function () {
      fetch("/create-checkout-session/{{conversation_id}}", {
        method: "POST",
      })
        .then(function (response) {
          return response.json();
        })
        .then(function (session) {
          return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function (result) {
          // If redirectToCheckout fails due to a browser or network
          // error, you should display the localized error message to your
          // customer using error.message.
          if (result.error) {
            alert(result.error.message);
          }
        })
        .catch(function (error) {
          console.error("Error:", error);
        });
    });
  </script>
</html>

Necesitamos una ruta a esta página web cuando se solicita la URL. La acción sugerida de la confirmación de la compra tiene un valor de openUrlAction establecido en {YOUR_DOMAIN}/checkout/{conversation_id}. Esto se traduce en https://<GCP-Project-ID>.appspot.com/checkout/abc123-cba321-abc123-cba321. Antes de crear esta ruta, revisemos el código JavaScript que se encuentra en la plantilla HTML.

...
  <script type="text/javascript">
    // Create an instance of the Stripe object with your publishable API key
    var stripe = Stripe("{{stripe_public_key}}");
    var checkoutButton = document.getElementById("checkout-button");

    checkoutButton.addEventListener("click", function () {
      fetch("/create-checkout-session/{{conversation_id}}", {
        method: "POST",
      })
        .then(function (response) {
          return response.json();
        })
        .then(function (session) {
          return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function (result) {
          // If redirectToCheckout fails due to a browser or network
          // error, you should display the localized error message to your
          // customer using error.message.
          if (result.error) {
            alert(result.error.message);
          }
        })
        .catch(function (error) {
          console.error("Error:", error);
        });
    });
  </script>
...

Revisemos el fragmento de código anterior.

  1. Primero, crea una entidad de Stripe con la clave pública que se pasa por el contexto desde la función de vista, otro paradigma de ⌘.
  2. Luego, el fragmento busca un elemento en la página con el ID checkout-button.
  3. Se agrega un objeto de escucha de eventos a ese elemento.

Este objeto de escucha de eventos se activará cuando un usuario haga clic en este botón o lo presione para iniciar una solicitud POST al servidor web que especifiques a través de la URL: {YOUR_DOMAIN}/create-checkout-session/{conversation_id}..

Puedes ver la lógica del servidor web en los siguientes fragmentos. Cuando el usuario presiona el botón con el ID "checkout-button", podemos esperar que muestre un ID de sesión de Stripe que se crea usando la API de Stripe y especifica el precio del carrito.

Si tu servidor pudo producir un ID de sesión válido, la lógica de la aplicación redireccionará al usuario a una página de confirmación de la compra de Stripe; de lo contrario, alertará al usuario con un mensaje de JavaScript estándar que indicará que se produjo un error.

Comencemos por agregar rutas de acceso nuevas al array de urlpatterns para admitir la página de confirmación de la compra y generar el ID de sesión. Agregue lo siguiente al array de urlpatterns en urls.py.

...
path('checkout/<str:conversation_id>', bopis_views.payment_checkout),
path('create-checkout-session/<str:conversation_id>', bopis_views.create_checkout_session),
...

A continuación, vamos a crear las funciones de vista en views.py para mostrar la plantilla checkout.html y generar la sesión de confirmación de compra de Stripe.

...

def payment_checkout(request, conversation_id):
  """Sends the user to a payment confirmation page before the payment portal.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """

  cart_price = get_cart_price(conversation_id)
  context = {'conversation_id': conversation_id,
             'stripe_public_key': STRIPE_PUBLIC_KEY,
             'cart_price': cart_price
            }
  return render(request, 'bopis/checkout.html', context)

@csrf_exempt
def create_checkout_session(request, conversation_id):
  """Creates a Stripe session to start a payment from the conversation.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """
  cart_price = get_cart_price(conversation_id)
  try:
    checkout_session = stripe.checkout.Session.create(
        payment_method_types=['card'],
        line_items=[
            {
                'price_data': {
                    'currency': 'usd',
                    'unit_amount': int(cart_price*100),
                    'product_data': {
                        'name': 'Bonjour Meal Checkout',
                        'images': ['https://storage.googleapis.com/bonjour-rail.appspot.com/apple-walnut-salad.png'],
                    },
                },
                'quantity': 1,
            },
        ],
        mode='payment',
        success_url=YOUR_DOMAIN + '/success/' + conversation_id,
        cancel_url=YOUR_DOMAIN + '/cancel/' + conversation_id,
    )

    return JsonResponse({
        'id': checkout_session.id
    })

  except Exception as e:
    # Handle exceptions according to your payment processor's documentation
    # https://stripe.com/docs/api/errors/handling?lang=python
    return HttpResponse(e)

...

Ambas funciones usan el ID de conversación a fin de asociar el carrito de compras con el usuario y, luego, determinar el precio que Stripe debería cobrarle.

Estos dos métodos componen la primera mitad del flujo de pago. Si implementas esto y pruebas la experiencia, verás un formulario de confirmación de la compra de Stripe en el que podrás completar el pago con una tarjeta de crédito de prueba, como se indica en la documentación para desarrolladores de Stripe a fin de probar la confirmación de compra de Visa.

La segunda mitad del flujo es cómo volvemos al usuario a la conversación una vez que recibimos la respuesta de Stripe con respecto al pago del usuario.

5. Respuestas con Stripe

Cuando un usuario participa en tu flujo de pago, puede completar el pago correctamente o no. En la función create_checkout_session, definimos un success_url y un cancel_url. Stripe redireccionará a una de estas dos URL según el estado del pago. Definamos estas dos rutas en urls.py y luego agreguemos dos funciones de vista a bopis/views.py para que admitan estos dos flujos posibles.

Agrega estas líneas al archivo urls.py.

...
    path('success/<str:conversation_id>', bopis_views.payment_success),
    path('cancel/<str:conversation_id>', bopis_views.payment_cancel),
...

Las vistas correspondientes se verán de la siguiente manera:

...

def payment_success(request, conversation_id):
  """Sends a notification to the user prompting them back into the conversation.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """
  message_obj = BusinessMessagesMessage(
      messageId=str(uuid.uuid4().int),
      representative=BOT_REPRESENTATIVE,
      suggestions=[
          BusinessMessagesSuggestion(
              reply=BusinessMessagesSuggestedReply(
                  text='Check on order', postbackData='check-order')),
      ],
      text='Awesome it looks like we\'ve received your payment.')

  send_message(message_obj, conversation_id)

  return render(request, 'bopis/success.html')

def payment_cancel(request, conversation_id):
  """Sends a notification to the user prompting them back into the conversation.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """
  message_obj = BusinessMessagesMessage(
      messageId=str(uuid.uuid4().int),
      representative=BOT_REPRESENTATIVE,
      suggestions=[
          BusinessMessagesSuggestion(
              action=BusinessMessagesSuggestedAction(
                  text='Checkout',
                  postbackData='checkout',
                  openUrlAction=BusinessMessagesOpenUrlAction(
                      url=f'{YOUR_DOMAIN}/checkout/{conversation_id}'))),
      ],
      text='It looks like there was a problem with checkout. Try again?')

  send_message(message_obj, conversation_id)

  return render(request, 'bopis/cancel.html')

...

Stripe redirecciona al dominio como especificaste en la constante DOMAIN, lo que significa que debes renderizar una respuesta HTML a través de una plantilla o el sitio web se verá muy simple. Creemos dos archivos HTML simples en el directorio bopis/templates/bopis/ junto con checkout.html.

bm-django-echo-bot/bopis/ templates/bopis/success.html

{% load static %}

<html>
<head>
  <title>Business Messages Payment Integration Sample!</title>
  <style>
    p{
      font-size: 4em;
    }
  </style>
</head>
<body>
  <section>

    <p>
      Checkout succeeded - We appreciate your business!
      <br/><br/>
      For support related questions, please email
      <a href="mailto:bm-support@google.com">bm-support@google.com</a>.

    </p>
  </section>
</body>
</html>

bm-django-echo-bot/bopis/ templates/bopis/cancel.html

{% load static %}

<html>
<head>
  <title>Checkout canceled</title>
  <style>
    p{
      font-size: 4em;
    }
    </style>
</head>
<body>
  <section>
    <p>Checkout canceled - Forgot to add something to your cart? Shop around then come back to pay!</p>
  </section>
</body>
</html>

Con estas dos plantillas, un usuario que completa un flujo de pago con tu integración en Stripe se redirecciona a las URL correspondientes y se presenta con las plantillas correspondientes. Además, recibirán un mensaje mediante Business Messages que les permitirá volver a la conversación.

6. Recibir pagos

¡Felicitaciones! Integraste correctamente un procesador de pagos en tu agente de Business Messages.

En esta serie, implementó una aplicación web en Google Cloud App Engine, configuró su webhook en Business Communications Developer Console, extendió la aplicación para que admita la búsqueda de inventario a través de una base de datos estática y creó un carrito de compras con Google Datastore. En la parte final de la serie, integraste con Stripe, un procesador de pagos que admite integraciones web y con esta experiencia. Ahora puedes participar en integraciones con otros procesadores de pagos y mucho más.

d6d80cf9c9fc621.png 44db8d6441dce4c5.png

¿Qué sigue?

Cuando estés listo, consulta algunos de los siguientes temas para obtener más información sobre interacciones más complejas en Business Messages:

Documentos de referencia