近日,在开发微信支付、微信退款、微信付款到零钱、微信订单查询、微信付款到银行卡的功能。以及其他微信API的使用。发现微信API的开发都是同样的套路~~封装参数–》包装成Xml—》提交到Api—》获取返回的内容就行了。因为会有多个API都设计到这些操作,特地抽取出来封装成工具类单独讲,学习完以下这5~8个放法、那么你的开发就很简便了 ~ ~ 一个支付方法简单的十五行代码立马搞定。希望对你们有帮助。个人浅薄的见解
一、微信签名的工具类 【温馨提示:】数字签名是一般开发人员容易遇到的错误,记住“你没遇到数字签名错误,都不好意思说自己做过微信开发”。
耐心解决就行 【Description】简单来解释,就是对自己要发送的数据进行加密处理、换句话说假如说你要传递A/B/C,就对这三者进行加密。初开发者的误区:例如看到别人代码拿D和E等去数字签名、然后在自己的开发中就拿D和E去签名,这是错误的做法,会出现数字签名错误。你要看一下你的开发需要传递D和F去后台,那么你应该拿D和F去签名就对了
package com.fh.util.weixin;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import com.fh.controller.app.nrb.bargain.wxconfig;
/**
* 签名工具类
* @author 小郑
* @date 2018年02月22日
* @Notice:wxconfig.apikey。这句代码是获取商务号设置的api秘钥。这里不方便贴出来,
* 复制签名代码的人,需要把该常量改成自己商务号的key值。原因是Api规定了签名必须加上自己的key值哦
* */
public class SignUtils {
/**
* @param characterEncoding 编码格式 utf-8
* */
public static String creatSign(String characterEncoding,
SortedMap parameters) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
//wxconfig.apikey。这句代码是获取商务号设置的api秘钥。这里不方便贴出来,
//复制签名代码的人,需要把该常量改成自己商务号的key值。原因是Api规定了签名必须加上自己的key值哦
sb.append("key=" + wxconfig.apikey);
String sign = MD5Utils.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
System.out.println(sign);
return sign;
}
}
二、将当前的map结合转化成xml格式 ~~ 微信请求参数是必须要封装成Xml格式的,在java中写xml很不方便,但是写Map集合很方便。故需要提供map转化xml的方法
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.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;
}
/*
* 将SortedMap 集合转化成 xml格式
*/
public static String getRequestXml(SortedMap parameters){
StringBuffer sb = new StringBuffer();
sb.append("");
Set es = parameters.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();
if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"");
}else {
sb.append("<"+k+">"+v+"");
}
}
sb.append("");
return sb.toString();
}
三、xml转化成map或者Bean 微信返回的信息是Xml格式、需要一个工具类把它解析成Map集合或者是一个Bean。方便我们去获取里面返回值。这里会介绍两种方式,一种是转化成Map集合,通过get出来使用,这个方法是通用的,第二种是转化成Bean、这种的话不通用。每个bean基本都不大一样,需要定制
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map.
Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
//不通用的、返回Bean格式
//以企业付款到零钱为例子~~根据Api会返回的参数,书写一个Bean类型
package com.fh.entity.nrb.xiaozheng;
/**
*
* 企业 付款到 客户 的 实体类
* @author xiaozheng
* @version 1.0
* @description: 收集企业 支付给客户成功后的返回信息
* @time : 2018-01-16 16:00:00
*/
public class EnterpriceToCustomer {
/*
*/
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 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。看个人的习惯呗~~我喜欢用bean存储数据的方式
* 解析企业支付申请
* 解析的时候自动去掉CDMA
* @param xml
*/
@SuppressWarnings("unchecked")
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 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());
}
//错误的编码
/*
private String err_code;
private String err_code_des;
* */
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;
}
四、Post请求+证书~~亲测可以的
package com.fh.entity.nrb.weixinResult;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
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 com.fh.controller.app.nrb.bargain.wxconfig;
/**
* This example demonstrates how to create secure connections with a custom SSL
* context.
*/
public class ClientCustomSSL {
@SuppressWarnings("deprecation")
public static String doRefund(String url,String data) throws Exception {
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
/**
*此处要改
*wxconfig.SSLCERT_PATH : 指向你的证书的绝对路径,带着证书去访问
*/
FileInputStream instream = new FileInputStream(new File(wxconfig.SSLCERT_PATH));//P12文件目录
try {
/**
* 此处要改
*
* 下载证书时的密码、默认密码是你的MCHID mch_id
* */
keyStore.load(instream, wxconfig.SSLCERT_PASSWORD.toCharArray());//这里写密码
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
/**
* 此处要改
* 下载证书时的密码、默认密码是你的MCHID mch_id
* */
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, wxconfig.SSLCERT_PASSWORD.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();
}
}
}