开发流程概述
- 开通企业付款到余额功能
- 去商户后台获取商户账号appid、商户号、证书
- 生成签名
- 生成请求XML
- 向微信请求付款
- 付款结果分析保存
1、开通企业付款到余额功能
可以参照官方教程
2、获取商户相关信息
方法自行百度
3、生成签名和请求XML
生成签名的官方教程链接
下面是具体的方法
/**
* 生成请求XML
* @param code 订单编号
* @param openid 微信用户的openid
* @param amount 提现金额(分)
* @return
*/
private String createXml(String code, String openid, String amount) {
String[] arr = new String[9];
arr[0] = "mch_appid=" + APP_ID; //商户账号appid
arr[1] = "mchid=" + MCH_ID; //商户号
arr[2] = "nonce_str=" + NONCE_STR; //随机字符串
arr[3] = "partner_trade_no=" + code; //订单编号
arr[4] = "openid=" + openid; //用户openid
arr[5] = "check_name=NO_CHECK"; //NO_CHECK:不校验真实姓名
arr[6] = "amount=" + amount;//金额
arr[7] = "desc=企业付款到余额"; //企业付款备注
arr[8] = "spbill_create_ip=192.168.**.****"; //Ip地址
Arrays.sort(arr);
String param = "";
for (int i = 0; i < arr.length; i++) {
param += arr[i] + "&";
}
param += "key=569C626880EF3EFFBB91A1E138EEC150"; //API密钥
String sign = MD5Util.MD5Encode(param, null).toUpperCase();
String xml = StringUtils.join(
"<xml>",
"<mch_appid>" + APP_ID + "</mch_appid>",
"<mchid>" + MCH_ID + "</mchid>",
"<nonce_str>" + NONCE_STR + "</nonce_str>",
"<partner_trade_no>" + code + "</partner_trade_no>",
"<openid>" + openid + "</openid>",
"<check_name>NO_CHECK</check_name>",
"<amount>" + amount + "</amount>",
"<desc>景芝云店余额提现</desc>",
"<spbill_create_ip>192.168.**.****</spbill_create_ip>",
"<sign>" + sign + "</sign>",
"</xml>"
);
return xml;
}
加密算法
import java.security.MessageDigest;
public class MD5Util {
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname)) {
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
} else {
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
}
} catch (Exception exception) {
}
return resultString;
}
4、向微信请求付款
String xml = createXml(code, wechatUser.getOpenId(), String.valueOf(payAmount));
ClientCustomSSL clientCustomSSL = new ClientCustomSSL();
/**
* URL:请求路径
*xml:请求参数
*CERT_PATH:证书路径(服务器绝对路径)
*MCH_ID:商户号
*/
String result = clientCustomSSL.doPay(URL, xml, CERT_PATH, MCH_ID);
请求方法
public class ClientCustomSSL {
/**
* 企业付款到余额
* @param url
* @param data
* @return
* @throws Exception
*/
public String doPay(String url, String data,String sslcertPath,String mch_id) throws Exception {
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
/**
*此处要改
*wxconfig.SSLCERT_PATH : 指向你的证书的绝对路径,带着证书去访问
*/
FileInputStream instream = new FileInputStream(new File(sslcertPath));//P12文件目录
try {
/**
* 此处要改
*
* 下载证书时的密码、默认密码是你的MCHID mch_id
* */
keyStore.load(instream, mch_id.toCharArray());//这里写密码
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
/**
* 此处要改
* 下载证书时的密码、默认密码是你的MCHID mch_id
* */
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mch_id.toCharArray())//这里也是写密码的
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpost = new HttpPost(url); // 设置响应头信息
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}
5、付款结果分析保存
EnterpriceToCustomer refundResult = parseXmlToMapEnterpriceToCustomer(result);
if (SUCCESS.equalsIgnoreCase(refundResult.getResult_code()) && SUCCESS.equalsIgnoreCase(refundResult.getReturn_code())) {
//8表示成功
} else {
//9 表示失败
}
返回结果实体类
/**
* 企业 付款到 客户 结果 实体类
*
* @author CHQ
* @create 2018-10-19 18:56
**/
public class EnterpriceToCustomer {
/* <xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[]]></return_msg>
<mchid><![CDATA[1488323162]]></mchid>
<nonce_str><![CDATA[o9fcpfvqow1aks48a2omvayu1ne7c709]]></nonce_str>
<result_code><![CDATA[SUCCESS]]></result_code>
<partner_trade_no><![CDATA[xvuct0087w4t1dpr87iqj98w5f71ljae]]></partner_trade_no>
<payment_no><![CDATA[1000018301201801163213961289]]></payment_no>
<payment_time><![CDATA[2018-01-16 14:52:16]]></payment_time>
</xml>
*/
private String return_code;
private String return_msg;
private String mchid;
private String nonce_str;
private String result_code;
private String partner_trade_no;
private String payment_no;
private String payment_time;
/*
* 支付错误时,返回的代码
* key是:return_code,值是:SUCCESS
key是:return_msg,值是:支付失败
key是:mch_appid,值是:wx49c22ad731b679c3
key是:mchid,值是:1488323162
key是:result_code,值是:FAIL
key是:err_code,值是:AMOUNT_LIMIT
key是:err_code_des,值是:付款金额超出限制。低于最小金额1.00元或累计超过20000.00元。
*
*/
private String err_code;
private String err_code_des;
public String getErr_code() {
return err_code;
}
public void setErr_code(String errCode) {
err_code = errCode;
}
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String errCodeDes) {
err_code_des = errCodeDes;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String returnCode) {
return_code = returnCode;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String returnMsg) {
return_msg = returnMsg;
}
public String getMchid() {
return mchid;
}
public void setMchid(String mchid) {
this.mchid = mchid;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonceStr) {
nonce_str = nonceStr;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String resultCode) {
result_code = resultCode;
}
public String getPartner_trade_no() {
return partner_trade_no;
}
public void setPartner_trade_no(String partnerTradeNo) {
partner_trade_no = partnerTradeNo;
}
public String getPayment_no() {
return payment_no;
}
public void setPayment_no(String paymentNo) {
payment_no = paymentNo;
}
public String getPayment_time() {
return payment_time;
}
public void setPayment_time(String paymentTime) {
payment_time = paymentTime;
}
@Override
public String toString() {
return "EnterpriceToCustomer [err_code=" + err_code + ", err_code_des="
+ err_code_des + ", mchid=" + mchid + ", nonce_str="
+ nonce_str + ", partner_trade_no=" + partner_trade_no
+ ", payment_no=" + payment_no + ", payment_time="
+ payment_time + ", result_code=" + result_code
+ ", return_code=" + return_code + ", return_msg=" + return_msg
+ "]";
}
结果解析方法
/**
* 下面是需要通过跟节点,找找到对应的类属性,手动把它set进去。因此API返回的参数不一样。需要写每个返回的Bean。
* 解析企业支付申请
* 解析的时候自动去掉CDMA
*
* @param xml
*/
public static EnterpriceToCustomer parseXmlToMapEnterpriceToCustomer(String xml) {
EnterpriceToCustomer enterpriceToCustomer = new EnterpriceToCustomer();
try {
StringReader read = new StringReader(xml);
// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
// 创建一个新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通过输入源构造一个Document
Document doc;
doc = (Document) sb.build(source);
Element root = doc.getRootElement();// 指向根节点
List<Element> list = root.getChildren();
if (list != null && list.size() > 0) {
for (Element element : list) {
System.out.println("key是:" + element.getName() + ",值是:" + element.getText());
if ("return_code".equals(element.getName())) {
enterpriceToCustomer.setReturn_code(element.getText());
}
if ("return_msg".equals(element.getName())) {
enterpriceToCustomer.setReturn_msg(element.getText());
}
if ("mchid".equals(element.getName())) {
enterpriceToCustomer.setMchid(element.getText());
}
if ("nonce_str".equals(element.getName())) {
enterpriceToCustomer.setNonce_str(element.getText());
}
if ("result_code".equals(element.getName())) {
enterpriceToCustomer.setResult_code(element.getText());
}
if ("partner_trade_no".equals(element.getName())) {
enterpriceToCustomer.setPartner_trade_no(element.getText());
}
if ("payment_no".equals(element.getName())) {
enterpriceToCustomer.setPayment_no(element.getText());
}
if ("payment_time".equals(element.getName())) {
enterpriceToCustomer.setPayment_time(element.getText());
}
if ("err_code".equals(element.getName())) {
enterpriceToCustomer.setErr_code(element.getText());
}
if ("err_code_des".equals(element.getName())) {
enterpriceToCustomer.setErr_code_des(element.getText());
}
}
}
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return enterpriceToCustomer;
}
如果正确会返回如下信息
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[]]></return_msg>
<mch_appid><![CDATA[wxc496d2a]]></mch_appid>
<mchid><![CDATA[10478671]]></mchid>
<nonce_str><![CDATA[5K8264ILTTCU16CQ2NMTM67VS]]></nonce_str>
<result_code><![CDATA[SUCCESS]]></result_code>
<partner_trade_no><![CDATA[TX154000445301]]></partner_trade_no>
<payment_no><![CDATA[100001830129966]]></payment_no>
<payment_time><![CDATA[2018-10-21 09:04:23]]></payment_time>
</xml>
如果按照上面的方法应该能成功
参考文章:
https://blog.csdn.net/xiaozhegaa/article/details/79178751
https://blog.csdn.net/xiaozhegaa/article/details/79127283