1 概述
微信转账新接口 发起商家转账API 微信开发文档
接口说明 适用对象:直连商户 请求URL:https://api.mch.weixin.qq.com/v3/transfer/batches 请求方式:POST 接口限频: 单个商户 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。 是否需要证书:是
商户可以通过该接口同时向多个用户微信零钱进行转账操作。
2 发起微信转账 请求参数组装
import cn.hutool.core.util.IdUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class WechatPay {
private static final Gson GSON = new GsonBuilder().create();
public static void main(String[] args) throws Exception {
weixinTransferBat();
}
public static String weixinTransferBat() {...};
public static String weixinTransferBat() throws Exception {
String mchid = "";
String appId = "";
String openId = "";
String wechatPayserialNo = "";
String privatekeypath = "";
Map<String, Object> postMap = new HashMap<String, Object>();
String outNo = IdUtil.getSnowflake(0, 0).nextIdStr();
postMap.put("appid", appId);
postMap.put("out_batch_no", outNo);
postMap.put("batch_name", "测试转账");
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);
list.add(subMap);
postMap.put("transfer_detail_list", list);
String resStr = HttpUtil.postTransBatRequest(
"https://api.mch.weixin.qq.com/v3/transfer/batches",
GSON.toJson(postMap),
wechatPayserialNo,
mchid,
privatekeypath);
}
商家批次单号,因为我这里只有一笔,所以用了同一个,读者可以生成多个批次单号, IdUtil 我用的是 cn.hutool 中的工具类,依赖如下:
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.2</version>
</dependency>
证书序列号需要在微信商户后台申请
3 发起微信转账 请求核心代码
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
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;
@Slf4j
public class HttpUtil {
public static String postTransBatRequest(
String requestUrl,
String requestJson,
String wechatPayserialNo,
String mchID4M,
String privatekeypath) {
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpEntity entity = null;
try {
HttpPost httpPost = new HttpPost(requestUrl);
httpPost.addHeader("Content-Type", "application/json");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Wechatpay-Serial", wechatPayserialNo);
String strToken = VechatPayV3Util.getToken("POST",
"/v3/transfer/batches",
requestJson,mchID4M,wechatPayserialNo, privatekeypath);
log.error("微信转账token "+strToken);
httpPost.addHeader("Authorization",
"WECHATPAY2-SHA256-RSA2048" + " "
+ strToken);
httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));
response = httpclient.execute(httpPost);
entity = response.getEntity();
log.info("-----getHeaders.Request-ID:"+response.getHeaders("Request-ID"));
return EntityUtils.toString(entity);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
return null;
}
}
3.1 微信转账 token
微信转账 token 签名认证 这套是固定的,大家可以直接复制使用
import java.io.IOException;
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;
import org.springframework.util.StringUtils;
@Slf4j
public class VechatPayV3Util {
public static String getToken(
String method,
String canonicalUrl,
String body,
String merchantId,
String certSerialNo,
String keyPath) throws Exception {
String signStr = "";
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) {
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());
}
public static PrivateKey getPrivateKey(String filename) throws IOException {
log.error("签名 证书地址是 "+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("无效的密钥格式");
}
}
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();
}
}
4 处理微信转账结果
如果正常微信转账成功,则会返回 JSON 格式的单号等信息
{
"out_batch_no": "plfk2020042013",
"batch_id": "1030000071100999991182020050700019480001",
"create_time": "2015-05-20T13:29:35.120+08:00"
}
小编这里定义的java bean 解析
TransferResponsEntity transferResponsEntity = GSON.fromJson(resStr, TransferResponsEntity.class);
TransferResponsEntity 的定义如下:
@Data
@NoArgsConstructor
public class TransferResponsEntity implements Serializable {
private String code;
private String message;
private String batch_id;
private String out_batch_no;
@Override
public String toString() {
return "TransferResponsEntity{" +
"code='" + code + '\'' +
", message='" + message + '\'' +
", batch_id='" + batch_id + '\'' +
", out_batch_no='" + out_batch_no + '\'' +
'}';
}
}
转账成功的时候 code 值为null ,转账失败里,code 、message 值不为 null.
如果提示你全额不足,可能是运营商账户问题
|