最近很久没有开始写博客了,主要换了一个城市工作,然后来到新的城市很多东西要做,外加工作上的不顺心.最近开始整理一下最近做到对接微信支付那点事吧.
前段时间做了微信支付H5的接口.相当坑爹.具体往下看吧.先把官方的网址贴出来.下面有什么变量不明白的地方自行看微信支付官网说明,我就不一一赘述了.当然也可以用我对接好的.直接拿来用就可以了.下面就让我开始吧.
官网API地址:微信支付API文档(H5)地址.
前言:由于微信这边需要会用到一个MD5加密.但是我对这方面不是很熟悉.这里MD5这方面我参考了该大佬的博客.大家可以去看看.(传送门)
开始:这里需要引入Apache的相关工具类.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
我根据官方文档做了一个基础的类.将必有的信息写进了一个基类去.后面的下单,退款,查询等的类直接继承该类即可.下面贴出该类代码.
import org.apache.commons.lang3.RandomUtils;
import sun.security.provider.MD5;
import java.util.*;
/**
* @Author: hr222
* @Date: 2019/5/27 10:07
* @Description: 对微信支付的进行一个拆分,将每个接口都需要用到的字段信息分进这个类种
*/
public class BaseWechatAO {
/**
* 因为生成签名的时候需要对参数按ASCII表顺序从大到小(字母序)排序对比,因此需要构建一个集合。
*/
protected Map<String, String> map = new HashMap<String, String>();
/**
* 公众帐号Id
*/
protected String appid;
/**
* 商户号Id
*/
protected String mch_id;
/**
* 随机字符串
*/
protected String nonce_str;
/**
* 签名
*/
protected String sign;
/**
* 商户订单号
*/
protected String out_trade_no;
public String getAppid() {
return appid;
}
/**
* 微信分配的appid。企业号corpid为此appid
*
* @param appid
*/
public void setAppid(String appid) {
this.appid = appid;
map.put("appid", appid);
}
public String getMch_id() {
return mch_id;
}
/**
* 微信支付分配的商户号
*
* @param mch_id
*/
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
map.put("mch_id", mch_id);
}
public String getNonce_str() {
return nonce_str;
}
/**
* 设置随机字符串,具体不能长于32位,参数设置请参考<a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3">官方文档</a>
*
* @param nonce_str
*/
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
map.put("nonce_str", nonce_str);
}
public String getSign() {
return sign;
}
/**
* 设置微信支付接口签名,由于签名需要按照ASCII码从小到大(字典序)进行。类中已经写好了。使用getSortSignByMD5()方法获取签名并赋值即可
*
* @param sign
*/
public void setSign(String sign) {
this.sign = sign;
map = null;
}
public String getOut_trade_no() {
return out_trade_no;
}
/**
* 说明见<a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2">官方文档</a>
* 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
*
* @param out_trade_no
*/
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
map.put("out_trade_no", out_trade_no);
}
/**
* 依据微信支付的随机字符串
*
* @return
*/
public String getTheOnceStr() {
char[] elements = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
String theNonceStr = "";
for (int i = 0; i < 32; i++) {
//发现这个比Math.random()*62好用
int index = RandomUtils.nextInt(0, 62);
theNonceStr += elements[index];
}
return theNonceStr;
}
/**
* 返回参数经过ASCII码排序后的签名
* 类中有个map生成完签名后设置为空,因此请在最后才开始设置签名,因为这个会和后面的转换成为XML形式的String有冲突所以只能设置为空。
*
* @param apiKey key为商户平台设置的密钥key
* @return
*/
public String getSortSignByMD5(String apiKey) throws Exception {
String sign = "";
if (map.size() == 0) {
throw new Exception("请先设置需要传输的参数在进行排序");
}
try {
//获取类中的Map属性信息,并将其转换为list对象
List<Map.Entry<String, String>> sortList = new ArrayList<>(map.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(sortList, new Comparator<Map.Entry<String, String>>() {
//这里需要说明一下ASCII排序,String中所实现的Comparable接口中的CompareTo方法对比正是采取他们两个字之间的ASCII码(字典序)
//对比则是比较两者中的字符的大小若是不同返回两者之间的差值若是一样则继续对比第二个,这里我们的做法也是一样的
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).compareTo(o2.getKey());
}
});
StringBuffer param = new StringBuffer();
for (Map.Entry<String, String> entry : sortList) {
if (entry.getKey() != null || entry.getKey() != "") {
String k = entry.getKey();
String v = entry.getValue();
if (!(v == "" || v == null)) {
param.append(k + "=" + v + "&");
}
}
}
sign = param.toString() + "key=" + apiKey;
map = null;
return MD5Util.md5Encode(sign).toUpperCase();
} catch (Exception e) {
e.printStackTrace();
}
return sign;
}
}
这个类包含了获取签名这个最麻烦的地方.不过正好JAVA底层对比的默认采取的是ASCII码对比所以还算是比较简单的.
这里由于支付的接口众多我这里就举例一个下单接口.剩余的可以看我上传的文件里面有很详细的讲解.下面我先贴出我的类对象,这里需要注意为了方便后面的发送和转换成为XML发送给微信那边我没有采用驼峰命名的方式.
/**
* @Author: hr222
* @Date: 2019/5/23 14:18
* @Description: 微信统一下单的参数对象, 具体可以参照<a href=" https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1">官网文档</a>,
* 这里仅列出当前项目所用到的字段和必须字段,若后面有需要可以到该类依照文档内容进行添加。这里需要满足微信支付接口的定义所以参数不是驼峰命名。
*/
public class NewOrderAO extends BaseWechatAO {
/**
* 商品描述
*/
private String body;
/**
* 支付总金额
*/
private int total_fee;
/**
* 终端Ip
*/
private String spbill_create_ip;
/**
* 附加通知,用于给回调地址信息
*/
private String attach;
/**
* 通知地址
*/
private String notify_url;
/**
* 通知类型
*/
private String trade_type;
/**
* 场景信息
*/
private String scene_info;
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
map.put("attach", attach);
}
public String getBody() {
return body;
}
/**
* 设置商品的简单描述,该字段须严格按照规范传递。具体参考<a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2">官方文档</a>
*
* @param body
*/
public void setBody(String body) {
this.body = body;
map.put("body", body);
}
public int getTotal_fee() {
return total_fee;
}
/**
* 设置支付总金额,单位为分
*
* @param total_fee
*/
public void setTotal_fee(int total_fee) {
this.total_fee = total_fee;
map.put("total_fee", total_fee + "");
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
/**
* 下单用户所在的终端的Ip地址,必须传正确的用户端IP,支持ipv4、ipv6格式。获取方式可以参考<a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_5">官方文档</a>
*
* @param spbill_create_ip
*/
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
map.put("spbill_create_ip", spbill_create_ip);
}
public String getNotify_url() {
return notify_url;
}
/**
* 通知地址,接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
*
* @param notify_url
*/
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
map.put("notify_url", notify_url);
}
public String getTrade_type() {
return trade_type;
}
/**
* 交易类型,web端使用的是MWEB,其余具体参数请参考<a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2">官方文档</a>
*
* @param trade_type
*/
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
map.put("trade_type", trade_type);
}
public String getScene_info() {
return scene_info;
}
/**
* 该字段用于上报支付的场景信息,针对H5支付有以下三种场景,请根据对应场景上报,H5支付不建议在APP端使用,针对场景1,2请接入APP支付,不然可能会出现兼容性问题。
* 该字符串必须是JSON数据类型的。例如:
* {"h5_info": {"type":"Wap","wap_url": "https://pay.qq.com","wap_name": "腾讯充值"}}
* @param scene_info
*/
public void setScene_info(String scene_info) {
this.scene_info = scene_info;
map.put("scene_info", scene_info);
}
}
这里我们已经完成了数据的组装.下面我需要appid,商户号,微信证书等等.为了方便后期更换商户号等我写如了properties文件中.下面贴出来
#############微信配置,相关信息可在官网中拿到###########
#公众帐号Id
wechat.appid=微信账号id
#商户号
wechat.mchId=商户号
#apiKey
wechat.apiKey=apikey
#同意下单地址
wechat.newOrderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder
#查询订单地址
wechat.queryOrderUrl=https://api.mch.weixin.qq.com/pay/orderquery
#关闭订单地址
wechat.closeOrderUrl=https://api.mch.weixin.qq.com/pay/closeorder
#退卡订单地址
wechat.refundOrderUrl=https://api.mch.weixin.qq.com/secapi/pay/refund
#查询退款订单地址
wechat.queryRefundOrderUrl=https://api.mch.weixin.qq.com/pay/refundquery
#微信新建订单的回调地址
wechat.notifyURLByNewOrder=你的回调地址必须是不包含参数的回调(除了request这类的对象)
#微信关闭订单的回调地址
wechat.notifyURLByRefundOrder=你的回调地址必须是不包含参数的回调(除了request这类的对象)
#微信证书
wechat.certPath=微信支付证书地址
下面完事具备,就差发送信息的httpclientUtil,和转换成为xml信息的工具类了.下面贴出我写好的工具类,这边需要引入的依赖有
<!-- HTTP工具 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
<!-- 微信支付需要发送到XML类型所使用的依赖 -->
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3_min</artifactId>
<version>1.1.4c</version>
</dependency>
<!-- 微信支付发送XML类型进行转换 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.9</version>
</dependency>
<!-- 用于将数据转换为Map -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1</version>
</dependency>
下面是我自己写的工具类,由于我这边做好了对象的转换所以我只打印了返回信息,有需要可以对该类进行改造.由于微信下单需要获取下单客户端的真实ip.这方面一开始用的request的后面发现会出问题,但是这种情况我第一次见到所以在获取IP方面我是搬运了一个大佬的博客.地址:大佬博客
下面是我的代码:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.thoughtworks.xstream.XStream;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.CharArrayBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URLEncoder;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.*;
/**
* @Author: hr222
* @Date: 2019/5/23 16:19
* @Description: 用于发送Http,Https请求,转换返回的信息,获取源IP的地址的工具类.已经转换成为相关的对象函数.具体可以参考Apache的http工具的API
*/
public class HttpClientUtil {
private static Logger log = LoggerFactory.getLogger(HttpClientUtil.class);
/**
* 采取单例模式
*/
private final static XStream XSTREAM = new XStream();
/**
* 定义默认编码
*/
private final static String CHARSET = "utf-8";
/**
* 用于获取IP地址中的未知值
*/
private final static String UNKNOWN = "unknown";
/**
* socket读写超时时间,单位毫秒
*/
private final static Integer SOCKET_TIME_OUT = 5000;
/**
* 设置接超时时间,单位毫秒
*/
private final static Integer CONNECT_TIME_OUT = 5000;
/**
* 设置请求超时时间,单位毫秒
*/
private final static Integer REQUEST_CONNECT_TIME = 5000;
/**
* 发送GET请求到目的URL,若有消息返回则打印消息并返回数据。没有返回null
*
* @param url 目标URL
* @param charset 编码,默认为UTF-8.
* @param data 需要发送到数据,若为空对象者则不发
*/
public static String sendGet(String url, String charset, Map<String, String> data) {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
if (data != null || data.size() != 0) {
StringBuffer pram = new StringBuffer();
//获取数据将MAP中的数据转换为String类型
Set<String> key = data.keySet();
try {
for (String k : key) {
pram.append(k + "=" + URLEncoder.encode(data.get(k), charset == null ? CHARSET : charset));
pram.append("&");
}
pram.deleteCharAt(pram.length() - 1);
url += "?" + pram.toString();
} catch (UnsupportedEncodingException e1) {
log.error(e1.getMessage());
}
//建立连接
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = null;
try {
// 配置信息
RequestConfig requestConfig = RequestConfig.custom()
// 设置连接超时时间(单位毫秒)
.setConnectTimeout(CONNECT_TIME_OUT)
// 设置请求超时时间(单位毫秒)
.setConnectionRequestTimeout(REQUEST_CONNECT_TIME)
// socket读写超时时间(单位毫秒)
.setSocketTimeout(SOCKET_TIME_OUT)
// 设置是否允许重定向(默认为true)
.setRedirectsEnabled(true).build();
// 将上面的配置信息 运用到这个Get请求里
httpGet.setConfig(requestConfig);
// 由客户端执行(发送)Get请求
response = httpClient.execute(httpGet);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
log.info("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
return printTheMsg(responseEntity, charset);
}
} catch (ClientProtocolException e) {
log.error(e.getMessage());
} catch (ParseException e) {
log.error(e.getMessage());
} catch (IOException e) {
log.error(e.getMessage());
} finally {
close(httpClient, response);
}
}
return null;
}
/**
* 发送POST请求到目的URL,假如有返回数据,则将数据打印并返回数据,没有则返回null
*
* @param url 发送目标地址
* @param type 发送到数据类型,如text/xml,application/json等
* @param charset 编码,默认UTF-8.
* @param data 需要发送到数据,若没有则发送空数据请求
*/
public static String sendPost(String url, String type, String charset, String data) throws Exception {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
if (type == null || type.equals("")) {
throw new Exception("请检查参数");
}
//建立链接
HttpPost httpPost = new HttpPost(url);
if (data != null) {
//设置发送参数类型
String contentType = charset == null || charset.equals("") ? type : type + ";charset=" + charset;
//设置发送数据的信息和编码
StringEntity entity = charset == null || charset.equals("") ? new StringEntity(data, CHARSET) : new StringEntity(data, charset);
httpPost.setEntity(entity);
httpPost.setHeader("Content-Type", contentType);
}
// 响应模型
CloseableHttpResponse response = null;
try {
// 由客户端执行(发送)Post请求
response = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
log.info("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
return printTheMsg(responseEntity, charset);
}
} catch (Exception e) {
log.error(e.getMessage());
} finally {
close(httpClient, response);
}
return null;
}
/**
* 发送POST请求到目的URL,假如有返回数据,则将数据打印并返回数据,没有则返回null
* 注:这个是发送带证书的那种SSL,微信后面有用到.
*
* @param url 发送目标地址
* @param type 发送到数据类型,如text/xml,application/json等
* @param charset 编码,默认UTF-8.
* @param data 需要发送到数据,若没有则发送空数据请求
* @param certPath SSL证书的路径
* @param certPassord SSL证书密码
*/
public static String sendPost(String url, String type, String charset, String data, String certPath, String certPassord) throws Exception {
if (type == null || type.equals("")) {
throw new Exception("请检查参数");
}
//获取keystore实例
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//加载本地的证书进行https加密传输
FileInputStream instream = new FileInputStream(new File(certPath));
try {
//加载证书密码,使用的是商户ID
keyStore.load(instream, certPassord.toCharArray());
} catch (CertificateException e) {
log.error(e.getMessage());
} catch (NoSuchAlgorithmException e) {
log.error(e.getMessage());
} finally {
instream.close();
}
//配置SSL链接
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, certPassord.toCharArray()).build();
// 只允许TLSv1
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
//根据默认超时限制初始化requestConfig
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SOCKET_TIME_OUT).setConnectTimeout(CONNECT_TIME_OUT).build();
//建立链接
HttpPost httpPost = new HttpPost(url);
if (data != null) {
//设置发送参数类型
String contentType = charset == null || charset.equals("") ? type : type + ";charset=" + charset;
//设置发送信息
StringEntity entity = charset == null || charset.equals("") ? new StringEntity(data, CHARSET) : new StringEntity(data, charset);
httpPost.setEntity(entity);
//设置请求器的配置
httpPost.setConfig(requestConfig);
httpPost.setHeader("Content-Type", contentType);
}
// 响应模型
CloseableHttpResponse response = null;
try {
// 由客户端执行(发送)Post请求
response = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
log.info("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
return printTheMsg(responseEntity, charset);
}
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
} finally {
close(httpClient, response);
}
return null;
}
/**
* 发送以表单对象的POST请求到目的URL,假如有返回数据,则将数据打印并返回数据,没有则返回null
*
* @param url 发送目标地址
* @param type 发送到数据类型,如text/xml,application/json等
* @param charset 编码,默认UTF-8.
* @param data 需要发送到数据,若没有则发送空数据请求
*/
public static String sendPost(String url, String type, String charset, Map<String, String> data) throws Exception {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
if (type == null || type.equals("")) {
throw new Exception("请检查参数");
}
//建立链接
HttpPost httpPost = new HttpPost(url);
if (data != null) {
//设置发送参数类型
String contentType = charset == null || charset.equals("") ? type : type + ";charset=" + charset;
//设置发送体的编码
String chartSet = charset == null || charset.equals("") ? "UTF-8" : charset;
//表单数据.这个是因为和同事对调接口的时候发现怎么弄他那边都接收不到数据map对象数据是空
//后面查阅后知道需要这样, 类型是application/x-www-form-urlencoded
List<NameValuePair> list = new ArrayList<>();
for (Map.Entry<String, String> entry : data.entrySet()) {
list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, chartSet);
httpPost.setEntity(urlEncodedFormEntity);
httpPost.setHeader("Content-Type", contentType);
}
// 响应模型
CloseableHttpResponse response = null;
try {
// 由客户端执行(发送)Post请求
response = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
log.info("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
return printTheMsg(responseEntity, charset);
}
} catch (Exception e) {
log.error(e.getMessage());
} finally {
close(httpClient, response);
}
return null;
}
/**
* 打印请求发送了请求后的返回信息,并返回消息体
*
* @param responseEntity 请求返回体
* @param charset 编码形式
* @return
* @throws IOException
*/
private static String printTheMsg(HttpEntity responseEntity, String charset) throws IOException {
log.info("响应内容长度为:" + responseEntity.getContentLength());
Reader reader = new InputStreamReader(responseEntity.getContent(), charset == null || charset.equals("") ? CHARSET : charset);
CharArrayBuffer buffer = new CharArrayBuffer((int) responseEntity.getContentLength());
final char[] tmp = new char[1024];
int line;
while ((line = reader.read(tmp)) != -1) {
buffer.append(tmp, 0, line);
}
log.info("响应内容为:" + buffer.toString());
return buffer.toString();
}
/**
* 释放资源
*
* @param response 返回的响应对象
*/
private static void close(CloseableHttpClient httpClient, CloseableHttpResponse response) {
try {
httpClient.close();
if (response != null) {
response.close();
}
} catch (IOException e) {
log.error(e.getMessage());
}
}
/**
* 将收到的对象类型转换成Json字符串
*
* @param tClass
* @param <T>
* @return
*/
public static <T> String parseToJsonString(T tClass) {
if (tClass instanceof org.json.JSONObject) {
return tClass.toString();
}
return JSON.toJSONString(tClass);
}
/**
* 将收到的Json字符串换成对象类型转
*
* @param tClass
* @param <T>
* @return
*/
public static <T> T parseToObjectByJSON(Class<T> tClass, String jsonString) {
JSON json = JSONObject.parseObject(jsonString);
return JSON.toJavaObject(json, tClass);
}
/**
* 将获取到的对象类,转换成XML形式字符串
*
* @param tClass
* @param name 顶级标签的名称
* @param <T>
* @return
*/
public static <T> String parseToXMLString(T tClass, String name) {
XSTREAM.alias("xml", tClass.getClass());
String xml = XSTREAM.toXML(tClass);
return xml;
}
/**
* 将XML字符串转换成为传入的对象
*
* @param clazz
* @param xml
* @param <T>
* @return 若没有报错则返回转换后的对象,若报错返回null
*/
public static <T> T parseToObjectByXML(Class<T> clazz, String xml) {
//由于接受到的数据是以XML为父标签的,由于转换不知道要转换成为什么类,因此需要将父标签转换成为类的对应路径(绝对路径)
xml = xml.replaceAll("xml", clazz.getCanonicalName());
//由于接收到的数据可能带有CDATA标签这里把这些都去掉
//xml = xml.replaceAll("<!\\u005BCDATA\\u005B","").replaceAll("]]>","");
T object = (T) XSTREAM.fromXML(xml);
return object;
}
/**
* 通过HttpServetRequest中的信息流转换数据
* 注:微信返回来需要通过request中的获取io对象读取里面的信息,转换为XML形式的字符串
*
* @param inputStream
* @return
*/
public static StringBuffer parseDataByNetInputStream(InputStream inputStream) throws IOException {
StringBuffer stringBuffer = new StringBuffer();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String data;
while ((data = in.readLine()) != null) {
stringBuffer.append(data);
}
in.close();
inputStream.close();
return stringBuffer;
}
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* 注:转换XML信息变换成MAP属性.当然也可以用class.不过涉及反射.我一开始做了后面发现不是很好用,故放弃
*
* @param xmlString xml格式的字符串
* @return
* @throws JDOMException
* @throws IOException
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static Map parseMapByXMLString(String xmlString) throws Exception {
xmlString = xmlString.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == xmlString || "".equals(xmlString)) {
return null;
}
Map<String, String> map = new HashMap<>(16);
InputStream in = new ByteArrayInputStream(xmlString.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v;
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
map.put(k, v);
}
// 关闭流
in.close();
return map;
}
/**
* 获取子结点的xml
*
* @param children
* @return String
*/
@SuppressWarnings({"rawtypes"})
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* 获取请求源的Ip地址,获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址,如果没有代理,则获取真实ip
* 注:该方法搬运自<a href="https://www.cnblogs.com/chinaifae/p/10189012.html">该博客</a>
*
* @param request
* @return
*/
public static String getSourceIpAddress(HttpServletRequest request) {
//代理进来,则透过防火墙获取真实IP地址
String ip = request.getHeader("X-Forwarded-For");
//以下是判断用户是否开启了代理模式
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
//如果没有代理,则获取真实ip
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}
}
这边具体做好后实际上我们就能通过Main方法跑一趟了.这边我先弄个小样子吧.按照惯例我先将微信统一下单的之后返回的对象贴出来方便参考.同样的我将基本返回对象抽出来单独做了一个基础bean.下面就贴上来吧
/**
* @Author: hr222
* @Date: 2019/5/27 10:29
* @Description: 微信支付查询类接口的返回基础字段信息,在接口返回中的字段
* return_code为SUCCESS的时候有返回。
*/
public class BaseReturnWechatAO {
/**
* 返回状态码
*/
protected String return_code;
/**
* 返回信息
*/
protected String return_msg;
/**
* 公众账号ID
*/
protected String appid;
/**
* 商户号
*/
protected String mch_id;
/**
* 随机字符串
*/
protected String nonce_str;
/**
* 签名
*/
protected String sign;
/**
* 业务结果
*/
protected String result_code;
/**
* 业务结果
*/
protected String result_msg;
/**
* 错误代码
*/
protected String err_code;
/**
* 错误代码描述
*/
protected String err_code_des;
/**
* 坑爹的隐藏字段
*/
private String sub_mch_id;
/**
* 当return_code为FAIL时返回信息为错误原因 ,例如
* <p>
* 签名失败
* <p>
* 参数格式校验错误
*
* @return
*/
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
/**
* 调用接口提交的公众账号ID
*
* @return
*/
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
/**
* 调用接口提交的终端设备号
*
* @return
*/
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
/**
* 微信返回的签名
*
* @return
*/
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
/**
* SUCCESS/FAIL
*
* @return
*/
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
/**
* 详细参见
* <a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1">错误列表</a>
*
* @return
*/
public String getErr_code() {
return err_code;
}
public void setErr_code(String err_code) {
this.err_code = err_code;
}
/**
* 错误返回的
* <a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1">信息描述</a>
*
* @return
*/
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String err_code_des) {
this.err_code_des = err_code_des;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
/**
* SUCCESS/FAIL
* <p>
* 此字段是通信标识,非交易标识,交易是否成功需要查看trade_state来判断
*
* @return
*/
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
/**
* 对业务结果的补充说明
* @return
*/
public String getResult_msg() {
return result_msg;
}
public void setResult_msg(String result_msg) {
this.result_msg = result_msg;
}
}
这个是下单的返回字段信息:
/**
* @Author: hr222
* @Date: 2019/5/25 16:54
* @Description: 统一下单后返回的信息对象,详情参考<a href="https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1">官方文档</a>,
* 由于需要满足微信支付接口参数的定义因此这里不能按照驼峰命名的规则,这里只添加必填类型,其余选填类型可以自行添加
*/
public class ReturnNewOrderAO extends BaseReturnWechatAO {
/**
* 设备号
*/
private String device_info;
/**
* 交易类型
*/
private String trade_type;
/**
* 预支付交易会话标识
*/
private String prepay_id;
/**
* 支付跳转链接
*/
private String mweb_url;
/**
* 订单号
*/
private String orderNum;
///以下字段在return_code为SUCCESS的时候有返回/
/**
* 调用接口提交的商户号
*
* @return
*/
public String getDevice_info() {
return device_info;
}
public void setDevice_info(String device_info) {
this.device_info = device_info;
}
///以下字段在return_code 和result_code都为SUCCESS的时候有返回///
/**
* 调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP,,H5支付固定传MWEB
*
* @return
*/
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
/**
* 微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时,针对H5支付此参数无特殊用途
*
* @return
*/
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
/**
* mweb_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,mweb_url的有效期为5分钟。
*
* @return
*/
public String getMweb_url() {
return mweb_url;
}
public void setMweb_url(String mweb_url) {
this.mweb_url = mweb_url;
}
public String getOrderNum() {
return orderNum;
}
public void setOrderNum(String orderNum) {
this.orderNum = orderNum;
}
}
好了现在就可以开始了.这里我弄成main方法.没有上接口啥的.有什么需要可以进行改造.
public static void main(String[] args) {
//这里微信API内规定发送请求必须以XML形式发送,这个请求类型这里设置成为XML,若后期修改了的话可以将这个提取出来进行优化
String contentType = "text/xml";
//包装设备网站信息
JSONObject json = new JSONObject();
json.put("type", "Wap");
json.put("wap_url", "https://pay.qq.com");
json.put("wap_name", "微信支付");
JSONObject scenceInfo = new JSONObject();
scenceInfo.put("h5_info", json);
//注:获取订单金额,若是金额是元,带小数点的那种,这里需要转换成为分单位,所以要乘100
Integer money = 100;
//以下是针对信息订单的相关信息设置,之后在统一转换成为XML形式,因为这里转换的时候如有下横杠会变成两个下横岗,垃圾的微信接口不认。
// 所以这里用只能替代成为一个
//注:参数值用XML转义即可,CDATA标签用于说明数据不被XML解析器解析。
String newOrderAO = initOrder("127.0.0.1", "1234567", money, "你的回调地址,没有不填", scenceInfo);
newOrderAO = newOrderAO.replaceAll("__", "_");
String returnNewOrderAO = HttpClientUtil.sendPost(newOrderUrl, contentType, null, newOrderAO);
ReturnNewOrderAO returnAO = HttpClientUtil.parseToObjectByXML(ReturnNewOrderAO.class, returnNewOrderAO);
system.out.print(returnAO);
}
private String initOrder(String ip, String orderNum, int totalMoney, String notifyUrl, JSONObject scenceInfo) throws Exception {
NewOrderAO newOrderAO = new NewOrderAO();
newOrderAO.setSpbill_create_ip(ip);
newOrderAO.setMch_id("你的商户号");
newOrderAO.setAppid("你的公众账号id");
newOrderAO.setNonce_str(newOrderAO.getTheOnceStr());
newOrderAO.setAttach(orderNum);
newOrderAO.setBody("微信支付-订单支付");
newOrderAO.setOut_trade_no(orderNum);
newOrderAO.setTotal_fee(totalMoney);
newOrderAO.setNotify_url(notifyUrl);
newOrderAO.setTrade_type("MWEB");
newOrderAO.setScene_info(scenceInfo.toString());
newOrderAO.setSign(newOrderAO.getSortSignByMD5("你的apikey"));
return HttpClientUtil.parseToXMLString(newOrderAO, "xml");
}
这里就大共告成了下面我整理我弄好的.打包好上来.