۱. مقدمه


آخرین بهروزرسانی: 2020-10-30
ساخت سبد خرید در پیامهای تجاری!
این دومین آزمایشگاه کد از مجموعه ای است که با هدف ایجاد یک تجربه کاربری خرید آنلاین و تحویل حضوری در فروشگاه انجام میشود. در بسیاری از سفرهای تجارت الکترونیک، سبد خرید کلید موفقیت در تبدیل کاربران به مشتریان پرداخت کننده است. سبد خرید همچنین راهی برای درک بهتر مشتریان شما و راهی برای ارائه پیشنهادات در مورد سایر مواردی است که ممکن است به آنها علاقه مند باشند. در این آزمایشگاه کد، ما بر ایجاد تجربه سبد خرید و استقرار برنامه در Google App Engine تمرکز خواهیم کرد.
یک سبد خرید خوب چه ویژگیهایی دارد؟
سبدهای خرید کلید یک تجربه خرید آنلاین موفق هستند. همانطور که مشخص شد، پیامهای تجاری نه تنها در تسهیل پرسش و پاسخ در مورد یک محصول با مشتری بالقوه خوب هستند، بلکه میتوانند کل تجربه خرید را از طریق تکمیل پرداخت در مکالمه تسهیل کنند.

فراتر از یک سبد خرید خوب، یک تجربه خرید خوب به کاربران این امکان را میدهد که اقلام را بر اساس دستهبندی مرور کنند و به کسب و کار اجازه میدهد محصولات دیگری را که ممکن است خریدار به آنها علاقهمند باشد، توصیه کند. پس از افزودن اقلام بیشتر به سبد خرید، کاربر میتواند کل سبد خرید خود را بررسی کند و قبل از پرداخت، اقلام را حذف یا اضافه کند.
آنچه خواهید ساخت
در این بخش از مجموعه codelab، شما قصد دارید عامل دیجیتالی که در بخش 1 برای شرکت فرضی Bonjour Meal ساختید را گسترش دهید، به طوری که کاربران بتوانند فهرستی از اقلام را مرور کرده و اقلامی را به سبد خرید اضافه کنند.
در این آزمایشگاه کد، برنامه شما
- نمایش فهرستی از سوالات در پیامهای تجاری
- مواردی را که ممکن است کاربران به آنها علاقه داشته باشند پیشنهاد دهید
- محتویات سبد خرید را بررسی کنید و خلاصهای از قیمت کل ایجاد کنید

آنچه یاد خواهید گرفت
- نحوه استقرار یک برنامه وب در App Engine در پلتفرم ابری گوگل
- نحوه استفاده از مکانیزم ذخیرهسازی پایدار برای ذخیره وضعیت سبد خرید
این آزمایشگاه کد بر گسترش عامل دیجیتال از بخش ۱ این مجموعه آزمایشگاه کد تمرکز دارد.
آنچه نیاز دارید
- یک پروژه GCP که برای استفاده با پیامهای تجاری ثبت و تأیید شده است
- برای دستورالعملهای مربوط به نحوهی انجام این کار، به سایت توسعهدهندگان ما مراجعه کنید.
- یک فایل اعتبارنامه JSON حساب کاربری سرویس که برای پروژه GCP شما ایجاد شده است
- یک دستگاه اندروید ۵+ یا یک دستگاه iOS با برنامه Google Maps
- تجربه برنامه نویسی تحت وب
- یه اتصال اینترنتی!
۲. راهاندازی
این آزمایشگاه کد فرض میکند که شما اولین عامل خود را ایجاد کرده و بخش اول آزمایشگاه کد را تکمیل کردهاید. به همین دلیل، ما به اصول اولیه فعالسازی پیامهای تجاری و APIهای ارتباطات تجاری، ایجاد کلیدهای حساب سرویس، استقرار یک برنامه یا تنظیم وبهوک شما در کنسول ارتباطات تجاری نخواهیم پرداخت. با این اوصاف، ما یک برنامه نمونه را شبیهسازی میکنیم تا مطمئن شویم که برنامه شما با آنچه که ما بر روی آن میسازیم سازگار است و API مربوط به Datastore را در پلتفرم Google Cloud فعال میکنیم تا بتواند دادههای مربوط به سبد خرید را ذخیره کند.
کلون کردن برنامه از گیتهاب
در ترمینال، نمونه ربات 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
فعال کردن API فروشگاه داده گوگل
در بخش اول این آزمایشگاه کد، شما API پیامهای تجاری، API ارتباطات تجاری و API ساخت ابری را فعال کردید.
برای این codelab، از آنجایی که ما با Google Datastore کار خواهیم کرد، باید این API را نیز فعال کنیم:
- API مربوط به فروشگاه داده گوگل (Google Datastore API) را در کنسول ابری گوگل (Google Cloud Console) باز کنید.
- مطمئن شوید که با پروژه GCP صحیح کار میکنید.
- روی فعال کردن کلیک کنید.
استقرار برنامه نمونه
در یک ترمینال، به دایرکتوری مرحله ۲ نمونه بروید.
برای نصب نمونه، دستورات زیر را در ترمینال اجرا کنید:
$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
- PROJECT_ID شناسه پروژهای است که برای ثبت در APIها استفاده کردهاید.
به آدرس URL برنامهی اجرا شده در خروجی آخرین دستور توجه کنید:
Deployed service [default] to [https://PROJECT_ID.appspot.com]
کدی که شما مستقر کردید شامل یک برنامه وب با یک وبهوک برای دریافت پیامها از Business Messages است. این کد شامل هر کاری است که از بخش اول آزمایشگاه کد انجام دادهایم. اگر قبلاً این کار را نکردهاید، لطفاً وبهوک خود را پیکربندی کنید .
این برنامه به برخی سوالات ساده مانند سوالات کاربر در مورد ساعات کاری Bonjour Meal پاسخ خواهد داد. شما باید این را از طریق URL های آزمایشی که میتوانید از اطلاعات نماینده در کنسول ارتباطات تجاری دریافت کنید، روی دستگاه تلفن همراه خود آزمایش کنید. URL های آزمایشی، تجربه پیامهای تجاری را در دستگاه تلفن همراه شما راهاندازی میکنند و میتوانید در آنجا با نماینده خود تعامل داشته باشید.
۳. کاتالوگ محصولات
یک سیستم موجودی
در بیشتر موارد، میتوانید مستقیماً از طریق یک 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
}
]
}
بیایید کاری کنیم که برنامه پایتون این فایل را بخواند!
خواندن از موجودی ما
موجودی یک فایل استاتیک به نام "inventory.json" است که در دایرکتوری ./resources یافت میشود. ما باید مقداری منطق پایتون به 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 " باشد. وقتی کاربران روی پاسخ پیشنهادی کلیک میکنند و برنامه وب شما دادههای postback را دریافت میکند، ما چرخ فلک rich card را ارسال خواهیم کرد. بیایید یک ثابت جدید برای این پاسخ پیشنهادی در بالای 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 ایجاد میکنیم. هر پیشنهاد نشان دهنده انتخاب کاربر برای یک محصول در چرخ فلک است. هنگامی که کاربر روی پاسخ پیشنهادی ضربه میزند، Business Messages یک postbackData حاوی JSON که آیتم و عملی را که کاربر میخواهد انجام دهد (اضافه کردن یا حذف از سبد خرید) را توصیف میکند، به وبهوک شما ارسال میکند. در بخش بعدی، پیامهایی را که به این شکل هستند تجزیه خواهیم کرد تا بتوانیم آیتم را به سبد خرید اضافه کنیم.
حالا که این تغییرات را اعمال کردهایم، بیایید برنامه وب را در Google App Engine مستقر کنیم و تجربه را امتحان کنیم!
$ gcloud app deploy
وقتی صفحه محاورهای روی دستگاه همراهتان بارگذاری شد، پیام «show-product-catalog» را ارسال کنید تا مجموعهای از محصولات به شکل زیر نمایش داده شود.

اگر روی افزودن کالا ضربه بزنید، در واقع هیچ اتفاقی نمیافتد جز اینکه عامل، دادههای postback را از پاسخ پیشنهادی تکرار میکند. در بخش بعدی، از کاتالوگ محصول استفاده خواهیم کرد و از آن برای ساخت سبد خریدی که کالا به آن اضافه خواهد شد، استفاده خواهیم کرد.
کاتالوگ محصولی که به تازگی ساختهاید را میتوان به روشهای مختلفی گسترش داد. ممکن است گزینههای منوی نوشیدنی یا گزینههای گیاهخواری مختلفی داشته باشید. استفاده از چرخ فلک یا تراشههای پیشنهادی راهی عالی برای این است که به کاربران اجازه دهید گزینههای منو را بررسی کنند تا به مجموعهای از محصولات مورد نظر خود برسند. به عنوان افزونهای برای این codelab، سعی کنید سیستم کاتالوگ محصولات را گسترش دهید تا کاربر بتواند نوشیدنیها را جدا از غذا در منو مشاهده کند یا حتی بتواند گزینههای گیاهخواری را مشخص کند.
۴. سبد خرید
در این بخش از آزمایشگاه کد، ما قابلیت سبد خرید را بر اساس بخش قبلی توسعه خواهیم داد که به ما امکان میدهد محصولات موجود را مرور کنیم.
در میان بسیاری از موارد، تجربه سبد خرید کلیدی به کاربران این امکان را میدهد که اقلامی را به سبد خرید اضافه کنند، اقلامی را از سبد خرید حذف کنند، تعداد هر کالا در سبد خرید را پیگیری کنند و اقلام موجود در سبد خرید را بررسی کنند.
پیگیری وضعیت سبد خرید به این معنی است که ما باید دادهها را در برنامه وب ذخیره کنیم. برای سادگی آزمایش و استقرار، از Google Datastore در Google Cloud Platform برای ذخیره دادهها استفاده خواهیم کرد. شناسه مکالمه بین کاربر و کسبوکار ثابت میماند، بنابراین میتوانیم از آن برای مرتبط کردن کاربران با اقلام سبد خرید استفاده کنیم.
بیایید با اتصال به Google Datastore و ذخیره دائمی شناسه مکالمه هنگام مشاهده آن شروع کنیم.
ارتباط با دیتا استور
هر زمان که تعاملی روی سبد خرید انجام شود، مثلاً وقتی کاربر در حال اضافه کردن یا حذف یک کالا است، ما با Google Datastore ارتباط برقرار خواهیم کرد. میتوانید اطلاعات بیشتری در مورد استفاده از این کتابخانه کلاینت برای تعامل با Google Datastore را در مستندات رسمی آن بیابید.
قطعه کد زیر تابعی را برای بهروزرسانی سبد خرید تعریف میکند. این تابع ورودیهای زیر را دریافت میکند: conversation_id و message . message شامل JSON است که عملی را که کاربر میخواهد انجام دهد توصیف میکند، که از قبل در carousel شما که کاتالوگ محصول را نمایش میدهد، تعبیه شده است. این تابع یک کلاینت 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 به وبهوک شما ارسال میشود. فیلد "item_name" شناسه منحصر به فردی است که با کالا در inventory.json مرتبط است.
وقتی دستور سبد خرید و آیتم سبد خرید را از پیام تجزیه کردیم، میتوانیم دستورات شرطی برای اضافه کردن آیتم بنویسیم. برخی از موارد خاص که باید در اینجا در نظر گرفته شوند این است که آیا فروشگاه داده هرگز شناسه مکالمه را ندیده است یا اینکه سبد خرید برای اولین بار این آیتم را دریافت میکند. در ادامه، افزونهای از قابلیت update_shopping_cart که در بالا تعریف شده است، آمده است. این تغییر، یک آیتم را به سبد خرید اضافه میکند که توسط فروشگاه داده گوگل ذخیره میشود.
قطعه کد زیر، افزونهای از تابع قبلی است که به فایل 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 منعکس شده است، مشاهده کنید. به تصویر زیر از کنسول Google Datastore نگاه کنید، یک موجودیت واحد وجود دارد که با نام Conversation ID نامگذاری شده و به دنبال آن برخی روابط با اقلام موجودی و تعداد اقلام موجود در سبد خرید وجود دارد.

در بخش بعدی، روشی برای فهرست کردن اقلام موجود در سبد خرید ایجاد خواهیم کرد. مکانیزم بررسی سبد خرید باید تمام اقلام موجود در سبد خرید، تعداد آنها و گزینهای برای حذف یک کالا از سبد خرید را به ما نشان دهد.
بررسی اقلام موجود در سبد خرید
فهرست کردن اقلام موجود در سبد خرید تنها راهی است که میتوانیم وضعیت سبد خرید را بفهمیم و بدانیم کدام اقلام را میتوانیم حذف کنیم.
بیایید ابتدا یک پیام دوستانه مانند "اینجا سبد خرید شماست:" ارسال کنیم، و به دنبال آن پیام دیگری که حاوی یک چرخ فلک کارت غنی با پاسخهای پیشنهادی مرتبط برای "حذف یکی" یا "اضافه کردن یکی" است، ارسال کنیم. چرخ فلک کارت غنی باید علاوه بر این، تعداد اقلام ذخیره شده در سبد خرید را نیز فهرست کند.
نکتهای که قبل از نوشتن تابع باید به آن توجه داشته باشیم: اگر فقط یک نوع کالا در سبد خرید وجود داشته باشد، نمیتوانیم آن را به صورت یک چرخ فلک نمایش دهیم. چرخ فلکهای کارت غنی باید حداقل شامل دو کارت باشند. از طرف دیگر، اگر هیچ کالایی در سبد خرید وجود نداشته باشد، میخواهیم یک پیام ساده مبنی بر خالی بودن سبد خرید نمایش دهیم.
با توجه به این نکته، بیایید تابعی به نام 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 تعریف کردهاید و اگر کاربر پیامی حاوی '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)
...

بر اساس منطقی که در تابع send_shopping_cart معرفی کردیم، وقتی عبارت 'show-cart' را تایپ میکنید، یا پیامی مبنی بر عدم وجود کالا در سبد خرید دریافت خواهیم کرد، یا یک کارت غنی که یک کالا را در سبد خرید نشان میدهد، یا یک کارت چرخان که چندین کالا را نشان میدهد. علاوه بر این، سه پاسخ پیشنهادی داریم: "مشاهده قیمت کل"، "خالی کردن سبد خرید" و "مشاهده منو".
سعی کنید تغییرات کد بالا را اعمال کنید تا مطمئن شوید سبد خرید شما اقلامی را که اضافه میکنید ردیابی میکند و میتوانید سبد خرید را از سطح گفتگوها، همانطور که در تصاویر بالا نشان داده شده است، بررسی کنید. میتوانید تغییرات را با اجرای این دستور از دایرکتوری مرحله ۲ که تغییرات خود را در آن اضافه میکنید، اعمال کنید.
$ 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)

باید همین کار را بکند! یک تجربه سبد خرید کامل که به کاربر امکان میدهد اقلام را اضافه، حذف و اقلام موجود در سبد خرید را بررسی کند.
در این مرحله، اگر میخواهید قابلیت سبد خرید را در گفتگوی پیامهای تجاری مشاهده کنید، برنامه را برای تعامل با نماینده خود مستقر کنید. میتوانید این کار را با اجرای این دستور در دایرکتوری step-2 انجام دهید.
$ gcloud app deploy
۵. آماده شدن برای پرداختها
برای آمادهسازی جهت ادغام با یک پردازنده پرداخت در بخش بعدی این مجموعه، به روشی برای دریافت قیمت سبد خرید نیاز داریم. بیایید تابعی بسازیم که با ارجاع متقابل به دادههای سبد خرید در 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
۶. تبریک
تبریک میگویم، شما با موفقیت یک تجربه سبد خرید در پیامهای تجاری ایجاد کردید.
چیزی که در این آزمایشگاه کد به آن نپرداختیم، قابلیت خالی کردن کل سبد خرید بود. اگر مایل باشید، سعی کنید برنامه را طوری توسعه دهید که قابلیت "خالی کردن سبد خرید" را داشته باشد. این راهکار در مرحله ۳ کد منبعی که کپی کردهاید، موجود است.
در بخش بعدی، ما با یک پردازنده پرداخت خارجی ادغام خواهیم شد تا کاربران شما بتوانند تراکنش پرداخت را با برند شما انجام دهند.
یک سبد خرید خوب چه ویژگیهایی دارد؟
یک تجربه خوب از سبد خرید در یک مکالمه، تفاوتی با یک اپلیکیشن موبایل یا یک فروشگاه فیزیکی ندارد. امکان اضافه کردن اقلام، حذف اقلام و محاسبه قیمت سبد خرید، تنها چند مورد از ویژگیهایی هستند که در این آزمایشگاه کد بررسی کردیم. چیزی که با سبد خرید در دنیای واقعی متفاوت است، امکان مشاهده قیمت همه اقلام موجود در سبد خرید در هر لحظه، همزمان با اضافه کردن یا حذف اقلام است. این نوع ویژگیهای ارزشمند، تجربه تجارت مکالمهای شما را متمایز میکند!
بعدش چی؟
وقتی آماده شدید، برخی از مباحث زیر را بررسی کنید تا در مورد تعاملات پیچیدهتری که میتوانید در پیامهای تجاری به آنها دست یابید، اطلاعات کسب کنید:
اسناد مرجع
- پاسخ پیشنهادی
- سند مرجع پیامهای تجاری
- تعریف JSON برای RichCard