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: 30/10/2020

Tạo Giỏ hàng trên Business Messages!

Đây là lớp học lập trình thứ hai trong một loạt lớp học nhằm xây dựng hành trình của người dùng trong quy trình Mua hàng trực tuyến và đến lấy hàng 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 để 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ề các 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. Hóa ra, Business Messages không chỉ hỗ trợ hiệu quả việc giải đáp thắc mắc về sản phẩm với khách hàng tiềm năng, 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 một 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 qua 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 các mặt hàng khác vào giỏ hàng, người dùng có thể xem lại toàn bộ giỏ hàng và xoá hoặc thêm các mặt hàng khác 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 trợ lý 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 qua 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 thị danh mục câu hỏi trong Business Messages
  • Đề xuất các mặt hàng mà người dùng có thể quan tâm
  • Xem lại 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 ứng dụng web trên App Engine trên Google Cloud Platform
  • Cách sử dụng cơ chế bộ nhớ cố định để 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 các kiến thức cơ bản về cách bật API Business Messages và Business Communications, 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. Do đó, chúng ta sẽ nhân bản một ứng dụng mẫu để đảm bảo ứng dụng của bạn nhất quán với ứng dụng mà chúng ta đang xây dựng. Đồng thời, chúng ta sẽ bật API cho Datastore trên Google Cloud Platform để có thể lưu trữ dữ liệu liên quan đến giỏ hàng.

Sao chép ứng dụng từ GitHub

Trong một thiết bị đầu cuối, hãy nhân bản Mẫu bot Django Echo 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 xác thực JSON đã 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 xác thực 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.

Trong lớp học lập trình này, vì 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 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 thiết bị đầu cuối, hãy chuyển đến thư mục bước-2 của mẫu.

Chạy các lệnh sau trong một thiết bị đầu cuối để 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.

Lưu ý URL của ứng dụng đã triển khai trong kết quả 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 chứa một ứng dụng web có webhook để nhận tin nhắn từ Business Messages. Tệp này chứa mọi nội dung chúng ta đã thực hiện trong phần 1 của lớp học lập trình. Vui lòng định cấu hình webhook nếu bạn chưa thực hiện.

Ứng dụng sẽ trả lời một số câu hỏi đơn giản như người dùng hỏi về giờ hoạt động của Bonjour Meal. Bạn nên kiểm thử điều này trên thiết bị di động thông qua các URL thử nghiệm mà bạn có thể truy xuất từ phần Thông tin của nhân viên hỗ trợ trong Business Communications Console. URL thử nghiệm sẽ khởi chạy trải nghiệm Business Messages trên thiết bị di động của bạn 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 quản lý khoảng không quảng cáo

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ể thu thập thông tin trên 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 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 đó vào cuộc trò chuyện và cho phép người dùng duyệt xem các mặt hàng có sẵn để thêm vào giỏ hàng.

Tệp khoảng không quảng cáo 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ảng không quảng cáo của chúng tôi

Khoảng không quảng cáo là một tệp tĩnh có tên "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 rồi hiển thị nội dung đó trong 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 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ảng không quảng cáo. Bây giờ, chúng ta cần một cách để đưa thông tin sản phẩm này vào cuộc trò chuyện.

Hiển thị danh mục sản phẩm

Để đơn giản hoá lớp học lập trình này, chúng ta 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 hàng cho cuộc trò chuyện trên Business Messages thông qua một băng chuyền thẻ đa dạng thức.

Để xem danh mục sản phẩm, chúng ta sẽ tạo một tin nhắn trả lời đề xuất có văn bản "Hiển thị trình đơn" và postbackData "show-product-catalog". Khi người dùng nhấn vào tin nhắn trả lời đề xuất và ứng dụng web của bạn nhận được dữ liệu đăng lại, chúng ta sẽ gửi băng chuyền thẻ đa dạng thức. 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 băng chuyền thẻ đa dạng thức 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 tin nhắn trả lời được đề 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 bổ sung vào câu lệnh if trong hàm route_message để kiểm tra xem normalized_message có bằng hằng số CMD_SHOW_PRODUCT_CATALOG mà chúng ta đã xác định trước đó hay không.

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ảng không quảng cáo mà chúng ta đã đọc trước đó.

Bạn có thể đặt định nghĩa hàm ở bất cứ vị trí nào trong views.py. Lưu ý rằng đoạn mã sau đây sử dụng hai hằng số mới cần được 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 bạn kiểm tra quá trình tạo các mục 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 một 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 tin nhắn trả lời được đề xuất, tính năng Tin nhắn cho doanh nghiệp 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 sau, chúng ta sẽ phân tích cú pháp các thông báo có dạng như sau để có thể thực sự thêm mặt hàng vào giỏ hàng.

Giờ đây, 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à trải nghiệm ứng dụng!

$ gcloud app deploy

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

4639da46bcc5230c.png

Nếu bạn nhấn vào Thêm mục,thì sẽ không có gì xảy ra ngoại trừ việc trợ lý sẽ lặp lại dữ liệu đăng lại từ thư trả lời được đề 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 vừa tạo theo nhiều cách. Bạn có thể có nhiều lựa chọn trong thực đơn đồ uống hoặc món chay. Việc sử dụng băng chuyền hoặc khối đề xuất là một cách hay để cho phép người dùng xem chi tiết 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 đồ ăn 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 để có thể duyệt xem các sản phẩm hiện có.

Trong số nhiều tính năng, 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 lại 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 lưu 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 để lưu trữ dữ liệu. Mã cuộc trò chuyện luôn giữ nguyên giữa người dùng và doanh nghiệp, vì vậy, chúng ta có thể sử 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à lưu trữ mã cuộc trò chuyện khi chúng ta thấy mã đó.

Kết nối với Datastore

Chúng tôi sẽ kết nối với Google Datastore mỗi khi có bất kỳ hoạt động tương tác nào được thực thi đối 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 tại 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 dữ liệu đầ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. Hành động này đã được tích hợp vào băng chuyền hiển thị danh mục sản phẩm. 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ã 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 mở rộng 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ó hai khoá là "action" và "item_name". Từ điển JSON này được gửi đến webhook của bạn. Trường "item_name" là giá trị nhận dạng duy nhất được liên kết với mặt hàng trong inventory.json.

Sau khi có lệnh giỏ hàng và mặt hàng trong giỏ hàng được phân tích cú pháp từ thông báo, chúng ta có thể viết 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 bao giờ thấy mã cuộc trò chuyện hoặc nếu giỏ hàng đang nhận 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 do Google Datastore lưu 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 sự 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 thành phần

Hãy nhớ thêm đường ống trong 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, thì 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 cách sử dụng quy ước mà chúng ta sử dụng trong toàn bộ lớp học lập trình này.

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 về giỏ hàng được phản ánh trong trang tổng quan về Google Datastore trong bảng điều khiển GCP. Hãy xem ảnh chụp màn hình bên dưới của bảng điều khiển Google Datastore, có một thực thể được đặt tên theo mã cuộc trò chuyện, theo sau là một số mối quan hệ với các mặt hàng trong kho và số lượng của các 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 lại giỏ hàng sẽ 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 lại 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 có thể hiểu trạng thái của giỏ hàng và biết những mặt hàng nào 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:", theo sau là một tin nhắn khác chứa băng chuyền thẻ thông tin chi tiết có các câu trả lời đề xuất liên quan cho lựa chọn "Xoá một mặt hàng" hoặc "Thêm một mặt hàng". Ngoài ra, băng chuyền thẻ đa dạng thức 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 và 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 thức phải chứa ít nhất 2 thẻ. Mặt khác, 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 trống.

Do đó, 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 mã cuộc trò chuyện. Sau khi có thông tin đó, chúng ta sẽ gọi hàm get_inventory_data và sử dụng 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 views.py ở bất kỳ vị trí nào.

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 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 thông báo cho biết không có mặt hàng nào trong giỏ hàng, một thẻ đa dạng thức hiển thị một mặt hàng trong giỏ hàng hoặc một băng chuyền thẻ hiển thị nhiều mặt hàng. Ngoài ra, chúng tôi có 3 câu trả lời đề xuất: "Xem tổng giá", "Rỗng 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ó 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 từ nền tảng 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 chạy từ thư mục bước-2 nơi bạn thêm các thay đổi.

$ gcloud app deploy

Chúng ta sẽ xây dựng tính năng "Xem tổng giá" trong phần tiếp theo sau khi xây dựng 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" ở chỗ hàm này sẽ tham chiếu chéo dữ liệu trong Datastore với tệp inventory.json để tạo 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, trong đó chúng ta sẽ tích hợp với tính năng 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 của 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 thông báo 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ọ. Việc này không chỉ giúp đặ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 để gửi thông báo đến mã cuộc trò chuyện cho biết 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 trình đơ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 nhân viên hỗ trợ. Bạn có thể thực hiện việc này bằng cách chạy lệnh này trong thư mục step-2.

$ gcloud app deploy

5. Chuẩn bị cho các khoản thanh toán

Để chuẩn bị tích hợp với trình xử lý thanh toán trong phần tiếp theo của loạt bài này, chúng ta cần có 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

...

Cuối cùng, chúng ta có thể sử dụng hàm đó và gửi thông báo đến 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 cho thấy những gì logic trên đạt được:

8feacf94ed0ac6c4.png

Khi đã sẵn sàng tích hợp với trình 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 trình xử lý thanh toán và bắt đầu quy trình thanh toán.

Xin nhắc lại, bạn có thể thử chức năng giỏ hàng này trong cuộc trò chuyện trê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 trong lớp học lập trình này là tính năng làm trống 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 "Rỗng giỏ hàng". Giải pháp có trong bước 3 của mã nguồn mà bạn đã nhân bản.

Trong phần sau, chúng tôi sẽ tích hợp với một trình 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 giỏ hàng hiệu quả trong cuộc trò chuyện không khác gì trải nghiệm trong ứng dụng di động hoặc tại 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á của 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 giữa giỏ hàng thực tế và giỏ hàng trên mạng là bạn có thể xem giá của tất cả mặt hàng trong giỏ hàng tại bất kỳ thời điểm nào khi thêm hoặc xoá mặt hàng. Những loại tính năng có giá trị cao này sẽ giúp trải nghiệm thương mại trò chuyện 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ề các hoạt động tương tác phức tạp hơn mà bạn có thể thực hiện trong tính năng Tin nhắn cho doanh nghiệp:

Tài liệu tham khảo