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

1. Wprowadzenie

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Ostatnia aktualizacja: 30 października 2020 r.

Tworzenie koszyka w Business Messages

To drugi z serii samouczków, które mają na celu stworzenie ścieżki użytkownika „Kup online, odbierz w sklepie”. W wielu ścieżkach zakupowych koszyk na zakupy jest kluczowy dla przekształcenia użytkowników w płacących klientów. Koszyk na zakupy to także sposób na lepsze poznanie klientów i oferowanie im sugestii dotyczących 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.

Czym cechuje się dobry koszyk na zakupy?

Koszyki są kluczowym elementem udanych zakupów online. Okazuje się, że Business Messages nie tylko ułatwia odpowiadanie na pytania potencjalnych klientów dotyczące produktu, ale też może ułatwiać cały proces zakupowy aż do dokonania płatności w ramach rozmowy.

9d17537b980d0e62.png

Oprócz dobrego koszyka na zakupy, dobre wrażenia z zakupów pozwalają użytkownikom przeglądać produkty według kategorii i umożliwiają firmie polecanie innych produktów, które mogą zainteresować kupującego. Po dodaniu kolejnych produktów do koszyka na zakupy użytkownik może przejrzeć jego zawartość i usunąć lub dodać kolejne produkty przed dokonaniem płatności.

Co utworzysz

W tej części serii Codelabs rozbudujesz cyfrowego agenta utworzonego w części 1 dla fikcyjnej firmy Bonjour Meal, aby użytkownicy mogli przeglądać katalog produktów i dodawać je do koszyka na zakupy.

W tym ćwiczeniu z programowania aplikacja:

  • Wyświetlanie katalogu pytań w Business Messages
  • sugerować produkty, które mogą zainteresować użytkowników;
  • sprawdzanie zawartości koszyka na zakupy i tworzenie podsumowania ceny całkowitej;

ab2fb6a4ed33a129.png

Czego się nauczysz

  • Jak wdrożyć aplikację internetową w App Engine na Google Cloud Platform
  • Jak używać mechanizmu pamięci trwałej do zapisywania stanu koszyka na zakupy

To ćwiczenie z programowania koncentruje się na rozszerzeniu agenta cyfrowego z części 1 tej serii ćwiczeń.

Czego potrzebujesz

2. Przygotowania

W tym ćwiczeniu zakłada się, że utworzono już pierwszego agenta i ukończono pierwszą część ćwiczenia. Nie będziemy więc omawiać podstawowych kwestii związanych z włączaniem interfejsów Business Messages API i Business Communications API, tworzeniem kluczy konta usługi, wdrażaniem aplikacji ani konfigurowaniem webhooka w konsoli Business Communications. Sklonujemy przykładową aplikację, aby upewnić się, że jest ona zgodna z naszą, i włączymy interfejs API Datastore na platformie Google Cloud Platform, aby można było przechowywać dane dotyczące koszyka na zakupy.

Klonowanie aplikacji z GitHuba

W terminalu sklonuj przykładowego bota Django Echo do katalogu roboczego projektu za pomocą tego polecenia:

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

Skopiuj plik JSON z danymi logowania utworzony dla konta usługi do folderu zasobów w przykładowym kodzie i zmień nazwę danych logowania 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łącz interfejs Google Datastore API

W części 1 tego ćwiczenia z programowania włączyliśmy interfejsy Business Messages API, Business Communications API i Cloud Build API.

W tym ćwiczeniu będziemy korzystać 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 pracujesz nad właściwym projektem GCP.
  3. Kliknij Włącz.

Wdrażanie przykładowej aplikacji

W terminalu przejdź do katalogu step-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 rejestracji w interfejsach API.

Zwróć uwagę na adres URL wdrożonej aplikacji w danych wyjściowych ostatniego polecenia:

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

Wdrożony przez Ciebie kod zawiera aplikację internetową z webhookiem do odbierania wiadomości z Business Messages. Zawiera wszystko, co zostało zrobione w części 1 samouczka. Jeśli jeszcze nie zostało to zrobione, skonfiguruj webhooka.

Aplikacja będzie odpowiadać na proste zapytania, np. gdy użytkownik zapyta o godziny otwarcia restauracji Bonjour Meal. Przetestuj to na urządzeniu mobilnym za pomocą adresów URL testu, które możesz pobrać z sekcji Informacje o agencie w konsoli usług komunikacji biznesowej. Adresy URL testu uruchomią funkcję Business Messages na urządzeniu mobilnym, dzięki czemu możesz rozpocząć interakcję z agentem.

3. Katalog produktów

system zarządzania asortymentem

W większości przypadków możesz zintegrować się bezpośrednio z asortymentem marki za pomocą wewnętrznego interfejsu API. W innych przypadkach możesz pobrać dane ze strony internetowej lub utworzyć własny system śledzenia asortymentu. Nie chcemy tworzyć systemu asortymentu. Użyjemy prostego pliku statycznego, który zawiera obrazy i informacje o produktach dla naszego agenta. W tej sekcji pobierzemy informacje z tego statycznego pliku, wyświetlimy je w konwersacji i umożliwimy użytkownikowi przeglądanie produktów, które można dodać do koszyka.

Statyczny plik asortymentu 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
        }
    ]
}

Sprawmy, aby aplikacja w Pythonie odczytała ten plik.

Odczytywanie z naszego asortymentu

Asortyment to plik statyczny o nazwie „inventory.json” znajdujący się w katalogu ./resources. Musimy dodać do pliku views.py logikę Pythona, która odczyta zawartość pliku JSON, a następnie wyświetli ją w rozmowie. Utwórzmy funkcję, która odczytuje dane z pliku JSON i zwraca listę dostępnych produktów.

Definicję tej funkcji można umieścić w dowolnym miejscu w pliku views.py.

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

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

Powinno to wystarczyć do odczytania danych z asortymentu. Teraz musimy znaleźć sposób na wyświetlanie tych informacji o produkcie w rozmowie.

Wyświetlanie katalogu produktów

W tym samouczku dla uproszczenia mamy ogólny katalog produktów, który wyświetla wszystkie produkty w rozmowie w Business Messages za pomocą jednego karuzeli z kartami z elementami multimedialnymi.

Aby wyświetlić katalog produktów, utworzymy sugerowaną odpowiedź z tekstem „Pokaż menu” i danymi zwrotnymi „show-product-catalog”. Gdy użytkownicy klikną sugerowaną odpowiedź, a Twoja aplikacja internetowa otrzyma dane zwrotne, wyślemy karuzelę kart rozszerzonych. Dodajmy nową stałą dla tej proponowanej odpowiedzi u góry pliku 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ę z elementami rozszerzonymi zawierającą katalog produktów. Najpierw rozszerz funkcję route_message, aby wywoływała funkcję „send_product_catalog”, gdy użytkownik kliknie sugerowaną odpowiedź. Następnie zdefiniujemy tę funkcję.

W tym fragmencie kodu dodaj do instrukcji if w funkcji route_message dodatkowy warunek, aby sprawdzić, czy normalized_message jest równe zdefiniowanej wcześniej stałej 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)
...

Upewnijmy się, że proces został ukończony i zdefiniowano send_product_catalog. send_product_catalog wywołuje funkcję get_menu_carousel,, która generuje karuzelę kart informacyjnych z pliku asortymentu odczytanego wcześniej.

Definicje funkcji mogą znajdować się w dowolnym miejscu w pliku views.py. Pamiętaj, że poniższy fragment kodu korzysta z 2 nowych stałych, 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 przyjrzysz się tworzeniu elementów karuzeli, zauważysz, że tworzymy też instancję klasy BusinessMessagesSuggestion. Każda sugestia reprezentuje wybór użytkownika dotyczący produktu w karuzeli. Gdy użytkownik kliknie sugerowaną odpowiedź, Business Messages wyślą do Twojego webhooka dane postbackData zawierające kod JSON opisujący produkt i działanie, które użytkownik chce wykonać (dodać lub usunąć z koszyka). W następnej sekcji przeanalizujemy wiadomości, które wyglądają w ten sposób, aby móc dodać produkt do koszyka.

Wprowadziliśmy już te zmiany, więc teraz wdróżmy aplikację internetową w Google App Engine i sprawdźmy, jak działa.

$ gcloud app deploy

Gdy na urządzeniu mobilnym załadujesz platformę konwersacyjną, wyślij wiadomość „show-product-catalog”. Powinna się pojawić karuzela produktów podobna do tej.

4639da46bcc5230c.png

Jeśli klikniesz Dodaj produkt, nic się nie stanie, a agent tylko powtórzy dane zwrotne z sugerowanej odpowiedzi. W następnej sekcji wykorzystamy katalog produktów i użyjemy go do utworzenia koszyka na zakupy, do którego zostanie dodany produkt.

Utworzony katalog produktów można rozbudowywać na różne sposoby. Możesz mieć różne opcje menu napojów lub opcje wegetariańskie. Korzystanie z karuzel lub elementów z sugestią to świetny sposób na umożliwienie użytkownikom pozyskiwania szczegółowych informacji o opcjach menu, aby znaleźć zestaw produktów, których szukają. W ramach rozszerzenia tego laboratorium możesz rozbudować system katalogu produktów, aby użytkownik mógł wyświetlać napoje oddzielnie od jedzenia w menu, a nawet określać opcje wegetariańskie.

4. koszyk na zakupy,

W tej części ćwiczenia w Codelabs rozbudujemy funkcję koszyka na zakupy, korzystając z informacji z poprzedniej sekcji, która umożliwia przeglądanie dostępnych produktów.

Najważniejsze funkcje koszyka na zakupy umożliwiają użytkownikom dodawanie i usuwanie produktów, śledzenie liczby poszczególnych produktów w koszyku na zakupy oraz przeglądanie produktów w koszyku na zakupy.

Śledzenie stanu koszyka na zakupy oznacza, że musimy przechowywać dane w aplikacji internetowej. Aby uprościć eksperymentowanie i wdrażanie, do przechowywania danych będziemy używać usługi Google Datastore na platformie Google Cloud Platform. Identyfikator rozmowy pozostaje stały między użytkownikiem a firmą, więc możemy go używać do przypisywania użytkowników do produktów w koszyku na zakupy.

Zacznijmy od połączenia z Google Datastore i utrwalenia identyfikatora rozmowy, gdy go zobaczymy.

Łączenie z Datastore

Za każdym razem, gdy użytkownik wejdzie w interakcję z koszykiem na zakupy, np. doda lub usunie produkt, połączymy się z Google Datastore. Więcej informacji o korzystaniu z tej biblioteki klienta do interakcji z Google Datastore znajdziesz w oficjalnej dokumentacji.

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

Skopiuj tę funkcję do pliku views.py. W następnej sekcji omówimy to bardziej szczegółowo.

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 dodawać produkty do koszyka.

Dodawanie produktów do koszyka

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

Gdy polecenie koszyka i element koszyka zostaną przeanalizowane z wiadomości, możemy napisać instrukcje warunkowe, aby dodać element. Warto tu rozważyć przypadki brzegowe, w których Datastore nigdy nie widział identyfikatora rozmowy lub koszyk na zakupy po raz pierwszy otrzymuje ten produkt. Poniżej znajdziesz rozszerzenie funkcjonalności update_shopping_cart zdefiniowanej powyżej. Ta zmiana dodaje produkt do koszyka na zakupy, który jest przechowywany w Google Datastore.

Ten fragment kodu jest rozszerzeniem poprzedniej funkcji dodanej do pliku views.py. Możesz dodać różnicę lub skopiować fragment kodu i zastąpić nim 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, aby obsługiwać sytuację, w której cart_cmd zawiera ciąg znaków „del-item” zdefiniowany w CMD_DEL_ITEM.

Podsumowanie

Dodaj kod w funkcji route_message, aby w przypadku otrzymania wiadomości z prośbą o dodanie produktu do koszyka wywoływana była funkcja update_shopping_cart. Musisz też zdefiniować stałą do dodawania elementów zgodnie z konwencją, której używamy w tym samouczku.

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 na zakupy. Jeśli wdrożysz zmiany w Google App Engine, powinny one być widoczne w panelu Google Datastore w konsoli GCP. Na zrzucie ekranu poniżej przedstawiającym konsolę Google Datastore widać pojedynczy obiekt o nazwie odpowiadającej identyfikatorowi rozmowy, a także relacje z produktami w magazynie i ich liczbą w koszyku na zakupy.

619dc18a8136ea69.png

W następnej sekcji utworzymy sposób wyświetlania produktów w koszyku na zakupy. Mechanizm sprawdzania koszyka na zakupy powinien wyświetlać wszystkie produkty w koszyku, ich liczbę oraz opcję usunięcia produktu z koszyka.

Sprawdzanie produktów w koszyku

Wyświetlenie listy produktów w koszyku na zakupy to jedyny sposób, abyśmy mogli poznać stan koszyka na zakupy i wiedzieć, które produkty możemy usunąć.

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

Zanim przejdziemy do pisania funkcji, warto pamiętać o jednej rzeczy: jeśli w koszyku na zakupy jest tylko jeden rodzaj produktu, nie możemy go wyświetlić w formie karuzeli. Karuzela kart rozszerzonych musi zawierać co najmniej 2 karty. Z drugiej strony, jeśli w koszyku nie ma żadnych produktów, chcemy wyświetlić prosty komunikat informujący, że koszyk jest pusty.

Zdefiniujmy więc funkcję o nazwie send_shopping_cart. Ta funkcja łączy się z Google Datastore i wysyła żądanie dotyczące encji ShoppingCart na podstawie identyfikatora rozmowy. Gdy to zrobimy, wywołamy funkcję get_inventory_data i użyjemy karuzeli kart informacyjnych, aby podać stan koszyka na zakupy. Musimy też uzyskać identyfikator produktu na podstawie jego nazwy. Możemy zadeklarować funkcję, która będzie wyszukiwać tę wartość w Google Datastore. Podczas tworzenia karuzeli możemy powiązać sugerowane odpowiedzi z usuwaniem lub dodawaniem elementów według identyfikatora produktu. Poniższy fragment kodu wykonuje wszystkie te operacje. Skopiuj kod w dowolne miejsce w pliku 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)

...

Upewnij się, że na początku pliku views.py zdefiniowano CMD_SHOW_CART, i wywołaj send_shopping_cart, jeśli użytkownik wyśle wiadomość zawierającą „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ą wprowadzoną w funkcji send_shopping_cart, gdy wpiszesz „show-cart”, otrzymasz komunikat o tym, że koszyk jest pusty, kartę z jednym produktem w koszyku lub karuzelę kart z wieloma produktami. Dodatkowo mamy 3 sugerowane odpowiedzi: „Zobacz całkowitą cenę”, „Opróżnij koszyk” i „Zobacz menu”.

Wdróż powyższe zmiany w kodzie, aby sprawdzić, czy koszyk na zakupy śledzi dodawane produkty i czy możesz przejrzeć koszyk na platformie do prowadzenia rozmów, jak pokazano na zrzutach ekranu powyżej. Możesz wdrożyć zmiany za pomocą tego polecenia uruchomionego w katalogu step-2, w którym dodajesz zmiany.

$ gcloud app deploy

Funkcję „Zobacz cenę całkowitą” utworzymy w następnej sekcji po utworzeniu funkcji usuwania produktu z koszyka. Funkcja get_cart_price będzie działać podobnie do funkcji „Zobacz koszyk na zakupy” w tym sensie, że będzie porównywać dane z różnych kanałów w Datastore z plikiem inventory.json, aby uzyskać łączną cenę koszyka na zakupy. Przyda się to w następnej części laboratorium, w której zintegrujemy płatności.

Usuwanie produktów z koszyka

Na koniec możemy uzupełnić działanie koszyka na zakupy o funkcję jego usuwania. Zastąp istniejącą funkcję update_shopping_cart tym fragmentem kodu.

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 jego działanie zostało zarejestrowane i że jego prośba została przetworzona. Nie tylko pomaga to zarządzać oczekiwaniami, ale też podtrzymuje rozmowę.

Rozszerzmy funkcję update_shopping_cart, aby wysyłała do identyfikatora rozmowy wiadomość z informacją o dodaniu lub usunięciu produktu oraz sugestie dotyczące sprawdzenia koszyka na zakupy lub ponownego wyświetlenia 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 wystarczyć. W pełni funkcjonalny koszyk na zakupy, który umożliwia użytkownikowi dodawanie i usuwanie produktów oraz przeglądanie ich zawartości.

Jeśli chcesz zobaczyć funkcję koszyka na zakupy w rozmowie Business Messages, wdróż aplikację, aby wejść w interakcję z agentem. W tym celu uruchom to polecenie w katalogu step-2.

$ gcloud app deploy

5. Przygotowywanie do płatności

W ramach przygotowań do integracji z firmą obsługującą płatności w kolejnej części serii musimy mieć możliwość pobierania ceny koszyka na zakupy. Utwórzmy funkcję, która pobiera cenę, porównując dane koszyka na zakupy w Google Datastore, pobierając cenę każdego produktu z asortymentu i mnożąc ją przez ilość każdego produktu w koszyku na zakupy.

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

...

Na koniec możemy użyć tej funkcji 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 wywołać 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 pokazujących, co można osiągnąć dzięki powyższej logice:

8feacf94ed0ac6c4.png

Gdy w kolejnej części tego laboratorium kodowania będziemy gotowi do integracji z firmą obsługującą płatności, wywołamy funkcję get_cart_price, aby przekazać dane do tej firmy i rozpocząć proces płatności.

Funkcję koszyka na zakupy możesz wypróbować w rozmowie w Business Messages, wdrażając aplikację i wchodząc w interakcję z agentem.

$ gcloud app deploy

6. Gratulacje

Gratulujemy! Udało Ci się utworzyć koszyk na zakupy w Business Messages.

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

W dalszej części zintegrujemy się z zewnętrzną firmą obsługującą płatności, aby umożliwić użytkownikom dokonywanie transakcji płatniczych związanych z Twoją marką.

Czym cechuje się dobry koszyk na zakupy?

Dobre wrażenia z korzystania z koszyka na zakupy w rozmowie nie różnią się od tych w aplikacji mobilnej czy 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 laboratorium. W przeciwieństwie do koszyka w prawdziwym świecie w koszyku online możesz w każdej chwili sprawdzić cenę wszystkich produktów, które się w nim znajdują, w miarę jak dodajesz lub usuwasz produkty. Takie funkcje o wysokiej wartości wyróżnią Twoją platformę handlu konwersacyjnego.

Co dalej?

Gdy będziesz gotowy(-a), zapoznaj się z tymi tematami, aby dowiedzieć się więcej o bardziej złożonych interakcjach, które możesz osiągnąć w Wiadomościach Biznesowych:

Dokumentacja