Купить онлайн-самовывоз в магазине: Bonjour Meal. Часть 2. Создание тележки для покупок

1. Введение

53003251caaf2be5.png8826bd8cb0c0f1c7.png

Последнее обновление: 30.10.2020

Создание корзины покупок на основе бизнес-сообщений!

Это второй практический урок из серии, посвященной созданию пользовательского пути «Купи онлайн, забери в магазине». Во многих системах электронной коммерции корзина покупок играет ключевую роль в превращении пользователей в платящих клиентов. Корзина покупок также позволяет лучше понять своих клиентов и предложить им другие товары, которые могут их заинтересовать. В этом практическом уроке мы сосредоточимся на создании интерфейса корзины покупок и развертывании приложения в Google App Engine.

Что делает тележку для покупок хорошей?

Корзина покупок — ключ к успешному онлайн-шопингу. Как оказалось, Business Messages не только хорошо подходит для организации вопросов и ответов о товаре с потенциальным клиентом, но и может упростить весь процесс покупки, вплоть до завершения оплаты, прямо в диалоге.

9d17537b980d0e62.png

Помимо удобной корзины покупок, хороший пользовательский опыт позволяет пользователям просматривать товары по категориям и дает возможность компании рекомендовать другие товары, которые могут заинтересовать покупателя. После добавления товаров в корзину пользователь может просмотреть всю свою корзину и иметь возможность удалить или добавить новые товары перед оформлением заказа.

Что вы построите

В этом разделе серии практических заданий вы расширите возможности цифрового агента, созданного вами в первой части для вымышленной компании Bonjour Meal, чтобы пользователи могли просматривать каталог товаров и добавлять товары в корзину.

В этом практическом занятии ваше приложение будет

  • Отобразить каталог вопросов в рамках бизнес-сообщений.
  • Предложите пользователям товары, которые могут их заинтересовать.
  • Проверьте содержимое корзины и составьте итоговую сумму.

ab2fb6a4ed33a129.png

Что вы узнаете

  • Как развернуть веб-приложение на App Engine на платформе Google Cloud Platform
  • Как использовать механизм постоянного хранения для сохранения состояния корзины покупок

Данная практическая работа посвящена расширению функционала цифрового агента из первой части этой серии практических занятий .

Что вам понадобится

2. Настройка

В этом практическом занятии предполагается, что вы создали своего первого агента и завершили первую часть . Поэтому мы не будем рассматривать основы включения API Business Messages и Business Communications, создания ключей учетных записей служб, развертывания приложения или настройки веб-перехватчика в консоли Business Communications. Тем не менее, мы клонируем тестовое приложение, чтобы убедиться, что ваше приложение соответствует тому, на чем мы будем основываться, и включим API Datastore на платформе Google Cloud Platform, чтобы иметь возможность сохранять данные, относящиеся к корзине покупок.

Клонирование приложения из GitHub.

В терминале клонируйте репозиторий Django Echo Bot Sample в рабочую директорию вашего проекта с помощью следующей команды:

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

Скопируйте созданный вами JSON-файл с учетными данными для сервисной учетной записи в папку ресурсов примера и переименуйте его в "bm-agent-service-account-credentials.json".

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

Включите API Google Datastore.

В первой части этого практического занятия вы включили API бизнес-сообщений, API бизнес-коммуникаций и API облачной сборки.

Для этой практической работы, поскольку мы будем работать с Google Datastore, нам также необходимо включить следующий API:

  1. Откройте API Google Datastore в консоли Google Cloud.
  2. Убедитесь, что вы работаете с правильным проектом GCP.
  3. Нажмите «Включить» .

Развертывание тестового приложения

В терминале перейдите в каталог, содержащий пример, относящийся к шагу 2.

Для развертывания примера выполните следующие команды в терминале:

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • PROJECT_ID — это идентификатор проекта, который вы использовали для регистрации в API.

Обратите внимание на URL-адрес развернутого приложения в выводе последней команды:

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

Развернутый вами код содержит веб-приложение с веб-хуком для получения сообщений от Business Messages. Он включает в себя все, что мы сделали в первой части практического занятия . Если вы еще этого не сделали, пожалуйста, настройте свой веб-хук .

Приложение будет отвечать на простые запросы, например, на вопрос пользователя о часах работы Bonjour Meal. Вы можете протестировать его на своем мобильном устройстве, используя тестовые URL-адреса, которые можно получить в разделе «Информация об агенте» в консоли бизнес-коммуникаций. Тестовые URL-адреса запустят приложение «Бизнес-сообщения» на вашем мобильном устройстве, и вы сможете начать взаимодействовать со своим агентом.

3. Каталог продукции

Система учета

В большинстве случаев вы можете интегрироваться с инвентарем бренда напрямую через внутренний API. В других случаях вы можете парсить веб-страницу или создать собственную систему отслеживания запасов. Наша задача не в том, чтобы создавать систему учета запасов; мы будем использовать простой статический файл, содержащий изображения и информацию о товарах для нашего агента. В этом разделе мы будем извлекать информацию из этого статического файла, отображать ее в диалоге и позволять пользователю просматривать товары, доступные для добавления в корзину.

Статический файл инвентаризации выглядит следующим образом:

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

Давайте заставим приложение на Python прочитать этот файл!

Считывание данных из нашего инвентаря

Инвентарь представляет собой статический файл "inventory.json", расположенный в каталоге ./resources. Нам нужно добавить в views.py некоторую логику на Python для чтения содержимого JSON-файла и последующего отображения его в диалоге. Давайте создадим функцию, которая считывает данные из JSON-файла и возвращает список доступных товаров.

Определение этой функции можно разместить в любом месте файла views.py.

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

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

Это должно дать нам все необходимое для считывания данных из инвентаря. Теперь нам нужен способ представить эту информацию о товаре в ходе разговора.

Появление каталога продукции

Для упрощения в этом практическом занятии мы используем общий каталог товаров, чтобы отображать все товары из наличия в диалоге бизнес-сообщений в виде единой расширенной карусели карточек.

Для просмотра каталога товаров мы создадим предлагаемый ответ с текстом "Показать меню" и данными обратной связи " show-product-catalog ". Когда пользователи нажмут на предлагаемый ответ и ваше веб-приложение получит данные обратной связи, мы отправим карусель с расширенными карточками. Давайте добавим новую константу для этого предлагаемого ответа в начало файла views.py.

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

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

Далее мы анализируем сообщение и направляем его в новую функцию, которая отправляет расширенную карусель с карточками, содержащую каталог товаров. Сначала расширим функцию route_message , чтобы она вызывала функцию " send_product_catalog " при нажатии на предложенный ответ, а затем определим эту функцию.

В следующем фрагменте кода добавьте дополнительное условие в оператор if в функции route_message , чтобы проверить, равно ли значение normalized_message константе, которую мы определили ранее, 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)
...

И давайте убедимся, что процесс завершен, и определим функцию send_product_catalog . Функция send_product_catalog вызывает get_menu_carousel, которая генерирует карусель расширенных карточек из файла инвентаризации, который мы считывали ранее.

Определения функций можно разместить в любом месте файла views.py. Обратите внимание, что в следующем фрагменте кода используются две новые константы, которые следует добавить в начало файла.

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

Если вы изучите процесс создания элементов карусели, вы увидите, что мы также создаём экземпляр класса BusinessMessagesSuggestion . Каждое предложение представляет собой выбор пользователем товара в карусели. Когда пользователь нажимает на предложенный ответ, Business Messages отправляет на ваш веб-хук данные postbackData, содержащие JSON-описание товара и действия, которое пользователь хочет выполнить (добавить или удалить из корзины). В следующем разделе мы проанализируем подобные сообщения, чтобы фактически добавить товар в корзину.

Теперь, когда мы внесли эти изменения, давайте развернем веб-приложение в Google App Engine и протестируем его!

$ gcloud app deploy

Когда на вашем мобильном устройстве будет загружена панель диалога, отправьте сообщение "show-product-catalog", и вы увидите карусель товаров, которая будет выглядеть примерно так.

4639da46bcc5230c.png

Если вы нажмете «Добавить товар», ничего не произойдет, кроме того, что агент отправит данные обратной связи из предложенного ответа. В следующем разделе мы воспользуемся каталогом товаров и создадим корзину покупок, куда будет добавлен товар.

Созданный вами каталог товаров можно расширить различными способами. Например, добавить различные варианты напитков или вегетарианские блюда. Использование каруселей или всплывающих подсказок — отличный способ позволить пользователям детально изучить меню и найти нужный им набор товаров. В качестве дополнения к этому практическому занятию попробуйте расширить систему каталога товаров таким образом, чтобы пользователь мог просматривать напитки отдельно от еды в меню или даже указывать вегетарианские блюда.

4. Тележка для покупок

В этом разделе практического занятия мы разработаем функциональность корзины покупок, опираясь на предыдущий раздел, который позволяет просматривать доступные товары.

В числе прочего, основная функция корзины покупок позволяет пользователям добавлять товары в корзину, удалять товары из корзины, отслеживать количество каждого товара в корзине и просматривать товары в корзине.

Для отслеживания состояния корзины покупок нам необходимо сохранять данные в веб-приложении. Для упрощения экспериментов и развертывания мы будем использовать Google Datastore в Google Cloud Platform для сохранения данных. Идентификатор диалога остается неизменным между пользователем и компанией, поэтому мы можем использовать его для сопоставления пользователей с товарами в корзине покупок.

Начнём с подключения к Google Datastore и сохранения идентификатора разговора при его обнаружении.

Подключение к хранилищу данных

Мы будем подключаться к Google Datastore всякий раз, когда происходит какое-либо взаимодействие с корзиной покупок, например, когда пользователь добавляет или удаляет товар. Более подробную информацию об использовании этой клиентской библиотеки для взаимодействия с Google Datastore можно найти в официальной документации .

Следующий фрагмент кода определяет функцию для обновления корзины покупок. Функция принимает на вход следующие параметры: conversation_id и message . message содержит JSON, описывающий действие, которое хочет совершить пользователь, и уже встроен в вашу карусель, отображающую каталог товаров. Функция создает клиент Google Datastore и немедленно получает сущность ShoppingCart, где ключом является идентификатор беседы (console.id).

Скопируйте следующую функцию в файл views.py. Мы продолжим подробно рассматривать её в следующем разделе.

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)

Давайте расширим эту функцию, чтобы она добавляла товар в корзину.

Добавление товаров в корзину

Когда пользователь нажимает на предложенное действие «Добавить товар» в карусели товаров, postbackData содержит JSON-данные, описывающие действие, которое пользователь хочет выполнить. Словарь JSON имеет два ключа: «action» и «item_name», и этот словарь JSON отправляется на ваш веб-хук. Поле «item_name» — это уникальный идентификатор, связанный с товаром в файле inventory.json.

После того, как мы получим команду добавления товара в корзину и сам товар из сообщения, мы можем написать условные операторы для добавления товара. Следует учитывать несколько крайних случаев: если хранилище данных никогда не видело идентификатор диалога или если корзина покупок получает этот товар впервые. Следующий код является расширением функциональности update_shopping_cart , описанной выше. Это изменение добавляет товар в корзину покупок, который сохраняется в хранилище данных Google.

Приведённый ниже фрагмент кода является расширением предыдущей функции, добавленной в ваш файл views.py. Вы можете внести изменения по своему усмотрению или скопировать фрагмент и заменить существующую версию функции 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)

В дальнейшем эта функция будет расширена для поддержки сценария, когда cart_cmd содержит строку 'del-item', определенную в CMD_DEL_ITEM .

Связывая все воедино

Убедитесь, что вы добавили необходимые механизмы в функцию route_message , чтобы при получении сообщения о добавлении товара в корзину вызывалась функция update_shopping_cart . Вам также потребуется определить константу для добавления товаров, используя соглашение, которое мы используем в этом практическом занятии.

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)

...

На данный момент у нас есть возможность добавлять товары в корзину. Если вы развернете свои изменения в Google App Engine, вы сможете увидеть изменения в корзине покупок на панели мониторинга Google Datastore, расположенной в консоли GCP. На скриншоте ниже показана консоль Google Datastore: там есть одна сущность, названная в честь идентификатора диалога, а также некоторые связи с товарами на складе и количеством этих товаров в корзине покупок.

619dc18a8136ea69.png

В следующем разделе мы создадим способ отображения списка товаров в корзине. Механизм просмотра содержимого корзины должен показывать все товары в корзине, их количество и возможность удалить товар из корзины.

Проверка товаров в корзине

Только перечислив товары в корзине, мы сможем понять ее состояние и определить, какие из них можно удалить.

Давайте сначала отправим дружелюбное сообщение типа «Вот ваша корзина покупок:», а затем еще одно сообщение, содержащее расширенную карусель с предлагаемыми вариантами ответов «Удалить один» или «Добавить один». В расширенной карусели также должно быть указано количество товаров, сохраненных в корзине.

Прежде чем приступить к написанию функции, следует учесть следующее: если в корзине только один тип товаров, мы не можем отобразить её в виде карусели. Карусели с расширенным набором карточек должны содержать как минимум две карточки. С другой стороны, если в корзине нет товаров, мы хотим отобразить простое сообщение о том, что корзина пуста.

Учитывая это, давайте определим функцию под названием send_shopping_cart . Эта функция подключается к Google Datastore и запрашивает сущность ShoppingCart на основе идентификатора диалога (Conversation ID). Получив его, мы вызовем функцию get_inventory_data и используем расширенную карусель карточек для отображения состояния корзины покупок. Нам также потребуется получить ID товара по названию, и мы можем объявить функцию для поиска этого значения в Google Datastore. По мере создания карусели мы можем связывать предлагаемые ответы с удалением или добавлением товаров по ID товара. Приведенный ниже фрагмент кода выполняет все эти операции. Скопируйте код в любое место файла 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)

...

Убедитесь, что вы уже определили CMD_SHOW_CART в верхней части файла views.py, и вызовите send_shopping_cart если пользователь отправит сообщение, содержащее '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

Исходя из логики, реализованной в функции send_shopping_cart , при вводе команды 'show-cart' вы получите либо сообщение о том, что в корзине ничего нет, либо расширенную карточку с одним товаром в корзине, либо карусель карточек с несколькими товарами. Кроме того, мы предлагаем три варианта ответа: «Посмотреть общую стоимость», «Очистить корзину» и «Посмотреть меню».

Попробуйте развернуть указанные выше изменения кода, чтобы проверить, отслеживает ли ваша корзина покупок добавляемые вами товары и можете ли вы просмотреть содержимое корзины в диалоговом окне, как показано на скриншотах выше. Вы можете развернуть изменения с помощью этой команды, запущенной из каталога, указанного в шаге 2, куда вы добавляете изменения.

$ gcloud app deploy

В следующем разделе, после создания функциональности удаления товара из корзины, мы разработаем функцию «Посмотреть общую цену». Функция get_cart_price будет работать аналогично функции «Посмотреть корзину» в том смысле, что она будет сопоставлять данные в Datastore с файлом inventory.json для получения общей цены корзины. Это будет полезно для следующей части практического занятия, где мы будем интегрировать систему платежей.

Удаление товаров из корзины

Наконец, мы можем завершить разработку поведения корзины покупок, добавив функцию удаления корзины. Замените существующую функцию update_shopping_cart следующим фрагментом кода.

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)

Отправка подтверждающего сообщения

Когда пользователь добавляет товар в корзину, следует отправить подтверждающее сообщение, указывающее на то, что его действие было выполнено и запрос обработан. Это не только помогает установить правильные ожидания, но и поддерживает диалог.

Давайте расширим функцию update_shopping_cart таким образом, чтобы она отправляла сообщение на идентификатор диалога о добавлении или удалении товара и предлагала просмотреть корзину покупок или снова посмотреть меню.

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

Вот и все! Полнофункциональная корзина покупок, позволяющая пользователю добавлять товары, удалять товары и просматривать их в корзине.

На этом этапе, если вы хотите увидеть функциональность корзины покупок в диалоге деловых сообщений, разверните приложение, чтобы взаимодействовать с вашим агентом. Вы можете сделать это, выполнив следующую команду в каталоге шага 2.

$ gcloud app deploy

5. Подготовка к платежам

В рамках подготовки к интеграции с платежным процессором в следующей части серии нам нужен способ получения цены корзины покупок. Давайте создадим функцию, которая будет получать цену, сопоставляя данные о корзине покупок в Google Datastore, извлекая цену каждого товара из складских запасов и умножая цену на количество каждого товара в корзине.

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

...

И наконец, мы можем использовать эту функцию и отправить сообщение пользователю.

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

Чтобы всё это объединить, давайте обновим функцию route_message и константу, чтобы запустить описанную выше логику.

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

Вот несколько скриншотов, демонстрирующих результат применения описанной выше логики:

8feacf94ed0ac6c4.png

Когда мы будем готовы к интеграции с платежным процессором в следующей части практического занятия, мы вызовем функцию get_cart_price , чтобы передать данные в платежный процессор и запустить процесс оплаты.

Ещё раз, вы можете опробовать функциональность корзины покупок в переписке бизнес-сообщений, развернув приложение и взаимодействуя со своим агентом.

$ gcloud app deploy

6. Поздравляем!

Поздравляем, вы успешно создали интерфейс корзины покупок в разделе «Бизнес-сообщения».

В этом практическом занятии мы не рассматривали функцию очистки всей корзины покупок. При желании вы можете расширить функциональность приложения, добавив функцию "Очистка корзины". Решение доступно на шаге 3 в клонированном вами исходном коде.

В одном из следующих разделов мы интегрируемся с внешней платежной системой, чтобы ваши пользователи могли совершать платежные транзакции с вашим брендом.

Что делает тележку для покупок хорошей?

Удобная корзина покупок в интерактивном режиме ничем не отличается от мобильного приложения или обычного магазина. Возможность добавлять и удалять товары, а также рассчитывать цену корзины — это лишь некоторые из функций, которые мы рассмотрели в этом практическом занятии. Отличие от реальной корзины покупок заключается в возможности видеть цену всех товаров в корзине в любой момент времени, по мере добавления или удаления товаров. Такие ценные функции сделают ваш интерактивный опыт покупок по-настоящему выдающимся!

Что дальше?

Когда будете готовы, ознакомьтесь с некоторыми из следующих тем, чтобы узнать о более сложных взаимодействиях, которые можно реализовать в бизнес-сообщениях:

Справочная документация