線上購買店內取貨:Bonjour Meal - 第 2 部分 - 建立購物車

Buy Online Pickup In Store:Bonjour 餐點 - 第 2 部分 - 建構購物車

程式碼研究室簡介

subject上次更新時間:11月 14, 2024
account_circle作者:Adam Chan

1. 簡介

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

上次更新時間:2020 年 10 月 30 日

在 Business Messages 中建立購物車!

這是一系列程式碼研究室的第二個課程,旨在建構線上購物店內取貨的使用者歷程。在許多電子商務旅程中,購物車是將使用者轉換為付費顧客的關鍵。購物車也是一種瞭解客戶的好方法,可根據他們可能感興趣的其他商品提供建議。在本程式碼研究室中,我們將專注於建構購物車體驗,並將應用程式部署至 Google App Engine。

優質的購物車具備哪些條件?

購物車是成功的線上購物體驗的關鍵。事實證明,商家訊息不僅能協助與潛在客戶進行產品問答,還能協助完成整個購物體驗,讓對方在對話中完成付款。

9d17537b980d0e62.png

除了提供優質的購物車功能,優質的購物體驗還能讓使用者依類別瀏覽商品,並讓商家推薦買家可能感興趣的其他產品。將更多商品加入購物車後,使用者可以查看整個購物車,並在結帳前移除或新增商品。

建構項目

在本節程式碼研究室系列課程中,您將為虛構公司 Bonjour Meal 擴充在第 1 部分中建構的數位服務代理程式,讓使用者能夠瀏覽商品目錄,並將商品加入購物車。

在本程式碼研究室中,您的應用程式將執行以下操作:

  • 在 Business Messages 中顯示問題目錄
  • 建議使用者可能感興趣的項目
  • 查看購物車內容並建立總價摘要

ab2fb6a4ed33a129.png

課程內容

  • 如何在 Google Cloud Platform 的 App Engine 上部署網頁應用程式
  • 如何使用持久性儲存機制來儲存購物車的狀態

本程式碼研究室著重於擴充 本程式碼研究室系列第 1 部分中的數位操作代理程式。

軟硬體需求

2. 開始設定

本程式碼研究室假設您已建立第一個代理程式,並完成程式碼研究室的第 1 部分。因此,我們不會介紹啟用 Business Messages 和 Business Communications API、建立服務帳戶金鑰、部署應用程式,或是在 Business Communications 控制台中設定 webhook 的基本操作。因此,我們會複製範例應用程式,確保您的應用程式與我們建構的應用程式一致,並在 Google Cloud Platform 上啟用 Datastore 的 API,以便持續儲存購物車相關資料。

從 GitHub 複製應用程式

在終端機中,使用下列指令將 Django Echo Bot 範例複製到專案的工作目錄:

$ 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

啟用 Google Datastore API

在本程式碼研究室的第 1 部分,您已啟用 Business Messages API、Business communications API 和 Cloud Build API。

在本程式碼研究室中,我們會使用 Google Datastore,因此也需要啟用這個 API:

  1. 在 Google Cloud 控制台中開啟 Google Datastore API
  2. 確認您使用的是正確的 GCP 專案。
  3. 按一下「啟用」

部署範例應用程式

在終端機中,前往範例的 step-2 目錄。

在終端機中執行下列指令,即可部署範例:

$ gcloud config set project PROJECT_ID*
$
gcloud app deploy
  • PROJECT_ID 是您用於註冊 API 的專案 ID。

請留意最後一行指令輸出內容中部署應用程式的網址:

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

您剛部署的程式碼包含一個含有 Webhook 的網頁應用程式,可用於接收 Business Messages 傳送的訊息。其中包含我們在程式碼研究室第 1 部分中完成的所有內容。如果您尚未設定,請設定 webhook

應用程式會回覆一些簡單的查詢,例如使用者詢問 Bonjour Meal 的營業時間。您應該透過行動裝置上的測試網址進行測試,這些網址可從 Business Communications 控制台的服務專員資訊中擷取。測試網址會在行動裝置上啟動 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 應用程式讀取這個檔案!

從我們的商品目錄讀取

廣告空間是位於 ./resources 目錄中的名為「inventory.json」的靜態檔案。我們需要在 views.py 中加入一些 Python 邏輯,讀取 JSON 檔案的內容,然後顯示在對話中。我們來建立一個函式,讀取 JSON 檔案中的資料,並傳回可用的產品清單。

這個函式定義可放在 views.py 的任何位置。

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

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

這應該會提供我們讀取商品目錄資料所需的資訊。接下來,我們需要在對話中顯示這項產品資訊。

顯示產品目錄

為了讓本程式碼研究室簡單明瞭,我們提供一般產品目錄,透過單一互動式資訊卡輪轉介面,將所有商品目錄項目顯示在 Business Messages 對話中。

如要查看產品目錄,我們會建立建議回覆,其中包含文字「Show Menu」和 postbackData「show-product-catalog」。當使用者輕觸建議回覆,且您的網頁應用程式收到 postback 資料時,我們就會傳送互動式資訊卡輪轉介面。請在 views.py 頂端為這個建議回覆新增常數。

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

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

從這裡開始,我們會剖析訊息並將其導向至新函式,該函式會傳送含有產品目錄的互動式資訊卡輪轉介面。首先,擴充 route_message 函式,在使用者輕觸建議回覆時呼叫函式「send_product_catalog」,接著我們會定義該函式。

在以下程式碼片段中,請在 route_message 函式中的 if 陳述式中新增額外條件,檢查 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_catalogsend_product_catalog 會呼叫 get_menu_carousel,,後者會根據先前讀取的廣告空間檔案產生複合式資訊卡的輪轉介面。

函式定義可放在 views.py 的任何位置。請注意,下列程式碼片段會使用兩個新的常數,這些常數應新增至檔案頂端。

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

...

CMD_ADD_ITEM
= 'add-item'
CMD_SHOW_CART
= 'show-cart'

...

def get_menu_carousel():
   
"""Creates a sample carousel rich card.

    Returns:
       A :obj: A BusinessMessagesCarouselCard object with three cards.
    """


    inventory
= get_inventory_data()

    card_content
= []

   
for item in inventory['food']:
        card_content
.append(BusinessMessagesCardContent(
            title
=item['name'],
            description
=item['price'],
            suggestions
=[
               
BusinessMessagesSuggestion(
                    reply
=BusinessMessagesSuggestedReply(
                        text
='Add item',
                        postbackData
='{'+f'"action":"{CMD_ADD_ITEM}","item_name":"{item["id"]}"'+'}'))

               
],
            media
=BusinessMessagesMedia(
                height
=BusinessMessagesMedia.HeightValueValuesEnum.MEDIUM,
                contentInfo
=BusinessMessagesContentInfo(
                    fileUrl
=item['image_url'],
                    forceRefresh
=False))))

   
return BusinessMessagesCarouselCard(
        cardContents
=card_content,
        cardWidth
=BusinessMessagesCarouselCard.CardWidthValueValuesEnum.MEDIUM)

def send_product_catalog(conversation_id):
   
"""Sends the product catalog to the conversation_id.

    Args:
        conversation_id (str): The unique id for this user and agent.
    """

    rich_card
= BusinessMessagesRichCard(carouselCard=get_menu_carousel())

    fallback_text
= ''

   
# Construct a fallback text for devices that do not support carousels
   
for card_content in rich_card.carouselCard.cardContents:
        fallback_text
+= (card_content.title + '\n\n' + card_content.description
                         
+ '\n\n' + card_content.media.contentInfo.fileUrl
                         
+ '\n---------------------------------------------\n\n')

    message_obj
= BusinessMessagesMessage(
        messageId
=str(uuid.uuid4().int),
        representative
=BOT_REPRESENTATIVE,
        richCard
=rich_card,
        fallback
=fallback_text,
        suggestions
=[
       
BusinessMessagesSuggestion(
            reply
=BusinessMessagesSuggestedReply(
                text
='See my cart',
                postbackData
=CMD_SHOW_CART)
           
),
       
BusinessMessagesSuggestion(
            reply
=BusinessMessagesSuggestedReply(
                text
='See the menu',
                postbackData
=CMD_SHOW_PRODUCT_CATALOG)
           
),
       
]
       
)

    send_message
(message_obj, conversation_id)
...

如果您檢查輪轉介面項目的建立作業,我們也會建立 BusinessMessagesSuggestion 類別的例項。每個建議都代表使用者在輪轉介面中選取的產品。當使用者輕觸建議回覆時,Business Messages 會將 postbackData 傳送至 webhook,其中包含描述商品的 JSON 和使用者要採取的動作 (新增或從購物車中移除)。在下一節中,我們會剖析類似以下的訊息,實際將商品加入購物車。

完成這些變更後,我們就來將網路應用程式部署至 Google App Engine,並試用看看吧!

$ gcloud app deploy

在行動裝置上載入對話介面後,請傳送「show-product-catalog」訊息,你應該會看到輪轉介面中的產品,如下所示。

4639da46bcc5230c.png

輕觸「Add item」後,系統不會執行任何操作,但會回傳建議回覆的回傳資料。在下一節中,我們將使用產品目錄,並利用該目錄建構購物車,以便新增商品。

您剛建立的產品目錄可透過多種方式擴充。你可能會提供不同的飲料選項或素食選項。使用輪轉介面或建議方塊,可讓使用者透過選單選項深入瞭解,找到所需的產品。您可以擴充本程式碼研究室的內容,嘗試擴充產品目錄系統,讓使用者在選單中分別查看飲料和食物,甚至指定素食選項。

4. 購物車

在本節程式碼研究室中,我們將根據先前建立的購物車功能,建構可瀏覽可用產品的功能。

在眾多購物車體驗中,主要功能是讓使用者將商品加入購物車、從購物車中移除商品、追蹤購物車中的每項商品數量,以及查看購物車中的商品。

追蹤購物車狀態代表我們需要在網頁應用程式中儲存資料。為了簡化實驗和部署作業,我們會使用 Google Cloud Platform 中的 Google Datastore 來儲存資料。使用者與商家之間的對話 ID 會保持不變,因此我們可以利用這項資訊將使用者與購物車商品建立關聯。

首先,我們要連結 Google Datastore,並在看到對話 ID 時保留該 ID。

連結至 Datastore

每當使用者執行與購物車相關的互動 (例如新增或刪除商品),我們就會連線至 Google Datastore。如要進一步瞭解如何使用這個用戶端程式庫與 Google Datastore 互動,請參閱官方說明文件

以下程式碼片段定義了用於更新購物車的函式。這個函式會接受以下輸入:conversation_idmessagemessage 包含 JSON,其中描述使用者要採取的動作,這項資訊已內建在用於顯示產品目錄的輪轉介面中。這個函式會建立 Google Datastore 用戶端,並立即擷取 ShoppingCart 實體,其中的鍵是對話 ID。

將下列函式複製到 views.py 檔案中。我們會在後續章節中進一步說明。

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

from google.oauth2 import service_account
from google.cloud import datastore

def update_shopping_cart(conversation_id, message):
       
credentials = service_account.Credentials.from_service_account_file(
       
SERVICE_ACCOUNT_LOCATION)

       
client = datastore.Client(credentials=credentials)
       
key = client.key('ShoppingCart', conversation_id)
       
entity = datastore.Entity(key=key)
       
result = client.get(key)
       
       
# TODO: Add logic to add and remove items from cart
       
       
entity.update(result)
       
client.put(entity)

讓我們擴充這個函式,將商品加入購物車。

將商品加入購物車

當使用者輕觸產品輪轉介面中的「Add item」建議動作時,postbackData 會包含 JSON,說明使用者要採取的動作。JSON 字典有兩個鍵:「action」和「item_name」,這個 JSON 字典會傳送至 webhook。「item_name」欄位是 inventory.json 中與商品相關聯的專屬 ID。

取得從訊息解析的購物車指令和購物車商品後,我們就可以編寫條件式陳述式來新增商品。這裡要考慮的極端情況包括:Datastore 從未看過對話 ID,或是購物車第一次收到此項目。以下是上述定義的 update_shopping_cart 功能擴充功能。這項變更會將商品加入由 Google Datastore 保存的購物車。

以下程式碼片段是先前新增至 views.py 的函式擴充功能。您可以自由新增差異,或是複製程式碼片段,然後取代現有的 update_shopping_cart 函式版本。

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

def update_shopping_cart(conversation_id, message):
    credentials = service_account.Credentials.from_service_account_file(
      SERVICE_ACCOUNT_LOCATION)
    inventory = get_inventory_data()

    cart_request = json.loads(message)
    cart_cmd = cart_request["action"]
    cart_item = cart_request["item_name"]

    item_name = inventory['food'][int(cart_item)]['name']

    client = datastore.Client(credentials=credentials)
    key = client.key('ShoppingCart', conversation_id)
    entity = datastore.Entity(key=key)
    result = client.get(key)

    if result is None:
        if cart_cmd == CMD_ADD_ITEM:
            entity.update({
                item_name: 1
            })

    else:
        if cart_cmd == CMD_ADD_ITEM:
            if result.get(item_name) is None:
                result[item_name] = 1
            else:
                result[item_name] = result[item_name] + 1

        entity.update(result)
    client.put(entity)

這個函式日後會擴充,以支援 cart_cmd 包含 CMD_DEL_ITEM 中定義的字串「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,應該會在 GCP 主控台的 Google Datastore 資訊主頁中看到購物車變更。請參閱下方 Google Datastore 控制台的螢幕截圖,其中有一個以對話 ID 命名的實體,後面接著是與商品目錄項目和購物車中這些項目的數量相關的一些關係。

619dc18a8136ea69.png

在下一節中,我們將建立一種方法,列出購物車中的商品。購物車檢查機制應顯示購物車中的所有商品、這些商品的數量,以及從購物車中移除商品的選項。

查看購物車中的商品

列出購物車中的商品,是我們瞭解購物車狀態並判斷可移除哪些商品的唯一方法。

我們先傳送友善的訊息,例如「這是你的購物車:」,接著傳送另一則訊息,其中包含複合式資訊卡輪轉介面,以及「移除一個」或「新增一個」的相關建議回覆。除了列出購物車中儲存的商品數量,資訊卡輪轉介面也應列出商品數量。

在我們實際編寫函式之前,請注意以下事項:如果購物車中只有一種商品類型,我們就無法將其轉譯為輪轉介面。資訊卡輪轉介面必須包含至少兩張資訊卡。反之,如果購物車中沒有商品,我們會顯示簡單的訊息,說明購物車為空。

考量到這一點,我們定義名為 send_shopping_cart 的函式。這個函式會連線至 Google Datastore,並根據對話 ID 要求 ShoppingCart 實體。取得這些資訊後,我們會呼叫 get_inventory_data 函式,並使用多元資訊卡輪轉介面回報購物車的狀態。我們也需要根據名稱取得產品 ID,並宣告函式來查看 Google Datastore,以便判斷該值。製作輪轉介面時,我們可以根據產品 ID 關聯建議回覆,藉此刪除或新增商品。下列程式碼片段會執行所有這些作業。將程式碼複製到 views.py 中的任何位置。

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

...
def get_id_by_product_name(product_name):
  inventory = get_inventory_data()
  for item in inventory['food']:
    if item['name'] == product_name:
      return int(item['id'])
  return False


def send_shopping_cart(conversation_id):
  credentials = service_account.Credentials.from_service_account_file(
      SERVICE_ACCOUNT_LOCATION)

  # Retrieve the inventory data
  inventory = get_inventory_data()

  # Pull the data from Google Datastore
  client = datastore.Client(credentials=credentials)
  key = client.key('ShoppingCart', conversation_id)
  result = client.get(key)

  shopping_cart_suggestions = [
      BusinessMessagesSuggestion(
          reply=BusinessMessagesSuggestedReply(
              text='See total price', postbackData='show-cart-price')),
      BusinessMessagesSuggestion(
          reply=BusinessMessagesSuggestedReply(
              text='Empty the cart', postbackData='empty-cart')),
      BusinessMessagesSuggestion(
          reply=BusinessMessagesSuggestedReply(
              text='See the menu', postbackData=CMD_SHOW_PRODUCT_CATALOG)),
  ]

  if result is None or len(result.items()) == 0:
    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        text='There are no items in your shopping cart.',
        suggestions=shopping_cart_suggestions)

    send_message(message_obj, conversation_id)
  elif len(result.items()) == 1:

    for product_name, quantity in result.items():
      product_id = get_id_by_product_name(product_name)

      fallback_text = ('You have one type of item in the shopping cart')

      rich_card = BusinessMessagesRichCard(
          standaloneCard=BusinessMessagesStandaloneCard(
              cardContent=BusinessMessagesCardContent(
                  title=product_name,
                  description=f'{quantity} in cart.',
                  suggestions=[
                      BusinessMessagesSuggestion(
                          reply=BusinessMessagesSuggestedReply(
                              text='Remove one',
                              postbackData='{'+f'"action":"{CMD_DEL_ITEM}","item_name":"{product_id}"'+'}'))
                  ],
                  media=BusinessMessagesMedia(
                      height=BusinessMessagesMedia.HeightValueValuesEnum.MEDIUM,
                      contentInfo=BusinessMessagesContentInfo(
                          fileUrl=inventory['food'][product_id]
                          ['image_url'],
                          forceRefresh=False)))))

      message_obj = BusinessMessagesMessage(
          messageId=str(uuid.uuid4().int),
          representative=BOT_REPRESENTATIVE,
          richCard=rich_card,
          suggestions=shopping_cart_suggestions,
          fallback=fallback_text)

      send_message(message_obj, conversation_id)
  else:
    cart_carousel_items = []

    # Iterate through the cart and generate a carousel of items
    for product_name, quantity in result.items():
      product_id = get_id_by_product_name(product_name)

      cart_carousel_items.append(
          BusinessMessagesCardContent(
              title=product_name,
              description=f'{quantity} in cart.',
              suggestions=[
                  BusinessMessagesSuggestion(
                      reply=BusinessMessagesSuggestedReply(
                          text='Remove one',
                          postbackData='{'+f'"action":"{CMD_DEL_ITEM}","item_name":"{product_id}"'+'}'))
              ],
              media=BusinessMessagesMedia(
                  height=BusinessMessagesMedia.HeightValueValuesEnum.MEDIUM,
                  contentInfo=BusinessMessagesContentInfo(
                      fileUrl=inventory['food'][product_id]
                      ['image_url'],
                      forceRefresh=False))))

    rich_card = BusinessMessagesRichCard(
        carouselCard=BusinessMessagesCarouselCard(
            cardContents=cart_carousel_items,
            cardWidth=BusinessMessagesCarouselCard.CardWidthValueValuesEnum
            .MEDIUM))

    fallback_text = ''

    # Construct a fallback text for devices that do not support carousels
    for card_content in rich_card.carouselCard.cardContents:
      fallback_text += (
          card_content.title + '\n\n' + card_content.description + '\n\n' +
          card_content.media.contentInfo.fileUrl +
          '\n---------------------------------------------\n\n')

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        richCard=rich_card,
        suggestions=shopping_cart_suggestions,
        fallback=fallback_text,
    )

    send_message(message_obj, conversation_id)

...

請確認您已在 views.py 頂端定義 CMD_SHOW_CART,並在使用者傳送含有「show-cart」的訊息時呼叫 send_shopping_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 函式,讓它傳送訊息至對話 ID,指出商品已新增或移除,並提供建議,讓使用者查看購物車或再次查看選單。

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 Datastore 中的購物車資料、從商品目錄中擷取每項商品的價格,然後將價格乘以購物車中每項商品的數量,擷取價格。

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

...
def get_cart_price(conversation_id):
   
# Pull the data from Google Datastore
    credentials
= service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_LOCATION
)
    client
= datastore.Client(credentials=credentials)
    key
= client.key('ShoppingCart', conversation_id)
    entity
= datastore.Entity(key=key)
    result
= client.get(key)

   
# Retrieve the inventory data
    inventory
= get_inventory_data()
   
   
# Start off with a total of 0 before adding up the total
    total_price
= 0

   
if len(result.items()) != 0:
     
for product_name, quantity in result.items():
        total_price
= total_price + float(
            inventory
['food'][get_id_by_product_name(product_name)]['price']) * int(quantity)

   
return total_price

...

最後,我們可以使用該函式,並傳送訊息給使用者。

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

...

def send_shopping_cart_total_price(conversation_id):
    cart_price = get_cart_price(conversation_id)

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        suggestions=[],
        text=f'Your cart\'s total price is ${cart_price}.')

    send_message(message_obj, conversation_id)
...

為了將所有內容整合在一起,我們來更新 route_message 函式和常數,以觸發上述邏輯。

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

...
CMD_GET_CART_PRICE
= 'show-cart-price'
...
def route_message(message, conversation_id):
   
'''
    Routes the message received from the user to create a response.

    Args:
        message (str): The message text received from the user.
        conversation_id (str): The unique id for this user and agent.
    '''

    normalized_message
= message.lower()

   
if normalized_message == CMD_RICH_CARD:
        send_rich_card
(conversation_id)
   
elif normalized_message == CMD_CAROUSEL_CARD:
        send_carousel
(conversation_id)
   
elif normalized_message == CMD_SUGGESTIONS:
        send_message_with_suggestions
(conversation_id)
   
elif normalized_message == CMD_BUSINESS_HOURS_INQUIRY:
        send_message_with_business_hours
(conversation_id)
   
elif normalized_message == CMD_ONLINE_SHOPPING_INQUIRY:
        send_online_shopping_info_message
(conversation_id)
   
elif normalized_message == CMD_SHOW_PRODUCT_CATEGORY:
        send_product_catalog
(conversation_id)
   
elif CMD_ADD_ITEM in normalized_message or CMD_DEL_ITEM in normalized_message:
        update_shopping_cart
(conversation_id, message)
   
elif normalized_message == CMD_SHOW_CART:
        send_shopping_cart
(conversation_id)
   
elif normalized_message == CMD_GET_CART_PRICE:
        send_shopping_cart_total_price
(conversation_id)
   
else:
        echo_message
(message, conversation_id)
...

以下是幾張螢幕截圖,說明上述邏輯可達成的效果:

8feacf94ed0ac6c4.png

在程式碼研究室的下一節中,我們將準備整合付款處理器,並呼叫 get_cart_price 函式,將資料傳遞至付款處理器,然後開始付款流程。

同樣地,您可以部署應用程式並與服務專員互動,在 Business Messages 對話中試用購物車功能。

$ gcloud app deploy

6. 恭喜

恭喜!您已成功在 Business Messages 中建立購物車體驗。

本程式碼研究室未介紹的功能是清空整個購物車。如有需要,請嘗試擴充應用程式,以便提供「清空購物車」功能。解決方案位於您複製的原始碼的步驟 3 中。

在後續章節中,我們會整合外部付款處理方,讓使用者能透過您的品牌完成付款交易。

優質的購物車具備哪些條件?

對話中的購物車體驗,與行動應用程式或實體商店的體驗相同。在這個程式碼研究室中,我們探索了一些功能,包括新增商品、移除商品,以及計算購物車的價格。與現實世界的購物車不同,您可以在任何時間點查看購物車中所有商品的價格,無論是新增或移除商品皆然。這類高價值功能可讓對話式商務體驗脫穎而出!

後續步驟

準備就緒後,請參閱下列部分主題,瞭解如何在商家訊息中進行更複雜的互動:

參考文件