ซื้อของออนไลน์แบบรับสินค้าที่ร้าน: อาหารบงชูร์ - ตอนที่ 2 - การสร้างรถเข็นช็อปปิ้ง

1. บทนำ

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

อัปเดตล่าสุด: 30-10-2020

สร้างรถเข็นช็อปปิ้งใน Business Messages

นี่คือโค้ดแล็บที่ 2 ในชุดโค้ดแล็บที่มีเป้าหมายเพื่อสร้างเส้นทางของผู้ใช้ในการซื้อออนไลน์และมารับสินค้าที่ร้าน ในเส้นทางอีคอมเมิร์ซหลายเส้นทาง รถเข็นช็อปปิ้งเป็นกุญแจสำคัญสู่ความสำเร็จในการเปลี่ยนผู้ใช้ให้กลายเป็นลูกค้าที่ชำระเงิน นอกจากนี้ รถเข็นช็อปปิ้งยังเป็นวิธีทำความเข้าใจลูกค้าได้ดียิ่งขึ้น และเป็นวิธีเสนอคำแนะนำเกี่ยวกับสินค้าอื่นๆ ที่ลูกค้าอาจสนใจ ใน Codelab นี้ เราจะมุ่งเน้นที่การสร้างประสบการณ์รถเข็นช็อปปิ้งและการติดตั้งใช้งานแอปพลิเคชันใน Google App Engine

รถเข็นช็อปปิ้งที่ดีควรเป็นอย่างไร

รถเข็นช็อปปิ้งเป็นกุญแจสำคัญในการสร้างประสบการณ์การช็อปปิ้งออนไลน์ที่ประสบความสำเร็จ ปรากฏว่าข้อความธุรกิจไม่ได้มีดีแค่การอำนวยความสะดวกในการถามตอบเกี่ยวกับผลิตภัณฑ์กับผู้มีโอกาสเป็นลูกค้าเท่านั้น แต่ยังช่วยอำนวยความสะดวกในประสบการณ์การช็อปปิ้งทั้งหมดไปจนถึงการชำระเงินภายในแชทได้อีกด้วย

9d17537b980d0e62.png

นอกเหนือจากรถเข็นช็อปปิ้งที่ดีแล้ว ประสบการณ์การช็อปปิ้งที่ดีจะช่วยให้ผู้ใช้เลือกดูสินค้าตามหมวดหมู่ได้ และช่วยให้ธุรกิจแนะนำผลิตภัณฑ์อื่นๆ ที่ผู้ซื้ออาจสนใจได้ หลังจากเพิ่มสินค้าลงในรถเข็นช็อปปิ้งแล้ว ผู้ใช้จะตรวจสอบรถเข็นทั้งหมดได้ และสามารถนำสินค้าออกหรือเพิ่มสินค้าก่อนชำระเงินได้

สิ่งที่คุณจะสร้าง

ในส่วนนี้ของชุด Codelab คุณจะได้ขยายเอเจนต์ดิจิทัลที่สร้างขึ้นในส่วนที่ 1 สำหรับบริษัทสมมติ Bonjour Meal เพื่อให้ผู้ใช้เรียกดูแคตตาล็อกสินค้าและเพิ่มสินค้าลงในรถเข็นช็อปปิ้งได้

ใน Codelab นี้ แอปของคุณจะทำสิ่งต่อไปนี้

  • แสดงแคตตาล็อกคำถามภายใน Business Messages
  • แนะนำไอเทมที่ผู้ใช้อาจสนใจ
  • ตรวจสอบเนื้อหาของรถเข็นช็อปปิ้งและสร้างสรุปราคารวม

ab2fb6a4ed33a129.png

สิ่งที่คุณจะได้เรียนรู้

  • วิธีนำเว็บแอปพลิเคชันไปใช้งานใน App Engine บน Google Cloud Platform
  • วิธีใช้กลไกการจัดเก็บข้อมูลแบบถาวรเพื่อบันทึกสถานะของรถเข็นช็อปปิ้ง

Codelab นี้มุ่งเน้นที่การขยายเอเจนต์ดิจิทัลจากส่วนที่ 1 ของชุด Codelab นี้

สิ่งที่คุณต้องมี

2. การเริ่มตั้งค่า

Codelab นี้ถือว่าคุณได้สร้าง Agent แรกและทำส่วนที่ 1 ของ Codelab เสร็จแล้ว ดังนั้น เราจะไม่พูดถึงพื้นฐานของการเปิดใช้ Business Messages และ Business Communications API, การสร้างคีย์บัญชีบริการ, การติดตั้งใช้งานแอปพลิเคชัน หรือการตั้งค่า Webhook ใน Business Communications Console อย่างไรก็ตาม เราจะโคลนแอปพลิเคชันตัวอย่างเพื่อให้แน่ใจว่าแอปพลิเคชันของคุณสอดคล้องกับสิ่งที่เรากำลังสร้างขึ้น และเราจะเปิดใช้ API สำหรับ Datastore ใน Google Cloud Platform เพื่อให้สามารถจัดเก็บข้อมูลที่เกี่ยวข้องกับรถเข็นช็อปปิ้งได้

โคลนแอปพลิเคชันจาก GitHub

ในเทอร์มินัล ให้โคลนตัวอย่างบ็อต Echo ของ Django ไปยังไดเรกทอรีการทำงานของโปรเจ็กต์ด้วยคำสั่งต่อไปนี้

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

คัดลอกไฟล์ข้อมูลเข้าสู่ระบบ JSON ที่สร้างขึ้นสำหรับบัญชีบริการไปยังโฟลเดอร์ทรัพยากรของตัวอย่าง แล้วเปลี่ยนชื่อข้อมูลเข้าสู่ระบบเป็น "bm-agent-service-account-credentials.json"

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

เปิดใช้ Google Datastore API

ในส่วนที่ 1 ของโค้ดแล็บนี้ คุณได้เปิดใช้ Business Messages API, Business Communications API และ Cloud Build API

สำหรับโค้ดแล็บนี้ เนื่องจากเราจะทำงานกับ Google Datastore จึงต้องเปิดใช้ API นี้ด้วย

  1. เปิด Google Datastore API ใน Google Cloud Console
  2. ตรวจสอบว่าคุณกำลังทำงานกับโปรเจ็กต์ GCP ที่ถูกต้อง
  3. คลิกเปิดใช้

การทําให้แอปพลิเคชันตัวอย่างใช้งานได้

ในเทอร์มินัล ให้ไปที่ไดเรกทอรี step-2 ของตัวอย่าง

เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัลเพื่อติดตั้งใช้งานตัวอย่าง

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • PROJECT_ID คือรหัสโปรเจ็กต์ของโปรเจ็กต์ที่คุณใช้ลงทะเบียนกับ API

จด URL ของแอปพลิเคชันที่ติดตั้งใช้งานในเอาต์พุตของคำสั่งสุดท้าย

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

โค้ดที่คุณเพิ่งติดตั้งใช้งานมีเว็บแอปพลิเคชันที่มีเว็บฮุคเพื่อรับข้อความจาก Business Messages ซึ่งมีทุกอย่างที่เราทำตั้งแต่ส่วนที่ 1 ของโค้ดแล็บ โปรดกำหนดค่า Webhook หากยังไม่ได้ทำ

แอปพลิเคชันจะตอบคำถามง่ายๆ บางอย่าง เช่น ผู้ใช้ถามเกี่ยวกับเวลาทำการของ Bonjour Meal คุณควรทดสอบในอุปกรณ์เคลื่อนที่ผ่าน URL สำหรับทดสอบที่ดึงมาจากข้อมูลตัวแทนภายในคอนโซล Business Communications URL ทดสอบจะเปิดประสบการณ์การใช้งาน Business Messages บนอุปกรณ์เคลื่อนที่ และคุณจะเริ่มโต้ตอบกับตัวแทนได้ที่นั่น

3. แคตตาล็อกสินค้า

ระบบสินค้าคงคลัง

ในกรณีส่วนใหญ่ คุณสามารถผสานรวมกับพื้นที่โฆษณาของแบรนด์ได้โดยตรงผ่าน API ภายใน ในกรณีอื่นๆ คุณอาจคัดลอกข้อมูลจากหน้าเว็บหรือสร้างระบบติดตามพื้นที่โฆษณาของคุณเอง เราไม่ได้มุ่งเน้นที่การสร้างระบบสินค้าคงคลัง แต่จะใช้ไฟล์แบบคงที่อย่างง่ายที่มีรูปภาพและข้อมูลผลิตภัณฑ์สำหรับเอเจนต์ ในส่วนนี้ เราจะดึงข้อมูลจากไฟล์แบบคงที่นี้ แสดงข้อมูลดังกล่าวในการสนทนา และอนุญาตให้ผู้ใช้เรียกดูสินค้าที่พร้อมเพิ่มลงในรถเข็นช็อปปิ้ง

ไฟล์สินค้าคงคลังแบบคงที่จะมีลักษณะดังนี้

bonjourmeal-codelab/step-2/resources/inventory.json

{

    "food": [
        {
            "id":0,
            "name": "Ham and cheese sandwich",
            "price": "6.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/ham-and-cheese.png",
            "remaining": 8
        },
        {
            "id":1,
            "name": "Chicken veggie wrap",
            "price": "9.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/chicken-veggie-wrap.png",
            "remaining": 2
        },
        {
            "id":2,
            "name": "Assorted cheese plate",
            "price": "7.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/assorted-cheese-plate.png",
            "remaining": 6
        },
        {
            "id":3,
            "name": "Apple walnut salad",
            "price": "12.99",
            "image_url": "https://storage.googleapis.com/bonjour-rail.appspot.com/apple-walnut-salad.png",
            "remaining": 1
        }
    ]
}

มาทำให้แอปพลิเคชัน Python อ่านไฟล์นี้กัน

การอ่านจากคลังของเรา

พื้นที่โฆษณาเป็นไฟล์แบบคงที่ชื่อ "inventory.json" ซึ่งอยู่ในไดเรกทอรี ./resources เราต้องเพิ่มตรรกะ Python บางอย่างลงใน views.py เพื่ออ่านเนื้อหาของไฟล์ JSON แล้วแสดงในการสนทนา มาสร้างฟังก์ชันที่อ่านข้อมูลจากไฟล์ JSON และแสดงรายการผลิตภัณฑ์ที่มีกัน

คุณวางคำจำกัดความฟังก์ชันนี้ได้ทุกที่ใน views.py

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

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

ซึ่งจะช่วยให้เรามีข้อมูลที่จำเป็นในการอ่านข้อมูลจากสินค้าคงคลัง ตอนนี้เราต้องการวิธีแสดงข้อมูลผลิตภัณฑ์นี้ในการสนทนา

การแสดงแคตตาล็อกผลิตภัณฑ์

เพื่อความสะดวกในโค้ดแล็บนี้ เรามีแคตตาล็อกผลิตภัณฑ์ทั่วไปเพื่อแสดงสินค้าคงคลังทั้งหมดในการสนทนาของ Business Messages ผ่านภาพสไลด์ริชการ์ดเดียว

หากต้องการดูแคตตาล็อกผลิตภัณฑ์ เราจะสร้างคำตอบที่แนะนำซึ่งมีข้อความ "แสดงเมนู" และ postbackData "show-product-catalog" เมื่อผู้ใช้แตะคำตอบที่แนะนำและเว็บแอปพลิเคชันของคุณได้รับข้อมูลการแจ้งผล Conversion เราจะส่งภาพสไลด์การ์ด Rich มาเพิ่มค่าคงที่ใหม่สำหรับคำตอบที่แนะนำนี้ที่ด้านบนของ views.py กัน

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

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

จากนั้นเราจะแยกวิเคราะห์ข้อความและกำหนดเส้นทางไปยังฟังก์ชันใหม่ที่ส่งภาพหมุนการ์ดริชมีเดียซึ่งมีแคตตาล็อกผลิตภัณฑ์ ก่อนอื่น ให้ขยายฟังก์ชัน route_message เพื่อเรียกใช้ฟังก์ชัน "send_product_catalog" เมื่อแตะคำตอบที่แนะนำ จากนั้นเราจะกำหนดฟังก์ชัน

ในข้อมูลโค้ดต่อไปนี้ ให้เพิ่มเงื่อนไขเพิ่มเติมลงในคำสั่ง if ในฟังก์ชัน route_message เพื่อตรวจสอบว่า normalized_message เท่ากับค่าคงที่ที่เรากำหนดไว้ก่อนหน้านี้หรือไม่ CMD_SHOW_PRODUCT_CATALOG

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

...
def route_message(message, conversation_id):
    '''
    Routes the message received from the user to create a response.

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

    if normalized_message == CMD_RICH_CARD:
        send_rich_card(conversation_id)
    elif normalized_message == CMD_CAROUSEL_CARD:
        send_carousel(conversation_id)
    elif normalized_message == CMD_SUGGESTIONS:
        send_message_with_suggestions(conversation_id)
    elif normalized_message == CMD_BUSINESS_HOURS_INQUIRY:
        send_message_with_business_hours(conversation_id)
    elif normalized_message == CMD_ONLINE_SHOPPING_INQUIRY:
        send_online_shopping_info_message(conversation_id)
    elif normalized_message == CMD_SHOW_PRODUCT_CATALOG:
        send_product_catalog(conversation_id)
    else:
        echo_message(message, conversation_id)
...

และอย่าลืมทำตามขั้นตอนให้เสร็จสิ้นและกำหนด send_product_catalog send_product_catalog เรียกใช้ get_menu_carousel, ซึ่งจะสร้างภาพสไลด์ของการ์ดริชมีเดียจากไฟล์สินค้าคงคลังที่เราอ่านก่อนหน้านี้

คุณวางคำจำกัดความฟังก์ชันไว้ที่ใดก็ได้ใน views.py โปรดทราบว่าข้อมูลโค้ดต่อไปนี้ใช้ค่าคงที่ใหม่ 2 รายการซึ่งควรเพิ่มไว้ที่ด้านบนของไฟล์

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

...

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

...

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

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

    inventory = get_inventory_data()

    card_content = []

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

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

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

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

    Args:
        conversation_id (str): The unique id for this user and agent.
    """
    rich_card = BusinessMessagesRichCard(carouselCard=get_menu_carousel())

    fallback_text = ''

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

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

    send_message(message_obj, conversation_id)
...

หากคุณตรวจสอบการสร้างรายการภาพสไลด์ เราจะสร้างอินสแตนซ์ของคลาส BusinessMessagesSuggestion ด้วย คำแนะนำแต่ละรายการแสดงถึงสิ่งที่ผู้ใช้เลือกสำหรับผลิตภัณฑ์ในภาพสไลด์ เมื่อผู้ใช้แตะการตอบกลับที่แนะนำ Business Messages จะส่ง postbackData ที่มี JSON ซึ่งอธิบายรายการและการดำเนินการที่ผู้ใช้ต้องการทำ (เพิ่มหรือนำออกจากรถเข็น) ไปยัง Webhook ของคุณ ในส่วนต่อไปนี้ เราจะแยกวิเคราะห์ข้อความที่มีลักษณะเช่นนี้เพื่อให้เพิ่มสินค้าลงในรถเข็นได้จริง

ตอนนี้เราได้ทำการเปลี่ยนแปลงเหล่านี้แล้ว มาติดตั้งใช้งานเว็บแอปพลิเคชันใน Google App Engine และลองใช้ประสบการณ์การใช้งานกันเลย

$ gcloud app deploy

เมื่อโหลดแพลตฟอร์มการสนทนาในอุปกรณ์เคลื่อนที่แล้ว ให้ส่งข้อความ "show-product-catalog" แล้วคุณจะเห็นภาพสไลด์ของผลิตภัณฑ์ที่มีลักษณะดังนี้

4639da46bcc5230c.png

หากคุณแตะเพิ่มรายการ จะไม่มีอะไรเกิดขึ้นจริง ยกเว้นเอเจนต์จะส่งข้อมูลการรายงานผล Conversion กลับจากคำตอบที่แนะนำ ในส่วนถัดไป เราจะใช้แคตตาล็อกผลิตภัณฑ์และใช้เพื่อสร้างรถเข็นช็อปปิ้งที่จะเพิ่มสินค้า

คุณขยายแคตตาล็อกผลิตภัณฑ์ที่เพิ่งสร้างได้หลายวิธี คุณอาจมีตัวเลือกเมนูเครื่องดื่มหรือตัวเลือกมังสวิรัติที่แตกต่างกัน การใช้ภาพสไลด์หรือชิปคำแนะนำเป็นวิธีที่ยอดเยี่ยมในการช่วยให้ผู้ใช้เจาะลึกตัวเลือกเมนูเพื่อไปยังชุดผลิตภัณฑ์ที่กำลังมองหา ลองขยายระบบแคตตาล็อกผลิตภัณฑ์เพื่อให้ผู้ใช้ดูเครื่องดื่มแยกจากอาหารในเมนู หรือแม้แต่ระบุตัวเลือกอาหารมังสวิรัติได้ ซึ่งเป็นส่วนขยายของโค้ดแล็บนี้

4. รถเข็นช็อปปิ้ง

ในส่วนนี้ของโค้ดแล็บ เราจะสร้างฟังก์ชันรถเข็นช็อปปิ้งโดยต่อยอดจากส่วนก่อนหน้าซึ่งช่วยให้เราเรียกดูผลิตภัณฑ์ที่มีได้

ประสบการณ์รถเข็นช็อปปิ้งที่สำคัญช่วยให้ผู้ใช้เพิ่มสินค้าลงในรถเข็น นำสินค้าออกจากรถเข็น ติดตามจำนวนสินค้าแต่ละรายการในรถเข็น และตรวจสอบสินค้าในรถเข็นได้

การติดตามสถานะของรถเข็นช็อปปิ้งหมายความว่าเราต้องเก็บข้อมูลไว้ในเว็บแอปพลิเคชัน เราจะใช้ Google Datastore ใน Google Cloud Platform เพื่อคงข้อมูลไว้เพื่อให้การทดลองและการติดตั้งใช้งานเป็นไปอย่างง่ายดาย รหัสการสนทนาจะคงที่ระหว่างผู้ใช้กับธุรกิจ ดังนั้นเราจึงใช้รหัสนี้เพื่อเชื่อมโยงผู้ใช้กับสินค้าในรถเข็นช็อปปิ้งได้

มาเริ่มต้นด้วยการเชื่อมต่อกับ Google Datastore และบันทึกรหัสการสนทนาเมื่อเราเห็นรหัส

การเชื่อมต่อกับ Datastore

เราจะเชื่อมต่อกับ Google Datastore ทุกครั้งที่มีการโต้ตอบกับรถเข็นช็อปปิ้ง เช่น เมื่อผู้ใช้เพิ่มหรือลบสินค้า ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ไลบรารีของไคลเอ็นต์นี้เพื่อโต้ตอบกับ Google Datastore ได้ที่เอกสารประกอบอย่างเป็นทางการ

ข้อมูลโค้ดต่อไปนี้กําหนดฟังก์ชันเพื่ออัปเดตรถเข็นช็อปปิ้ง ฟังก์ชันนี้รับอินพุต conversation_id และ message message มี JSON ที่อธิบายการดำเนินการที่ผู้ใช้ต้องการทำ ซึ่งรวมอยู่ในภาพสไลด์ที่แสดงแคตตาล็อกผลิตภัณฑ์อยู่แล้ว ฟังก์ชันนี้จะสร้างไคลเอ็นต์ Google Datastore และดึงข้อมูลเอนทิตี ShoppingCart ทันที ซึ่งคีย์คือรหัสการสนทนา

คัดลอกฟังก์ชันต่อไปนี้ไปยังไฟล์ views.py เราจะขยายความในส่วนถัดไป

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

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

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

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

มาขยายฟังก์ชันนี้เพื่อเพิ่มสินค้าลงในรถเข็นกัน

การเพิ่มสินค้าลงในรถเข็น

เมื่อผู้ใช้แตะการดำเนินการที่แนะนำ เพิ่มสินค้า จากแคโรเซลผลิตภัณฑ์ postbackData จะมี JSON ที่อธิบายการดำเนินการที่ผู้ใช้ต้องการทำ พจนานุกรม JSON มีคีย์ 2 รายการ ได้แก่ "action" และ "item_name" และระบบจะส่งพจนานุกรม JSON นี้ไปยัง Webhook ของคุณ ฟิลด์ "item_name" คือตัวระบุที่ไม่ซ้ำกันซึ่งเชื่อมโยงกับสินค้าใน inventory.json

เมื่อมีคำสั่งรถเข็นและรายการรถเข็นที่แยกวิเคราะห์จากข้อความแล้ว เราจะเขียนคำสั่งแบบมีเงื่อนไขเพื่อเพิ่มรายการได้ กรณีที่ควรพิจารณาที่นี่คือหาก Datastore ไม่เคยเห็นรหัสการสนทนาหรือหากรถเข็นช็อปปิ้งได้รับสินค้าเป็นครั้งแรก ต่อไปนี้คือส่วนขยายของupdate_shopping_cartฟังก์ชันการทำงานที่กำหนดไว้ข้างต้น การเปลี่ยนแปลงนี้จะเพิ่มสินค้าลงในรถเข็นช็อปปิ้งซึ่ง Google Datastore จะเก็บไว้

ข้อมูลโค้ดต่อไปนี้เป็นการขยายฟังก์ชันก่อนหน้าที่เพิ่มลงใน views.py คุณสามารถเพิ่มความแตกต่าง หรือคัดลอกข้อมูลโค้ดและแทนที่ฟังก์ชัน update_shopping_cart เวอร์ชันที่มีอยู่ได้

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

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

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

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

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

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

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

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

เราจะขยายฟังก์ชันนี้ในภายหลังเพื่อรองรับสถานการณ์ที่ cart_cmd มีสตริง "del-item" ที่กำหนดไว้ใน CMD_DEL_ITEM

การเชื่อมโยง

ตรวจสอบว่าคุณได้เพิ่มการเชื่อมต่อในฟังก์ชัน route_message เพื่อให้ระบบเรียกใช้ฟังก์ชัน update_shopping_cart หากคุณได้รับข้อความให้เพิ่มสินค้าลงในรถเข็น นอกจากนี้ คุณยังต้องกำหนดค่าคงที่สำหรับการเพิ่มรายการโดยใช้รูปแบบที่เราใช้ตลอดทั้งโค้ดแล็บ

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

...

CMD_DEL_ITEM = 'del-item'

...

def route_message(message, conversation_id):
    '''
    Routes the message received from the user to create a response.

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

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

...

ตอนนี้เรามีฟีเจอร์ที่ช่วยให้เพิ่มสินค้าลงในรถเข็นช็อปปิ้งได้ หากคุณนําการเปลี่ยนแปลงไปใช้กับ Google App Engine คุณควรจะเห็นการเปลี่ยนแปลงรถเข็นช็อปปิ้งแสดงในแดชบอร์ด Google Datastore ที่อยู่ในคอนโซล GCP ดูภาพหน้าจอของคอนโซล Google Datastore ด้านล่าง จะเห็นเอนทิตีเดียวซึ่งตั้งชื่อตามรหัสการสนทนา ตามด้วยความสัมพันธ์กับสินค้าคงคลังและจำนวนสินค้าเหล่านั้นในรถเข็นช็อปปิ้ง

619dc18a8136ea69.png

ในส่วนถัดไป เราจะสร้างวิธีแสดงรายการในรถเข็นช็อปปิ้ง กลไกการตรวจสอบรถเข็นช็อปปิ้งควรแสดงสินค้าทั้งหมดในรถเข็น จำนวนสินค้าเหล่านั้น และตัวเลือกในการนำสินค้าออกจากรถเข็น

ตรวจสอบสินค้าในรถเข็น

การแสดงรายการสินค้าในรถเข็นช็อปปิ้งเป็นวิธีเดียวที่เราจะเข้าใจสถานะของรถเข็นช็อปปิ้งและทราบว่าเรานำสินค้าใดออกได้

ก่อนอื่นให้ส่งข้อความที่เป็นมิตร เช่น "นี่คือรถเข็นช็อปปิ้งของคุณ" ตามด้วยข้อความอีกข้อความที่มีภาพสไลด์ Rich Card พร้อมคำตอบที่แนะนำที่เกี่ยวข้องสำหรับ "นำออก 1 รายการ" หรือ "เพิ่ม 1 รายการ" นอกจากนี้ แคร์รอสเซลริชการ์ดควรแสดงจำนวนสินค้าที่บันทึกไว้ในรถเข็นด้วย

สิ่งที่คุณควรทราบก่อนที่เราจะไปเขียนฟังก์ชันจริงๆ ก็คือ หากมีสินค้าเพียงประเภทเดียวในรถเข็นช็อปปิ้ง เราจะแสดงเป็นภาพสไลด์ไม่ได้ ภาพหมุนริชการ์ดต้องมีการ์ดอย่างน้อย 2 ใบ ในทางกลับกัน หากไม่มีสินค้าในรถเข็น เราต้องการแสดงข้อความง่ายๆ ที่ระบุว่ารถเข็นว่างเปล่า

เมื่อทราบดังนี้แล้ว เรามากำหนดฟังก์ชันที่ชื่อ send_shopping_cart กัน ฟังก์ชันนี้เชื่อมต่อกับ Google Datastore และขอเอนทิตี ShoppingCart ตามรหัสการสนทนา เมื่อได้ข้อมูลดังกล่าวแล้ว เราจะเรียกใช้ฟังก์ชัน get_inventory_data และใช้ภาพสไลด์การ์ดริชมีเดียเพื่อรายงานสถานะของรถเข็นช็อปปิ้ง นอกจากนี้ เรายังต้องรับรหัสของผลิตภัณฑ์ตามชื่อ และสามารถประกาศฟังก์ชันเพื่อค้นหาใน Google Datastore เพื่อกำหนดค่านั้นได้ ขณะที่สร้างภาพสไลด์ เราจะเชื่อมโยงคำตอบที่แนะนำเพื่อลบหรือเพิ่มสินค้าตามรหัสผลิตภัณฑ์ได้ ข้อมูลโค้ดด้านล่างจะดำเนินการทั้งหมดนี้ คัดลอกโค้ดไปยังส่วนใดก็ได้ใน 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)

...

ตรวจสอบว่าคุณได้กำหนด CMD_SHOW_CART ไว้ที่ด้านบนของ views.py แล้ว และเรียกใช้ send_shopping_cart หากผู้ใช้ส่งข้อความที่มีคำว่า "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

ตามตรรกะที่เราแนะนำในฟังก์ชัน send_shopping_cart เมื่อคุณพิมพ์ "show-cart" เราจะแสดงข้อความที่ระบุว่าไม่มีสินค้าในรถเข็น การ์ดที่สมบูรณ์ซึ่งแสดงสินค้า 1 รายการในรถเข็น หรือภาพสไลด์ของการ์ดที่แสดงสินค้าหลายรายการ นอกจากนี้ เรายังมีคำตอบที่แนะนำ 3 รายการ ได้แก่ "ดูราคารวม" "ล้างรถเข็น" และ "ดูเมนู"

ลองใช้การเปลี่ยนแปลงโค้ดข้างต้นเพื่อทดสอบว่ารถเข็นช็อปปิ้งติดตามสินค้าที่คุณเพิ่ม และคุณสามารถตรวจสอบรถเข็นจากการสนทนาตามที่แสดงในภาพหน้าจอด้านบน คุณสามารถใช้คำสั่งนี้เพื่อนำการเปลี่ยนแปลงไปใช้จากไดเรกทอรี step-2 ที่คุณเพิ่มการเปลี่ยนแปลง

$ gcloud app deploy

เราจะสร้างฟีเจอร์ "ดูราคารวม" ในส่วนถัดไปหลังจากสร้างฟังก์ชันการทำงานเพื่อนำสินค้าออกจากรถเข็น ฟังก์ชัน get_cart_price จะทํางานคล้ายกับฟีเจอร์ "ดูรถเข็นช็อปปิ้ง" ในแง่ที่จะอ้างอิงข้อมูลใน Datastore กับไฟล์ inventory.json เพื่อแสดงราคารวมของรถเข็นช็อปปิ้ง ซึ่งจะมีประโยชน์สำหรับส่วนถัดไปของโค้ดแล็บที่เราจะผสานรวมกับการชำระเงิน

การนำสินค้าออกจากรถเข็น

สุดท้าย เราจะทำให้พฤติกรรมรถเข็นช็อปปิ้งสมบูรณ์ด้วยการเปิดตัวฟังก์ชันการนำรถเข็นออก แทนที่ฟังก์ชัน update_shopping_cart ที่มีอยู่ด้วยข้อมูลโค้ดต่อไปนี้

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

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

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

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


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

    if result is None:
        if cart_cmd == CMD_ADD_ITEM:
            entity.update({
                item_name: 1
            })
        elif cart_cmd == CMD_DEL_ITEM:
            # The user is trying to delete an item from an empty cart. Pass and skip
            pass

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

        elif cart_cmd == CMD_DEL_ITEM:
            if result.get(item_name) is None:
                # The user is trying to remove an item that's no in the shopping cart. Pass and skip
                pass
            elif result[item_name] - 1 > 0:
                result[item_name] = result[item_name] - 1
            else:
                del result[item_name]

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

การส่งข้อความยืนยัน

เมื่อผู้ใช้เพิ่มสินค้าลงในรถเข็น คุณควรส่งข้อความยืนยันเพื่อรับทราบการดำเนินการของผู้ใช้และแจ้งว่าคุณได้ประมวลผลคำขอของผู้ใช้แล้ว ซึ่งไม่เพียงช่วยกำหนดความคาดหวัง แต่ยังช่วยให้การสนทนาดำเนินต่อไปได้ด้วย

มาขยายฟังก์ชัน update_shopping_cart เพื่อให้ส่งข้อความไปยังรหัสการสนทนาที่ระบุว่ามีการเพิ่มหรือนำสินค้าออก และให้คำแนะนำในการตรวจสอบรถเข็นช็อปปิ้งหรือดูเมนูอีกครั้ง

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

def update_shopping_cart(conversation_id, message):

     # No changes to the function, except appending the following logic
     ...
   
    if cart_cmd == CMD_ADD_ITEM:
        message = 'Great! You\'ve added an item to the cart.'
    else:
        message = 'You\'ve removed an item from the cart.'

    message_obj = BusinessMessagesMessage(
        messageId=str(uuid.uuid4().int),
        representative=BOT_REPRESENTATIVE,
        text=message,
        suggestions=[
            BusinessMessagesSuggestion(
            reply=BusinessMessagesSuggestedReply(
                text='Review shopping cart',
                postbackData=CMD_SHOW_CART)
            ),
            BusinessMessagesSuggestion(
            reply=BusinessMessagesSuggestedReply(
                text='See menu again',
                postbackData=CMD_SHOW_PRODUCT_CATALOG)
            ),
            ])
    send_message(message_obj, conversation_id)

905a1f3d89893ba0.png

เท่านี้ก็เรียบร้อย ประสบการณ์รถเข็นช็อปปิ้งที่มีฟีเจอร์ครบถ้วน ซึ่งช่วยให้ผู้ใช้เพิ่ม นำออก และตรวจสอบสินค้าในรถเข็นได้

ในตอนนี้ หากต้องการดูฟังก์ชันรถเข็นช็อปปิ้งในการสนทนา Business Messages ให้ติดตั้งใช้งานแอปพลิเคชันเพื่อโต้ตอบกับเอเจนต์ โดยทำได้โดยการเรียกใช้คำสั่งนี้ในไดเรกทอรี step-2

$ gcloud app deploy

5. การเตรียมพร้อมสำหรับการชำระเงิน

ในการเตรียมพร้อมสำหรับการผสานรวมกับผู้ประมวลผลการชำระเงินในส่วนถัดไปของชุดบทความนี้ เราจำเป็นต้องมีวิธีรับราคาของรถเข็นช็อปปิ้ง มาสร้างฟังก์ชันที่ดึงราคาให้เราโดยการอ้างอิงโยงข้อมูลรถเข็นช็อปปิ้งใน Google Datastore ดึงราคาของสินค้าแต่ละรายการจากสินค้าคงคลัง และคูณราคาด้วยจำนวนสินค้าแต่ละรายการในรถเข็น

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

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

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

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

    return total_price

...

และสุดท้าย เราสามารถใช้ฟังก์ชันนั้นและส่งข้อความถึงผู้ใช้ได้

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

...

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

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

    send_message(message_obj, conversation_id)
...

เรามาอัปเดตฟังก์ชัน route_message และค่าคงที่เพื่อทริกเกอร์ตรรกะข้างต้นกัน

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

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

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

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

ภาพหน้าจอบางส่วนที่แสดงให้เห็นว่าตรรกะข้างต้นทําอะไรได้บ้างมีดังนี้

8feacf94ed0ac6c4.png

เมื่อพร้อมที่จะผสานรวมกับผู้ประมวลผลการชำระเงินในส่วนถัดไปของ Codelab เราจะเรียกใช้ฟังก์ชัน get_cart_price เพื่อส่งข้อมูลไปยังผู้ประมวลผลการชำระเงินและเริ่มขั้นตอนการชำระเงิน

คุณลองใช้ฟังก์ชันรถเข็นช็อปปิ้งนี้ในการสนทนา Business Messages ได้อีกครั้งโดยการติดตั้งใช้งานแอปพลิเคชันและโต้ตอบกับตัวแทน

$ gcloud app deploy

6. ขอแสดงความยินดี

ยินดีด้วย คุณสร้างประสบการณ์รถเข็นช็อปปิ้งภายใน Business Messages ได้สำเร็จแล้ว

ฟีเจอร์ในการล้างรถเข็นช็อปปิ้งทั้งหมดเป็นสิ่งที่เราไม่ได้กล่าวถึงในโค้ดแล็บนี้ หากต้องการ ลองขยายแอปพลิเคชันเพื่อตอบสนองฟีเจอร์ "ล้างรถเข็น" โซลูชันนี้อยู่ในขั้นตอนที่ 3 ของซอร์สโค้ดที่คุณโคลน

ในส่วนถัดไป เราจะผสานรวมกับผู้ประมวลผลการชำระเงินภายนอกเพื่อให้ผู้ใช้ทำธุรกรรมการชำระเงินกับแบรนด์ของคุณได้

รถเข็นช็อปปิ้งที่ดีควรเป็นอย่างไร

ประสบการณ์รถเข็นช็อปปิ้งที่ดีในการสนทนาไม่แตกต่างจากแอปบนอุปกรณ์เคลื่อนที่หรือในร้านค้าจริง การเพิ่มสินค้า การนำสินค้าออก และการคำนวณราคารถเข็นเป็นเพียงฟีเจอร์บางส่วนที่เราได้สำรวจในโค้ดแล็บนี้ สิ่งที่แตกต่างจากรถเข็นช็อปปิ้งในโลกแห่งความเป็นจริงคือการดูราคาของสินค้าทั้งหมดในรถเข็นได้ทุกเมื่อที่คุณเพิ่มหรือนำสินค้าออก ฟีเจอร์ที่มีคุณค่าสูงเหล่านี้จะช่วยให้ประสบการณ์การสนทนาเพื่อการพาณิชย์ของคุณโดดเด่น

สิ่งต่อไปที่ควรทำ

เมื่อพร้อมแล้ว โปรดดูหัวข้อต่อไปนี้เพื่อเรียนรู้เกี่ยวกับการโต้ตอบที่ซับซ้อนมากขึ้นซึ่งคุณทำได้ใน Business Messages

เอกสารอ้างอิง