微信支付:商家转账到零钱的开发

news/2025/2/12 20:00:10/

主要所需:1、微信商户平台的证书apiclient_cert.pem 2、微信商户平台证书的密钥apiclient_key.pem 3、微信商户平台的证书的序列号

一、转账所需字段

public class WxTransferAccounts {private String appid;// 小程序ID	private String out_batch_no;// 商家批次订单号   由数字、大小写字母组成[1,32]private String batch_name;// 商家批次名称   示例值:2019年1月深圳分部报销单[1,32]	private String batch_remark;//批次备注  , [1,32]private int total_num;// 转账总笔数   这个总笔数要等于明细笔数的汇总private Integer total_amount;// 转账总金额   ,单位为分 这个总金额要等于明细金额的汇总private List<TransferAccountsArray> transfer_detail_list;//收款方明细public String getAppid() {return appid;}public void setAppid(String appid) {this.appid = appid;}public String getOut_batch_no() {return out_batch_no;}public void setOut_batch_no(String out_batch_no) {this.out_batch_no = out_batch_no;}public String getBatch_name() {return batch_name;}public void setBatch_name(String batch_name) {this.batch_name = batch_name;}public String getBatch_remark() {return batch_remark;}public void setBatch_remark(String batch_remark) {this.batch_remark = batch_remark;}public int getTotal_num() {return total_num;}public void setTotal_num(int total_num) {this.total_num = total_num;}public Integer getTotal_amount() {return total_amount;}public void setTotal_amount(Integer total_amount) {this.total_amount = total_amount;}public List<TransferAccountsArray> getTransfer_detail_list() {return transfer_detail_list;}public void setTransfer_detail_list(List<TransferAccountsArray> transfer_detail_list)         {this.transfer_detail_list = transfer_detail_list;}
}public class TransferAccountsArray {private String out_detail_no;// 商家明细单号[1,32]private String openid;//用户openidprivate String user_name;// 用户真实姓名,要与微信号绑定的身份实名,超过2000元时必填  需进行加密处理;如低于2000元的转账,则可以不需要此字段private String transfer_remark;//转账备注   [1,32]private int transfer_amount;// 转账金额public String getOut_detail_no() {return out_detail_no;}public void setOut_detail_no(String out_detail_no) {this.out_detail_no = out_detail_no;}public String getOpenid() {return openid;}public void setOpenid(String openid) {this.openid = openid;}public String getUser_name() {return user_name;}public void setUser_name(String user_name) {this.user_name = user_name;}public String getTransfer_remark() {return transfer_remark;}public void setTransfer_remark(String transfer_remark) {this.transfer_remark = transfer_remark;}public int getTransfer_amount() {return transfer_amount;}public void setTransfer_amount(int transfer_amount) {this.transfer_amount = transfer_amount;}
}

二、转账接口调用前准备

            WxTransferAccounts paramWxTransferAccounts=new WxTransferAccounts();paramWxTransferAccounts.setAppid("个人的小程序appid,要与商户绑定");paramWxTransferAccounts.setBatch_name("2022.09.29测试新版转账");paramWxTransferAccounts.setBatch_remark("2022.09.29测试新版转账");paramWxTransferAccounts.setOut_batch_no("商户订单号 32位  自己生成");paramWxTransferAccounts.setTotal_amount(100);paramWxTransferAccounts.setTotal_num(1);TransferAccountsArray paramTransferAccountsArray=new TransferAccountsArray();paramTransferAccountsArray.setOpenid("收款人的opendid");paramTransferAccountsArray.setOut_detail_no("明细订单号 32位 自己生成");paramTransferAccountsArray.setTransfer_amount(100);paramTransferAccountsArray.setTransfer_remark("2022.09.29测试新版转账");paramTransferAccountsArray.setUser_name(rsaEncryptOAEP("真实姓名", certificate));//如果转账低于2000,无需这个字段,否则需要进行隐私信息进行加密处理,加密代码在后面List<TransferAccountsArray> listAccounts=new ArrayList<>();listAccounts.add(paramTransferAccountsArray);paramWxTransferAccounts.setTransfer_detail_list(listAccounts);

三、隐私信息安全加密

//要先获取微信支付平台的公钥证书,通过api获取;而后可以考虑放redis
String mch_id="你自己的商户号";
String privatekeypath="商户平台证书密钥的路径";
String nonce_str=StrUtil.getRandomStringByLength(32);//随机32位字符串
String body="";
long timestamp = System.currentTimeMillis() / 1000;
String orgSignText = "GET\n"+ "/v3/certificates\n"+ timestamp + "\n"+ nonce_str + "\n"+ body + "\n";
String signStr=VechatPayV3Util.sign(orgSignText.getBytes("utf-8"), privatekeypath);//获得签名String wechatPayserialNo="微信商户平台证书的序列号";String auth = "WECHATPAY2-SHA256-RSA2048 "+ "mchid=\""+mch_id+"\",nonce_str=\""+ nonce_str + "\",timestamp=\"" + timestamp+ "\",serial_no=\"" + wechatPayserialNo + "\",signature=\"" + signStr + "\"";//获取微信支付平台公钥证书String platform_publickey = HttpUtil.sendGetRequest("https://api.mch.weixin.qq.com/v3/certificates", auth,null);
//获取的微信支付平台公钥证书是一个json字符串,自行转成json对象//获得的公钥证书是加密的,需要用apiV3的密钥进行解密
String publickey=decryptResponseBody(tempWxpublicKeyData);//tempWxpublicKeyData 这个对象就是取回来的公钥字符串转换的
ByteArrayInputStream inputStream = new ByteArrayInputStream(publickey.getBytes(StandardCharsets.UTF_8));X509Certificate certificate2=getCertificate(inputStream);
rsaEncryptOAEP("用户的真实姓名", certificate2)//加密隐私信息  这里我用来加密转账所需的姓名/*** 解密响应体.   得到微信平台证书公钥,解密后的字符串即为公钥字符串** @param apiV3Key       API V3 KEY  API v3密钥 商户平台设置的32位字符串* @param associatedData  response.body.data[i].encrypt_certificate.associated_data* @param nonce          response.body.data[i].encrypt_certificate.nonce* @param ciphertext     response.body.data[i].encrypt_certificate.ciphertext* @return the string* @throws GeneralSecurityException the general security exception*/public static String decryptResponseBody(WxpublicKeyData tempWxpublicKeyData) {//tempWxpublicKeyData 这个对象就是取回来的公钥字符串转换的,有时回取回多条公钥,取时间最新的String apiV3Key="微信商户平台apiV3的密钥,记得去微信商户平台设置";String associatedData=tempWxpublicKeyData.getEncrypt_certificate().getAssociated_data();String nonce=tempWxpublicKeyData.getEncrypt_certificate().getNonce();String ciphertext=tempWxpublicKeyData.getEncrypt_certificate().getCiphertext();try {Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.DECRYPT_MODE, key, spec);cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));byte[] bytes;try {bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));} catch (GeneralSecurityException e) {throw new IllegalArgumentException(e);}    return new String(bytes, StandardCharsets.UTF_8);} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {throw new IllegalStateException(e);} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {throw new IllegalArgumentException(e);}}/*** 获取证书** @param inputStream 证书文件* @return {@link X509Certificate} 获取证书*/public static X509Certificate getCertificate(InputStream inputStream) {try {CertificateFactory cf = CertificateFactory.getInstance("X509");X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);cert.checkValidity();return cert;} catch (CertificateExpiredException e) {throw new RuntimeException("证书已过期", e);} catch (CertificateNotYetValidException e) {throw new RuntimeException("证书尚未生效", e);} catch (CertificateException e) {throw new RuntimeException("无效的证书", e);}}/*** 公钥加密   加密隐私信息数据** @param data        待加密数据* @param certificate 平台公钥证书* @return 加密后的数据* @throws Exception 异常信息*/public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {try {Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);byte[] cipherData = cipher.doFinal(dataByte);// String s = new String(cipherData);return java.util.Base64.getEncoder().encodeToString(cipherData);} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);} catch (InvalidKeyException e) {throw new IllegalArgumentException("无效的证书", e);} catch (IllegalBlockSizeException | BadPaddingException e) {throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");}}

四、微信商家转账到零钱接口调用

 //发起转账操作 //certificate2.getSerialNumber().toString(16).toUpperCase() :微信支付平台证书的序列号
//wechatPayserialNo :微信商户平台证书的序列号
//mch_id:商户号
//privatekeypath:微信商户平台密钥地址
//注:我这边传递了两个证书序列号,实际post时只需要一个,做测试发现当有传递加密隐私信息时,序列号用微信支付平台证书的序列号;没有传递加密隐私信息时,则用微信商户平台证书的序列号即可String transferurl="https://api.mch.weixin.qq.com/v3/transfer/batches";String resStr = HttpUtil.postTransBatRequest(transferurl,JSONObject.toJSONString(paramWxTransferAccounts),certificate2.getSerialNumber().toString(16).toUpperCase(),wechatPayserialNo,mch_id,privatekeypath);

HttpUtil的公用类

import java.io.IOException;
import java.util.HashMap;import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;import ch.qos.logback.classic.Logger;/*** 微信支付专用类 请求操作方法 2022.09.29  新版商家转账到零钱** @author Administrator*/
public class HttpUtil {/*** 发起批量转账API 批量转账到零钱** @param requestUrl* @param requestJson 组合参数* @param wechatPayserialNo 商户证书序列号* @param mchID4M  商户号 * @param privatekeypath  商户私钥证书路径* @return*/public static String postTransBatRequest(String requestUrl,String requestJson,String platform_wechatPayserialNo,String wechatPayserialNo,String mchID4M,String privatekeypath) {CloseableHttpClient httpclient = HttpClients.createDefault();CloseableHttpResponse response = null;HttpEntity entity = null;try {//商户私钥证书HttpPost httpPost = new HttpPost(requestUrl);// NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误httpPost.addHeader("Content-Type", "application/json");httpPost.addHeader("Accept", "application/json");//httpPost.addHeader("Wechatpay-Serial", wechatPayserialNo);httpPost.addHeader("Wechatpay-Serial", platform_wechatPayserialNo);//用了隐私信息加密时,上传的微信支付平台公钥的序列号//-------------------------核心认证 start-----------------------------------------------------------------String strToken = VechatPayV3Util.getToken("POST","/v3/transfer/batches",requestJson,mchID4M,wechatPayserialNo, privatekeypath);System.out.println("微信转账token "+strToken);// 添加认证信息httpPost.addHeader("Authorization","WECHATPAY2-SHA256-RSA2048" + " "+ strToken);//---------------------------核心认证 end---------------------------------------------------------------httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));//发起转账请求response = httpclient.execute(httpPost);entity = response.getEntity();//获取返回的数据return EntityUtils.toString(entity);} catch (Exception e) {System.out.println(e.getMessage());e.printStackTrace();} finally {// 关闭流}return null;}/*** 发送HTTP_GET请求* * @see 该方法会自动关闭连接,释放资源* @param reqURL*            请求地址(含参数)* @param decodeCharset*            解码字符集,解析响应数据时用之,其为null时默认采用UTF-8解码* @return 远程主机响应正文*/public static String sendGetRequest(String reqURL,String auth,String decodeCharset) {long responseLength = 0; // 响应长度String responseContent = null; // 响应内容HttpClient httpClient = new DefaultHttpClient(); // 创建默认的httpClient实例HttpGet httpGet = new HttpGet(reqURL); // 创建org.apache.http.client.methods.HttpGethttpGet.addHeader("Authorization", auth);httpGet.addHeader("Accept", "application/json");httpGet.addHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");try {HttpResponse response = httpClient.execute(httpGet); // 执行GET请求HttpEntity entity = response.getEntity(); // 获取响应实体if (null != entity) {responseLength = entity.getContentLength();responseContent = EntityUtils.toString(entity, decodeCharset == null ? "UTF-8" : decodeCharset);EntityUtils.consume(entity); // Consume response content}} catch (ClientProtocolException e) {System.out.println("该异常通常是协议错误导致,比如构造HttpGet对象时传入的协议不对(将'http'写成'htp')或者服务器端返回的内容不符合HTTP协议要求等,堆栈信息如下");} catch (ParseException e) {System.out.println(e.getMessage());} catch (IOException e) {System.out.println("该异常通常是网络原因引起的,如HTTP服务器未启动等,堆栈信息如下");} finally {httpClient.getConnectionManager().shutdown(); // 关闭连接,释放资源}return responseContent;}}

VechatPayV3Util公用类

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import java.util.Random;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;import org.apache.commons.codec.binary.Base64;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;import cn.eeyycc.mina.framework.wxpay.model.WxpublicKeyData;public class VechatPayV3Util {/*** * @param method 请求方法 post* @param canonicalUrl 请求地址* @param body 请求参数* @param merchantId 这里用的商户号* @param certSerialNo 商户证书序列号* @param keyPath 商户证书地址* @return* @throws Exception*/public static String getToken(String method,String canonicalUrl,String body,String merchantId,String certSerialNo,String keyPath) throws Exception {String signStr = "";//获取32位随机字符串String nonceStr = getRandomString(32);//当前系统运行时间long timestamp = System.currentTimeMillis() / 1000;if (StringUtils.isEmpty(body)) {body = "";}//签名操作String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);//签名操作String signature = sign(message.getBytes("utf-8"), keyPath);//组装参数signStr = "mchid=\"" + merchantId + "\",timestamp=\"" +  timestamp+ "\",nonce_str=\"" + nonceStr+ "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\"";return signStr;}public static String buildMessage(String method, String canonicalUrl, 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";}public static String sign(byte[] message, String keyPath) throws Exception {Signature sign = Signature.getInstance("SHA256withRSA");sign.initSign(getPrivateKey(keyPath));sign.update(message);return Base64.encodeBase64String(sign.sign());}/*** 微信支付-前端唤起支付参数-获取商户私钥** @param filename 私钥文件路径  (required)* @return 私钥对象*/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+", "");//System.out.println("--------privateKey---------:"+privateKey);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("无效的密钥格式");}}/*** 获取随机位数的字符串* @param length* @return*/public static String getRandomString(int length) {String base = "abcdefghijklmnopqrstuvwxyz0123456789";Random random = new Random();StringBuffer sb = new StringBuffer();for (int i = 0; i < length; i++) {int number = random.nextInt(base.length());sb.append(base.charAt(number));}return sb.toString();}}

转账成功示例:

 

获取微信支付平台证书时,解密可能会报错密钥

 java.security.InvalidKeyException: Illegal key size

解决方案:(异常: java.security.InvalidKeyException: Illegal key size - 萌新啊萌新是我 - 博客园)

至于报错IP未设置,api支付未开启,余额不足啥的,都在微信商户平台进行设置即可

开发参考:微信支付 发起商家转账API 2022年v3 transfer batches_早起的年轻人的博客-CSDN博客_微信转账api

 java开发 微信商家转账到零钱,发起商家转账API,微信支付_海贝里的灰尘的博客-CSDN博客_java实现微信转账

Java中的微信支付(2):API V3 微信平台证书的获取与刷新 - 走看看

微信支付V3获取平台证书并解密平台证书详细流程_低调使人进步的博客-CSDN博客_微信支付平台证书


http://www.ppmy.cn/news/654987.html

相关文章

微信支付转账到银行卡 加密问题解决方法

[TOC]微信支付转账到银行卡 加密问题解决方法 java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider解决方法 java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider解决方法 1.查看/etc/profile下JAVA_HOME…

微信24小时到账_最新微信转账延迟24小时到账骗局

原标题&#xff1a;最新微信转账延迟24小时到账骗局 微信有个转账延时到账的设置&#xff0c;可以设置延迟2小时到账或者延迟24小时到账&#xff0c;本来这个设置是为了防止用户转错账设置的&#xff0c;原本出于好意的设计&#xff0c;但却被骗子钻了漏洞。这不最近骗子又有利…

微信支付商户号商家转账到零钱现金红包如何开通

微信作为现在用户群体最大的社交工具&#xff0c;自然也就成了众多商家做活动的首选渠道。企业付款到零钱作为商家营销必不可少的工具&#xff0c;开通起来却还是比较费劲的。 开通条件&#xff1a;1&#xff0c;商户号需入驻90天&#xff1b;2&#xff0c;需要有连续30天的交…

微信支付v3——批量转账到零钱(提现)

微信支付v3——批量转账到零钱(提现) 进入正题之前先吐槽一下微信官方文档吧&#xff01;一开始在官网上下载了微信支付版本V3的Demo&#xff0c;下载下来基本上难以投入使用&#xff0c;要学会使用的话&#xff0c;就要去看源码&#xff0c;看文档&#xff0c;稍微错过一点就…

微信支付 发起商家转账API 2022年v3 transfer batches

1 概述 微信转账新接口 发起商家转账API 微信开发文档 接口说明 适用对象&#xff1a;直连商户 请求URL&#xff1a;https://api.mch.weixin.qq.com/v3/transfer/batches 请求方式&#xff1a;POST 接口限频&#xff1a; 单个商户 50QPS&#xff0c;如果超过频率限制&#xff…

微信“商家转账到零钱“功能接入以及如何获得转账结果?

先说答案:无法即时获得转账结果 首先按照商家转账到零钱文档接入,发现响应结果中没有转账成功或者失败的结果 使用通过微信批次单号查询批次单和通过微信明细单号查询明细单接口进行转账结果查询,发现无法即时获得结果 查询相关资料【商家转账到零钱】常见问题 很明确的告知…

解说--1--微信商户转账给个人银行卡或微信零钱功能

一、功能现状 目前该功能属于灰度期。 二、申请条件 1、商户号&#xff08;或同主体其他商户号&#xff09;已入驻90日 2、商户号&#xff08;或同主体其他商户号&#xff09;有近30天连续正常交易 3、 登录微信支付商户平台-产品中心&#xff0c;开通企业付款。 如果当前结算…

服务器上的微信转账记录能删除吗,微信如何彻底删除转账记录?微信删除转账记录方法...

原标题&#xff1a;微信如何彻底删除转账记录&#xff1f;微信删除转账记录方法 微信在我们的生活和工作中扮演者各种各样的角色&#xff0c;帮我们发送文件、言语表达、收支款项等等。其中最为重要的还是收支款项这一功能。当我们更换微信号、或者想藏点私房钱&#xff0c;都需…