文章标签: java 微信 开发语言
版权
开通过程不做叙述,查看微信官方文档,仅介绍java调用api。
本文先展示发起转账API,后面再讲述发起转账需要的资料以及途中遇到的坑。
1、发起商家转账到零钱方法:
/**
*调用API参数准备 当传入姓名的时候需要做敏感信息加解密
*/
public HttpResult wechatTransfer(String orderNo, String openid, String userName, Integer amount){
Map<String, Object> postMap = new HashMap<>(10);
postMap.put("appid", DouDianConfig.APPID);
postMap.put("out_batch_no", orderNo);
//该笔批量转账的名称
postMap.put("batch_name", "商户提现");
//转账说明,UTF8编码,最多允许32个字符
postMap.put("batch_remark", username);
//转账金额单位为“分”。 总金额
System.out.println("转账总金额"+amount.toString());
postMap.put("total_amount", amount);
//。转账总笔数
postMap.put("total_num", 1);
List<Map> list = new ArrayList<>();
Map<String, Object> subMap = new HashMap<>(4);
//商家明细单号
subMap.put("out_detail_no", orderNo);
//转账金额
subMap.put("transfer_amount", amount);
//转账备注
subMap.put("transfer_remark", "用户提现(姓名:"+ userName +")");
//用户在直连商户应用下的用户标示
subMap.put("openid", openid);
//转账金额
log.info("用户提现(姓名:"+ userName)");
subMap.put("user_name", rsaEncryptOAEP(userName));
list.add(subMap);
postMap.put("transfer_detail_list", list);
return postTransBatRequest(
"https://api.mch.weixin.qq.com/v3/transfer/batches", JSON.toJSONString(postMap));
}
/**
* 发起转账请求 (微信所有的接口都需要获取access_token,下面有getToken方法)
* @param requestUrl 请求地址
* @param requestJson 请求参数
* @return 响应
*/
public HttpResult postTransBatRequest(
String requestUrl,
String requestJson) {
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");
//"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
httpPost.addHeader("Wechatpay-Serial", DouDianConfig.WX_SERIAL_NO);
//-------------------------核心认证 start-----------------------------------------------------------------
String authorization = WechatUtils.getToken("POST","/v3/transfer/batches",requestJson,DouDianConfig.APICLIENT_KEY,DouDianConfig.MCHID,DouDianConfig.SERIAL_NO);
// 添加认证信息
httpPost.addHeader("Authorization",
"WECHATPAY2-SHA256-RSA2048" + " "
+ authorization);
//---------------------------核心认证 end---------------------------------------------------------------
httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));
//发起转账请求
response = httpclient.execute(httpPost);
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
log.info("微信转账响应"+content);
return new HttpResult(response.getStatusLine().getStatusCode(),
content);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
}
return null;
}
/**
* 获取微信api v3请求验证头 Authorization
* @param method http请求方法 大写
* @param url 请求地址 不含域名
* @param body 请求报文主体 无填写 ""
* @param privateKey 请求
* @param mchid 商户号
* @param serialNo 证书序列号
* @return
*/
public static String getToken (String method, String url, String body, String privateKey, String mchid, String serialNo){
try {
String nonceStr = WXPayUtil.generateNonceStr();
long timestamp = System.currentTimeMillis() / 1000;
String msg = buildMessage(method, url, timestamp, nonceStr, body);
return "mchid=\"" + mchid + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + serialNo + "\","
+ "signature=\"" + sha256withRSA(msg,privateKey) + "\"";
} catch (Exception e) {
e.printStackTrace();
log.error("[WechatUtils getToken] SHA256withRSA 转义失败 error message:{}",e.getMessage());
}
return "fail";
}
/**
* 敏感信息加密 (这是最坑的接口,没有之一,加密用到的证书是平台证书!!!不是商家证书)
* 后面再叙述平台证书获取
* @param message 需加密信息
* @return 加密之后的信息
*/
private String rsaEncryptOAEP(String message) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE,getCertificate(new ByteArrayInputStream(DouDianConfig.CIPHERTEXT.getBytes())));
byte[] data = message.getBytes("utf-8");
byte[] cipherdata = cipher.doFinal(data);
return 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字节");
throw new BizException("加密原串的长度不能超过214字节");
}catch (Exception e){
e.printStackTrace();
throw new BizException("系统错误");
}
}
/**
* 证书解析
* @param inputStream 证书数据流
* @return X509
*/
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);
}
}
2、配置类,除平台证书外的参数请自行在商家平台获取
public class DouDianConfig {
/**
* 固定值
*/
public static final String GRANT_TYPE = "client_credential";
/**
* 小程序appid
*/
public static final String APPID = "";
/**
* 小程序密钥
*/
public static final String SECRET = "";
/**
* 商户号
*/
public static final String MCHID = "";
/**
* 商户密钥 --- 同 API V3 密钥
*/
public static final String KEY = "";
/**
* API V3 秘钥
*/
public static final String API_V3_KEY = "";
public static final String BODY = "";
/**
* 支付分签名方式
*/
public static final String SIGN_TYPE = "HMAC-SHA256";
/**
* 金额环回说明
*/
public static final String REFUND_DESC = "";
/**
* 商户api证书序列号
* https://myssl.com/cert_decode.html 证书解析地址
*/
public static final String SERIAL_NO = "";
/**
* 证书内容 apiclient_cert.pem文件中的内容 除去开头结尾行
* 请求量大为节约资源直接拿出来
*/
public static final String PRIVATE_CONTENT = "";
public static final String APICLIENT_KEY = "";
/**
* 微信平台证书 5年有效期 通过接口获取
*/
public static final String CIPHERTEXT="";
/**
* 平台证书序列号
*/
public static final String WX_SERIAL_NO="";
}
3、平台证书获取(在转账不需要校验用户姓名的情况下是不需要平台证书的)
/**
* 获取微信支付平台证书
*/
public static HttpResult getWeChatCertificates(){
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpEntity entity = null;
try {
String requestUrl = "https://api.mch.weixin.qq.com/v3/certificates";
//商户私钥证书
HttpGet httpPost = new HttpGet(requestUrl);
// NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
httpPost.addHeader("Content-Type", "application/json");
httpPost.addHeader("Accept", "application/json");
//"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + DouDianConfig.MCHID);
//-------------------------核心认证 start-----------------------------------------------------------------
String authorization =getToken("GET", "/v3/certificates", "", DouDianConfig.APICLIENT_KEY, DouDianConfig.MCHID, DouDianConfig.SERIAL_NO);
// 添加认证信息
httpPost.addHeader("Authorization",
"WECHATPAY2-SHA256-RSA2048" + " "
+ authorization);
//发起转账请求
response = httpclient.execute(httpPost);
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(content);
return new HttpResult(response.getStatusLine().getStatusCode(),
content);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
}
return null;
}
上面接口返回的是一串json字符串因为比较懒,而且证书有效期是5年,所以我这边是把请求拿到的值没有直接转成对象,而是把需要的三个参数复制出来:
String ciphertext = "";
String nonce="";
String associated_data ="";
EncodeRsa encodeRsa = new EncodeRsa(DouDianConfig.KEY.getBytes());
String s = encodeRsa.decryptToString(associated_data.getBytes(),nonce.getBytes(),ciphertext);
s就是最终得到的平台证书内容了,复制到配置类中就行了;
4、接口回查
不要以为上面的流程走完就结束了,还有最大的一个坑,商家转账到零钱新接口因为是批量操作,接口调起以后并不会直接返回给你成功还是失败,需要进行回查才能知道哪一笔转账是否成功,一般情况下是不会出现失败,但是如果转账时候传入了username,需要校验姓名是否正确的时候,出现失败的可能性就极大的提高了,必须回查,有空再写回查方法,也可以去微信官方文档复制
API列表 - 商家转账到零钱 | 微信支付商户文档中心
————————————————
联系客服