Kup online z odbiorem w sklepie: posiłek bonjour – część 2 – tworzenie koszyka na zakupy

1. Wstęp

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Ostatnia aktualizacja: 30.10.2020

Jak utworzyć koszyk na zakupy w Business Messages

To drugie ćwiczenie z programowania z serii, które ma na celu budowanie ścieżki użytkownika do zakupu online i odbioru produktów w sklepie. Na wielu ścieżkach e-commerce koszyk na zakupy jest kluczem do konwersji użytkowników w płacących klientów. Koszyk na zakupy to także sposób na lepsze poznanie klientów i proponowanie innych produktów, które mogą ich zainteresować. W tym ćwiczeniu z programowania skupimy się na tworzeniu koszyka na zakupy i wdrażaniu aplikacji w Google App Engine.

Co wyróżnia dobry koszyk na zakupy?

Koszyki są kluczem do udanego zakupu online. Okazuje się, że Business Messages nie tylko pomaga w zapytaniu potencjalnych klientów o produkt, ale też ułatwia cały proces zakupu, a nawet zrealizowanie płatności w rozmowie.

9d17537b980d0e62.png

Dobry koszyk na zakupy pozwala użytkownikom przeglądać produkty według kategorii i polecać inne produkty, które mogą zainteresować kupującego. Po dodaniu większej liczby produktów do koszyka użytkownik może przejrzeć cały koszyk i go usunąć lub dodać kolejne.

Co utworzysz

W tej sekcji z serii ćwiczeń z programowania rozszerzysz możliwości agenta cyfrowego utworzonego w części 1 dla fikcyjnej firmy Bonjour Meal, aby użytkownicy mogli przeglądać katalog produktów i dodawać je do koszyka.

W tym ćwiczeniu z programowania Twoja aplikacja

  • Wyświetlanie katalogu pytań w Business Messages
  • Sugeruj produkty, które mogą zainteresować użytkowników
  • Sprawdzanie zawartości koszyka i tworzenie podsumowania ceny

ab2fb6a4ed33a129.png

Czego się nauczysz

  • Jak wdrożyć aplikację internetową w App Engine w Google Cloud Platform
  • Jak zapisać stan koszyka na zakupy za pomocą mechanizmu pamięci trwałej

To ćwiczenie z programowania koncentruje się na zwiększeniu możliwości agenta cyfrowego z 1 części z tej serii ćwiczeń z programowania.

Czego potrzebujesz

2. Konfiguracja

W tym ćwiczeniu z programowania zakładamy, że masz utworzonego pierwszego agenta i masz już za sobą część 1 ćwiczenia z programowania. Dlatego nie będziemy omawiać podstaw włączania interfejsów Business Messages i Business Communications API, tworzenia kluczy konta usługi, wdrażania aplikacji ani konfigurowania webhooka w Business Communications Console. Skopiujemy więc przykładową aplikację, aby upewnić się, że jest ona zgodna z tym, nad czym pracujemy. Włączymy też interfejs API dla Datastore w Google Cloud Platform, aby umożliwić zapisywanie danych koszyka na zakupy.

Klonowanie aplikacji z GitHuba

W terminalu skopiuj przykładowy bota Django Echo Bot do katalogu roboczego swojego projektu za pomocą tego polecenia:

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

Skopiuj plik danych logowania w formacie JSON utworzony dla konta usługi do folderu zasobów przykładu i zmień nazwę danych na „bm-agent-service-account-credentials.json”.

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

Włączanie Google Datastore API

W części 1 tego ćwiczenia z programowania udało Ci się włączyć interfejsy Business Messages API, Business Communication API oraz Cloud Build API.

Na potrzeby tego ćwiczenia z programowania będziemy pracować z Google Datastore, więc musimy też włączyć ten interfejs API:

  1. Otwórz Google Datastore API w konsoli Google Cloud.
  2. Sprawdź, czy korzystasz z właściwego projektu GCP.
  3. Kliknij Włącz.

Wdrażanie przykładowej aplikacji

W terminalu przejdź do katalogu kroku 2 przykładu.

Aby wdrożyć przykład, uruchom w terminalu te polecenia:

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • PROJECT_ID to identyfikator projektu użytego do zarejestrowania się w interfejsach API.

Zapisz adres URL wdrożonej aplikacji w danych wyjściowych ostatniego polecenia:

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

Wdrożony właśnie kod zawiera aplikację internetową z webhookiem, która umożliwia odbieranie wiadomości z Business Messages. Zawiera on wszystkie elementy z pierwszej części ćwiczeń z programowania. Skonfiguruj webhooka, jeśli jeszcze nie zostało to zrobione.

Aplikacja będzie odpowiadać na proste pytania, np. o godziny otwarcia restauracji Bonjour Meal. Przetestuj to na urządzeniu mobilnym za pomocą testowych adresów URL dostępnych w sekcji Informacje o agencie w konsoli komunikacji biznesowej. Testowe adresy URL uruchomią wersję Business Messages na urządzeniu mobilnym, na którym możesz zacząć korzystać z agenta.

3. Katalog produktów

System asortymentowy

W większości przypadków możesz przeprowadzić integrację bezpośrednio z zasobami reklamowymi marki za pomocą wewnętrznego interfejsu API. W innych, możesz np. skopiować stronę internetową lub utworzyć własny system śledzenia asortymentu. Nie skupimy się na tworzeniu systemu zasobów reklamowych. Użyjemy prostego pliku statycznego z obrazami i informacjami o produkcie dla naszego agenta. W tej sekcji pobieramy informacje z tego statycznego pliku, przekazujemy je do rozmowy i umożliwiamy użytkownikowi przeglądanie produktów, które można dodać do koszyka.

Statyczny plik zasobów reklamowych wygląda tak:

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

Pobierzmy aplikację w Pythonie do odczytania tego pliku.

Czytanie z naszych zasobów reklamowych

Zasoby reklamowe to statyczny plik o nazwie „inventory.json” w katalogu ./resources. Aby odczytać zawartość pliku JSON, a następnie wyświetlić go w rozmowie, musimy dodać do pliku view.py elementy logiczne Pythona. Utwórzmy funkcję, która odczytuje dane z pliku JSON i zwraca listę dostępnych produktów.

Tę definicję funkcji można umieścić w dowolnym miejscu w pliku view.py.

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

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

Powinno to dać nam to, czego potrzebujemy do odczytania danych z zasobów. Teraz musimy znaleźć sposób na umieszczenie informacji o tym produkcie w rozmowie.

Zaprezentowanie katalogu produktów

Dla uproszczenia tych ćwiczeń z programowania mamy ogólny katalog produktów, który pozwala wyświetlać wszystkie elementy zasobów reklamowych w rozmowie w Business Messages za pomocą pojedynczej karuzeli kart rozszerzonych.

Aby wyświetlić katalog produktów, utworzymy sugerowaną odpowiedź zawierającą tekst „Pokaż menu” oraz postbackData „show-product-catalog”. Gdy użytkownik kliknie sugerowaną odpowiedź, a Twoja aplikacja internetowa otrzyma dane wywołania zwrotnego, wyślemy karuzelę kart rozszerzonych. Dodajmy nową stałą dla tej sugerowanej odpowiedzi u góry strony views.py.

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

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

Następnie analizujemy wiadomość i kierujemy ją do nowej funkcji, która wysyła karuzelę kart rozszerzonych zawierającą katalog produktów. Najpierw rozszerz funkcję route_message, aby wywoływała funkcję „send_product_catalog” po kliknięciu sugerowanej odpowiedzi, a następnie zdefiniujemy tę funkcję.

W tym fragmencie kodu dodaj do instrukcji if w funkcji route_message dodatkowy warunek, który pozwoli Ci sprawdzić, czy normalized_message równa się stałej zdefiniowanej wcześniej (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)
...

Dokończmy proces i zdefiniujmy send_product_catalog. Funkcja send_product_catalog wywołuje funkcję get_menu_carousel,, która generuje karuzelę kart rozszerzonych z odczytanego wcześniej pliku zasobów reklamowych.

Definicję funkcji można umieścić w dowolnym miejscu w pliku view.py. Zwróć uwagę, że ten fragment wykorzystuje 2 nowe stałe, które należy dodać na początku pliku.

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

Jeśli sprawdzasz tworzenie elementów karuzeli, utworzymy też wystąpienie klasy BusinessMessagesSuggestion. Każda sugestia odzwierciedla wybór użytkownika do produktu w karuzeli. Gdy użytkownik kliknie sugerowaną odpowiedź, Business Messages wyśle do webhooka informacje o wywołaniu zwrotnym zawierające dane w formacie JSON zawierające opis produktu i działanie, które użytkownik chce wykonać (dodanie lub usunięcie z koszyka). W tej sekcji przeanalizujemy takie wiadomości, by dowiedzieć się, jak dodać produkt do koszyka.

Teraz, gdy wprowadziliśmy te zmiany, możemy wdrożyć aplikację internetową w Google App Engine i wypróbować jej działanie.

$ gcloud app deploy

Po załadowaniu platformy do rozmowy na urządzeniu mobilnym wyślij wiadomość „show-product-catalog”, a zobaczysz karuzelę produktów podobną do tej.

4639da46bcc5230c.png

Po kliknięciu Dodaj element nie dzieje się tak naprawdę nic poza tym,że pracownik obsługi klienta powtórzy dane wywołania zwrotnego z sugerowanej odpowiedzi. W następnej sekcji wykorzystamy katalog produktów i utworzymy z niego koszyk na zakupy, do którego produkt zostanie dodany.

Stworzony właśnie katalog produktów możesz rozszerzyć na różne sposoby. W menu możesz podać inne napoje lub dania wegetariańskie. Korzystanie z karuzeli lub elementów z sugestią to świetny sposób na umożliwienie użytkownikom przejrzenia opcji menu w celu dotarcia do zestawu produktów, których szukają. Jako rozszerzenie tego ćwiczenia z programowania spróbuj rozszerzyć system katalogu produktów, aby użytkownik mógł wyświetlać napoje niezależnie od jedzenia w menu, a nawet określić opcje wegetariańskie.

4. Koszyk

W tej sekcji ćwiczenia z programowania stworzymy funkcję koszyka na zakupy na podstawie poprzedniej sekcji, która umożliwi nam przeglądanie dostępnych produktów.

Najważniejsze funkcje koszyka na zakupy pozwalają użytkownikom dodawać produkty do koszyka, usuwać z niego produkty, sprawdzać liczbę wszystkich pozycji w koszyku i sprawdzać, co znajduje się w koszyku.

Śledzenie stanu koszyka oznacza, że musimy utrzymywać dane w aplikacji internetowej. Aby ułatwić eksperymentowanie i wdrażanie, do przechowywania danych będziemy używać Google Datastore w Google Cloud Platform. Identyfikator rozmowy pozostaje niezmienny między użytkownikiem a firmą, więc możemy go używać do kojarzenia użytkowników z produktami w koszyku na zakupy.

Zacznijmy od połączenia się z Google Datastore i zachowania identyfikatora rozmowy, gdy tylko go zauważymy.

Łączenie z Datastore

Łączymy się z Google Datastore za każdym razem, gdy zostanie wykonana dowolna interakcja z koszykiem na zakupy, na przykład dodanie lub usunięcie produktu. Więcej informacji o korzystaniu z tej biblioteki klienta do interakcji z Google Datastore znajdziesz w oficjalnej dokumentacji.

Poniższy fragment kodu definiuje funkcję do aktualizacji koszyka na zakupy. Funkcja przyjmuje te dane wejściowe: conversation_id i message. message zawiera plik JSON opisujący działanie, które chce wykonać użytkownik. Jest on już wbudowany w karuzelę wyświetlającą katalog produktów. Funkcja tworzy klienta Google Datastore i od razu pobiera encję ShoppingCart, gdzie klucz to identyfikator rozmowy.

Skopiuj poniższą funkcję do pliku view.py. W następnej sekcji omówimy tę funkcję.

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)

Rozszerzmy tę funkcję, aby dodać produkt do koszyka.

Dodawanie produktów do koszyka

Gdy użytkownik kliknie sugerowane działanie Dodaj element w karuzeli produktów, postbackData będzie zawierał plik JSON opisujący działanie, które chce wykonać użytkownik. Słownik JSON ma 2 klucze: „action” i „item_name”. Ten słownik JSON jest wysyłany do webhooka. Pole „item_name” to unikalny identyfikator powiązany z elementem w pliku Inventory.json.

Gdy już przeanalizujemy polecenie koszyka i jego element w wiadomości, możemy dodać instrukcje warunkowe, aby dodać ten element. Warto rozważyć przypadki, gdy usługa Datastore nigdy nie wykryła identyfikatora rozmowy lub gdy koszyk na zakupy otrzymuje ten produkt po raz pierwszy. Poniżej znajduje się rozszerzenie zdefiniowanej powyżej funkcji update_shopping_cart. Ta zmiana dodaje do koszyka produkt przechowywany w Google Datastore.

Ten fragment jest rozszerzeniem poprzedniej funkcji dodanej do pliku view.py. Możesz dodać różnicę lub skopiować fragment kodu i zastąpić obecną wersję funkcji 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)

Ta funkcja zostanie później rozszerzona o obsługę scenariusza, w którym cart_cmd zawiera ciąg znaków „del-item” zdefiniowany w zasadzie CMD_DEL_ITEM.

Jak to połączyć

Pamiętaj, by dodać usługi hydrauliczne do funkcji route_message, aby w przypadku otrzymania wiadomości o konieczności dodania produktu do koszyka wywoływała się funkcja update_shopping_cart. Musisz też określić stałą potrzebę dodawania elementów zgodnie z konwencją używaną podczas ćwiczeń z programowania.

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)

...

Obecnie możemy dodawać produkty do koszyka. Po wdrożeniu zmian w Google App Engine powinny być one widoczne w panelu Google Datastore dostępnym w konsoli GCP. Na zrzucie ekranu poniżej konsoli Google Datastore znajduje się 1 encja o nazwie Identyfikator rozmowy, wraz z określonymi relacjami z przedmiotami w asortymencie i liczbą produktów w koszyku.

619dc18a8136ea69.png

W następnej sekcji omówimy sposób wyświetlania produktów w koszyku. Mechanizm sprawdzania koszyka powinien pokazywać wszystkie produkty w koszyku, ich liczbę i opcję usunięcia z niego.

Sprawdzanie produktów w koszyku

Dodanie listy produktów do koszyka to jedyny sposób, w jaki możemy poznać jego stan i określić, które produkty możemy usunąć.

Najpierw wyślijmy przyjazną wiadomość, np. „Oto Twój koszyk:”, a potem kolejną wiadomość z karuzelą kart informacyjnych z powiązanymi sugerowanymi odpowiedziami „Usuń jeden z nich” lub „Dodaj”. Karuzela kart rozszerzonych powinna dodatkowo zawierać liczbę produktów zapisanych w koszyku.

O czym należy pamiętać, zanim przejdziemy do tworzenia funkcji: jeśli w koszyku znajduje się tylko jeden rodzaj produktu, nie możemy wyświetlić go w karuzeli. Karuzele kart rozszerzonych muszą zawierać co najmniej 2 karty. Z drugiej strony, jeśli w koszyku nie ma żadnych produktów, chcemy wyświetlić prosty komunikat, że koszyk jest pusty.

Mając to na uwadze, zdefiniujmy funkcję o nazwie send_shopping_cart. Ta funkcja łączy się z Google Datastore i wysyła żądanie encji ShoppingCart na podstawie identyfikatora rozmowy. Wywołujemy funkcję get_inventory_data i używamy karuzeli kart rozszerzonych, by raportować stan koszyka na zakupy. Musimy też uzyskać identyfikator produktu z nazwy. Możemy zadeklarować funkcję, aby umożliwić sprawdzenie tej wartości w Google Datastore. Podczas tworzenia karuzeli możemy powiązać sugerowane odpowiedzi, aby usunąć produkty lub dodać produkty według identyfikatora produktu. Wszystkie te operacje wykonuje się we fragmencie kodu. Skopiuj kod w dowolnym miejscu do pliku 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)

...

Upewnij się, że u góry strony view.py jest już zdefiniowana opcja CMD_SHOW_CART, i wywołaj metodę send_shopping_cart, jeśli użytkownik wyśle wiadomość zawierającą dyrektywę „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

Zgodnie z logiką wprowadzonej w funkcji send_shopping_cart po wpisaniu „show-cart” zobaczysz komunikat z informacją, że w koszyku nie ma nic, kartę informacyjną z jednym produktem lub karuzelę kart z wieloma produktami. Mamy też 3 sugerowane odpowiedzi: „Zobacz łączną cenę”, „Opróżnij koszyk” i „Zobacz menu”.

Spróbuj wdrożyć powyższe zmiany w kodzie, aby sprawdzić, czy koszyk na zakupy śledzi dodane przez Ciebie produkty i czy możesz sprawdzić zawartość koszyka z poziomu interfejsu wątków, tak jak to widać na zrzutach ekranu powyżej. Zmiany możesz wdrożyć za pomocą tego polecenia w katalogu p kroku 2, w którym dodajesz zmiany.

$ gcloud app deploy

Funkcję „Zobacz łączną cenę” utworzymy w następnej sekcji po zbudowaniu funkcji usuwania produktu z koszyka. Funkcja get_cart_price będzie działać podobnie do funkcji „Zobacz koszyk” w tym sensie, że porównuje dane w Datastore z plikiem Inventory.json w celu uzyskania łącznej ceny koszyka na zakupy. Będzie to przydatne w następnej części ćwiczeń z programowania, w ramach których omawiamy integrację z płatnościami.

Usuwanie produktów z koszyka

Możemy zakończyć działanie koszyka, wprowadzając funkcję usuwania koszyka. Zastąp obecną funkcję update_shopping_cart tym fragmentem.

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)

Wysyłanie wiadomości z potwierdzeniem

Gdy użytkownik doda produkt do koszyka, wyślij wiadomość z potwierdzeniem, że ten krok został odebrany i że jego prośba została rozpatrzona. W ten sposób nie tylko określasz oczekiwania klienta, ale też podtrzymujesz rozmowę.

Rozszerzmy funkcję update_shopping_cart, aby wysyłała do identyfikatora rozmowy wiadomość, że produkt został dodany lub usunięty, oraz sugerowała sprawdzenie koszyka na zakupy lub ponowne wyświetlenie menu.

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

To powinno zadziałać! Wszechstronny koszyk na zakupy, który umożliwia użytkownikom dodawanie i usuwanie produktów oraz recenzowanie produktów.

Jeśli na tym etapie chcesz, aby funkcja koszyka na zakupy była widoczna w rozmowie z Business Messages, wdróż aplikację, aby nawiązać kontakt z pracownikiem obsługi klienta. Aby to zrobić, uruchom to polecenie w katalogu kroku 2.

$ gcloud app deploy

5. Przygotowuję do płatności

Przygotowując się do integracji z firmą obsługującą płatności w następnej części tej serii, potrzebujemy sposobu na uzyskanie ceny koszyka. Utwórzmy funkcję, która pobiera dla nas cenę, porównując ceny produktów w Google Datastore, pobierając cenę każdego produktu z asortymentu i mnożąc cenę przez liczbę sztuk każdego produktu w koszyku.

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

...

Możemy też wykorzystać tę funkcję i wysłać wiadomość do użytkownika.

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

Aby to wszystko połączyć, zaktualizujmy funkcję route_message i stałą, aby aktywować powyższą logikę.

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

Oto kilka zrzutów ekranu, które pokazują, do czego służy przedstawiona logika:

8feacf94ed0ac6c4.png

Gdy w następnej części ćwiczenia z programowania będziemy gotowi do integracji z firmą przetwarzającą płatności, wywołamy funkcję get_cart_price, aby przekazać dane do podmiotu przetwarzającego płatności i rozpocząć proces płatności.

Ponownie możesz wypróbować tę funkcję koszyka na zakupy w rozmowie w Business Messages, wdrażając aplikację i wchodząc w interakcję z pracownikiem obsługi klienta.

$ gcloud app deploy

6. Gratulacje

Gratulacje, udało Ci się utworzyć obsługę koszyka w Business Messages.

W tym ćwiczeniu z programowania nie omawialiśmy funkcji opróżniania całego koszyka. Jeśli chcesz, możesz spróbować rozszerzyć aplikację o funkcję „Opróżnij koszyk”. Rozwiązanie jest dostępne w kroku 3 sklonowanego kodu źródłowego.

W przyszłości przeprowadzimy integrację z zewnętrznym dostawcą usług płatniczych, aby umożliwić użytkownikom realizowanie transakcji płatności związanych z Twoją marką.

Co wyróżnia dobry koszyk na zakupy?

Sposób korzystania z koszyka podczas rozmowy nie różni się niczym od korzystania z aplikacji mobilnej czy w sklepie stacjonarnym. Możliwość dodawania i usuwania produktów oraz obliczania ceny koszyka to tylko niektóre z funkcji, które omówiliśmy w tym ćwiczeniu z programowania. Inną funkcją niż prawdziwy koszyk na zakupy jest możliwość sprawdzenia ceny wszystkich produktów w koszyku w danym momencie, gdy dodajesz produkty lub usuwasz je. Dzięki tym rodzajom funkcji o wysokiej wartości Twoje rozmowy w sklepie będą się wyróżniać.

Co dalej?

Następnie zapoznaj się z poniższymi tematami, aby dowiedzieć się więcej o bardziej złożonych interakcjach, które można nawiązywać w Business Messages:

Dokumentacja