Java微信支付案例完整实现指南(含核心源码)
目录导读
- 微信支付接入前的必备准备
- 核心API与支付流程解析
- Java后端实现:统一下单与回调处理
- 常见问题与排坑问答(FAQ)
- 安全与性能优化建议
微信支付接入前的必备准备
在开始Java微信支付案例之前,需要完成以下环境与资质准备:

-
商户号与AppID
登录微信支付商户平台,获取商户号(mch_id),同时需拥有已认证的微信服务号或小程序,获取对应的AppID,两者需在商户平台进行关联绑定。 -
API密钥与证书
- APIv3密钥:在商户平台设置APIv3密钥,用于生成请求头中的签名。
- 商户证书:下载商户证书(p12或pem格式),用于加密敏感信息(如退款接口)或作为回调验证凭证。
- 平台证书:通过平台证书下载接口获取,用于验证微信回调通知的签名。
-
开发环境
- JDK 8+
- Maven或Gradle(推荐Maven)
- 内网穿透工具(如ngrok)或已备案域名,用于回调地址测试
注意:回调域名必须在商户平台配置白名单,否则支付成功后无法收到通知。
核心API与支付流程解析
微信支付的主流场景包括:JSAPI支付(公众号内支付)、小程序支付、Native支付(扫码支付)、H5支付,本文以JSAPI支付为例,其流程如下:
用户触发支付 → 后端调用统一下单API → 返回预支付ID → 前端调起微信支付 → 用户密码验证 → 异步回调通知后端 → 后端查询订单状态 → 完成业务
关键接口清单
| 接口名称 | 请求方式 | 功能简述 |
|---|---|---|
| v3/pay/transactions/jsapi | POST | 统一下单,返回prepay_id |
| v3/pay/transactions/id/{transaction_id} | GET | 通过微信支付订单号查询订单 |
| v3/pay/transactions/out-trade-no/{out_trade_no} | GET | 通过商户订单号查询订单 |
| v3/pay/transactions/jsapi/... | POST | 关闭订单(支付超时后使用) |
Java后端实现:统一下单与回调处理
关键依赖(pom.xml)
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
配置类(WechatPayConfig.java)
@Component
@ConfigurationProperties(prefix = "wechat.pay")
public class WechatPayConfig {
private String appId;
private String mchId;
private String apiV3Key;
// 证书路径
private String privateKeyPath;
private String merchantSerialNumber;
private String notifyUrl;
// 省略getter/setter
}
统一下单核心代码(PayService.java)
@Service
public class PayService {
@Autowired
private WechatPayConfig wechatPayConfig;
public String createJsapiOrder(String openId, String orderNo, Integer totalFee, String description) {
try {
// 1. 构建HTTP客户端
HttpMethods httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(wechatPayConfig.getMchId(),
wechatPayConfig.getMerchantSerialNumber(),
new File(wechatPayConfig.getPrivateKeyPath()))
.withValidator(new WechatPay2Validator(VerifierHolder.getVerifier()))
.build();
// 2. 构建请求体
JSONObject params = new JSONObject();
params.put("appid", wechatPayConfig.getAppId());
params.put("mchid", wechatPayConfig.getMchId());
params.put("description", description);
params.put("out_trade_no", orderNo);
params.put("notify_url", wechatPayConfig.getNotifyUrl());
params.put("amount", new JSONObject() {{
put("total", totalFee); // 单位:分
put("currency", "CNY");
}});
params.put("payer", new JSONObject() {{
put("openid", openId);
}});
// 3. 发送请求
HttpUriRequest request = RequestBuilder.create("POST")
.setUri("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi")
.setHeader("Content-Type", "application/json")
.setHeader("Accept", "application/json")
.setEntity(new StringEntity(params.toJSONString(), "utf-8"))
.build();
HttpResponse response = httpClient.execute(request);
String result = EntityUtils.toString(response.getEntity());
// 4. 解析预支付ID
JSONObject respObj = JSONObject.parseObject(result);
String prepayId = respObj.getString("prepay_id");
// 5. 生成前端调起支付所需的参数
return generatePayParams(prepayId);
} catch (Exception e) {
log.error("统一下单失败", e);
throw new BusinessException("支付创建失败");
}
}
}
回调通知处理(PayNotifyController.java)
@PostMapping("/pay/notify")
public String handleNotify(HttpServletRequest request) {
// 1. 读取请求体
String body = IOUtils.toString(request.getInputStream(), "utf-8");
// 2. 获取签名头
String signature = request.getHeader("Wechatpay-Signature");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
// 3. 验证签名(核心安全步骤)
if (!WechatPayUtil.verifySignature(body, signature, timestamp, nonce)) {
log.warn("回调签名验证失败");
return "FAIL";
}
// 4. 解析回调数据
JSONObject notifyObj = JSONObject.parseObject(body);
String resourceType = notifyObj.getString("resource_type");
if ("encrypt-resource".equals(resourceType)) {
// 解密resource中的ciphertext
JSONObject resource = notifyObj.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String associatedData = resource.getString("associated_data");
String nonceStr = resource.getString("nonce");
String plaintext = WechatPayUtil.decrypt(ciphertext, associatedData, nonceStr, apiV3Key);
JSONObject payResult = JSONObject.parseObject(plaintext);
String outTradeNo = payResult.getString("out_trade_no");
String transactionId = payResult.getString("transaction_id");
String tradeState = payResult.getString("trade_state");
// 5. 更新订单状态(加锁防重复)
if ("SUCCESS".equals(tradeState)) {
orderService.updateOrderStatus(outTradeNo, transactionId);
}
}
return "SUCCESS"; // 返回SUCCESS给微信
}
常见问题与排坑问答(FAQ)
Q1:统一下单时报错“签名错误”怎么办?
A:检查以下三点:
privateKeyPath是否正确指向商户证书的私钥文件(通常是pem格式,需要包含-----BEGIN PRIVATE KEY-----)merchantSerialNumber是否与微信商户平台显示的证书序列号一致(可在商户平台“API安全”-“API证书”中查看)- 请求体中的
out_trade_no是否每次唯一,且长度≤32位
Q2:用户支付成功后,回调通知一直收不到?
A:按顺序排查:
- 回调地址
notify_url必须是外网可访问的URL,且已配置在商户平台的“开发配置”-“支付回调域名”中。 - 检查服务器防火墙是否放行443端口,且无反向代理做IP白名单过滤。
- 使用ngrok或公网服务器测试时,确认请求体未被篡改或压缩。
Q3:前端调起支付后弹窗提示“缺少参数”?
A:前端参数需要从后端返回的 generatePayParams 方法中获取,检查以下JSON结构是否正确:
{
"appId": "wx...",
"timeStamp": "当前时间戳",
"nonceStr": "随机字符串",
"package": "prepay_id=wx...",
"signType": "RSA",
"paySign": "通过规则生成的签名"
}
注意 paySign 的签名规则:以 appId + \n + timeStamp + \n + nonceStr + \n + package + \n 拼接字符串,再用商户私钥进行SHA256-RSA签名。
Q4:支付金额是1元,但用户实际支付多了一次手续费?
A:微信支付没有额外手续费,检查你的计算逻辑:totalFee 单位是“分”,即1元需传 100,如果误传了 1,用户支付0.01元,而数据库记录为1元,则会出现金额不符,建议在关键位置增加日志:log.info("支付金额:{}分", totalFee);
Q5:如何防止回调重复处理?
A:回调可能因为网络重试等原因到达多次,建议的做法:
- 在更新订单状态时使用数据库行锁:
select ... for update - 或使用Redis分布式锁,key =
order:notify:${orderNo},过期时间设10秒。 - 判断
trade_state后,若订单状态已为“已支付”,直接返回SUCCESS不处理。
安全与性能优化建议
-
密钥管理
私钥文件绝不可上传到代码仓库,建议保存在外部配置中心(如Apollo、Nacos)或服务器环境变量中,生产环境可使用HashiCorp Vault进行密钥轮换。 -
签名验证
所有的微信回调(包括退款结果通知)都必须验证签名,推荐使用wechatpay-java官方SDK的WechatPay2Validator,它内置了平台证书自动更新逻辑。 -
超时与重试
统一下单接口建议设置连接超时5s,读取超时10s,对于退款、查询订单等幂等接口,可以实现指数退避重试策略。 -
日志脱敏
打印日志时要注意脱敏。openid只保留前4位和后4位(如:o***1234),totalFee可记录,但apiV3Key和私钥内容绝不能输出。 -
并发控制
如果秒杀场景下大量并发下单,建议预扣库存并在回调成功后再确认扣减,注意微信支付对同一商户号下并发请求有限流(默认2000 QPS),超出会返回SYSTEM_ERROR。
本文从准备工作、API流程、Java代码实现到排坑问答,完整覆盖了微信支付(JSAPI)的实战全链路,核心要点在于:正确配置证书、严格验证回调签名、幂等处理通知,如果你需要对接Native支付或小程序支付,只需将接口地址替换为对应接口,并调整前端调起支付方式即可,希望这篇案例能帮你一次跑通微信支付流程,避免常见踩坑。
参考来源:微信支付官方文档V3、开源SDK
wechatpay-java示例、互联网社区实践总结(去伪存真整理)。