Achat en ligne, retrait en magasin: Bonjour Meal - Partie 3 - Intégration à un processeur de paiement

1. Introduction

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

Dernière mise à jour:13/09/2021

Collecte des paiements

Collecter des paiements dans Business Messages vous offre de nouvelles opportunités commerciales sur la plate-forme conversationnelle. Imaginez qu'un client potentiel vous envoie une demande concernant un produit sur lequel il souhaite en savoir plus. Une fois que les utilisateurs ont répondu à leurs questions, vous pouvez conclure la vente en leur fournissant une passerelle de paiement directement dans la conversation.

fe0c6754fb69d708.png

Qu'est-ce qu'une bonne expérience de paiement ?

Une bonne expérience de paiement permet aux utilisateurs de payer comme ils en ont l'habitude.

Les utilisateurs ont des préférences en termes de modes de paiement, et les modes de paiement sont plus courants que dans d'autres parties du monde. Avec Business Messages, vous pouvez intégrer plusieurs outils de traitement des paiements pour simplifier l'utilisation.

Lorsqu'un utilisateur effectue un paiement, vous devez l'informer que vous avez reçu son paiement. La plupart des sociétés de traitement des paiements incluent un rappel de réussite ou d'échec qui envoie une requête HTTP à l'URL de votre choix une fois le processus de paiement terminé.

Objectifs de l'atelier

Dans la section précédente de la série d'ateliers de programmation, vous avez étendu l'agent Bonjour Meal pour présenter un catalogue d'articles, créé un panier permettant aux utilisateurs d'ajouter et de supprimer des articles, et calculé le prix total du panier. Dans cette section, vous allez étendre davantage l'agent afin qu'il puisse traiter les paiements en fonction du contenu du panier.

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

  • Intégrer la passerelle de paiement Stripe
  • Autoriser un utilisateur à finaliser le processus de paiement en fonction du prix du panier
  • Envoyer une notification à la surface de conversation pour informer l'utilisateur de l'état du paiement

ba08a4d2f8c09c0e.png

Objectifs de l'atelier

  • Intégrer la société de traitement des paiements Stripe
  • Envoyez une demande à Stripe pour commencer une session de paiement.
  • Gérer les réponses aux paiements ou les échecs de paiement de Stripe

Prérequis

  • Un projet GCP enregistré et approuvé pour une utilisation avec Business Messages
  • Consultez notre site pour les développeurs pour connaître la procédure à suivre
  • Un appareil Android 5 ou version ultérieure OU un appareil iOS disposant de l'application Google Maps.
  • Une expérience dans la programmation d'applications Web
  • Une connexion Internet

2. Ajouter des dépendances

Mise à jour du fichier requirements.txt

Comme nous allons l'intégrer à la société de traitement des paiements Stripe, nous pouvons utiliser la bibliothèque cliente Python Stripe. Ajoutez stripe au fichier requirements.txt sans version afin d'obtenir la dernière version de la dépendance.

Cela est nécessaire pour que l'environnement d'exécution Python de Google Cloud App Engine inclue le module Python à rayures.

requirements.txt

...
stripe
...

Préparation de bopis/views.py

En haut du fichier bopis/views.py, importez render depuis django.shortcuts et JsonResponse depuis django.http. De plus, nous devrons importer stripe pour prendre en charge les appels à la bibliothèque cliente Python Stripe.

...
from django.shortcuts import render
from django.http import JsonResponse
import stripe
...

3. Utiliser Stripe

Créer un compte sur Stripe.com

Dans cet atelier de programmation, nous utilisons Stripe, mais vous pouvez l'intégrer à n'importe quel processeur compatible avec l'intégration Web. Créez un compte sur stripe.com. Nous utiliserons ce profil à des fins de test et de formation pour apprendre à intégrer directement une société de traitement des paiements tierce.

6731d123c56feb67.png

Une fois que vous avez créé un compte et que vous vous êtes connecté, un tableau de bord semblable à celui-ci s'affiche.

6D9D165d2d1fbb8c.png

Assurez-vous d'utiliser le mode "Test", puis cliquez sur le bouton Développeurs comme indiqué dans la capture d'écran ci-dessus pour rechercher vos clés API. Vous devriez voir deux ensembles de clés API: une clé publiable et une clé secrète. Vous avez besoin de ces deux clés pour faciliter les transactions avec Stripe.

Mettre à jour bopis/views.py

Votre application nécessite les deux ensembles de clés. Vous devez donc les mettre à jour dans le fichier "views.py".

Vous pouvez définir la clé secrète directement dans la propriété "tripe.api_key" et lui attribuer la valeur de la clé "Secret" figurant dans le tableau de bord du développeur Stripe. Créez ensuite une variable globale appelée STRIPE_PUBLIC_KEY et définissez-la sur la clé publiable.

De plus, Stripe doit rediriger l'utilisateur vers une page Web que vous gérez. Vous devez donc créer une variable globale supplémentaire afin d'inclure votre domaine accessible publiquement.

À la fin de ces modifications, vous aurez quelque chose comme ceci:

stripe.api_key = 'sk_test_abcde-12345'
STRIPE_PUBLIC_KEY = 'pk_test_edcba-54321'
YOUR_DOMAIN = 'https://<GCP_PROJECT_ID>.appspot.com'

Vous n'avez rien d'autre à faire pour configurer Stripe.

4. Fonctionnalité de paiement

Mettre à jour la fonction de prix total du panier

Actuellement, la fonction send_shopping_cart_total_price n'envoie qu'un message spécifiant le prix du panier. Ajoutons une action suggérée pour ouvrir une URL vers la page de paiement.

def send_shopping_cart_total_price(conversation_id):
  """Sends shopping cart price to the user through Business Messages.

  Args:
    conversation_id (str): The unique id for this user and agent.
  """
  cart_price = get_cart_price(conversation_id)

  message_obj = BusinessMessagesMessage(
      messageId=str(uuid.uuid4().int),
      representative=BOT_REPRESENTATIVE,
      text=f'Your cart\'s total price is ${cart_price}.',
      suggestions=[
          BusinessMessagesSuggestion(
              action=BusinessMessagesSuggestedAction(
                  text='Checkout',
                  postbackData='checkout',
                  openUrlAction=BusinessMessagesOpenUrlAction(
                      url=f'{YOUR_DOMAIN}/checkout/{conversation_id}'))),
      ]
    )

  send_message(message_obj, conversation_id)

Lorsque l'utilisateur appuie sur l'action suggérée, il est redirigé vers une page Web indiquant son prix total et un bouton permettant d'effectuer le paiement avec Stripe.

Créons une page Web simple qui permettra ce processus.

Dans le code source du projet, recherchez le répertoire nommé bopis. Créez un répertoire appelé "templates" dans les bopis et, dans les modèles, créez un répertoire nommé bopis. Il s'agit d'un modèle de conception Django qui permet de spécifier le nom de l'application dans le répertoire des modèles. Cela permet de réduire la confusion entre les modèles Django.

Vous devriez maintenant avoir un répertoire avec un chemin d'accès à bopis/templates/bopis/. Vous pouvez créer des fichiers HTML dans ce répertoire pour diffuser des pages Web. Django les appelle des modèles qui sont affichés dans le navigateur. Commençons par checkout.html.

Dans ce répertoire, créez checkout.html. L'extrait de code suivant affiche un bouton de règlement et le prix du panier. Il inclut également JavaScript pour lancer le règlement Stripe.

{% load static %}

<!DOCTYPE html>
<html>
  <head>
    <title>Purchase from Bonjour Meal</title>

    <script src="https://js.stripe.com/v3/"></script>
    <style>
      .description{
        font-size: 4em;
      }
      button {
        color: red;
        padding: 40px;
        font-size: 4em;
      }
    </style>
  </head>
  <body>
    <section>
      <img
        src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
        alt="Bonjour Meal Restaurant"
      />
      <div class="product">
        <div class="description">
          <h3>Your Bonjour Meal Total</h3>
          <h5>${{cart_price}}</h5>
        </div>
      </div>
      <button type="button" id="checkout-button">Checkout</button>
    </section>
  </body>
  <script type="text/javascript">
    // Create an instance of the Stripe object with your publishable API key
    var stripe = Stripe("{{stripe_public_key}}");
    var checkoutButton = document.getElementById("checkout-button");

    checkoutButton.addEventListener("click", function () {
      fetch("/create-checkout-session/{{conversation_id}}", {
        method: "POST",
      })
        .then(function (response) {
          return response.json();
        })
        .then(function (session) {
          return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function (result) {
          // If redirectToCheckout fails due to a browser or network
          // error, you should display the localized error message to your
          // customer using error.message.
          if (result.error) {
            alert(result.error.message);
          }
        })
        .catch(function (error) {
          console.error("Error:", error);
        });
    });
  </script>
</html>

Nous avons besoin d'un itinéraire vers cette page Web lorsque l'URL est demandée. La valeur openUrlAction est définie sur {YOUR_DOMAIN}/checkout/{conversation_id} pour l'action de paiement suggérée. Le résultat est semblable à https://<GCP-Project-ID>.appspot.com/checkout/abc123-cba321-abc123-cba321. Avant de créer cette route, examinons le code JavaScript trouvé dans le modèle HTML.

...
  <script type="text/javascript">
    // Create an instance of the Stripe object with your publishable API key
    var stripe = Stripe("{{stripe_public_key}}");
    var checkoutButton = document.getElementById("checkout-button");

    checkoutButton.addEventListener("click", function () {
      fetch("/create-checkout-session/{{conversation_id}}", {
        method: "POST",
      })
        .then(function (response) {
          return response.json();
        })
        .then(function (session) {
          return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function (result) {
          // If redirectToCheckout fails due to a browser or network
          // error, you should display the localized error message to your
          // customer using error.message.
          if (result.error) {
            alert(result.error.message);
          }
        })
        .catch(function (error) {
          console.error("Error:", error);
        });
    });
  </script>
...

Examinons ensemble l'extrait de code ci-dessus.

  1. Tout d'abord, il crée une entité Stripe avec la clé publique transmise dans le contexte à partir de la fonction de vue, un autre modèle Django.
  2. L'extrait recherche ensuite un élément de la page dont l'ID est checkout-button.
  3. Un écouteur d'événements est ajouté à cet élément.

Cet écouteur d'événements sera déclenché lorsqu'un utilisateur cliquera ou appuiera sur ce bouton, ce qui déclenchera une requête POST auprès du serveur Web spécifié via l'URL: {YOUR_DOMAIN}/create-checkout-session/{conversation_id}.

Vous pouvez voir la logique du serveur Web dans les extraits ci-dessous. Lorsque l'utilisateur appuie sur le bouton associé à l'ID "checkout-button", nous pouvons nous attendre à ce qu'il renvoie un ID de session Stripe créé à l'aide de l'API Stripe, qui spécifie le prix du panier.

Si votre serveur a pu générer un ID de session valide, la logique d'application redirige l'utilisateur vers une page Stripe Checkout. Dans le cas contraire, il avertit l'utilisateur avec un message JavaScript standard indiquant qu'un problème est survenu.

Commençons par ajouter de nouveaux chemins au tableau de formats d'URL pour prendre en charge la page de paiement et générer l'ID de session. Ajoutez le code suivant au tableau urlpatterns dans urls.py.

... 
path('checkout/<str:conversation_id>', bopis_views.payment_checkout),
path('create-checkout-session/<str:conversation_id>', bopis_views.create_checkout_session),
...

Définissons ensuite les fonctions de la vue dans le fichier "views.py" pour renvoyer le modèle checkout.html et générer la session de règlement Stripe.

... 

def payment_checkout(request, conversation_id):
  """Sends the user to a payment confirmation page before the payment portal.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """

  cart_price = get_cart_price(conversation_id)
  context = {'conversation_id': conversation_id,
             'stripe_public_key': STRIPE_PUBLIC_KEY,
             'cart_price': cart_price
            }
  return render(request, 'bopis/checkout.html', context)


@csrf_exempt
def create_checkout_session(request, conversation_id):
  """Creates a Stripe session to start a payment from the conversation.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """
  cart_price = get_cart_price(conversation_id)
  try:
    checkout_session = stripe.checkout.Session.create(
        payment_method_types=['card'],
        line_items=[
            {
                'price_data': {
                    'currency': 'usd',
                    'unit_amount': int(cart_price*100),
                    'product_data': {
                        'name': 'Bonjour Meal Checkout',
                        'images': ['https://storage.googleapis.com/bonjour-rail.appspot.com/apple-walnut-salad.png'],
                    },
                },
                'quantity': 1,
            },
        ],
        mode='payment',
        success_url=YOUR_DOMAIN + '/success/' + conversation_id,
        cancel_url=YOUR_DOMAIN + '/cancel/' + conversation_id,
    )

    return JsonResponse({
        'id': checkout_session.id
    })

  except Exception as e:
    # Handle exceptions according to your payment processor's documentation
    # https://stripe.com/docs/api/errors/handling?lang=python
    return HttpResponse(e)

...

Ces deux fonctions utilisent le champ "conversation_id" pour associer le panier à l'utilisateur, puis déterminer le prix auquel Stripe doit facturer l'utilisateur.

Ces deux méthodes constituent la première moitié du flux de paiement. Si vous déployez cette solution et que vous testez l'expérience, un formulaire de règlement Stripe s'affiche. Vous pouvez y effectuer un paiement à l'aide d'une carte de crédit test, comme indiqué dans la documentation destinée aux développeurs Stripe pour tester le règlement avec Visa.

La seconde moitié du processus consiste à faire revenir l'utilisateur dans la conversation après que nous avons reçu la réponse de Stripe concernant le paiement de l'utilisateur.

5. Réponses par bandes

Lorsqu'un utilisateur s'engage dans votre processus de paiement, il a réussi ou non à finaliser le paiement. Dans la fonction create_checkout_session, nous avons défini success_url et cancel_url. Stripe redirigera vers l'une de ces deux URL en fonction de l'état du paiement. Définissons ces deux routes dans urls.py, puis ajoutons deux fonctions d'affichage à bopis/views.py pour prendre en charge ces deux flux possibles.

Ajoutez ces lignes au fichier urls.py.

... 
    path('success/<str:conversation_id>', bopis_views.payment_success),
    path('cancel/<str:conversation_id>', bopis_views.payment_cancel),
...

Les vues correspondantes se présenteront comme suit:

... 

def payment_success(request, conversation_id):
  """Sends a notification to the user prompting them back into the conversation.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """
  message_obj = BusinessMessagesMessage(
      messageId=str(uuid.uuid4().int),
      representative=BOT_REPRESENTATIVE,
      suggestions=[
          BusinessMessagesSuggestion(
              reply=BusinessMessagesSuggestedReply(
                  text='Check on order', postbackData='check-order')),
      ],
      text='Awesome it looks like we\'ve received your payment.')

  send_message(message_obj, conversation_id)

  return render(request, 'bopis/success.html')


def payment_cancel(request, conversation_id):
  """Sends a notification to the user prompting them back into the conversation.

  Args:
    request (HttpRequest): Incoming Django request object
    conversation_id (str): The unique id for this user and agent.

  Returns:
    Obj (HttpResponse): Returns an HTTPResponse to the browser
  """
  message_obj = BusinessMessagesMessage(
      messageId=str(uuid.uuid4().int),
      representative=BOT_REPRESENTATIVE,
      suggestions=[
          BusinessMessagesSuggestion(
              action=BusinessMessagesSuggestedAction(
                  text='Checkout',
                  postbackData='checkout',
                  openUrlAction=BusinessMessagesOpenUrlAction(
                      url=f'{YOUR_DOMAIN}/checkout/{conversation_id}'))),
      ],
      text='It looks like there was a problem with checkout. Try again?')

  send_message(message_obj, conversation_id)

  return render(request, 'bopis/cancel.html')

...

Stripe redirige vers le domaine que vous avez spécifié dans la constante DOMAIN, ce qui signifie que vous devez afficher une réponse HTML via un modèle. Sinon, le site Web semblera très simple. Créons deux fichiers HTML simples dans le répertoire bopis/templates/bopis/ avec checkout.html.

bm-django-echo-bot/bopis/ templates/bopis/success.html

{% load static %}

<html>
<head>
  <title>Business Messages Payment Integration Sample!</title>
  <style>
    p{
      font-size: 4em;
    }
  </style>
</head>
<body>
  <section>

    <p>
      Checkout succeeded - We appreciate your business!
      <br/><br/>
      For support related questions, please email
      <a href="mailto:bm-support@google.com">bm-support@google.com</a>.

    </p>
  </section>
</body>
</html>

bm-django-echo-bot/bopis/ templates/bopis/cancel.html

{% load static %}

<html>
<head>
  <title>Checkout canceled</title>
  <style>
    p{
      font-size: 4em;
    }
    </style>
</head>
<body>
  <section>
    <p>Checkout canceled - Forgot to add something to your cart? Shop around then come back to pay!</p>
  </section>
</body>
</html>

Avec ces deux modèles, un utilisateur qui finalise le processus de paiement avec votre intégration Stripe est redirigé vers les URL appropriées et se voit présenter les modèles correspondants. Ils recevront également un message via Business Messages les invitant à reprendre la conversation.

6. Recevez des paiements !

Félicitations ! Vous avez intégré une société de traitement des paiements dans votre agent Business Messages.

Dans cette série, vous avez déployé une application Web sur Google Cloud App Engine, défini votre webhook dans la console de développement Business Communications, étendu l'application pour permettre la recherche d'inventaire via une base de données statique et créé un panier à l'aide de Google Datastore. Dans la dernière partie de la série, vous avez intégré à Stripe, une société de traitement des paiements compatible avec les intégrations Web et cette expérience. Vous pouvez désormais intégrer d'autres sociétés de traitement des paiements et plus encore.

D6D80cf9c9fc621.png 44db8d6441dce4c5.png

Étapes suivantes

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

Documents de référence