微信支付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分布式锁或数据库记录 - 回调处理幂等:处理回调时先检查订单状态,已成功的直接返回成功响应
- 前端防抖:支付结果确认前禁用支付按钮
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播