简介:一份面向中高级Java开发者的支付业务实战PDF,聚焦服务器端集成微信支付与支付宝支付及退款功能,解决APP或线上业务中统一下单、签名、回调、退款等核心难点。包内为1个PDF文件,约68KB,内容紧凑,配合代码片段可直接查阅。这份PDF从微信支付统一下单流程讲到签名规则、HTTP请求与XML响应解析,再对比支付宝支付在接口和参数上的差异,并给出退款功能的使用说明。资料以WXPay工具类的prePay统一下单、genProductArgs生成请求参数、getRep签名封装为主线,覆盖支付宝接口差异、PayService模块化封装、退款与回调处理,以及事务、日志、安全等工程化要点。已有396人学习下载,适合在开发支付模块时快速对照、排错与补全设计。 搞支付对接这几年,我最大的感受是:真正让你加班到凌晨的,往往不是需求有多复杂,而是那些藏在签名、回调和退款里的细节。项目标题叫“java服务器端微信、支付宝支付和退款功能”,听起来就是调三个接口的事,实际上涉及两套完全不同的安全体系、三四个证书密钥、回调幂等、退款状态机,任何一个环节想当然,都会让联调时间翻倍。这篇文章我不打算贴那种满屏代码的“官方文档复读”,而是把从零到一接入微信支付v3和支付宝支付的完整链路拆开讲清楚,重点放在那些文档里不会明说、但线上一定会踩的坑上。
1. 支付对接的底层逻辑:先搞懂这笔钱是怎么“安全”流动的
1.1 微信和支付宝,其实是两套不同的安全模型
很多人第一次接支付,习惯性以为微信和支付宝的对接方式差不多,真正动手才发现,两者从签名方式到证书体系几乎是两个世界。微信支付v3是“商户私钥签名 + 微信平台证书验签 + APIv3密钥解密回调”,支付宝则是“应用私钥签名 + 支付宝公钥验签”。差在哪里?微信的回调报文是加密的,里面还有一个独立的“平台证书”用来验证微信响应的真实性;支付宝的回调报文是明文的,全靠验签来判断是不是支付宝发来的。这导致你写代码时要处理的东西完全不同。
还有一个最容易被忽略的差异:金额单位。微信支付所有金额单位都是“分”,整数;支付宝大部分接口的金额单位是“元”,浮点数,而且要求保留两位小数。这看起来是个小问题,实际是最高频的线上Bug来源,后文我会专门讲。
1.2 服务器端到底要负责哪几件事
“服务器端支付功能”不是只发一个下单请求就完了,整个闭环里服务器端要承担这些职责:
- 创建订单时,调用支付平台的下单接口,拿到支付参数返回给客户端。
- 接收支付平台的异步回调,更新订单状态为“已支付”。
- 处理用户发起的退款请求,调用退款接口,并处理退款结果。
- 定时对账,保证本地订单状态和支付平台一致。
也就是说,你必须先把“下单、回调、退款、对账”这四个动作当成一个完整的消息闭环来设计,而不是一个个孤立的接口。尤其是回调环节,支付平台会多次重试,如果你没有做幂等,一笔订单被重复更新成“已支付”只是小问题,如果重复发货就是事故了。
2. 密钥与证书体系:微信和支付宝分歧最大的地方
2.1 微信支付v3需要准备哪些“钥匙”
微信支付的密钥体系是它最劝退新人的地方,我画个简化的对应关系:
| 名称 | 用途 | 谁生成 |
|---|---|---|
| 商户号(mchid) | 商户唯一标识 | 商户平台 |
| 商户API证书(apiclient_cert.pem、apiclient_key.pem) | 下单/退款请求签名 | 商户平台申请 |
| APIv3密钥 | 解密回调、部分接口加签 | 商户平台自行设置 |
| 微信平台证书 | 验证微信响应的签名 | 微信支付平台 |
这里有个最常见的误区:很多人以为API证书和APIv3密钥是一个东西。不是。API证书里的私钥用来给你发出的请求做签名,证明“这个请求是商户发的”;APIv3密钥是你在商户平台手动设置的32字节对称密钥,用来解密微信回调里的加密订单数据。漏配任何一个,都会出现“验签失败”或者“解密失败”的报错。
2.2 支付宝的密钥体系,相对“亲民”但容易搞混公钥
支付宝用“应用私钥 + 支付宝公钥”这套非对称签名体系。你在开放平台创建应用后,用官方密钥工具生成RSA2密钥对,把应用公钥上传到平台,平台会返回一个支付宝公钥。商户私钥负责签名,支付宝公钥负责验签。
这里需要特别注意的是,支付宝开放平台有两种密钥格式:公钥模式和公钥证书模式。普通商户用公钥模式就够了,只需要配置应用私钥和支付宝公钥两段字符串。不要一开始就去折腾证书模式,那是给超大型企业用的。使用官方SDK时,代码里配置的是应用私钥,验签用支付宝公钥,这两个字符串千万别填反了——我见过有人把应用私钥当支付宝公钥填进去,结果所有异步通知验签全部失败,排查了一整天。
补充一个实操技巧:支付宝的沙箱环境非常完善,开放平台免费提供沙箱应用和模拟买家账号,可以用它完整走一遍支付到回调再到退款的全流程。微信目前没有这么“傻瓜式”的沙箱,一般用小程序或App测试环境小额实测,或者直接用商户平台的“模拟支付”能力做部分场景验证。
3. 微信支付v3实战:下单、回调、退款的关键细节
3.1 下单接口:签名是第一步也是最大的一道坎
微信支付v3的下单接口是POST /v3/pay/transactions/jsapi(JSAPI支付)或/v3/pay/transactions/app(App支付),请求头里必须带一个Authorization头,格式是WECHATPAY2-SHA256-RSA2048。这个头的构造逻辑是:
- 拼接签名串,格式为
请求方法\n请求路径\n时间戳\n随机串\n请求体\n。 - 用商户私钥做 SHA256withRSA 签名。
- Base64编码后,连同商户号、随机串、时间戳、证书序列号一起放进 Header。
核心代码如下,这段代码建议直接沉淀成工具类,微信和支付宝的不同接口都能复用:
private String buildAuthorization(String method, String urlPath, String body) throws Exception { long timestamp = System.currentTimeMillis() / 1000; String nonceStr = UUID.randomUUID().toString().replace("-", ""); String message = method + "\n" + urlPath + "\n" + timestamp + "\n" + nonceStr + "\n" + (body == null ? "" : body) + "\n"; PrivateKey privateKey = loadPrivateKey("/path/apiclient_key.pem"); Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(privateKey); sign.update(message.getBytes(StandardCharsets.UTF_8)); String signature = Base64.getEncoder().encodeToString(sign.sign()); return "WECHATPAY2-SHA256-RSA2048 mchid=\"" + mchId + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\"" + serialNo + "\"," + "signature=\"" + signature + "\""; }注意几个细节:时间戳单位是秒,不是毫秒;serial_no是商户API证书的序列号,不是商户号;请求体如果是空字符串也要拼一个空行进去。很多“签名错误”就是这三个点写错导致的。
3.2 回调处理:不是“收到就返回success”那么简单
微信支付成功后会往你配置的通知地址发一个POST请求,body里的resource字段是加密的,需要用APIv3密钥做 AES-256-GCM 解密。解密逻辑如下:
private String decryptResource(Resource resource, String apiV3Key) throws Exception { byte[] nonce = resource.getNonce().getBytes(StandardCharsets.UTF_8); byte[] associatedData = resource.getAssociatedData().getBytes(StandardCharsets.UTF_8); byte[] ciphertext = Base64.getDecoder().decode(resource.getCiphertext()); SecretKeySpec keySpec = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES"); GCMParameterSpec spec = new GCMParameterSpec(128, nonce); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, keySpec, spec); cipher.updateAAD(associatedData); return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8); }解密出来的 JSON 里包含out_trade_no、transaction_id、trade_state、amount等字段。关键的业务逻辑在这里:
- 先查本地订单,确认订单存在。
- 校验
amount.total是否和本地订单金额一致,防止有人伪造回调或传错金额。 - 用
out_trade_no做数据库唯一索引或加分布式锁,保证并发回调下订单状态只更新一次。 - 业务处理成功后返回 HTTP 200 且 body 为
{"code":"SUCCESS","message":"成功"}。如果返回非200,微信会按照一定策略重试多次,重试持续数小时甚至一天,所以回调处理必须稳定。
3.3 退款接口:金额校验比下单更严格
微信退款的接口是POST /v3/refund/domestic/refunds,注意退款接口不需要微信平台证书验签,但仍需要用商户私钥做请求签名。请求参数里最关键的是这三个:
out_trade_no:原支付订单号(或transaction_id)。out_refund_no:商户侧退款单号,必须唯一,它就是退款请求的幂等键。amount:包含refund(退款金额,分)、total(原单总金额,分)和currency。
有一个很阴间的细节:amount.total是原订单总金额,不是本次退款金额。有些人只把refund填对,total随手填了退款金额,微信会直接拒绝。退款提交成功后,微信返回“退款单号”等信息,但退款资金是异步到账的,不能以接口同步返回作为退款成功的唯一依据。需要后续用退款查询接口确认status变成SUCCESS,再更新本地退款状态。另外注意:全额退款和部分退款的业务逻辑要考虑清楚,部分退款时原订单还可以继续部分支付,这会影响订单状态机的设计。
4. 支付宝支付实战:签名、异步通知与退款
4.1 下单与签名:SDK帮了大忙,但参数坑不可不防
支付宝的电脑网站支付、手机网站支付、App支付,本质都是先调用alipay.trade.*.pay一类接口,拿到一个支付请求字符串或二维码链接。官方SDK封装了签名逻辑,你用起来确实省心,但参数配置一定要走心。以电脑网站支付为例:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setNotifyUrl("https://yourdomain.com/notify"); request.setReturnUrl("https://yourdomain.com/return"); JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderNo); bizContent.put("total_amount", "0.01"); bizContent.put("subject", "测试商品"); bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); request.setBizContent(bizContent.toJSONString()); String form = alipayClient.pageExecute(request).getBody();几个容易出问题的地方:out_trade_no必须唯一,重复会直接报错;total_amount必须是字符串且是元,不是分;subject不要放太长或带特殊字符的文本,部分场景会被支付宝拦截。如果你在沙箱测试时发现页面打不开或支付失败,大概率是沙箱应用的网关和密钥配错了。
4.2 异步通知验签:这套逻辑值得背下来
支付宝的异步通知是POST一个表单格式的键值对数据,验签方法官方SDK一行就能搞定:
boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, alipayPublicKey, "UTF-8", "RSA2");验签通过后,业务校验的重点是:
app_id必须是你自己的应用ID。out_trade_no必须在本地订单中存在。total_amount必须和本地订单金额一致。trade_status只有为TRADE_SUCCESS或TRADE_FINISHED时才更新订单为已支付。
支付宝的通知成功应答非常“朴素”,直接输出纯文本success,不能输出JSON,也不能输出html。如果你返回别的,支付宝会每隔一段时间重发通知,直到你返回“success”。这种重试机制本身是对业务幂等性的压力测试:通知到达顺序可能乱,重复通知可能间隔很久,没有幂等设计迟早出事。
4.3 退款接口:同步结果和异步到账要分清楚
支付宝退款对应接口是alipay.trade.refund,关键参数:
out_trade_no或trade_no:原订单号。refund_amount:退款金额,单位元,注意这里是字符串。out_request_no:退款请求号,一笔退款请求的唯一标识,部分退款时这个参数坚决不能重复。
支付宝退款接口是同步返回的,返回结果里有fund_change字段,基本能告诉你钱是否立即退了。但真正资金到账、银行处理可能有延迟,尤其跨境或信用卡场景。所以严谨的做法是:先用alipay.trade.refund发起,然后定期调用alipay.trade.fastpay.refund.query查询退款状态,以此更新本地退款状态。很多做电商的同学会忽略这个查询步骤,结果就是本地显示“退款成功”,用户银行卡迟迟没到账,客诉来了才发现问题。
5. 退款与对账:比付款更需要设计的状态管理
5.1 退款状态机:从“退款申请”到“退款完成”有中间态
支付系统常见的错误是把退款当成一瞬间的事,本地只有“未退款”和“已退款”两个状态。实际上退款至少有申请中、退款中、退款成功、退款失败四个状态。以微信为例,退款提交后返回PROCESSING,过一段时间才会变成SUCCESS或FAILED。如果下单和退款在一个事务里同步处理,遇到PROCESSING状态就不知所措,报表也会对不上。
我的做法是:退款单独立表,和原订单解耦。退款单状态包括INIT、REFUNDING、SUCCESS、FAILED,用一个定时任务轮询微信或支付宝的退款查询接口,把状态推进到位。对于退款失败的,要设计重试机制,同时给运营一个手动发起退款的入口,线上环境难免有自动退款失败的情况。
5.2 对账是支付闭环的“最后兜底”
再完美的实时回调,也可能丢消息或出现状态不一致。支付平台都提供对账单下载接口:微信有v3/bill/tradebill,支付宝有alipay.data.dataservice.bill.downloadurl.query,一般是下载日账单文件。我的习惯是每天凌晨拉取前一天的账单文件,逐笔和本地订单比对,重点关注三类差异:
- 本地已支付,但账单里没有:可能是回调还没到,也可能是账单延迟。
- 账单里有支付,本地没有:可能是回调丢失,需要主动查询并补单。
- 金额不一致:基本都是单位换算或金额写死导致的,需要立刻人工介入。
这个环节虽然枯燥,但它是整个支付系统最可靠的安全网。甚至可以这样理解:回调是实时通道,对账是兜底通道,两者都做了才叫完整的支付闭环。
6. 沙箱联调与高频报错:线上环境是唯一考官
6.1 支付宝沙箱和微信测试环境的差异
支付宝沙箱是我用过最顺手的支付联调环境:开放平台里创建沙箱应用,会给你一套虚拟的AppID、支付宝网关、应用私钥和支付宝公钥,还有模拟买家账号。用它跑通“下单→跳转收银台→支付→异步通知→退款”整个流程,大概半小时就能完成,强烈建议把所有接口先在这里跑一遍再上微信。
微信这边没有完全等价的沙箱,但也不是完全不能测试。小程序可以配测试号环境,App支付可以配测试包BundleID,只是每个环境都要单独申请商户号和关联AppID,流程上麻烦一些。如果实在嫌麻烦,也可以小金额真实支付测试,但千万别频繁小额支付测试,容易被风控盯上,可能触发商户号异常。
6.2 我整理的高频报错排查表
把这些年运维中遇到的高频报错整理成一张表,基本能覆盖80%的线上问题:
| 报错现象 | 根本原因 | 处理方法 |
|---|---|---|
| 微信下单报“签名错误” | 时间戳用了毫秒、证书序列号填错、签名串拼接顺序错 | 按官方文档逐一核对签名串,先输出签名串和官方Demo对比 |
| 微信回调解密失败 | APIv3密钥设置错误,或解密的密文不是基于该密钥加密 | 去商户平台重置APIv3密钥,确认32字节的密钥和代码配置一致 |
| 微信支付回调多次收到 | 业务代码返回非200,或者没有正确返回成功应答 | 保证回调接口幂等,返回正确的成功JSON |
| 支付宝验签失败 | 应用私钥和支付宝公钥不匹配、使用了错误的字符编码 | 重新确认密钥对,注意使用RSA2模式验签 |
| 支付宝通知一直收不到 | 通知地址外网不可达、返回了非“success”内容 | 检查公网地址和防火墙,统一用纯文本返回“success” |
| 退款金额校验失败 | 微信的total填成了退款金额、支付宝的金额单位错误 | 仔细阅读接口文档金额字段说明,退款单独立日志排查 |
还有一个容易被忽视的问题:证书过期。微信商户API证书有效期为5年,看起来很长,但很多老项目里证书是硬编码在服务器上的,到期前一个月就要做换证书演练。平台证书如果是手动下载的静态文件,同样需要关注有效期,我建议直接用官方 SDK 里封装的证书自动更新逻辑,或者定时任务定期拉取平台证书,避免线上突然验签失败。
支付接入做到最后,考验的其实不是编码能力,而是你能不能把所有“可能出错但文档没说”的细节提前堵住。签名串、金额单位、回调应答格式、退款状态机、对账任务,这五个点一旦处理好,微信和支付宝的支付退款功能就是一套可以稳定跑很多年的基础设施。至于更复杂的服务商模式、分账、跨境支付,无非是在这套基础认知上做延伸,地基打牢了,上层就快了。
本文还有配套的精品资源,点击获取