微信支付服務端流程:
首先客戶端調起服務端接口來進行微信的統一下單(微信統一下單要進行兩次簽名驗證),通過驗證后返回處理結果,得到成功狀態碼后通知客戶端,並返回相應的信息。
然后客戶端發起支付,調用微信服務端。支付成功后,微信調用服務端的回調函數通知服務端支付結果,然后服務端進行一些后續處理操作。
1--------- 微信支付統一下單及驗證簽名
/** * 微信支付統一下單及驗證簽名 * * @param request * 請求信息 * @return 處理結果JSON信息 * @throws 無 */ @RequestMapping(value = "/wxPayinfo", method = RequestMethod.GET) @ResponseBody public ResultTO wxPayinfo(String orderId,String memberId,HttpServletRequest request, HttpServletResponse response) { // 下單詳細 Map<String, Object> back = null; // 未輸入訂單ID if (orderId==null || orderId.isEmpty() || "".equals(orderId)) { // 返回未輸入訂單ID錯誤信息 return ResultTO.newFailResultTO("參數orderId不能為空", null); } // 未輸入用戶ID if (memberId==null || memberId.isEmpty() || "".equals(memberId)) { // 返回未輸入用戶ID錯誤信息 return ResultTO.newFailResultTO("參數memberId不能為空", null); } try { // 提交前訂單信息 Map<String,Object> map = new HashMap<String,Object>(); map.put("orderId", orderId); Order oldOrder = OrderService.selectObject(map); if (oldOrder != null) { Double amount = oldOrder.getOrderAmount().doubleValue(); TenPayManager tenPayManager = new TenPayManager();
//一次簽名驗證 back = tenPayManager.weixinPay(oldOrder.getOrderSn().toString(), amount, request, response); } if(back.get("return_code").toString().equals("FAIL")){ String msg = back.get("return_msg").toString(); // 返回登錄信息錯誤信息 return ResultTO.newFailResultTO(msg, null); } else { logger.info("++++++++++++++++++++++++++++++++++ back: "+back); //二次簽名驗證 TenPayManager tenPayManager = new TenPayManager(); Map<String, Object> back2 = tenPayManager.weixinSignSecond(back); logger.info("++++++++++++++++++++++++++++++++++ back2: "+back2); // 返回處理成功信息 return ResultTO.newSuccessResultTO("微信驗證成功", back2); } } catch (Exception e) { // 保存錯誤LOG logger.error(e.getLocalizedMessage()); // 返回登錄信息錯誤信息 return ResultTO.newFailResultTO("微信驗證失敗", null); } }
2------微信工具類
package com.util.pay.tenpay; import java.util.HashMap; import java.util.Map; import java.util.Random; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jboss.logging.Logger; import com.util.AmountUtils; import com.util.DateUtil; import com.util.pay.tenpay.util.HttpPost; import com.util.pay.tenpay.util.MD5Weixin; import com.util.pay.tenpay.util.XmlParser; public class TenPayManager { /** * 發單超過系統次數支付接口 * * @param orderId * @param request * @param response * @return */ public Map<String, Object> weixinPay(String orderSn, double amount, HttpServletRequest request, HttpServletResponse response) { //服務器IP String serverIP = "127.0.0.1"; // if(request != null){ // serverIP = request.getRemoteAddr(); // } String nonce_str = createNonceStr(); String body = "訂單:" + orderSn; //注意:微信中金額單位為分,傳參值要轉化為分 String oAmount = moneyToFen(amount); //要刪--用於測試 start //oAmount = "1"; // end //簽名驗證,拼接字符串 String stringA = "appid=" + Constant.appID + "&body=" + body + "&mch_id=" + Constant.mchID + "&nonce_str=" + nonce_str + "¬ify_url=" + Constant.wxNoticeUrl + "&out_trade_no=" + orderSn + "&spbill_create_ip=" + serverIP + "&total_fee=" + oAmount + "&trade_type=" + Constant.wxTradeType;
//拼接key String stringSignTemp = stringA + "&key=" + Constant.key; System.out.println(stringA); System.out.println(stringSignTemp); // 簽名最后全部轉為大寫 String sign = MD5Weixin.MD5Encode(stringSignTemp).toUpperCase(); System.out.println(sign); // 拼接xml StringBuffer xml = new StringBuffer(); xml.append("<xml>"); xml.append("<appid>").append(Constant.appID).append("</appid>"); xml.append("<body>").append(body).append("</body>"); xml.append("<mch_id>").append(Constant.mchID).append("</mch_id>"); xml.append("<nonce_str>").append(nonce_str).append("</nonce_str>"); xml.append("<notify_url>").append(Constant.wxNoticeUrl) .append("</notify_url>"); xml.append("<out_trade_no>").append(orderSn).append("</out_trade_no>"); xml.append("<spbill_create_ip>").append(serverIP) .append("</spbill_create_ip>"); xml.append("<total_fee>").append(oAmount).append("</total_fee>"); xml.append("<trade_type>").append(Constant.wxTradeType) .append("</trade_type>"); xml.append("<sign><![CDATA[").append(sign).append("]]></sign>"); xml.append("</xml>"); // System.out.println( "----------微信下單-----下單參數----------:" + // xml.toString() ); Map<String, Object> resultMap = new HashMap<String, Object>(); String retXmlStr = null; retXmlStr = HttpPost.sendPostWithXmlPara(Constant.pay_address, new String(xml.toString())); if (null != retXmlStr) { resultMap = XmlParser.xmlStrParser(retXmlStr); resultMap.put("timestamp", DateUtil.intDate()); resultMap.put("pack", "Sign=WXPay"); } return resultMap; } public static String createNonceStr() { String letter[] = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "S", "Y", "Z" }; String nonce_str = ""; for (int i = 0; i < 26; i++) { int num = new Random().nextInt(26); nonce_str += letter[num]; } return nonce_str; } /** * 將金額轉換成分 * * @param amount * 金額 * @return String 分為單位的金額 */ public static String moneyToFen(double amount) { // 分為單位的金額 String toFenTemp = String.valueOf(amount); // 臨時保存用金額 String toFen = AmountUtils.changeY2F(toFenTemp); // 返回分為單位的金額 return toFen; } /** * 微信二次簽名 * * @return */ public Map<String, Object> weixinSignSecond(Map<String,Object> back) { String stringA = "appid=" + Constant.appID + "&noncestr=" + back.get("nonce_str") + "&package=Sign=WXPay" + "&partnerid=" + Constant.mchID + "&prepayid=" + back.get("prepay_id") + "×tamp=" + DateUtil.intDate(); System.out.println(stringA); String stringSignTemp = stringA + "&key=" + Constant.key; System.out.println(stringSignTemp); // 簽名最后全部轉為大寫 String sign = MD5Weixin.MD5Encode(stringSignTemp).toUpperCase(); System.out.println(sign); Map<String, Object> resultMap = new HashMap<String, Object>(); resultMap.put("appid", Constant.appID); resultMap.put("partnerid", Constant.mchID); resultMap.put("prepayid", back.get("prepay_id")); resultMap.put("pack", "Sign=WXPay"); resultMap.put("noncestr", back.get("nonce_str")); resultMap.put("timestamp", DateUtil.intDate()); resultMap.put("sign", sign); return resultMap; } }
3------配置類
package com.util.pay.tenpay; public class Constant { public static final String key = "";// 簽名算法需要用到的秘鑰 public static final String appID = "";// 開放平台AppID public static final String mchID = "100011111";// 商戶號 public static final String pwd = "111111";// // public static final String wxBody = "微信test";// public static final String pay_address = "https://api.mch.weixin.qq.com/pay/unifiedorder";// 統一支付API接口地址 public static final String check_url = "https://api.mch.weixin.qq.com/pay/orderquery";// 查詢訂單API接口地址 public static final String close_url = "https://api.mch.weixin.qq.com/pay/closeorder";// 關閉訂單API接口地址 public static final String refund_url = "https://api.mch.weixin.qq.com/secapi/pay/refund"; //退款 //測試用地址 // public static String wxNoticeUrl = "http://xxx.xxx.xxx.xx:8080/api/wxPayReturn/wxPayNotifyReturn"; public static String wxNoticeUrl = "http://xxx.xxx.xxx.xx:8080/api/wxPayReturn/wxPayNotifyReturn";
public static final String wxTradeType = "APP";// 取值如下:JSAPI,NATIVE,APP }
4-----回調函數
package com.controller.api.; import java.io.BufferedOutputStream; import java.io.StringReader; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.jdom.Document; import org.jdom.Element; import org.jdom.input.SAXBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.xml.sax.InputSource; import com.controller.AbstractAPIController; import com.po.Order; import com.po.Store; import com.service.OrderService; import com.service.StoreService; import com.to.ResultTO; import com.util.RequestParamUtil; import com.util.pay.tenpay.TenPayManager; import com.util.pay.tenpay.bean.WxPayResult; /** * FileName AppCourseController.java * * Version 1.0 * * Create by zy 2016/3/20 * * APP課程業務控制器 */ @Controller @RequestMapping("/api/wxPayReturn") public class WxPayReturnController extends AbstractAPIController { /** 日志文件生成器 */ private static Logger log = Logger.getLogger(WxPayReturnController.class); @Autowired private OrderService OrderService; @Autowired private StoreService StoreService; /** * 微信支付回調接口 * * @author zy * @since Version 1.0 * @param request * 請求信息 * @return 處理結果JSON信息 * @throws 無 */ @RequestMapping(value = "/wxPayNotifyReturn") @ResponseBody public ResultTO wxPayNotifyReturn(HttpServletRequest request, HttpServletResponse response) { log.info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-------進入微信回調函數wxPayNotifyReturn!"); try { // 把如下代碼貼到的你的處理回調的servlet 或者.do 中即可明白回調操作 String inputLine; String notityXml = ""; String resXml = ""; try { while ((inputLine = request.getReader().readLine()) != null) { notityXml += inputLine; } request.getReader().close(); } catch (Exception e) { e.printStackTrace(); } System.out.println("接收到的報文:" + notityXml); Map<String,String> m = parseXmlToList2(notityXml); WxPayResult wpr = new WxPayResult(); wpr.setAppid(m.get("appid").toString()); wpr.setBankType(m.get("bank_type").toString()); wpr.setCashFee(m.get("cash_fee").toString()); wpr.setFeeType(m.get("fee_type").toString()); wpr.setIsSubscribe(m.get("is_subscribe").toString()); wpr.setMchId(m.get("mch_id").toString()); wpr.setNonceStr(m.get("nonce_str").toString()); wpr.setOpenid(m.get("openid").toString()); wpr.setOutTradeNo(m.get("out_trade_no").toString()); wpr.setResultCode(m.get("result_code").toString()); wpr.setReturnCode(m.get("return_code").toString()); wpr.setSign(m.get("sign").toString()); wpr.setTimeEnd(m.get("time_end").toString()); wpr.setTotalFee(m.get("total_fee").toString()); wpr.setTradeType(m.get("trade_type").toString()); wpr.setTransactionId(m.get("transaction_id").toString()); boolean payFlag = false; log.info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-------wpr.getReturnCode():"+wpr.getReturnCode()); if ("SUCCESS".equals(wpr.getReturnCode())) { // 支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; payFlag = true; } else { resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> "; } // 支付成功 if (payFlag) { log.info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA----------------payFlag:"+payFlag); //支付成功后,商戶操作處理 } BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } catch (Exception e) { // 保存錯誤LOG log.error(e.getLocalizedMessage()); // 返回登錄信息錯誤信息 return ResultTO.newFailResultTO("支付失敗!", null); } // 返回處理成功信息以及課程列表 return ResultTO.newSuccessResultTO("支付成功", null); } /** * description: 解析微信通知xml * * @param xml * @return * @author ex_yangxiaoyi * @see */ private static Map<String,String> parseXmlToList2(String xml) { Map<String,String> retMap = new HashMap<String,String>(); try { StringReader read = new StringReader(xml); // 創建新的輸入源SAX 解析器將使用 InputSource 對象來確定如何讀取 XML 輸入 InputSource source = new InputSource(read); // 創建一個新的SAXBuilder SAXBuilder sb = new SAXBuilder(); // 通過輸入源構造一個Document Document doc = (Document) sb.build(source); Element root = doc.getRootElement();// 指向根節點 List<Element> es = root.getChildren(); if (es != null && es.size() != 0) { for (Element element : es) { retMap.put(element.getName(), element.getValue()); } } } catch (Exception e) { e.printStackTrace(); } return retMap; }
5、AmountUtils
/** * 將元為單位的轉換為分 替換小數點,支持以逗號區分的金額 * * @param amount * @return */ public static String changeY2F(String amount) { String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 處理包含, ¥ // 或者$的金額 int index = currency.indexOf("."); int length = currency.length(); Long amLong = 0l; if (index == -1) { amLong = Long.valueOf(currency + "00"); } else if (length - index >= 3) { amLong = Long.valueOf((currency.substring(0, index + 3)).replace( ".", "")); } else if (length - index == 2) { amLong = Long.valueOf((currency.substring(0, index + 2)).replace( ".", "") + 0); } else { amLong = Long.valueOf((currency.substring(0, index + 1)).replace( ".", "") + "00"); } return amLong.toString(); }