之前项目用的微信支付都是v2版本的,然后新项目我尝试下v3的,然后碰到很多坑,记录一下

我用的是微信支付的一个sdk

首先引入pom

<dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.2.1</version>
        </dependency>

先做下准备工作,就是证书,因为v3版本是必须要带证书的,这里面有个坑,搞了半天才解决。

这里需要两个证书,一个是商户的证书,还一个是微信平台的证书,开始的时候以为只是商户证书,一直报签名错误。商户证书怎么获取不用说,都知道,来说说微信平台的证书,这里是命令行下载平台证书。

Certificate Downloader 是 Java 微信支付 APIv3 平台证书的命令行下载工具。该工具可从 https://api.mch.weixin.qq.com/v3/certificates 接口获取商户可用证书,并使用 APIv3 密钥 和 AES_256_GCM 算法进行解密,并把解密后证书下载到指定位置。该工具已经通过 Maven 打包成 CertificateDownloader.jar,可在 release 中下载。

这里,必需参数有:

  • 商户的私钥文件,即 -f
  • 证书解密的密钥,即 -k
  • 商户号,即 -m
  • 保存证书的路径,即 -o
  • 商户证书的序列号,即 -s

非必需参数有:

  • 微信支付证书,用于验签,即 -c

完整命令如:

java -jar CertificateDownloader.jar -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath} -c ${wechatpayCertificateFilePath}

获取完证书,接下来就简单多了,我这里只展示二维码付款的示例,其他都差不多了

初始化参数

private void init() {
        //商户私钥
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                new ByteArrayInputStream(ResourceUtil.readBytes(wxpayPty.getMchKeyPath())));
        //微信平台证书
        X509Certificate wechatpayCertificate = PemUtil.loadCertificate(
                new ByteArrayInputStream(ResourceUtil.readBytes(wxpayPty.getWxCertPath())));

        ArrayList<X509Certificate> listCertificates = new ArrayList<>();
        listCertificates.add(wechatpayCertificate);

        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(wxpayPty.getMchId(), wxpayPty.getMchCertNo(), merchantPrivateKey)
                .withWechatpay(listCertificates)
                .build();
    }

获取二维码

public byte[] ftf(String orderNo, String ip) throws IOException {
        init();

        JSONObject reqdata = new JSONObject();
        reqdata.set("appid", wxpayPty.getAppId());
        reqdata.set("mchid", wxpayPty.getMchId());
        reqdata.set("description", "xx");
        reqdata.set("out_trade_no", order.getOrderNo());
        reqdata.set("notify_url", wxpayPty.getNotifyUrl());

        JSONObject amount = new JSONObject();
//        amount.set("total", order.getTotalPrice().multiply(new BigDecimal(100)).intValue());
        amount.set("total", new BigDecimal(0.01).multiply(new BigDecimal(100)).intValue());

        reqdata.set("amount", amount);

        HttpPost httpPost = new HttpPost(wxpayPty.getNativeUrl());

        // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
        StringEntity reqEntity = new StringEntity(
                reqdata.toString(), ContentType.create("application/json", "utf-8"));
        httpPost.setEntity(reqEntity);
        httpPost.addHeader("Accept", "application/json");

        CloseableHttpResponse response = httpClient.execute(httpPost);
        byte[] code = null;
        //response.getStatusLine().getStatusCode() != 401
        try {
            HttpEntity entity = response.getEntity();
            // do something useful with the response body

            JSONObject resp = JSONUtil.parseObj(EntityUtils.toString(entity));
            if (resp.get("code_url") != null) {
                //业务处理
                //生成二维码
                code = QrCodeUtil.generatePng(resp.getStr("code_url"), QrConfig.create().setImg("static/image/log.jpg"));
            }
            // and ensure it is fully consumed
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }

        return code;
    }

支付回调

public int notifyV3(HttpServletRequest request) throws IOException, GeneralSecurityException {
        log.info("微信回调开始");
        JSONObject result = getResource(request, "trade");
        String orderNo = result.getStr("out_trade_no");
        if ("SUCCESS".equals(result.getStr("code"))) {
            // 业务处理
        }
        return 0;
    }

获取回调结果,方法里面的type,是想代码重用,所以加了这个字段,就是根据type获取不同的值,看明白

private JSONObject getResource(HttpServletRequest request, String type) throws IOException, GeneralSecurityException {
        JSONObject result = new JSONObject();
        String respStr = RequestUtil.getRequestJsonString(request);
        JSONObject resp = JSONUtil.parseObj(respStr);
        log.info("微信回调:"+respStr);
        if ("TRANSACTION.SUCCESS".equals(resp.getStr("event_type")) || "REFUND.SUCCESS".equals(resp.getStr("event_type"))) {
            AesUtil aesUtil = new AesUtil(wxpayPty.getV3Key().getBytes("utf-8"));

            JSONObject resource = resp.getJSONObject("resource");
            String oriResourceStr = aesUtil.decryptToString(resource.getStr("associated_data").getBytes(), resource.getStr("nonce").getBytes(),
                    resource.getStr("ciphertext"));

            JSONObject oriResource = JSONUtil.parseObj(oriResourceStr);

            log.info("微信回调resource: "+oriResourceStr);
            String status = oriResource.getStr(type+"_state");
            if ("SUCCESS".equals(status)) {
                result.set("code","SUCCESS");
                String no = oriResource.getStr("out_"+type+"_no");
                result.set("out_"+type+"_no",no);
                log.info("回调状态" + no + "成功");
                return result;
            }
        }
        log.info("回调状态失败");
        result.set("code","FAIL");
        return result;
    }

申请退款

public void refund(Long refundId) throws IOException {
        

        init();
        JSONObject reqdata = new JSONObject();
        reqdata.set("out_trade_no", refund.getOrderNo());
        reqdata.set("out_refund_no", refund.getRefundNo());
        reqdata.set("notify_url", wxpayPty.getRefundNotifyUrl());

        JSONObject amount = new JSONObject();
        amount.set("refund", refund.getRefundAmount().multiply(new BigDecimal(100)).intValue());
        amount.set("amount", refund.getAmount().intValue()*100);
        amount.set("currency", "CNY");
        reqdata.set("amount", amount);

        HttpPost httpPost = new HttpPost(wxpayPty.getRefundUrl());

        StringEntity reqEntity = new StringEntity(
                reqdata.toString(), ContentType.create("application/json", "utf-8"));
        httpPost.setEntity(reqEntity);
        httpPost.addHeader("Accept", "application/json");

        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            HttpEntity entity = response.getEntity();
            JSONObject resp = JSONUtil.parseObj(EntityUtils.toString(entity));
            if ("SUCCESS".equals(resp.get("status")) || "PROCESSING".equals(resp.get("status"))) {
                //业务处理
            }

            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    }

退款回调

public int refundNotify(HttpServletRequest request) throws IOException, GeneralSecurityException {
        log.info("微信退款回调开始");
        JSONObject result = getResource(request, "refund");

        if ("SUCCESS".equals(result.getStr("code"))) {
            log.error("退款:" + refundNo + "成功");

            // 业务处理
            
        } else {
            // 业务处理
            
        }
        return 0;
    }

 

到此就差不多了,这里我用到了几个工具类,都是hutool里的,想用的话可以引进来