近期公司app要接入余额提现的功能,接入支付宝和微信的提现功能。忍不住想吐槽微信,文档做的很简单,接口调用也比较复杂。下面就把我的接入过程记录一点。
1.微信公众号,商户平台都是公司原有的,其他项目组在用。这里申请过程就不说了。
2.前期准备:
1):去微信商户平台--产品中心--api安全下载证书。(可能需要管理员授权安装操作证书,需要管理员手机验证码)
2):如果是正在用的商户平台需要向公司前辈要密钥,如果是没在使用的商户平台可以自己生成一个32位密钥,然后在微信商户平台--产品中心--api安全这个下面设置密钥,记住,设置完成一定要保存好密钥;
3):商户平台下微信商户平台--产品中心--api安全这里的商户转账的ip限制和转账次数设置一下。
3.参考微信api文档进行开发,文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
4.把下载下来的证书放到项目目录中,请求时候会用到。
5.相关代码:
1).WeXinPay.java
package com.qlwb.business.payment;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.aspire.boc.util.ResourceManager;
import com.github.wxpay.sdk.WXPayUtil;
import com.qlwb.business.payment.util.WxpayUtil;
import com.qlwb.business.test.util.WeChatConfig;
import com.qlwb.business.util.StringUtil;
/**
* 微信相关的业务
* @author 朱玉猛
* @since
*
*/
public class WeXinPay {
//关联配置文件
private static ResourceManager rm = ResourceManager.getInstance();
//商家付款的url
private static String post_transfer_url=rm.getValue("post_transfer_url");
//默认字符集
public static String charSet="UTF-8";
//p12证书的路径
private static String ca_path=StringUtil.getRootFileStr("/weixinCert/apiclient_cert.p12");
//API_KEY
private static String api_key=rm.getValue("api_key");
//商户号
private static String mch_id=rm.getValue("mch_id");
//商户app_id
private static String mch_app_id=rm.getValue("mch_app_id");
//check_name
private static String check_name=rm.getValue("check_name");
//desc
private static String desc=rm.getValue("desc");
private static WeXinPay instance=new WeXinPay();
private WeXinPay(){
}
public static WeXinPay getInstance(){
return instance;
}
/**
* 银行向个人微信钱包转账,用于提现
* @param re_openid openid
* @param trade_no 订单编号,转账id
* @param amount 转账金额,元为单位两位小数
* @return success 转账成功与否的标志 returnCode 请求发送成功标志 resultCode转账成功标志 err_code_des 结果描述
*/
public static Map<String,String> payToUser(String re_openid,String trade_no,double amount) {
Map<String,String> resMap=new HashMap<String,String>();
Map<String, String> map = new HashMap<String, String>();
map.put("nonce_str", WxpayUtil.getNonceStr());
map.put("mchid", mch_id);
map.put("mch_appid", mch_app_id);
map.put("partner_trade_no", WxpayUtil.getNonceStr());
// 用户的openid
map.put("openid", re_openid);
//付款金额,单位分
map.put("amount", Integer.toString((int)(amount*100)));
//校验姓名:不校验
map.put("check_name", check_name);
map.put("desc",desc);
map.put("spbill_create_ip", "58.56.96.154");
String xmlResult="";
try {
String sign = WxpayUtil.getSgin(map, api_key);
map.put("sign", sign);
String strParams=WxpayUtil.map2xml(map);
xmlResult=WxpayUtil.post(strParams, mch_id, post_transfer_url,ca_path);
Map<String, String> result = WxpayUtil.xml2map(xmlResult);
System.out.println(result);
// 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
String return_code = result.get("return_code");
// 业务结果
String result_code = result.get("result_code");
String err_code_des=result.get("err_code_des");
//请求不成功,建议重试
if (StringUtils.isBlank(return_code) || !"SUCCESS".equals(return_code)) {
resMap.put("succes", "false");
resMap.put("returnCode", "fail");
resMap.put("resultCode", "fail");
resMap.put("desc", "请求失败,建议重试");
}else if("SUCCESS".equals(return_code)&&"FAIL".equals(result_code)){//请求成功,但是支付失败
resMap.put("succes", "false");
resMap.put("returnCode", "success");
resMap.put("resultCode", "fail");
resMap.put("desc", err_code_des);
}else if("SUCCESS".equals(return_code)&&"SUCCESS".equals(result_code)){//支付成功
resMap.put("succes", "true");
resMap.put("returnCode", "success");
resMap.put("resultCode", "success");
resMap.put("desc", "支付成功");
}else{
resMap.put("succes", "false");
resMap.put("returnCode", return_code);
resMap.put("resultCode", result_code);
resMap.put("desc", err_code_des);
}
} catch (Exception e) {
resMap.put("succes", "false");
resMap.put("returnCode", "fail");
resMap.put("resultCode", "fail");
resMap.put("desc", "转账失败,请联系系统管理员");
e.printStackTrace();
}
return resMap;
}
}
2).WxpayUtil :
package com.qlwb.business.payment.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
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;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.qlwb.business.payment.WeXinPay;
import com.tenpay.util.MD5Util;
/**
* 微信转账相关工具类
* @author Administrator
*
*/
public class WxpayUtil {
private static String characterEncoding = "UTF-8";
/**
* post提交到微信服务器
*
* @param requestXML
* @param instream
* @return
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws IOException
* @throws KeyManagementException
* @throws UnrecoverableKeyException
* @throws KeyStoreException
* @throws java.security.cert.CertificateException
*/
public static String post(String requestXML, String mch_id,String url,String ca_path)
throws NoSuchAlgorithmException, Exception, IOException,
KeyManagementException, UnrecoverableKeyException,
KeyStoreException, java.security.cert.CertificateException {
//加载微信提供给商户的证书
FileInputStream instream = new FileInputStream(new File(ca_path));
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try {
keyStore.load(instream, mch_id.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mch_id.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[] { "TLSv1" }, null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf).build();
String result = "";
try {
HttpPost httpPost = new HttpPost(url);
StringEntity reqEntity = new StringEntity(requestXML, characterEncoding); // 如果此处编码不对,可能导致客户端签名跟微信的签名不一致
reqEntity.setContentType("application/x-www-form-urlencoded");
reqEntity.setContentEncoding("UTF-8");
httpPost.setEntity(reqEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(entity.getContent(), characterEncoding));
String text;
while ((text = bufferedReader.readLine()) != null) {
result += text;
}
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
return result;
}
public static String getSgin(Map<String, String> params, String wxkey) {
String qr = "";
String[] p = new String[params.size()];
int x = 0;
for (String key : params.keySet()) {
p[x] = key + "=" + params.get(key);
x++;
}
// 参数排序
Arrays.sort(p);
// 拼出查询参数
for (String param : p) {
String[] value = param.split("=");
qr += value[0] + "=" + params.get(value[0]) + "&";
}
return MD5Util.MD5Encode(qr + "key=" + wxkey,characterEncoding).toUpperCase();
}
public static String getNonceStr() {
Random random = new Random();
return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), characterEncoding);
}
public static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
/**
* 生成提交给微信服务器的xml格式参数
*
* @param params
* @return
*/
public static String map2xml(Map<String, String> params) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = params.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
}
sb.append("</xml>");
return sb.toString();
}
public static Map<String, String> xml2map(String responseXML) {
Document document = null;
InputStream in = null;
Map<String, String> resultMap = new TreeMap<String, String>();
try {
in = new ByteArrayInputStream(responseXML.getBytes(characterEncoding));
// 读取输入流
SAXReader reader = new SAXReader();
document = reader.read(in);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList) {
resultMap.put(e.getName(), e.getText());
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
return resultMap;
}
}
3).由于我的md5调用的公司内部的,加密过程如下:
package com.tenpay.util;
import java.security.MessageDigest;
public class MD5Util
{
private static final String[] hexDigits = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
private static String byteArrayToHexString(byte[] b)
{
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
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 localException) {
}
return resultString;
}
}
6.恩,调用过程就是这样,正常情况下会成功的。但是,微信支付比较恶心,接口证书错误,参数少了,余额不足的情况下会提示,但是有一个错误【签名错误】比较难找。原因如下:
1).商户平台的密钥不对
2).参数传多了!!(这个问题很难发现,我找了一下午)
3).发送请求的时候字符集错误
基本就是这样。我是调通了,其间遇到很多坑。这个地方没必要多研究,有用到的拿去用,少走一些弯路。