先吐槽下微信,完全就是个坑,文档确实有,但是个别细节没有提,有些得自己试。
下面总结下微信商户支付的开发,用处一般是用户从商户提现。
首先,要获取提现用户的openId,openId是用户对于公众号的唯一标识,openId是用户在公众 号中的唯一标识,一个openId只属于一个公众号。openId在用户关注公众号之类的活动中可以得到。
接下来按照微信的文档,准备需要提交的参数:
Map<String, Object> paras = new HashMap<>();
paras.put("mch_appid" , "*******");
......
然后是根据参数生成签名:
生成前要对参数根据参数名的ascii码顺序排序:
List<Map.Entry<String, Object>> list = new ArrayList<>(paras.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Object>>() {
public int compare(Map.Entry<String, Object> kv1, Map.Entry<String, Object> kv2) {
return kv1.getKey().compareTo(kv2.getKey());
}
});
生成签名:
private String getSign(List<Map.Entry<String, Object>> list) {
StringBuffer para = new StringBuffer();
if (!list.isEmpty()) {
for (Map.Entry<String, Object> one : list) {
String value = String.valueOf(one.getValue());
if (isNotEmpty(value)){
para.append("&")
.append(one.getKey())
.append("=")
.append(one.getValue());
}
}
para.deleteCharAt(0);
para.append("&key=" + WXConstants.SECRET_KEY);
}
return Md5Util.encodeUpper(para.toString());
}
注意:所有参数后需要加入秘钥,并且key一定要放在最后,秘钥在商户后台的API安全中设置
然后要以同样的顺序生成xml格式的参数,这个参数里要加入签名sign,key是不需要加进来的。
private String getWXPara(List<Map.Entry<String, Object>> list){
StringBuffer para = new StringBuffer();
para.append("<xml>");
for (Map.Entry<String, Object> one : list){
para.append(String.format("<%s>", one.getKey()))
.append(String.format("<![CDATA[%s]]>", one.getValue()))
.append(String.format("</%s>", one.getKey()));
}
para.append("</xml>");
return para.toString();
}
有了参数就可以通过微信提供的接口提交数据了,提交时是需要证书的,证书在商户平台API安全中下载。
带着证书发送:
public String post(String para, String pathCA, String key) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
log.info("wx.post, {} : request {} & para : {}\n", this.name, this.url, para);
StringEntity entity = new StringEntity(para, EscapeUtil.CHARSET_UTF8);
HttpPost post = new HttpPost(url);
post.setEntity(entity);
CloseableHttpClient client = getSSLClient(pathCA, key);
CloseableHttpResponse response = client.execute(post);
int httpCode = response.getStatusLine().getStatusCode();
byte[] bs = EntityUtils.toByteArray(response.getEntity());
String result = new String(bs, EscapeUtil.CHARSET_UTF8);
if (httpCode != 200){
log.warn("wx.post.httpCodeErr, {} : return {} & httpCode : {} & content : {} & para : {}\n", this.name, httpCode, result, this.url, para);
}
log.info("wx.post, {} : return {} & para : {}\n", this.name, result, para);
return content;
}
private CloseableHttpClient getSSLClient(final String pathCA, final String key) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
KeyStore keyStore = KeyStore.getInstance(WXConstants.CERT_TYPE);
FileInputStream instream = new FileInputStream(new File(pathCA));try {
keyStore.load(is, key.toCharArray());
} finally {
is.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, key.toCharArray())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { WXConstants.SSL_PROTOCOL },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
return httpclient;
}
如果人品还过得去,一般就可以了,不过还有一个小坑,微信文档上说返回成功的时候返回值中会有sign,不过实际上其实是没有的。。。