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

1. Введение

53003251caaf2be5.png8826bd8cb0c0f1c7.png

Последнее обновление: 30 октября 2020 г.

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

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

Что такое хорошая корзина для покупок?

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

9d17537b980d0e62.png

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

Что ты построишь

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

В этой кодовой лаборатории ваше приложение будет

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

ab2fb6a4ed33a129.png

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

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

Целью этой лаборатории является расширение цифрового агента из первой части этой серии лабораторий .

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

2. Приступаем к настройке

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

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

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

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

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

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

  1. Откройте API хранилища данных Google в Google Cloud Console.
  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-адреса запустят возможности Business Messages на вашем мобильном устройстве, и вы сможете начать там взаимодействовать со своим агентом.

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. Нам нужно добавить некоторую логику Python в view.py, чтобы прочитать содержимое файла JSON и затем представить его в разговоре. Давайте создадим функцию, которая считывает данные из файла JSON и возвращает список доступных продуктов.

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

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

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

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

Вывод каталога продукции

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

Чтобы просмотреть каталог продуктов, мы создадим предлагаемый ответ с текстом «Показать меню» и postbackData « 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, который генерирует карусель расширенных карточек из файла инвентаря, который мы прочитали ранее.

Определения функций можно разместить в любом месте файлаview.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 и сохранения идентификатора разговора, когда мы его увидим.

Соединение с хранилищем данных

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

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

Скопируйте следующую функцию в файл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 есть два ключа: «действие» и «имя_элемента», и этот словарь JSON отправляется на ваш веб-перехватчик. Поле «item_name» — это уникальный идентификатор, связанный с элементом в файле Inventory.json.

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

Следующий фрагмент является расширением предыдущей функции, добавленной в ваш файлview.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 и запрашивает объект ShoppingCart на основе идентификатора разговора. Получив это, мы вызовем функцию get_inventory_data и воспользуемся каруселью с расширенными карточками, чтобы сообщить о состоянии корзины покупок. Нам также потребуется получить идентификатор продукта по названию, и мы можем объявить функцию для поиска в хранилище данных Google для определения этого значения. Во время создания карусели мы можем связать предлагаемые ответы для удаления элементов или добавления элементов по идентификатору продукта. Фрагмент ниже выполняет все эти операции. Скопируйте код в любое место в view.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 в верхней части файла view.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

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

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

$ gcloud app deploy

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

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

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 , чтобы передать данные в платежный процессор и запустить поток платежей.

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

$ gcloud app deploy

6. Поздравления

Поздравляем, вы успешно создали корзину покупок в Business Messages.

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

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

Что такое хорошая корзина для покупок?

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

Что дальше?

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

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