شراء الطلب على الإنترنت من المتجر: وجبة بونجور - الجزء 3 - الدمج مع معالج عمليات الدفع

1- المقدمة

53003251caaf2be5.png 8826bd8cb0c0f1c7.png

تاريخ التعديل الأخير: 13-09-2021

تحصيل الدفعات

من خلال جمع الدفعات في ميزة "الرسائل التجارية"، يمكنك الوصول إلى عالم جديد تمامًا من الفرص التجارية في منصّة المحادثات. لنفترض أنّ عميلاً محتملاً يرسل إليك استفسارًا عن منتج يريد معرفة المزيد من المعلومات عنه. وبعد الإجابة عن أسئلته، يمكنك إغلاق الصفقة معه من خلال تقديم مدخل للدفع داخل المحادثة مباشرةً.

fe0c6754fb69d708.png

ما الذي يوفّر تجربة دفع جيدة؟

تُعدّ تجربة الدفع الجيدة خيارًا يتيح للمستخدمين الدفع بالطريقة المعتادة.

عندما يفضّل المستخدمون طريقة الدفع، تكون طرق الدفع المختلفة أكثر شيوعًا من غيرها في مناطق مختلفة من العالم. باستخدام ميزة الرسائل التجارية، يمكنك الدمج مع أكثر من معالج معالجة واحد للحصول على أعلى درجة من الراحة للمستخدمين.

بعد أن يُكمل المستخدم عملية دفع معيّنة، تريد إعلامه باستلامك دفعتك بنجاح. تتضمّن معظم معالجات الدفع طلب نجاح أو فشل في الإرسال يُرسل طلب HTTP إلى عنوان URL من اختيارك عند اكتمال عملية الدفع.

ما يتم إنشاؤه

في القسم السابق من سلسلة الدرس التطبيقي حول الترميز، مدَّدت وكيل Bojour Meal لتقديم مجموعة من السلع وأنشأت سلة تسوّق تسمح للمستخدمين بإضافة عناصر وإزالتها، وحساب السعر الإجمالي لسلة التسوّق. في هذا القسم، ستوسّع موظّف الدعم ليتمكن من معالجة الدفعات استنادًا إلى محتوى سلة التسوّق.

في هذا الدرس التطبيقي حول الترميز، سيعمل تطبيقك على

  • الدمج مع بوابة الدفع Stripe
  • السماح للمستخدم بإكمال عملية الدفع استنادًا إلى سعر سلة التسوق
  • أرسِل إشعارًا إلى لوحة المحادثة مرة أخرى لإعلام المستخدم بحالة الدفع.

ba08a4d2f8c09c0e.png

المهام التي ستنفِّذها

  • الدمج مع معالج الدفع في Stripe
  • أرسل طلبًا إلى Stripe لبدء جلسة دفع.
  • معالجة طلبات الدفع أو الإخفاق في الدفع من Stripe.

المتطلبات

  • مشروع Google Cloud Platform مسجَّل وموافَق عليه للاستخدام مع الرسائل التجارية.
  • يمكنك الانتقال إلى الموقع الإلكتروني لمطوّري البرامج للحصول على تعليمات حول كيفية
  • يجب أن يكون لديك جهاز يعمل بالإصدار 5 أو إصدار أحدث من نظام التشغيل Android أو أن تستخدم جهازًا يعمل بنظام التشغيل iOS مثبّتًا عليه تطبيق "خرائط Google".
  • خبرة في برمجة تطبيقات الويب
  • اتصال بالإنترنت

2. إضافة تبعيات

جارٍ تعديل متطلبات ملف robots.txt

ونظرًا لدمجنا مع معالج الدفع في Stripe، يمكننا استخدام مكتبة عملاء Stripe Python. أضِف stripe إلىالمتطلبات.txt بدون إصدار للحصول على أحدث إصدار من الاعتمادية.

يعدّ ذلك ضروريًا حتى يتم تضمين وقت تشغيل Python لـ Google Cloud App Engine لتضمين وحدة Python الشريطية.

settings.txt

...
stripe
...

إعداد bopis/views.py

في أعلى قائمة bopis/views.py، يمكنك استيراد render من django.shortcuts وJsonResponse من django.http. بالإضافة إلى ذلك، سنحتاج إلى استيراد stripe لدعم المكالمات إلى مكتبة عملاء Stripe Python.

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

3- التعامل مع Stripe

إنشاء حساب على Stripe.com

في هذا الدرس التطبيقي حول الترميز، تبيّن لنا أننا نستخدم تطبيق Stripe، ولكن يمكنك الدمج مع أي معالج يتيح إمكانية دمج الويب. يمكنك إنشاء حساب على stripe.com. سنستخدم هذا الملف الشخصي لأغراض الاختبار والأغراض التعليمية لمعرفة كيفية الدمج مباشرةً مع أي جهة مسؤولة عن معالجة المعاملات تابعة لطرف ثالث.

6731d123c56feb67.png

بعد إنشاء حساب وتسجيل الدخول، من المفترض أن تظهر لك لوحة بيانات تبدو كما يلي.

6d9d165d2d1fbb8c.png

تأكّد من أنك تشغّل في "وضع الاختبار&; " وانقر على زر مطوّري البرامج كما هو موضّح في لقطة الشاشة أعلاه للبحث عن مفاتيح واجهة برمجة التطبيقات. من المفترض أن تظهر لك مجموعتَين من مفاتيح واجهة برمجة التطبيقات: مفتاح قابل للنشر ومفتاح سري. وستحتاج إلى هذين المفتاحين لتسهيل معاملات الدفع باستخدام Stripe.

تعديل bopi/views.py

يحتاج التطبيق إلى مجموعتَي مفاتيح، لذلك يُرجى تحديثهما في الملف الشخصي.py.

يمكنك ضبط المفتاح السري مباشرةً على الموقعأشرطةe.api_key وتخصيص قيمة المفتاح السري المتوفّرة في لوحة بيانات مطوّري البرامج في تطبيق Stripe. بعد ذلك، أنشئ متغيّرًا عموميًا باسم STRIPE_PUBLIC_KEY واضبطه على المفتاح القابل للنشر.

بالإضافة إلى ذلك، يحتاج Stripe إلى إعادة التوجيه إلى صفحة ويب تديرها، لذا يُرجى إنشاء متغيّر عمومي إضافي لتضمين نطاق تطبيقك المتاح للجميع.

في نهاية هذه التعديلات، ستحصل على ما يلي:

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

وهذا كل ما تحتاج إليه لتنفيذ إعداد Stripe.

4. وظائف الدفع

تعديل وظيفة السعر الإجمالي في سلّة التسوّق

في الوقت الحالي، ترسل دالة send_shopping_cart_total_price رسالة تحدد سعر سلة التسوق فقط. لنبدأ إجراءً مقترَحًا لفتح عنوان URL في صفحة الدفع.

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)

عندما ينقر المستخدم على "اقتراح الإجراء"، يتم توجيهه إلى صفحة ويب تعرض السعر الإجمالي وزر لبدء الدفع باستخدام Stripe.

لننشئ صفحة ويب بسيطة تدعم هذه العملية.

في رمز مصدر المشروع، ابحث عن الدليل باسم bopis. أنشئ دليلاً جديدًا ضمن قوائم bopis باسم templates، وضمن النماذج أنشئ دليلًا آخر باسم bopis. ويُعد هذا نمط تصميم Django لتحديد اسم التطبيق ضمن دليل النماذج. ويساعد ذلك في تقليل ارتباك النماذج بين تطبيقات Django.

من المفترض أن يكون لديك الآن دليل بمسار على bopis/templates/bopis/. يمكنك إنشاء ملفات HTML في هذا الدليل لعرض صفحات الويب. تشير استراتيجية Django إلى هذه النماذج التي يتم عرضها على المتصفّح. لنبدأ بـ checkout.html.

في هذا الدليل، عليك إنشاء checkout.html. يعرض مقتطف الرمز التالي زرًا للدفع وسعر سلة التسوّق. ويتضمن أيضًا JavaScript لبدء عملية دفع 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>

نحتاج إلى مسار إلى صفحة الويب هذه عند طلب عنوان URL. تم ضبط القيمة openUrlAction على عملية الدفع المقترَحة على {YOUR_DOMAIN}/checkout/{conversation_id}. ويتحوّل ذلك إلى نص مثل https://<GCP-Project-ID>.appspot.com/checkout/abc123-cba321-abc123-cba321. قبل إنشاء هذا المسار، لنبدأ في مراجعة JavaScript التي تم العثور عليها في نموذج 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>
...

لنستعرض مقتطف الرمز أعلاه معًا.

  1. أولاً، يتم إنشاء كيان Stripe بالمفتاح العام الذي يتم تمريره من خلال السياق من دالة العرض، وهو نموذج Django آخر.
  2. وبعد ذلك، يبحث المقتطف عن عنصر على الصفحة برقم التعريف checkout-button.
  3. تتم إضافة أداة معالجة الحدث إلى هذا العنصر.

سيتم تشغيل أداة معالجة الحدث هذه عندما ينقر أحد المستخدمين على هذا الزر، ما يؤدي إلى إرسال طلب POST إلى خادم الويب الذي تحدّده من خلال عنوان URL: {YOUR_DOMAIN}/create-checkout-session/{conversation_id}.

يمكنك الاطّلاع على منطق خادم الويب في المقتطفات أدناه. عندما ينقر المستخدم على الزر الذي يحتوي على رقم التعريف "checkout-button"نتوقع أن يعرض معرّف جلسة شريطية يتم إنشاؤه باستخدام واجهة برمجة تطبيقات Stripe التي تحدّد سعر سلة التسوق.

إذا تمكن الخادم من إنشاء معرّف جلسة صالح، سيعيد منطق التطبيق توجيه المستخدم إلى صفحة Stripe Checkout، وإلا فسيتم تنبيه المستخدم برسالة JavaScript عادية تفيد بحدوث خطأ.

لنبدأ بإضافة مسارات جديدة إلى مصفوفة urlpatterns لإتاحة إمكانية الوصول إلى صفحة الدفع وإنشاء معرّف الجلسة. أضف ما يلي إلى مصفوفة urlpatterns في عنوان URL:

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

بعد ذلك، لننشئ دوال العرض في view.py لعرض نموذج checkout.html وإنشاء جلسة الدفع في شريط.

... 

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)

...

تستخدم كلتا هاتين الميزتين chat_id لربط سلة التسوق بالمستخدم، ثم لتحديد السعر الذي يجب أن يحصّله "شريط الصور" مقابل المستخدم.

تشكّل هاتان الطريقتان النصف الأول من عملية الدفع. إذا نشرت هذا الإصدار واختبرت التجربة، سيظهر لك نموذج دفع في تطبيق Stripe حيث يمكنك إكمال عملية الدفع باستخدام بطاقة ائتمان تجريبية كما هو موضّح في مستندات مطوّري برامج Stripe لاختبار الدفع باستخدام Visa.

النصف الثاني من العملية هو الطريقة التي نعيد بها المستخدم إلى المحادثة بعد أن نتلقّى الرد من Stripe بشأن دفعة المستخدم.

5. تخطيطات الردود

عندما يتفاعل المستخدم مع خطوات الدفع، يكون قد نجح في عملية الدفع أو تعذّر إتمامها. في الدالة create_checkout_session، حدّدنا success_url وcancel_url. سيعيد تطبيق Stripe التوجيه إلى أحد عنوانَي URL هذين وفقًا لحالة الدفع. لنحدّد هذين المسارين في urls.py ثم أضِف دالتَي عرض إلى bopis/views.py لإتاحة تنفيذ هذين المسارين.

أضف هذه الأسطر إلى ملف urls.py.

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

وستبدو طرق العرض المقابلة على النحو التالي:

... 

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 على إعادة توجيه المستخدم إلى النطاق بالشكل الذي حددته في ثابت DOMAIN، ما يعني أنه عليك عرض استجابة HTML من خلال نموذج أو أن الموقع الإلكتروني سيبدو محدودًا جدًا. لننشئ ملفَي HTML بسيطَين في الدليل bopis/templates/bopis/ إلى جانب 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>

وباستخدام هذين النموذجين، تتم إعادة توجيه المستخدم الذي يُكمل عملية دفع باستخدام دمج Stripe إلى عناوين URL المناسبة، ويتم عرضه على النماذج ذات الصلة. وسيتلقّى أيضًا رسالة عبر ميزة "الرسائل التجارية" تتيح له العودة إلى المحادثة.

6. استلام الدفعات

تهانينا، لقد نجحت في دمج جهة مسؤولة عن معالجة المعاملات في وكيل الرسائل التجارية.

في هذه السلسلة، نشرت تطبيق ويب على Google Cloud App Engine، وحدّدت الرد التلقائي على الويب في وحدة تحكم مطوّري البرامج في "اتصالات الأنشطة التجارية"، ووسّعت التطبيق لدعم البحث في المستودع من خلال قاعدة بيانات ثابتة، وأنشأت سلة تسوق باستخدام Google Datastore. في الجزء الأخير من السلسلة، تم دمج Stripe، وهو جهة معالجة دفعات توفّر إمكانية دمج الويب وتقدّم هذه التجربة. يمكنك الآن التفاعل مع عمليات دمج مع جهات معالجة دفعات أخرى وغير ذلك.

d6d80cf9c9fc621.png 44db8d6441dce4c5.png

الخطوات التالية

عندما تكون مستعدًا، اطّلِع على بعض المواضيع التالية للتعرّف على التفاعلات الأكثر تعقيدًا التي يمكنك تحقيقها في ميزة "الرسائل التجارية":

المستندات المرجعية