」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 將 Stripe 整合到單一產品 Django Python 商店中

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

發佈於2024-11-05
瀏覽:434

In the first part of this series, we created a Django online shop with htmx.

In this second part, we'll handle orders using Stripe.

What We'll Do

We'll integrate Stripe to handle payments securely. This is what we want to achieve:

  1. In the purchase view, we start by creating a Stripe checkout session and redirect the customer to the corresponding URL. This is where we tell Stripe about the product we are selling, its quantity, and where the customer should be redirected to after a successful purchase (the success_url).
  2. The customer fills in their payment details on the Stripe checkout page and completes the payment. Stripe then makes a POST request to a webhook endpoint on our website, where we listen to events and process them accordingly. If the payment is successful, we save the order in our database and notify the customer (and our staff users) about the purchase.
  3. Finally, if the webhook returns a response with a 200 OK HTTP status code, Stripe redirects to the success_url created in the first step.

Setting Up Stripe for Our Django Python Store

We first need to jump over to Stripe and do the following:

  1. Create a Stripe account.
  2. Create a product (with a payment id).
  3. Create a webhook.

1: Create a Stripe Account

Start by creating a Stripe account. For now, you don’t really need to activate your account. You can just work in test mode, which will prevent you from making real payments while testing. Go to the API keys page and retrieve the publishable and secret keys. Save them in your project environment variables (STRIPE_PUBLISHABLE_KEY and STRIPE_SECRET_KEY). We will use these keys to authenticate your Stripe requests.

2: Create Your Product

Create a new product on the products page. Fill out the details and set the payment type to one-off. Your product should look something like this:

Integrating Stripe Into A One-Product Django Python Shop

Once you press Add product, you should be able to see your product on the product list. If you click on it and scroll down to the Pricing section, you can find the API ID for the price item you created — it should be something like price_3ODP5…. Save it in an environment variable (STRIPE_PRICE_ID): you will need this when creating the Stripe checkout session.

3: Create the Webhook

We need to create a webhook endpoint for Stripe to call when a payment completes. In the webhooks page, choose to test in the local environment. This will allow you to forward the request to a local URL, like http://127.0.0.1:8000. Start by downloading the Stripe CLI. Then, you can:

  1. Log into Stripe
stripe login
  1. Forward events to the webhook endpoint that you will create:
stripe listen --forward-to http://127.0.0.1:8000/webhook
> Ready! Your webhook signing secret is whsec_06531a7ba22363ac038f284ac547906b89e5c939f8d55dfd03a3619f9adc590a (^C to quit)

This ensures that once a purchase is made, Stripe forwards the webhook calls to your local endpoint. The command will log a webhook signing secret, which you should also save as a project environment variable (STRIPE_WEBHOOK_SECRET). This will prove useful for verifying that a request does indeed come from Stripe and that you are handling the right webhook.

By the end of this section, you should have four Stripe environment variables. You can now load them in 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")

Note: We are using python-dotenv to load the environment variables.

Extend the Views

We now need to extend the views to integrate Stripe by creating a checkout session, a successful purchase view, and a webhook view.

1: Create a Stripe Checkout Session

In the purchase view, we'll create a Stripe checkout session if the purchase form is valid:

# 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})

Let’s break this down:

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

Thank you for your purchase

Your purchase was successful. You will receive an email with the details of your purchase soon.

{% 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
        """,
        "[email protected]",
        [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
            """,
        "[email protected]",
        [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:

  • Write tests - you can see some examples in the GitHub repository.
  • If you have more products to sell, create a database model for them, and connect the LineOrder through a ForeignKey.
  • Configure email settings according to Django's email documentation. You can also use libraries such as django-post-office to manage your email templates and queues.
  • Once you deploy your website, create an actual webhook (not a local listener).
  • Take a look at the Stripe docs for alternatives to the checkout process we've outlined, including an embedded checkout form.

Wrapping Up

In this two-part series, we successfully built a one-product e-commerce site using Django, htmx, and Stripe. This guide has walked you through setting up your Django project, integrating htmx for seamless user interactions, and incorporating secure payments with Stripe.

We also covered how to handle order processing, including saving order information to your database, notifying staff users of new purchases, and sending confirmation emails to your customers. With these foundations, you can further customize and expand your e-commerce site to suit your specific needs.

Happy coding!

P.S. If you'd like to read Python posts as soon as they get off the press, subscribe to our Python Wizardry newsletter and never miss a single post!

版本聲明 本文轉載於:https://dev.to/appsignal/integrating-stripe-in​​to-a-one-product-django-python-shop-41ep?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何使用Python理解有效地創建字典?
    如何使用Python理解有效地創建字典?
    在python中,詞典綜合提供了一種生成新詞典的簡潔方法。儘管它們與列表綜合相似,但存在一些顯著差異。 與問題所暗示的不同,您無法為鑰匙創建字典理解。您必須明確指定鍵和值。 For example:d = {n: n**2 for n in range(5)}This creates a dict...
    程式設計 發佈於2025-04-16
  • 如何將來自三個MySQL表的數據組合到新表中?
    如何將來自三個MySQL表的數據組合到新表中?
    mysql:從三個表和列的新表創建新表 答案:為了實現這一目標,您可以利用一個3-way Join。 選擇p。 *,d.content作為年齡 來自人為p的人 加入d.person_id = p.id上的d的詳細信息 加入T.Id = d.detail_id的分類法 其中t.taxonomy ...
    程式設計 發佈於2025-04-16
  • 如何從PHP中的數組中提取隨機元素?
    如何從PHP中的數組中提取隨機元素?
    從陣列中的隨機選擇,可以輕鬆從數組中獲取隨機項目。考慮以下數組:; 從此數組中檢索一個隨機項目,利用array_rand( array_rand()函數從數組返回一個隨機鍵。通過將$項目數組索引使用此鍵,我們可以從數組中訪問一個隨機元素。這種方法為選擇隨機項目提供了一種直接且可靠的方法。
    程式設計 發佈於2025-04-16
  • 如何從Google API中檢索最新的jQuery庫?
    如何從Google API中檢索最新的jQuery庫?
    從Google APIS 問題中提供的jQuery URL是版本1.2.6。對於檢索最新版本,以前有一種使用特定版本編號的替代方法,它是使用以下語法:獲取最新版本:未壓縮)While these legacy URLs still remain in use, it is recommended ...
    程式設計 發佈於2025-04-16
  • 如何有效地選擇熊貓數據框中的列?
    如何有效地選擇熊貓數據框中的列?
    在處理數據操作任務時,在Pandas DataFrames 中選擇列時,選擇特定列的必要條件是必要的。在Pandas中,選擇列的各種選項。 選項1:使用列名 如果已知列索引,請使用ILOC函數選擇它們。請注意,python索引基於零。 df1 = df.iloc [:,0:2]#使用索引0和1 ...
    程式設計 發佈於2025-04-16
  • 使用wp-api-angular連接Angular與WordPress API
    使用wp-api-angular連接Angular與WordPress API
    本教程使用 wp-api angular 庫將Angular 2應用程序與WordPress REST API連接。 該圖書館支持主要的WP資源(用戶,帖子,評論,媒體,分類法)。我們將構建展示其易用性的功能:JWT身份驗證,用戶和發布列表,以及Post Crud(創建,讀取,更新,刪除)操作。 ...
    程式設計 發佈於2025-04-16
  • 為什麼我在Silverlight Linq查詢中獲得“無法找到查詢模式的實現”錯誤?
    為什麼我在Silverlight Linq查詢中獲得“無法找到查詢模式的實現”錯誤?
    查詢模式實現缺失:解決“無法找到”錯誤在Silverlight應用程序中,嘗試使用LINQ建立LINQ連接以錯誤而實現的數據庫”,無法找到查詢模式的實現。”當省略LINQ名稱空間或查詢類型缺少IEnumerable 實現時,通常會發生此錯誤。 解決問題來驗證該類型的質量是至關重要的。在此特定實例...
    程式設計 發佈於2025-04-16
  • 如何從Python中的字符串中刪除表情符號:固定常見錯誤的初學者指南?
    如何從Python中的字符串中刪除表情符號:固定常見錯誤的初學者指南?
    從python import codecs import codecs import codecs 導入 text = codecs.decode('這狗\ u0001f602'.encode('utf-8'),'utf-8') 印刷(文字)#帶有...
    程式設計 發佈於2025-04-16
  • 如何使用替換指令在GO MOD中解析模塊路徑差異?
    如何使用替換指令在GO MOD中解析模塊路徑差異?
    在使用GO MOD時,在GO MOD 中克服模塊路徑差異時,可能會遇到衝突,其中可能會遇到一個衝突,其中3派對軟件包將另一個帶有導入套件的path package the Imptioned package the Imptioned package the Imported tocted pac...
    程式設計 發佈於2025-04-16
  • 如何使用FormData()處理多個文件上傳?
    如何使用FormData()處理多個文件上傳?
    )處理多個文件輸入時,通常需要處理多個文件上傳時,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    程式設計 發佈於2025-04-16
  • 為什麼我會收到MySQL錯誤#1089:錯誤的前綴密鑰?
    為什麼我會收到MySQL錯誤#1089:錯誤的前綴密鑰?
    mySQL錯誤#1089:錯誤的前綴鍵錯誤descript [#1089-不正確的前綴鍵在嘗試在表中創建一個prefix鍵時會出現。前綴鍵旨在索引字符串列的特定前綴長度長度,可以更快地搜索這些前綴。 了解prefix keys `這將在整個Movie_ID列上創建標準主鍵。主密鑰對於唯一識...
    程式設計 發佈於2025-04-16
  • 如何在其容器中為DIV創建平滑的左右CSS動畫?
    如何在其容器中為DIV創建平滑的左右CSS動畫?
    通用CSS動畫,用於左右運動 ,我們將探索創建一個通用的CSS動畫,以向左和右移動DIV,從而到達其容器的邊緣。該動畫可以應用於具有絕對定位的任何div,無論其未知長度如何。 問題:使用左直接導致瞬時消失 更加流暢的解決方案:混合轉換和左 [並實現平穩的,線性的運動,我們介紹了線性的轉換。...
    程式設計 發佈於2025-04-16
  • 在C#中如何高效重複字符串字符用於縮進?
    在C#中如何高效重複字符串字符用於縮進?
    在基於項目的深度下固定字符串時,重複一個字符串以進行凹痕,很方便有效地有一種有效的方法來返回字符串重複指定的次數的字符串。使用指定的次數。 constructor 這將返回字符串“ -----”。 字符串凹痕= new String(' - ',depth); console.W...
    程式設計 發佈於2025-04-16
  • Go web應用何時關閉數據庫連接?
    Go web應用何時關閉數據庫連接?
    在GO Web Applications中管理數據庫連接很少,考慮以下簡化的web應用程序代碼:出現的問題:何時應在DB連接上調用Close()方法? ,該特定方案將自動關閉程序時,該程序將在EXITS EXITS EXITS出現時自動關閉。但是,其他考慮因素可能保證手動處理。 選項1:隱式關閉終...
    程式設計 發佈於2025-04-16
  • Python中何時用"try"而非"if"檢測變量值?
    Python中何時用"try"而非"if"檢測變量值?
    使用“ try“ vs.” if”來測試python 在python中的變量值,在某些情況下,您可能需要在處理之前檢查變量是否具有值。在使用“如果”或“ try”構建體之間決定。 “ if” constructs result = function() 如果結果: 對於結果: ...
    程式設計 發佈於2025-04-16

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3