Django 从 0 到 1 打造完整电商平台:集成支付宝沙箱支付

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。


上一篇我们完成了从购物车到订单的全流程------用户勾选商品、选择地址、提交订单,数据库里已经有了待支付的订单。但用户还没有付钱!今天我们要打通电商的"最后一公里"------集成支付宝沙箱支付,让用户真正扫码付款,完成交易闭环。

支付宝沙箱环境是支付宝官方提供的测试环境,不需要真实资金,不需要营业执照,任何人都可以免费申请。开发阶段用它来调试支付流程,完全够用。本篇我会手把手带你申请沙箱、配置密钥、安装 SDK、编写支付视图,并处理好同步回跳和异步通知两个回调逻辑。

提示 :接入支付宝支付并非只有 python-alipay-sdk 这一个库,你也可以选择支付宝官方提供的 alipay-sdk-pythonpython-alipay-sdk 是社区维护的第三方 SDK(GitHub 仓库:fzlee/alipay),因其封装简洁、对 Django 友好,本系列选择使用它。-


一、支付宝沙箱环境申请与配置

1.1 什么是沙箱环境?

支付宝沙箱是支付宝提供给开发者的测试环境 ,它拥有独立的 APPID、独立的密钥、独立的网关地址,以及专属的测试账号(买家、卖家各一个)。在沙箱环境中,所有支付操作都不会产生真实资金流动 ,余额可以随意设置,支付密码也是固定的。-1

沙箱的核心信息包括:

1.2 申请步骤

步骤 1:登录支付宝开放平台

访问 支付宝开放平台,用你的支付宝账号登录(推荐手机扫码登录)。-17

步骤 2:进入沙箱环境

登录后,点击右上角「控制台」→ 在左侧菜单或页面底部找到「沙箱环境」,进入沙箱应用页面。沙箱环境的链接是:open.alipay.com/develop/san...

进入后,你会看到系统已自动为你创建了一个沙箱应用,页面上展示了:

  • APPID(记录下来,后续配置用)

  • 应用私钥 / 应用公钥 / 支付宝公钥(沙箱默认提供,可直接使用)

  • 支付宝网关地址

  • 沙箱买家账号和卖家账号(含登录密码、支付密码)

步骤 3:获取密钥(两种方式)

方式一(推荐新手): 使用沙箱页面自动生成的默认密钥。直接复制应用私钥和支付宝公钥即可,无需自己生成。-1

方式二: 自定义密钥。下载支付宝官方「开放平台开发助手」工具,选择 RSA2、PKCS1 格式生成密钥对,将应用公钥 上传到沙箱的「接口加签方式」中,保存后获取支付宝公钥-17

⚠️ 重要提示 :沙箱的 APPID、密钥和正式环境完全不同,不能混用。沙箱买家账号也必须在沙箱页面查看,用你自己的真实支付宝扫码支付会报错。-27

步骤 4:保存关键信息

申请完成后,你需要保存以下信息备用(建议新建一个文本文件):

bash 复制代码
APPID: 你的沙箱APPID
应用私钥: MIIEpQIBAAKCAQEA...(完整复制,不要漏字符)
支付宝公钥: MIIBIjANBgkqhkiG...(完整复制)
网关地址: https://openapi-sandbox.dl.alipaydev.com/gateway.do
沙箱买家账号: sandbox_xxx@xxx.com
沙箱买家登录密码: 111111
沙箱买家支付密码: 111111

二、安装 Python 支付宝 SDK

进入项目虚拟环境,安装 python-alipay-sdk

bash 复制代码
pip install python-alipay-sdk==3.3.0

控制台输出:

bash 复制代码
Collecting python-alipay-sdk==3.3.0
  Downloading python_alipay_sdk-3.3.0-py3-none-any.whl
Installing collected packages: python-alipay-sdk
Successfully installed python-alipay-sdk-3.3.0

这个 SDK 封装了支付宝复杂的签名和验签逻辑,我们只需要调用几个关键方法即可。-1

同时,SDK 依赖 pycryptodome 进行加密运算,确保已安装:


三、配置 settings.py

将支付宝沙箱的参数写入 django_ecommerce/settings.py 末尾:

bash 复制代码
# ==================== 支付宝沙箱环境配置 ====================
ALIPAY_APPID = '你的沙箱APPID'  # 替换为真实 APPID
ALIPAY_DEBUG = True  # 沙箱环境设为 True,正式环境改为 False

# 支付宝沙箱网关地址
ALIPAY_GATEWAY = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'

# 同步回调地址:用户支付成功后,支付宝自动跳回的页面(后续会配置路由)
ALIPAY_RETURN_URL = 'http://127.0.0.1:8000/payment/return/'

# 异步通知地址:支付宝服务器主动 POST 通知支付结果(本地开发时可用内网穿透工具)
ALIPAY_NOTIFY_URL = 'http://127.0.0.1:8000/payment/notify/'

# 应用私钥(从沙箱页面复制,注意保持原始格式)
ALIPAY_APP_PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY-----
你的应用私钥内容(保持原样,包括换行)
-----END RSA PRIVATE KEY-----'''

# 支付宝公钥(从沙箱页面复制)
ALIPAY_PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
你的支付宝公钥内容(保持原样,包括换行)
-----END PUBLIC KEY-----'''

重要说明:

  • 私钥和公钥字符串一定要保持原始的 PEM 格式(包含 -----BEGIN/END----- 标记和换行符),复制时注意不要多或少空格、换行。-1

  • ALIPAY_RETURN_URLALIPAY_NOTIFY_URL 必须是绝对 URL。本地开发用 127.0.0.1:8000,异步通知(notify_url)在本地无法被支付宝回调,需要使用内网穿透工具 (如 ngrok、花生壳)暴露一个公网地址。我们会在后面详细讲解回调处理。-27-


四、创建支付模块(payment app)

第 2 篇我们已经创建了 payment app,并定义了 Payment 模型。现在让它"工作"起来。

4.1 配置 URL 路由

编辑 apps/payment/urls.py

bash 复制代码
from django.urls import path
from . import views

app_name = 'payment'

urlpatterns = [
    path('go/<int:order_id>/', views.payment_go, name='payment_go'),  # 去支付
    path('return/', views.payment_return, name='payment_return'),      # 同步回跳
    path('notify/', views.payment_notify, name='payment_notify'),      # 异步通知
]

然后在项目 django_ecommerce/urls.py 中 include:

bash 复制代码
path('payment/', include('apps.payment.urls')),

4.2 修改提交订单后的跳转

打开 apps/orders/views.py,找到 order_submit 视图,将提交成功后的跳转改为支付页面:

bash 复制代码
# 原来:
# return redirect('orders:order_detail', pk=order.pk)

# 改为:
return redirect('payment:payment_go', order_id=order.pk)

五、编写支付视图

5.1 发起支付------生成支付宝支付链接

编辑 apps/payment/views.py

bash 复制代码
import logging
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.conf import settings
from alipay import AliPay, AliPayConfig
from orders.models import Order
from .models import Payment

logger = logging.getLogger('payment')


def get_alipay_client():
    """创建并返回 AliPay 实例"""
    alipay = AliPay(
        appid=settings.ALIPAY_APPID,
        app_notify_url=settings.ALIPAY_NOTIFY_URL,   # 异步通知地址
        app_private_key_string=settings.ALIPAY_APP_PRIVATE_KEY,
        alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY,
        sign_type='RSA2',
        debug=settings.ALIPAY_DEBUG,
        verbose=True,                                 # 控制台输出调试信息
        config=AliPayConfig(timeout=15),
    )
    return alipay


@login_required(login_url='users:login')
def payment_go(request, order_id):
    """发起支付------跳转到支付宝收银台"""
    user = request.user
    order = get_object_or_404(Order, pk=order_id, user=user)

    # 只有待支付的订单才能发起支付
    if order.status != 0:
        messages.warning(request, f'该订单当前状态为「{order.get_status_display()}」,无法支付。')
        return redirect('orders:order_detail', pk=order.pk)

    # 创建或更新支付记录
    payment, created = Payment.objects.get_or_create(
        order=order,
        defaults={'amount': order.total_amount, 'status': 0}
    )

    # 使用 AliPay SDK 生成支付链接
    alipay = get_alipay_client()
    order_string = alipay.api_alipay_trade_page_pay(
        out_trade_no=order.order_no,               # 商户订单号
        total_amount=float(order.total_amount),     # 订单金额(元)
        subject=f'Django商城订单-{order.order_no}', # 订单标题
        return_url=settings.ALIPAY_RETURN_URL,      # 支付成功后同步回跳地址
    )

    # 拼接完整的支付宝收银台 URL
    pay_url = settings.ALIPAY_GATEWAY + '?' + order_string

    return redirect(pay_url)

核心逻辑解读:

  • get_alipay_client() 每次调用都新建一个 AliPay 实例,传入 APPID、密钥和网关信息。

  • alipay.api_alipay_trade_page_pay() 生成支付宝电脑网站支付的参数字符串,SDK 内部会自动进行 RSA2 签名。参数 out_trade_no 用我们之前生成的订单号,total_amount 是支付金额。-18

  • 拼接 网关地址 + '?' + order_string,得到完整的支付链接,用 redirect 让用户浏览器跳转到支付宝收银台。

5.2 同步回跳------用户支付后返回网站

用户支付成功后(或中途取消),支付宝会将浏览器重定向回 return_url。注意:同步回跳不可靠(用户可能关闭浏览器、网络超时等),只用于展示支付结果给用户看,真正的订单状态更新要依赖异步通知。

bash 复制代码
@login_required(login_url='users:login')
def payment_return(request):
    """支付完成后的同步回跳页面"""
    alipay = get_alipay_client()

    # 从 GET 参数中取出 sign 进行验签
    data = request.GET.dict()
    sign = data.pop('sign', None)

    if not sign or not alipay.verify(data, sign):
        messages.error(request, '支付验证失败,请联系客服。')
        return redirect('home')

    # 验签通过,获取订单信息
    out_trade_no = data.get('out_trade_no')
    trade_no = data.get('trade_no')  # 支付宝交易号

    try:
        order = Order.objects.get(order_no=out_trade_no)
        payment = order.payment

        # 如果支付记录尚未标记成功,则更新(通常在异步通知中已更新,此处为兜底)
        if payment.status != 1:
            payment.trade_no = trade_no
            payment.status = 1
            payment.save(update_fields=['trade_no', 'status', 'update_time'])

            # 更新订单状态
            if order.status == 0:
                order.set_status(1)  # 待支付 → 待发货

        return render(request, 'payment/pay_success.html', {
            'order': order,
            'trade_no': trade_no,
        })
    except Order.DoesNotExist:
        messages.error(request, '订单不存在。')
        return redirect('home')

关键点:

  • request.GET.dict() 将 QueryDict 转为普通字典,方便验签。-45

  • alipay.verify(data, sign) 验证支付宝返回数据的签名,防止伪造回调。-27

  • 验签通过后,根据 out_trade_no(订单号)找到对应订单并更新状态。

5.3 异步通知------支付宝服务端主动回调(⭐核心)

这是支付宝最关键的接口。用户支付成功后,支付宝服务器会以 POST 方式主动请求 notify_url,告知支付结果。这个请求不经过浏览器,直接由支付宝服务器发起。我们需要在这个视图中完成最终的业务处理。

bash 复制代码
@csrf_exempt
def payment_notify(request):
    """
    支付宝异步通知视图
    注意:
    1. 必须加 @csrf_exempt,支付宝的请求不带 CSRF Token
    2. 验签通过后,必须返回纯文本 'success',否则支付宝会重复发送
    3. 需要做幂等处理,防止重复通知
    """
    if request.method != 'POST':
        return HttpResponse('Method Not Allowed', status=405)

    alipay = get_alipay_client()

    # 将 POST 数据转为字典
    data = request.POST.dict()

    # 取出签名
    sign = data.pop('sign', None)
    sign_type = data.get('sign_type', 'RSA2')

    # 验签
    if not sign or not alipay.verify(data, sign):
        logger.error('支付宝异步通知验签失败')
        return HttpResponse('failure')

    # 验签通过,处理业务逻辑
    out_trade_no = data.get('out_trade_no')
    trade_no = data.get('trade_no')
    trade_status = data.get('trade_status')
    total_amount = data.get('total_amount')

    logger.info(f'收到支付宝通知:订单号={out_trade_no}, 交易号={trade_no}, 状态={trade_status}')

    try:
        order = Order.objects.select_for_update().get(order_no=out_trade_no)

        # 防止重复处理(幂等性)
        if order.status != 0:
            logger.info(f'订单 {out_trade_no} 已处理过,跳过')
            return HttpResponse('success')

        # 校验金额
        if float(total_amount) != float(order.total_amount):
            logger.error(f'订单 {out_trade_no} 金额不一致:支付宝={total_amount}, 本地={order.total_amount}')
            return HttpResponse('failure')

        # 根据支付宝交易状态更新订单
        if trade_status == 'TRADE_SUCCESS' or trade_status == 'TRADE_FINISHED':
            payment = order.payment
            payment.trade_no = trade_no
            payment.status = 1
            payment.save(update_fields=['trade_no', 'status', 'update_time'])

            order.set_status(1)  # 待支付 → 待发货
            logger.info(f'订单 {out_trade_no} 支付成功')

    except Order.DoesNotExist:
        logger.error(f'订单 {out_trade_no} 不存在')
        return HttpResponse('failure')

    # ⚠️ 必须返回 'success',否则支付宝会持续重发(最多25小时)
    return HttpResponse('success')

关键点:

关于本地开发的异步通知:

支付宝异步通知需要支付宝服务器能访问到你的 notify_url127.0.0.1 显然不行。解决方案:

  1. 使用内网穿透工具 (推荐):如 ngrok、花生壳等,生成一个公网 URL,替换 ALIPAY_NOTIFY_URL。例如运行 ngrok http 8000 后获得 https://xxxx.ngrok.io,然后修改 settings 为 https://xxxx.ngrok.io/payment/notify/

  2. 不依赖异步通知 :在同步回跳中更新订单状态(我们的 payment_return 已做兜底),异步通知留到生产环境再配置。


六、支付成功页面模板

创建 apps/payment/templates/payment/pay_success.html

bash 复制代码
{% extends 'base.html' %}
{% block title %}支付成功{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6 text-center">
        <div class="card shadow-sm">
            <div class="card-body py-5">
                <h1 class="text-success mb-3">✅</h1>
                <h3>支付成功!</h3>
                <p class="text-muted">感谢您的购买,我们会尽快为您发货。</p>
                <hr>
                <p><strong>订单号:</strong>{{ order.order_no }}</p>
                <p><strong>支付宝交易号:</strong>{{ trade_no }}</p>
                <p><strong>支付金额:</strong>¥{{ order.total_amount|floatformat:2 }}</p>
                <a href="{% url 'orders:order_detail' order.pk %}" class="btn btn-primary mt-2">查看订单详情</a>
                <a href="{% url 'home' %}" class="btn btn-outline-secondary mt-2">返回首页</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}

七、完整支付流程测试

启动开发服务器:

bash 复制代码
python manage.py runserver

⚠️ 测试前提:确保已有一个状态为「待支付」的订单。如果没有,请先走一遍购物车 → 下单流程。

7.1 进入支付页

访问订单详情,点击"去支付"(或直接访问 /payment/go/1/)。

终端输出:

bash 复制代码
[26/May/2026 09:30:12] "GET /payment/go/1/ HTTP/1.1" 302 0

页面自动跳转到支付宝沙箱收银台(URL 为 https://openapi-sandbox.dl.alipaydev.com/gateway.do?...)。

7.2 登录沙箱买家账号

在支付宝收银台页面,使用沙箱买家账号登录(账号密码在沙箱页面查看),确认金额后点击支付。沙箱支付密码通常为 111111

支付成功后,浏览器跳转回:

bash 复制代码
http://127.0.0.1:8000/payment/return/?charset=utf-8&out_trade_no=20260526093012X7K9M2&...&sign=...

页面显示"支付成功",展示订单号和支付宝交易号。

终端输出:

bash 复制代码
[26/May/2026 09:31:45] "GET /payment/return/?charset=utf-8&out_trade_no=20260526093012X7K9M2&... HTTP/1.1" 200 6543

7.3 验证数据库

打开 dbshell

bash 复制代码
-- 查看订单状态
SELECT id, order_no, status, total_amount FROM tb_order WHERE order_no = '20260526093012X7K9M2';

状态应为 1(待发货)。

bash 复制代码
-- 查看支付记录
SELECT id, order_id, trade_no, amount, status FROM tb_payment WHERE order_id = 1;

支付记录状态为 1trade_no 有值。


八、常见问题与避坑指南


九、总结与下集预告

今天我们完成了电商交易闭环的最后一步------支付宝沙箱支付:

  • 申请了支付宝沙箱环境,获取了 APPID 和密钥;

  • 安装了 python-alipay-sdk,配置了 settings.py

  • 编写了发起支付视图,生成支付宝收银台跳转链接;

  • 实现了同步回跳页面,展示支付结果;

  • 实现了异步通知视图,安全更新订单状态。

现在,用户从浏览商品、加购、下单到支付,整个链路全部打通!但支付后的订单状态管理还有很多细节需要完善:退款、支付超时取消、状态同步等。第 21 篇 ,我将带大家深入处理 支付结果与订单状态更新,包括定时查询支付结果、超时未支付自动取消订单、以及支付回滚等实战技巧。

想了解更多还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !


本文为《Django 从 0 到 1 打造完整电商平台》系列第 20 篇。

相关推荐
渐儿15 小时前
第 05 章 · SQL 写法
后端
invicinble15 小时前
对于spring的bean应该有哪些领域的认识
java·后端·spring
Amazing530715 小时前
docker compose 漏一个参数全失效
后端·代码规范
ZengLiangYi15 小时前
从零实现 Embedding 服务:文本转向量
人工智能·后端
星栈15 小时前
订单状态机别写散:我在 Rust CRM 里把 6 个状态收进领域模型
后端·rust·全栈
韩小兔修媛史16 小时前
SpringBoot面试八股文(持续更新)
spring boot·后端·面试
码上出头16 小时前
地理围栏从0到1:我是怎么把轮询接口从每分钟2000次干到0次的
后端
神奇小汤圆16 小时前
搞懂数据库索引:它到底帮了什么忙,又埋了什么坑?
后端
浮游本尊16 小时前
Java学习第38天 - 企业级 REST API 设计、OpenAPI 契约与接口可靠性
后端
苍何16 小时前
分享最近高频用 Agent 提效的 4 大场景
后端