خرید آنلاین پیکاپ در فروشگاه: Bonjour Meal - قسمت 3 - ادغام با یک پردازشگر پرداخت

1. مقدمه

53003251caaf2be5.png8826bd8cb0c0f1c7.png

آخرین به روز رسانی: 2021-09-13

جمع آوری پرداخت ها!

جمع‌آوری پرداخت‌ها در پیام‌های تجاری، دنیای جدیدی از فرصت‌های تجاری را در بستر مکالمه به شما باز می‌کند. تصور کنید یک مشتری بالقوه درباره محصولی که می‌خواهد درباره آن اطلاعات بیشتری کسب کند، درخواستی برای شما ارسال می‌کند. هنگامی که آنها به سؤالات خود پاسخ دادند، می توانید با ارائه یک دروازه پرداخت درست در داخل مکالمه، معامله را با آنها ببندید.

fe0c6754fb69d708.png

تجربه پرداخت خوب چیست؟

تجربه پرداخت خوب، تجربه ای است که کاربران می توانند به روشی که به آن عادت کرده اند پرداخت کنند.

در مورد نحوه پرداخت، کاربران ترجیحاتی دارند و روش‌های پرداخت مختلف نسبت به سایرین در نقاط مختلف جهان رایج‌تر است. با پیام‌های تجاری، می‌توانید با بیش از یک پردازشگر پرداخت یکپارچه شوید تا بیشترین میزان راحتی را برای کاربران داشته باشید.

هنگامی که کاربر یک جریان پرداخت را تکمیل می کند، می خواهید به او اطلاع دهید که پرداخت خود را با موفقیت دریافت کرده اید. اکثر پردازشگرهای پرداخت شامل یک پاسخ تماس موفقیت آمیز یا ناموفق هستند که پس از اتمام جریان پرداخت، یک درخواست HTTP را به URL انتخابی شما ارسال می کند.

چیزی که خواهی ساخت

در بخش قبلی مجموعه کدلب، شما عامل Bonjour Meal را برای ارائه کاتالوگ اقلام گسترش دادید، یک سبد خرید ایجاد کردید که به کاربران اجازه می‌دهد اقلام اضافه و حذف کنند، و قیمت کل سبد خرید را محاسبه کردید. در این بخش، می‌خواهید نماینده را بیشتر گسترش دهید تا بتواند پرداخت‌ها را براساس محتویات سبد خرید پردازش کند.

در این لبه کد، برنامه شما این کار را انجام می دهد

  • با درگاه پرداخت Stripe یکپارچه شوید
  • به کاربر اجازه دهید تا جریان پرداخت را بر اساس قیمت سبد خرید کامل کند
  • برای اطلاع کاربر از وضعیت پرداخت، یک اعلان به صفحه مکالمه ارسال کنید

ba08a4d2f8c09c0e.png

کاری که خواهی کرد

  • با پردازشگر پرداخت Stripe یکپارچه شوید.
  • برای شروع جلسه پرداخت، درخواستی به Stripe ارسال کنید.
  • پاسخ های موفقیت یا عدم موفقیت پرداخت را از Stripe مدیریت کنید.

آنچه شما نیاز دارید

  • یک پروژه GCP که برای استفاده با Business Messages ثبت و تأیید شده است
  • برای دستورالعمل‌های نحوه انجام ، سایت توسعه‌دهنده ما را بررسی کنید
  • یک دستگاه Android با نسخه 5 یا بالاتر یا یک دستگاه iOS با برنامه Google Maps
  • تجربه برنامه نویسی وب اپلیکیشن
  • اتصال به اینترنت!

2. افزودن وابستگی ها

در حال به روز رسانی requires.txt

از آنجایی که با پردازشگر پرداخت Stripe ادغام می‌شویم، می‌توانیم از کتابخانه کلاینت Stripe Python استفاده کنیم. برای دریافت آخرین نسخه وابستگی، stripe را بدون نسخه به requirement.txt اضافه کنید.

این برای Google Cloud App Engine Python لازم است تا ماژول Python خطی را در بر بگیرد.

الزامات. txt

...
stripe
...

در حال آماده سازی bopis/views.py

در بالای bopis/views.py، render از django.shortcuts و JsonResponse از django.http وارد کنید. علاوه بر این، برای پشتیبانی از تماس‌ها به کتابخانه سرویس گیرنده Stripe Python، باید stripe را وارد کنیم.

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

3. کار با Stripe

یک حساب کاربری در Stripe.com ایجاد کنید

در این کد لبه، ما اتفاقاً از Stripe استفاده می کنیم، اما شما می توانید با هر پردازنده ای که از یکپارچه سازی وب پشتیبانی می کند، ادغام کنید. یک حساب کاربری در stripe.com ایجاد کنید. ما از این نمایه برای اهداف آزمایشی و آموزشی استفاده خواهیم کرد تا نحوه ادغام مستقیم با هر پردازشگر پرداخت شخص ثالث را بیاموزیم.

6731d123c56feb67.png

پس از ایجاد یک حساب کاربری و ورود به سیستم، باید داشبوردی را مشاهده کنید که شبیه این است.

6d9d165d2d1fbb8c.png

مطمئن شوید که در حالت تست کار می کنید و روی دکمه Developers همانطور که در تصویر بالا مشخص شده است کلیک کنید تا کلیدهای API خود را جستجو کنید. شما باید دو مجموعه از کلیدهای API را ببینید: یک کلید قابل انتشار و یک کلید مخفی . برای تسهیل تراکنش های پرداخت با Stripe به هر دوی این کلیدها نیاز دارید.

bopis/views.py را به روز کنید

برنامه شما به هر دو مجموعه کلید نیاز دارد، بنابراین آنها را در views.py به روز کنید.

می‌توانید کلید مخفی را مستقیماً روی ویژگی stripe.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 ایجاد کنید. این یک الگوی طراحی جنگو برای تعیین نام برنامه در فهرست قالب ها است. این به کاهش سردرگمی قالب ها بین برنامه های جنگو کمک می کند.

اکنون باید دایرکتوری با مسیری در bopis/templates/bopis/ . شما می توانید فایل های HTML را در این دایرکتوری برای ارائه صفحات وب ایجاد کنید. جنگو به آنها به عنوان الگوهایی اشاره می کند که به مرورگر ارائه می شوند. بیایید با checkout.html شروع کنیم.

در این فهرست، checkout.html را ایجاد کنید. قطعه کد زیر دکمه پرداخت و قیمت سبد را نشان می دهد. همچنین شامل جاوا اسکریپت برای شروع پرداخت 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 درخواست می شود، به یک مسیر به این صفحه وب نیاز داریم. تسویه حساب suggestedAction دارای یک مقدار openUrlAction است که روی {YOUR_DOMAIN}/checkout/{conversation_id} تنظیم شده است. این به چیزی شبیه https://<GCP-Project-ID>.appspot.com/checkout/abc123-cba321-abc123-cba321 می شود. قبل از ایجاد این مسیر، اجازه دهید جاوا اسکریپت موجود در قالب 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 با کلید عمومی ایجاد می کند که از طریق متن از تابع view منتقل می شود، یکی دیگر از الگوهای جنگو.
  2. سپس، قطعه با checkout-button دنبال عنصری در صفحه می‌گردد.
  3. شنونده رویداد به آن عنصر اضافه می شود.

این شنونده رویداد زمانی فعال می‌شود که کاربر روی این دکمه کلیک کند یا روی آن ضربه بزند که درخواست POST را به سرور وب که از طریق URL تعیین کرده‌اید آغاز می‌کند: {YOUR_DOMAIN}/create-checkout-session/{conversation_id}.

می توانید منطق وب سرور را در قطعه های زیر مشاهده کنید. هنگامی که کاربر روی دکمه با شناسه " checkout-button " ضربه می‌زند، می‌توان انتظار داشت که شناسه جلسه Stripe که با استفاده از Stripe API ایجاد شده و قیمت سبد خرید را مشخص می‌کند، بازگرداند.

اگر سرور شما توانست یک Session ID معتبر تولید کند، منطق برنامه کاربر را به صفحه Stripe Checkout هدایت می کند، در غیر این صورت، با یک پیام استاندارد جاوا اسکریپت به کاربر هشدار می دهد که مشکلی پیش آمده است.

بیایید با اضافه کردن مسیرهای جدید به آرایه الگوهای url برای پشتیبانی از صفحه پرداخت و تولید Session ID شروع کنیم. موارد زیر را به آرایه urlpatterns در urls.py اضافه کنید.

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

سپس اجازه دهید توابع view را در views.py ایجاد کنیم تا الگوی checkout.html را برگردانیم و جلسه پرداخت 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)

...

هر دوی این توابع از talk_id برای مرتبط کردن سبد خرید به کاربر و سپس تعیین قیمتی که Stripe باید از کاربر دریافت کند، استفاده می‌کنند.

این دو روش نیمه اول جریان پرداخت را تشکیل می دهند. اگر این را اجرا کنید و تجربه را آزمایش کنید، فرم پرداخت Stripe را مشاهده خواهید کرد که در آن می‌توانید با یک کارت اعتباری آزمایشی پرداخت را تکمیل کنید، همانطور که در مستندات برنامه‌نویس Stripe برای آزمایش پرداخت Visa توصیه شده است.

نیمه دوم جریان نحوه بازگرداندن کاربر به مکالمه پس از دریافت پاسخ از Stripe در مورد پرداخت کاربر است.

5. پاسخ های راه راه

هنگامی که کاربر در جریان پرداخت شما قرار می گیرد، یا موفق به تکمیل پرداخت شده یا ناموفق بوده است. در تابع create_checkout_session ، یک success_url و یک cancel_url تعریف کردیم. Stripe بسته به وضعیت پرداخت به یکی از این دو URL هدایت می شود. بیایید این دو مسیر را در urls.py تعریف کنیم و سپس دو تابع view را به 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')

...

همانطور که در ثابت DOMAIN مشخص کرده‌اید، Stripe به دامنه بازمی‌گردد، به این معنی که باید یک پاسخ 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 های مناسب هدایت می شود و با الگوهای مربوطه ارائه می شود. آنها همچنین پیامی از طریق Business Messages دریافت خواهند کرد که به آنها امکان می دهد به مکالمه بازگردند.

6. دریافت پرداخت!

تبریک می‌گوییم، شما با موفقیت یک پردازشگر پرداخت را در نماینده پیام‌های تجاری خود ادغام کردید!

در این مجموعه، شما یک برنامه وب را در Google Cloud App Engine اجرا کردید، وب هوک خود را بر روی کنسول توسعه دهنده ارتباطات تجاری تنظیم کردید، برنامه را برای پشتیبانی از جستجوی موجودی از طریق پایگاه داده ایستا گسترش دادید و یک سبد خرید با استفاده از Google Datastore ایجاد کردید. در قسمت پایانی مجموعه، شما با Stripe، یک پردازشگر پرداخت که از ادغام وب پشتیبانی می کند و با این تجربه، یکپارچه شده اید. اکنون می توانید در یکپارچگی با سایر پردازشگرهای پرداخت و موارد دیگر شرکت کنید!

d6d80cf9c9fc621.png44db8d6441dce4c5.png

بعدش چی؟

وقتی آماده شدید، برخی از موضوعات زیر را بررسی کنید تا در مورد تعاملات پیچیده تری که می توانید در Business Messages به دست آورید، بیاموزید:

اسناد مرجع