Django与Stripe集成:实现单品与订单支付的教程

聖光之護
发布: 2025-11-28 13:56:51
原创
512人浏览过

Django与Stripe集成:实现单品与订单支付的教程

本教程详细介绍了如何在django项目中集成stripe支付,以支持单品购买和多商品订单支付。内容涵盖了stripe checkout会话的创建、django模型与视图的实现、url路由配置以及前端javascript集成。同时,文章还提供了针对常见404错误和代码逻辑问题的排查与优化建议,旨在帮助开发者构建稳定可靠的电商支付系统。

在现代Web应用中,集成第三方支付平台是实现电子商务功能的关键一环。Stripe以其强大的API和灵活的集成方式,成为许多开发者的首选。本教程将以一个Django项目为例,展示如何利用Stripe Checkout实现商品和订单的支付流程。

1. 项目概览与模型设计

为了处理商品和订单,我们需要定义相应的Django模型。

models.py

from django.db import models

class Item(models.Model):
    """
    商品模型,包含名称、描述和价格。
    """
    name = models.CharField(max_length=100)
    description = models.TextField()
    price = models.IntegerField(default=0)  # 价格以美分存储,方便Stripe处理

    def __str__(self):
        return self.name

    def display_price(self):
        """格式化显示价格为美元"""
        return "{0:.2f}".format(self.price / 100) # 注意:这里假设price是美分,显示时除以100

class Order(models.Model):
    """
    订单模型,包含客户信息、创建时间及关联的商品。
    """
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField()
    address = models.CharField(max_length=250)
    postal_code = models.CharField(max_length=20)
    city = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    paid = models.BooleanField(default=False)
    items = models.ManyToManyField(Item, through='OrderItem') # 通过中间模型关联商品

    class Meta:
        ordering = ('-created_at',)

    def __str__(self):
        return f'Order {self.id} | {self.created_at.strftime("%d.%m.%Y %H:%M")}'

    def get_total_cost(self):
        """计算订单总价"""
        # 注意:这里需要遍历OrderItem来获取实际的购买价格和数量
        # 如果OrderItem的price字段代表的是购买时的单价,则应使用OrderItem的price
        # 否则,如果OrderItem只关联Item,且Item的price是实时价格,则使用Item.price
        # 假设OrderItem的price和quantity是准确的
        return sum(item.get_cost() for item in self.order_items.all()) # 访问related_name 'order_items'

class OrderItem(models.Model):
    """
    订单项模型,用于连接订单和商品,并记录购买时的价格和数量。
    """
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='order_items')
    item = models.ForeignKey(Item, related_name='order_items', on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=10, decimal_places=2) # 购买时的单价
    quantity = models.PositiveIntegerField(default=1)

    def __str__(self):
        return f'{self.id}'

    def get_cost(self):
        """计算单个订单项的总价"""
        return self.price * self.quantity
登录后复制

模型说明:

  • Item:代表可单独购买的商品。price字段以整数形式存储价格(例如,1000代表10.00美元),这是Stripe推荐的做法,避免浮点数精度问题。
  • Order:代表一个订单,包含客户信息和订单创建时间。它通过 OrderItem 中间模型与 Item 建立多对多关系。get_total_cost 方法用于计算订单的总金额。
  • OrderItem:作为 Order 和 Item 之间的桥梁,记录了特定商品在特定订单中的购买价格和数量,这对于处理价格变动至关重要。

2. Stripe Checkout会话创建视图

Stripe Checkout通过创建会话(Session)来引导用户完成支付。我们需要为单品购买和订单购买分别创建视图。

views.py

import stripe
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.views import View
from django.views.generic import TemplateView

from .models import Item, Order, OrderItem

# 初始化Stripe API密钥
stripe.api_key = settings.STRIPE_SECRET_KEY

class ProductLandingPageView(TemplateView):
    """
    单个商品详情页视图。
    """
    template_name = 'landing.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        item_id = self.kwargs["item_id"]
        item = get_object_or_404(Item, id=item_id)
        context['item'] = item
        context.update({
            "STRIPE_PUBLIC_KEY": settings.STRIPE_PUBLISHABLE_KEY
        })
        return context

class CreateCheckoutSessionView(View):
    """
    创建单个商品Stripe Checkout会话的视图。
    """
    def get(self, request, *args, **kwargs):
        item_id = self.kwargs["item_id"]
        # 确保域名与Stripe配置的Webhooks一致,生产环境应使用HTTPS
        DOMAIN: str = 'http://127.0.0.1:8000' 
        item = get_object_or_404(Item, id=item_id) # 使用get_object_or_404更健壮

        session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=[
                {
                    'price_data': {
                        'currency': 'usd',
                        'unit_amount': item.price,  # 确保这里是美分
                        'product_data': {
                            'name': item.name,
                        },
                    },
                    'quantity': 1,
                },
            ],
            payment_intent_data={
                'metadata': {
                    'item_id': item.id,
                },
            },
            mode='payment',
            success_url=DOMAIN + '/success/',
            cancel_url=DOMAIN + '/cancel/',
        )
        return JsonResponse({'id': session.id})

class CreateCheckoutSessionOrderView(View):
    """
    创建订单Stripe Checkout会话的视图。
    """
    def get(self, request, *args, **kwargs):
        order_id = self.kwargs["order_id"]
        DOMAIN: str = 'http://127.0.0.1:8000'
        order = get_object_or_404(Order, id=order_id) # 使用get_object_or_404更健壮

        # 针对订单,Stripe Checkout通常有两种处理方式:
        # 1. 将整个订单作为一个“产品”,价格为订单总价。
        # 2. 将订单中的每个商品作为单独的line_item。
        # 这里采用第一种方式,将订单总价作为单个line_item。

        # 优化:确保订单总价计算正确,且Stripe产品名称使用str(order)获取
        total_cost_cents = int(order.get_total_cost() * 100) # 将Decimal转换为整数美分

        session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=[
                {
                    'price_data': {
                        'currency': 'usd',
                        'unit_amount': total_cost_cents, # 订单总价(美分)
                        'product_data': {
                            'name': str(order), # 正确调用__str__方法
                        },
                    },
                    'quantity': 1, # 订单整体数量为1
                },
            ],
            payment_intent_data={
                'metadata': {
                    'order_id': order.id,
                },
            },
            mode='payment',
            success_url=DOMAIN + '/success/',
            cancel_url=DOMAIN + '/cancel/',
        )
        return JsonResponse({'id': session.id})

class OrderView(TemplateView):
    """
    订单详情页视图。
    """
    template_name = 'order.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        order_id = self.kwargs["order_id"]
        order = get_object_or_404(Order, id=order_id)
        context['order'] = order
        context.update({
            "STRIPE_PUBLIC_KEY": settings.STRIPE_PUBLISHABLE_KEY,
            'name': str(order), # 确保这里也正确调用__str__
            'items': order.order_items.all(), # 访问OrderItem而不是Item
            'total': order.get_total_cost()
        })
        return context

class SuccessView(TemplateView):
    """
    支付成功页面视图。
    """
    template_name = "success.html"

class CancelView(TemplateView):
    """
    支付取消页面视图。
    """
    template_name = "cancel.html"
登录后复制

视图说明与优化:

  • ProductLandingPageView 和 OrderView:用于展示商品和订单详情,并将Stripe公钥传递给前端。
  • CreateCheckoutSessionView:处理单个商品的支付。line_items中包含一个商品的信息,unit_amount直接使用item.price(假设已是美分)。
  • CreateCheckoutSessionOrderView:处理订单的支付。
    • 关键优化1:product_data['name'] 应使用 str(order) 或 order.__str__() 来正确获取订单的字符串表示,而不是 order.__str__ (方法引用)。
    • 关键优化2:unit_amount 需将 order.get_total_cost() 返回的Decimal类型转换为整数美分,即 int(order.get_total_cost() * 100)。
    • 这里选择将整个订单作为一个line_item,其价格为订单总价。如果需要Stripe Checkout页面显示订单中的所有商品列表,则需要遍历order.order_items.all(),为每个OrderItem创建一个line_item。
  • SuccessView 和 CancelView:简单的模板视图,用于在支付成功或取消后重定向用户。

3. URL路由配置

URL配置将HTTP请求映射到相应的Django视图。

eShop公众号商城
eShop公众号商城

项目介绍: eShop是基于eFrameWork低代码开发平台搭建的微信公众号商城系统,主要功能包括:产品、订单、购物车、收藏、收货地址。已集成微信登录、微信支付、分享等接口。更多功能可自行二次开发实现。 当前发布的数据库有两个版本,SQLServer和SQLite(无需安装数据库),默认为SQLite,根据实际需要切换。 项目版本:VS2012+, 数据库版本:S

eShop公众号商城 13
查看详情 eShop公众号商城

urls.py

from api.views import (
    CancelView, SuccessView, CreateCheckoutSessionView, 
    ProductLandingPageView, OrderView, CreateCheckoutSessionOrderView
)
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    # 订单支付Checkout会话创建,注意添加了尾部斜杠
    path('order_checkout/<int:order_id>/', CreateCheckoutSessionOrderView.as_view(), name='order_checkout'),
    # 订单详情页
    path('order/<int:order_id>/', OrderView.as_view(), name='order'),
    # 单个商品详情页
    path('item/<int:item_id>/', ProductLandingPageView.as_view(), name='item'),
    # 单个商品支付Checkout会话创建
    path('buy/<int:item_id>/', CreateCheckoutSessionView.as_view(), name='buy'),
    # 支付取消页
    path('cancel/', CancelView.as_view(), name='cancel'),
    # 支付成功页
    path('success/', SuccessView.as_view(), name='success'),
]
登录后复制

URL配置说明与404排查:

  • 404错误分析:原始问题中 /order_checkout/1/ 路径出现404错误,而URL配置是 path('order_checkout/<int:order_id>', ...)。Django的URL解析器对尾部斜杠非常敏感。如果 APPEND_SLASH 设置为 False (非默认),那么 /order_checkout/1/ 将不会匹配 order_checkout/<int:order_id>。
  • 解决方案:为了确保URL匹配的健壮性,建议在URL模式的末尾也添加斜杠,使其与前端请求的URL保持一致。例如,将 path('order_checkout/<int:order_id>', ...) 修改为 path('order_checkout/<int:order_id>/', ...)。这使得无论 APPEND_SLASH 设置如何,URL匹配都更加明确。

4. 前端集成与支付流程

前端负责触发Stripe Checkout流程。

order.html (示例,landing.html 类似)

{% extends 'base.html' %}

{% block title %}
    订单支付
{% endblock %}

{% block content %}
    <div class="description">
        <h3>{{ name }}</h3>
        <div class="order-info">
            <h5>总成本 {{ total }} USD.</h5>
        </div>
        <button id="buy-button" data-order-id="{{ order.id }}">购买</button>
    </div>

    <script src="https://js.stripe.com/v3/"></script>
    <script>
        document.getElementById('buy-button').addEventListener('click', function() {
            var orderId = this.getAttribute('data-order-id');
            var stripe = Stripe("{{ STRIPE_PUBLIC_KEY }}"); // 从Django上下文获取Stripe公钥

            // 发送请求到Django后端创建Checkout会话
            fetch('/order_checkout/' + orderId + '/') // 确保URL与urls.py中的模式一致,包含尾部斜杠
                .then(function(response) {
                    if (!response.ok) {
                        // 如果后端返回非2xx状态码,抛出错误
                        return response.json().then(errorData => {
                            throw new Error(errorData.message || '后端创建会话失败');
                        });
                    }
                    return response.json();
                })
                .then(function (session) {
                    // 重定向到Stripe Checkout页面
                    return stripe.redirectToCheckout({ sessionId: session.id });
                })
                .catch(function(error) {
                    console.error('支付流程出错:', error);
                    alert('支付过程中发生错误,请稍后再试。'); // 友好提示用户
                });
        });
    </script>
{% endblock %}
登录后复制

前端说明:

  • 引入Stripe.js库:<script src="https://js.stripe.com/v3/"></script>。
  • 通过 data-order-id 获取订单ID。
  • 点击“购买”按钮时,JavaScript会向Django后端发送一个 fetch 请求到 /order_checkout/<order_id>/ 路径。
  • 后端返回Stripe Checkout Session ID后,使用 stripe.redirectToCheckout({ sessionId: session.id }) 将用户重定向到Stripe的支付页面。
  • 注意:fetch 请求的URL '/order_checkout/' + orderId + '/' 必须与 urls.py 中定义的模式(包括尾部斜杠)精确匹配。

5. 支付结果处理

用户完成Stripe Checkout后,Stripe会将用户重定向到 success_url 或 cancel_url。

  • success.html:显示支付成功的消息。
  • cancel.html:显示支付取消的消息。

在实际生产环境中,支付成功后,您需要通过Stripe Webhooks来异步更新订单状态,而不是仅仅依赖 success_url。因为用户可能在支付成功页面加载前关闭浏览器,或者支付本身是异步完成的。

6. 总结与注意事项

以上就是Django与Stripe集成:实现单品与订单支付的教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号