微信支付Java怎么写?从零到上线完整实战指南
目录导读
微信支付Java集成核心概念
微信支付Java集成是电商、餐饮、会员系统等场景中高频需求,开发者需要理解以下核心概念:

- JSAPI支付:适用于在微信内置浏览器中打开的H5页面,需要用户授权获取openid。
- Native支付:生成二维码供用户扫码,适用于PC端或线下扫码场景。
- App支付:微信外部App调用微信支付,适用于自有App。
- H5支付:在非微信浏览器中唤起微信支付,需处理商户号配置与域名拦截。
核心流程:商户系统 → 调用微信支付统一下单接口 → 获取prepay_id → 构建支付参数 → 前端调起支付 → 异步通知处理支付结果 → 主动查询结果。
开发前准备:申请与配置
申请商户号
- 访问pay.weixin.qq.com 注册服务商或直连商户
- 提交营业执照、法人信息,审核通常1-3个工作日
配置API密钥与证书
- APIv3密钥:32位字符,用于敏感信息加密
- 商户证书:p12或pem格式,用于请求签名
- 平台证书:定期轮换,用于验证微信回调签名
设置支付回调域名
- 登录商户平台 → 产品中心 → 开发配置
- 设置JSAPI支付授权目录(如
https://yourdomain.com/pay/) - 设置Native支付回调域名(与服务器IP或域名一致)
微信支付Java SDK选择与依赖配置
官方SDK vs 第三方SDK
- 官方推荐:wechatpay-apiv3(腾讯官方维护,支持v3接口,无XML解析烦恼)
- 成熟第三方:WxJava(开源,社区活跃,功能全面)
Maven依赖示例(官方v3)
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
配置文件yml示例
wechat:
pay:
app-id: wx1234567890abcdef
mch-id: 1230000109
api-v3-key: 你的32位APIv3密钥
merchant-private-key-path: /cert/apiclient_key.pem
merchant-serial-number: 证书序列号
注意:私钥文件不要暴露在git仓库中,建议使用环境变量或密钥管理服务。
关键代码实现:统一下单与支付回调
初始化支付服务
@Configuration
public class WechatPayConfig {
@Bean
public WechatPayClient wechatPayClient(@Value("${wechat.pay.mch-id}") String mchId,
@Value("${wechat.pay.merchant-private-key-path}") String privateKeyPath,
@Value("${wechat.pay.merchant-serial-number}") String serialNo,
@Value("${wechat.pay.api-v3-key}") String apiV3Key) {
// 加载私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new File(privateKeyPath));
// 构建签名器
PrivateKeySigner signer = new PrivateKeySigner(serialNo, merchantPrivateKey);
// 构建客户端
WechatPayClient client = new WechatPayClient.Builder()
.withMerchant(mchId, serialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(Verifier))
.build();
return client;
}
}
统一下单(JSAPI支付)
public String createOrder(String openid, String outTradeNo, Integer totalFee, String description) {
try {
// 构建请求体
Map<String, Object> params = new HashMap<>();
params.put("appid", wechatAppId);
params.put("mchid", wechatMchId);
params.put("description", description);
params.put("out_trade_no", outTradeNo);
params.put("notify_url", "https://你的域名/api/pay/notify");
params.put("amount", Map.of("total", totalFee, "currency", "CNY"));
params.put("payer", Map.of("openid", openid));
// 发起请求
HttpResponse response = wechatPayClient.post(
"https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi",
params
);
// 解析prepay_id
String prepayId = JsonUtil.parse(response.getBody())
.get("prepay_id").asText();
// 构建前端调起支付参数
return buildJsApiParam(prepayId);
} catch (Exception e) {
log.error("统一下单失败", e);
throw new BizException("支付下单失败");
}
}
构建前端支付参数字符串
private String buildJsApiParam(String prepayId) {
long timeStamp = System.currentTimeMillis() / 1000;
String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
String packageStr = "prepay_id=" + prepayId;
// 生成签名
String signStr = wechatAppId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n";
String paySign = RSASignUtil.sign(signStr, merchantPrivateKey);
// 返回JSON字符串
Map<String, String> result = new HashMap<>();
result.put("appId", wechatAppId);
result.put("timeStamp", String.valueOf(timeStamp));
result.put("nonceStr", nonceStr);
result.put("package", packageStr);
result.put("signType", "RSA");
result.put("paySign", paySign);
return JSON.toJSONString(result);
}
处理支付结果回调(Notify)
@PostMapping("/api/pay/notify")
public String handleNotify(HttpServletRequest request, HttpServletResponse response) {
try {
// 1. 读取请求体
String body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
String wechatpaySignature = request.getHeader("Wechatpay-Signature");
String wechatpaySerial = request.getHeader("Wechatpay-Serial");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
// 2. 验证签名(需使用微信平台证书)
boolean verify = verifyWechatSign(body, wechatpaySignature, wechatpaySerial, timestamp, nonce);
if (!verify) {
return failResponse(response, "签名验证失败");
}
// 3. 解密资源数据(AES-256-GCM)
Resource resource = JsonUtil.parse(body).get("resource");
String ciphertext = resource.get("ciphertext").asText();
String associatedData = resource.get("associated_data").asText();
String nonceCipher = resource.get("nonce").asText();
String plaintext = AesUtil.decryptToString(ciphertext, associatedData, nonceCipher, apiV3Key);
// 4. 更新订单状态
JSONObject payResult = JSON.parseObject(plaintext);
String tradeState = payResult.getString("trade_state");
String outTradeNo = payResult.getString("out_trade_no");
if ("SUCCESS".equals(tradeState)) {
orderService.updatePaySuccess(outTradeNo);
}
// 5. 返回成功应答
return successResponse(response);
} catch (Exception e) {
log.error("支付回调处理异常", e);
return failResponse(response, "处理失败");
}
}
常见问题与错误码解析
| 错误码 | 含义 | 排查方向 |
|---|---|---|
| INVALID_REQUEST | 请求参数错误 | 检查appid、mchid、金额单位(分)是否正确 |
| SIGN_ERROR | 签名错误 | 确认APIv3密钥、证书序列号、签名算法是否匹配 |
| ORDERPAID | 订单已支付 | 业务层面幂等处理,避免重复通知 |
| NOTENOUGH | 余额不足 | 引导用户更换支付方式 |
| SYSTEMERROR | 系统繁忙 | 使用相同参数重试(幂等) |
高频问题:
- "签名验证失败":检查微信平台证书是否已自动更新(建议使用证书下载器定时更新)
- "支付成功后未收到回调":确认回调地址可公网访问,检查防火墙是否拦截
- "金额单位错误":微信支付金额以分为单位,前端需转换
安全与性能优化建议
- 使用v3接口:v1接口使用XML+MD5签名已过时,v3使用JSON+RSA/AES更安全
- 敏感信息加密:客户openid、交易金额等不记入日志
- 回调防重放:记录通知的ID(
id字段),相同ID只处理一次 - 证书管理:使用自动证书下载器,避免证书到期中断支付
- 超时设置:HTTP请求设置10秒超时,避免阻塞线程
- 异步处理:回调逻辑尽量异步化,避免阻塞微信服务器重试
问答环节
Q1:微信支付Java开发,必须要使用官方SDK吗?
A:不必须,你可以使用原生的HttpClient发送请求,但需要自行实现RSA签名、AES解密、平台证书验证等逻辑,复杂度较高,推荐使用官方SDK或经过广泛验证的开源库如WxJava,可节省50%以上的开发时间。
Q2:Native支付和JSAPI支付在代码实现上的区别是什么?
A:主要区别在于:
- 接口路径:Native使用
/v3/pay/transactions/native,JSAPI使用/v3/pay/transactions/jsapi - 返回参数:Native返回
code_url(二维码内容),JSAPI返回prepay_id - 前端调用:Native需生成二维码展示,JSAPI需要调用
wx.chooseWXPay - 需要openid:JSAPI必须传递用户openid,Native可以不传(扫码后由微信端获取)
Q3:微信支付回调通知接收不到,如何排查?
A:按以下步骤排查:
- 确认回调URL是公网可达的HTTPS地址,且未配置IP白名单或防火墙限制
- 检查商户平台 → 产品中心 → 开发配置中是否设置了正确的回调域名
- 在回调方法入口打印日志,确认是否有请求到达
- 使用Postman模拟微信回调(注意签名验证需用真实密文)
- 检查服务器时间是否与标准时间偏差超过5分钟(影响签名验证)
Q4:如何处理支付金额的精度问题?
A:微信支付中金额以分为单位,是整数,建议:
- 前端展示元,后端存储和传参使用分
- Java中使用
int或Long存储,避免浮点数精度丢失 - 转换公式:
分 = (int)(元 * 100),注意使用BigDecimal进行乘法public static Long yuanToFen(BigDecimal yuan) { return yuan.multiply(new BigDecimal("100")).longValue(); }
Q5:订单支付成功后,如何防止用户重复提交?
A:建议采用以下策略:
- 数据库唯一索引:订单号
out_trade_no设置唯一约束 - 幂等键:每次下单生成唯一
request_id,使用Redis分布式锁或数据库记录 - 回调处理幂等:处理回调时先检查订单状态,已成功的直接返回成功响应
- 前端防抖:支付结果确认前禁用支付按钮