Mua Trực tuyến đến lấy hàng tại cửa hàng: Bonjour Liêm - Phần 2 - Xây dựng một giỏ hàng

1. Giới thiệu

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Lần cập nhật gần đây nhất: ngày 30 tháng 10 năm 2020

Xây dựng giỏ hàng trên Business Messages!

Đây là lớp học lập trình thứ hai trong loạt lớp học lập trình nhằm mục đích xây dựng hành trình của người dùng Mua hàng trực tuyến và đến lấy tại cửa hàng. Trong nhiều hành trình thương mại điện tử, giỏ hàng là yếu tố then chốt giúp bạn chuyển đổi người dùng thành khách hàng trả phí. Giỏ hàng cũng là một cách để hiểu rõ hơn về khách hàng và đưa ra đề xuất về những mặt hàng khác mà họ có thể quan tâm. Trong lớp học lập trình này, chúng ta sẽ tập trung vào việc xây dựng trải nghiệm giỏ hàng và triển khai ứng dụng lên Google App Engine.

Điều gì tạo nên một giỏ hàng hiệu quả?

Giỏ hàng là yếu tố then chốt để mang đến trải nghiệm mua sắm trực tuyến thành công. Business Messages không chỉ giúp bạn dễ dàng trao đổi với khách hàng tiềm năng về sản phẩm, mà còn có thể hỗ trợ toàn bộ trải nghiệm mua sắm cho đến khi hoàn tất thanh toán trong cuộc trò chuyện.

9d17537b980d0e62.png

Ngoài giỏ hàng tốt, trải nghiệm mua sắm tốt còn cho phép người dùng duyệt xem các mặt hàng theo danh mục và cho phép doanh nghiệp đề xuất các sản phẩm khác mà người mua có thể quan tâm. Sau khi thêm nhiều mặt hàng vào giỏ hàng, người dùng có thể xem lại toàn bộ giỏ hàng và có thể xoá hoặc thêm mặt hàng trước khi thanh toán.

Sản phẩm bạn sẽ tạo ra

Trong phần này của loạt lớp học lập trình, bạn sẽ mở rộng tác nhân kỹ thuật số mà bạn đã tạo trong phần 1 cho công ty hư cấu Bonjour Meal, để người dùng có thể duyệt xem danh mục mặt hàng và thêm mặt hàng vào giỏ hàng.

Trong lớp học lập trình này, ứng dụng của bạn sẽ

  • Hiện danh mục câu hỏi trong Business Messages
  • Đề xuất những mặt hàng mà người dùng có thể quan tâm
  • Xem nội dung của giỏ hàng và tạo bản tóm tắt tổng giá

ab2fb6a4ed33a129.png

Kiến thức bạn sẽ học được

  • Cách triển khai một ứng dụng web trên App Engine trên Google Cloud Platform
  • Cách sử dụng cơ chế lưu trữ liên tục để lưu trạng thái của giỏ hàng

Lớp học lập trình này tập trung vào việc mở rộng tác nhân kỹ thuật số từ phần 1 của loạt lớp học lập trình này.

Bạn cần có

2. Thiết lập

Lớp học lập trình này giả định rằng bạn đã tạo tác nhân đầu tiên và hoàn thành phần 1 của lớp học lập trình. Do đó, chúng tôi sẽ không đề cập đến những kiến thức cơ bản về cách bật Business Messages và Business Communications API, tạo khoá tài khoản dịch vụ, triển khai ứng dụng hoặc thiết lập webhook trên Business Communications Console. Tuy nhiên, chúng tôi sẽ sao chép một ứng dụng mẫu để đảm bảo ứng dụng của bạn nhất quán với những gì chúng tôi đang xây dựng, đồng thời chúng tôi sẽ bật API cho Datastore trên Google Cloud Platform để có thể duy trì dữ liệu liên quan đến giỏ hàng.

Sao chép ứng dụng trên GitHub

Trong cửa sổ dòng lệnh, hãy sao chép Django Echo Bot Sample (Mẫu bot phản hồi Django) vào thư mục làm việc của dự án bằng lệnh sau:

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

Sao chép tệp thông tin đăng nhập JSON mà bạn đã tạo cho tài khoản dịch vụ vào thư mục tài nguyên của mẫu và đổi tên thông tin đăng nhập thành "bm-agent-service-account-credentials.json".

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

Bật Google Datastore API

Trong Phần 1 của lớp học lập trình này, bạn đã bật Business Messages API, Business Communications API và Cloud Build API.

Đối với lớp học lập trình này, vì chúng ta sẽ làm việc với Google Datastore, nên chúng ta cũng cần bật API này:

  1. Mở Google Datastore API trong Google Cloud Console.
  2. Đảm bảo rằng bạn đang làm việc với đúng dự án GCP.
  3. Nhấp vào Bật.

Triển khai ứng dụng mẫu

Trong một cửa sổ dòng lệnh, hãy chuyển đến thư mục step-2 của mẫu.

Chạy các lệnh sau trong một cửa sổ dòng lệnh để triển khai mẫu:

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • PROJECT_ID là mã dự án của dự án mà bạn đã dùng để đăng ký với các API.

Ghi lại URL của ứng dụng đã triển khai trong đầu ra của lệnh cuối cùng:

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

Mã bạn vừa triển khai có chứa một ứng dụng web có webhook để nhận tin nhắn từ Business Messages. Thư mục này chứa mọi thứ chúng ta đã làm trong phần 1 của lớp học lập trình. Nếu bạn chưa làm, vui lòng định cấu hình webhook.

Ứng dụng sẽ trả lời một số câu hỏi đơn giản, chẳng hạn như khi người dùng hỏi về giờ hoạt động của Bonjour Meal. Bạn nên kiểm thử tính năng này trên thiết bị di động thông qua các URL kiểm thử mà bạn có thể truy xuất từ phần Thông tin về nhân viên hỗ trợ trong Business Communications Console. Các URL kiểm thử sẽ khởi chạy trải nghiệm Business Messages trên thiết bị di động và bạn có thể bắt đầu tương tác với nhân viên hỗ trợ tại đó.

3. Danh mục sản phẩm

Hệ thống kho hàng

Trong hầu hết các trường hợp, bạn có thể tích hợp trực tiếp với khoảng không quảng cáo của một thương hiệu thông qua một API nội bộ. Trong các trường hợp khác, bạn có thể trích xuất dữ liệu từ một trang web hoặc xây dựng hệ thống theo dõi khoảng không quảng cáo của riêng mình. Chúng ta không tập trung vào việc xây dựng một hệ thống kho hàng; chúng ta sẽ sử dụng một tệp tĩnh đơn giản chứa hình ảnh và thông tin sản phẩm cho tác nhân của mình. Trong phần này, chúng ta sẽ lấy thông tin từ tệp tĩnh này, hiển thị thông tin đó trong cuộc trò chuyện và cho phép người dùng duyệt xem các mặt hàng có thể thêm vào giỏ hàng.

Tệp kho hàng tĩnh có dạng như sau:

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

Hãy để ứng dụng Python đọc tệp này!

Đọc từ kho hàng của chúng tôi

Khoảng không quảng cáo là một tệp tĩnh có tên là "inventory.json" nằm trong thư mục ./resources. Chúng ta cần thêm một số logic Python vào views.py để đọc nội dung của tệp JSON, sau đó hiển thị nội dung đó cho cuộc trò chuyện. Hãy tạo một hàm đọc dữ liệu từ tệp JSON và trả về danh sách các sản phẩm hiện có.

Bạn có thể đặt định nghĩa hàm này ở bất kỳ vị trí nào trong views.py.

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

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

Thao tác này sẽ cung cấp cho chúng ta những gì cần thiết để đọc dữ liệu từ kho hàng. Giờ đây, chúng ta cần một cách để hiển thị thông tin sản phẩm này trong cuộc trò chuyện.

Đưa danh mục sản phẩm lên

Để đơn giản hoá lớp học lập trình này, chúng tôi có một danh mục sản phẩm chung để hiển thị tất cả các mặt hàng trong kho cho cuộc trò chuyện trên Business Messages thông qua một băng chuyền thẻ đa dạng duy nhất.

Để xem danh mục sản phẩm, chúng ta sẽ tạo một câu trả lời đề xuất có văn bản "Show Menu" (Hiện trình đơn) và postbackData "show-product-catalog". Khi người dùng nhấn vào câu trả lời đề xuất và ứng dụng web của bạn nhận được dữ liệu postback, chúng ta sẽ gửi băng chuyền thẻ đa dạng. Hãy thêm một hằng số mới cho câu trả lời đề xuất này ở đầu views.py.

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

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

Từ đây, chúng ta sẽ phân tích cú pháp thông báo và định tuyến thông báo đó đến một hàm mới gửi một băng chuyền thẻ đa dạng nội dung chứa danh mục sản phẩm. Trước tiên, hãy mở rộng hàm route_message để gọi hàm "send_product_catalog" khi người dùng nhấn vào câu trả lời đề xuất, sau đó chúng ta sẽ xác định hàm này.

Trong đoạn mã sau, hãy thêm một điều kiện khác vào câu lệnh if trong hàm route_message để kiểm tra xem normalized_message có bằng hằng số mà chúng ta đã xác định trước đó hay không, 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)
...

Hãy nhớ hoàn tất quy trình và xác định send_product_catalog. send_product_catalog gọi get_menu_carousel, để tạo băng chuyền gồm các thẻ thông tin chi tiết từ tệp kho hàng mà chúng ta đã đọc trước đó.

Bạn có thể đặt định nghĩa hàm ở bất kỳ vị trí nào trong views.py. Xin lưu ý rằng đoạn mã sau đây sử dụng 2 hằng số mới mà bạn nên thêm vào đầu tệp.

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

Nếu xem xét việc tạo các mục trong băng chuyền, chúng ta cũng sẽ tạo một thực thể của lớp BusinessMessagesSuggestion. Mỗi đề xuất đại diện cho lựa chọn của người dùng về một sản phẩm trong băng chuyền. Khi người dùng nhấn vào câu trả lời được đề xuất, Business Messages sẽ gửi postbackData chứa JSON mô tả mặt hàng và hành động mà người dùng muốn thực hiện (thêm hoặc xoá khỏi giỏ hàng) đến webhook của bạn. Trong phần tiếp theo, chúng ta sẽ phân tích cú pháp các thông báo có dạng như thế này để có thể thực sự thêm mặt hàng vào giỏ hàng.

Sau khi thực hiện những thay đổi này, hãy triển khai ứng dụng web lên Google App Engine và dùng thử!

$ gcloud app deploy

Khi tải giao diện đàm thoại trên thiết bị di động, hãy gửi thông báo "show-product-catalog" (hiện danh mục sản phẩm) và bạn sẽ thấy một băng chuyền sản phẩm có dạng như sau.

4639da46bcc5230c.png

Nếu bạn nhấn vào Thêm mặt hàng, thì thực tế sẽ không có gì xảy ra, ngoại trừ việc tác nhân lặp lại dữ liệu postback từ câu trả lời đề xuất. Trong phần tiếp theo, chúng ta sẽ sử dụng danh mục sản phẩm để tạo giỏ hàng nơi mặt hàng sẽ được thêm vào.

Bạn có thể mở rộng danh mục sản phẩm mà bạn vừa tạo theo nhiều cách. Bạn có thể có nhiều lựa chọn về đồ uống hoặc đồ ăn chay. Sử dụng băng chuyền hoặc các khối đề xuất là một cách hiệu quả để cho phép người dùng truy sâu vào các lựa chọn trong trình đơn để tìm thấy một nhóm sản phẩm mà họ đang tìm kiếm. Để mở rộng lớp học lập trình này, hãy thử mở rộng hệ thống danh mục sản phẩm để người dùng có thể xem đồ uống riêng biệt với thực phẩm trong thực đơn, hoặc thậm chí có thể chỉ định các lựa chọn ăn chay.

4. Giỏ hàng

Trong phần này của lớp học lập trình, chúng ta sẽ xây dựng chức năng giỏ hàng dựa trên phần trước đó, cho phép chúng ta duyệt xem các sản phẩm hiện có.

Trong số nhiều thứ, trải nghiệm giỏ hàng chính cho phép người dùng thêm mặt hàng vào giỏ hàng, xoá mặt hàng khỏi giỏ hàng, theo dõi số lượng của từng mặt hàng trong giỏ hàng và xem xét các mặt hàng trong giỏ hàng.

Việc theo dõi trạng thái của giỏ hàng có nghĩa là chúng ta cần duy trì dữ liệu trên ứng dụng web. Để đơn giản hoá việc thử nghiệm và triển khai, chúng ta sẽ sử dụng Google Datastore trong Google Cloud Platform để duy trì dữ liệu. Mã cuộc trò chuyện vẫn giữ nguyên giữa người dùng và doanh nghiệp, vì vậy, chúng ta có thể dùng mã này để liên kết người dùng với các mặt hàng trong giỏ hàng.

Hãy bắt đầu bằng cách kết nối với Google Datastore và duy trì mã nhận dạng cuộc trò chuyện khi chúng ta thấy mã này.

Kết nối với Datastore

Chúng ta sẽ kết nối với Google Datastore bất cứ khi nào có hoạt động tương tác với giỏ hàng, chẳng hạn như khi người dùng thêm hoặc xoá một mặt hàng. Bạn có thể tìm hiểu thêm về cách sử dụng thư viện ứng dụng này để tương tác với Google Datastore trong tài liệu chính thức.

Đoạn mã sau đây xác định một hàm để cập nhật giỏ hàng. Hàm này nhận đầu vào sau: conversation_idmessage. message chứa JSON mô tả hành động mà người dùng muốn thực hiện, được tích hợp sẵn vào băng chuyền hiển thị danh mục sản phẩm của bạn. Hàm này tạo một ứng dụng Google Datastore và ngay lập tức tìm nạp một thực thể ShoppingCart, trong đó khoá là mã nhận dạng cuộc trò chuyện.

Sao chép hàm sau vào tệp views.py. Chúng ta sẽ tiếp tục tìm hiểu về vấn đề này trong phần tiếp theo.

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)

Hãy mở rộng hàm này để thêm một mặt hàng vào giỏ hàng.

Thêm mặt hàng vào giỏ hàng

Khi người dùng nhấn vào một hành động được đề xuất Thêm mặt hàng trong băng chuyền sản phẩm, postbackData sẽ chứa JSON mô tả hành động mà người dùng muốn thực hiện. Từ điển JSON có 2 khoá là "action" và "item_name". Từ điển JSON này sẽ được gửi đến webhook của bạn. Trường "item_name" là giá trị nhận dạng riêng biệt được liên kết với mặt hàng trong inventory.json.

Sau khi phân tích cú pháp lệnh giỏ hàng và mặt hàng trong giỏ hàng từ thông báo, chúng ta có thể viết các câu lệnh có điều kiện để thêm mặt hàng. Một số trường hợp đặc biệt cần cân nhắc ở đây là nếu Datastore chưa từng thấy mã nhận dạng cuộc trò chuyện hoặc nếu giỏ hàng nhận được mặt hàng này lần đầu tiên. Sau đây là phần mở rộng của chức năng update_shopping_cart được xác định ở trên. Thay đổi này sẽ thêm một mặt hàng vào giỏ hàng được Google Datastore duy trì.

Đoạn mã sau đây là phần mở rộng của hàm trước đó được thêm vào views.py. Bạn có thể thêm phần khác biệt hoặc sao chép đoạn mã và thay thế phiên bản hiện có của hàm 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)

Hàm này sẽ được mở rộng sau để hỗ trợ trường hợp cart_cmd chứa chuỗi "del-item" được xác định trong CMD_DEL_ITEM.

Kết hợp các yếu tố

Đảm bảo bạn thêm các thành phần cần thiết vào hàm route_message để nếu bạn nhận được thông báo thêm một mặt hàng vào giỏ hàng, hàm update_shopping_cart sẽ được gọi. Bạn cũng cần xác định một hằng số để thêm các mục bằng quy ước mà chúng ta sử dụng trong suốt lớp học lập trình.

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)

...

Hiện tại, chúng ta có thể thêm mặt hàng vào giỏ hàng. Nếu triển khai các thay đổi cho Google App Engine, bạn sẽ thấy các thay đổi đối với giỏ hàng được phản ánh trong trang tổng quan Google Datastore trong bảng điều khiển GCP. Xem ảnh chụp màn hình bên dưới về bảng điều khiển Google Datastore. Có một thực thể duy nhất được đặt tên theo mã nhận dạng cuộc trò chuyện, sau đó là một số mối quan hệ với các mặt hàng trong kho và số lượng mặt hàng đó trong giỏ hàng.

619dc18a8136ea69.png

Trong phần tiếp theo, chúng ta sẽ tạo một cách để liệt kê các mặt hàng trong giỏ hàng. Cơ chế xem xét giỏ hàng phải cho chúng ta thấy tất cả các mặt hàng trong giỏ hàng, số lượng của các mặt hàng đó và lựa chọn xoá một mặt hàng khỏi giỏ hàng.

Xem xét các mặt hàng trong giỏ hàng

Liệt kê các mặt hàng trong giỏ hàng là cách duy nhất để chúng ta hiểu được trạng thái của giỏ hàng và biết những mặt hàng mà chúng ta có thể xoá.

Trước tiên, hãy gửi một tin nhắn thân thiện như "Đây là giỏ hàng của bạn:", sau đó gửi một tin nhắn khác có băng chuyền thẻ thông tin chi tiết kèm theo các câu trả lời đề xuất liên quan cho "Xoá một" hoặc "Thêm một". Ngoài ra, băng chuyền thẻ đa dạng cũng phải liệt kê số lượng mặt hàng đã lưu trong giỏ hàng.

Một điều cần lưu ý trước khi chúng ta thực sự đi vào viết hàm: nếu chỉ có một loại mặt hàng trong giỏ hàng, chúng ta không thể hiển thị mặt hàng đó dưới dạng băng chuyền. Băng chuyền thẻ đa dạng phải có ít nhất 2 thẻ. Ngược lại, nếu không có mặt hàng nào trong giỏ hàng, chúng ta muốn hiển thị một thông báo đơn giản cho biết giỏ hàng đang trống.

Với ý nghĩ đó, hãy xác định một hàm có tên là send_shopping_cart. Hàm này kết nối với Google Datastore và yêu cầu một thực thể ShoppingCart dựa trên Conversation ID. Sau khi có được thông tin đó, chúng ta sẽ gọi hàm get_inventory_data và dùng một băng chuyền thẻ thông tin chi tiết để báo cáo trạng thái của giỏ hàng. Chúng ta cũng cần lấy mã nhận dạng của một sản phẩm theo tên và có thể khai báo một hàm để xem xét Google Datastore nhằm xác định giá trị đó. Khi băng chuyền đang được tạo, chúng ta có thể liên kết các câu trả lời đề xuất để xoá hoặc thêm các mục theo mã sản phẩm. Đoạn mã dưới đây thực hiện tất cả các thao tác này. Sao chép mã vào bất kỳ vị trí nào trong 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)

...

Đảm bảo bạn đã xác định CMD_SHOW_CART ở đầu views.py và gọi send_shopping_cart nếu người dùng gửi một thông báo có chứa "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

Dựa trên logic mà chúng ta đã giới thiệu trong hàm send_shopping_cart, khi bạn nhập "show-cart", chúng ta sẽ nhận được một thông báo cho biết không có gì trong giỏ hàng, một thẻ đa dạng thông tin cho biết một mặt hàng trong giỏ hàng hoặc một băng chuyền gồm các thẻ cho biết nhiều mặt hàng. Ngoài ra, chúng tôi có 3 câu trả lời gợi ý: "Xem tổng giá", "Xoá giỏ hàng" và "Xem thực đơn".

Hãy thử triển khai các thay đổi về mã ở trên để kiểm tra xem giỏ hàng của bạn có đang theo dõi các mặt hàng mà bạn thêm hay không và bạn có thể xem lại giỏ hàng trên giao diện cuộc trò chuyện như trong ảnh chụp màn hình ở trên hay không. Bạn có thể triển khai các thay đổi bằng lệnh này từ thư mục step-2 nơi bạn đang thêm các thay đổi.

$ gcloud app deploy

Chúng ta sẽ tạo tính năng "Xem tổng giá" trong phần tiếp theo sau khi tạo chức năng xoá một mặt hàng khỏi giỏ hàng. Hàm get_cart_price sẽ hoạt động tương tự như tính năng "Xem giỏ hàng" theo nghĩa là hàm này sẽ tham chiếu chéo dữ liệu trong Datastore với tệp inventory.json để tạo ra tổng giá cho giỏ hàng. Điều này sẽ hữu ích cho phần tiếp theo của lớp học lập trình, nơi chúng ta tích hợp với các khoản thanh toán.

Xoá mặt hàng khỏi giỏ hàng

Cuối cùng, chúng ta có thể hoàn tất hành vi giỏ hàng bằng cách giới thiệu chức năng xoá giỏ hàng. Thay thế hàm update_shopping_cart hiện có bằng đoạn mã sau.

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)

Gửi tin nhắn xác nhận

Khi người dùng thêm một mặt hàng vào giỏ hàng, bạn nên gửi một thông báo xác nhận để xác nhận hành động của họ và cho biết rằng bạn đã xử lý yêu cầu của họ. Điều này không chỉ giúp bạn đặt ra kỳ vọng mà còn giúp cuộc trò chuyện tiếp tục diễn ra.

Hãy mở rộng hàm update_shopping_cart để hàm này gửi một thông báo đến mã nhận dạng cuộc trò chuyện, cho biết rằng mặt hàng đã được thêm hoặc xoá và đưa ra đề xuất xem lại giỏ hàng hoặc xem lại thực đơn.

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

Vậy là xong! Trải nghiệm giỏ hàng đầy đủ tính năng, cho phép người dùng thêm, xoá và xem lại các mặt hàng trong giỏ hàng.

Tại thời điểm này, nếu bạn muốn xem chức năng giỏ hàng trong cuộc trò chuyện trên Business Messages, hãy triển khai ứng dụng để tương tác với tác nhân của bạn. Bạn có thể thực hiện việc này bằng cách chạy lệnh sau trong thư mục step-2.

$ gcloud app deploy

5. Chuẩn bị cho việc thanh toán

Để chuẩn bị tích hợp với một công ty xử lý thanh toán trong phần tiếp theo của loạt bài này, chúng ta cần một cách để lấy giá của giỏ hàng. Hãy tạo một hàm truy xuất giá cho chúng ta bằng cách tham chiếu chéo dữ liệu giỏ hàng trong Google Datastore, truy xuất giá của từng mặt hàng trong kho hàng và nhân giá với số lượng của từng mặt hàng trong giỏ hàng.

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

...

Và cuối cùng, chúng ta có thể sử dụng hàm đó và gửi tin nhắn cho người dùng.

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

Để kết hợp tất cả, hãy cập nhật hàm route_message và hằng số để kích hoạt logic ở trên.

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

Sau đây là một số ảnh chụp màn hình minh hoạ những gì mà logic trên đạt được:

8feacf94ed0ac6c4.png

Khi sẵn sàng tích hợp với công ty xử lý thanh toán trong phần tiếp theo của lớp học lập trình, chúng ta sẽ gọi hàm get_cart_price để truyền dữ liệu vào công ty xử lý thanh toán và bắt đầu quy trình thanh toán.

Bạn có thể thử chức năng giỏ hàng này trong cuộc trò chuyện Business Messages bằng cách triển khai ứng dụng và tương tác với nhân viên hỗ trợ.

$ gcloud app deploy

6. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo thành công trải nghiệm giỏ hàng trong Business Messages.

Một tính năng mà chúng ta chưa đề cập đến trong lớp học lập trình này là tính năng xoá toàn bộ giỏ hàng. Nếu muốn, hãy thử mở rộng ứng dụng để thực hiện tính năng "Xoá nội dung trong giỏ hàng". Giải pháp có trong bước 3 của mã nguồn mà bạn đã sao chép.

Trong một phần sau, chúng ta sẽ tích hợp với một công ty xử lý thanh toán bên ngoài để cho phép người dùng hoàn tất giao dịch thanh toán với thương hiệu của bạn.

Điều gì tạo nên một giỏ hàng hiệu quả?

Trải nghiệm tốt về giỏ hàng trong một cuộc trò chuyện cũng giống như trong một ứng dụng di động hoặc tại một cửa hàng thực tế. Khả năng thêm mặt hàng, xoá mặt hàng và tính giá giỏ hàng chỉ là một vài tính năng mà chúng ta đã khám phá trong lớp học lập trình này. Một điểm khác biệt so với giỏ hàng thực tế là bạn có thể xem giá của tất cả mặt hàng trong giỏ hàng bất cứ lúc nào, khi bạn thêm hoặc xoá mặt hàng. Những tính năng có giá trị cao như vậy sẽ giúp trải nghiệm thương mại đàm thoại của bạn trở nên nổi bật!

Tiếp theo là gì?

Khi bạn đã sẵn sàng, hãy xem một số chủ đề sau để tìm hiểu về những lượt tương tác phức tạp hơn mà bạn có thể đạt được trong Business Messages:

Tài liệu tham khảo