在线支付系列(二):支付宝 微信支付——一杯咖啡的扫码之旅
在线支付系列二支付宝 微信支付——一杯咖啡的扫码之旅一杯 ¥28 的拿铁触发了什么周一早上 8:30你走进公司楼下的咖啡店扫了吧台上的二维码选了一杯冰美式¥28。打开微信扫一下输入指纹手机震动——“支付成功”。整个过程不到 2 秒。你可能觉得这没什么。但如果你是这家咖啡店的老板刚请了个开发者帮你搭线上点单系统——你的开发者正在跟支付宝和微信支付的签名机制较劲。这篇文章就从这杯 ¥28 的拿铁出发带你走通支付宝和微信支付的完整对接流程。我们不会一上来就甩代码而是先搞懂每一步为什么要这么做然后再写代码。一、扫码支付的全景从你扫码到商户收钱让我们先跟着这笔 ¥28看看它完整的旅程。1.1 支付宝 Native 扫码支付的时序你用支付宝扫码支付时背后发生的事情是这样的你的手机App 咖啡店服务器 支付宝服务器 │ │ │ │ │ ① 创建订单 │ │ │ 冰美式 ¥28 │ │ │─────────────────→│ │ │ ② 返回二维码链接 │ │ │←─────────────────│ │ ③ 吧台展示二维码 │ │ │ │ │ │ ④ 你扫码 输入指纹 │ │───────────────────────────────────→│ │ │ │ ⑤ 支付宝扣款 │ │ ⑥ 异步通知¥28 已收到 │ │ │←─────────────────│ │ │ ⑦ 更新订单状态 │ │ ⑧ 手机显示支付成功 │ │←──────────────────────────────────│注意第 ⑥ 步支付宝不是直接把结果告诉你的手机而是通过一个异步通知告诉咖啡店的服务器。这个异步通知机制是整个支付对接中最核心、也最容易出问题的部分。1.2 微信支付 Native 扫码支付的时序微信支付的流程几乎一样但在细节上有重要差异——最大的不同是微信支付的异步通知是加密的。你的手机App 咖啡店服务器 微信支付服务器 │ │ │ │ │ ① 创建订单 │ │ │ 冰美式 2800分 │ ← 注意微信用分 │ │─────────────────→│ │ │ ② 返回二维码链接 │ │ │←─────────────────│ │ │ │ │ ③ 你扫码 输入密码 │ │───────────────────────────────────→│ │ │ ④ 异步通知AES-GCM 加密│ │ │←─────────────────│ │ │ ⑤ 解密 → 验签 │ │ │ → 更新订单 │两个关键差异先记住后面写代码时会反复用到金额单位支付宝用元字符串28.00微信用分整数2800回调加密支付宝是明文 签名微信是AES-GCM 加密 签名需要先解密再验签二、安全机制为什么要签名在我们动手写代码之前必须先搞懂一个问题为什么每个请求都需要签名设想这个场景你的咖啡店服务器向支付宝发了一个请求——“帮我收 ¥28”。如果有人在中间截获了这个请求把 ¥28 改成 ¥0.01那不是白喝咖啡了数字签名就是为了解决这个问题。它保证三件事数据没被改过完整性¥28 就是 ¥28没人能偷偷改成 ¥0.01请求确实是你发的身份认证不是别人冒充你的咖啡店你不能赖账不可否认你发了这个请求就不能否认签名的原理很简单你的请求数据冰美式¥28订单号 20260404001 │ ▼ 用你的私钥加密 签名值a3f8b2c1d4e5f6... │ ▼ 支付宝用你的公钥解密验证 结果数据没被篡改 ✅ 确实是你发的 ✅2.1 支付宝 V3 签名机制支付宝用的是RSA-SHA256非对称加密。你在支付宝开放平台配置应用时会生成一对密钥应用私钥只有你知道用来给请求签名应用公钥上传给支付宝支付宝用它验证你的签名支付宝公钥证书支付宝给你的你用它验证支付宝的响应待签名字符串的拼接方式HTTP方法\n 请求路径\n 时间戳\n 随机字符串\n 请求体\n2.2 微信支付 V3 签名机制微信支付同样用 RSA-SHA256但密钥管理方式不同商户 API 证书包含你的私钥用来签名微信支付平台证书微信给你的用来验签API V3 密钥一个 32 位的字符串专门用来解密回调通知待签名字符串几乎一样HTTP方法\n URL含查询参数\n 时间戳\n 随机字符串\n 请求体\n2.3 关键差异虽然两者都是 RSA-SHA256但有几个细节差异会让你踩坑支付宝 V3微信支付 V3RSA 填充方式PSS 填充PKCS#1 v1.5 填充密钥管理应用公私钥对API 证书含私钥回调内容明文 RSA 签名AES-GCM 加密 RSA 签名证书获取手动下载支付宝根证书API 自动获取需定期轮换金额单位元字符串28.00分整数2800回调响应返回字符串success返回 JSON{code:SUCCESS}经验之谈金额单位不一致是最常见的生产事故之一。建议在你的系统内部统一用分存储展示时再转换成元。三、动手支付宝 Native 扫码支付理论够了让我们开始写代码。我们用Python FastAPI实现因为它简洁易懂。3.1 接入准备在写代码之前你需要准备这些东西第一步注册企业支付宝账号 ▼ 第二步登录开放平台open.alipay.com创建应用 ▼ 第三步配置应用——生成 RSA2 密钥对上传公钥推荐证书模式 ▼ 第四步提交审核获取 AppID ▼ 第五步签约「电脑网站支付」/「当面付」能力你需要拿到这些配置配置项说明安全级别AppID应用唯一标识可公开应用私钥给请求签名绝对不能泄露 绝密支付宝公钥证书验证支付宝的响应可公开回调地址Notify URL接收异步支付通知必须 HTTPS—3.2 创建支付——生成二维码当咖啡店的点单系统收到一笔 ¥28 的订单后它需要调用支付宝 API 生成一个二维码。顾客扫这个码就能付款。importjsonimportosimportuuidfromfastapiimportFastAPI,Request,Responsefromalipay_sdkimportAliPay# alipay-sdk-pythonappFastAPI()# 初始化支付宝客户端证书模式推荐alipayAliPay(appidos.getenv(ALIPAY_APP_ID),app_private_key_pathos.getenv(ALIPAY_PRIVATE_KEY_PATH),alipay_public_key_cert_pathos.getenv(ALIPAY_PUBLIC_KEY_CERT_PATH),app_public_key_cert_pathos.getenv(APP_PUBLIC_KEY_CERT_PATH),root_cert_pathos.getenv(ALIPAY_ROOT_CERT_PATH),sign_typeRSA2,)app.post(/api/pay/alipay/native)asyncdefcreate_alipay_native(request:Request):顾客下单后生成支付宝二维码bodyawaitrequest.json()biz_content{out_trade_no:fORDER_{uuid.uuid4().hex[:16]},# 商户订单号total_amount:body[amount],# 金额元字符串subject:body[subject],# 商品标题product_code:FACE_TO_FACE_PAYMENT,# 当面付}# 这一步SDK 会自动用你的私钥签名responsealipay.api_alipay_trade_precreate(biz_contentbiz_content)ifresponse[code]10000:return{qr_code:response[qr_code],# 二维码链接out_trade_no:biz_content[out_trade_no],}else:return{error:response[msg]}吧台收到qr_code后把它渲染成二维码图片展示出来。顾客用支付宝 App 扫这个码就会进入支付流程。3.3 接收异步通知——钱到了吗顾客扫码支付成功后支付宝会向你预设的回调地址发送一个 POST 请求告诉你¥28 收到了。这是整个对接过程中最关键的一段代码。写错了要么钱收到了但订单没更新用户投诉要么被人伪造通知白嫖你的商品。app.post(/api/pay/alipay/notify)asyncdefalipay_notify(request:Request):支付宝异步通知回调——每一行都很重要# 1. 获取所有 POST 参数支付宝用 Form 表单格式form_dataawaitrequest.form()paramsdict(form_data)# 2. 验签最关键的一步不验签 裸奔signparams.pop(sign)sign_typeparams.pop(sign_type)ifnotalipay.verify(params,sign):returnResponse(contentfail,status_code400)# 3. 检查交易状态ifparams[trade_status]notin(TRADE_SUCCESS,TRADE_FINISHED):returnResponse(contentsuccess)# 非成功状态直接确认收到out_trade_noparams[out_trade_no]total_amountparams[total_amount]# 4. 幂等检查——支付宝可能重复推送# 25 小时内最多推 8 次你必须保证重复处理不会出问题orderawaitget_order(out_trade_no)iforder.statusPAID:returnResponse(contentsuccess)# 已处理过直接返回# 5. 金额校验——防止有人篡改金额iffloat(total_amount)!float(order.amount):logger.error(f金额不匹配:{total_amount}vs{order.amount})returnResponse(contentfail)# 6. 一切正常更新订单状态awaitupdate_order_status(out_trade_no,PAID,trade_noparams[trade_no])# 7. 返回 success 告知支付宝不再重试returnResponse(contentsuccess)让我解释一下为什么每一步都不能少第 2 步验签如果你不验签任何人都可以伪造一个支付成功的通知发到你的服务器白拿你的咖啡。第 4 步幂等检查支付宝会在 25 小时内最多重试 8 次。如果你的代码不做幂等检查同一笔订单可能被处理多次——比如多次发货。第 5 步金额校验虽然有签名保护但防御要纵深。万一你的系统有其他漏洞导致订单金额被修改这一步能兜住底。第 7 步返回 “success”如果你不返回这个字符串或者返回太慢超过 5 秒支付宝会认为你没收到通知继续重试。四、动手微信支付 Native 扫码支付搞定了支付宝接下来是微信支付。流程类似但有几个坑需要特别注意。4.1 接入准备第一步注册微信支付商户号pay.weixin.qq.com ▼ 第二步完成实名认证和签约 ▼ 第三步商户平台 → API安全 → 申请 API v3 证书 ▼ 第四步设置 APIv3 密钥32 位字符串用于解密回调通知 ▼ 第五步关联 AppID公众号/小程序/移动应用配置项说明安全级别商户号mchid商户唯一标识可公开API V3 密钥解密回调通知 绝密商户 API 证书包含私钥用于签名 绝密微信支付平台证书验证微信支付的响应可公开⚠️安全警告私钥和 API V3 密钥绝对不能硬编码在代码中。用环境变量或者更好——用密钥管理服务如 HashiCorp Vault。4.2 创建支付微信支付的 API V3 需要你手动构造签名不像支付宝有比较完善的官方 SDK 一步到位所以代码会稍微复杂一些。importhashlibimportjsonimportosimporttimeimportuuidimportrequestsfromfastapiimportFastAPI,Request appFastAPI()MCHIDos.getenv(WECHAT_MCHID)# 商户号APPIDos.getenv(WECHAT_APPID)# 应用 IDAPI_V3_KEYos.getenv(WECHAT_API_V3_KEY)# APIv3 密钥SERIAL_NOos.getenv(WECHAT_CERT_SERIAL)# 证书序列号app.post(/api/pay/wechat/native)asyncdefcreate_wechat_native(request:Request):生成微信支付二维码bodyawaitrequest.json()out_trade_nofWX_{uuid.uuid4().hex[:16]}# 构造请求体——注意金额单位是分¥28 2800 分pay_body{appid:APPID,mchid:MCHID,description:body[subject],out_trade_no:out_trade_no,notify_url:https://yourdomain.com/api/pay/wechat/notify,amount:{total:int(float(body[amount])*100),# 元 → 分currency:CNY}}# 构造 API v3 签名timestampstr(int(time.time()))nonce_struuid.uuid4().hexsign_strbuild_sign_string(POST,/v3/pay/transactions/native,timestamp,nonce_str,json.dumps(pay_body))signaturersa_sign(sign_str)# 用商户私钥签名# 发送请求headers{Authorization:fWECHATPAY2-SHA256-RSA2048 fmchid{MCHID},fnonce_str{nonce_str},fsignature{signature},ftimestamp{timestamp},fserial_no{SERIAL_NO},Content-Type:application/json,}resprequests.post(https://api.mch.weixin.qq.com/v3/pay/transactions/native,headersheaders,jsonpay_body)resultresp.json()return{code_url:result.get(code_url),# 二维码链接out_trade_no:out_trade_no,}defbuild_sign_string(method,url,timestamp,nonce_str,body):构造 API v3 待签名字符串returnf{method}\n{url}\n{timestamp}\n{nonce_str}\n{body}\n4.3 接收异步通知——先解密再验签这是微信支付和支付宝最大的区别微信的回调通知是加密的。为什么要加密因为微信更进一步地保护了交易数据——即使有人截获了回调请求也看不到里面的订单信息。处理流程是先验签确认是微信发的→ 再解密用 API V3 密钥→ 最后处理业务。fromcryptography.hazmat.primitives.ciphers.aeadimportAESGCMimportbase64app.post(/api/pay/wechat/notify)asyncdefwechat_notify(request:Request):微信支付异步通知回调——需要先解密bodyawaitrequest.json()# 1. 验证请求签名用微信支付平台证书的公钥timestamprequest.headers.get(Wechatpay-Timestamp)noncerequest.headers.get(Wechatpay-Nonce)signaturerequest.headers.get(Wechatpay-Signature)serialrequest.headers.get(Wechatpay-Serial)raw_bodyawaitrequest.body()ifnotverify_wechat_signature(timestamp,nonce,raw_body.decode(),signature,serial):return{code:FAIL,message:验签失败}# 2. 解密通知内容AEAD_AES_256_GCM# 这一步是微信支付独有的resourcebody[resource]plaintextdecrypt_aes_gcm(nonceresource[nonce],ciphertextresource[ciphertext],associated_dataresource[associated_data],api_v3_keyAPI_V3_KEY)notificationjson.loads(plaintext)# 3. 和支付宝一样幂等检查 金额校验 更新状态out_trade_nonotification[out_trade_no]totalnotification[amount][total]# 单位分orderawaitget_order(out_trade_no)iforder.statusPAID:return{code:SUCCESS,message:OK}# 金额对比时记得统一单位iftotal!int(order.amount*100):# 如果 order.amount 存的是元return{code:FAIL,message:金额不匹配}awaitupdate_order_status(out_trade_no,PAID,trade_nonotification[transaction_id])# 微信支付要求返回 JSON 格式不是字符串 successreturn{code:SUCCESS,message:OK}defdecrypt_aes_gcm(nonce,ciphertext,associated_data,api_v3_key):解密微信支付回调通知——AES-256-GCMkeyapi_v3_key.encode(utf-8)nonce_bytesnonce.encode(utf-8)adassociated_data.encode(utf-8)ifassociated_dataelsebdatabase64.b64decode(ciphertext)aesgcmAESGCM(key)returnaesgcm.decrypt(nonce_bytes,data,ad).decode(utf-8)五、回调机制深度对比走到这里你已经成功对接了支付宝和微信支付。让我们停下来对比一下两者回调机制的关键差异帮你避开生产环境中最常见的坑。5.1 回调重试策略当你的服务器没有正确响应时支付平台不会就此罢休——它们会反复重试支付宝在 25 小时内最多重试8 次。间隔大约是 4m、10m、10m、1h、2h、6h、15h。微信支付最多重试15 次间隔从 15 秒递增到 6 小时15s、15s、30s、3m、10m、20m、30m、30m、30m、60m、3h、3h、3h、6h、6h。这意味着微信支付的重试更频繁、更执着。所以你的回调接口必须做好幂等处理——同一笔订单被推送 15 次你只能处理一次。5.2 通知格式差异维度支付宝微信支付数据格式Form 表单keyvalueJSON加密方式明文 RSA 签名AES-GCM 加密 RSA 签名成功响应返回字符串success返回 JSON{code:SUCCESS}金额字段total_amount元字符串amount.total分整数订单号字段out_trade_noout_trade_no渠道交易号trade_notransaction_id坑中之坑支付宝返回纯字符串success微信返回 JSON。搞反了的话支付平台会认为你没收到通知一直重试。六、你一定会踩的那些坑对接了几十家商户的支付宝和微信支付后我总结了最常见的踩坑清单坑 1签名验证失败症状调用支付宝/微信 API 时返回签名错误。常见原因参数排序不对签名要求按 ASCII 升序排列编码问题中文参数没有 UTF-8 编码私钥和平台上配置的公钥不是一对换行符搞错了签名串里的\n不能少解决方案别自己拼签名串用官方 SDK。如果必须手动实现用官方提供的在线验签工具逐字段比对。坑 2回调收不到症状用户明明支付成功了但服务器没收到通知订单状态卡在待支付。常见原因回调地址不是公网可访问的 HTTPS 地址你的回调接口处理超过 5 秒支付平台会超时防火墙/WAF 拦截了支付平台的请求解决方案开发阶段用 ngrok 或 frp 做内网穿透回调接口里只做轻量处理验签 写数据库重逻辑异步处理永远不要只依赖回调——同时实现主动查询接口作为补偿坑 3金额单位混乱症状用户付了 ¥28你的系统显示收到 ¥2800或 ¥0.28。原因微信用分支付宝用元。存储时没统一。解决方案系统内部**一律用最小单位分**存储。amount_cents 2800而不是amount 28.00。展示时再除以 100。坑 4重复处理症状同一笔订单被处理了多次。比如多次发了优惠券、多次通知了业务系统。原因回调接口没做幂等。解决方案数据库对订单号加唯一约束更新状态前先检查当前状态只从 PENDING → PAID不从 PAID → PAID使用乐观锁或分布式锁坑 5证书过期症状某天凌晨你的支付突然全部失败。原因商户证书或平台证书过期了没有及时更新。解决方案微信支付平台证书会定期轮换你需要实现自动获取逻辑商户 API 证书有效期设置到期前 30 天告警在 CI/CD 流程中加入证书有效期检查七、不仅仅是扫码支付宝和微信的其他支付场景扫码支付只是冰山一角。实际业务中你可能还需要对接这些场景H5 支付用户在手机浏览器里打开你的网站点击支付后唤起支付宝/微信 App 完成支付。适合移动端网页。JSAPI 支付微信独有用户在微信内置浏览器中打开你的公众号/小程序页面直接弹出支付弹窗。这是微信生态内最流畅的支付方式——需要获取用户的openid。App 支付用户在你的原生 App 中发起支付唤起支付宝/微信 App 完成支付。这些场景的核心对接逻辑签名、回调、幂等和本文讲的 Native 扫码完全一样只是请求参数和前端调用方式略有不同。掌握了本文的内容切换到其他场景会非常顺畅。八、退款把钱退回去有收款就有退款。支付宝和微信的退款逻辑相对简单支付宝退款调用alipay.trade.refund接口传入原交易号和退款金额即可。支持部分退款。退款通常即时到账最慢 5 个工作日。注意支付宝没有退款异步通知你需要主动调用查询接口确认退款状态。微信支付退款调用退款接口同样支持部分退款。到账时间 1~3 个工作日。微信有退款异步通知——同样是 AES-GCM 加密的。两个好消息支付宝和微信支付的退款手续费免费不像 PayPal 退款不退手续费而且退款时限都比较长支付宝 3 个月微信 1 年。九、最佳实践清单在结束之前给你一份在生产环境中对接支付宝和微信支付的最佳实践清单✅ 用官方 SDK不要手动拼签名除非你有特殊需求 ✅ 密钥通过环境变量或密钥管理服务存储绝不硬编码 ✅ 回调接口做幂等唯一约束 状态检查 ✅ 同时实现异步通知 主动查询双保险 ✅ 金额统一用最小单位分存储 ✅ 回调处理控制在 5 秒内复杂逻辑异步化 ✅ 所有支付操作记录详细日志排查问题的救命稻草 ✅ 监控证书有效期设置到期告警 ✅ 先在沙盒环境调通再切换到生产环境 ✅ 建立每日自动对账机制本系列文章导读篇目标题你将学到第 1 篇概览篇——一笔订单触发的支付之旅四方模型、支付生命周期、市场格局、成本分析、选型决策第 2 篇支付宝 微信支付——一杯咖啡的扫码之旅本文签名机制、回调处理、AES-GCM 解密、完整对接代码第 3 篇Stripe 信用卡——一件跨境商品的卡支付之旅Payment Intents、3D Secure、PCI DSS、前后端代码第 4 篇PayPal——一位海外买家的安全支付之旅OAuth 认证、Smart Buttons、争议保护、Webhook第 5 篇统一支付网关——当四条河流汇入一片海三层架构、策略模式、幂等设计、对账补偿参考来源支付宝开放平台 - 当面付接入指南支付宝 V3 版签名机制微信支付 API v3 文档微信支付 Native 支付微信支付回调通知欢迎关注公众号coft获取更多深度技术文章。下一篇我们跟着一件 $149.99 的限量球鞋走进 Stripe 和信用卡的世界——3D Secure、PCI DSS、授权与捕获这些听起来吓人的概念其实并没有那么难。