微信支付Java怎么写?

wen python案例 4

微信支付Java怎么写?从零到上线完整实战指南

目录导读


微信支付Java集成核心概念

微信支付Java集成是电商、餐饮、会员系统等场景中高频需求,开发者需要理解以下核心概念:

微信支付Java怎么写?

  1. JSAPI支付:适用于在微信内置浏览器中打开的H5页面,需要用户授权获取openid。
  2. Native支付:生成二维码供用户扫码,适用于PC端或线下扫码场景。
  3. App支付:微信外部App调用微信支付,适用于自有App。
  4. 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 系统繁忙 使用相同参数重试(幂等)

高频问题

  • "签名验证失败":检查微信平台证书是否已自动更新(建议使用证书下载器定时更新)
  • "支付成功后未收到回调":确认回调地址可公网访问,检查防火墙是否拦截
  • "金额单位错误":微信支付金额以为单位,前端需转换

安全与性能优化建议

  1. 使用v3接口:v1接口使用XML+MD5签名已过时,v3使用JSON+RSA/AES更安全
  2. 敏感信息加密:客户openid、交易金额等不记入日志
  3. 回调防重放:记录通知的ID(id字段),相同ID只处理一次
  4. 证书管理:使用自动证书下载器,避免证书到期中断支付
  5. 超时设置:HTTP请求设置10秒超时,避免阻塞线程
  6. 异步处理:回调逻辑尽量异步化,避免阻塞微信服务器重试

问答环节

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:按以下步骤排查:

  1. 确认回调URL是公网可达的HTTPS地址,且未配置IP白名单或防火墙限制
  2. 检查商户平台 → 产品中心 → 开发配置中是否设置了正确的回调域名
  3. 在回调方法入口打印日志,确认是否有请求到达
  4. 使用Postman模拟微信回调(注意签名验证需用真实密文)
  5. 检查服务器时间是否与标准时间偏差超过5分钟(影响签名验证)

Q4:如何处理支付金额的精度问题?

A:微信支付中金额以分为单位,是整数,建议:

  • 前端展示元,后端存储和传参使用分
  • Java中使用 intLong 存储,避免浮点数精度丢失
  • 转换公式:分 = (int)(元 * 100),注意使用 BigDecimal 进行乘法
    public static Long yuanToFen(BigDecimal yuan) {
      return yuan.multiply(new BigDecimal("100")).longValue();
    }

Q5:订单支付成功后,如何防止用户重复提交?

A:建议采用以下策略:

  1. 数据库唯一索引:订单号 out_trade_no 设置唯一约束
  2. 幂等键:每次下单生成唯一 request_id,使用Redis分布式锁或数据库记录
  3. 回调处理幂等:处理回调时先检查订单状态,已成功的直接返回成功响应
  4. 前端防抖:支付结果确认前禁用支付按钮

抱歉,评论功能暂时关闭!