一、前言
这次的项目主要是关于微信公众号的一个开发,本人这次分配的模块是后台微信公众号的支付和退款,第一次接触微信公众的项目刚开始一脸懵逼,开发过程中遇到各种坑,所以想自己写一篇详细的关于微信公众号的开发,希望能对小伙伴们有所帮助!
二、统一支付接口
支付开发文档:微信公众支付开发文档
支付流程:首先由前端调用后台的统一支付接口,通过统一支付接口拿到返回参数,主要是要拿到前端调用支付时的prepay_id,将统一支付接口返回的参数进行签名、封装之后返回给前端,前端通过H5或者JSSDK调用微信支付功能,调用成功后微信会根据统一支付接口配置的通知URL来调用开发中的通知接口,同时微信端会将数据发送到通知接口来,然后在通知接口中做其他业务的处理就可以了,处理成功给微信端返回成功状态码,失败返回失败状态码。
统一下单接口代码:
/**
*
* @Title: wxPay
* @Description: 微信统一支付
* @param request
* @param response
* @throws Exception
* @return Map<String,String>
*/
@RequestMapping("/wxPay")
@ResponseBody
public JsPayResult wxPay(HttpServletRequest request, HttpServletResponse response) throws Exception {
String usid = request.getParameter("usid");
Account ac = userCache.get(usid);
// 交易类型
String trade_type = "JSAPI";
// 用户标识
String openid = ac.getOpenid();
// 公众账号ID
String appid = Constants.APPID;
// 商户号
String mch_id = Constants.MCHID;
// 通知地址
String notify_url = Constants.PAY_NOTIFY_URL;
// 随机字符串
String nonce_str = CommonUtil.getRandomStr();
// 商品描述
String body = request.getParameter("body");
//String body = "测试";
// 终端IP
String spbill_create_ip = request.getRemoteAddr();
// 商户订单号
String out_trade_no = request.getParameter("out_trade_no");
//String out_trade_no = "111";
// 金额
String total_fee = CommonUtil.getMoney(request.getParameter("total_fee"));
//String total_fee = CommonUtil.getMoney("0.01");
//String total_fee = "1";
// 将请求参数封装至Map集合中
SortedMap<String, String> paramMap = new TreeMap<String, String>();
paramMap.put("appid", appid);
paramMap.put("mch_id", mch_id);
paramMap.put("nonce_str", nonce_str);
paramMap.put("body", body);
paramMap.put("out_trade_no", out_trade_no);
paramMap.put("total_fee", total_fee);
paramMap.put("spbill_create_ip", spbill_create_ip);
paramMap.put("notify_url", notify_url);
paramMap.put("trade_type", trade_type);
paramMap.put("openid", openid);
logger.info("支付IP" + spbill_create_ip);
// 签名
String sign = SignUtil.createSign(paramMap, Constants.PARTNER_KEY);
paramMap.put("sign", sign);
// 请求的xml数据
String requestXml = XMLUtil.map2Xml(paramMap,"xml");
// 调用post请求,同时返回的xml数据
String resposeXmL = CommonUtil.weChatPayhttpsRequest(Constants.ORDER_PAY_URL, Constants.POST, requestXml);
Map<String, String> responseMap = XMLUtil.xml2Map(resposeXmL);
SortedMap<String, String> rspMap = new TreeMap<String, String>();
JsPayResult result = new JsPayResult();
if (Constants.RETURN_CODE.equals(responseMap.get("return_code"))) {
String nonceStr = CommonUtil.getRandomStr();
String timeStamp = CommonUtil.createTimeStamp();
result.setAppId(responseMap.get("appid"));
result.setTimeStamp(timeStamp);
result.setNonceStr(nonceStr);
result.setSignType("MD5");
rspMap.put("appId", responseMap.get("appid"));
rspMap.put("timeStamp",timeStamp);
rspMap.put("nonceStr",nonceStr );
rspMap.put("signType", "MD5");
if (Constants.RESULT_CODE.equals(responseMap.get("result_code"))) {
result.setPackaged("prepay_id=" + responseMap.get("prepay_id"));
rspMap.put("package", result.getPackaged());
String paySign = SignUtil.createSign(rspMap, Constants.PARTNER_KEY);
result.setPaySign(paySign);
result.setResultCode(Constants.SUCCESS_CODE);
result.setMessage("支付成功!");
}
logger.info("*****统一支付:支付成功!*****"+responseMap.get("return_code")+"--"+responseMap.get("return_msg"));
} else {
result.setResultCode(Constants.FAIL_CODE);
result.setMessage("签名失败!");
logger.info("*****统一支付:签名失败!*****"+responseMap.get("return_code")+"--"+responseMap.get("return_msg"));
}
return result;
}
微信支付请求:
/**
*
* @Title: weChatPayhttpsRequest @Description: 微信支付请求 @param @param
* requestUrl @param @param requestMethod @param @param
* outputStr @param @return @return String @throws
*/
public static String weChatPayhttpsRequest(String requestUrl, String requestMethod, String outputStr) {
StringBuffer buffer = new StringBuffer();
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod)) {
httpUrlConn.connect();
}
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
} catch (ConnectException ce) {
ce.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
签名方法:
/**
* 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
*/
public static String createSign(SortedMap<String, String> packageParams,String key) {
StringBuffer sb = new StringBuffer();
Set<Entry<String,String>> es = packageParams.entrySet();
Iterator<Entry<String, String>> it = es.iterator();
while (it.hasNext()) {
Map.Entry<String,String> entry = it.next();
String pName = entry.getKey();
String pValue = entry.getValue();
if (StringUtils.isNotBlank(pValue) && !"sign".equals(pName)&& !"key".equals(pName)) {
sb.append(pName).append("=").append(pValue).append("&");
}
}
sb.append("key=" + key);
String sign = MD5Util.MD5Encode(sb.toString(), "utf-8").toUpperCase();
return sign;
}
将返回的xml转换成map:
public static Map<String, String> xml2Map(String xml) {
// 将解析结果存储在HashMap中
HashMap<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
InputStream xmlIs;
Document document = null;
try {
xmlIs = new ByteArrayInputStream(xml.getBytes("utf-8"));
document = reader.read(xmlIs);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 得到xml根元素
Element root = document.getRootElement();
recursiveParseXML(root,map);
return map;
}
三、支付结果通知接口
支付结果通知接口是是统一支付接口中配置的,主要是为了获取微信端的交易订单号,同时在支付结果通知中处理其他的业务逻辑,处理完成后需要给微信端返回成功码,如果返回失败码微信端会一直发送微信支付结果通知,发送次数达到一定数量后就停止发送。
支付结果通知接口:
/**
*
* @Title: notify
* @Description: 微信支付结果通知
* @param @param request
* @param @param response
* @param @return
* @param @throws Exception
* @return Map<String,String>
* @throws
*/
@ResponseBody
@RequestMapping("/notify")
public JsPayResult notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
JsPayResult result = new JsPayResult();
logger.info("开始处理支付返回的请求");
// 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
Map<String, String> xmlMap = CommonUtil.parseXmlForPay(request);
logger.info("微信支付系统发送的数据" + xmlMap);
// 返回给微信服务器的xml
String respXml = "";
// 判断返回是否成功
if (Constants.RETURN_CODE.equals(xmlMap.get("return_code"))) {
String time_end = xmlMap.get("time_end");
ResponseResult r = payRegistration(Constants.BRANCHCODE,xmlMap.get("out_trade_no"),xmlMap.get("transaction_id"),
Constants.PAYMODE,xmlMap.get("total_fee"),DateUtil.getTradeTime(time_end),Constants.YYSOURCE);
if (Constants.RESULTCODE.equals(r.getResultCode())) {
respXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
result.setMessage("支付成功!");
logger.info("*******支付结果通知**********"+"支付成功!"+xmlMap.get("return_code") + "**************" + xmlMap.get("return_msg"));
}
else {
respXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[ERROR]]></return_msg>" + "</xml> ";
result.setMessage("支付失败!");
logger.error("*******支付结果通知**********"+"支付失败!"+xmlMap.get("return_code") + "**************" + xmlMap.get("return_msg"));
}
result.setResultCode(Constants.SUCCESS_CODE);
} else {
respXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA["
+ xmlMap.get("return_code") + "]]></return_msg>" + "</xml> ";
result.setResultCode(Constants.FAIL_CODE);
result.setMessage("支付失败!");
logger.error("*******支付结果通知**********"+"支付失败!"+xmlMap.get("return_code") + "**************" + xmlMap.get("return_msg"));
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(respXml.getBytes());
out.flush();
out.close();
return result;
}