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

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

Đây là lớp học lập trình thứ hai trong loạt nội dung hướng đến việc xây dựng hành trình của người dùng khi mua hàng trên mạng đế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 của bạn và là cách để cung cấp đề 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 cho Google App Engine.

Điều gì tạo nên một giỏ hàng chất lượng?

Giỏ hàng là chìa khoá cho trải nghiệm mua sắm trực tuyến thành công. Kết quả là Business Messages không chỉ hỗ trợ quá trình hỏi và đáp về sản phẩm với khách hàng tiềm năng, mà còn tạo điều kiện cho toàn bộ trải nghiệm mua sắm đến việc hoàn tất thanh toán trong cuộc trò chuyện.

9d17537b980d0e62.png

Ngoài việc mang đến một giỏ hàng chất lượng cao, trải nghiệm mua sắm chất lượng cao 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 hơn vào giỏ hàng, người dùng có thể xem lại toàn bộ giỏ hàng và có thể xoá bớt mặt hàng hoặc thêm 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 nhân viên hỗ trợ kỹ thuật số đã tạo ở phần 1 cho công ty hư cấu Bonjourft, để người dùng có thể duyệt xem danh mục cá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ế lưu trữ ổn đị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

2. Thiết lập

Lớp học lập trình này giả định rằng bạn đã tạo nhân viên hỗ trợ đầu tiên và hoàn thành phần 1 của lớp học lập trình. Do đó, chúng ta sẽ không tìm hiểu những thông tin 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. Do đó, 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ể lưu giữ dữ liệu liên quan đến giỏ hàng.

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

Trong dòng lệnh, hãy sao chép Django Echo Bot Sample vào thư mục đang 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 đã tạo cho tài khoản dịch vụ vào thư mục tài nguyên của mẫu rồi đổ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 các API Business Messages, 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, 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 xử lý đúng dự án GCP.
  3. Nhấp vào Bật.

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

Trong dòng lệnh, 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 gần đây nhất:

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. Lớp này chứa mọi kiến thức chúng ta đã làm trong phần 1 của lớp học lập trình. Vui lòng định cấu hình webhook của bạn nếu bạn chưa làm như vậy.

Ứng dụng này sẽ trả lời một số câu hỏi đơn giản như một người dùng hỏi về giờ làm việc của Bonjourhalf. Bạn nên kiểm tra tính năng 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. Các URL thử nghiệm sẽ mở 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ợ của mình trên thiết bị đó.

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 hàng của thương hiệu thông qua API nội bộ. Trong các trường hợp khác, bạn có thể trích xuất dữ liệu một trang web hoặc xây dựng hệ thống theo dõi kho hàng của riêng mình. Trọng tâm của chúng tôi không phải là xây dựng hệ thống kho hàng; chúng tôi sẽ sử dụng một tệp tĩnh đơn giản có chứa hình ảnh và thông tin sản phẩm cho đại lý của chúng tôi. Trong mục này, chúng ta sẽ lấy thông tin từ tệp tĩnh này, đưa thông tin đó vào cuộc trò chuyện và cho phép người dùng duyệt qua những mặt hàng có thể thêm vào giỏ hàng.

Tệp khoảng không quảng cáo tĩnh sẽ 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 dùng ứ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 view.py để đọc nội dung của tệp JSON rồi đưa nội dung này vào 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 có sẵn.

Bạn có thể đặt định nghĩa hàm này ở bất kỳ đâu trong view.py.

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

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

Việ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. Giờ đây, chúng ta cần một cách để trình bày thông tin sản phẩm này trong cuộc trò chuyện.

Hiển thị danh sách sản phẩm

Để đơn giản hoá trong lớp học lập trình này, chúng ta có một danh mục sản phẩm chung để giới thiệu tất cả các mặt hàng tồn kho trong cuộc trò chuyện từ Business Messages thông qua một băng chuyền thẻ thông tin.

Để xem danh sách sản phẩm, chúng tôi sẽ tạo một câu trả lời đề xuất có văn bản "Hiển thị thực đơn" và kioskData "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 đăng lại, chúng tôi sẽ gửi băng chuyền thẻ thông tin. Hãy thêm một hằng số mới cho câu trả lời đề xuất này ở đầu view.py.

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

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

Từ đây, chúng tôi phân tích cú pháp thông báo và chuyển thông báo đó đến một chức năng mới sẽ gửi băng chuyền thẻ thông tin có chứa danh sách sản phẩm. Trước tiên, hãy mở rộng hàm route_message để gọi một 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 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)
...

Và hãy đảm bảo hoàn tất quy trình này và xác định send_product_catalog. send_product_catalog gọi get_menu_carousel,. Thao tác này sẽ tạo băng chuyền gồm các thẻ thông tin 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ứ đâu trong view.py. Lưu ý rằng đoạn mã sau đây sử dụng 2 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 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 một lựa chọn của người dùng cho 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 đề xuất, Business Messages sẽ gửi postData có 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 trông giống như thế này để có thể thực sự thêm mặt hàng vào giỏ hàng.

Giờ chúng ta đã thực hiện những thay đổi này, hãy triển khai ứng dụng web cho Google App Engine và thử trải nghiệm!

$ gcloud app deploy

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

4639da46bcc5230c.png

Nếu bạn nhấn vào Thêm mặt hàng,thì sẽ không có gì xảy ra ngoại trừ việc nhân viên hỗ trợ sẽ đọc lại dữ liệu đăng lại trong 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 và sử dụng danh mục này để tạo giỏ hàng nơi mặt hàng sẽ được thêm vào.

Danh mục sản phẩm bạn vừa tạo có thể mở rộng theo nhiều cách. Bạn có thể có các lựa chọn khác nhau trên thực đơn đồ uống hoặc lựa chọn cho người ăn chay. Sử dụng băng chuyền hoặc khối đề xuất là một cách hiệu quả để cho phép người dùng xem chi tiết các lựa chọn trong trình đơn để xem 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 tách biệt với đồ ăn trong thực đơn, hoặc thậm chí có thể chỉ định các mó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 từ phần trước. Chức năng này cho phép chúng ta duyệt qua các sản phẩm có sẵn.

Trải nghiệm chính trong giỏ hàng 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.

Theo dõi trạng thái của giỏ hàng đồng nghĩa với việc chúng tôi cần duy trì dữ liệu trên ứng dụng web. Để đơn giản hoá quá trình thử nghiệm và triển khai, chúng tôi sẽ sử dụng Google Datastore trong Google Cloud Platform để lưu trữ dữ liệu. Mã cuộc trò chuyện không đổi 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à duy trì mã nhận dạng 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 bất cứ khi nào có bất kỳ hoạt động tương tác nào đối với giỏ hàng (ví dụ: 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 hàm để cập nhật giỏ hàng. Hàm này nhận giá trị đầ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 sẵn trong băng chuyền hiển thị danh sách sản phẩm. Hàm này tạo một ứng dụng khách Google Datastore và tìm nạp ngay 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 view.py. Chúng tôi 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 đề xuất là Thêm mặt hàng trên băng chuyền sản phẩm, hệ thống đăng lạiData 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" và 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 liên kết với mặt hàng trong tệp 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á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 xem xét ở đây là nếu Datastore chưa từng thấy mã 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. Phầ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 mà Google Datastore duy trì.

Đoạn mã sau là phần mở rộng cho hàm trước được thêm vào view.py. Bạn có thể bổ sung điểm khác biệt hoặc sao chép đoạn mã và thay thế phiên bản hiện tại 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 này để hỗ trợ trường hợp cart_cmd chứa chuỗi "del-item" được xác định trong CMD_DEL_ITEM.

Liên kết với nhau

Hãy nhớ thêm hệ thống ống nước 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, hàm update_shopping_cart sẽ được gọi. Bạn cũng cần định nghĩa một hằng số để thêm các mặt hàng bằng cách sử dụng quy ước mà chúng tôi sử dụng trong suốt 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 bạn triển khai các thay đổi của mình đối với Google App Engine, bạn có thể thấy các thay đổi đối với giỏ hàng được phản ánh trong trang tổng quan của Google Datastore trong bảng điều khiển GCP. Hãy xem ảnh chụp màn hình dưới đây trong bảng điều khiển Google Datastore, có một thực thể đượ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 tồn kho và số lượng những mặt hàng có trong giỏ hàng.

619dc18a8136ea69.png

Trong phần tiếp theo, chúng ta sẽ tạo cách để liệt kê các mặt hàng trong giỏ hàng. Cơ chế xem xét 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 tôi có thể hiểu được trạng thái của giỏ hàng và biết được chúng tôi có thể xoá những mặt hàng nào.

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 chứa băng chuyền thẻ thông tin với câu trả lời đề xuất được liên kết cho "Xoá một thẻ" hoặc "Thêm một". Băng chuyền thẻ thông tin sẽ liệt kê thêm số lượng mặt hàng được lưu trong giỏ hàng.

Một điều bạn cần lưu ý trước khi bắt đầu triển khai và viết hàm của mình: nếu chỉ có một loại mặt hàng trong giỏ hàng, chúng ta không thể kết xuất mặt hàng đó dưới dạng băng chuyền. Băng chuyền thẻ thông tin 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 sẽ thấy một thông báo đơn giản cho biết giỏ hàng đang 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ể Giỏ hàng dựa trên Mã cuộc trò chuyện. Sau đó, chúng ta sẽ gọi hàm get_inventory_data và sử dụng băng chuyền thẻ thông tin để báo cáo trạng thái của giỏ hàng. Chúng ta cũng sẽ cần lấy mã nhận dạng của sản phẩm theo tên, sau đó 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 tôi có thể liên kết các câu trả lời đề xuất để xoá mặt hàng hoặc thêm mặt hàng theo mã sản phẩm. Đoạn mã dưới đây thực hiện tất cả những thao tác này. Sao chép mã ở vị trí bất kỳ vào view.py.

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

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


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

  # Retrieve the inventory data
  inventory = get_inventory_data()

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

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

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

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

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

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

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

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

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

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

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

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

    fallback_text = ''

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

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

    send_message(message_obj, conversation_id)

...

Đảm bảo bạn đã xác định CMD_SHOW_CART ở đầu view.py và gọi send_shopping_cart nếu người dùng gửi tin nhắn 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 chúng tôi đã 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ó gì trong giỏ hàng, một thẻ thông tin hiển thị một mặt hàng trong giỏ hàng hoặc một băng chuyền gồm nhiều thẻ cho thấy nhiều mặt hàng. Ngoài ra, chúng tôi cũng có ba câu trả lời đề xuất: "Xem tổng giá", "Dọn sạch giỏ hàng" và "Xem thực đơn".

Hãy thử triển khai các thay đổi 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 giỏ hàng từ các khu vực trò chuyện như trong ảnh chụp màn hình ở trên. Bạn có thể triển khai các thay đổi bằng lệnh này chạy từ thư mục thep step-2 nơi bạn đang 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", theo đó hàm này sẽ tham chiếu chéo dữ liệu trong Datastore bằng 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, nơi chúng ta tích hợp với thanh toán.

Đang 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 thông báo xác nhận để xác nhận hành động của họ và rằng bạn đã xử lý yêu cầu của họ. Điều này không chỉ giúp đặt ra kỳ vọng mà còn duy trì cuộc trò chuyện.

Hãy mở rộng hàm update_shopping_cart để hàm này 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 bị xoá, đồng thời đề 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 mặt hàng, xoá mặt hàng 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 về 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 bước 2.

$ gcloud app deploy

5. Chuẩn bị nhận tiền thanh toán

Để chuẩn bị cho việc tích hợp với công ty xử lý thanh toán trong phần tiếp theo của loạt bài tập 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 từ 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 chức năng đó và gửi thông báo 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)
...

Để liên kết tất cả lại với nhau, 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)
...

Dưới đây là một số ảnh chụp màn hình cho thấy những lợi ích 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 này, 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ể dùng thử chức năng giỏ hàng này trong cuộc trò chuyện với 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 tôi 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 để dùng tính năng "Dọn sạch giỏ hàng". Giải pháp có sẵn trong bước 3 của mã nguồn mà bạn đã sao chép.

Trong tương lai, chúng tôi 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 chất lượng?

Trải nghiệm giỏ hàng tốt trong cuộc trò chuyện không khác với ứng dụng dành cho thiết bị di động hoặc tại cửa hàng thực tế. Việc có thể 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 đã tìm hiểu trong lớp học lập trình này. Một thứ khác với giỏ hàng trong thế giới thực là bạn có thể nhìn thấy giá của tất cả các mặt hàng trong giỏ hàng tại bất kỳ thời điểm nào, khi bạn thêm mặt hàng hoặc xoá mặt hàng. Các 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 Business Messages:

Tài liệu tham khảo