微信V3接口商家转账到零钱
来自微信官方文档v3接口说明
需要引入一下mavn包 ;
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.2</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
1、下载微信平台证书
这里用到的是接口方式调用 ,一劳永逸。平台证书和微信商户证书不是同一个,商户证书在微信支付api中下载,平台证书通过接口下载,定期更新。
1、下载微信平台证书
/**
* 获取微信平台证书并解密
* 公钥
* 敏感数据需通过平台公钥加密。。
* @param
* @return
*/
public static String plateCert() {
String pulicKey = "";
String vmsg = "";
try {
//时间戳
long timestamp = System.currentTimeMillis() / 1000;
//随机字符串(用UUID去掉-就行)
String nonce = IdUtil.fastSimpleUUID().toUpperCase();
String body = "";
//拼接要签名的字符串
ClassPathResource mchPrivateKeyFile = new ClassPathResource("static/cert/apiclient_key.pem");
InputStream mchPrivateKeyInputStream = null;
try {
mchPrivateKeyInputStream = mchPrivateKeyFile.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
PrivateKey mchPrivateKey = PemUtil.loadPrivateKey(mchPrivateKeyInputStream);//用流的形式加载私钥key
//拼接要签名的字符串
String orgSignText = "GET\n"
+ "/v3/certificates\n"
+ timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
// 生成签名
String sign = RsaKit.encryptByPrivateKey(orgSignText, mchPrivateKey);
//要放在HttpHeader中的auth信息
// 获取商户证书序列号 这里填写公钥路径 也就是apiclient_cert.pem这个文件的路径
//证书序列号
ClassPathResource mchPublicKeyFile = new ClassPathResource("static/cert/apiclient_cert.pem");
InputStream mchPublicKeyInputStream = null;
try {
mchPublicKeyInputStream = mchPublicKeyFile.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
X509Certificate certificate = PemUtil.loadCertificate(mchPublicKeyInputStream);
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
String auth = "WECHATPAY2-SHA256-RSA2048 "
+ "mchid=\"" + MyConfig.MCH_ID + "\",nonce_str=\""
+ nonce + "\",timestamp=\"" + timestamp
+ "\",serial_no=\"" + serialNo + "\",signature=\"" + sign + "\"";
String url = "https://api.mch.weixin.qq.com/v3/certificates";
HashMap<String, Object> tmap = new HashMap<String, Object>();
tmap.put("Authorization", auth);//tmap.put("token","tonken值");
tmap.put("Accept", "application/json");
tmap.put("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
vmsg = httpGet(url, tmap);//获取请求的返回结果
System.out.println("获取平台证书" + vmsg);
//解密
String ciphertext = "";
String associated_data = "";
String nonce1 = "";
String serial_no = "";//微信平台证书序列号
JSONObject jsonObj = JSON.parseObject(vmsg);
JSONArray data = jsonObj.getJSONArray("data");
for (int i = 0; i < data.size(); i++) {
JSONObject temp = (JSONObject) data.get(i);
JSONObject encrypt_certificate = temp.getJSONObject("encrypt_certificate");
ciphertext = encrypt_certificate.getString("ciphertext");
associated_data = encrypt_certificate.getString("associated_data");
nonce1 = encrypt_certificate.getString("nonce");
serial_no = temp.getString("serial_no");
}
//解密方法
AesUtil aesUtil = new AesUtil(MyConfig.MCH_V3KEY.getBytes("UTF-8"));
pulicKey = aesUtil.decryptToString(associated_data.getBytes("utf-8"), nonce1.getBytes("utf-8"), ciphertext);
//得到微信支付平台公钥(此公钥和微信商户支付公钥不一样)
System.out.println("解密微信平台证书后数据" + pulicKey);
// 保存证书
//这里可用保存证书到本地 也可以直接复制pulicKey到证书文件中然后使用
// 获取平台证书序列号
X509Certificate platcertificate = PemUtil.loadCertificate(new ByteArrayInputStream(pulicKey.getBytes()));
String platcertxlh= platcertificate.getSerialNumber().toString(16).toUpperCase();
System.out.println("平台序号"+platcertxlh);
} catch (Exception e) {
e.printStackTrace();
}
return vmsg;
}
/**
* HttpGet请求
* @param vurl:请求地址,map:{头部信息}
* @return 返回消息
*/
public static String httpGet(String vurl,HashMap<String, Object> map) {
try {
URL url = new URL(vurl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
for (Map.Entry item : map.entrySet()) {
connection.setRequestProperty(item.getKey().toString(),item.getValue().toString());//设置header
}
InputStream in = connection.getInputStream();
InputStreamReader isr = new InputStreamReader(in, "utf-8");
BufferedReader br = new BufferedReader(isr);
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
isr.close();
in.close();
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
上面用到的工具包
package io.cxxz.common.wechatpay.utils;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
public class RsaKit {
/**
* 加密算法RSA
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 私钥签名
*
* @param data 需要加密的数据
* @param privateKey 私钥
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
signature.initSign(privateKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return StrUtil.str(Base64.encode(signed));
}
/**
* 从字符串中加载私钥<br>
* 加载时使用的是PKCS8EncodedKeySpec(PKCS#8编码的Key指令)。
*
* @param privateKeyStr 私钥
* @return {@link PrivateKey}
* @throws Exception 异常信息
*/
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");
}
}
/**
* 公钥验证签名
*
* @param data 需要加密的数据
* @param sign 签名
* @param publicKey 公钥
* @return 验证结果
* @throws Exception 异常信息
*/
public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
}
2、微信客户端读取证书
// An highlighted block
package io.cxxz.common.wechatpay.V3;
import cn.hutool.core.io.FileUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.cxxz.common.wxpay.sdk.MyConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
public class WeChatClient {
/**
* 微信通讯client
* @return CloseableHttpClient
*/
public static CloseableHttpClient getClient() {
/**商户私钥文件*/
ClassPathResource mchPrivateKeyFile = new ClassPathResource("static/cert/apiclient_key.pem");
InputStream mchPrivateKeyInputStream = null;
try {
mchPrivateKeyInputStream = mchPrivateKeyFile.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
/**微信平台公钥文件,是获取平台证书接口得到的pulicKey存到wechatpay.pem中*/
ClassPathResource platformKeyFile = new ClassPathResource("static/cert/wechatpay.pem");
InputStream platformKeyInputStream = null;
try {
platformKeyInputStream = platformKeyFile.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
PrivateKey mchPrivateKey = PemUtil.loadPrivateKey(mchPrivateKeyInputStream);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(MyConfig.MCH_ID, MyConfig.MCH_XLH, mchPrivateKey)
.withWechatPay(Arrays.asList(PemUtil.loadCertificate(platformKeyInputStream)));
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 微信通讯client1 读取本地文件方法用以下方法
* @return CloseableHttpClient
*/
public static CloseableHttpClient getClient1() {
/**商户私钥文件*/
File mchPrivateKeyFile = new File("C:\\cert\\apiclient_key.pem");
InputStream mchPrivateKeyInputStream = FileUtil.getInputStream(mchPrivateKeyFile);
/**微信平台公钥文件*/
File platformKeyFile = new File("C:\\cert\\wechatpay.pem");
InputStream platformKeyInputStream = FileUtil.getInputStream(platformKeyFile);
PrivateKey mchPrivateKey = PemUtil.loadPrivateKey(mchPrivateKeyInputStream);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(MyConfig.MCH_ID, MyConfig.MCH_XLH, mchPrivateKey)
.withWechatPay(Arrays.asList(PemUtil.loadCertificate(platformKeyInputStream)));
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 微信敏感数据加密公钥
* @return
*/
public static X509Certificate getSaveCertificates() {
/**商户公钥证书文件*/
File mchPublicKeyFile = new File("C:\\cert\\wechatpay.pem");
InputStream mchPublicKeyInputStream = FileUtil.getInputStream(mchPublicKeyFile);
return PemUtil.loadCertificate(mchPublicKeyInputStream);
}
}
业务逻辑
@Override
public R paytochange(PageData pd) {
String openId = pd.getString("account");
CloseableHttpClient httpClient = WeChatClient.getClient();
X509Certificate x509Certificate = WeChatClient.getSaveCertificates();
Map<String, Object> postMap = new HashMap<String, Object>();
//商家批次单号 长度 1~32
String outNo = IdUtil.getSnowflake(0, 0).nextIdStr();
postMap.put("appid", MyConfig.APP_ID);
postMap.put("out_batch_no", outNo);
//该笔批量转账的名称
postMap.put("batch_name", "测试转账");
//转账说明,UTF8编码,最多允许32个字符
postMap.put("batch_remark", "测试转账");
//转账金额单位为“分”。 总金额
postMap.put("total_amount", 100);
//转账总笔数
postMap.put("total_num", 1);
List<Map> list = new ArrayList<>();
Map<String, Object> subMap = new HashMap<>(4);
//商家明细单号
subMap.put("out_detail_no", outNo);
//转账金额
subMap.put("transfer_amount", 100);
//转账备注
subMap.put("transfer_remark", "明细备注1");
//用户在直连商户应用下的用户标示
subMap.put("openid", openId);
// subMap.put("user_name", RsaCryptoUtil.encryptOAEP(userName, x509Certificate));
list.add(subMap);
postMap.put("transfer_detail_list", list);
//发起转账操作
try {
String body = JSONUtil.toJsonStr(postMap);
System.out.println("请求参数:" + body);
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/transfer/batches");
httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
httpPost.addHeader("Wechatpay-Serial", MyConfig.MCH_XLH);
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();
}
} catch (Exception e) {
e.printStackTrace();
}
return R.ok();
}
以上方法中用到的常量 MyConfig 常量类中定义
public static final String APP_ID = “你自己的appid”; //app Id
public static final String MCH_KEY = “你自己设置的商户key”;//签名秘钥
public static final String MCH_ID = “你的商户号”; //商户号
public static final String MCH_XLH = “商户证书序列号”; //商户证书序列号
public static final String MCH_V3KEY = “证书的V3密钥 在微信支付平台设置的”; //证书V3秘钥