☰
微信支付与支付宝接入全指南:从资质准备到联调避坑
2026/10/9 3:52:23 网站建设 项目流程

第一次接支付需求那天,我花了一下午时间把微信支付和支付宝开放平台的文档翻了个遍。文档都写得挺规范,但问题在于它们是两套不同的体系:一个讲证书、一个讲公钥;一个单位用分、一个单位用元;一个回调加密、一个回调明文。真正动手的时候,让我上头的不是某个接口的代码量,而是这几套东西之间的逻辑关系。这篇内容就把我梳理好的主链路完整写一遍:资质准备、参数体系、下单、支付、回调、查单、退款,以及联调时容易翻车的细节。适合三类人看:正要接手支付模块的后端开发、给小程序或App接入支付的同学、还有准备自己维护支付系统的独立开发者。

1. 对接前的硬性门槛:资质、账号、产品选型和成本核算

很多人一上来就打开API文档开始写代码,等到要真实验证才发现商户号还没申请,甚至营业执照都没有。支付对接的第一步其实不是技术,而是先确认你有没有资格去申请这些能力。

1.1 主体资质要求

微信支付商户号和支付宝商家账号,申请的前提都是有一个真实经营主体。企业营业执照和个体工商户都可以申请,但提交的主体信息必须和后续结算账户保持一致。名称、法人、证件号任何一个对不上,审核都会打回来。

结算账户这块注意一下:企业主体基本是对公账户,个体工商户在部分平台可以结算到法人本人的银行账户,这个以申请页面的实际提示为准。个人开发者如果暂时没有主体,可以先用沙箱环境或者测试商户号把技术链路跑通,但上线正式交易肯定还是得补上商事主体资质。

1.2 申请流程的几个关键点

微信侧通常是先有一个公众号、小程序或者App,然后在后台找到微信支付入口,提交主体信息、经营信息、结算账户,审核通过后拿到商户号。支付宝侧则是注册商家账号后,在开放平台创建应用,完成配置后再签约对应的支付产品。

这个流程一般一两天能过,慢的话三到五个工作日也正常。资料照片要拍清楚,营业执照不能过期,法人身份证要和执照一致。我见过最多的驳回原因是:结算账户开户名和营业执照主体不一致,或者法人姓名填成了经营者姓名以外的字。别笑,真有人把“经营者”填成“法人代表”然后被打回来。

1.3 支付产品选型:不是每个产品都要接

微信和支付宝都按照使用场景拆分了多个支付产品,先列一张表:

使用场景微信侧产品支付宝侧产品
微信内公众号/小程序支付JSAPI支付小程序支付
微信外手机浏览器支付H5支付手机网站支付
App内支付APP支付App支付
电脑网站扫码支付Native支付电脑网站支付

对接之前先想清楚自己的业务到底在哪个场景出现。如果只有一个小程序,就只需要微信用JSAPI、支付宝用小程序支付,其他的产品一概不用签,减少审核时间和维护成本。如果业务是PC网站,那就接微信Native支付和支付宝电脑网站支付。

1.4 费率与结算周期

费率一般在签约页面能看到,普通行业约0.6%,部分优惠类目能做到更低。结算周期常见的T+1,也就是第二天到账,节假日顺延。这个费率要计入项目成本,尤其你做的是低毛利商品,手续费会直接影响利润测算。

还有一个容易忽略的点:退款时原手续费退不退,不同平台不同产品的规则不完全一样。有些是退款成功后按退款金额等比退还手续费,有些则不退。这个细节建议在开发退款功能前问清楚客服或在页面确认,避免上线后财务对不上账。

2. 参数与密钥体系:把AppID、商户号、证书、公钥先串起来

支付对接最劝退新手的就是参数太多,而且微信和支付宝叫法还完全不一样。这里我直接把两边各自要用的核心参数拆开讲,讲清楚每个东西到底干嘛用的。

2.1 微信支付侧参数全家桶

  • AppID:你那个公众号或小程序的应用ID,用来标识“你是哪个应用”。
  • 商户号(mchid):微信支付分配给商户的唯一编号,标识“你是哪个商户”。
  • APIv3 Key:32字节的对称密钥,主要是解密微信支付回调通知里加密的订单数据。
  • 商户API证书:包含apiclient_cert.pem和apiclient_key.pem两个文件,其中私钥文件用于请求接口时的签名。
  • 证书序列号:微信服务端验证你请求签名时用。
  • 微信支付平台公钥/证书:用于验证回调通知的签名,确认通知真的是微信发来的。

这里有一个重要认知:商户API证书和APIv3 Key是两个东西,用途完全不同。初学者经常把APIv3 Key填到签名私钥位置,然后报一堆看不懂的错。

2.2 支付宝侧参数全家桶

支付宝的体系相对直白:

  • AppID:开放平台应用的标识。
  • 应用私钥:你自己生成并保存在服务端的密钥,用来给请求参数签名。
  • 应用公钥:把私钥对应的公钥上传到支付宝开放平台,支付宝用它验证你的请求。
  • 支付宝公钥:支付宝提供给你的公钥,用来验证支付宝回调通知的签名。

普通模式下支付宝没有像微信那样的“证书+对称密钥”概念,就是典型的非对称RSA2签名体系。理解了这个区别,后面看文档会轻松很多。

2.3 签名与加密的设计逻辑

用生活类比解释一下:你给平台发的请求,相当于寄一封要盖章的信。你的私钥是你的章,你盖上章说明这封信确实是你发的;平台那边拿着你上传的公章印模(应用公钥)来核对。反过来,平台发给你的回调通知,平台用自己的私钥盖章,你用平台给的公章印模(支付宝公钥或微信平台公钥)来验证。

微信多了一层加密:它回调的数据体本身被AES-256-GCM加密了,所以在验签之后还要解密才能看到订单内容。支付宝普通模式回调是明文表单,验签通过后直接读参数即可。

明白了这套逻辑之后,就不会再把“应用公钥”和“支付宝公钥”混用了。这两个东西在支付宝后台长得有点像,但一个是验你的请求,一个是验平台的回调,拿错了就会一直验签失败。

2.4 密钥保管的底线要求

  • 私钥文件绝不能提交到Git仓库,哪怕私有仓库也建议排除,通过配置中心或环境变量注入。
  • 开发环境、测试环境、生产环境的密钥要分开,尤其是支付宝沙箱和正式环境的AppID、私钥完全不能混用。
  • APIv3 Key和应用私钥要有轮换机制,至少半年到一年换一次,换的时候注意提前过渡。

3. 核心链路:从下单到回调的完整拼图

把前置条件准备好之后,真正开发的核心链路其实就四步:后端调统一下单接口、前端拉起支付、支付完成后平台异步回调通知、后端处理回调并更新订单状态。

3.1 统一下单:微信JSAPI支付示例

以微信JSAPI支付为例,后端需要请求:

POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求体大概是这样的:

{ "appid": "你的AppID", "mchid": "你的商户号", "description": "商品描述", "out_trade_no": "商户订单号", "notify_url": "https://api.example.com/pay/wx/notify", "amount": { "total": 100 }, "payer": { "openid": "用户的openid" } }

注意这里的amount.total单位是分,也就是1元要传100。这个“分”和“元”的坑,几乎每个接支付的人都踩过。

这个请求需要在Header里带上签名,核心是构造一个待签字符串,用商户私钥做SHA256-RSA签名:

import time, secrets, base64 def build_message(method, url, timestamp, nonce, body): return f"{method}\n{url}\n{timestamp}\n{nonce}\n{body}\n" def sign_request(method, url, body, private_key): timestamp = str(int(time.time())) nonce = secrets.token_hex(16) message = build_message(method, url, timestamp, nonce, body) signature = base64.b64encode( private_key.sign( message.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256() ) ).decode("utf-8") sign_str = ( f'WECHATPAY2-SHA256-RSA2048 ' f'mchid="{mchid}",' f'nonce_str="{nonce}",' f'signature="{signature}",' f'timestamp="{timestamp}",' f'serial_no="{cert_serial_no}"' ) return sign_str

看起来麻烦,但逻辑就是:把请求方法、URL路径、时间戳、随机串、请求体拼成一个字符串,用私钥签名,再把签名结果和其他信息拼成Authorization头。这里有个细节:URL用的是去掉域名的路径部分,比如/v3/pay/transactions/jsapi。

支付宝侧的思路类似,不过它把签名结果放在请求参数里,而不是HTTP头。下单请求的biz_content里包含out_trade_no、total_amount、subject等字段,其中total_amount单位是元,比如"0.01"。用应用私钥对参数做SHA256withRSA签名,Base64编码后放入sign参数。

3.2 前端拉起支付:后端拿到参数后的处理

微信JSAPI下单成功后返回prepay_id。这个prepay_id不能直接给前端用,后端还要用它重新生成调起支付的必要参数,比如timeStamp、nonceStr、package(package的值为prepay_id=xxx)、signType、paySign。然后用当前商户的私钥对这五个字段做签名,再交给前端调用小程序或公众号的支付SDK。

支付宝侧的处理方式略有不同。App支付下单后会返回orderStr,前端直接拿orderStr调起支付宝SDK;手机网站支付会返回一段自动提交的表单HTML,后端把它直接输出到浏览器就行。

这里有一个安全原则:下单和签名必须在后端完成,前端不要接触任何私钥。我看到有人把私钥放在前端代码里去生成签名,这是严重的资金级安全漏洞。

3.3 异步回调通知:核心中的核心

支付完成后,微信和支付宝会向下单时提交的notify_url发送异步通知。这个回调如果处理不好,订单状态就会错乱。

微信回调的body是加密的。处理流程是:先验签,再用APIv3 Key解密,最后校验参数。解密核心逻辑:

from cryptography.hazmat.primitives.ciphers.aead import AESGCM # resource字段包含ciphertext、nonce、associated_data # ciphertext需要base64解码 key = api_v3_key.encode("utf-8") aesgcm = AESGCM(key) plaintext = aesgcm.decrypt( nonce, # 回调报文里的nonce,bytes b64decode(ciphertext), # 解码后的密文+tag associated_data # 回调报文里的associated_data,bytes ).decode("utf-8")

解密后拿到的字段里有out_trade_no(商户订单号)、transaction_id(微信支付订单号)、trade_state(交易状态)、amount.total(支付金额,单位分)等。后端要做的事情很明确:

  1. check签名和解密是否成功;
  2. 校验商户订单号是否存在;
  3. 校验支付金额是否和订单金额一致;
  4. 更新订单状态为已支付;
  5. 返回响应,告诉微信“我处理成功了”。

这里有一个非常容易错的点:微信要求收到回调后HTTP响应必须是2xx且返回体符合要求,处理失败时不要直接返回错误页面,而是记录日志后返回失败状态,让微信稍后再次通知。如果后端回调服务崩溃了或者返回了500,微信会按策略重发多次通知。

支付宝的回调是明文表单,验签方式是:把回调参数里的sign和sign_type去掉,剩余参数按key的ASCII码升序排列,拼成key=value&key=value形式,用支付宝公钥做RSA2验签。验签通过后,再校验app_id、out_trade_no、trade_status和total_amount。注意支付宝的trade_status只有TRADE_SUCCESS和TRADE_FINISHED才表示交易成功。处理完毕后,响应体要原样输出英文单词success,不能返回JSON、不能输出HTML、不能重定向,否则支付宝会认为回调失败并重发通知。

3.4 主动查单、退款和对账兜底

回调是主路径,但网络世界没有绝对可靠,所以查单接口是重要的兜底。微信支付查询接口可以通过out_trade_no查询交易状态,支付宝有alipay.trade.query。建议在以下场景主动查单:用户支付后回调迟迟没来、订单处于中间状态超过一定时间、用户反馈已付款但订单未更新。

退款链路也是一定要提前联调的。微信支付退款接口是POST /v3/refund/domestic/refunds,支付宝对应alipay.trade.refund。退款一般原路返回,不是即时到账,需要关注退款状态异步通知。部分退款时,累计退款金额不能超过原订单总额,这个校验后端要做。

3.5 两边核心差异对照

环节微信支付支付宝
下单接口示例v3/pay/transactions/jsapialipay.trade.create
金额单位分(整数)元(两位小数)
回调数据AES-256-GCM加密明文表单
验签方式微信支付平台公钥验签支付宝公钥验签
成功响应返回200和SUCCESS原样输出success

4. 边界处理与安全设计:支付系统不能只做到“能付通”

把主链路跑通只是开始,真正上线前还要处理一堆边界情况和安全问题。支付系统最怕的不是功能没有,而是边界没想清楚。

4.1 金额单位与精度问题

微信用分,支付宝用元,这在同一个项目里其实是个大坑。

我自己的做法是:数据库统一以分存储,用整数类型或长整型,避免浮点数精度问题。如果有对外的金额展示,再转换成元。数据库存金额不要用float或double,你可以用decimal,或者干脆都存整数分。所有金额运算用整型分完成,最后展示层再做格式化。

4.2 重复通知与数据幂等

微信和支付宝的通知机制都允许重复通知,同一个订单的支付成功通知可能收到多次。后端处理回调时,不能每次通知都直接更新订单状态,必须做幂等控制。

最基本的方式是:回调处理时先查询订单当前状态,如果订单已经是“已支付”,直接返回成功响应,不再重复处理。更严谨的方式是给支付流水表加唯一索引,以transaction_id作为订单维度的唯一键,重复通知插入时直接报错回滚,再根据唯一索引冲突判断为重复。

4.3 订单状态机设计

支付相关订单状态建议设计成:待支付、已支付、已关闭、退款中、已退款。用户支付成功前如果主动取消订单,订单进入已关闭。已支付后用户申请退款,订单进入退款中,退款完成进入已退款。

这里有一个值得注意的操作顺序:用户支付成功的那个瞬间,如果用户同时点击了取消订单,后端要以回调通知为准,而不是以前端取消操作为准。前端可以发关闭请求,但后端要判断订单当前是否已支付,已支付订单不能关,要引导走退款流程。

4.4 回调验签与日志脱敏

所有回调接口都必须验签,这是非商量项。不验签的后果是伪造通知任意改订单状态,等于把资金安全裸奔。

日志方面,回调原文、响应结果、异常堆栈都要记录,但是不能打印完整的签名参数、APIv3 Key、应用私钥。订单号、金额可以打,完整卡号、密钥类字段必须脱敏。线上排查问题的时候,日志是否足够决定了你的排障时间是十分钟还是两个小时。

5. 联调阶段常见的坑与完整排查思路

这里把我在联调阶段踩过的坑和排查链路整理出来,每个问题都给到排查顺序,方向对了答案自然出来。

5.1 签名报错“验签失败”

微信侧排查顺序:

  1. 确认你签名用的私钥还是不是商户API证书里的私钥,新人经常会拿错成平台证书私钥。
  2. 确认待签字符串的格式,尤其是URL部分要写路径而不是完整域名,method要大写。
  3. 确认时间戳是秒级,且服务器时间偏差不能太大。
  4. 确认Authorization头里serial_no用的是证书序列号,不是商户号。

支付宝侧排查顺序:

  1. 确认拼接验签串时去掉了sign和sign_type两种参数。
  2. 确认value部分没有做二次URL编码,就用原始的键值。
  3. 确认用的是支付宝公钥验签,不是应用公钥。
  4. 如果你的支付宝公钥是从后台复制的,注意去掉多余的换行符。

5.2 回调收不到或回调报错

回调收不到先别怀疑平台,按这个顺序自查:

  1. 回调地址在公网能否访问,用的HTTPS证书是否有效,自签名证书平台会拒绝。
  2. 商户平台配置的notify_url和下单请求里传的notify_url是否一致,两边会校验。
  3. 服务器安全组、防火墙是否放行了443端口,反向代理有没有把POST请求转发到后端。
  4. 接口是否返回了非200状态,或者返回了HTML内容。微信和支付宝对响应格式有严格要求。
  5. 有没有加全局重定向,比如HTTP跳HTTPS、加www跳转,这些都会截断回调。

调试阶段用内网穿透工具把本地服务暴露出去是最快的,但生产环境老老实实上正规域名和HTTPS证书,不要省这个钱。

5.3 微信回调解密失败

微信回调解密失败几乎都是这三类原因:

第一,APIv3 Key复制不全,多复制了空格、漏了几个字符,密钥是32字节,错了任何一个字节解密都会失败。建议把密钥放到配置中心后,写一个自检接口测试加解密,不要肉眼比对。

第二,密文没有用原始body字节,很多框架会先把请求体解析成JSON对象,你再把这些字段重新序列化去解密,结果字节顺序变了导致解密失败。正确做法是直接用request.body的原生字节。

第三,AES-256-GCM的参数顺序写错。decrypt()方法接收的顺序是nonce、data(密文+tag)、associated_data,这三个字段来自微信回调的resource节点,别把顺序搞反。

5.4 支付宝notify验签失败

支付宝notify验签失败,我见过最多的是这两个原因:

一是用JSON解析了请求体。支付宝notify是以application/x-www-form-urlencoded格式POST过来的,应该用request.POST或对应语言的表单解析方式拿到参数,而不是把body当JSON解析。你用JSON解析出来的键值对大概率没问题,但有些字段值里的符号会发生变化,导致验签串不一致。

二是验签用的公钥不对。支付宝后台有两个公钥展示位,一个是应用公钥,是你上传上去的;一个是支付宝公钥,是支付宝提供的。验签要用后者。把两个搞混,会一直验签失败。

5.5 沙箱环境和正式环境混用

支付宝沙箱环境提供了一套独立的AppID和密钥,跟正式环境完全隔离。联调时用沙箱配置、上线前换成正式配置,经常有人换漏了一个参数。我的建议是:把环境配置集中到一个文件或配置中心,切换环境时一次性替换整套参数,不要分散写在代码里手动改。

微信侧相对简单,一般直接用正式商户号在测试模式下产生少量真实订单来验证,所以环境隔离的坑没有支付宝那么明显,但测试订单要记得处理掉,别影响线上数据。

6. 上线前自查清单与值得长期保留的习惯

支付模块上线前的自查,我整理了一份清单,照着过一遍能挡掉绝大多数低级问题:

  • [ ] 回调接口验签、解密逻辑完整且经过测试
  • [ ] 重复回调幂等验证过(用工具或脚本连续推送两次同一通知)
  • [ ] 金额单位统一,分和元的转换没有遗漏
  • [ ] 下单、回调、查单、退款全链路联调通过
  • [ ] 退款链路走通,包含部分退款场景
  • [ ] 密钥不落在代码仓库,生产配置加密
  • [ ] 支付相关日志完整,但敏感字段已脱敏
  • [ ] 对账任务已部署,差异告警有人处理

最后分享三个我长期保留的习惯。

第一个习惯是建支付流水表。每一笔下单请求的请求参数、返回结果、回调原文、处理结果都记录到一张独立的流水表里,排障时不需要翻服务器日志,直接查这张表就能串起来整条链路。

第二个习惯是回调处理先落库再报错。回调进来后先把原始报文存下来,再去做验签和业务处理,即使后面业务处理出了问题,原始数据也留住了。

第三个习惯是真实环境用小额订单验证全链路。微信支付最低可以交易1分钱,支付宝也支持0.01元,在正式上线前用真实环境产生一笔小额订单,跑通下单、支付、回调、对账全流程后再发起退款,这套组合拳比任何代码review都管用。

我在实际维护支付模块时有个体会:支付链路里越简单越可靠,回调处理顺序执行、先落库再返回、查单兜底定时跑,做到这些,再复杂的对接也就是几个接口的事。支付系统不需要炫技,稳定和安全才是第一位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询