Achat en ligne, retrait en magasin : Bonjour Meal (2e partie) – Créer un panier

1. Présentation

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Dernière mise à jour : 30/10/2020

Créer un panier sur Business Messages

Il s'agit du deuxième atelier de programmation d'une série consacrée à la création d'un parcours utilisateur "Achat en ligne, retrait en magasin". Dans de nombreux parcours d'e-commerce, un panier est essentiel pour convertir les utilisateurs en clients payants. Le panier vous permet également de mieux comprendre vos clients et de leur suggérer d'autres articles susceptibles de les intéresser. Dans cet atelier de programmation, nous allons aborder la création d'une expérience d'achat et le déploiement de l'application sur Google App Engine.

Qu'est-ce qu'un bon panier ?

Les paniers sont essentiels pour créer une expérience d'achat en ligne réussie. Non seulement Business Messages est très efficace pour faciliter les questions/réponses sur un produit avec un client potentiel, mais il simplifie également l'expérience d'achat globale en permettant de finaliser un paiement au cours de la conversation.

9d17537b980d0e62.png

Au-delà d'un bon panier, une bonne expérience d'achat permet aux utilisateurs de parcourir les articles par catégorie, et permet à l'entreprise de recommander des produits susceptibles d'intéresser l'acheteur. Après avoir ajouté d'autres articles au panier, l'utilisateur peut consulter l'intégralité de son panier et supprimer des articles ou en ajouter, avant de procéder au règlement.

Objectifs de l'atelier

Dans cette section de la série d'ateliers de programmation, vous allez développer l'agent numérique créé dans la partie 1 pour la société fictive Bonjour Meal, afin de permettre aux utilisateurs de parcourir un catalogue de produits et d'ajouter des articles à un panier.

À l'issue de cet atelier de programmation, votre application offrira ces fonctionnalités :

  • Afficher un catalogue de questions dans Business Messages
  • Suggérer des articles susceptibles d'intéresser les utilisateurs
  • Afficher le contenu du panier et créer un récapitulatif indiquant le prix total

ab2fb6a4ed33a129.png

Points abordés

  • Déployer une application Web sur App Engine sur Google Cloud Platform
  • Utiliser un mécanisme de stockage persistant pour enregistrer l'état d'un panier

Cet atelier de programmation porte sur l'extension de l'agent numérique de la première partie de cette série d'ateliers.

Prérequis

2. Configuration

Cet atelier de programmation implique que vous avez suivi les étapes préalables, créé votre premier agent et terminé la partie 1 de l'atelier de programmation. Par conséquent, nous n'expliquerons pas les principes de base de l'activation des API Business Messages et Business Communications, de la création des clés de compte de service, du déploiement d'une application ni de la configuration de votre webhook dans la console Business Communications. Cela dit, nous allons cloner un exemple d'application pour nous assurer que votre application est cohérente avec celle sur laquelle nous nous appuyons pour le développement. De plus, nous activerons l'API pour Datastore sur Google Cloud Platform afin de pouvoir conserver les données relatives au panier.

Cloner l'application depuis GitHub

Dans un terminal, clonez le bot exemple Django Echo vers le répertoire de travail de votre projet à l'aide de la commande suivante :

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

Copiez le fichier d'identifiants JSON créé pour le compte de service dans le dossier des ressources de l'exemple et renommez les identifiants "bm-agent-service-account-credentials.json".

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

Activer l'API Google Datastore

Dans la première partie de cet atelier de programmation, vous avez activé l'API Business Messages, l'API Business Communications et l'API Cloud Build.

Pour cet atelier de programmation, étant donné que nous allons travailler avec Google Datastore, nous devons également activer cette API :

  1. Ouvrez l'API Google Datastore dans Google Cloud Console.
  2. Assurez-vous que vous travaillez sur le bon projet GCP.
  3. Cliquez sur Activer.

Déployer l'exemple d'application

Dans un terminal, accédez au répertoire de l'étape 2 de l'exemple.

Exécutez les commandes suivantes dans un terminal pour déployer l'exemple :

$ gcloud config set project PROJECT_ID*
$ gcloud app deploy
  • "PROJECT_ID" est l'ID du projet que vous avez utilisé pour vous inscrire auprès des API.

Notez l'URL de l'application déployée dans le résultat de la dernière commande :

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

Le code que vous venez de déployer contient une application Web avec un webhook permettant de recevoir des messages de Business Messages. Vous y trouverez tout ce que nous avons fait dans la partie 1 de l'atelier de programmation. Si vous ne l'avez pas encore fait, veuillez configurer votre webhook.

L'application répond à quelques questions simples, comme un utilisateur qui s'interroge sur les horaires d'ouverture de Bonjour Meal. Effectuez un test sur votre appareil mobile via les URL de test que vous pouvez récupérer dans les informations sur l'agent dans la console Business Communications. Les URL de test lancent l'interface Business Messages sur votre appareil mobile, où vous pouvez commencer à interagir avec votre agent.

3. Catalogue de produits

Système d'inventaire

Dans la plupart des cas, vous pouvez intégrer directement l'inventaire d'une marque via une API interne. Dans d'autres cas, vous pouvez extraire le contenu d'une page Web ou créer votre propre système de suivi d'inventaire. Notre objectif n'est pas de créer un système d'inventaire : nous utiliserons un fichier statique simple contenant des images et des informations produit pour notre agent. Dans cette section, nous allons extraire des informations de ce fichier statique, les afficher dans la conversation et permettre à l'utilisateur de parcourir les articles pouvant être ajoutés à son panier.

Le fichier d'inventaire statique se présente comme suit :

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
        }
    ]
}

Maintenant, ouvrez l'application Python pour lire ce fichier.

Lire les données de notre inventaire

L'inventaire est un fichier statique appelé "inventory.json" qui se trouve dans le répertoire "./resources". Nous devons ajouter une logique Python au fichier "views.py" pour lire le contenu du fichier JSON, puis l'afficher dans la conversation. Nous allons maintenant créer une fonction qui lit les données du fichier JSON et renvoie la liste des produits disponibles.

Cette définition de fonction peut être placée n'importe où dans le fichier "views.py".

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

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

Cela nous permet de lire les données de l'inventaire. Nous devons maintenant afficher ces informations produit dans la conversation.

Présenter le catalogue de produits

Pour plus de simplicité dans cet atelier de programmation, nous proposons un catalogue de produits général permettant de présenter tous les articles d'inventaire dans la conversation Business Messages via un seul carrousel de cartes enrichies.

Pour afficher le catalogue de produits, nous allons créer une suggestion de réponse avec le texte "Afficher le menu" et l'attribut "show-product-catalog" "postbackData". Lorsque les utilisateurs appuient sur la réponse suggérée et que votre application Web reçoit les données de postback, nous envoyons le carrousel de cartes enrichies. Ajoutons une nouvelle constante à cette suggestion de réponse en haut du fichier "views.py".

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

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

À partir de là, nous analysons le message et le dirigeons vers une nouvelle fonction qui envoie un carrousel de cartes enrichies contenant le catalogue de produits. Commençons par étendre la fonction route_message pour appeler une fonction "send_product_catalog" lorsque l'utilisateur appuie sur la réponse suggérée, puis définissons la fonction.

Dans l'extrait de code suivant, nous allons ajouter une condition à l'instruction "if" dans la fonction route_message pour vérifier que normalized_message correspond à la constante définie précédemment (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)
...

N'oublions pas de terminer le flux et de définir send_product_catalog. La fonction send_product_catalog appelle get_menu_carousel,, ce qui génère le carrousel de cartes enrichies à partir du fichier d'inventaire que nous avons lu précédemment.

Vous pouvez placer les définitions de fonction n'importe où dans le fichier "views.py". Notez que l'extrait de code suivant utilise deux nouvelles constantes à ajouter en haut du fichier.

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)
...

Si vous examinez la création des éléments du carrousel, vous pouvez constater que nous créons également une instance de la classe BusinessMessagesSuggestion. Chaque suggestion représente une sélection de l'utilisateur pour un produit du carrousel. Lorsqu'un utilisateur appuie sur la réponse suggérée, Business Messages envoie l'attribut "postbackData" qui contient le code JSON décrivant l'article et l'action que l'utilisateur souhaite effectuer (ajouter au panier ou supprimer du panier) à votre webhook. Dans la section suivante, nous allons analyser des messages comme ceux-ci afin de pouvoir ajouter l'article au panier.

Maintenant que nous avons apporté ces modifications, déployons l'application Web sur Google App Engine et testons l'expérience.

$ gcloud app deploy

Une fois la surface de conversation chargée sur votre appareil mobile, envoyez le message "show-product-catalog" pour afficher un carrousel de produits semblable à celui-ci.

4639da46bcc5230c.png

Si vous appuyez sur Ajouter un article, rien ne se passe, sauf si l'agent fait écho aux données de postback de la réponse suggérée. Dans la section suivante, nous allons utiliser le catalogue de produits pour créer le panier dans lequel l'article sera ajouté.

Le catalogue de produits que vous venez de créer peut être étendu de différentes manières. Vous pouvez choisir différents menus avec boisson ou des options végétariennes. Les carrousels ou les chips de suggestion permettent aux utilisateurs de parcourir les options de menu pour accéder à l'ensemble des produits qu'ils recherchent. Pour aller plus loin dans cet atelier de programmation, essayez d'étendre le système de catalogue de produits afin qu'un utilisateur puisse voir les boissons séparément des aliments dans le menu, ou même spécifier des options végétariennes.

4. Panier

Dans cette section de l'atelier de programmation, nous allons développer la fonctionnalité de panier à partir de la section précédente, qui nous permet de parcourir les produits disponibles.

L'expérience d'achat fondamentale permet, entre autres, d'ajouter des articles au panier, de supprimer des articles du panier, de suivre le nombre d'articles dans le panier et de vérifier les articles du panier.

Le suivi de l'état du panier implique que nous devons conserver des données dans l'application Web. Pour simplifier les tests et le déploiement, nous allons utiliser Google Datastore dans Google Cloud Platform pour conserver les données. L'identifiant de conversation reste constant entre un utilisateur et l'entreprise. Nous pouvons donc l'utiliser pour associer des utilisateurs à des articles de panier.

Commençons par nous connecter à Google Datastore et enregistrer l'ID de conversation lorsqu'il s'affiche.

Se connecter à Datastore

Nous nous connectons à Google Datastore chaque fois qu'une interaction est exécutée dans le panier, par exemple lorsqu'un utilisateur ajoute ou supprime un article. Pour savoir comment utiliser cette bibliothèque cliente avec Google Datastore, consultez la documentation officielle.

L'extrait de code suivant définit une fonction permettant de mettre à jour le panier. La fonction prend les entrées suivantes : conversation_id et message. message contient le code JSON décrivant l'action que l'utilisateur souhaite effectuer. Il est déjà intégré dans votre carrousel et affiche le catalogue de produits. La fonction crée un client Google Datastore et récupère immédiatement une entité ShoppingCart, où la clé correspond à l'ID de conversation.

Copiez la fonction suivante dans votre fichier "views.py". Nous continuerons à la développer dans la section suivante.

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)

Développons cette fonction pour ajouter un article au panier.

Ajouter des articles au panier

Lorsque l'utilisateur appuie sur l'action suggérée Ajouter un article dans le carrousel des produits, l'attribut "postbackData" contient le code JSON décrivant l'action à effectuer. Le dictionnaire JSON comporte deux clés, "action" et "item_name", et ce dictionnaire JSON est envoyé à votre webhook. Le champ "item_name" correspond à l'identifiant unique associé à l'article dans "inventory.json".

Une fois la commande et l'article du panier analysés à partir du message, nous pouvons écrire des instructions conditionnelles afin d'ajouter l'article. Réfléchissez à ce cas de figure : le Datastore n'a jamais vu l'ID de la conversation ou le panier reçoit cet article pour la première fois. Voici une extension de la fonctionnalité update_shopping_cart définie ci-dessus. Cette modification ajoute un article au panier, lequel est conservé par Google Datastore.

L'extrait de code suivant est une extension de la fonction précédente ajoutée à vos "views.py". N'hésitez pas à ajouter la différence, ou à copier l'extrait et à remplacer la version existante de la fonction 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)

Cette fonction sera étendue ultérieurement pour la prise en charge des scénarios où cart_cmd contient la chaîne "del-item" définie dans CMD_DEL_ITEM.

Essayer ensemble

Assurez-vous de consolider la fonction route_message de sorte que, si vous recevez un message vous invitant à ajouter un article au panier, la fonction update_shopping_cart est appelée. Vous devez également définir une constante pour ajouter des articles en respectant la convention utilisée dans cet atelier de programmation.

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)

...

Pour le moment, nous pouvons ajouter des articles au panier. Si vous déployez vos modifications sur Google App Engine, vous devriez voir les modifications apportées au panier dans le tableau de bord Google Datastore disponible dans la console GCP. Comme le montre la capture d'écran de la console Google Datastore présentée ci-dessous, il existe une seule entité nommée d'après l'ID de la conversation, suivie de relations entre des articles d'inventaire et la quantité de ces articles figurant dans le panier.

619dc18a8136ea69.png

Dans la section suivante, nous allons créer un moyen de lister les articles du panier. Le processus d'examen du panier doit afficher tous les articles du panier, leur quantité et une option permettant d'en supprimer un.

Examiner les articles du panier

Répertorier les articles du panier est le seul moyen de déterminer l'état du panier et de savoir quels articles nous pouvons supprimer.

Commencez par envoyer un message convivial tel que "Voici votre panier", puis un autre message contenant un carrousel de cartes enrichies avec les suggestions de réponses "Supprimer un article" ou "Ajouter un article". Le carrousel de cartes enrichies doit également indiquer la quantité d'articles enregistrés dans le panier.

Points à prendre en compte avant d'écrire une fonction : si le panier contient un seul type d'article, nous ne pouvons pas l'afficher sous forme de carrousel. Les carrousels de cartes enrichies doivent contenir au moins deux cartes. En revanche, si le panier ne contient aucun article, nous voulons afficher un message simple indiquant que le panier est vide.

En gardant cela à l'esprit, nous allons définir une fonction appelée send_shopping_cart. Cette fonction se connecte à Google Datastore et demande une entité ShoppingCart en fonction de l'ID de la conversation. Nous appelons ensuite la fonction get_inventory_data et utilisons un carrousel de cartes enrichies pour indiquer l'état du panier. Nous devons également obtenir l'ID d'un produit à l'aide de son nom. Nous pouvons déclarer une fonction qui examinera Google Datastore pour déterminer cette valeur. Au fur et à mesure que le carrousel est généré, nous pouvons associer des réponses suggérées pour supprimer des articles ou en ajouter par le biais de l'ID de produit. L'extrait ci-dessous effectue toutes ces opérations. Copiez le code n'importe où dans le fichier "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)

...

Assurez-vous d'avoir défini CMD_SHOW_CART en haut du fichier "views.py" et d'appeler send_shopping_cart si l'utilisateur envoie un message contenant "show-cart".

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

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

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

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

34801776a97056ac.png

Selon la logique que nous avons introduite dans la fonction send_shopping_cart, en saisissant "show-cart", nous obtenons un message indiquant qu'il n'y a rien dans le panier, une carte enrichie affichant l'article dans le panier, ou un carrousel de cartes montrant plusieurs articles. Nous avons également reçu trois suggestions de réponses : "Voir le prix total", "Vider le panier" et "Afficher le menu".

Déployez les modifications de code ci-dessus pour vérifier que votre panier effectue le suivi des articles que vous ajoutez. Vous pouvez vérifier le panier à partir de la liste des conversations comme indiqué dans les captures d'écran ci-dessus. Vous pouvez déployer les modifications à l'aide de cette commande à partir du répertoire de l'étape 2 dans lequel vous ajoutez vos modifications.

$ gcloud app deploy

La fonctionnalité "Afficher le prix total" sera créée dans la section suivante, après celle permettant de supprimer un article du panier. La fonction get_cart_price se comportera de la même manière que la fonctionnalité "Afficher le panier" dans le sens où elle croisera les données de Datastore avec le fichier "inventory.json" afin de générer le prix total du panier. Cela sera utile pour la prochaine partie de l'atelier de programmation dans laquelle nous intégrerons les paiements.

Supprimer des articles du panier

Enfin, nous pouvons terminer le comportement du panier en ajoutant une fonctionnalité permettant de le supprimer. Remplacez la fonction update_shopping_cart existante par l'extrait de code suivant.

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)

Envoyer un message de confirmation

Lorsque l'utilisateur ajoute un article au panier, vous devez envoyer un message confirmant l'action et indiquant que vous avez traité sa demande. Cela permet non seulement de définir des attentes, mais aussi d'entretenir la conversation.

Nous allons maintenant étendre la fonction update_shopping_cart de sorte qu'elle envoie un message à l'ID de la conversation pour indiquer que l'article a été ajouté ou supprimé et suggérer à l'utilisateur d'examiner son panier ou de revoir le menu.

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

Cela devrait suffire. Ceci est une expérience complète de panier permettant aux utilisateurs d'ajouter, de supprimer et d'examiner des articles dans le panier.

À ce stade, si vous souhaitez voir la fonctionnalité de panier dans la conversation Business Messages, déployez l'application afin d'interagir avec votre agent. Vous pouvez le faire en exécutant cette commande dans le répertoire de l'étape 2.

$ gcloud app deploy

5. Préparer les paiements

Pour préparer l'intégration d'une société de traitement des paiements dans la prochaine partie de la série, nous avons besoin de connaître le prix du panier. Créons une fonction qui récupère le prix pour nous en croisant les données du panier dans Google Datastore, en récupérant le prix de chaque article de l'inventaire, puis en multipliant le prix par la quantité de chaque article dans le panier.

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

...

Enfin, nous pouvons utiliser cette fonction et envoyer un message à l'utilisateur.

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)
...

Pour établir la liaison, nous allons mettre à jour la fonction route_message et la constante pour déclencher la logique ci-dessus.

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)
...

Voici quelques captures d'écran qui montrent ce que la logique permet d'effectuer :

8feacf94ed0ac6c4.png

Une fois que nous serons prêts à intégrer la société de traitement des paiements dans la partie suivante de l'atelier de programmation, nous allons appeler la fonction get_cart_price pour transmettre les données à cette société et démarrer le flux de paiement.

Vous pouvez à nouveau essayer cette fonctionnalité de panier dans la conversation Business Messages en déployant l'application et en interagissant avec votre agent.

$ gcloud app deploy

6. Félicitations

Félicitations, vous avez créé une expérience de panier dans Business Messages.

Pendant cet atelier de programmation, nous n'avons pas abordé la fonctionnalité permettant de vider l'intégralité du panier. Si vous le souhaitez, essayez d'étendre l'application pour exécuter la fonctionnalité "Vider le panier". La solution est disponible à l'étape 3 du code source que vous avez cloné.

Dans une prochaine section, nous intégrerons une société de traitement des paiements externe pour permettre à vos utilisateurs d'effectuer un paiement auprès de votre marque.

Qu'est-ce qu'un bon panier ?

Il n'y a pas de différences en termes d'expérience d'achat entre une application mobile et un magasin physique. Pouvoir ajouter des articles, en supprimer et calculer le prix du panier ne sont que quelques fonctionnalités, dont nous avons parlé lors de cet atelier de programmation. Une différence avec les achats en magasin est la possibilité de voir le prix de tous les articles du panier à tout moment, lorsque vous ajoutez ou supprimez des articles. Ce type de fonctionnalité à forte valeur ajoutée rend votre expérience de commerce interactif unique.

Étapes suivantes

Lorsque vous êtes prêt, consultez certains des sujets suivants pour en savoir plus sur les interactions plus complexes disponibles avec Business Messages :

Documents de référence