首頁  >  文章  >  後端開發  >  將 Stripe 整合到單一產品 Django Python 商店中

將 Stripe 整合到單一產品 Django Python 商店中

DDD
DDD原創
2024-09-18 22:52:53454瀏覽

在本系列的第一部分中,我們使用 htmx 建立了一個 Django 線上商店。

在第二部分中,我們將使用 Stripe 處理訂單。

我們會做什麼

我們將整合 Stripe 以安全地處理付款。這就是我們想要實現的目標:

  1. 在購買視圖中,我們首先建立 Stripe 結帳工作階段並將客戶重新導向到對應的 URL。我們在這裡告訴 Stripe 我們正在銷售的產品、數量以及成功購買後客戶應重定向到的位置(success_url)。
  2. 客戶在 Stripe 結帳頁面填寫付款詳細資料並完成付款。然後,Stripe 向我們網站上的 Webhook 端點發出 POST 請求,我們在其中監聽事件並進行相應的處理。如果付款成功,我們會將訂單保存在資料庫中,並通知客戶(以及我們的員工用戶)有關購買的資訊。
  3. 最後,如果 webhook 傳回帶有​​ 200 OK HTTP 狀態碼的回應,Stripe 會重新導向到第一步驟中建立的 success_url。

為我們的 Django Python 商店設定 Stripe

我們首先需要跳到 Stripe 並執行以下操作:

  1. 建立一個 Stripe 帳戶。
  2. 建立產品(帶有付款 ID)。
  3. 建立一個 webhook。

1:建立 Stripe 帳戶

先建立一個 Stripe 帳戶。目前,您實際上不需要啟動您的帳戶。您可以只在測試模式下工作,這將阻止您在測試時進行真正的付款。前往 API 金鑰頁面並檢索可發布金鑰和秘密金鑰。將它們保存在專案環境變數中(STRIPE_PUBLISHABLE_KEY 和 STRIPE_SECRET_KEY)。我們將使用這些金鑰來驗證您的 Stripe 請求。

2:創建您的產品

在產品頁面上建立新產品。填寫詳細資料並將付款類型設定為一次性。您的產品應如下所示:

Integrating Stripe Into A One-Product Django Python Shop

按下新增產品後,您應該能夠在產品清單中看到您的產品。如果您點擊它並向下捲動到定價部分,您可以找到您建立的價格項目的API ID - 它應該類似於price_3ODP5…。將其保存在環境變數 (STRIPE_PRICE_ID) 中:建立 Stripe 結帳會話時將需要它。

3:創建網路鉤子

我們需要建立一個 Webhook 端點,以便 Stripe 在付款完成時呼叫。在webhooks頁面,選擇在本地環境進行測試。這將允許您將請求轉發到本機 URL,例如 http://127.0.0.1:8000。首先下載 Stripe CLI。然後,您可以:

  1. 登入 Stripe
stripe login
  1. 將事件轉送到您將要建立的 Webhook 端點:
stripe listen --forward-to http://127.0.0.1:8000/webhook
> Ready! Your webhook signing secret is whsec_06531a7ba22363ac038f284ac547906b89e5c939f8d55dfd03a3619f9adc590a (^C to quit)

這可確保一旦進行購買,Stripe 會將 Webhook 呼叫轉發到您的本地端點。該命令將記錄一個 Webhook 簽章金鑰,您也應該將其儲存為專案環境變數 (STRIPE_WEBHOOK_SECRET)。這對於驗證請求確實來自 Stripe 並且您正在處理正確的 Webhook 非常有用。

在本節結束時,您應該有四個 Stripe 環境變數。現在您可以將它們載入到 ecommerce_site/settings.py 中:

# ecommerce_site/settings.py

import os
from dotenv import load_dotenv
load_dotenv()

STRIPE_PUBLISHABLE_KEY = os.environ.get("STRIPE_PUBLISHABLE_KEY")
STRIPE_SECRET_KEY = os.environ.get("STRIPE_SECRET_KEY")
STRIPE_PRICE_ID = os.environ.get("STRIPE_PRICE_ID")
STRIPE_WEBHOOK_SECRET = os.environ.get("STRIPE_WEBHOOK_SECRET")

注意:我們使用 python-dotenv 來載入環境變數。

拓展視野

我們現在需要透過建立結帳會話、成功購買視圖和 Webhook 視圖來擴展視圖以整合 Stripe。

1:建立 Stripe 結帳會話

在購買檢視中,如果購買表單有效,我們將建立一個 Stripe 結帳會話:

# ecommerce/views.py
from django_htmx import HttpResponseClientRedirect
from django.conf import settings
import stripe

@require_POST
def purchase(request):
    form = OrderForm(request.POST)
    if form.is_valid():
        quantity = form.cleaned_data["quantity"]

        # replace time.sleep(2) with the following code ⬇️

        # 1 - set stripe api key
        stripe.api_key = settings.STRIPE_SECRET_KEY

        # 2 - create success url
        success_url = (
            request.build_absolute_uri(
                reverse("purchase_success")
            )
            + "?session_id={CHECKOUT_SESSION_ID}"
        )

        # 3 - create cancel url
        cancel_url = request.build_absolute_uri(reverse("home"))

        # 4 - create checkout session
        checkout_session = stripe.checkout.Session.create(
            line_items=[
                {
                    "price": settings.STRIPE_PRICE_ID,
                    "quantity": quantity,
                }
            ],
            mode="payment",
            success_url=success_url,
            cancel_url=cancel_url
        )

        # 5 - redirect to checkout session url
        return HttpResponseClientRedirect(checkout_session.url)
    return render(request, "product.html", {"form": form})

讓我們分解一下:

  1. We first set the Stripe API key.
  2. We then create a successful purchase URL pointing to the purchase_success view (which we'll create in the next step). Stripe should automatically populate the CHECKOUT_SESSION_ID.
  3. We create a URL for when a purchase is canceled — for example, when the customer changes their mind. In this case, it’s just the home view.
  4. We create a Stripe checkout session with our price ID (the product identifier) and the quantity the customer wants to purchase.
  5. Stripe returns a session object from which we can extract the URL and redirect the customer. Since this request is coming from htmx, we can’t really use the standard Django redirect function. Instead, we use the django-htmx package, which provides this HttpResponseClientRedirect class.

2: Create the Successful Purchase View

After completing the purchase, Stripe will redirect the customer to our specified success_url. Here, we can handle the post-purchase logic:

from django.shortcuts import redirect

def purchase_success(request):
    session_id = request.GET.get("session_id")
    if session_id is None:
          return redirect("home")

    stripe.api_key = settings.STRIPE_SECRET_KEY
    try:
        stripe.checkout.Session.retrieve(session_id)
    except stripe.error.InvalidRequestError:
        messages.error(request, "There was a problem while buying your product. Please try again.")
        return redirect("home")
    return render(request, "purchase_success.html")

In this view, we first check if the session_id query parameter is present. If it is, we retrieve the corresponding session from Stripe using the secret key and the session_id. We then render the successful purchase template, which looks like this:

# ecommerce/templates/purchase_success.html {% extends "base.html" %} {% block
content %}
<section>
  <header>
    <h2>Thank you for your purchase</h2>
    <p>
      Your purchase was successful. You will receive an email with the details
      of your purchase soon.
    </p>
  </header>
</section>
{% endblock %}

You should also add it to the urlpatterns:

# ecommerce_site/urls.py

# ... same imports as before

urlpatterns = [
    # ... same urls as before
    path("purchase_success", views.purchase_success, name="purchase_success"),  # ⬅️ new
]

3: Create the Webhook View

While the customer is in the purchase process, and before they are redirected to the success view, Stripe will call our webhook endpoint (remember to have the webhook listener running, as explained in the earlier 'Create the Webhook' section of this post):

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

@csrf_exempt
def webhook(request):
    stripe.api_key = settings.STRIPE_SECRET_KEY
    sig_header = request.headers.get('stripe-signature')
    payload = request.body
    event = None
    try:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
            )
    except stripe.error.SignatureVerificationError:
        # Invalid signature
        return HttpResponse(status=400)

    # Handle the checkout.session.completed event
    if event.type == "checkout.session.completed":
        # TODO: create line orders
        return HttpResponse(status=200)
    return HttpResponse(status=400)

Let’s break this down:

  • We try to construct a Stripe event from the payload, the signature header, and the webhook secret: the first is used to build the actual event, and the last two variables are relevant to validate the authenticity of the request.
  • If the signature verification fails, we return a 400 HTTP response. Remember that Stripe is actually calling this endpoint, not our customer, so Stripe will know what to do in this scenario.
  • We check if the event type is checkout.session.completed, i.e., if a customer successfully paid for our product. For now, we don’t do much else here, but we will process the order in the next step.

Note: A Stripe event can have multiple types but we will only handle completed sessions in this post. However, you can (and should) extend a webhook by following the docs.

You should also add this view to urlpatterns:

# ecommerce_site/urls.py

# ... same imports as before

urlpatterns = [
    # ... same urls as before
    path("webhook", views.webhook, name="webhook"),  # ⬅️ new
]

If everything works well, once you click “buy”, you should be redirected to a Stripe payment page. Since we are in test mode, we can fill in the payment details with dummy data, like a 4242 4242 4242 4242 card:

Integrating Stripe Into A One-Product Django Python Shop

Once you press Pay, Stripe should call the webhook view and redirect you to the purchase_success view. Congratulations, you have successfully processed a payment with Stripe!

Create the Orders and Notify Users

Once a purchase is completed, we need to do a few things in the webhook view:

  • Save the order information in our database.
  • Notify staff users about the recent purchase.
  • Send a confirmation email to the customer.

Let’s create a LineOrder database model in ecommerce/models.py to store some of the order information:

# ecommerce/models.py

from django.db import models

class LineOrder(models.Model):
    quantity = models.IntegerField()
    name = models.CharField(max_length=255, null=True, blank=True)
    email = models.EmailField(null=True, blank=True)
    shipping_details = models.TextField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Order {self.id} - {self.quantity} units"

Remember to create and run the migrations:

python manage.py makemigrations # ⬅️ creates the migration files
python manage.py migrate # ⬅️ applies the migrations in the database

We can now create a function to process the orders and call it from the webhook view:

# ecommerce/views.py

@csrf_exempt
def webhook(request):
    # ...same code as before
        if event.type == "checkout.session.completed":
            create_line_orders(event.data.object) # ⬅️ new
            return HttpResponse(status=200)
        return HttpResponse(status=400)

# new ⬇️
def create_line_orders(session: stripe.checkout.Session):
    line_items = stripe.checkout.Session.list_line_items(session.id)
    for line_item in line_items.data:
        LineOrder.objects.create(
            name=session.customer_details.name,
            email=session.customer_details.email,
            shipping_details=session.shipping_details,
            quantity=line_item.quantity,
        )
    mail.send_mail(
        "Your order has been placed",
        f"""
        Hi {session.customer_details.name},
        Your order has been placed. Thank you for shopping with us!
        You will receive an email with tracking information shortly.

        Best,
        The one product e-commerce Team
        """,
        "from@example.com",
        [session.customer_details.email],
    )

    staff_users = User.objects.filter(is_staff=True)
    mail.send_mail(
        "You have a new order!",
        """
            Hi team!
            You have a new order in your shop! go to the admin page to see it.

            Best,
            The one product e-commerce Team
            """,
        "from@example.com",
        [user.email for user in staff_users],
    )

Let’s break this down:

  • We first create line order instances from the Stripe session and send a confirmation email to the customer about their purchase.
  • We then send an email to all staff users telling them to check the admin panel.

You can now register the LineOrder model in the admin panel, so it’s accessible to staff users:

# ecommerce/admin.py
from django.contrib import admin
from ecommerce.models import LineOrder

# Register your models here.
admin.site.register(LineOrder)

When staff users log in to the admin page, they will now be able to check new orders and process them accordingly — in this case, pack and ship mugs to the customer!

Some Tips to Optimize Your Django Store

Here are some tips to further improve on the store you've built:

  • 編寫測試 - 您可以在 GitHub 儲存庫中查看一些範例。
  • 如果您有更多產品要銷售,請為它們建立資料庫模型,並透過ForeignKey連接LineOrder。
  • 根據 Django 的電子郵件文件配置電子郵件設定。您也可以使用 django-post-office 等函式庫來管理您的電子郵件範本和佇列。
  • 部署網站後,建立一個實際的 Webhook(不是本機偵聽器)。
  • 查看 Stripe 文檔,以了解我們概述的結帳流程的替代方案,包括嵌入式結帳表單。

總結

在這個由兩部分組成的系列中,我們使用 Django、htmx 和 Stripe 成功建立了一個單一產品的電子商務網站。本指南引導您完成 Django 專案的設定、整合 htmx 以實現無縫用戶交互,以及將安全支付與 Stripe 結合。

我們也介紹如何處理訂單處理,包括將訂單資訊儲存到您的資料庫、通知員工使用者新購買的商品以及向您的客戶發送確認電子郵件。有了這些基礎,您可以進一步客製化和擴展您的電子商務網站以滿足您的特定需求。

編碼愉快!

P.S.如果您想在 Python 文章發布後立即閱讀,請訂閱我們的 Python Wizardry 時事通訊,不錯過任何一篇文章!

以上是將 Stripe 整合到單一產品 Django Python 商店中的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn