Java实现微信小程序商家转账到零钱功能V3版本
工具类
该工具类主要用于发送请求签名等
java
@Slf4j
public class WechatPayV3Util {
/**
* @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(StandardCharsets.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) {
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 {
//todo 我放在项目里的 也可以放在服务器上 都可以 看自己需求了
String content = new String(Files.readAllBytes(Paths.get("src/main/resources" + filename)), StandardCharsets.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("不支持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();
}
/**
* 微信敏感数据加密-公钥
* @return
*/
public static X509Certificate getSaveCertificates(String platformCertPath) throws IOException {
InputStream inputStream = Files.newInputStream(new File(platformCertPath).toPath());
BufferedInputStream bis = new BufferedInputStream(inputStream);
return PemUtil.loadCertificate(bis);
}
}
请求地址信息
java
public class WxApiUrl {
/**
* 商家转账到零钱
*/
public static String WECHAT_TRANSFERS_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches";
/**
* 商户批次号查询
*/
public static String SELECT_ORDER_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no";
}
配置文件中值填充到属性中
java
@Configuration
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyName;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
}
配置文件
java
# 微信支付相关参数
wxpay:
# 商户号
mch-id: xxxxxxx
# 商户API证书序列号
mch-serial-no: xxxxxxxxxxxxxxxxxxxxxxxxxxx
# 商户私钥文件
# 注意:该文件放在resource/file
private-key-name: file/apiclient_key.pem
# APIv3密钥
api-v3-key: xxxxxxxxxxxxxxxxxxxxx
# APPID
appid: xxxxxxxxxxxxxxx
# 微信服务器地址
domain: https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
notify-domain: http://tozzn.e3.luyouxia.net:10062
提现与账单查询
java
public interface WeChatApiDao {
/**
* 提現
* @return
*/
boolean transfers(String realName,String openId ,String outTradeNo, BigDecimal amount);
/**
* 状态 0待确认 1已受理 2转账中 3已完成 4已关闭
* @param outTradeNo
* @return
*/
Integer queryTransfersStatus(String outTradeNo);
}
java
@Slf4j
@Component
public class WeChatApiDaoImpl implements WeChatApiDao {
@Resource
private WxPayConfig wxPayConfig;
@Override
@SneakyThrows
public boolean transfers(String realName,String openId ,String outTradeNo, BigDecimal amount) {
// if (StringUtils.isBlank(realName)) {
// throw new NingException(500, "请确认是否设置真实姓名");
// }
int value = amount.multiply(new BigDecimal(100)).intValue();
Map<String, Object> postMap = new HashMap<String, Object>();
//小程序 id
postMap.put("appid", wxPayConfig.getAppid()); // 需要跟商户ID 绑定的奥
postMap.put("out_batch_no", outTradeNo);
//该笔批量转账的名称
postMap.put("batch_name", "提现");
//转账说明
postMap.put("batch_remark", "提现");
//转账金额单位为"分"。 总金额
postMap.put("total_amount", value);
//转账总笔数
postMap.put("total_num", 1);
// 转账批次集合
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> subMap = new HashMap<>(4);
//商家明细单号 (系统内唯一)
subMap.put("out_detail_no", CommUtils.getUUID());
//转账金额
subMap.put("transfer_amount", value);
//转账备注
subMap.put("transfer_remark", "提现到账");
//用户使用小程序时 记录了用户的微信opneId 必须是在Appid 下的openId 不然不会成功的奥
subMap.put("openid", openId);
//大金额需要传入真实姓名 根据自己需求设置
// subMap.put("user_name", RsaCryptoUtil.encryptOAEP(realName, WechatPayV3Util.getSaveCertificates(wxPayConfig.getPrivateKeyName())));
list.add(subMap);
postMap.put("transfer_detail_list", list);
log.info("请求参数:{}",JSONObject.toJSONString(postMap));
String result = HttpUtil.postTransBatRequest(
WxApiUrl.WECHAT_TRANSFERS_URL,// 请求地址
JSONObject.toJSONString(postMap),// 请求参数
wxPayConfig.getMchSerialNo(),// 支付证书序列号
wxPayConfig.getMchId(),// 商户号
wxPayConfig.getPrivateKeyName()); //证书私钥地址
JSONObject jsonObject = JSONObject.parseObject(result);
log.info("返回结果:{}",jsonObject);
/*正常返回
*{
"out_batch_no" : "plfk2020042013",
"batch_id" : "1030000071100999991182020050700019480001",
"create_time" : "2015-05-20T13:29:35.120+08:00",
"batch_status" : "ACCEPTED"
}
*/
return "ACCEPTED".equals(jsonObject.getString("batch_status"));
}
@Override
public Integer queryTransfersStatus(String outTradeNo) {
String result = HttpUtil.getTransBatRequest(
WxApiUrl.SELECT_ORDER_URL + "/" + outTradeNo + "?need_query_detail=true&detail_status=ALL",// 请求地址
wxPayConfig.getMchSerialNo(),// 支付证书序列号
wxPayConfig.getMchId(),// 商户号
wxPayConfig.getPrivateKeyName(),
"/v3/transfer/batches/out-batch-no/" + outTradeNo + "?need_query_detail=true&detail_status=ALL"
); //证书私钥地址
JSONObject jsonObject = JSONObject.parseObject(result);
JSONArray orderArr = jsonObject.getJSONArray("transfer_detail_list");
if(orderArr == null){
return 5;
}
JSONObject transferBatch = (JSONObject) orderArr.get(0);
String status = transferBatch.getString("detail_status");
if ("WAIT_PAY".equals(status)) {
return 1;
}else if ("PROCESSING".equals(status)) {
return 2;
}else if ("SUCCESS".equals(status)) {
return 3;
}else if ("FAIL".equals(status)) {
return 4;
}else if ("INIT".equals(status)) {
return 0;
}
return 5;
}
ok,大功告成,亲测可用~