IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。
上一篇我们完成了从购物车到订单的全流程------用户勾选商品、选择地址、提交订单,数据库里已经有了待支付的订单。但用户还没有付钱!今天我们要打通电商的"最后一公里"------集成支付宝沙箱支付,让用户真正扫码付款,完成交易闭环。
支付宝沙箱环境是支付宝官方提供的测试环境,不需要真实资金,不需要营业执照,任何人都可以免费申请。开发阶段用它来调试支付流程,完全够用。本篇我会手把手带你申请沙箱、配置密钥、安装 SDK、编写支付视图,并处理好同步回跳和异步通知两个回调逻辑。
提示 :接入支付宝支付并非只有
python-alipay-sdk这一个库,你也可以选择支付宝官方提供的alipay-sdk-python。python-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_URL和ALIPAY_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_url,127.0.0.1 显然不行。解决方案:
-
使用内网穿透工具 (推荐):如 ngrok、花生壳等,生成一个公网 URL,替换
ALIPAY_NOTIFY_URL。例如运行ngrok http 8000后获得https://xxxx.ngrok.io,然后修改 settings 为https://xxxx.ngrok.io/payment/notify/。 -
不依赖异步通知 :在同步回跳中更新订单状态(我们的
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;
支付记录状态为 1,trade_no 有值。
八、常见问题与避坑指南
九、总结与下集预告
今天我们完成了电商交易闭环的最后一步------支付宝沙箱支付:
-
申请了支付宝沙箱环境,获取了 APPID 和密钥;
-
安装了
python-alipay-sdk,配置了 settings.py; -
编写了发起支付视图,生成支付宝收银台跳转链接;
-
实现了同步回跳页面,展示支付结果;
-
实现了异步通知视图,安全更新订单状态。
现在,用户从浏览商品、加购、下单到支付,整个链路全部打通!但支付后的订单状态管理还有很多细节需要完善:退款、支付超时取消、状态同步等。第 21 篇 ,我将带大家深入处理 支付结果与订单状态更新,包括定时查询支付结果、超时未支付自动取消订单、以及支付回滚等实战技巧。
想了解更多还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !
本文为《Django 从 0 到 1 打造完整电商平台》系列第 20 篇。