直接上代碼:
1、支付配置PayCommonUtil
import com.legendshop.payment.tenpay.util.MD5Util; import com.legendshop.util.AppUtils; import javax.servlet.http.HttpServletRequest; import java.util.*; public class PayCommonUtil { /** * @Date: 2018/6/6 11:08 * @Descript: 因為公司app端與網頁端商戶號不一樣,所以這里需要做一些判斷 */ /* 微信公眾號 */ public static final String appid_wap = "your appid"; public static final String MCHID_wap = "商戶號"; /** * 微信原生 * appid 查看方法:登錄微信商戶平台(https://pay.weixin.qq.com/index.php/core/home/login) --> 營銷中心 --> 支付后配置 */ public static final String appid = "your appid"; public static final String MCHID = "商戶號"; public static final String notify_url = "你的通知接口地址"; /** * 兩個商戶號的key又是一樣的,這里就共用一個變量。 * key 查看方法:登錄微信商戶平台 --> 賬戶中心 --> API安全 --> API密鑰 */ public static final String key = "your key"; /** * 創建微信交易對象 */ public static SortedMap<Object, Object> getWXPrePayID(String tradeType, String openid) { SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); Boolean isWap = false; Boolean isWx = false; if (AppUtils.isNotBlank(tradeType)) { if (tradeType.equals("JSAPI")) { //微信瀏覽器內執行 isWx = true; isWap = true; } if (tradeType.equals("MWEB")) { //H5 isWap = true; } } parameters.put("appid", isWap ? appid_wap : appid); parameters.put("mch_id", isWap ? MCHID_wap : MCHID); parameters.put("nonce_str", createNoncestr()); parameters.put("fee_type", "CNY"); parameters.put("notify_url", notify_url); parameters.put("trade_type", AppUtils.isBlank(tradeType) ? "APP" : tradeType); if (isWx) { //微信瀏覽器內執行需要openid parameters.put("openid", openid); } return parameters; } /** * 再次簽名(僅APP支付需要) */ public static SortedMap<Object, Object> startWXPay(Map map) { try { SortedMap<Object, Object> parameterMap = new TreeMap<Object, Object>(); parameterMap.put("appid", appid); parameterMap.put("partnerid", MCHID); parameterMap.put("prepayid", map.get("prepay_id")); parameterMap.put("package", "Sign=WXPay"); parameterMap.put("noncestr", createNoncestr()); // 10位 parameterMap.put("timestamp", Long.parseLong(String.valueOf(System.currentTimeMillis()).toString().substring(0, 10))); String sign = PayCommonUtil.createSign("UTF-8", parameterMap); parameterMap.put("sign", sign); return parameterMap; } catch (Exception e) { e.printStackTrace(); } return null; } /* * 獲取真實IP * */ public static String getRemortIP(HttpServletRequest request) { if (request.getHeader("x-forwarded-for") == null) { return request.getRemoteAddr(); } return request.getHeader("x-forwarded-for"); } /** * 創建隨機數 * * @param length * @return */ public static String createNoncestr() { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < 16; i++) { Random rd = new Random(); res += chars.charAt(rd.nextInt(chars.length() - 1)); } return res; } /** * @param characterEncoding 編碼格式 * @param parameters 請求參數 * @return * @Description:創建sign簽名 */ public static String createSign(String characterEncoding, SortedMap<Object, Object> parameters) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } }
工具類MD5Util
public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; }
2、接下來是調用,這里只貼核心部分代碼
/** * body:訂單標題 * out_trade_no:結算單流水號,一般用商城訂單號就可以了,但是這樣會有個問題就是在不同平台(微信瀏覽器內、微信外的瀏覽器、APP等)分別提交訂單或者訂單價格的改變等因素都會出現“201 訂單重復“的問題,可根據實際業務需求做相應的處理 * fee:最小是1,表示1分錢 * tradeType:JSAPI(微信瀏覽器支付)、MWEB(微信以外的瀏覽器)、APP(原生支付) * openid:當tradeType = JSAPI,該屬性是必須的 * redirectUrl:當tradeType = MWEB,表示支付成功后的跳轉地址,一般是跳轉到支付成功的頁面 */ private Map getWXPay(String body,String out_trade_no,String fee,String tradeType,String openid,String redirectUrl) throws Exception{ SortedMap<Object, Object> parameters = PayCommonUtil.getWXPrePayID(tradeType,openid); // 獲取預付單,此處已做封裝,需要工具類 // String body = "雅量商品支付"; // // String out_trade_no = PayCommonUtil.getDateStr(); // // String fee = "1"; parameters.put("body", AppUtils.isBlank(body) ? "雅量商品支付" : body); //獲取ip要注意反向代理的ip獲取配置 parameters.put("spbill_create_ip", PayCommonUtil.getRemortIP(request)); parameters.put("out_trade_no", out_trade_no); parameters.put("total_fee", fee); /** * 簽名(簽名規則:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3,) * 注意:使用SortedMap已符合”參數名ASCII碼從小到大排序(字典序)“ */ String sign = PayCommonUtil.createSign("UTF-8", parameters); parameters.put("sign", sign); // 封裝請求參數結束 String requestXML = PayCommonUtil.getRequestXml(parameters); // 獲取xml結果 /** * 調用統一下單接口(APP、微信內支付、H5都是使用該接口) * * 區別: * APP支付:統一下單 --> 二次簽名 --> 返回數據給客戶端調起支付 * 微信內支付:統一下單 --> 返回數據給客戶端調起支付 * H5支付:統一下單 --> 返回鏈接給客戶端,客戶端進行跳轉 */ String result = PayCommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", requestXML); Map<String, String> map = XMLUtil.doXMLParse(result); Map allData = new HashedMap(); Map runRs = new HashedMap(); String rsCode = map.get("result_code").toString(); runRs.put("result_code", rsCode); if(rsCode.equals("FAIL")){ runRs.put("err_code_des",map.get("err_code_des")); } allData.put("runRs",runRs); if(AppUtils.isNotBlank(tradeType) && (tradeType.equals("JSAPI") || tradeType.equals("MWEB"))){ SortedMap<Object, Object> signData = new TreeMap<Object, Object>(); signData.put("appId",map.get("appid")); signData.put("timeStamp", Long.parseLong(String.valueOf(System.currentTimeMillis()).toString().substring(0, 10))); signData.put("nonceStr",map.get("nonce_str")); signData.put("package","prepay_id=" + map.get("prepay_id")); signData.put("signType","MD5"); String paySign = PayCommonUtil.createSign("UTF-8", signData); signData.put("paySign",paySign); if(tradeType.equals("MWEB")){ //跳轉鏈接 signData.put("mwebUrl",map.get("mweb_url") + "&redirect_url=" + redirectUrl); } allData.put("data",signData); }else{ SortedMap<Object, Object> parMap = PayCommonUtil.startWXPay(map); allData.put("data",parMap); } return allData; }
工具類XMLUtil
import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; /** * xml工具類 * @author miklchen * */ public class XMLUtil { /** * 解析xml,返回第一級元素鍵值對。如果第一級元素有子節點,則此節點的值是子節點的xml數據。 * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map doXMLParse(String strxml) throws JDOMException, IOException { if(null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = HttpClientUtil.String2Inputstream(strxml); 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 = XMLUtil.getChildrenText(children); } m.put(k, v); } //關閉流 in.close(); return m; } /** * 獲取子結點的xml * @param children * @return String */ 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(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 獲取xml編碼字符集 * @param strxml * @return * @throws IOException * @throws JDOMException */ public static String getXMLEncoding(String strxml) throws JDOMException, IOException { InputStream in = HttpClientUtil.String2Inputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); in.close(); return (String)doc.getProperty("encoding"); } }
千萬要注意參數的大小寫問題,一個接口一個樣
3、接收通知
支付成功后不是由客戶端通知服務器的,這樣無法保證正確性,而是應該由微信服務器來通知我們的項目服務器,並且要做簽名驗證保證正確性、一致性。
通知接口關鍵代碼
@RequestMapping(value = "/notify", method = RequestMethod.POST) public void notify(HttpServletRequest request,HttpServletResponse response) throws Exception { String resXml = ""; /** 支付成功后,微信回調返回的信息 */ Map<String, String> map=null; try { map = WeiXinUtil.parseXml(request); } catch (XmlPullParserException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } try { // SortedMap<Object,Object> parameters = new TreeMap<Object,Object>(); // parameters.put("appid", map.get("appid")); // parameters.put("attach",map.get("attach")); // parameters.put("bank_type",map.get("bank_type")); // parameters.put("cash_fee", map.get("cash_fee")); // parameters.put("fee_type", map.get("fee_type")); // parameters.put("is_subscribe", map.get("is_subscribe")); // parameters.put("mch_id", map.get("mch_id")); // parameters.put("nonce_str", map.get("nonce_str")); // parameters.put("openid", map.get("openid")); // parameters.put("out_trade_no", map.get("out_trade_no")); // parameters.put("result_code", map.get("result_code")); // parameters.put("return_code", map.get("return_code")); // parameters.put("time_end", map.get("time_end")); // parameters.put("total_fee", map.get("total_fee")); // parameters.put("trade_type", map.get("trade_type")); // parameters.put("transaction_id", map.get("transaction_id")); // 用於驗簽 SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); for (Object keyValue : map.keySet()) { //** 輸出返回的訂單支付信息 *//* if (!"sign".equals(keyValue)) { parameters.put(keyValue, map.get(keyValue)); } } String out_trade_no=map.get("out_trade_no"); String openid=map.get("openid"); boolean config=true; RequestHandler reqHandler = new RequestHandler(request,response); String checkSign = reqHandler.createSign(parameters); //簽名驗證 if( AppUtils.isBlank(out_trade_no) || AppUtils.isBlank(openid) || map.get("result_code").toString().equalsIgnoreCase("FAIL") || !checkSign.equals(map.get("sign")) ){ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文有誤]]></return_msg>" + "</xml> "; config=false; } if(config){ //根據out_trade_no查找訂單,代碼根據自己的業務進行修改 SubSettlement subSettlement= subSettlementService.getSubSettlementBySn(out_trade_no); if(AppUtils.isBlank(subSettlement)){ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[訂單找不到]]></return_msg>" + "</xml> "; config=false; } if(config){ // *************** //校驗返回的訂單金額是否與商戶側的訂單金額一致 String total_fee=map.get("total_fee"); String OrderTotal=String.valueOf(new BigDecimal(Arith.sub(subSettlement.getCashAmount(), subSettlement.getPdAmount())).setScale(2,BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).intValue()); if(total_fee.equals(OrderTotal)){ //微信返回的金額與數據庫的金額不一致性 //業務邏輯,一般是改變訂單狀態、發送訂單通知等等 // 支付成功,返回success,微信服務器就不會再重復發送該通知 resXml = "<xml>"+ "<return_code><![CDATA[SUCCESS]]></return_code>"+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[回調失敗]]></return_msg>" + "</xml> "; } } } }catch (Exception e) { e.printStackTrace(); resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[支付失敗]]></return_msg>" + "</xml> "; } BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); }
條碼支付很簡單,下載demo和證書下來配置一下就可以直接運行
微信條碼支付demo:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1
微信簽名要求:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
微信支付開發文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html
轉載請注明博客出處:http://www.cnblogs.com/cjh-notes/