微信商户平台转账到零钱功能接入实战
admin
2024-02-15 14:38:08
0

     1.背景说明
     2.实现过程
         2.1 接入之前的准备工作
         2.2 代码实现

     3.注意事项以及相关说明
         3.1 参数组装说明
         3.2 resource配置文件读取
         3.3 错误的签名,验签失败问题分析以及处理
         3.4.转账到零钱产品功能配置

背景说明

     近期营销活动中需要商户转账到微信用户零钱,实战角度说下接入过程,期间用的时间也比较多,把遇到的问题以及如何处理问题过程记录一下,希望对有同样需求的同学有所帮助,尽量少用一些时间,更专注业务处理.本文仅以发起商家转账( /v3/transfer/batches)功能进行讲解.

2.实现过程

2.1接入之前的准备工作

    开通微信商户账号以及开通商家转账到零钱产品功能并对指定功能进行相关设置.官方接入的详情地址:
https://pay.weixin.qq.com/docs/merchant/products/batch-transfer-to-balance/preparation.html

2.2 代码实现

controller:

	@ApiOperation("提现到零钱")@PostMapping("/transferAccount")public ResultVo transferAccount(String openId) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException, KeyStoreException {payService.transferAccount(openId);return ResultVoUtil.success();}

service:

public interface PayService {// add by txm 2022/10/29 提现到微信零钱void transferAccount(String openIdId) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException, KeyStoreException;}

参数实体类:

@ApiModel("转账请求参数")
@Data
public class TransferDto  {@ApiModelProperty(value = "直连商户的appid",example = "123",dataType = "String")private String appid;@ApiModelProperty(value = "商家批次单号",example = "plfk2020042013",dataType = "String")private String out_batch_no;@ApiModelProperty(value = "批次名称",example = "2019年1月深圳分部报销单",dataType = "String")private String batch_name;@ApiModelProperty(value = "批次备注",example = "2019年1月深圳分部报销单",dataType = "String")private String batch_remark;@ApiModelProperty(value = "转账总金额,单位分",example = "1",dataType = "Integer")private Integer total_amount;@ApiModelProperty(value = "转账总笔数",example = "1",dataType = "Integer")private Integer total_num;@ApiModelProperty(value = "转账明细列表",dataType = "list.class")private List transfer_detail_list=new ArrayList<>();
}
@ApiModel("转账请求详情参数")
@Data
public class TransferDetailDto  {@ApiModelProperty(value = "商家明细单号(相当于子订单)",example = "x23zy545Bd5436",dataType = "String")private String out_detail_no;@ApiModelProperty(value = "转账金额,单位分",example = "2",dataType = "Integer")private Integer transfer_amount;@ApiModelProperty(value = "转账备注",example = "2020年4月报销",dataType = "String")private String transfer_remark;@ApiModelProperty(value = "用户在直连商户应用下的用户标示",example = "2019年1月深圳分部报销单",dataType = "String")private String openid;}

业务实现类:

@Slf4j
@Service
public class PayServiceImpl implements PayService {/*** @Author: txm* @Description: 转账逻辑* @Param: [method, url, body]* @return: java.lang.String* @Date:  2022/11/24 16:08**/@Overridepublic void transferAccount(String openIdId) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException, KeyStoreException {// 组装转账到零钱参数TransferDto transferDto = new TransferDto();transferDto.setAppid("小程序APPID或是公众号id");String out_batch_no = RandomUtil.randomNumbers(10);out_batch_no=StrUtil.concat(true,"sc",out_batch_no);transferDto.setOut_batch_no(out_batch_no);transferDto.setBatch_name("test1");transferDto.setBatch_remark("test2");transferDto.setTotal_amount(1);transferDto.setTotal_num(1);TransferDetailDto transferDetailDto = new TransferDetailDto();String out_detail_no = RandomUtil.randomNumbers(10);out_detail_no=StrUtil.concat(true,"detail",out_detail_no);transferDetailDto.setOut_detail_no(out_detail_no);transferDetailDto.setTransfer_amount(1);transferDetailDto.setTransfer_remark("test3");transferDetailDto.setOpenid(openIdId);transferDto.getTransfer_detail_list().add(transferDetailDto);String transferDtoStr = JSONUtil.toJsonStr(transferDto);// 组装Authorization信息HttpUrl httpUrl = HttpUrl.get("https://api.mch.weixin.qq.com/v3/transfer/batches");String tokenInfo=getToken("POST",httpUrl,transferDtoStr);log.info("Authorization认证信息:{}",tokenInfo);// Authorization认证类型String authType="WECHATPAY2-SHA256-RSA2048";// Authorization信息 认证类型 认证信息,此处使用hutool工具类中concat进行拼接,注意两部分中间用空格分割String authorization= StrUtil.concat(true, authType," ",tokenInfo);// 发送请求String returnMsg = HttpRequest.post("https://api.mch.weixin.qq.com/v3/transfer/batches").header("Authorization", authorization).header("Wechatpay-Serial","证书序列号").body(transferDtoStr).execute().body();JSONObject returnTransferInfo = JSON.parseObject(returnMsg);log.info("转账申请返回信息:{}",returnTransferInfo);}/*** @Author: txm* @Description: 获取Authorization认证签名信息* @Param: [method, url, body]* @return: java.lang.String* @Date:  2022/11/24 16:08**/public  String getToken(String method, HttpUrl url, String body) throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException, KeyStoreException {// 随机字符串String nonceStr = RandomUtil.randomString(26);// 时间戳,单位秒long timestamp = System.currentTimeMillis() / 1000;// 组装签名串信息String message = buildMessage(method, url, timestamp, nonceStr, body);log.info("签名串:{}",message);// 签名串加密处理String signature = sign(message.getBytes("utf-8"));log.info("签名信息:{}",signature);return "mchid=\"" + "商户id" + "\","+ "serial_no=\"" + "证书序列号" + "\","+ "nonce_str=\"" + nonceStr + "\","+ "timestamp=\"" + timestamp + "\","+ "signature=\"" + signature + "\"";}/*** @Author: txm* @Description: 组装签名请求信息* @Param: [method, url, timestamp, nonceStr, body]* @return: java.lang.String* @Date:  2022/11/24 16:09**/public String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {String canonicalUrl = url.encodedPath();if (url.encodedQuery() != null) {canonicalUrl += "?" + url.encodedQuery();}return method + "\n"+ canonicalUrl + "\n"+ timestamp + "\n"+ nonceStr + "\n"+ body + "\n";}/*** @Author: txm* @Description: 签名加密* @Param: [byte[]]* @return: java.lang.String* @Date:  2022/11/24 16:09**/public String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, KeyStoreException, IOException {Signature sign = Signature.getInstance("SHA256withRSA");// apiclient_key.pem存在到resource/config/cert下Resource resource = resourceLoader.getResource("classpath:/config/cert/apiclient_key.pem");File file = resource.getFile();String path = file.getPath();// 获取私钥key,实际读取apiclient_key.pem文件信息创建PrivateKey 对象PrivateKey privateKey = getPrivateKey(path);sign.initSign(privateKey);sign.update(message);return Base64.encodeBase64String(sign.sign());}/*** @Author: txm* @Description: 获取PrivateKey * @Param: [byte[]]* @return: java.lang.String* @Date:  2022/11/24 16:09**/public static PrivateKey getPrivateKey(String filename) throws IOException {String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");try {String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");KeyFactory kf = KeyFactory.getInstance("RSA");return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));} catch (NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持RSA", e);} catch (InvalidKeySpecException e) {throw new RuntimeException("无效的密钥格式");}}}

3.注意事项以及相关说明

3.1 参数组装说明

         3.1参数组装说明
    转账到微信零钱官方文档:https://pay.weixin.qq.com/docs/merchant/apis/batch-transfer-to-balance/transfer-batch/initiate-batch-transfer.html
    请求由两部分组成:详细请求信息和请求头信息,前者不用多说,按照官方文档进行参数对应即可.后者请求头信息包含两个请求头:Wechatpay-SerialAuthorization,前者为证书序列号,可以直接从商户平台证书管理中查看,Authorization为签名信息.

签名解释的稍微通俗一点,就是把要传递给接口的参数,进行加密,加密的这个动作就是签名,作用就是保障这条请求是你的请求是安全的.

    Authorization又分为两部分:认证类型(固定为WECHATPAY2-SHA256-RSA2048)和认证信息.认证信息由商户号、随机字符串、时间戳(单位秒)、证书序列号、签名信息组成,其中签名信息由请求方式、随机字符串、时间戳(单位秒)、请求路径(/v3/transfer/batches)、请求体加密组成.具体以官方文档为准.参数确实很多,不过代码里面都已经组装好了,只需要更换对应的配置信息即可.接入过程中会遇到很多问题,自己已经调通,很多坑已经踩过,按照上面的签名方式进行可以排除掉大部分可能出现错误原因.

3.2.resource配置文件读取

    首先说下和证书相关的三个文件:
apiclient_cert.p12 商户证书,
apiclient_cert.pem 商户证书相关加密文件,
apiclient_key.pem 商户秘钥文件.后两者都是基于证书进行导出的.基本上都是商户证书用的多比如说支付或是退款,后两者用的不多。其中获取privateKey对象就是基于读取apiclient_key.pem实现.在springboot项目中配置文件一般放置在resource目录下,关于读取方式这里踩过坑,可以使用resourceLoader进行读取.具体实现参考上面代码.

3.3 错误的签名,验签失败问题分析以及处理

    接口本地测试的时候这个问题耗费的时间最多,也是大部分同学都会遇到的问题,看过一篇总结贴感觉不错,可以按照里面说的进行自查:验签失败原因分析.
仔细对比之后验签失败的原因锁定在商户号、证书序列号、apiclient_key.pem三者是否匹配这个问题上。关于校验三者是否匹配,提供的检验方法是使用postman导入官方提供的测试脚本,具体操作可以参考:
https://github.com/wechatpay-apiv3/wechatpay-postman-script
按照步骤执行之后发现测试结果是认证失败,所以考虑如何将三者进行正确匹配。
    简单交代下我的情况:apiclient_cert.p12apiclient_key.pem都是之前的人交接过来的,由于线上支付和退款都正常在用,所以apiclient_cert.p12 证书序列号 商户号应该是正常的,那唯一有问题的可能就是apiclient_key.pem.apiclient_key.pem可以通过apiclient_cert.p12重新导出,毕竟如果更换apiclient_cert.p12 需要将线上的证书进行更换,成本较大.
    使用apiclient_cert.p12生成apiclient_key.pem的方法是使用openssl.
window 64位openssl下载地址后期补充.
安装直接默认安装即可,不再展开.
    安装好之后打开黑窗口,进入到apiclient_cert.p12所在目录(可以将原来的apiclient_key.pem重命名做备份),运行以下命令即可生成apiclient_key.pem:

openssl pkcs12 -nodes -clcerts -in apiclient_cert.p12 -out apiclient_key.pem

    如果提示输入密码,可以直接输入商户号.生成的文件中保留从-----BEGIN PRIVATE KEY-----到-----END PRIVATE KEY----内容即可,否则解析文件时会失败.生成新的apiclient_key.pem重新请求接口签名问题解决.
    这里说下之前感到困惑的地方,官方提供过签名以及验签的工具,注意工具只校验签名的方式是否正确,不校验参数的正确性.问题记录贴:
https://developers.weixin.qq.com/community/pay/doc/00004e6fa08528c447eea27cf56800

3.4.转账到零钱产品功能配置

    商户平台开通转账到零钱产品只是第一步,需要到产品设置中进行开启api权限相关配置,设置比较简单这里不在展开.
    以上是对接商户转账到零钱过程中的总结和相关注意事项说明,看到这里希望对你有所帮助,欢迎评论区留言交流遇到的问题.

相关内容

热门资讯

女生说今天好热啊该怎么回复 女生说今天好热啊该怎么回复高情商回复如下:1、“你很热吗,那我给你讲个冷笑话中和一下吧,然后你就找个...
一家人过河的问题 一家人爸爸 ... 一家人过河的问题 一家人爸爸 妈妈 2儿子 2女儿 一个管家 一条狗爱因斯坦的智力题目得买7张票,宠...
作为强国一代的青年大学生,在宏... 作为强国一代的青年大学生,在宏伟壮阔的科技强国梦中应该有着怎样的使命和担当?作为强国一代的青年大学生...
催眠大师的电影里所用到的心理学... 催眠大师的电影里所用到的心理学常识和原理是什么?《催眠大师》的引导方式是瞬间催眠,在现实人群中只有少...
我是个什么样的人,谁能帮我分析... 我是个什么样的人,谁能帮我分析一下,谢谢了?自己是什么样的人,没有和你接触,没有和你交往过,肯定不会...
求桔子树的早期作品集 求桔子树的早期作品集《片段》《妖孽并出》《暗涌》《Ne me quitte pas》《左右之间》《我...
一个人一个世界 那两个人几个世... 一个人一个世界 那两个人几个世界?一个人一个世界,两个人也是一个世界,因为(另一个)是他喜欢的人,他...
巨魔盗贼PVP 怎么样? 巨魔盗贼PVP 怎么样?同上可以说没有优势~PVE还行~是要看种族天赋的~
云南盘鮈鱼能吃吗 云南盘鮈鱼能吃吗能吃啊,而且很好吃的。
急求一篇写初中生的校园故事作文... 急求一篇写初中生的校园故事作文(记叙文)在学校发生的,真实点急求一篇写初中生的校园故事作文(记叙文)...
安徽基础教育平台学生完成后教师... 安徽基础教育平台学生完成后教师怎么遴选视频?安徽基础教育平台学生完成后,教师怎么遴选视频可以根据一些...
有人知道这是个什么鸟吗? 有人知道这是个什么鸟吗?灰喜鹊…………+幼雏喜鹊,还很小,看起来都很脆弱幼雏很难变认,有点像灰喜雀幼...
清扬控油洗发水是不是有激素洗了... 清扬控油洗发水是不是有激素洗了头发就不油,然后换其他洗发水就很油。现在根本没法用其他洗发水了。有一款...
阴阳师人生赢家成就是什么 阴阳师人生赢家成就是什么人生赢家成就就是那个日御悄月同辉啊,同时达成全图鉴和非态拆没帆纳洲大阴阳师成...
主角武器是飞扬枪跋扈盾的网游小... 主角武器是飞扬枪跋扈盾的网游小说主角武器是飞扬枪跋扈盾的网游小说《正前方》更新超级慢
真的有白蛇白素贞这个人吗? 真的有白蛇白素贞这个人吗?我刚刚看了百家讲坛,白素贞这个人是没有的,她是一个小说的角色。并且她的角色...
迪丽热巴拍过的电影或电视剧你认... 迪丽热巴拍过的电影或电视剧你认为如何?我认为还是很不错的,迪丽热巴的演技是很好的,她长得也是比较漂亮...
求南派三叔所有与盗墓笔记有关的... 求南派三叔所有与盗墓笔记有关的书、文章(各种番外、特别篇、贺岁篇)(只要名字就好),谢谢啦~《吴邪的...
范增和张良什么关系 范增和张良什么关系范增是项羽谋士,张良为刘邦谋士,各为其主,战场上是敌对关系
像《觅渡》之类的书有哪些 像《觅渡》之类的书有哪些写一些推荐的书知识性比较强的 比较容易懂的钱穆 湖上闲思录