微信支付API V3版本JAVA开发指南

微信支付版本V3的Demo,在官方上下载下来,压根就是不能直接用的东西,你要想学会用,你就得一层一层的看源码,看文档,要求你事无巨细的做一个接入者。

如果接入API需要让人看源码来理解,我觉得是一件让人很费劲的事情。

就像你一个买了一个手机,你除了开机,关机,你啥都不知道怎么操作,截个屏,你分分钟都要在说明说书上找半天。

就像你买了个苹果手机,想长截屏,你看了好久的系统,搜了好久的百度,发现,最后是要另外下第三方的App才能支持。

然而安卓自带的长截屏,早就有了。

就像我写微信支付,我要写一个V3批量到零钱的接口,我看了好久的文档,下了demo,最后你只给了我些加密方式和一些其他无关痛痒的接口事例。

然而阿里的Demo早就Run一下就通了。

批量到零钱的接口的传参你写在了文档,Demo压根就没有这个批量到零钱的接口,我要看你Demo的其他事例,搞懂你的调用的原理,再来看具体的文档,自己再来写一遍,然后不断的Run看返回的信息,如果失败,再来问客服(回复速度,令人心感疲惫),然后自己也开始面向百度编程,面向CSDN编程。

学习成本太高,增加了入手难度,对于接入的使用者来说,是一件灾难的事情。

在我心目中的接入API就是一件,看着文档,把自己的参数设置进去Demo里面,跑一个main方法,叮咚,完成了。

很多阿里的API就是如此,方便快捷,你在对照着文档来设置参数就行,你压根不需要怎么看源码。

我自己本身的业务逻辑已经很多了,接入你的产品,你要我看源码来理解,看了又不懂,不懂我问了你又回的慢,回的慢就算了,你甚至会不回,我的内心接近崩溃的,我又不是你们公司的,为什么我需要懂你们的代码,懂了对我而言,有什么好处呢?我能去你公司上班,你能给我发工资吗?

以上吐槽只是个人对接过程遇到问题,不是捧谁踩谁。

真心希望科技是以人为本,服务所有的用户,做到傻瓜式接入。

科技的本质就是让人偷懒的,科技的进步也是从偷懒开始的。

微信在V3版本的Demo真的存在很多不完善,希望以后越做越好吧。

废话不多说,下面上下我自己理解的代码,你只需要跑一个main方法就行了。

(具体进件的注册商户的东西,我这里就略过了,自己看着官网流程即可)

下面的例子是,批量转账到零钱(服务商)的接入示例:

(微信文档地址:开发文档-微信支付批量转账到零钱

微信支付V3版本,微信SDK,内部封装了部分方法,参照着官方Demo,下载地址:GitHub - wechatpay-apiv3/wechatpay-apache-httpclient: 微信支付 APIv3 Apache HttpClient装饰器(decorator)

步骤1:引入微信SDK依赖

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

<!--另外需要使用到的依赖-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.6.6</version>
</dependency>

步骤2:构造微信接口通讯Client,构造隐私数据加密公钥,每次调用微信V3的接口都需要用Client来发起,敏感数据需通过平台公钥加密。

微信的的商户私钥和平台公钥有效期是一年的,一年到期需要手动去后台更新重新配置,微信说有延迟过期时间的重新获取平台公钥的方法,这里跳过了。

因为就算延迟了,到时候还是要手动后台在重新设置,怎样都无可避免重新设置,感觉意义不大,就跳过了。

但是第一次获取平台公钥的方法还是很麻烦的,最好的方法是下载他们的jar包去获取,这样会减少很多碰壁。

下载地址在这里:

GitHub - wechatpay-apiv3/CertificateDownloader: Java 微信支付 APIv3 平台证书的命令行下载工具

具体的配置方法,在文档地址里面都有说明,需要详细阅读,按照步骤操作即可,

商户号,商户API密钥、商户API密钥序列号、商户API V3密钥、都需要从微信后台去设置,具体可以登录商户后台设置,设置完之后保存本地,即可获得。(参数很多,需要自己看文档流程理解)

我执行jar包的命令:

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

获取后,保存下来平台公钥、公钥序列号,可多次使用。

平台公钥私钥如:

下面是构造微信Client的代码:

 

package Util;

import cn.hutool.core.io.FileUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.File;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;

/**
 * @Author zkinghao
 * @Date 2022/1/11 15:05
 */
public class WeChatClient {
    
    /**
     * 微信通讯client
     * @return CloseableHttpClient 
     */
    public static CloseableHttpClient getClient() {
        /**商户私钥文件*/
        File privateKeyFile = new File("E:\\wechat\\apiclient_key.pem");
        InputStream privateKeyInputStream = FileUtil.getInputStream(privateKeyFile);

        /**微信平台公钥文件*/
        File platFormKeyFile= new File("E:\\wechat\\wechatpay.pem");
        InputStream platformCertInputStream = FileUtil.getInputStream(platFormKeyFile);

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKeyInputStream);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant("商户号", "商户证书序列号", merchantPrivateKey)
                .withWechatPay(Arrays.asList(PemUtil.loadCertificate(platformCertInputStream)));
        CloseableHttpClient httpClient = builder.build();
        return httpClient;
    }

    
    /**
     * 微信敏感数据加密公钥
     * @return
     */
    public static X509Certificate getSaveCertificates() {
        File file2 = new File("微信平台私钥文件");
        InputStream platformCertInputStream = FileUtil.getInputStream(file2);
        return PemUtil.loadCertificate(platformCertInputStream);
    }

}

步骤3:调用-发起批量转账API

前置:

1、需用户与小程序绑定,获取用户的openId;

2、也需在特约商户后台,服务商后台绑定对应的小程序的appId。

具体步骤可以参考:

​​​​​​商家商户号与AppID账号关联管理

绑定成功后,即可用调用接口,

微信接口说明地址:开发文档-微信支付批量转账到零钱

代码如下:

import Util.WeChatClient;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import javax.crypto.IllegalBlockSizeException;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * @Author zkinghao
 * @Date 2022/1/11 14:19
 */
public class TestBatch {

    /**
     * 发起批量转账API
     *
     * @throws IllegalBlockSizeException
     * @throws IOException
     */
    public static void batchPay() throws IllegalBlockSizeException, IOException {
        CloseableHttpClient httpClient = WeChatClient.getClient();
        X509Certificate x509Certificate = WeChatClient.getSaveCertificates();
        Map<String, Object> map = new HashMap<>();
        map.put("sub_mchid", "特约商户号");
        map.put("authorization_type", "FUND_AUTHORIZATION_TYPE");
        map.put("out_batch_no", "batch018");
        map.put("batch_name", "测试3");
        map.put("batch_remark", "批次备注出款");
        map.put("total_amount", 50);
        map.put("total_num", 1);
        List<Map> list = new ArrayList<>();
        Map<String, Object> subMap = new HashMap<>(4);
        subMap.put("out_detail_no", "detail018");
        subMap.put("transfer_amount", 50);
        subMap.put("transfer_remark", "明细备注1");
        subMap.put("openid", "收款用户openid");
        subMap.put("user_name", RsaCryptoUtil.encryptOAEP("收款用户姓名", x509Certificate));
        list.add(subMap);
        map.put("transfer_detail_list", list);
        map.put("sp_appid", "服务商的appid");
        String body = JSONUtil.toJsonStr(map);
        System.out.println("请求参数:" + body);
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/partner-transfer/batches");
        httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
        httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
        httpPost.addHeader("Wechatpay-Serial", "微信平台证书序列号");

        httpPost.setEntity(new StringEntity(body, "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println("返回参数:" + bodyAsString);
        } finally {
            response.close();
        }
    }

    public static void main(String[] args) throws Exception {
        /**发起批量转账API*/
        batchPay();
    }
}

步骤4:

商家明细单号查询明细单API

微信接口说明地址:开发文档-微信支付批量转账到零钱

代码如下:

import Util.WeChatClient;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URISyntaxException;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * @Author zkinghao
 * @Date 2022/1/11 15:08
 */
public class TestQueryBatch {

    public static final Integer SUCCESS_CODE = 200;


    /**
     * 商家明细单号查询明细单API
     * @throws URISyntaxException
     * @throws IOException
     */
    public static void queryBatch() throws URISyntaxException, IOException {
        CloseableHttpClient httpClient = WeChatClient.getClient();
        //批次号
        String batchCode = "batch018";
        //明细号
        String detailCode = "detail018";
        StringBuilder url = new StringBuilder("https://api.mch.weixin.qq.com/v3/partner-transfer/batches/out-batch-no/");
        url.append(batchCode).append("/details/out-detail-no/").append(detailCode);
        URIBuilder uriBuilder = new URIBuilder(url.toString());
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println("微信支付查询返回:" + bodyAsString);
            JSONObject jsonObject = JSONUtil.parseObj(bodyAsString);
            if (SUCCESS_CODE.equals(response.getStatusLine().getStatusCode())) {
                //成功    或未知,仍需要判断具体状态
                /**
                 * 枚举值:
                 * PROCESSING:转账中。正在处理中,转账结果尚未明确
                 * SUCCESS:转账成功
                 * FAIL:转账失败。需要确认失败原因后,再决定是否重新发起对该笔明细单的转账(并非整个转账批次单)
                 * 示例值:SUCCESS
                 */

                String status = jsonObject.getStr("detail_status");
                String failReason = jsonObject.getStr("fail_reason");

                System.out.println("交易状态:" + status);
                System.out.println("失败原因:" + failReason);
            } else {
                //失败
            }
        } finally {
            response.close();
        }
    }

    public static void main(String[] args) throws Exception {
        /**商家明细单号查询明细单API*/
        queryBatch();
    }
}

上面两个事例事例是对应post和get请求的区别,其他接口也都类似这样实现即可,看着文档实现就行,下面就不一一示范了。

但是有一个最坑的接口的接口,写了希望大家可以避坑,这也是我写这篇博客的最大意愿,希望大家都可以少走弯路。

 那就是:下载电子回单API。

就是这个api:开发文档-微信支付批量转账到零钱

官方文档写的根本就没有代码,只有一个postman的脚本,还是我自己看脚本,看规则,然后自己抓包来看那个Authorization,再自己写一个类似postman脚本的代码调试出来的,问技术基本也是没有回过声的。

真的,没有文档的东西,开发起来,真的是让人疲惫,耗时间,希望后面的人可以不要踩坑了。也希望微信能好好更新文档,弄的更通俗易懂,V3版本的demo真的是少的可怜,开发起来都要从头到尾看一遍。看的是都以为自己是腾讯人员的开发人员一样,要从头到尾的来读你的源码。

我还去他们的问答区写下了自己源码,希望对需要的你们有用。

下面是下载电子回单API的步骤:

步骤1:

调用:转账明细电子回单受理API

开发文档-微信支付批量转账到零钱

代码如下:

import Util.WeChatClient;
import cn.hutool.json.JSONUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * @Author zkinghao
 * @Date 2022/1/11 17:57
 */
public class TestApplyReceipts {

    /**
     * 申请电子回单
     * @throws URISyntaxException
     * @throws IOException
     */
    public static void apply() throws URISyntaxException, IOException {
        CloseableHttpClient httpClient = WeChatClient.getClient();
        Map<String, Object> map = new HashMap<>(1);
        map.put("out_batch_no", "batch016");
        String body = JSONUtil.toJsonStr(map);
        System.out.println("请求参数:" + body);
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/transfer/bill-receipt");
        httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
        httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
        httpPost.setEntity(new StringEntity(body, "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println("接口返回:" + bodyAsString);
        } finally {
            response.close();
        }
    }

    public static void main(String[] args) throws Exception{
        /**申请电子回单*/
        apply();
    }
}

步骤2:

查询转账明细电子回单受理结果API

开发文档-微信支付批量转账到零钱

代码如下:

import Util.WeChatClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URISyntaxException;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * @Author zkinghao
 * @Date 2022/1/11 17:51
 */
public class TestGetDownLoadUrl {

    /**
     * 查询转账明细电子回单受理结果API
     *
     * @throws URISyntaxException
     * @throws IOException
     */
    public static void getDownLoadUrl() throws URISyntaxException, IOException {
        CloseableHttpClient httpClient = WeChatClient.getClient();
        String url = "https://api.mch.weixin.qq.com/v3/transfer/bill-receipt/batch016";
        URIBuilder uriBuilder = new URIBuilder(url);

        HttpGet httpGet = new HttpGet(uriBuilder.build());

        httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println("返回结果:" + bodyAsString);
        } finally {
            response.close();
        }
    }

    public static void main(String[] args) throws Exception {
        /**查询转账明细电子回单受理结果API*/
        getDownLoadUrl();
    }
}

步骤3:

下载电子回单API

开发文档-微信支付批量转账到零钱

代码如下:

import Util.DownLoadBillUtil;
import cn.hutool.core.io.FileUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;

import java.io.File;
import java.io.InputStream;
import java.security.PrivateKey;

/**
 * @Author zkinghao
 * @Date 2022/1/11 19:17
 */
public class TestDownLoadReceipts {
    public static void billDownLoad3() throws Exception {
        File file = new File("商户私钥文件");
        InputStream privateKeyInput = FileUtil.getInputStream(file);
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKeyInput);

        DownLoadBillUtil downLoadBillUtil = new DownLoadBillUtil("商户号",
                "平台证书序列号", merchantPrivateKey);
        //下载地址
        String url = "https://api.mch.weixin.qq.com/v3/transferdownload/signfile?token=A9TMeNMBAAABAA1AAACq6YGHyqm650A_n2vdYSAAAAArJppkbNTL7ba83Qu52eBlrYb12LXyYf_BuqLxJtMcKrjwb-KVl_NqfzeKUa_OP_b1QRkq2IRuOkoX8N-uUT6nCZ0DKF_86JLOuYFFpQ6uxFgZQsItXJwAN-pwO0MUOc1yHYAVkHmqTK2DXEIu10GHprm3YZcuhlS59niwuLYx7Vp5cKZsm-MIuuTyb4VEgq9sGQ";
        DownLoadBillUtil.writeToLocal("E://80.pdf", downLoadBillUtil.downloadBill(url));
        System.out.printf("返回成功");
    }

    public static void main(String[] args) throws Exception{
        billDownLoad3();
    }
}

下载工具类:

package Util;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.UUID;

/**
 * @Author zkinghao
 * @Date 2022/1/11 17:41
 */
public class DownLoadBillUtil {
    /**
     * 商户号
     */
    private String merchantId;
    /**
     * 商户序列号
     */
    private String certificateSerialNo;
    /**
     * 商户私钥
     */
    private PrivateKey privateKey;

    public DownLoadBillUtil(String merchantId, String certificateSerialNo, PrivateKey privateKey) {
        this.merchantId = merchantId;
        this.certificateSerialNo = certificateSerialNo;
        this.privateKey = privateKey;
    }

    public InputStream downloadBill(String downloadUrl) throws Exception {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        ;
        HttpGet httpGet = new HttpGet(downloadUrl);
        String path = httpGet.getURI().getPath();
        String canonicalUrl = httpGet.getURI().getQuery();
        if (canonicalUrl != null) {
            path += "?" + canonicalUrl;
        }
        String billSign = this.createBillSign(nonceStr, timestamp, path);
        StringBuilder sb = new StringBuilder("WECHATPAY2-SHA256-RSA2048 mchid=").append("\"").append(this.merchantId).append("\",");
        sb.append("serial_no=").append("\"").append(this.certificateSerialNo).append("\",");
        sb.append("nonce_str=").append("\"").append(nonceStr).append("\",");
        sb.append("timestamp=").append("\"").append(timestamp).append("\",");
        sb.append("signature=").append("\"").append(billSign).append("\"");
        String auth = sb.toString();
        HttpResponse execute = HttpRequest.get(downloadUrl).auth(auth).execute();
        return execute.bodyStream();
    }

    public String createBillSign(String nonceStr, String timestamp, String download) throws Exception {
        String plain_text = "GET" + "\n"
                + download + "\n"
                + timestamp + "\n"
                + nonceStr + "\n";
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(this.privateKey);
        sign.update(plain_text.getBytes("utf-8"));
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * 将InputStream写入本地文件
     *
     * @param destination 写入本地目录
     * @param input       输入流
     * @throws IOException
     */
    public static void writeToLocal(String destination, InputStream input)
            throws IOException {
        int index;
        byte[] bytes = new byte[1024];
        FileOutputStream downloadFile = new FileOutputStream(destination);
        while ((index = input.read(bytes)) != -1) {
            downloadFile.write(bytes, 0, index);
            downloadFile.flush();
        }
        downloadFile.close();
        input.close();
    }
}

以上就是我个人写的微信支付V3版本的一些Demo,希望能帮助到未来要对接的人,让你们少走一些弯路,少踩一些坑。

让写代码的过程可以简单些。

不喜勿喷。

  • 28
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值