配置文件
package com.nroad.config;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
/**
* WeixinPayConfig
* Created by Administrator on 2017/5/4.
* 微信支付配置文件
*/
@Component
public class WeixinPayConfig {
private static Log log = LogFactory.getLog(WeixinPayConfig.class);
private static Configuration configs;
static {
WeixinPayConfig.init("weixinpayinfo.properties");
}
public static String appid; //微信支付分配的公众账号ID(企业号corpid即为此appId)
public static String mch_id; //微信支付分配的商户号
public static String device_info; //设备号,PC网页或公众号内支付可以传"WEB"
public static String post_url; //请求路径
public static String query_url; //查询路径
public static String appsecret;
public static String mchsecret; //签名时使用的key
private WeixinPayConfig() {
}
private static synchronized void init(String filePath) {
if (configs == null) {
try {
configs = new PropertiesConfiguration(filePath);
} catch (ConfigurationException var2) {
var2.printStackTrace();
}
}
if (configs == null) {
throw new IllegalStateException("can`t find file by path:" + filePath);
} else {
appid = configs.getString("appid");
mch_id = configs.getString("mch_id");
device_info = configs.getString("device_info");
post_url = configs.getString("post_url");
query_url = configs.getString("query_url");
appsecret = configs.getString("appsecret");
mchsecret = configs.getString("mchsecret");
log.info("微信支付配置如下: ");
log.info("配置文件名: " + filePath);
log.info(description());
}
}
private static String description() {
StringBuilder sb = new StringBuilder("Configs{");
sb.append("微信公众账号ID: ").append(appid).append("\n");
sb.append("微信商户号: ").append(mch_id).append("\n");
sb.append("微信设备号: ").append(device_info).append("\n");
sb.append("请求路径: ").append(post_url).append("\n");
sb.append("查询路径: ").append(query_url).append("\n");
sb.append("支付秘钥: ").append(mchsecret).append("\n");
sb.append("}");
return sb.toString();
}
微信支付业务流程
package com.nroad.service.wechat.pay;
import com.nroad.config.WeixinPayConfig;
import com.nroad.dto.OrderDto;
import com.nroad.exception.PayException;
import com.nroad.utils.MathUtil;
import com.nroad.utils.OrderUtil;
import com.nroad.utils.PayCommonUtil;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Created by Administrator on 2017/5/4.
*/
@Component
public class WeChat {
/**
* 获取微信支付二维码
* @return
*/
public String trade_precreate(String cip, OrderDto orderDto,String serverName){
//获取二维码生存周期
String[] QRTime = OrderUtil.QRTime();
//生成随机字符串
String nonce_str = OrderUtil.nonce();
//参数收集
SortedMap<Object,Object> params=new TreeMap<Object, Object>();
params.put("appid", WeixinPayConfig.appid); //公众账号ID
params.put("mch_id", WeixinPayConfig.mch_id); //商户号
params.put("device_info", WeixinPayConfig.device_info); //设备号
params.put("nonce_str", nonce_str); //随机字符串
params.put("out_trade_no",orderDto.getOrderNo()); //商户订单号
params.put("body",orderDto.getSubject()); //商品描述
params.put("detail",orderDto.getBody()); //商品详情
params.put("total_fee", MathUtil.movePointRight_s(orderDto.getTotalMoney(),2)); //订单总金额
params.put("spbill_create_ip",cip); //用户端ip
params.put("time_start",QRTime[0]); //订单生成时间
params.put("time_expire",QRTime[1]); //订单失效时间
params.put("notify_url","http://"+serverName+":8100/weChat_notify"); //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
params.put("trade_type","NATIVE"); //交易类型---JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
//生成签名
String sign = PayCommonUtil.generateSign("UTF-8", params, WeixinPayConfig.mchsecret);
params.put("sign",sign); //添加签名信息
//生成xml格式的String
String requestXml = PayCommonUtil.generateRequestXml(params);
//将xml格式数据发送给微信第三方,通知获取到其反馈信息(也为xml格式)
String resXml = null;
try {
resXml = PayCommonUtil.postData(WeixinPayConfig.post_url, requestXml);
} catch (PayException e) {
e.printStackTrace();
throw new PayException("微信支付异常");
}
//将xml解析为map
Map map = PayCommonUtil.doXMLParse(resXml);
return (String) map.get("code_url");
}
/**
* 支付结果结果查询
*/
public String resultQuery(WeChatQuery weChatQuery){
SortedMap<Object, Object> requestMap = weChatQuery.getMap();
requestMap.put("nonce_str",OrderUtil.nonce());
String sign = PayCommonUtil.generateSign("UTF-8", requestMap, WeixinPayConfig.mchsecret);
requestMap.put("sign",sign);
// 生成xml格式的String
String requestXml = PayCommonUtil.generateRequestXml(requestMap);
//将xml格式数据发送给微信第三方,通知获取到其反馈信息(也为xml格式)
String resXml = PayCommonUtil.postData(WeixinPayConfig.query_url, requestXml);
//将xml解析为map
Map map = PayCommonUtil.doXMLParse(resXml);
String result = (String) map.get("trade_state");
System.out.println("微信支付 : " + result);
return result;
}
}
需要使用的工具类
package com.nroad.utils;
import java.time.LocalDateTime;
/**
* Created by Administrator on 2017/5/4.
* 订单工具
*/
public class OrderUtil {
public final static String TIME_REGEX = "[-:T.]";
/**
* 生成订单号
* @return
*/
public static String nonce(){
return String.valueOf(System.currentTimeMillis()
+ (long) (Math.random() * 10000000L));
}
/**
* 生成商户订单号
* @return
*/
public static String merchantNo(){
String now = LocalDateTime.now().toString();
return now.replaceAll(TIME_REGEX, "")+"Lb"+(long)(Math.random()*1000000000000L);
}
/**
* 获取交易起始时间和失效时间
* 暂时默认为10分钟
*/
public static String[] QRTime(){
LocalDateTime startTime = LocalDateTime.now();
LocalDateTime endTime = startTime.plusMinutes(30L);
String start = startTime.toString().split("[.]")[0];
String end = endTime.toString().split("[.]")[0];
return new String[]{start.replaceAll(TIME_REGEX, ""), end.replaceAll(TIME_REGEX, "")};
}
}
package com.nroad.utils;
import org.apache.commons.lang.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
/**
* Created by Administrator on 2017/5/5.
* 支付相关工具
*/
public class PayCommonUtil {
private final static int CONNECT_TIMEOUT = 5000;
private final static String DEFAULT_ENCODING = "UTF-8";
/**
* 生成签名
*
* @param characterEncoding
* @param packageParams
* @param API_KEY
* @return
*/
public static String generateSign(String characterEncoding, Map<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set<Map.Entry<Object, Object>> entries = packageParams.entrySet();
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
//第一步,生成签名字符串
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) {
sb.append(key + "=" + value + "&");
}
}
sb.append("key=" + API_KEY);
//通过MD5算法转换 并将其全部转化成大写
String sign = MD5.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* 将请求参数转换为xml格式的string
*
* @param parameters
* @return
*/
public static String generateRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set<Map.Entry<Object, Object>> entries = parameters.entrySet();
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry next = iterator.next();
String key = (String) next.getKey();
String value = (String) next.getValue();
if ("".equals(StringUtils.trimToEmpty(value))){
continue;
}
if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {
sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
} else {
sb.append("<" + key + ">" + value + "</" + key + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* 将xml数据转换成为 Map类型
*
* @param strxml
* @return
*/
public static Map doXMLParse(String strxml) {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in =null;
try {
in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Element e = (Element) iterator.next();
String name = e.getName();
String value = "";
List children = e.getChildren();
//如果存在子节点,继续遍历
if (children.isEmpty()) {
value = e.getTextNormalize();
} else {
value = PayCommonUtil.getChildrenText(children);
}
m.put(name, value);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (in !=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return m;
}
/**
* 获取子节点的xml
* @param children
* @return
*/
public static String getChildrenText(List children){
StringBuffer sb =new StringBuffer();
//子节点不为空,迭代子节点
if (!children.isEmpty()){
Iterator iterator = children.iterator();
while (iterator.hasNext()){
Element element = (Element) iterator.next();
String name = element.getName();
String value = element.getTextNormalize();
List list = element.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()){
sb.append(PayCommonUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name +">");
}
}
return sb.toString();
}
/**
* 请求数据
* @param url
* @param requestXML
* @return
*/
public static String postData(String url, String requestXML) {
return postData(url, requestXML, null);
}
/**
* 请求数据,如果成功,获得的是一个xml数据
* @param strUrl
* @param data
* @param conentType
* @return
*/
private static String postData(String strUrl, String data, String conentType) {
BufferedReader reader = null;
try {
URL url = new URL(strUrl);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
if (conentType != null) {
conn.setRequestProperty("content-type", conentType);
}
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if (data == null) {
data = "";
}
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
StringBuffer sb = new StringBuffer();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null)
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public static boolean isTenpaySign(String characterEncoding , SortedMap<Object, Object> packageParams, String API_KEY){
StringBuffer sb = new StringBuffer();
Set<Map.Entry<Object, Object>> entries = packageParams.entrySet();
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry) iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (!"sign".equalsIgnoreCase(key) && null != value && !"".equals(value)){
sb.append(key + "=" + value +"&");
}
}
sb.append("key=" + API_KEY);
//算出摘要
String mysign = MD5.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();
System.out.println("mysign : "+mysign);
System.out.println("tenpaySign : "+tenpaySign);
return tenpaySign.equals(mysign);
}
}
异步通知
@RequestMapping("/weChat_notify")
public void weChat_notify(HttpServletRequest request, HttpServletResponse response) {
log.info("微信异步通知开始");
//读取参数
InputStream inputStream = null;
StringBuffer sb = new StringBuffer();
BufferedReader bufferedReader = null;
try {
log.info("获取返回参数");
String s = "";
inputStream = request.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = bufferedReader.readLine()) != null) {
sb.append(s);
}
} catch (IOException e) {
log.info("返回参数获取失败");
e.printStackTrace();
} finally {
try {
if (null != bufferedReader)
bufferedReader.close();
if (null != inputStream)
inputStream.close();
} catch (IOException e) {
log.info("io流关闭失败");
e.printStackTrace();
}
}
log.info("开始解析xml参数");
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
Map map = PayCommonUtil.doXMLParse(sb.toString());
//过滤空 设置 TreeMap
log.info("开始过滤空值");
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String parameter = (String) iterator.next();
String paramterValue = (String) map.get(parameter);
String value = "";
//去空格,将NULL 和 "" 转换为""
value = StringUtils.trimToEmpty(paramterValue);
packageParams.put(parameter, value);
}
//判断签名是否正确
log.info("签名验证开始");
if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, WeixinPayConfig.mchsecret)) {
log.info("签名验证成功");
//------------------------------
//处理业务开始
//------------------------------
String resXml = "";
if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
//这里是支付成功
///////////////////开始自己的业务///////////////////////
String out_trade_no = (String) packageParams.get("out_trade_no");
save(out_trade_no,"微信");
///////////////////开始自己的业务///////////////////////
log.info("支付成功");
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
log.info("支付失败,错误信息:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
//------------------------------
//处理业务完毕
//------------------------------
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
log.info("通知签名验证失败");
}
}