Compra en línea, retira en la tienda: Bonjour Meal: Parte 2: Crea un carrito de compras

1. Introducción

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Última actualización: 30 de octubre de 2020

Crea un carrito de compras en Business Messages

Este es el segundo codelab de una serie cuyo objetivo es crear un recorrido del usuario de Compra en línea y retiro en la tienda. En muchos recorridos de comercio electrónico, un carrito de compras es clave para convertir a los usuarios en clientes que pagan. El carrito de compras también es una manera de comprender mejor a tus clientes y ofrecerles sugerencias de otros artículos que puedan interesarles. En este codelab, nos enfocaremos en crear la experiencia de carrito de compras y en implementar la aplicación en Google App Engine.

¿Qué características tiene un buen carrito de compras?

Los carritos de compras son clave para lograr una experiencia de compra en línea exitosa. Además, Business Messages no solo facilita las preguntas y respuestas sobre un producto para un cliente potencial, sino que también puede simplificar toda la experiencia de compra hasta completar un pago dentro de la conversación.

9d17537b980d0e62.png

Más allá de un buen carrito de compras, una buena experiencia de compra permite que los usuarios exploren los artículos por categoría y que la empresa recomiende otros productos que podrían interesarle al comprador. Después de agregar más artículos al carrito, el usuario puede revisarlo completo y quitar o agregar artículos antes de pagar.

Qué compilarás

En esta sección de la serie de codelab, ampliarás el agente digital que creaste en la parte 1 para la empresa ficticia Bonjour Meal a fin de que los usuarios puedan explorar un catálogo de artículos y agregarlos a un carrito de compras.

En este codelab, tu app hará lo siguiente:

  • Mostrar un catálogo de preguntas en Business Messages
  • Sugerir artículos que pueden interesarles a los usuarios
  • Revisar el contenido del carrito de compras y crear un resumen del precio total

ab2fb6a4ed33a129.png

Qué aprenderás

  • Cómo implementar una aplicación web en App Engine en Google Cloud Platform
  • Cómo usar un mecanismo de almacenamiento persistente para guardar el estado de un carrito de compras

Este codelab se enfoca en extender el agente digital de la parte 1 de esta serie de codelab.

Requisitos

2. Cómo prepararte

En este codelab, se supone que creaste tu primer agente y completaste la parte 1. Por lo tanto, no repasaremos los conceptos básicos de la habilitación de las API de Business Messages y Business Communications, la creación de claves de cuentas de servicio, la implementación de aplicaciones ni la configuración de tu webhook en Business Communications Console. Dicho esto, clonaremos una aplicación de ejemplo para asegurarnos de que sea coherente con lo que estamos compilando y habilitaremos la API de Datastore en Google Cloud Platform para conservar los datos del carrito de compras.

Clona la aplicación desde GitHub

En una terminal, clona la Muestra de Django Echo Bot en el directorio de trabajo de tu proyecto con el siguiente comando:

$ git clone https://github.com/google-business-communications/bm-bonjour-meal-django-starter-code

Copia el archivo de credenciales JSON que creaste para la cuenta de servicio en la carpeta de recursos de la muestra y cambia el nombre de las credenciales a “bm-agent-service-account-credentials.json”.

bm-bonjour-meal-django-starter-code/bonjourmeal-codelab/step-2/resources/bm-agent-service-account-credentials.json

Habilita la API de Google Datastore

En la Parte 1 de este codelab, habilitaste la API de Business Messages, la API de Business Communications y la API de Cloud Build.

En este codelab, debido a que trabajaremos con Google Datastore, también necesitamos habilitar esta API:

  1. Abre la API de Google Datastore en Google Cloud Console.
  2. Asegúrate de estar trabajando con el proyecto de GCP correcto.
  3. Haz clic en Habilitar.

Implementa la aplicación de ejemplo

En una terminal, navega al directorio del paso 2 de la muestra.

Ejecuta los siguientes comandos en una terminal para implementar la muestra:

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • PROJECT_ID es el ID del proyecto que usaste para registrarte en las API.

Observa la URL de la aplicación implementada en el resultado del último comando:

Deployed service [default] to [https://PROJECT_ID.appspot.com]

El código que acabas de implementar contiene una aplicación web con un webhook para recibir mensajes de Business Messages. Contiene todo lo que hicimos en la parte 1 del codelab. Si aún no lo hiciste, configura tu webhook.

La aplicación responderá algunas consultas sencillas, por ejemplo, las preguntas de un usuario sobre el horario de atención de Bonjour Meal. Debes probar esto en tu dispositivo móvil a través de las URLs de prueba que puedes recuperar de la sección Información sobre el agente en Business Communications Console. Las URLs de prueba lanzarán la experiencia de Business Messages en tu dispositivo móvil, y podrás comenzar a interactuar con tu agente allí.

3. El catálogo de productos

Un sistema de inventario

En la mayoría de los casos, puedes integrar directamente el inventario de una marca mediante una API interna. En otras instancias, puedes extraer una página web o crear tu propio sistema de seguimiento de inventario. Nuestro objetivo no es crear un sistema de inventario. Usaremos un archivo estático sencillo con imágenes e información de los productos para nuestro agente. En esta sección, extraeremos información de este archivo estático, la mostraremos en la conversación y permitiremos que el usuario explore los artículos disponibles para agregarlos a un carrito de compras.

El archivo del inventario estático se verá de la siguiente manera:

bonjourmeal-codelab/step-2/resources/inventory.json

{

    "food": [
        {
            "id":0,
            "name": "Ham and cheese sandwich",
            "price": "6.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/ham-and-cheese.png",
            "remaining": 8
        },
        {
            "id":1,
            "name": "Chicken veggie wrap",
            "price": "9.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/chicken-veggie-wrap.png",
            "remaining": 2
        },
        {
            "id":2,
            "name": "Assorted cheese plate",
            "price": "7.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/assorted-cheese-plate.png",
            "remaining": 6
        },
        {
            "id":3,
            "name": "Apple walnut salad",
            "price": "12.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/apple-walnut-salad.png",
            "remaining": 1
        }
    ]
}

Busquemos la aplicación de Python para leer este archivo.

Cómo leer en nuestro inventario

El inventario es un archivo estático llamado “inventory.json” y se encuentra en el directorio ./resources. Debemos agregar lógica de Python a views.py para leer el contenido del archivo JSON y, luego, mostrarlo en la conversación. Creemos una función que lea los datos del archivo JSON y muestre la lista de productos disponibles.

Esta definición de función se puede ubicar en cualquier parte de views.py.

bonjourmeal-codelab/step-2/bopis/views.py

...
def get_inventory_data():
        f = open(INVENTORY_FILE)
        inventory = json.load(f)
        return inventory
...

Esto debería proporcionarnos lo que necesitamos para leer los datos del inventario. Ahora necesitamos una manera de mostrar esta información del producto en la conversación.

Cómo mostrar el catálogo de productos

Para simplificar este codelab, tenemos un catálogo general de productos que muestra todos los artículos del inventario en la conversación de Business Messages mediante un solo carrusel de tarjetas enriquecidas.

Para ver el catálogo de productos, crearemos una respuesta sugerida con el texto “Mostrar menú” y los datos de notificación de conversión “show-product-catalog”. Cuando los usuarios presionen la respuesta sugerida y tu aplicación web reciba los datos de notificación de conversión, enviaremos el carrusel de tarjetas enriquecidas. Agreguemos una nueva constante para esta respuesta sugerida en la parte superior de views.py.

bonjourmeal-codelab/step-2/bopis/views.py

...
CMD_SHOW_PRODUCT_CATALOG = 'show-product-catalog'
...

A partir de allí, analizamos el mensaje y lo enrutamos hacia una nueva función que envía un carrusel de tarjetas enriquecidas con el catálogo de productos. Primero, extiende la función route_message para llamar a una función “send_product_catalog” cuando se presione la respuesta sugerida. Luego, debemos definir la función.

En el siguiente fragmento, agrega una condición adicional a la sentencia if en la función route_message para verificar si normalized_message es igual a la constante que definimos antes, CMD_SHOW_PRODUCT_CATALOG.

bonjourmeal-codelab/step-2/bopis/views.py

...
def route_message(message, conversation_id):
    '''
    Routes the message received from the user to create a response.

    Args:
        message (str): The message text received from the user.
        conversation_id (str): The unique id for this user and agent.
    '''
    normalized_message = message.lower()

    if normalized_message == CMD_RICH_CARD:
        send_rich_card(conversation_id)
    elif normalized_message == CMD_CAROUSEL_CARD:
        send_carousel(conversation_id)
    elif normalized_message == CMD_SUGGESTIONS:
        send_message_with_suggestions(conversation_id)
    elif normalized_message == CMD_BUSINESS_HOURS_INQUIRY:
        send_message_with_business_hours(conversation_id)
    elif normalized_message == CMD_ONLINE_SHOPPING_INQUIRY:
        send_online_shopping_info_message(conversation_id)
    elif normalized_message == CMD_SHOW_PRODUCT_CATALOG:
        send_product_catalog(conversation_id)
    else:
        echo_message(message, conversation_id)
...

Ahora, asegurémonos de completar el flujo y definir send_product_catalog. send_product_catalog llama a get_menu_carousel,, que genera el carrusel de tarjetas enriquecidas a partir del archivo de inventario que leímos anteriormente.

Las definiciones de la función se pueden ubicar en cualquier parte de views.py. Ten en cuenta que el siguiente fragmento usa dos constantes nuevas que deben agregarse a la parte superior del archivo.

bonjourmeal-codelab/step-2/bopis/views.py

...

CMD_ADD_ITEM = 'add-item'
CMD_SHOW_CART = 'show-cart'

...

def get_menu_carousel():
    """Creates a sample carousel rich card.

    Returns:
       A :obj: A BusinessMessagesCarouselCard object with three cards.
    """

    inventory = get_inventory_data()

    card_content = []

    for item in inventory['food']:
        card_content.append(BusinessMessagesCardContent(
            title=item['name'],
            description=item['price'],
            suggestions=[
                BusinessMessagesSuggestion(
                    reply=BusinessMessagesSuggestedReply(
                        text='Add item',
                        postbackData='{'+f'"action":"{CMD_ADD_ITEM}","item_name":"{item["id"]}"'+'}'))

                ],
            media=BusinessMessagesMedia(
                height=BusinessMessagesMedia.HeightValueValuesEnum.MEDIUM,
                contentInfo=BusinessMessagesContentInfo(
                    fileUrl=item['image_url'],
                    forceRefresh=False))))

    return BusinessMessagesCarouselCard(
        cardContents=card_content,
        cardWidth=BusinessMessagesCarouselCard.CardWidthValueValuesEnum.MEDIUM)

def send_product_catalog(conversation_id):
    """Sends the product catalog to the conversation_id.

    Args:
        conversation_id (str): The unique id for this user and agent.
    """
    rich_card = BusinessMessagesRichCard(carouselCard=get_menu_carousel())

    fallback_text = ''

    # Construct a fallback text for devices that do not support carousels
    for card_content in rich_card.carouselCard.cardContents:
        fallback_text += (card_content.title + '\n\n' + card_content.description
                          + '\n\n' + card_content.media.contentInfo.fileUrl
                          + '\n---------------------------------------------\n\n')

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        richCard=rich_card,
        fallback=fallback_text,
        suggestions=[
        BusinessMessagesSuggestion(
            reply=BusinessMessagesSuggestedReply(
                text='See my cart',
                postbackData=CMD_SHOW_CART)
            ),
        BusinessMessagesSuggestion(
            reply=BusinessMessagesSuggestedReply(
                text='See the menu',
                postbackData=CMD_SHOW_PRODUCT_CATALOG)
            ),
        ]
        )

    send_message(message_obj, conversation_id)
...

Si examinas la creación de los elementos del carrusel, notarás que también creamos una instancia de la clase BusinessMessagesSuggestion. Cada sugerencia representa la selección de un usuario de un producto en el carrusel. Cuando un usuario presiona la respuesta sugerida, Business Messages enviará los datos de notificación de conversión que contengan JSON a fin de describir el artículo y la acción que el usuario desea realizar (agregarlo o quitarlo del carrito) a tu webhook. En la siguiente sección, analizaremos mensajes como este para poder agregar el artículo al carrito.

Ahora que realizamos estos cambios, implementemos la aplicación web en Google App Engine y probemos la experiencia.

$ gcloud app deploy

Cuando la plataforma de conversación se cargue en tu dispositivo móvil, envía el mensaje “show-product-catalog”. Deberías ver un carrusel de productos similar al siguiente.

4639da46bcc5230c.png

Si presionas Agregar artículo, no sucede nada, excepto que el agente repite los datos de notificación de conversión de la respuesta sugerida. En la siguiente sección, usaremos el catálogo de productos para crear el carrito de compras donde se agregará el artículo.

El catálogo de productos que acabas de crear se puede extender de varias maneras. Puedes tener diferentes opciones de menú de bebidas, o bien opciones vegetarianas. Usar carruseles o fichas de sugerencias es ideal para que los usuarios exploren las opciones del menú a fin de encontrar un conjunto de productos que buscan. Como extensión de este codelab, prueba ampliar el sistema de catálogo de productos para que un usuario pueda ver bebidas separadas de la comida en el menú o, incluso, especificar opciones vegetarianas.

4. El carrito de compras

En esta sección del codelab, compilaremos la funcionalidad del carrito de compras a partir de la sección anterior, que nos permite explorar los productos disponibles.

Entre otras cosas, la experiencia clave del carrito de compras permite que los usuarios agreguen artículos, los quiten, hagan un seguimiento de la cantidad de artículos y los revisen.

Hacer un seguimiento del estado del carrito de compras significa que necesitamos conservar datos en la aplicación web. Para simplificar la experimentación y la implementación, usaremos Google Datastore en Google Cloud Platform a fin de conservar los datos. El ID de la conversación permanece constante entre un usuario y la empresa, por lo que podemos usarlo para asociar a los usuarios con artículos del carrito de compras.

Para comenzar, nos conectaremos con Google Datastore y conservaremos el ID de la conversación cuando lo veamos.

Conéctate con Datastore

Nos conectaremos con Google Datastore cada vez que se ejecute cualquier interacción con el carrito de compras, por ejemplo, cuando un usuario agregue o borre un artículo. Si quieres obtener más información sobre el uso de esta biblioteca cliente para interactuar con Google Datastore, consulta la documentación oficial.

El siguiente fragmento define una función para actualizar el carrito de compras. La función toma la siguiente entrada: conversation_id y message. message contiene JSON que describe la acción que el usuario desea realizar, la que ya está incorporada en tu carrusel que muestra el catálogo de productos. La función crea un cliente de Google Datastore y, de inmediato, recupera una entidad de ShoppingCart, en la que la clave es el ID de la conversación.

Copia la siguiente función en el archivo views.py. Seguiremos ampliándolo en la próxima sección.

bonjourmeal-codelab/step-2/bopis/views.py

from google.oauth2 import service_account
from google.cloud import datastore

def update_shopping_cart(conversation_id, message):
        credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_LOCATION)

        client = datastore.Client(credentials=credentials)
        key = client.key('ShoppingCart', conversation_id)
        entity = datastore.Entity(key=key)
        result = client.get(key)
        
        # TODO: Add logic to add and remove items from cart
        
        entity.update(result)
        client.put(entity)

Extendamos esta función para agregar un artículo al carrito.

Agrega artículos al carrito

Cuando el usuario presiona una acción sugerida de Agregar artículo del carrusel de productos, los datos de notificación de conversión contienen un archivo JSON que describe la acción que el usuario desea realizar. El diccionario JSON tiene dos claves, “item_name” y “action”, y este diccionario JSON se envía al webhook. El campo “item_name” es el identificador único asociado con el artículo en el archivo inventory.json.

Una vez que se analicen el comando cart y el elemento cart del mensaje, podemos escribir sentencias condicionales para agregar el artículo. Algunos casos extremos que considerar son, por ejemplo, si Datastore nunca vio el ID de la conversación o si el carrito de compras recibe este artículo por primera vez. La siguiente es una extensión de la funcionalidad update_shopping_cart definida anteriormente. Este cambio agrega un artículo al carrito de compras que Google Datastore conserva.

El siguiente fragmento es una extensión de la función anterior que se agregó a views.py. Puedes agregar la diferencia o copiar el fragmento y reemplazar la versión existente de la función update_shopping_cart.

bonjourmeal-codelab/step-2bopis/views.py

def update_shopping_cart(conversation_id, message):
    credentials = service_account.Credentials.from_service_account_file(
      SERVICE_ACCOUNT_LOCATION)
    inventory = get_inventory_data()

    cart_request = json.loads(message)
    cart_cmd = cart_request["action"]
    cart_item = cart_request["item_name"]

    item_name = inventory['food'][int(cart_item)]['name']

    client = datastore.Client(credentials=credentials)
    key = client.key('ShoppingCart', conversation_id)
    entity = datastore.Entity(key=key)
    result = client.get(key)

    if result is None:
        if cart_cmd == CMD_ADD_ITEM:
            entity.update({
                item_name: 1
            })

    else:
        if cart_cmd == CMD_ADD_ITEM:
            if result.get(item_name) is None:
                result[item_name] = 1
            else:
                result[item_name] = result[item_name] + 1

        entity.update(result)
    client.put(entity)

Esta función se extenderá más adelante para admitir la situación en la que cart_cmd contiene la string “del-item” definida en CMD_DEL_ITEM.

Combina las piezas

Asegúrate de agregar todo en la función route_message de modo que, si recibes un mensaje para agregar un artículo al carrito, se llame a la función update_shopping_cart. También deberás definir una constante para agregar artículos con la convención que usamos durante el codelab.

bonjourmeal-codelab/step-2bopis/views.py

...

CMD_DEL_ITEM = 'del-item'

...

def route_message(message, conversation_id):
    '''
    Routes the message received from the user to create a response.

    Args:
        message (str): The message text received from the user.
        conversation_id (str): The unique id for this user and agent.
    '''
    normalized_message = message.lower()

    if normalized_message == CMD_RICH_CARD:
        send_rich_card(conversation_id)
    elif normalized_message == CMD_CAROUSEL_CARD:
        send_carousel(conversation_id)
    elif normalized_message == CMD_SUGGESTIONS:
        send_message_with_suggestions(conversation_id)
    elif normalized_message == CMD_BUSINESS_HOURS_INQUIRY:
        send_message_with_business_hours(conversation_id)
    elif normalized_message == CMD_ONLINE_SHOPPING_INQUIRY:
        send_online_shopping_info_message(conversation_id)
    elif normalized_message == CMD_SHOW_PRODUCT_CATEGORY:
        send_product_catalog(conversation_id)
    elif CMD_ADD_ITEM in normalized_message or CMD_DEL_ITEM in normalized_message:
       update_shopping_cart(conversation_id, message)
    else:
        echo_message(message, conversation_id)

...

Por ahora, podemos agregar artículos al carrito de compras. Si implementas tus cambios en Google App Engine, deberías poder ver los cambios del carrito de compras reflejados en el panel de Google Datastore que se encuentra en GCP Console. Consulta la siguiente captura de pantalla de la consola de Google Datastore. En ella, aparece una entidad única que lleva el nombre del ID de conversación, seguida de algunas relaciones con los artículos del inventario y la cantidad de esos artículos que se encuentran en el carrito de compras.

619dc18a8136ea69.png

En la siguiente sección, crearemos una forma de enumerar los artículos del carrito de compras. El mecanismo de revisión del carrito de compras debe mostrarnos todos los artículos del carrito, la cantidad de artículos y una opción para quitar un artículo.

Revisa los artículos del carrito

Mostrar los artículos del carrito de compras es la única manera de comprender el estado del carrito y saber qué artículos podemos quitar.

Primero, te enviaremos un mensaje amigable, como “Este es tu carrito de compras”, seguido de otro mensaje que contenga un carrusel de tarjetas enriquecidas con respuestas sugeridas para “Quitar uno” o “Agregar uno”. Además, el carrusel de tarjetas enriquecidas debería mostrar la cantidad de artículos guardados en el carrito.

Algo que debes tener en cuenta antes de que sigamos y escribamos nuestra función es que, si hay solo un tipo de elemento en el carrito de compras, no podemos renderizarlo como un carrusel. Los carruseles de tarjetas enriquecidas deben contener al menos dos tarjetas. Por otro lado, si no hay artículos en el carrito, queremos mostrar un mensaje sencillo que diga que el carrito está vacío.

Con esto en mente, definamos una función llamada send_shopping_cart. Esta función se conecta con Google Datastore y solicita una entidad de ShoppingCart basada en el ID de la conversación. Una vez que tengamos esto, llamaremos a la función get_inventory_data y usaremos un carrusel de tarjetas enriquecidas para informar el estado del carrito de compras. También necesitaremos obtener el ID de un producto por nombre y podemos declarar una función para buscar en Google Datastore a fin de determinar ese valor. A medida que se genera el carrusel, podemos asociar las respuestas sugeridas para borrar o agregar artículos por ID del producto. El siguiente fragmento realiza todas estas operaciones. Copia el código en cualquier lugar en views.py.

bonjourmeal-codelab/step-2/bopis/views.py

...
def get_id_by_product_name(product_name):
  inventory = get_inventory_data()
  for item in inventory['food']:
    if item['name'] == product_name:
      return int(item['id'])
  return False


def send_shopping_cart(conversation_id):
  credentials = service_account.Credentials.from_service_account_file(
      SERVICE_ACCOUNT_LOCATION)

  # Retrieve the inventory data
  inventory = get_inventory_data()

  # Pull the data from Google Datastore
  client = datastore.Client(credentials=credentials)
  key = client.key('ShoppingCart', conversation_id)
  result = client.get(key)

  shopping_cart_suggestions = [
      BusinessMessagesSuggestion(
          reply=BusinessMessagesSuggestedReply(
              text='See total price', postbackData='show-cart-price')),
      BusinessMessagesSuggestion(
          reply=BusinessMessagesSuggestedReply(
              text='Empty the cart', postbackData='empty-cart')),
      BusinessMessagesSuggestion(
          reply=BusinessMessagesSuggestedReply(
              text='See the menu', postbackData=CMD_SHOW_PRODUCT_CATALOG)),
  ]

  if result is None or len(result.items()) == 0:
    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        text='There are no items in your shopping cart.',
        suggestions=shopping_cart_suggestions)

    send_message(message_obj, conversation_id)
  elif len(result.items()) == 1:

    for product_name, quantity in result.items():
      product_id = get_id_by_product_name(product_name)

      fallback_text = ('You have one type of item in the shopping cart')

      rich_card = BusinessMessagesRichCard(
          standaloneCard=BusinessMessagesStandaloneCard(
              cardContent=BusinessMessagesCardContent(
                  title=product_name,
                  description=f'{quantity} in cart.',
                  suggestions=[
                      BusinessMessagesSuggestion(
                          reply=BusinessMessagesSuggestedReply(
                              text='Remove one',
                              postbackData='{'+f'"action":"{CMD_DEL_ITEM}","item_name":"{product_id}"'+'}'))
                  ],
                  media=BusinessMessagesMedia(
                      height=BusinessMessagesMedia.HeightValueValuesEnum.MEDIUM,
                      contentInfo=BusinessMessagesContentInfo(
                          fileUrl=inventory['food'][product_id]
                          ['image_url'],
                          forceRefresh=False)))))

      message_obj = BusinessMessagesMessage(
          messageId=str(uuid.uuid4().int),
          representative=BOT_REPRESENTATIVE,
          richCard=rich_card,
          suggestions=shopping_cart_suggestions,
          fallback=fallback_text)

      send_message(message_obj, conversation_id)
  else:
    cart_carousel_items = []

    # Iterate through the cart and generate a carousel of items
    for product_name, quantity in result.items():
      product_id = get_id_by_product_name(product_name)

      cart_carousel_items.append(
          BusinessMessagesCardContent(
              title=product_name,
              description=f'{quantity} in cart.',
              suggestions=[
                  BusinessMessagesSuggestion(
                      reply=BusinessMessagesSuggestedReply(
                          text='Remove one',
                          postbackData='{'+f'"action":"{CMD_DEL_ITEM}","item_name":"{product_id}"'+'}'))
              ],
              media=BusinessMessagesMedia(
                  height=BusinessMessagesMedia.HeightValueValuesEnum.MEDIUM,
                  contentInfo=BusinessMessagesContentInfo(
                      fileUrl=inventory['food'][product_id]
                      ['image_url'],
                      forceRefresh=False))))

    rich_card = BusinessMessagesRichCard(
        carouselCard=BusinessMessagesCarouselCard(
            cardContents=cart_carousel_items,
            cardWidth=BusinessMessagesCarouselCard.CardWidthValueValuesEnum
            .MEDIUM))

    fallback_text = ''

    # Construct a fallback text for devices that do not support carousels
    for card_content in rich_card.carouselCard.cardContents:
      fallback_text += (
          card_content.title + '\n\n' + card_content.description + '\n\n' +
          card_content.media.contentInfo.fileUrl +
          '\n---------------------------------------------\n\n')

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        richCard=rich_card,
        suggestions=shopping_cart_suggestions,
        fallback=fallback_text,
    )

    send_message(message_obj, conversation_id)

...

Asegúrate de haber definido CMD_SHOW_CART en la parte superior de views.py y llama a send_shopping_cart si el usuario envía un mensaje que contiene “show-cart”.

bonjourmeal-codelab/step-2/bopis/views.py

...
def route_message(message, conversation_id):
    '''
    Routes the message received from the user to create a response.

    Args:
        message (str): The message text received from the user.
        conversation_id (str): The unique id for this user and agent.
    '''
    normalized_message = message.lower()

    if normalized_message == CMD_RICH_CARD:
        send_rich_card(conversation_id)
    elif normalized_message == CMD_CAROUSEL_CARD:
        send_carousel(conversation_id)
    elif normalized_message == CMD_SUGGESTIONS:
        send_message_with_suggestions(conversation_id)
    elif normalized_message == CMD_BUSINESS_HOURS_INQUIRY:
        send_message_with_business_hours(conversation_id)
    elif normalized_message == CMD_ONLINE_SHOPPING_INQUIRY:
        send_online_shopping_info_message(conversation_id)
    elif normalized_message == CMD_SHOW_PRODUCT_CATEGORY:
        send_product_catalog(conversation_id)
    elif CMD_ADD_ITEM in normalized_message or CMD_DEL_ITEM in normalized_message:
        update_shopping_cart(conversation_id, message)
    elif normalized_message == CMD_SHOW_CART:
        send_shopping_cart(conversation_id)
    else:
        echo_message(message, conversation_id)
...

34801776a97056ac.png

Según la lógica que presentamos en la función send_shopping_cart, cuando escribas “show-cart”, obtendremos un mensaje que indica que no hay nada en el carrito, una tarjeta enriquecida que muestra el único artículo en el carrito o un carrusel de tarjetas que muestra varios artículos. Además, tenemos tres respuestas sugeridas: “Ver el precio total” (See total price), “Vaciar el carrito” (Empty the cart) y “Ver el menú” (See the menu).

Implementa los cambios anteriores del código para probar si tu carrito de compras está haciendo un seguimiento de los artículos que agregaste y que puedes revisar el carrito desde la plataforma de conversaciones, tal como se muestra en las capturas de pantalla anteriores. Puedes implementar los cambios con la ejecución de este comando desde el directorio del paso 2, en el que agregarás los cambios.

$ gcloud app deploy

Crearemos la función “Ver precio total” en la siguiente sección después de compilar la funcionalidad para quitar un artículo del carrito. La función get_cart_price se comportará de forma similar a la función “Ver carrito de compras” en el sentido de que hará referencias cruzadas de los datos en Datastore con el archivo inventory.json a fin de producir un precio total para el carrito de compras. Esto será útil para la siguiente parte del codelab, en la que integramos los pagos.

Quita artículos del carrito

Por último, podemos completar el comportamiento del carrito de compras mediante la introducción de la función para quitar el carrito. Reemplaza la función update_shopping_cart existente por el siguiente fragmento.

bonjourmeal-codelab/step-2/ bopis/views.py

def update_shopping_cart(conversation_id, message):
    credentials = service_account.Credentials.from_service_account_file(
      SERVICE_ACCOUNT_LOCATION)
    inventory = get_inventory_data()

    cart_request = json.loads(message)
    cart_cmd = cart_request["action"]
    cart_item = cart_request["item_name"]

    item_name = inventory['food'][int(cart_item)]['name']


    client = datastore.Client(credentials=credentials)
    key = client.key('ShoppingCart', conversation_id)
    entity = datastore.Entity(key=key)
    result = client.get(key)

    if result is None:
        if cart_cmd == CMD_ADD_ITEM:
            entity.update({
                item_name: 1
            })
        elif cart_cmd == CMD_DEL_ITEM:
            # The user is trying to delete an item from an empty cart. Pass and skip
            pass

    else:
        if cart_cmd == CMD_ADD_ITEM:
            if result.get(item_name) is None:
                result[item_name] = 1
            else:
                result[item_name] = result[item_name] + 1

        elif cart_cmd == CMD_DEL_ITEM:
            if result.get(item_name) is None:
                # The user is trying to remove an item that's no in the shopping cart. Pass and skip
                pass
            elif result[item_name] - 1 > 0:
                result[item_name] = result[item_name] - 1
            else:
                del result[item_name]

        entity.update(result)
    client.put(entity)

Envía un mensaje de confirmación

Cuando el usuario agrega un artículo al carrito, debes enviarle un mensaje de confirmación que confirme su acción y que afirme que se procesó su solicitud. Esto no solo ayuda a establecer expectativas, sino que también mantiene activa la conversación.

Extenderemos la función update_shopping_cart de manera que envíe un mensaje al ID de la conversación que indique que se agregó o quitó el elemento, y que ofrezca sugerencias para revisar su carrito de compras o volver a ver el menú.

bonjourmeal-codelab/step-2/bopis/views.py

def update_shopping_cart(conversation_id, message):

     # No changes to the function, except appending the following logic
     ...
   
    if cart_cmd == CMD_ADD_ITEM:
        message = 'Great! You\'ve added an item to the cart.'
    else:
        message = 'You\'ve removed an item from the cart.'

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        text=message,
        suggestions=[
            BusinessMessagesSuggestion(
            reply=BusinessMessagesSuggestedReply(
                text='Review shopping cart',
                postbackData=CMD_SHOW_CART)
            ),
            BusinessMessagesSuggestion(
            reply=BusinessMessagesSuggestedReply(
                text='See menu again',
                postbackData=CMD_SHOW_PRODUCT_CATALOG)
            ),
            ])
    send_message(message_obj, conversation_id)

905a1f3d89893ba0.png

Esto debería ser suficiente. Una experiencia de carrito de compras con todas las funciones, que permite que un usuario agregue artículos, los quite y revise los artículos del carrito.

En este punto, si deseas ver la funcionalidad del carrito de compras en la conversación de Business Messages, implementa la aplicación para interactuar con tu agente. Para hacerlo, ejecuta el siguiente comando en el directorio del paso 2.

$ gcloud app deploy

5. Prepárate para los pagos

A fin de preparar la integración en un procesador de pagos en la próxima parte de la serie, necesitamos una forma de obtener el precio del carrito de compras. Compilemos una función para recuperar el precio mediante la referencia cruzada de los datos del carrito de compras en Google Datastore, la recuperación del precio de cada artículo del inventario y la multiplicación del precio por la cantidad de cada artículo en el carrito.

bonjourmeal-codelab/step-2/bopis/views.py

...
def get_cart_price(conversation_id):
    # Pull the data from Google Datastore
    credentials = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_LOCATION)
    client = datastore.Client(credentials=credentials)
    key = client.key('ShoppingCart', conversation_id)
    entity = datastore.Entity(key=key)
    result = client.get(key)

    # Retrieve the inventory data
    inventory = get_inventory_data()
   
    # Start off with a total of 0 before adding up the total
    total_price = 0

    if len(result.items()) != 0:
      for product_name, quantity in result.items():
        total_price = total_price + float(
            inventory['food'][get_id_by_product_name(product_name)]['price']) * int(quantity)

    return total_price

...

Por último, podemos consumir esa función y enviarle un mensaje al usuario.

bonjourmeal-codelab/step-2/bopis/views.py

...

def send_shopping_cart_total_price(conversation_id):
    cart_price = get_cart_price(conversation_id)

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        suggestions=[],
        text=f'Your cart\'s total price is ${cart_price}.')

    send_message(message_obj, conversation_id)
...

Para unirlo todo, actualicemos la función route_message y la constante a fin de activar la lógica anterior.

bonjourmeal-codelab/step-2/bopis/views.py

...
CMD_GET_CART_PRICE = 'show-cart-price'
...
def route_message(message, conversation_id):
    '''
    Routes the message received from the user to create a response.

    Args:
        message (str): The message text received from the user.
        conversation_id (str): The unique id for this user and agent.
    '''
    normalized_message = message.lower()

    if normalized_message == CMD_RICH_CARD:
        send_rich_card(conversation_id)
    elif normalized_message == CMD_CAROUSEL_CARD:
        send_carousel(conversation_id)
    elif normalized_message == CMD_SUGGESTIONS:
        send_message_with_suggestions(conversation_id)
    elif normalized_message == CMD_BUSINESS_HOURS_INQUIRY:
        send_message_with_business_hours(conversation_id)
    elif normalized_message == CMD_ONLINE_SHOPPING_INQUIRY:
        send_online_shopping_info_message(conversation_id)
    elif normalized_message == CMD_SHOW_PRODUCT_CATEGORY:
        send_product_catalog(conversation_id)
    elif CMD_ADD_ITEM in normalized_message or CMD_DEL_ITEM in normalized_message:
        update_shopping_cart(conversation_id, message)
    elif normalized_message == CMD_SHOW_CART:
        send_shopping_cart(conversation_id)
    elif normalized_message == CMD_GET_CART_PRICE:
        send_shopping_cart_total_price(conversation_id)
    else:
        echo_message(message, conversation_id)
...

Las siguientes capturas de pantalla muestran lo que logra la lógica anterior:

8feacf94ed0ac6c4.png

Cuando esté todo listo para la integración en el procesador de pagos en la próxima parte del codelab, llamaremos a la función get_cart_price para transferir los datos al procesador de pagos y, luego, iniciar el flujo de pago.

Recuerda que puedes probar esta funcionalidad del carrito de compras en la conversación de Business Messages mediante la implementación de la aplicación y la interacción con tu agente.

$ gcloud app deploy

6. Felicitaciones

Felicitaciones. Creaste correctamente una experiencia de carrito de compras en Business Messages.

Algo que no revisamos en este codelab es la función para vaciar todo el carrito de compras. Si lo deseas, prueba extender la aplicación para agregar la función “Vaciar el carrito”. La solución está disponible en el paso 3 del código fuente que clonaste.

En una sección futura, realizaremos una integración en un procesador de pagos externo para permitir que tus usuarios completen una transacción de pago con tu marca.

¿Qué características tiene un buen carrito de compras?

Una buena experiencia del carrito de compras en una conversación no es diferente a la de una app para dispositivos móviles o una tienda física. Poder agregar y quitar artículos, y calcular el precio del carrito son solo algunas de las características que exploramos en este codelab. A diferencia de un carrito de compras del mundo real, se puede ver el precio de todos los artículos del carrito en cualquier momento, a medida que agregas o quitas artículos. Estos tipos de funciones valiosas harán que tu experiencia de comercio conversacional se destaque.

¿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