1. مقدمة
تاريخ آخر تعديل: 30 تشرين الأول (أكتوبر) 2020
إنشاء سلة تسوّق في ميزة "الرسائل التجارية"
هذا هو ورشة العمل الثانية في سلسلة تهدف إلى إنشاء رحلة مستخدِم لخدمة "الشراء على الإنترنت واستلام الطلب من المتجر". في العديد من مسارات التجارة الإلكترونية، تشكّل سلة التسوّق عاملاً رئيسيًا لنجاح تحويل المستخدِمين إلى عملاء يدفعون رسومًا. تُعدّ سلة التسوّق أيضًا طريقة لفهم عملائك بشكل أفضل وتقديم اقتراحات بشأن سلع أخرى قد تهمّهم. في هذا الدليل التعليمي حول رموز البرامج، سنركّز على إنشاء تجربة سلة التسوّق ونشر التطبيق على Google App Engine.
ما هي العوامل التي تجعل سلة التسوّق جيدة؟
تُعدّ سلال التسوّق عاملاً أساسيًا لنجاح تجربة التسوّق على الإنترنت. تبيّن أنّ ميزة "الرسائل التجارية" ليست جيدة فقط في تسهيل إجراء أسئلة وأجوبة حول منتج مع عميل محتمل، بل يمكنها أيضًا تسهيل تجربة التسوّق بالكامل حتى إكمال عملية الدفع خلال المحادثة.
بالإضافة إلى توفير سلة تسوّق جيدة، تسمح تجربة التسوّق الجيدة للمستخدمين بتصفّح السلع حسب الفئة، كما تتيح للنشاط التجاري اقتراح منتجات أخرى قد تهمّ المشتري. بعد إضافة المزيد من السلع إلى سلة التسوّق، يمكن للمستخدم مراجعة سلّته بالكامل، وسيكون بإمكانه إزالة سلع أو إضافة المزيد منها قبل الدفع.
التطبيق الذي ستصممه
في هذا القسم من سلسلة "مختبر الرموز البرمجية"، ستوسّع نطاق وكيل الدعم الرقمي الذي أنشأته في الجزء 1 للشركة الخيالية Bonjour Meal، حتى يتمكّن المستخدمون من تصفُّح قائمة سلع وإضافة سلع إلى سلة تسوّق.
في هذا الدرس التطبيقي حول الترميز، سينفّذ تطبيقك ما يلي:
- عرض كتالوج من الأسئلة ضمن ميزة "الرسائل التجارية"
- اقتراح عناصر قد تهمّ المستخدمين
- مراجعة محتوى سلة التسوّق وإنشاء ملخّص لإجمالي السعر
ما ستتعرّف عليه
- كيفية نشر تطبيق ويب على App Engine على Google Cloud Platform
- كيفية استخدام آلية تخزين دائم لحفظ حالة سلة تسوّق
يركز هذا الدرس التطبيقي حول الترميز على توسيع نطاق عمل الوكيل الرقمي من الجزء 1 من سلسلة الدروس التطبيقية حول الترميز هذه.
المتطلبات
- مشروع على Google Cloud Platform تم تسجيله والموافقة على استخدامه مع ميزة "الرسائل التجارية"
- يُرجى الانتقال إلى الموقع الإلكتروني للمطوّرين للحصول على تعليمات حول كيفية
- ملف بيانات اعتماد JSON لحساب الخدمة تم إنشاؤه لمشروعك على Google Cloud Platform
- جهاز Android يعمل بالإصدار 5 أو إصدار أحدث أو جهاز iOS مزوّد بتطبيق "خرائط Google"
- خبرة في برمجة تطبيقات الويب
- اتصال بالإنترنت
2. الإعداد
يفترض هذا الدليل التعليمي أنّك أنشأت وكيلك الأول وأكملت الجزء 1 من الدليل التعليمي. وبناءً على ذلك، لن نتناول أساسيات تفعيل واجهتَي برمجة التطبيقات Business Messages API وBusiness Communications API أو إنشاء مفاتيح حساب الخدمة أو نشر تطبيق أو إعداد رابط webhook في "وحدة تحكّم الاتصالات التجارية". ومع ذلك، سننسخ نموذج تطبيق للتأكّد من أنّ تطبيقك متوافق مع ما نبنيه عليه، وسنفعّل واجهة برمجة التطبيقات Datastore على Google Cloud Platform لنتمكّن من الاحتفاظ بالبيانات المتعلّقة بسلة التسوّق.
استنساخ التطبيق من GitHub
في وحدة طرفية، يمكنك استنساخ نموذج Django Echo Bot إلى دليل العمل في مشروعك باستخدام الأمر التالي:
$ 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، علينا أيضًا تفعيل واجهة برمجة التطبيقات هذه:
- افتح Google Datastore API في Google Cloud Console.
- تأكَّد من استخدام مشروع Google Cloud Platform الصحيح.
- انقر على تفعيل.
نشر نموذج التطبيق
في وحدة طرفية، انتقِل إلى دليل الخطوة 2 الخاص بالعيّنة.
لتنفيذ النموذج، نفِّذ الأوامر التالية في وحدة طرفية:
$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
- PROJECT_ID هو رقم تعريف المشروع الذي استخدمته للتسجيل في واجهات برمجة التطبيقات.
يُرجى ملاحظة عنوان URL للتطبيق الذي تم نشره في ناتج الأمر الأخير:
Deployed service [default] to [https://PROJECT_ID.appspot.com]
يحتوي الرمز الذي تم نشره للتو على تطبيق ويب يتضمّن ردًا تلقائيًا على الويب لتلقّي الرسائل من ميزة "الرسائل التجارية". يحتوي على كل ما فعلناه في الجزء 1 من ورشة رموز البرامج. يُرجى ضبط رابط البيانات في الخادم إذا لم يسبق لك إجراء ذلك.
سيردّ التطبيق على بعض الاستفسارات البسيطة، مثل سؤال يطرحه مستخدم عن ساعات عمل مطعم Bonjour Meal. يجب اختبار ذلك على جهازك الجوّال من خلال عناوين URL الاختبارية التي يمكنك استردادها من "معلومات موظّف الدعم" ضمن "وحدة تحكّم اتصالات النشاط التجاري". ستؤدي عناوين URL التجريبية إلى تشغيل تجربة "الرسائل التجارية" على جهازك الجوّال، ويمكنك بدء التفاعل مع موظّف الدعم من هناك.
3- كتالوج المنتجات
نظام مستودع
في معظم الحالات، يمكنك الدمج مباشرةً مع مستودع إحدى العلامات التجارية من خلال واجهة برمجة تطبيقات داخلية. في حالات أخرى، يمكنك استخراج البيانات من صفحة ويب أو إنشاء نظام تتبُّع المستودع الخاص بك. لا نركّز على إنشاء نظام مستودع، بل سنستخدم ملفًا ثابتًا بسيطًا يحتوي على صور ومعلومات المنتجات لعرضها على موظّف الدعم. في هذا القسم، سنسحب المعلومات من هذا الملف الثابت، وسنعرض هذه المعلومات في المحادثة، ونسمح للمستخدم بتصفّح السلع المتاحة لإضافتها إلى سلة تسوّق.
يظهر ملف المستودع الثابت على النحو التالي:
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
...
من المفترض أن يمنحنا ذلك ما نحتاجه لقراءة البيانات من المستودع. نحتاج الآن إلى طريقة لعرض معلومات المنتج هذه في المحادثة.
عرض كتالوج المنتجات
لتبسيط هذا الدليل التعليمي حول الرموز البرمجية، لدينا كتالوج منتجات عام لعرض جميع سلع المستودع في محادثة "الرسائل التجارية" من خلال لوحة عرض دوّارة واحدة لبطاقات مليئة بالمحتوى.
لعرض قائمة المنتجات، سننشئ ردًا مقترَحًا يتضمّن النص "عرض القائمة" و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
دالة 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
. يمثّل كل اقتراح منتجًا اختاره المستخدم في لوحة العرض الدوّارة. عندما ينقر المستخدم على الردّ المقترَح، ستُرسِل ميزة "الرسائل التجارية" إلى رابط الويب الخاص بك مَعلمة postbackData التي تحتوي على ملف JSON يصف السلعة والإجراء الذي يريد المستخدم اتّخاذه (إضافة إلى سلة التسوّق أو إزالتها منها). في القسم التالي، سنحلّل الرسائل التي تبدو على هذا النحو لنتمكّن من إضافة السلعة إلى سلة التسوق.
بعد إجراء هذه التغييرات، لننشر تطبيق الويب على Google App Engine ونختبر التجربة.
$ gcloud app deploy
بعد تحميل مساحة المحادثة على جهازك الجوّال، أرسِل الرسالة "show-product-catalog"، ومن المفترض أن تظهر لك لوحة عرض دوّارة للمنتجات بالشكل التالي.
إذا نقرت على إضافة عنصر، لن يحدث أيّ شيء سوى أن يكرّر موظّف الدّعم بيانات تسجيل الإحالات الناجحة من الردّ المقترَح. في القسم التالي، سنستفيد من كتالوج المنتجات ونستخدمه لإنشاء سلة التسوّق التي ستتم إضافة السلعة إليها.
يمكن توسيع نطاق كتالوج المنتجات الذي أنشأته للتو بعدة طرق. قد يكون لديك خيارات مختلفة لقائمة المشروبات أو خيارات نباتية. يُعدّ استخدام لوحات العرض الدوّارة أو شرائح الاقتراحات طريقة رائعة للسماح للمستخدمين بالتوغّل في خيارات القائمة للوصول إلى مجموعة من المنتجات التي يبحثون عنها. كإضافة إلى هذا الدليل التعليمي، جرِّب توسيع نطاق نظام قائمة المنتجات ليتمكّن المستخدم من عرض المشروبات بشكل منفصل عن الطعام في القائمة، أو حتى تحديد الخيارات النباتية.
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 على مفتاحَين، هما "action" و "item_name"، ويتم إرسال معجم JSON هذا إلى رابط الويب الخاص بخدمة Webhook. حقل item_name هو المعرّف الفريد المرتبط بالعنصر في ملف inventory.json.
بعد الحصول على أمر سلة التسوّق وتحليل سلة التسوّق من الرسالة، يمكننا بعد ذلك كتابة عبارات شرطية لإضافة السلعة. في ما يلي بعض الحالات الشاذة التي يجب أخذها في الاعتبار: إذا لم يسبق لـ "مستودع البيانات" الاطّلاع على معرّف المحادثة أو إذا كانت سلّة التسوّق تتلقّى هذا العنصر للمرّة الأولى. في ما يلي إضافة إلى وظيفة 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 المتوفّرة في وحدة تحكّم Google Cloud Platform. اطّلِع على لقطة الشاشة أدناه لمحطة تحكّم Google Datastore، حيث يتوفّر عنصر واحد تم تسميته باسم معرّف المحادثة متبوعًا ببعض العلاقات بعناصر المستودع وكمية هذه العناصر المتوفّرة في سلة التسوّق.
في القسم التالي، سننشئ طريقة لعرض السلع في سلة التسوّق. من المفترض أن تعرض لنا آلية مراجعة سلة التسوّق جميع السلع في سلة التسوّق وكميتها وخيار إزالة سلعة من سلة التسوّق.
مراجعة السلع في سلة التسوّق
إنّ إدراج السلع في سلة التسوّق هو الطريقة الوحيدة التي يمكننا من خلالها فهم حالة سلة التسوّق ومعرفة السلع التي يمكننا إزالتها.
لنرسل أولاً رسالة ودية مثل "إليك سلة التسوّق:"، ثم رسالة أخرى تتضمّن لوحة دوار للبطاقات التفاعلية مع الردود المقترَحة المرتبطة بها "إزالة سلعة" أو "إضافة سلعة". يجب أن يعرض عرض البطاقات الدوّارة المفصّل أيضًا كمية السلع المحفوظة في سلة التسوّق.
هناك ملاحظة يجب أخذها في الاعتبار قبل الانتقال إلى كتابة الدالة: إذا كان هناك نوع واحد فقط من السلع في سلة التسوّق، لا يمكننا عرضها كعرض دوار. يجب أن تحتوي لوحات العرض الدوّارة للبطاقات الغنية على بطاقتَين على الأقل. في المقابل، إذا لم تكن هناك أي سلع في سلة التسوق، نريد عرض رسالة بسيطة تفيد بأنّ سلة التسوق فارغة.
مع أخذ ذلك في الاعتبار، لنحدِّد دالة تُسمى 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)
...
استنادًا إلى المنطق الذي قدّمناه في دالة send_shopping_cart
، عند كتابة "show-cart"، ستظهر لنا رسالة تفيد بعدم توفّر أي سلع في سلة التسوّق، أو بطاقة غنية تعرض سلعة واحدة في سلة التسوّق، أو لوحة عرض دوّارة لبطاقات تعرض سلعًا متعددة. بالإضافة إلى ذلك، لدينا ثلاثة ردود مقترَحة: "عرض السعر الإجمالي" و"إفراغ سلة التسوّق" و "عرض القائمة".
حاوِل نشر التغييرات على الرمز أعلاه لاختبار ما إذا كانت سلة التسوّق تتتبّع السلع التي تضيفها وما إذا كان بإمكانك مراجعة سلة التسوّق من واجهة المحادثات كما هو موضّح في لقطات الشاشة أعلاه. يمكنك نشر التغييرات باستخدام هذا الأمر الذي يتم تشغيله من الدليل 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)
من المفترض أن يؤدي ذلك إلى حلّ المشكلة. تجربة سلة تسوّق كاملة الميزات تتيح للمستخدم إضافة سلع وإزالتها ومراجعتها في سلة التسوّق
في هذه المرحلة، إذا أردت الاطّلاع على وظيفة سلة التسوّق في محادثة "الرسائل التجارية"، يمكنك نشر التطبيق للتفاعل مع موظّف الدعم. ويمكنك إجراء ذلك من خلال تنفيذ هذا الأمر في الدليل 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)
...
في ما يلي بعض لقطات الشاشة التي توضّح ما يحقّقه المنطق أعلاه:
عندما نكون جاهزين للدمج مع جهة معالجة المعاملات في الجزء التالي من ورشة رموز البرامج، سنستدعي الدالة get_cart_price
لتمرير البيانات إلى جهة معالجة المعاملات وبدء عملية الدفع.
مرة أخرى، يمكنك تجربة وظيفة سلة التسوّق هذه في محادثة "الرسائل التجارية" من خلال نشر التطبيق والتفاعل مع موظّف الدعم.
$ gcloud app deploy
6- تهانينا
تهانينا، لقد أنشأت بنجاح تجربة سلة تسوّق في ميزة "الرسائل التجارية".
لم نتطرّق في هذا الدليل التعليمي إلى ميزة إفراغ سلة التسوّق بالكامل. يمكنك محاولة تمديد التطبيق لتفعيل ميزة "إفراغ سلة التسوّق". يتوفّر الحلّ في الخطوة 3 من رمز المصدر الذي نسخته.
في قسم مستقبلي، سندمج مع معالج دفع خارجي لنتمكّن من السماح للمستخدمين بإكمال معاملة دفع مع علامتك التجارية.
ما هي العوامل التي تجعل سلة التسوّق جيدة؟
لا تختلف تجربة سلة التسوّق الجيدة في المحادثة عن تجربة استخدام تطبيق جوّال أو متجر عادي. إنّ إمكانية إضافة سلع وإزالتها واحتساب سعر سلة التسوّق هي بعض الميزات التي استكشَفناها في هذا الدليل التعليمي حول رموز البرامج. يختلف هذا الإجراء عن سلة التسوّق في العالم الواقعي، حيث يمكنك الاطّلاع على سعر كل السلع في سلة التسوّق في أي وقت، أثناء إضافة سلع أو إزالتها. ستجعل هذه الأنواع من الميزات العالية القيمة تجربة التجارة الحوارية مميزة.
الخطوة التالية
عندما تكون مستعدًا، اطّلِع على بعض المواضيع التالية للتعرّف على التفاعلات الأكثر تعقيدًا التي يمكنك إجراؤها في ميزة "الرسائل التجارية":
- آلية عمل ميزة "الرسائل التجارية"
- أفضل الممارسات
- إرشادات حول الشعارات
- تحويل المحادثة إلى موظّف دعم مباشر
مستندات المرجع
- SuggestedReply
- مستند مرجعي لرسائل "الرسائل التجارية"
- تعريف JSON لعنصر RichCard