קנייה של איסוף באינטרנט בחנות: ארוחת בונז'ור – חלק 2 – בניית עגלת קניות

1. מבוא

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

העדכון האחרון: 2020-10-30

איך יוצרים עגלת קניות ב-Business Messages

זוהי Codelab השנייה בסדרה שמטרתה ליצור מסלול משתמשים בתכונה 'קנייה באינטרנט ואיסוף בחנות'. במסלולי רכישה רבים במסחר אלקטרוני, עגלת קניות היא מרכיב מרכזי בהמרת משתמשים ללקוחות משלמים. עגלת הקניות מאפשרת לכם להבין טוב יותר את הלקוחות שלכם ולהציע להם הצעות למוצרים נוספים שעשויים לעניין אותם. ב-Codelab זה נתמקד בבניית חוויית השימוש בעגלת הקניות ובפריסת האפליקציה ב-Google App Engine.

מה הופך עגלת קניות לטובה?

עגלות קניות הן מרכיב מרכזי בחוויית קנייה מוצלחת באינטרנט. מסתבר ש-Business Messages לא רק מאפשר לנהל שיחות עם לקוחות פוטנציאליים כדי לענות על שאלות לגבי מוצר, אלא גם מאפשר לנהל את כל תהליך הקנייה עד להשלמת התשלום בתוך השיחה.

9d17537b980d0e62.png

בנוסף לעגלת קניות טובה, חוויית קנייה טובה מאפשרת למשתמשים לעיין בפריטים לפי קטגוריה, ומאפשרת לעסק להמליץ על מוצרים אחרים שעשויים לעניין את הקונה. אחרי שהמשתמש מוסיף עוד פריטים לעגלת הקניות, הוא יכול לבדוק את כל הפריטים בעגלה, להסיר פריטים או להוסיף עוד פריטים לפני שהוא משלם.

מה תפַתחו

בקטע הזה בסדרת ה-codelab, נרחיב את הסוכן הדיגיטלי שיצרנו בחלק 1 עבור החברה הבדיונית Bonjour Meal, כדי שהמשתמשים יוכלו לעיין בקטלוג פריטים ולהוסיף פריטים לעגלת קניות.

ב-Codelab הזה, האפליקציה שלכם

  • הצגת קטלוג שאלות ב-Business Messages
  • הצעת פריטים שעשויים לעניין את המשתמשים
  • בודקים את התוכן של עגלת הקניות ויוצרים סיכום של המחיר הכולל

ab2fb6a4ed33a129.png

מה תלמדו

  • איך פורסים אפליקציית אינטרנט ב-App Engine ב-Google Cloud Platform
  • איך משתמשים במנגנון אחסון מתמשך כדי לשמור את המצב של עגלת קניות

ה-Codelab הזה מתמקד בהרחבת הנציג הדיגיטלי מחלק 1 בסדרת ה-Codelab הזו.

מה תצטרכו

2. תהליך ההגדרה

ב-codelab הזה אנחנו מניחים שיצרתם את הסוכן הראשון שלכם והשלמתם את חלק 1 של ה-codelab. לכן, לא נסביר כאן איך להפעיל את ממשקי Business Messages ו-Business Communications API, איך ליצור מפתחות לחשבון שירות, איך לפרוס אפליקציה או איך להגדיר את ה-webhook במסוף Business Communications. עם זאת, אנחנו נשכפל אפליקציה לדוגמה כדי לוודא שהאפליקציה שלכם עקבית עם מה שאנחנו בונים על בסיס, ונוכל להפעיל את ה-API של Datastore ב-Google Cloud Platform כדי לשמור נתונים שקשורים לעגלת הקניות.

שיבוט האפליקציה מ-GitHub

בטרמינל, משכפלים את הדוגמה של בוט Django Echo לספריית העבודה של הפרויקט באמצעות הפקודה הבאה:

$ 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 של ה-codelab הזה הפעלתם את Business Messages API,‏ Business communications API ו-Cloud Build API.

ב-codelab הזה אנחנו עובדים עם Google Datastore, ולכן אנחנו צריכים להפעיל גם את ה-API הזה:

  1. פותחים את Google Datastore API במסוף Google Cloud.
  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]

הקוד שפרסתם עכשיו מכיל אפליקציית אינטרנט עם webhook לקבלת הודעות מ-Business Messages. הוא מכיל את כל מה שעשינו בחלק 1 של ה-Codelab. אם עוד לא עשיתם זאת, עליכם להגדיר את ה-webhook.

האפליקציה תגיב לכמה פניות פשוטות, כמו משתמש ששואל מהן שעות הפעילות של Bonjour Meal. כדאי לבדוק את זה במכשיר הנייד באמצעות כתובות ה-URL לבדיקה שאפשר לאחזר מפרטי הנציג ב-Business Communications Console. כתובות ה-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
...

הפעולה הזו תספק לנו את מה שצריך כדי לקרוא את הנתונים מהמלאי. עכשיו אנחנו צריכים דרך להציג את פרטי המוצר בשיחה.

הצגת קטלוג המוצרים

כדי לפשט את התהליך ב-Codelab הזה, יש לנו קטלוג מוצרים כללי שמאפשר להציג את כל פריטי המלאי בשיחה ב-Business Messages באמצעות קרוסלה אחת של כרטיסים עשירים.

כדי להציג את קטלוג המוצרים, אנחנו ניצור הצעה לתגובה עם הטקסט 'הצגת התפריט' ועם postbackData ‏'show-product-catalog'. כשהמשתמשים יקישו על ההצעה לתגובה ואפליקציית האינטרנט שלכם תקבל את נתוני הפוסטבק, אנחנו נשלח את קרוסלת הכרטיסים העשירים. נוסיף קבוע חדש לתשובה המוצעת הזו בחלק העליון של הקובץ 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 calls get_menu_carousel, שמייצר את קרוסלת הכרטיסים העשירים מקובץ המלאי שקראנו קודם.

אפשר למקם את הגדרות הפונקציות בכל מקום בקובץ views.py. שימו לב שבקטע הקוד הבא נעשה שימוש בשני קבועים חדשים שצריך להוסיף לחלק העליון של הקובץ.

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

...

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

...

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

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

    inventory = get_inventory_data()

    card_content = []

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

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

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

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

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

    fallback_text = ''

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

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

    send_message(message_obj, conversation_id)
...

אם בודקים את יצירת הפריטים בקרוסלה, רואים שנוצר גם מופע של המחלקה BusinessMessagesSuggestion. כל הצעה מייצגת בחירה של משתמש במוצר בקרוסלה. כשמשתמש לוחץ על התשובה המוצעת, פרוטוקול Business Messages שולח את הנתונים של הפוסטבק שכוללים JSON עם תיאור של הפריט והפעולה שהמשתמש רוצה לבצע (הוספה לעגלת הקניות או הסרה ממנה) אל ה-webhook. בקטע הבא ננתח הודעות שנראות כך כדי שנוכל להוסיף את הפריט לעגלה.

עכשיו, אחרי שביצענו את השינויים האלה, נפרס את אפליקציית האינטרנט ב-Google App Engine וננסה את החוויה!

$ gcloud app deploy

אחרי שהמשטח לשיחה נטען בנייד, שולחים את ההודעה show-product-catalog. אמורה להופיע קרוסלה של מוצרים שנראית כך.

4639da46bcc5230c.png

אם תקישו על הוספת פריט, לא יקרה שום דבר בפועל, מלבד העובדה שה-Agent ישחזר את נתוני הדיווח החוזר על המרה (PostBack) מהתשובה המוצעת. בקטע הבא נשתמש בקטלוג המוצרים כדי ליצור את עגלת הקניות שאליה יתווסף הפריט.

יש מגוון דרכים להרחיב את קטלוג המוצרים שיצרתם. יכול להיות שיהיו לכם אפשרויות שונות בתפריט המשקאות, או אפשרויות צמחוניות. שימוש בקרוסלות או בצ'יפים של הצעות הוא דרך מצוינת לאפשר למשתמשים להתעמק באפשרויות התפריט כדי להגיע לסט של מוצרים שהם מחפשים. כדי להרחיב את ה-Codelab הזה, נסו להרחיב את מערכת קטלוג המוצרים כך שמשתמש יוכל לראות את המשקאות בנפרד מהאוכל בתפריט, או אפילו לציין אפשרויות צמחוניות.

4. עגלת הקניות

בקטע הזה של ה-codelab, נבנה את הפונקציונליות של עגלת הקניות על בסיס הקטע הקודם, שמאפשר לנו לעיין במוצרים הזמינים.

בין היתר, חוויית השימוש בעגלת הקניות מאפשרת למשתמשים להוסיף פריטים לעגלה, להסיר פריטים מהעגלה, לעקוב אחרי מספר הפריטים מכל סוג בעגלה ולעיין בפריטים בעגלה.

כדי לעקוב אחרי מצב עגלת הקניות, אנחנו צריכים לשמור נתונים באפליקציית האינטרנט. כדי לפשט את תהליך הניסוי והפריסה, נשתמש ב-Google Datastore ב-Google Cloud Platform כדי לשמור את הנתונים. מזהה השיחה נשאר קבוע בין המשתמש לעסק, כך שאפשר להשתמש בו כדי לשייך משתמשים לפריטים בעגלת הקניות.

נתחיל בחיבור ל-Google 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)

בואו נרחיב את הפונקציה הזו כדי להוסיף פריט לעגלת הקניות.

הוספת פריטים לעגלת הקניות

כשהמשתמש מקיש על ההצעה לפעולה הוספת פריט בקרוסלת המוצרים, הנתונים של הפוסטבק מכילים JSON שמתאר את הפעולה שהמשתמש רוצה לבצע. מילון ה-JSON מכיל שני מפתחות: 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 תופעל. תצטרכו גם להגדיר קבוע להוספת פריטים באמצעות המוסכמה שבה אנחנו משתמשים לאורך כל ה-codelab.

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 Console. בצילום המסך של מסוף Google Datastore שמופיע למטה, יש ישות אחת שנקראת על שם מזהה השיחה, ואחריה כמה קשרים לפריטים במלאי ולכמות הפריטים האלה שנמצאים בעגלת הקניות.

619dc18a8136ea69.png

בקטע הבא ניצור דרך להצגת הפריטים בעגלת הקניות. מנגנון הבדיקה של עגלת הקניות צריך להציג לנו את כל הפריטים בעגלה, את הכמות של הפריטים האלה ואפשרות להסיר פריט מהעגלה.

בדיקת הפריטים בעגלת הקניות

הדרך היחידה שבה אנחנו יכולים להבין את מצב עגלת הקניות ולדעת אילו פריטים אפשר להסיר היא באמצעות רשימה של הפריטים בעגלת הקניות.

קודם נשלח הודעה ידידותית כמו "זו עגלת הקניות שלך:", ואחריה עוד הודעה שמכילה קרוסלת כרטיסים עשירה עם תשובות מוצעות שקשורות אליה כמו "הסרת פריט אחד" או "הוספת פריט אחד". בנוסף, בקרוסלת הכרטיסים המתקדמים צריך להופיע מספר הפריטים שנשמרו בעגלת הקניות.

חשוב לדעת לפני שמתחילים לכתוב את הפונקציה: אם יש רק סוג אחד של פריט בעגלת הקניות, אי אפשר להציג אותו כקרוסלה. קרוסלות של כרטיסים עשירים חייבות להכיל לפחות שני כרטיסים. מצד שני, אם אין פריטים בעגלת הקניות, אנחנו רוצים להציג הודעה פשוטה שאומרת שהעגלה ריקה.

עם זאת, נגדיר פונקציה בשם send_shopping_cart. הפונקציה הזו מתחברת ל-Google Datastore ומבקשת ישות של עגלת קניות על סמך מזהה השיחה. אחרי שנקבל את הנתונים האלה, נקרא לפונקציה 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 וקוראים ל-CMD_SHOW_CART אם המשתמש שולח הודעה שמכילה את המחרוזת show-cart.send_shopping_cart

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

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

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

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

34801776a97056ac.png

על סמך הלוגיקה שהגדרנו בפונקציה send_shopping_cart, כשמקלידים 'show-cart', נקבל הודעה שאומרת שאין שום דבר בעגלה, כרטיס עשיר שמציג את הפריט היחיד בעגלה או קרוסלה של כרטיסים שמציגה כמה פריטים. בנוסף, יש לנו שלוש הצעות לתשובות: 'הצגת המחיר הכולל', 'ריקון העגלה' ו'הצגת התפריט'.

כדאי לנסות להטמיע את השינויים בקוד שצוינו למעלה כדי לבדוק שמערכת עגלת הקניות עוקבת אחרי הפריטים שאתם מוסיפים, ושאתם יכולים לבדוק את עגלת הקניות בממשק השיחות, כמו שמוצג בצילומי המסך שלמעלה. אפשר לפרוס את השינויים באמצעות הפעלת הפקודה הזו מהספרייה step-2 שבה מוסיפים את השינויים.

$ gcloud app deploy

אנחנו נבנה את התכונה 'הצגת המחיר הכולל' בקטע הבא אחרי שנבנה את הפונקציונליות להסרת פריט מהעגלה. הפונקציה get_cart_price תפעל באופן דומה לתכונה 'הצגת עגלת הקניות', כלומר היא תבצע הצלבת נתונים ב-Datastore עם הקובץ inventory.json כדי להפיק מחיר כולל לעגלת הקניות. הפעולה הזו תהיה שימושית בחלק הבא של ה-codelab, שבו נשלב את התכונה עם תשלומים.

הסרת פריטים מעגלת הקניות

לבסוף, אנחנו יכולים להשלים את התנהגות עגלת הקניות על ידי הוספת פונקציונליות להסרת העגלה. מחליפים את הפונקציה הקיימת 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.

ב-codelab הזה לא הסברנו על התכונה של ריקון עגלת הקניות. אם רוצים, אפשר לנסות להרחיב את האפליקציה כדי לממש את התכונה 'ריקון עגלת הקניות'. הפתרון זמין בשלב 3 של קוד המקור ששיבטתם.

בקטע הבא נסביר איך לשלב מעבד תשלומים חיצוני כדי לאפשר למשתמשים להשלים עסקת תשלום עם המותג שלכם.

מה הופך עגלת קניות לטובה?

חוויית קנייה טובה בעגלת קניות בשיחה לא שונה מחוויית קנייה באפליקציה לנייד או בחנות פיזית. היכולת להוסיף פריטים, להסיר פריטים ולחשב את המחיר של עגלת הקניות הן רק חלק מהתכונות שבדקנו ב-codelab הזה. ההבדל בין עגלת קניות וירטואלית לבין עגלת קניות בעולם האמיתי הוא שבעגלת קניות וירטואלית אפשר לראות את המחיר של כל הפריטים בכל רגע נתון, כשמוסיפים או מסירים פריטים. תכונות כאלה בעלות ערך גבוה יגרמו לחוויית המסחר השיחתי שלכם להתבלט!

מה השלב הבא?

כשאתם מוכנים, כדאי לעיין בנושאים הבאים כדי לקבל מידע על אינטראקציות מורכבות יותר שאפשר להשיג ב-Business Messages:

מאמרי עזרה