⚠️免责声明:本文项目仅供技术学习参考,严禁直接作为毕业设计或课程作业提交。请结合自身需求进行二次开发与创新,学术诚信从我做起 🙂
先说我踩通的路
05篇讲订单的时候,支付那一段我只写了个"模拟支付成功"——点一下按钮,订单状态从待支付变成待发货。
能跑,但答辩的时候老师大概率会追一句:
“你这个支付,是真的吗?”
这一问,档次就分出来了。
接支付宝沙箱,不需要营业执照、不需要企业认证、不用花一分钱。沙箱是支付宝官方给开发者准备的联调环境,跟生产环境完全隔离,随便测。
跑通之后,你能拿到一整套真实链路:生成支付链接 → 跳转到支付宝 → 付款 → 支付宝服务器回调你的接口 → 验签 → 改订单状态。
答辩现场拿手机扫一下、付个 0.01 元,订单状态实时变掉,这个演示效果,比讲十分钟原理管用。
这篇就按我自己当时踩通的顺序来写,一步不跳。
第0步:先搞明白支付的完整链路
动手之前,先把链路在脑子里过一遍,不然代码会写得很懵。
用户点"去支付" ↓ 后端生成支付链接(带签名) ↓ 浏览器跳到支付宝收银台 ↓ 用户付款 ↓ 支付宝同时做两件事: ├─ 同步跳转 → return_url(用户浏览器跳回你的页面) └─ 异步通知 → notify_url(支付宝服务器 POST 给你的服务器) ↓ 你验签通过 → 把订单改成"待发货"这里有个关键认知,答辩必问:
真正改订单状态的,是异步通知(notify_url),不是同步跳转。
- 同步跳转(return_url)是给用户看的,浏览器跳回来显示"支付成功"。但它不可靠:用户可能中途关掉浏览器,或者网络断了,你根本收不到。
- 异步通知(notify_url)是支付宝服务器直接 POST 给你的服务器,可靠得多。你没返回成功,它还会按 0秒、2分钟、10分钟、1小时、2小时、6小时、15小时 的间隔重试,最多 7 次。
所以记住一句话:改状态只信 notify,return 只负责给用户看。
第1步:开通沙箱,拿到 4 样东西
打开支付宝开放平台,扫码登录,进"研发服务 / 沙箱环境"。
你需要拿到 4 样东西:
| 名称 | 用途 |
|---|---|
| APPID | 沙箱应用的唯一标识 |
| 网关地址 | 沙箱是openapi.alipaydev.com系 |
| 应用私钥 | 你用它给请求签名 |
| 支付宝公钥 | 你用它验证支付宝的签名 |
密钥怎么生成:
下载支付宝官方的"密钥生成器"(或自己用 openssl 生成 RSA2 密钥对),生成之后:
- 工具里会给你一个应用公钥,复制它,填到沙箱的"开发信息 → 接口加签方式"里,保存
- 保存设置后,页面会显示一个支付宝公钥,把这个复制下来
- 你自己的应用私钥也保存好
⚠️第一个大坑,也是新手 80% 报错的原因:
应用公钥 ≠ 支付宝公钥。
你自己生成的那对密钥里,公钥是交给支付宝的;支付宝会另外给你一个它自己的公钥。
代码里初始化时,填的是支付宝公钥,不是你自己生成的公钥。
我第一次接的时候就是填错了这个,报了一晚上验签失败,最后对着官方文档一行行看才发现。
还有一点:复制密钥的时候,前后绝对不能有空格和换行。多一个空格,验签就废。
沙箱买家账号:沙箱环境 → 测试账号,里面有买家账号和登录密码,付款时用。记得下载"沙箱版支付宝 App",用它登录买家账号,不然付不了款。
第2步:装 SDK,写配置
pipinstallpython-alipay-sdk新建一个alipay_config.py:
# alipay_config.pyfromalipayimportAliPay# 应用私钥(你自己生成的,注意首尾行固定,中间是密钥串,不能有空格)APP_PRIVATE_KEY_STRING="""-----BEGIN RSA PRIVATE KEY----- 这里粘贴你的应用私钥 -----END RSA PRIVATE KEY-----"""# 支付宝公钥(注意!是支付宝给你的那个,不是你自己生成的)ALIPAY_PUBLIC_KEY_STRING="""-----BEGIN PUBLIC KEY----- 这里粘贴支付宝公钥 -----END PUBLIC KEY-----"""# 沙箱 APPIDAPP_ID="你的沙箱APPID"# 沙箱网关GATEWAY="https://openapi.alipaydev.com/gateway.do"defget_alipay():"""返回一个配置好的 AliPay 实例"""returnAliPay(appid=APP_ID,app_notify_url=None,# 默认通知地址,一般设 Noneapp_private_key_string=APP_PRIVATE_KEY_STRING,alipay_public_key_string=ALIPAY_PUBLIC_KEY_STRING,sign_type="RSA2",# 签名算法,推荐 RSA2debug=True,# True=沙箱环境,False=生产环境)关于debug=True:它会让 SDK 用沙箱网关。但支付宝近两年调整过沙箱域名,如果你的报错提示"网关不正确",把 SDK 内部网关手动改成新版:
alipay=get_alipay()alipay._gateway="https://openapi-sandbox.dl.alipaydev.com/gateway.do"第3步:下单之后,跳去支付
先看整条支付链路在我们 demo 里的样子(demo 用模拟支付,流程和真实沙箱一致,就是不会真的跳支付宝收银台):
图:下单后,订单列表里显示「待支付」
图:进订单详情,点「去支付」
图:进入支付页(demo 里是模拟支付,选了支付宝)
图:支付完成,订单变成「待发货」
05篇的下单接口跑完之后,返回的是order_id。用户点"去支付",就走下面这个接口。
# views/pay.pyfromflaskimportrequest,redirect,current_appfromflask_loginimportlogin_required,current_userfrommodelsimportOrderInfofromalipay_configimportget_alipay,GATEWAY@app.route('/order/pay/<int:order_id>')@login_requireddeforder_pay(order_id):"""生成支付链接并跳转"""order=OrderInfo.query.filter_by(id=order_id,user_id=current_user.id).first_or_404()# 只有待支付状态才能去付款iforder.status!=0:return"订单状态异常,无法支付"alipay=get_alipay()# 生成支付参数串(已带签名)order_string=alipay.api_alipay_trade_page_pay(out_trade_no=order.order_no,# 你的订单号(必须唯一)total_amount=str(order.pay_amount),# 金额,字符串,两位小数subject=f"MYD商城订单{order.order_no}",# 订单标题return_url="http://你的域名/order/pay/return",notify_url="http://你的域名/order/pay/notify",)# 拼成完整支付地址,跳过去pay_url=GATEWAY+"?"+order_stringreturnredirect(pay_url)几个参数要注意:
out_trade_no用你自己的订单号,必须唯一,支付宝靠它跟你对账total_amount是字符串,不是数字。str(order.pay_amount)就对了subject随便写,用户会在收银台看到
第4步:同步回调 return_url
用户付完款,浏览器会跳回这个地址。它的作用就一个:告诉用户"付好了"。
@app.route('/order/pay/return')@login_requireddeforder_pay_return():"""同步回调:只负责给用户看结果"""data=request.args.to_dict()alipay=get_alipay()signature=data.pop('sign',None)data.pop('sign_type',None)ifalipay.verify(data,signature):# 验签通过,展示成功页returnrender_template('order/pay_success.html')else:returnrender_template('order/pay_fail.html')注意:这里不要改订单状态。
原因前面说过了:同步回调可能丢。改状态放到异步通知里做。
同步回调里只做一件事:验签,然后给用户看个结果页。真正的状态更新,在下一步。
第5步:异步通知 notify_url(核心中的核心)
这个接口,是支付宝服务器来调你的,不是你用户点的。
@app.route('/order/pay/notify',methods=['POST'])deforder_pay_notify():"""异步通知:真正改订单状态的地方"""data=request.form.to_dict()alipay=get_alipay()signature=data.pop('sign',None)data.pop('sign_type',None)# ========== 1. 验签 ==========ifnotalipay.verify(data,signature):return"fail"# 验签失败,告诉支付宝别重试了(或者让它重试)# ========== 2. 二次校验(重要!) ==========out_trade_no=data.get('out_trade_no')trade_status=data.get('trade_status')total_amount=data.get('total_amount')order=OrderInfo.query.filter_by(order_no=out_trade_no).first()ifnotorder:return"fail"# 校验金额对不对(防止有人改金额)ifstr(order.pay_amount)!=str(total_amount):return"fail"# ========== 3. 幂等处理 ==========# 支付宝会重复通知,如果订单已经是待发货了,直接返回 success,别重复处理iforder.status!=0:return"success"# ========== 4. 只有交易成功才改状态 ==========iftrade_statusin('TRADE_SUCCESS','TRADE_FINISHED'):order.status=1# 待发货order.pay_type='alipay'order.pay_time=datetime.now()db.session.commit()# ========== 5. 必须返回 success 字符串 ==========return"success"这段代码里有 4 个点,每一个都能变成答辩题:
① 为什么必须返回success?
支付宝收到success才算通知成功。你返回别的、或者报错了,支付宝会一直重试,重试 7 次还不行,你的订单状态可能永远停在"待支付"。
② 为什么要二次校验?
验签只证明"这条通知是支付宝发的",但不证明"这条通知的内容是对的"。还得自己核对:
out_trade_no是不是我系统里真实存在的订单号total_amount跟我订单金额对不对得上(防止有人篡改金额)app_id是不是我自己的应用
③ 为什么要做幂等?
支付宝重试机制会导致同一条通知来好几次。如果每次都执行"改状态 + 加销量",就会重复处理。所以进业务前先判断:订单已经是待发货了,直接返回 success。
④ 为什么返回 “success” 而不是 json?
支付宝只认这个纯字符串,别的都不认。这是接口约定。
第6步:本地调试怎么收到异步通知
这一步卡住了无数人。
异步通知是支付宝服务器来调你的接口,它得能通过公网访问到你。你在本地127.0.0.1:5000跑,支付宝是找不到的。
解决办法:内网穿透。
用 ngrok、花生壳、cpolar 这类工具,把你的本地 5000 端口映射到一个公网地址,然后 notify_url 填这个公网地址。
以 ngrok 为例:
ngrok http5000它会给你一个类似https://xxxx.ngrok-free.app的公网地址,那么:
notify_url="https://xxxx.ngrok-free.app/order/pay/notify"这样支付宝才能 POST 到你本地。
排查小技巧:ngrok 的免费版可以在它本地面板http://127.0.0.1:4040看到所有进来的请求。如果支付宝回调了但你代码没反应,先去这个面板看请求有没有到、返回了什么。
三个必踩的坑
坑1:应用公钥填成了支付宝公钥
前面说过,再强调一遍。你生成密钥对,得到的是"应用公钥 + 应用私钥"。应用公钥交给支付宝,支付宝给你"支付宝公钥"。
代码里初始化填的是支付宝公钥。
症状:所有接口都报验签失败/invalid signature。
坑2:本地调试收不到异步通知
没做内网穿透,或者 notify_url 写的是127.0.0.1/localhost。
支付宝服务器访问不到你的本地地址,通知永远不来。
判断方法:同步跳转能看到"支付成功"页面,但订单状态没变,那就是 notify 没收到。
坑3:金额传成了数字
# 错误total_amount=order.pay_amount# 正确total_amount=str(order.pay_amount)支付宝要求金额是字符串。传数字可能报参数错误,或者金额精度出问题。
支付这块,答辩爱问这几问
1. 同步回调和异步回调有什么区别?
答:同步回调(return_url)是用户付款后浏览器跳回来的地址,作用只是展示结果给用户看,可能因为用户关页面或网络问题丢失,不可靠。异步回调(notify_url)是支付宝服务器直接 POST 到我的服务器,可靠且有重试机制。所以我只在异步回调里改订单状态,同步回调只做展示。
2. 为什么支付要验签?
答:防止伪造通知。别人可以构造一个 POST 请求假装是支付宝,告诉我"订单付好了"。验签是用支付宝的公钥验证签名,只有支付宝私钥签出来的数据才能通过,这样能确认通知确实来自支付宝。
3. 为什么验签之后还要做二次校验?
答:验签只能证明"消息是支付宝发的",不能证明"消息内容没被篡改或不是针对我别的订单的"。所以还要核对订单号是否存在于我的系统、金额是否一致、app_id 是不是我的。这是官方文档要求的步骤,双重保险。
4. 支付宝重复通知怎么办?
答:做幂等处理。进业务逻辑之前先查订单当前状态,如果订单已经是"待发货"了,说明这条通知已经处理过,直接返回 success 不再重复执行。否则会出现重复加销量、重复改状态的问题。
5. 支付超时怎么处理?
答:一是给订单设过期时间,用定时任务定期扫描,超过 30 分钟未支付的订单自动取消并回滚库存;二是在生成支付链接时用timeout_express参数限制支付时长。两者结合,避免订单一直卡在"待支付"占着库存。
6. 沙箱环境和生产环境有什么区别?
答:沙箱是支付宝提供的隔离测试环境,网关地址、APPID、密钥都不同,用沙箱买家账号付款,不走真实资金。上线时要把网关、APPID、密钥换成生产环境的,并完成应用上线审核。沙箱可以随便测,生产要慎重。
支付这一块,代码其实不多,难点全在概念上。
同步 / 异步、验签 / 二次校验、幂等——这几个词搞懂了,接支付就没什么神秘的。
答辩的时候,你能把"异步通知才是改状态的唯一依据"这句话讲清楚,老师基本就信你是真碰过这套东西。🍃
梅雅达编程笔记原创 · 首发 CSDN · 转载请注明出处