/**
* 微信-企业付款到银行卡接口
* @param partner_trade_no 商户订单号
* @param enc_bank_no 收款方银行卡号
* @param enc_true_name 收款方用户名
* @param bank_code 收款方开户行代码
* @param amount 付款金额:RMB分(支付总额,不含手续费);每笔按付款金额收取手续费,按金额0.1%收取,最低1元,最高25元;amount字段必填,必须大于0或者小于等于2亿
* @return
*/
public static AjaxJson payBank(String partner_trade_no,
String enc_bank_no, String enc_true_name, String bank_code, String amount) {
logger.debug("进入企业付款到银行卡"+partner_trade_no+"||"+enc_bank_no+"||"+enc_true_name+"||"+bank_code+"||"+amount);
AjaxJson j = new AjaxJson();
try {
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config, SignType.MD5);
Map<String, String> data = new HashMap<String, String>();
data.put("partner_trade_no", partner_trade_no);
//收款方银行卡号RSA
data.put("enc_bank_no", getRsaEncryptStr(enc_bank_no, RSA_PUBLIC_KEY));//RSA加密公钥(PKCS8格式)
//收款方用户名RSA
data.put("enc_true_name", getRsaEncryptStr(enc_true_name, RSA_PUBLIC_KEY));//RSA加密公钥(PKCS8格式)
//收款方开户行
data.put("bank_code", bank_code);
data.put("amount", amount);
//订单备注
data.put("desc", "用户提现");
Map<String, String> resp = wxpay.payBank(data);
String msg = resp.get("return_code") + "=" + resp.get("return_msg");
logger.debug("企业付款到银行卡返回结果:"+msg);
if(resp.get("result_code") != null || resp.get("err_code_des") != null){
msg += (";" + resp.get("result_code") + "=" + resp.get("err_code_des"));
}
msg += (";payment_no=" + resp.get("payment_no"));
j.setMsg("提现结果:" + msg);
logger.info("调用微信-企业付款到银行卡接口结果:{}", resp);
if("SUCCESS".equals(resp.get("return_code"))
&& "SUCCESS".equals(resp.get("result_code"))
&& resp.get("partner_trade_no") != null
&& resp.get("partner_trade_no").equals(partner_trade_no)){
j.setSuccess(true);
} else {
j.setSuccess(false);
//注意:当状态为FAIL时,存在业务结果未明确的情况,所以如果状态为FAIL,
//请务必通过查询接口确认此次付款的结果(关注错误码err_code字段)。
//如果要继续进行这笔付款,请务必用原商户订单号和原参数来重入此接口。
if("SUCCESS".equals(resp.get("return_code"))
&& "FAIL".equals(resp.get("result_code"))){
j.setErrorCode("2");
}
}
} catch (Exception e) {
j.setSuccess(false);
j.setMsg("微信-企业付款到银行卡处理异常");
logger.error("调用微信-企业付款到银行卡接口处理异常{}", e);
}
return j;
}
public static final String MMPAYSPTRANS_PAY_BANK_URL_SUFFIX = "/mmpaysptrans/pay_bank";
/**
* 企业付款到银行卡
* @param reqData
* @param connectTimeoutMs
* @param readTimeoutMs
* @return
* @throws Exception
*/
public Map<String, String> payBank(Map<String, String> reqData) throws Exception {
String respXml = this.requestWithCert(WXPayConstants.MMPAYSPTRANS_PAY_BANK_URL_SUFFIX, this.fillRequestData3(reqData), config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
return this.processResponseXml2(respXml);
}
/**
* 需要证书的请求
* @param urlSuffix String
* @param reqData 向wxpay post的请求数据 Map
* @param connectTimeoutMs 超时时间,单位是毫秒
* @param readTimeoutMs 超时时间,单位是毫秒
* @return API返回数据
* @throws Exception
*/
public String requestWithCert(String urlSuffix, Map<String, String> reqData,
int connectTimeoutMs, int readTimeoutMs) throws Exception {
String msgUUID= reqData.get("nonce_str");
String reqBody = WXPayUtil.mapToXml(reqData);
String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
return resp;
}
/**
* 可重试的,双向认证的请求
* @param urlSuffix
* @param uuid
* @param data
* @param connectTimeoutMs
* @param readTimeoutMs
* @return
*/
public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
}
private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
Exception exception = null;
long elapsedTimeMillis = 0;
long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
boolean firstHasDnsErr = false;
boolean firstHasConnectTimeout = false;
boolean firstHasReadTimeout = false;
IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
if(domainInfo == null){
throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
}
try {
String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
return result;
}
catch (UnknownHostException ex) { // dns 解析错误,或域名不存在
exception = ex;
firstHasDnsErr = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout
);
}
catch (ConnectTimeoutException ex) {
exception = ex;
firstHasConnectTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout
);
}
catch (SocketTimeoutException ex) {
exception = ex;
firstHasReadTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
}
catch (Exception ex) {
exception = ex;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
}
config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
throw exception;
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 不需要appid的请求使用,且不需要sign_type参与签名的
* @param reqData
* @return
* @throws Exception
*/
public Map<String, String> fillRequestData3(Map<String, String> reqData) throws Exception {
reqData.put("mch_id", WxPayConfig.MCHID);
reqData.put("nonce_str", WXPayUtil.generateNonceStr());
reqData.put("sign", WXPayUtil.generateSignature(reqData, WxPayConfig.KEY, this.signType));
return reqData;
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,无需验证签名的。
* @param xmlStr API返回的XML格式数据
* @return Map类型数据
* @throws Exception
*/
public Map<String, String> processResponseXml2(String xmlStr) throws Exception {
String RETURN_CODE = "return_code";
String return_code;
Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
if (respData.containsKey(RETURN_CODE)) {
return_code = respData.get(RETURN_CODE);
}
else {
throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
}
if (return_code.equals(WXPayConstants.FAIL)) {
return respData;
}
else if (return_code.equals(WXPayConstants.SUCCESS)) {
return respData;
}
else {
throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
}
}