JAVA WEB實現微信掃碼支付(模式二)
阿新 • • 發佈:2019-02-15
一.準備微信掃碼支付要用到的相關引數,這裡將其全部寫入一個配置類,程式碼如下:
public class ZbWxPayConfig { public static String APP_ID = "***********************"; public static String APP_SECRET = "***********************"; public static String MCH_ID = "***********************"; //商戶號 public static String MCH_KEY = "***********************"; //商戶平臺設定的金鑰 public static String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//統一下單介面 public static String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";//統一查詢訂單介面 public static String NOTIFY_URL = "***********************";//支付成功微信回撥地址 }
二.相關工具類
public class WxPayUtil { /** * @author : YY * @date : 2018-7-3-下午04:20:37 * @param : @return * @Description: 生成隨機字串 */ public static String getNonceStr(){ String allChar = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; int maxPos = allChar.length(); String nonceStr = ""; for ( int i = 0; i < 16; i++) { nonceStr += allChar.charAt((int) Math.floor(Math.random() * maxPos)); } return nonceStr; } /** * @author : YY * @date : 2018-7-3-下午04:26:04 * @param : @return * @Description: 獲取當前時間字串 */ public static String getCurrTime() { Date now = new Date(); SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String s = outFormat.format(now); return s; } /** * @author : YY * @date : 2018-7-3-下午04:30:48 * @param : @return * @Description: 獲取10分鐘後的時間字串 */ public static String getAddCueeTime(){ long currentTime = System.currentTimeMillis() ; currentTime +=10*60*1000; Date date=new Date(currentTime); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String s = dateFormat.format(date); return s; } /** * @author : YY * @date : 2018-7-3-下午04:26:58 * @param : @param length * @param : @return * @Description: 生成指定長度隨機數 */ public static int buildRandom(int length) { int num = 1; double random = Math.random(); if (random < 0.1) { random = random + 0.1; } for (int i = 0; i < length; i++) { num = num * 10; } return (int) ((random * num)); } /** * @author : YY * @date : 2018-7-3-下午04:43:23 * @param : @param params * @param : @param mchKey * @param : @return * @Description: 生成微信支付簽名 */ @SuppressWarnings("unchecked") public static String createSign(SortedMap<String, String> params, String mchKey) { StringBuffer sb = new StringBuffer(); Set set = params.entrySet(); Iterator it = set.iterator(); while(it.hasNext()){ Entry entry = (Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){ sb.append(k + "=" + v + "&"); } } sb.append("key=" + mchKey); String sign = ZbMD5Util.EncryptionStr(sb.toString()).toUpperCase(); return sign; } /** * @author : YY * @date : 2018-7-4-下午03:21:07 * @param : @param packageParams * @param : @param mchKey * @param : @return * @Description: 驗證簽名 是否簽名正確,規則是:按引數名稱a-z排序,遇到空值的引數不參加簽名。 */ @SuppressWarnings("unchecked") public static boolean isTenpaySign(SortedMap<String, String> packageParams,String mchKey) { if(packageParams != null){ StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if (!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + mchKey); // 算出摘要 String mysign = MD5Util.EncryptionStr(sb.toString()).toLowerCase(); String tenpaySign = ((String) packageParams.get("sign")).toLowerCase(); return tenpaySign.equals(mysign); }else{ return false; } } @SuppressWarnings("unchecked") public static SortedMap<String, String> xmlConvertToMap(String rxml) { // 解析xml成map Map<String, String> resultMap = new HashMap<String, String>(); try { Document dom = DocumentHelper.parseText(rxml); Element root = dom.getRootElement(); List<Element> elementList = root.elements(); for (Element e : elementList) { resultMap.put(e.getName(), e.getText()); } } catch (Exception e) { e.printStackTrace(); } // 過濾空 設定 SortedMap SortedMap<String, String> packageParams = new TreeMap<String, String>(); Iterator it = resultMap.keySet().iterator(); while (it.hasNext()) { String parameter = (String) it.next(); String parameterValue = resultMap.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } return packageParams; } /** * @author : YY * @date : 2018-7-3-下午04:48:23 * @param : @param arr * @param : @return * @Description: 將map資料轉化為xml格式字串 */ public static String MapToXml(Map<String, String> arr) { String xml = "<xml>"; Iterator<Entry<String, String>> iter = arr.entrySet().iterator(); while (iter.hasNext()) { Entry<String, String> entry = iter.next(); String key = entry.getKey(); String val = entry.getValue(); if (IsNumeric(val)) { xml += "<" + key + ">" + val + "</" + key + ">"; } else xml += "<" + key + "><![CDATA[" + val + "]]></" + key + ">"; } xml += "</xml>"; return xml; } private static boolean IsNumeric(String str) { if (str.matches("\\d *")) { return true; } else { return false; } } } public class MD5Util { /** * @author : YY * @time : 2018-7-3-下午04:44:24 * @package : package com.tcmce.zibo.wxpay.utils; * @description : MD5加密字串 */ public static String EncryptionStr(String str){ StringBuffer sb = new StringBuffer(); try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); byte[] bytes = md.digest(); for (byte aByte : bytes) { String s=Integer.toHexString(0xff & aByte); if(s.length()==1){ sb.append("0"+s); }else{ sb.append(s); } } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return sb.toString(); } } public class HttpUtil { /** * @author : YY * @date : 2018-7-6-上午11:25:01 * @param : @param urlStr * @param : @param data * @param : @return * @param : @throws DocumentException * @Description: HTTP請求微信介面,返回map格式資料 */ @SuppressWarnings("unchecked") public static Map<String, String> postWx(String requestUrl, String outputStr) throws DocumentException{ try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 設定請求方式(GET/POST) //conn.setRequestMethod(requestMethod); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 當outputStr不為null時向輸出流寫資料 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意編碼格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 從輸入流讀取返回內容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 釋放資源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); if(buffer != null && buffer.toString().length() != 0){ Map<String, String> preMap = new HashMap<String, String>(); Document dom = DocumentHelper.parseText(buffer.toString()); Element root = dom.getRootElement(); List<Element> elementList = root.elements(); for (Element e : elementList) { preMap.put(e.getName(), e.getText()); } return preMap; } } catch (Exception e) { logger.error("HTTP請求:["+requestUrl+"]異常: " + e.getMessage()); } return null; } }
三.編寫呼叫微信下單介面(使用AJAX請求該地址,返回JSON格式資料,返回支付二維碼地址,用於下一步生成二維碼圖片)
public class UnifiedOrderServlet extends HttpServlet{ private static Logger logger = Logger.getLogger(UnifiedOrderServlet.class); private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); Map<String, String> msgMap = new HashMap<String, String>(); try { String prodcutId = request.getParameter("prodcutId").trim();//商品ID String orderType = request.getParameter("orderType").trim();//訂單型別 //生成商戶訂單號 String outTradeNo = WxPayUtil.getCurrTime() + WxPayUtil.buildRandom(4); //獲取商品價格 String totalFee = "999"; //回撥引數 長度有限制 Map<String, String> attachMap = new HashMap<String, String>(); attachMap.put("memberId", decryMemberId); attachMap.put("prodcutId", prodcutId); JSONObject attachJson = JSONObject.fromObject(attachMap); //完善微信下單所需引數 SortedMap<String, String> orderParams = new TreeMap<String, String>(); orderParams.put("appid", WxPayConfig.APP_ID); orderParams.put("mch_id", WxPayConfig.MCH_ID); orderParams.put("nonce_str", WxPayUtil.getNonceStr()); orderParams.put("body", "商品描述");//商品描述 orderParams.put("attach", attachJson.toString()); orderParams.put("out_trade_no", outTradeNo); orderParams.put("total_fee", new BigDecimal(totalFee).multiply(new BigDecimal(100)).intValue() + "");//訂單總金額,單位為分 orderParams.put("spbill_create_ip", request.getRemoteAddr()); orderParams.put("time_start", WxPayUtil.getCurrTime());////交易起始時間 orderParams.put("time_expire", WxPayUtil.getAddCueeTime());//交易結束時間 orderParams.put("notify_url", WxPayConfig.NOTIFY_URL);//非同步接收微信支付結果通知的回撥地址,通知url必須為外網可訪問的url,不能攜帶引數。 orderParams.put("trade_type", "NATIVE");//交易型別 JSAPI 公眾號支付 NATIVE 掃碼支付 APP APP支付 orderParams.put("device_info", "WEB");//終端裝置號(門店號或收銀裝置ID),預設請傳"WEB" orderParams.put("product_id", prodcutId);//商品ID String sign = WxPayUtil.createSign(orderParams, WxPayConfig.MCH_KEY);//將所有引數進行簽名 orderParams.put("sign", sign);//簽名 String xmlContent = WxPayUtil.MapToXml(orderParams); //呼叫微信下單介面 Map<String, String> resultMap = HttpUtil.postWx(WxPayConfig.UFDODER_URL, xmlContent); logger.info("下單結果:" + resultMap.toString()); //下單成功 if("SUCCESS".equals(resultMap.get("result_code"))){ //二維碼連結 orderParams.put("code_url", resultMap.get("code_url")); //預支付ID orderParams.put("prepay_id", resultMap.get("prepay_id")); orderParams.put("total_fee", totalFee.toString()); orderParams.put("payType", payType); orderParams.put("memberId", decryMemberId); orderParams.put("trade_state", "NOTPAY"); orderParams.put("trade_state_desc", "未支付"); orderParams.put("wx_pc", "PC"); orderParams.put("pay_source", "ZB"); //商戶系統記錄訂單資訊 此處程式碼省略 //以下引數為了頁面顯示所用, msgMap.put("body", body); msgMap.put("outTradeNo", outTradeNo); msgMap.put("totalFee", totalFee.toString()); msgMap.put("codeUrl", resultMap.get("code_url"));//二維碼支付地址 msgMap.put("success", "true"); }else{ msgMap.put("success", "false"); msgMap.put("message", "下單失敗,您可以嘗試稍後再進行下單!"); } } catch (Exception e) { msgMap.put("success", "false"); msgMap.put("message", "系統異常,您可以稍後再進行嘗試下單!"); logger.error("呼叫微信下單介面異常:" + e.getMessage()); } Gson gson = new Gson(); String json = gson.toJson(msgMap); PrintWriter out = response.getWriter(); out.write(json); out.close(); } }
三.利用上一步獲得的二維碼路徑生成二維碼,用於頁面展示:
public class CreateCodeServlet extends HttpServlet{
//二維碼預設尺寸
private static int defaultWidthAndHeight = 250;
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
try {
String codeUrl = request.getParameter("code_url").trim();
//以下開始生成二維碼
Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
//指定糾錯等級
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
//指定編碼格式
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
//hints.put(EncodeHintType.MARGIN, 1);
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl, BarcodeFormat.QR_CODE, defaultWidthAndHeight, defaultWidthAndHeight,hints);
OutputStream outputStream = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);//輸出二維碼
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
四.實現支付成功後微信後臺回撥的函式,第一個配置檔案裡最後一個引數就是指向這裡,必須可以外網訪問的地址:
public class WxPayNotifyServlet extends HttpServlet{
private static Logger logger = Logger.getLogger(WxPayNotifyServlet.class);
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
String resXml = "";
try {
InputStream inputStream = request.getInputStream();
StringBuffer sb = new StringBuffer();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
logger.error("支付結果:" + sb.toString());
if(sb != null && sb.toString().length() != 0){
SortedMap<String, String> resultMap = ZbWxPayUtil.xmlConvertToMap(sb.toString());
//驗證簽名
if(ZbWxPayUtil.isTenpaySign(resultMap, ZbWxPayConfig.MCH_KEY)){
if("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))){
//呼叫支付成功後執行業務邏輯程式碼
Integer rows = this.executePayNotify(resultMap);
if(rows != 0){
resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else{
logger.error("----業務結果執行失敗 返回FALL 通知微信繼續重試----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[商戶系統出錯]]></return_msg></xml> ";
}
}else{
logger.error("----支付失敗----" + resultMap.get("return_msg"));
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付失敗]]></return_msg></xml>";
}
}else{
logger.error("----簽名驗證失敗----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名驗證失敗]]></return_msg></xml> ";
}
}else{
logger.error("----未接收到微信通知資訊----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[未接收到回撥資訊]]></return_msg></xml> ";
}
} catch (Exception e) {
logger.error("----商戶系統異常----");
resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[商戶系統異常]]></return_msg></xml> ";
e.printStackTrace();
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
/**
* @author : YY
* @date : 2018-7-6-下午12:59:58
* @param : @param resultMap
* @param : @return
* @param : @throws Exception
* @Description: 微信掃碼支付成功後執行業務程式碼
*/
private Integer executePayNotify(Map<String, String> resultMap) throws Exception{
Double cashFee = Double.parseDouble(resultMap.get("cash_fee")) / 100,//現金支付金額
totalFee = Double.parseDouble(resultMap.get("total_fee")) / 100;//總金額
String bankType = resultMap.get("bank_type"),//付款銀行
feeType = resultMap.get("fee_type"),//貨幣種類
openid = resultMap.get("openid"),//使用者標識
timeEnd = resultMap.get("time_end"),//支付完成時間
transactionId = resultMap.get("transaction_id"),//微信支付訂單號
out_trade_no = resultMap.get("out_trade_no"),//商戶訂單號
attach = resultMap.get("attach");//商家資料包 傳入的是JSON格式字串
//解析訂單攜帶引數
JSONObject attachJsonObj = JSONObject.fromObject(attach);
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put("bankType", bankType);
argsMap.put("cashFee", cashFee.toString());
argsMap.put("feeType", feeType);
argsMap.put("openid", openid);
argsMap.put("timeEnd", timeEnd);
argsMap.put("memberId", memberId);
argsMap.put("transactionId", transactionId);
argsMap.put("out_trade_no", out_trade_no);
argsMap.put("trade_state", "SUCCESS");
argsMap.put("trade_state_desc", "支付成功");
此處開始執行商戶系統邏輯業務程式碼,自主實現
return 0;
}
}
五.支付功能到此結束,此處新增一個支付狀態查詢功能,支付成功後微信後臺不會主動通知支付頁面,需要自主新增查詢按鈕或定時查詢功能:
public class QueryOrderStatusServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
Map<String, String> result = new HashMap<String, String>();
try {
String outTradeNo = request.getParameter("outTradeNo");
if(StringUtils.isNotBlank(outTradeNo)){
String nonceStr = WxPayUtil.getNonceStr();
SortedMap<String, String> orderParams = new TreeMap<String, String>();
orderParams.put("appid", WxPayConfig.APP_ID);
orderParams.put("mch_id", WxPayConfig.MCH_ID);
orderParams.put("out_trade_no", outTradeNo);
orderParams.put("nonce_str", nonceStr);
//引數簽名
String sign = WxPayUtil.createSign(orderParams, WxPayConfig.MCH_KEY);
orderParams.put("sign", sign);
String xmlContent = WxPayUtil.MapToXml(orderParams);
Map<String, String> resultMap = HttpUtil.postWx(WxPayConfig.ORDER_QUERY_URL, xmlContent);
if("SUCCESS".equals(resultMap.get("return_code"))){
if("SUCCESS".equals(resultMap.get("result_code"))){
String tradeState = resultMap.get("trade_state");
if("SUCCESS".equals(tradeState)){
result.put("code", "success");
result.put("msg", "支付成功 [ 即將跳轉至學習頁面......請稍後!]。");
}else{
result.put("code", "fail");
result.put("msg", resultMap.get("trade_state_desc") + " [ 請您在微信上確認支付成功後,再執行該操作。]");
}
}else{
result.put("code", "fail");
result.put("msg", resultMap.get("err_code_des"));
}
}else{
result.put("code", "fail");
result.put("msg", "網路異常,您可以稍後再試!");
}
}else{
result.put("code", "fail");
result.put("msg", "訂單號有誤!");
}
} catch (Exception e) {
result.put("code", "fail");
result.put("msg", "系統異常,您可以稍後再試!");
e.printStackTrace();
}
Gson gson = new Gson();
String json = gson.toJson(result);
PrintWriter out = response.getWriter();
out.write(json);
out.close();
}
}