注:本文仅供参考,应用于生产需完善 刷新支付证书序列号及转账验签操作,具体实现由于本农最近疯狂加班,暂时没有时间更新。不久将来会继续完善本文章
开发前准备:
1.商户id
2.支付证书序列号
3.证书私钥 apiclient_key.pem 文件
4.小程序或公众号需要与商户号绑定
PS
转账地址:批量转账
https://api.mch.weixin.qq.com/v3/transfer/batches
查询地址: 商户批次号查询
https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no
官方jar 坐标
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.7</version> </dependency>
代码
1.工具类
package '文件包路径';
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Random;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
@Slf4j
public class VechatPayV3Util {
/**
* @param method 请求方法 post
* @param canonicalUrl 请求地址
* @param body 请求参数 GET请求传空字符
* @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;
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 {
//log.info("签名 证书地址是 " + filename);
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("无效的密钥格式");
}
}
/**
* 获取随机位数的字符串
*
* @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();
}
}
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
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.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;
import static org.apache.http.HttpHeaders.*;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
/**
* 微信支付专用类 请求操作方法
*
* @author Administrator
*/
@Slf4j
public class HttpUtil {
/**
* 发起批量转账API 批量转账到零钱
*
* @param requestUrl
* @param requestJson 组合参数
* @param wechatPayserialNo 商户证书序列号
* @param privatekeypath 商户私钥证书路径
* @return
*/
public static String postTransBatRequest(
String requestUrl,
String requestJson,
String wechatPayserialNo,
String mchId,
String privatekeypath, String url) {
CloseableHttpResponse response = null;
HttpEntity entity = null;
CloseableHttpClient httpClient = null;
try {
HttpPost httpPost = createHttpPost(requestUrl, requestJson, wechatPayserialNo, mchId, privatekeypath, url);
httpClient = HttpClients.createDefault();
//发起转账请求
response = httpClient.execute(httpPost);
log.info("response:{}", response);
entity = response.getEntity();//获取返回的数据
log.info("-----getHeaders.Request-ID:" + response.getHeaders("Request-ID"));
return EntityUtils.toString(entity);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (httpClient!=null) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 账单查询
*
* @param requestUrl 请求完整地址
* @param wechatPayserialNo 商户证书序列号
* @param privatekeypath 商户私钥证书路径
* @param privatekeypath 支付秘钥
* @return
*/
public static String getTransBatRequest(
String requestUrl,
String wechatPayserialNo,
String mchId,
String privatekeypath, String url) throws NotFoundException {
CloseableHttpResponse response = null;
HttpEntity entity = null;
CloseableHttpClient httpClient = null;
try {
HttpGet httpPost = createHttpGet(requestUrl, wechatPayserialNo, mchId, privatekeypath, url);
httpClient = HttpClients.createDefault();
//发起转账请求
response = httpClient.execute(httpPost);
log.info("response:{}", response);
entity = response.getEntity();//获取返回的数据
log.info("-----getHeaders.Request-ID:" + response.getHeaders("Request-ID"));
return EntityUtils.toString(entity);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (httpClient!=null) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
*
* @param requestUrl 请求完整地址
* @param requestJson 请求参数
* @param wechatPayserialNo 支付证书序列号
* @param mchId 商户号
* @param privatekeypath 私钥路径
* @param ServletPath 相对路径
* @return
*/
private static HttpPost createHttpPost(String requestUrl,
String requestJson,
String wechatPayserialNo,
String mchId,
String privatekeypath, String ServletPath) {
//商户私钥证书
HttpPost httpPost = new HttpPost(requestUrl);
// NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
//"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
httpPost.addHeader(WECHAT_PAY_SERIAL, wechatPayserialNo);
//-------------------------核心认证 start-----------------------------------------------------------------
String strToken = null;
try {
log.info("requestJson:{}", requestJson);
strToken = VechatPayV3Util.getToken("POST",
ServletPath,
requestJson, mchId, wechatPayserialNo, privatekeypath);
} catch (Exception e) {
log.error("createHttpPost error:", e);
e.printStackTrace();
}
StringEntity reqEntity = new StringEntity(requestJson, APPLICATION_JSON);
log.info("token " + strToken);
// 添加认证信息
httpPost.addHeader("Authorization",
"WECHATPAY2-SHA256-RSA2048" + " "
+ strToken);
//---------------------------核心认证 end---------------------------------------------------------------
httpPost.setEntity(reqEntity);
return httpPost;
}
/**
* 创建get 请求
*
* @param requestUrl 请求完整地址
* @param wechatPayserialNo 支付证书序列号
* @param mchId 商户号
* @param privatekeypath 私钥路径
* @param ServletPath 相对路径 请求地址上如果有参数 则此处需要带上参数
* @return HttpGet
*/
private static HttpGet createHttpGet(String requestUrl,
String wechatPayserialNo,
String mchId,
String privatekeypath, String ServletPath) {
//商户私钥证书
HttpGet httpGet = new HttpGet(requestUrl);
// NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
httpGet.addHeader("Content-Type", "application/json");
httpGet.addHeader("Accept", "application/json");
//"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
httpGet.addHeader("Wechatpay-Serial", wechatPayserialNo);
//-------------------------核心认证 start-----------------------------------------------------------------
String strToken = null;
try {
strToken = VechatPayV3Util.getToken("GET",
ServletPath,
"", mchId, wechatPayserialNo, privatekeypath);
} catch (Exception e) {
log.error("createHttpGet error:", e);
e.printStackTrace();
}
log.info("token " + strToken);
// 添加认证信息
httpGet.addHeader("Authorization",
"WECHATPAY2-SHA256-RSA2048" + " "
+ strToken);
//---------------------------核心认证 end---------------------------------------------------------------
return httpGet;
}
2 创建参数调用
-------------------------------------------------转账------------------------------------------------
Map<String, Object> postMap = new HashMap<String, Object>();
//小程序 id
postMap.put("appid", appId);
postMap.put("out_batch_no", outNo);
//该笔批量转账的名称
postMap.put("batch_name", batchName);
//转账说明,UTF8编码,最多允许32个字符
postMap.put("batch_remark", batchRemark);
//转账金额单位为“分”。 总金额
postMap.put("total_amount",integer );
//转账总笔数
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", integer);
//转账备注
subMap.put("transfer_remark", remark);
//用户在直连商户应用下的用户标示
subMap.put("openid", openId);
//大金额需要传入真实姓名
//subMap.put("user_name", RsaCryptoUtil.encryptOAEP(userName, VechatPayV3Util.getSaveCertificates(privatekeypath)));
list.add(subMap);
postMap.put("transfer_detail_list", list);
String result = HttpUtil.postTransBatRequest(
"https://api.mch.weixin.qq.com/v3/transfer/batches",
JSONObject.toJSONString(postMap),
"支付证书序列号",
"商户号",
"私钥路径","/v3/transfer/batches");
JSONObject jsonObject = JSONObject.parseObject(result);
//成功示例
/**
* {
* "out_batch_no": "plfk2020042013",
* "batch_id": "1030000071100999991182020050700019480001",
* "create_time": "2015-05-20T13:29:35.120+08:00"
* }
*
*/
if (!StringUtils.isEmpty(jsonObject.get("create_time"))){
//转账成功
}else{
//失败
}
-----------------------------------------------------查询------------------------------------------------------------
//使用商家批次号查询
String result = HttpUtil.getTransBatRequest(
"https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no"+"/"+"商家批次号"+"?need_query_detail=True&detail_status=ALL",
"支付证书序列号",
"商户号",
"商户私钥路径","/v3/transfer/batches/out-batch-no"+"/"+"商家批次号"+"?need_query_detail=True&detail_status=ALL");
JSONObject jsonOb = JSONObject.parseObject(result);
//正常返回示例
/**
* {
* "limit": 20,
* "offset": 1,
* "transfer_batch": {
* "mchid": "1900001109",
* "out_batch_no": "plfk2020042013",
* "batch_id": "1030000071100999991182020050700019480001",
* "appid": "wxf636efh567hg4356",
* "batch_status": "ACCEPTED",
* "batch_type": "API",
* "batch_name": "2019年1月深圳分部报销单",
* "batch_remark": "2019年1月深圳分部报销单",
* "close_reason": "OVERDUE_CLOSE",
* "total_amount": 4000000,
* "total_num": 200,
* "create_time": "2015-05-20T13:29:35.120+08:00",
* "update_time": "2015-05-20T13:29:35.120+08:00",
* "success_amount": 3900000,
* "success_num": 199,
* "fail_amount": 100000,
* "fail_num": 1
* },
* "transfer_detail_list": [
* {
* "detail_id": "1040000071100999991182020050700019500100",
* "out_detail_no": "x23zy545Bd5436", 详细单号
* "detail_status": "SUCCESS" 成功 FAIL=失败 PROCESSING =支付中
* }
* ]
* }
*/