Comprar on-line e retirar na loja: Bonjour Meal – parte 2: como criar um carrinho de compras

1. Introdução

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Última atualização: 30/10/2020

Como criar um carrinho de compras no Business Messages

Este é o segundo codelab em uma série destinada à criação de uma jornada do usuário para comprar on-line e retirar na loja. Em muitas jornadas de e-commerce, o carrinho de compras é fundamental para o sucesso da conversão de usuários em clientes pagantes. O carrinho de compras também é uma maneira de entender melhor seus clientes, além de oferecer sugestões sobre outros itens que podem ser do interesse deles. Neste codelab, o foco será a criação de uma experiência de carrinho de compras e a implantação do aplicativo no Google App Engine.

Quais são as características de um bom carrinho de compras?

Os carrinhos de compras são essenciais para uma experiência de compra on-line perfeita. O Business Messages não só é uma ótima ferramenta para perguntas e respostas sobre um produto com um cliente em potencial, como também viabiliza toda a experiência de compra, até a conclusão de um pagamento dentro da conversa.

9d17537b980d0e62.png

Além de um bom carrinho de compras, uma boa experiência de compra permite que os usuários procurem itens por categoria e que a empresa recomende outros produtos em que o comprador pode ter interesse. Após adicionar mais itens, o usuário pode analisar o carrinho e remover ou adicionar outros produtos antes de finalizar a compra.

O que você criará

Nesta seção da série do codelab, você vai ampliar o agente digital criado na parte 1 da empresa fictícia, a Bonjour Meal, para que os usuários possam pesquisar em um catálogo de itens e adicioná-los em um carrinho de compras.

Neste codelab, seu app vai:

  • mostrar um catálogo de perguntas no Business Messages;
  • sugerir itens que possam ser de interesse dos usuários;
  • revisar o conteúdo do carrinho de compras e criar um resumo do preço total.

ab2fb6a4ed33a129.png

O que você aprenderá

  • Como implantar um aplicativo da Web no App Engine no Google Cloud Platform
  • Como usar um mecanismo de armazenamento permanente para salvar o estado de um carrinho de compras

O foco deste codelab é ampliar o agente digital da parte 1 desta série de codelab.

Pré-requisitos

2. Etapas da configuração

Este codelab presume que você criou seu primeiro agente e concluiu a parte 1 do codelab. Por isso, não vamos falar sobre os princípios básicos da ativação do Business Messages e das APIs Business Communications, como criar chaves de conta de serviço, como implantar um aplicativo ou como configurar seu webhook no Business Communications Console. Depois disso, vamos clonar um aplicativo de exemplo para garantir que ele seja consistente com o que estamos desenvolvendo. Além disso, vamos ativar a API para o Datastore no Google Cloud Platform para manter os dados relacionados ao carrinho de compras.

Como clonar o aplicativo do GitHub

Em um terminal, clone a Amostra de bot do Django Echo no diretório de trabalho do seu projeto com o seguinte comando:

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

Copie o arquivo de credenciais JSON criado para a conta de serviço na pasta de recursos de amostra e renomeie as credenciais para "bm-agent-service-account-credentials.json".

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

Ativar a API Google Datastore

Na parte 1 deste codelab, você ativou as APIs Business Messages, Business Communications e Cloud Build.

Para este codelab, como vamos trabalhar com o Google Datastore, também vamos ter que ativar essa API:

  1. Abra a API Google Datastore no Console do Google Cloud.
  2. Verifique se você está trabalhando com o projeto correto do GCP.
  3. Selecione Ativar.

Como implantar o aplicativo de amostra

Em um terminal, acesse o diretório da etapa 2 do exemplo.

Execute os seguintes comandos em um terminal para implantar o exemplo:

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • PROJECT_ID é o ID do projeto que você usou para se registrar nas APIs.

Observe o URL do aplicativo implantado na saída do último comando:

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

O código que você acabou de implantar contém um aplicativo da Web com um webhook para receber mensagens do Business Messages. Ele contém tudo o que fizemos da parte 1 do codelab. Configure seu webhook, caso ainda não tenha feito isso.

O aplicativo responderá a algumas consultas simples, como um usuário perguntando sobre o horário de funcionamento da Bonjour Meal. Faça isso no seu dispositivo móvel pelos URLs de teste que podem ser recuperados em "Informações do agente" no console do Business Communications. Os URLs de teste vão iniciar a experiência do Business Messages no seu dispositivo móvel, e você vai poder começar a interagir com seu agente.

3. O catálogo de produtos

Um sistema de inventário

Na maioria dos casos, é possível fazer a integração direta com o inventário de uma marca usando uma API interna. Em outros casos, você pode copiar uma página da Web ou criar seu próprio sistema de rastreamento de inventário. Nosso foco não é criar um sistema de inventário: vamos usar um arquivo estático simples que contém imagens e informações do produto para nosso agente. Nesta seção, vamos colocar as informações desse arquivo estático na conversa e permitir que o usuário pesquise os itens disponíveis para adicioná-los a um carrinho de compras.

O arquivo de inventário estático tem esta aparência:

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
        }
    ]
}

Vamos fazer o aplicativo Python ler esse arquivo.

Como ler nosso inventário

O inventário é um arquivo estático chamado "inventory.json" encontrado no diretório ./resources. Precisamos adicionar lógica do Python ao views.py para ler o conteúdo do arquivo JSON e depois exibi-lo na conversa. Vamos criar uma função que lê dados do arquivo JSON e retorna a lista de produtos disponíveis.

Essa definição de função pode ser colocada em qualquer lugar em views.py.

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

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

Isso vai nos fornecer o que precisamos para ler os dados do inventário. Agora precisamos de uma maneira de mostrar essas informações sobre o produto na conversa.

Como exibir o catálogo de produtos

Para simplificar este codelab, temos um catálogo de produtos geral que mostra todos os itens do inventário na conversa do Business Messages usando um único carrossel com rich card.

Para visualizar o catálogo de produtos, vamos criar uma resposta sugerida com o texto "Exibir menu" e dados de postback "show-product-catalog". Quando os usuários tocarem na resposta sugerida e seu aplicativo da Web receber os dados de postback, vamos enviar o carrossel com rich card. Vamos adicionar uma nova constante para essa resposta sugerida na parte superior de views.py.

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

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

Depois, analisamos a mensagem e a encaminhamos para uma nova função que envia um carrossel com rich card contendo o catálogo de produtos. Primeiro, estenda a função route_message para chamar uma função "send_product_catalog" quando a resposta sugerida for tocada. Em seguida, vamos redefinir a função.

No snippet a seguir, adicione uma condição extra à instrução "if" na função route_message para verificar se normalized_message é igual à constante definida anteriormente, 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)
...

Vamos concluir o fluxo e definir send_product_catalog. send_product_catalog chama get_menu_carousel,, que gera o carrossel com rich cards do arquivo de inventário lido anteriormente.

As definições da função podem ser colocadas em qualquer lugar em views.py. O snippet a seguir usa duas novas constantes que precisam ser adicionadas à parte superior do arquivo.

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)
...

Se você examinar a criação dos itens do carrossel, também vamos criar uma instância da classe BusinessMessagesSuggestion. Cada sugestão representa uma seleção de usuários para um produto no carrossel. Quando um usuário toca na resposta sugerida, o Business Messages envia os dados de postback com o JSON descrevendo o item e a ação que ele quer realizar (adicionar ou remover do carrinho) para o webhook. Na seção a seguir, vamos analisar as mensagens como esta para adicionar o item ao carrinho.

Agora que essas alterações foram feitas, vamos implantar o aplicativo da Web no Google App Engine e testar a experiência.

$ gcloud app deploy

Depois de carregar a superfície de conversa no dispositivo móvel, envie a mensagem "show-product-catalog" para ver um carrossel de produtos assim.

4639da46bcc5230c.png

Se você tocar em Adicionar item, a única ação que acontece é que o agente ecoa os dados de postback da resposta sugerida. Na próxima seção, vamos usar o catálogo de produtos para criar o carrinho de compras em que o item será adicionado.

O catálogo de produtos que você acabou de criar pode ser ampliado de várias maneiras. Você pode ter opções diferentes de menu de bebidas ou opções vegetarianas. O uso de carrosséis ou ícones de sugestão é uma ótima forma de permitir que os usuários conheçam as opções do menu para encontrar produtos que procuram. Como extensão deste codelab, tente ampliar o sistema do catálogo de produtos para que o usuário possa ver bebidas separadamente dos alimentos do menu, ou até mesmo especificar opções vegetarianas.

4. O carrinho de compras

Nesta seção do codelab, vamos criar a funcionalidade do carrinho de compras com base na seção anterior, o que vai permitir pesquisar os produtos disponíveis.

Entre outras coisas, o recurso de carrinho de compras permite que os usuários adicionem e removam itens, acompanhem a quantidade e revisem os produtos.

Monitorar o estado do carrinho de compras significa que precisamos manter os dados no aplicativo da Web. Para simplificar a experimentação e a implantação, vamos usar o Google Datastore no Google Cloud Platform para manter os dados. O ID da conversa é constante entre um usuário e a empresa. Por isso, podemos usá-lo para associar usuários a itens do carrinho de compras.

Para começar, vamos nos conectar ao Google Datastore e manter o ID da conversa quando ele for exibido.

Como se conectar ao Datastore

Vamos nos conectar ao Google Datastore sempre que uma interação for executada no carrinho de compras, por exemplo, quando um usuário estiver adicionando ou excluindo um item. Para saber mais sobre como usar essa biblioteca de cliente para interagir com o Google Datastore, acesse a documentação oficial (em inglês).

O snippet a seguir define uma função para atualizar o carrinho de compras. A função recebe a seguinte entrada: conversation_id e message. O arquivo message contém um JSON que descreve a ação que o usuário quer realizar, integrada ao carrossel que exibe o catálogo de produtos. A função cria um cliente do Google Datastore e busca imediatamente uma entidade ShoppingCart, em que a chave é o ID da conversa.

Copie a seguinte função no arquivo views.py. Vamos falar mais sobre isso na próxima seção.

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)

Vamos ampliar essa função para adicionar um item ao carrinho.

Como adicionar itens ao carrinho

Quando o usuário toca em uma ação sugerida Adicionar item no carrossel de produtos, os dados de postback incluem o JSON que descreve a ação que ele quer realizar. O dicionário JSON tem duas chaves, "action" e "item_name", e é enviado para o webhook. O campo "item_name" é o identificador exclusivo associado ao item no inventory.json.

Depois que o comando do carrinho e o item do carrinho são analisados na mensagem, é possível criar instruções condicionais para adicionar o item. Há alguns casos extremos a serem considerados: se o repositório de dados nunca viu o ID da conversa ou se o carrinho de compras está recebendo esse item pela primeira vez. A extensão a seguir é da funcionalidade update_shopping_cart definida acima. Essa mudança adiciona um item ao carrinho de compras mantido pelo Google Datastore.

O snippet a seguir é uma extensão da função anterior adicionada ao views.py. É possível adicionar a diferença ou copiar o snippet e substituir a versão atual da função 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)

Essa função vai ser estendida posteriormente para ser compatível com o cenário em que cart_cmd contém a string "del-item" definida em CMD_DEL_ITEM.

Como colocar tudo junto

Adicione o encanamento na função route_message de forma que, se você receber uma mensagem para adicionar um item ao carrinho, chame a função update_shopping_cart. Também vai ser necessário definir uma constante para adicionar itens usando a convenção que usamos no 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 enquanto, podemos adicionar itens ao carrinho de compras. Se você implantar as alterações no Google App Engine, vai ver as alterações no carrinho de compras refletidas no painel do Google Datastore, encontrado no Console do GCP. Veja a captura de tela abaixo do console do Google Datastore. Há uma única entidade com o nome do ID da conversa seguido de algumas relações com os itens de inventário e a quantidade desses itens que estão no carrinho de compras.

619dc18a8136ea69.png

Na próxima seção, vamos criar uma forma de listar itens no carrinho de compras. O mecanismo de análise do carrinho de compras vai mostrar todos os itens que estão no carrinho, a quantidade deles e a opção de removê-los do carrinho.

Como revisar itens no carrinho

Listar os itens no carrinho de compras é a única maneira de entender o estado do carrinho de compras e saber quais itens podemos remover.

Primeiro, vamos enviar uma mensagem amigável, como "Confira seu carrinho de compras:", seguida por outra mensagem que contém um carrossel com rich card com respostas sugeridas associadas a "Remover um" ou "Adicionar um". Além disso, o carrossel com rich card tem que listar a quantidade de itens salvos no carrinho.

Uma consideração importante antes de entrarmos e escrevermos nossa função: se houver apenas um tipo de item no carrinho de compras, ele não vai poder ser renderizado como um carrossel. Os carrosséis com rich card precisam conter pelo menos dois cartões. Por outro lado, se não houver itens no carrinho, convém exibir uma mensagem simples dizendo que o carrinho está vazio.

Com isso em mente, vamos definir uma função chamada send_shopping_cart. Ela se conecta ao Google Datastore e solicita uma entidade ShoppingCart com base no ID da conversa. Em seguida, vamos chamar a função get_inventory_data e usar um carrossel com rich card para informar o estado do carrinho de compras. Também precisamos encontrar o ID de um produto pelo nome para podermos declarar uma função para analisar o Google Datastore e determinar esse valor. À medida que o carrossel é produzido, podemos associar respostas sugeridas para excluir ou adicionar itens por ID do produto. O snippet abaixo executa todas essas operações. Copie o código em qualquer lugar em 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)

...

Verifique se você já definiu CMD_SHOW_CART na parte superior de views.py e chame send_shopping_cart se o usuário enviar uma mensagem contendo "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

Com base na lógica que introduzimos na função send_shopping_cart, quando você digitar "show-cart", vamos receber uma mensagem informando que não há nada no carrinho, um rich card mostrando o item no carrinho ou um carrossel com cards mostrando vários itens. Além disso, temos três respostas sugeridas: "Ver o preço total", "Esvaziar o carrinho" e "Ver o menu".

Tente implantar as alterações de código acima para testar se o carrinho de compras está rastreando os itens adicionados e se você pode analisar o carrinho na superfície das conversas, conforme mostrado nas capturas de tela acima. É possível implantar as alterações usando este comando executado no diretório da etapa 2, onde está adicionando as alterações.

$ gcloud app deploy

Na próxima seção, criaremos o recurso "Ver preço total" para remover um item do carrinho. A função get_cart_price vai se comportar de maneira semelhante ao recurso "Ver carrinho de compras", no sentido de cruzar dados entre o Datastore e o arquivo inventory.json para produzir um preço total no carrinho de compras. Isso vai ser útil na próxima parte do codelab, em que fazemos a integração com pagamentos.

Como remover itens do carrinho

Por fim, podemos concluir o comportamento do carrinho de compras introduzindo a funcionalidade para removê-lo. Substitua a função update_shopping_cart atual pelo snippet a seguir.

bonjourmeal-codelab/step-2/ bopis/views.py (link em inglês)

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)

Como enviar uma mensagem de confirmação

Quando o usuário adicionar um item ao carrinho, envie uma mensagem confirmando o processamento da solicitação. Isso ajuda a definir as expectativas e mantém a conversa em andamento.

Vamos estender a função update_shopping_cart para que ela envie uma mensagem ao ID da conversa informando que o item foi adicionado ou removido e ofereça sugestões para analisar o carrinho de compras ou ver o menu novamente.

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

Isso deve ser suficiente. Uma experiência de carrinho de compras completa que permite que o usuário adicione, remova e revise os itens no carrinho.

Agora, se você quiser ver a funcionalidade do carrinho de compras na conversa do Business Messages, implante o aplicativo para interagir com seu agente. Faça isso no comando da etapa 2.

$ gcloud app deploy

5. Preparação para fazer pagamentos

Como preparo para a integração com um processador de pagamentos na próxima parte da série, precisamos de uma maneira de ver o preço do carrinho de compras. Vamos criar uma função que recupera o preço por meio da referência cruzada dos dados do carrinho de compras no Google Datastore, da recuperação do preço de cada item do inventário e da multiplicação do preço pela quantidade de cada item no carrinho.

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 fim, podemos consumir essa função e enviar uma mensagem ao usuário.

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 unir tudo, vamos atualizar a função route_message e a constante para acionar a lógica acima.

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)
...

Veja algumas capturas de tela que mostram os resultados da lógica acima:

8feacf94ed0ac6c4.png

Quando estiver tudo pronto para integrar o processador de pagamentos na próxima parte do codelab, vamos chamar a função get_cart_price para transmitir os dados ao processador de pagamentos e iniciar o fluxo de pagamento.

Mais uma vez, é possível testar essa funcionalidade de carrinho de compras na conversa do Business Messages implantando o aplicativo e interagindo com seu agente.

$ gcloud app deploy

6. Parabéns

Parabéns, você criou uma experiência de carrinho de compras no Business Messages.

Algo que não vimos neste codelab é o recurso para esvaziar todo o carrinho de compras. Se você quiser, tente ir além no aplicativo para conferir a seção "Esvaziar o carrinho". A solução está disponível na etapa 3 do código-fonte que você clonou.

Em uma seção futura, vamos fazer a integração com um processador de pagamentos externo para permitir que os usuários concluam uma transação de pagamento com sua marca.

Quais são as características de um bom carrinho de compras?

Uma boa experiência de carrinho de compras em uma conversa não é diferente do que a experiência em um app para dispositivos móveis ou em uma loja física. Poder adicionar, remover e calcular o preço do carrinho são apenas alguns dos recursos que vimos neste codelab. Uma das diferenças de um carrinho de compras real é poder ver o preço de todos os itens a qualquer momento, conforme você adiciona ou remove produtos. Esses tipos de recursos de alto valor vão ajudar a destacar sua experiência de comércio eletrônico.

Qual é a próxima etapa?

Quando tudo estiver pronto, confira alguns dos seguintes tópicos para saber mais sobre as interações mais complexas que você pode realizar no Business Messages:

Documentos de referência