先吐槽下微信,完全就是个坑,文档确实有,但是个别细节没有提,有些得自己试。
下面总结下微信商户支付的开发,用处一般是用户从商户提现。
首先,要获取提现用户的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 : {} ", 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 : {} ", this.name, httpCode, result, this.url, para); } log.info("wx.post, {} : return {} & para : {} ", 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,不过实际上其实是没有的。。。