首先感謝一下【架構之路】盆友!我就是在他的基礎之上改的。原文連接。再三感謝!php
在最下,我會貼上全部的代碼。我代碼log日誌打的很是的多,可能會顯得很羅嗦。見諒。html
前面的一些純文字介紹看的會很累。可是建議好好看一下。尤爲是我貼上的微信的官方文檔。裏面其實已經寫的很是清楚了。前端
略java
IP白名單:首頁->基本配置->IP白名單。寫入服務器的地址。
git
進入微信商戶中心,下載證書,安裝控件、證書,設置API密鑰(KEY)。web
設置支付受權目錄。http://www.*.com/cms/wepay/。ajax
MCH_ID:首頁->微信支付。能夠查看到微信支付的商戶號。
spring
APPID、APP_SECRET:首頁->基本配置;能夠找到開發者ID。同時重置APP_SECRET。數據庫
全部使用公衆號支付方式發起支付請求的連接地址,都必須在支付受權目錄之下;apache
最多設置5個支付受權目錄,且域名必須經過ICP備案;
頭部要包含http或https,須細化到二級或三級目錄,以左斜槓「/」結尾。
簡單說。設置好受權方式,來獲取code。由code獲取accessToken,由accessToken再獲取用戶的open_id和其餘信息。
2 第二步:經過code換取網頁受權access_token
3 第三步:刷新access_token(若是須要)。得到open_id。
4 第四步:拉取用戶信息(需scope爲 snsapi_userinfo)
由微信的tab直接請求toPay方法。這個過程,首先是發起受權請求。用戶容許後,開始獲取openid。以後,再跳轉至購買頁面。
首先強調第一點:token是有區別的。 經過code換取的access_token(第二步)和以後拉取用戶時的access_token(第四步)不同。這點尤爲須要注意。第二步的token是oauth2加密的token,而第四步的token只是普通的token。具體請看官方文檔。 其次:token的使用。 下面代碼中的使用方式爲:每次使用時,都去微信api申請一個token。可是微信api每日調用token接口的次數是有限的(一天1萬次)。因此須要更精確的對其進行控制。 能夠將token存入數據庫。使用定時任務刷新token。 我採用了比較簡單的方式,將信息存到了session裏面。取出時,判斷下時間便可。
本項目操做過程爲:用戶手動點擊支付按鈕->獲取支付須要的參數->將預付款訂單參數傳遞給微信。
1 function onBridgeReady(){ 2 WeixinJSBridge.invoke( 3 'getBrandWCPayRequest', { 4 "appId":appId, //公衆號名稱,由商戶傳入 5 "paySign":sign, //微信簽名 6 "timeStamp":timeStamp, //時間戳,自1970年以來的秒數 7 "nonceStr":nonceStr , //隨機串 8 "package":packageStr, //預支付交易會話標識 9 "signType":signType //微信簽名方式 10 }, 11 function(res){ 12 alert(JSON.stringify(res));//出錯能夠在這裏看看..... 13 if(res.err_msg == "get_brand_wcpay_request:ok" ) { 14 //window.location.replace("index.html"); 15 alert('支付成功'); 16 }else if(res.err_msg == "get_brand_wcpay_request:cancel"){ 17 alert('支付取消'); 18 }else if(res.err_msg == "get_brand_wcpay_request:fail" ){ 19 alert('支付失敗'); 20 } 21 //使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回 ok,但並不保證它絕對可靠。 22 } 23 ); 24 }
若是出現簽名錯誤相關錯誤,我是用的此頁面的校驗工具進行測試的。微信在線校驗工具。
用戶信息類。若是隻作支付不保存用戶信息,此類可無。
public class UserInfo { private String openid; private String unionid; private String headimgurl; private String nickname; private String refreshtoken; private int siteid; }
微信常量信息類。
public class WeChatConst { // 公衆號支付APPID public static final String APPID = WePropertieUtil.getValue("APPID"); // 公衆號支付AppSecret public static final String APP_SECRET = WePropertieUtil.getValue("APP_SECRET"); // 公衆號支付商戶號 public static final String MCH_ID = WePropertieUtil.getValue("MCH_ID"); // 商戶後臺配置的一個32位的key,位置:微信商戶平臺-帳戶中心-API安全 public static final String KEY = WePropertieUtil.getValue("KEY"); // 交易類型 public static final String TRADE_TYPE = WePropertieUtil.getValue("TRADE_TYPE"); // 根路徑 public static final String BASE_URL = WePropertieUtil.getValue("BASE_URL"); }
用戶信息類。若是隻作支付不保存用戶信息,此類可無。
public class WeixinLoginUser implements Serializable { private static final long serialVersionUID = -8449856597137213512L; private String openID; private String unionID; private String headImageUrl; private String nickName; private String refreshToken; private int siteID; }
微信支付參數。事實上我只用了裏面極少一部分。
public class WxPaySendData { /** * 公衆帳號ID */ private String appid; /** * 附加數據 */ private String attach; /** * 商品描述 */ private String body; /** * 商戶號 */ private String mch_id; /** * 隨機字符串 */ private String nonce_str; /** * 通知地址 */ private String notiry_url; /** * 商戶訂單號 */ private String out_trade_no; /** * 標價金額 */ private String total_fee; /** * 交易類型 */ private String trade_type; /** * 終端IP */ private String spbill_create_ip; /** * 用戶標識 */ private String openid; /** * 簽名 */ private String sign; /** * 預支付id */ private String prepay_id; /** * 簽名類型:MD5 */ private String signType; /** * 時間戳 */ private String timeStamp; /** * 微信支付時用到的prepay_id */ private String packageStr; private String return_code; private String return_msg; private String result_code; private String bank_type; private Integer cash_fee; private String fee_type; private String is_subscribe; private String time_end; /** * 微信支付訂單號 */ private String transaction_id; }
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URL; import java.net.URLConnection; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import javax.servlet.http.HttpServletRequest; /** * @Description http請求工具類 */ public class HttpUtil { /** * 向指定URL發送GET方法的請求 * * @param url * 發送請求的URL * @param param * 請求Map參數,請求參數應該是 {"name1":"value1","name2":"value2"}的形式。 * @param charset * 發送和接收的格式 * @return URL 所表明遠程資源的響應結果 */ public static String sendGet(String url, Map<String, Object> map) { StringBuffer sb = new StringBuffer(); // 構建請求參數 if (map != null && map.size() > 0) { Iterator<?> it = map.entrySet().iterator(); // 定義迭代器 while (it.hasNext()) { Map.Entry<?, ?> er = (Entry<?, ?>) it.next(); sb.append(er.getKey()); sb.append("="); sb.append(er.getValue()); sb.append("&"); } } return sendGet(url, sb.toString()); } /** * 向指定URL發送POST方法的請求 * * @param url * 發送請求的URL * @param param * 請求Map參數,請求參數應該是 {"name1":"value1","name2":"value2"}的形式。 * @param charset * 發送和接收的格式 * @return URL 所表明遠程資源的響應結果 */ public static String sendPost(String url, Map<String, Object> map) { StringBuffer sb = new StringBuffer(); // 構建請求參數 if (map != null && map.size() > 0) { for (Entry<String, Object> e : map.entrySet()) { sb.append(e.getKey()); sb.append("="); sb.append(e.getValue()); sb.append("&"); } } return sendPost(url, sb.toString()); } /** * 向指定URL發送GET方法的請求 * * @param url * 發送請求的URL * @param param * 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。 * @param charset * 發送和接收的格式 * @return URL 所表明遠程資源的響應結果 */ public static String sendGet(String url, String param) { String result = ""; String line; StringBuffer sb = new StringBuffer(); BufferedReader in = null; try { String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); // 打開和URL之間的鏈接 URLConnection conn = realUrl.openConnection(); // 設置通用的請求屬性 設置請求格式 conn.setRequestProperty("contentType", "utf-8"); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 設置超時時間 conn.setConnectTimeout(600); conn.setReadTimeout(600); // 創建實際的鏈接 conn.connect(); // 定義 BufferedReader輸入流來讀取URL的響應,設置接收格式 in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8")); while ((line = in.readLine()) != null) { sb.append(line); } result = sb.toString(); } catch (Exception e) { System.out.println("發送GET請求出現異常!" + e); e.printStackTrace(); } // 使用finally塊來關閉輸入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } /** * 向指定 URL 發送POST方法的請求 * * @param url * 發送請求的 URL * @param param * 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。 * @param charset * 發送和接收的格式 * @return 所表明遠程資源的響應結果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; String line; StringBuffer sb = new StringBuffer(); try { URL realUrl = new URL(url); // 打開和URL之間的鏈接 URLConnection conn = realUrl.openConnection(); // 設置通用的請求屬性 設置請求格式 conn.setRequestProperty("contentType", "utf-8"); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 設置超時時間 conn.setConnectTimeout(600); conn.setReadTimeout(600); // 發送POST請求必須設置以下兩行 conn.setDoOutput(true); conn.setDoInput(true); // 獲取URLConnection對象對應的輸出流 out = new PrintWriter(conn.getOutputStream()); // 發送請求參數 out.print(param); // flush輸出流的緩衝 out.flush(); // 定義BufferedReader輸入流來讀取URL的響應 設置接收格式 in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8")); while ((line = in.readLine()) != null) { sb.append(line); } result = sb.toString(); } catch (Exception e) { System.out.println("發送 POST請求出現異常!" + e); e.printStackTrace(); } // 使用finally塊來關閉輸出流、輸入流 finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } public static String getRemoteIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); }
// 若是手機是雙卡雙待,那麼會獲取到兩個IP。咱們僅須要有一個。
return ip.split(",")[0];
}
}
import java.security.MessageDigest; public class MD5Util { private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; /** * MD5加密解密工具類 * * @param b * 字節數組 * @return 16進制字串 */ public 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 + n; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); resultString = byteArrayToHexString(md.digest(resultString .getBytes("utf-8"))); } catch (Exception ex) { } return resultString; } public static boolean isValidate(String input,String output){ boolean status = false; if(MD5Util.MD5Encode(input).equals(output)){ status = true; }else{ status = false; } return status; } }
import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.meihe.dto.wePay.WeChatConst; @SuppressWarnings("deprecation") public class WeChatUtil { private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class); /** * 統一下單 得到PrePayId * * @param body * 商品或支付單簡要描述 * @param out_trade_no * 商戶系統內部的訂單號,32個字符內、可包含字母 * @param total_fee * 訂單總金額,單位爲分 * @param IP * APP和網頁支付提交用戶端ip * @param notify_url * 接收微信支付異步通知回調地址 * @param openid * 用戶openId * @throws Exception */ public static String unifiedorder(String body, String out_trade_no, Integer total_fee, String ip, String notify_url, String openId) throws Exception { log.debug(">>>>>>>>>>>>>>>>>開始準備統一下單流程...."); /** * 組裝請求參數 按照ASCII排序 */ SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); String nonce_str = getNonceStr();// 隨機數據 parameters.put("appid", WeChatConst.APPID); parameters.put("body", body); parameters.put("mch_id", WeChatConst.MCH_ID); parameters.put("nonce_str", nonce_str); parameters.put("notify_url", notify_url); parameters.put("openid", openId); parameters.put("out_trade_no", out_trade_no); parameters.put("spbill_create_ip", ip); parameters.put("total_fee", total_fee.toString()); parameters.put("trade_type", WeChatConst.TRADE_TYPE); String sign = WxSign.createSign(parameters, WeChatConst.KEY); /** * 組裝XML */ log.debug(">>>>>>>>>>>>>>>>>統一下單數據準備完成,準備組裝....{}",parameters.toString()); StringBuilder sb = new StringBuilder(""); sb.append("<xml>"); setXmlKV(sb, "appid", WeChatConst.APPID); setXmlKV(sb, "body", body); setXmlKV(sb, "mch_id", WeChatConst.MCH_ID); setXmlKV(sb, "nonce_str", nonce_str); setXmlKV(sb, "notify_url", notify_url); setXmlKV(sb, "openid", openId); setXmlKV(sb, "out_trade_no", out_trade_no); setXmlKV(sb, "spbill_create_ip", ip); setXmlKV(sb, "total_fee", total_fee.toString()); setXmlKV(sb, "trade_type", WeChatConst.TRADE_TYPE); setXmlKV(sb, "sign", sign); sb.append("</xml>"); // 這個處理是爲了防止傳中文的時候出現簽名錯誤 StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1")); log.debug(">>>>>>>>>>>>>>>>>統一下單數據組裝完成,準備下單。"); HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder"); httppost.setEntity(reqEntity); log.debug(">>>>>>>>>>>>>>>>>請求地址、請求數據封裝完成。開始下單。"); @SuppressWarnings("resource") DefaultHttpClient httpclient = new DefaultHttpClient(); HttpResponse response = httpclient.execute(httppost); log.debug(">>>>>>>>>>>>>>>>>下單請求結束。獲取返回值。"); String strResult = EntityUtils.toString(response.getEntity(),Charset.forName("utf-8")); log.debug(">>>>>>>>>>>>>>>>>下單請求結束。轉換字符串返回值結束。"); return strResult; } // 得到隨機字符串 public static String getNonceStr() { String str = ""; Random random = new Random(); try { str = MD5Util.MD5Encode(String.valueOf(random.nextInt(10000))); } catch (Exception e) { e.printStackTrace(); } return str; } // 插入XML標籤 public static StringBuilder setXmlKV(StringBuilder sb, String Key, String value) { sb.append("<"); sb.append(Key); sb.append(">"); sb.append(value); sb.append("</"); sb.append(Key); sb.append(">"); return sb; } // 解析XML 得到 PrePayId public static String getPrePayId(String xml) { int start = xml.indexOf("<prepay_id>"); int end = xml.indexOf("</prepay_id>"); if (start < 0 && end < 0) { return null; } return xml.substring(start + "<prepay_id>".length(), end) .replace("<![CDATA[", "").replace("]]>", ""); } // 商戶訂單號 public static String getOut_trade_no() { DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); return df.format(new Date()) + buildRandom(7); } // 時間戳 public static String getTimeStamp() { return String.valueOf(System.currentTimeMillis() / 1000); } // 隨機4位數字 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)); } public static String inputStream2String(InputStream inStream, String encoding) { String result = null; try { if (inStream != null) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] tempBytes = new byte[1024]; int count = -1; while ((count = inStream.read(tempBytes, 0, 1024)) != -1) { outStream.write(tempBytes, 0, count); } tempBytes = null; outStream.flush(); result = new String(outStream.toByteArray(), encoding); } } catch (Exception e) { result = null; } return result; } }
import java.io.IOException; import java.text.MessageFormat; import java.util.Properties; /** * 讀取微信properties配置文件 * * */ public class WePropertieUtil { private static Properties env; private static Properties getEnv() { try { if (env == null) { env = new Properties(); env.load(WePropertieUtil.class.getClassLoader().getResourceAsStream("config/wechat-const.properties")); } } catch (IOException e) { e.printStackTrace(); } return env; } /** * 獲取對應的key * * @param key * @return */ public static String getValue(String key) { if (env == null) { return getEnv().getProperty(key); } else { return env.getProperty(key); } } /** * 獲取key對應的值,並替換其中的參數 * * @param key * @param arguments * @return */ public static String getValue(String key, Object... arguments) { return MessageFormat.format(getValue(key), arguments); } }
import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class WxSign { private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class); /** * 簽名封裝類。建立簽名。 * @param parameters * @param key * @return * @throws Exception */ @SuppressWarnings("rawtypes") public static String createSign(SortedMap<Object, Object> parameters,String key) throws Exception { log.info(">>>>>>>>>>>>>>>>>開始封裝數據,建立簽名...."); StringBuffer sb = new StringBuffer(); // 全部參與傳參的參數按照accsii排序(升序) 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); log.info(">>>>>>>>>>>>>>>>>封裝數據{}",sb.toString()); String str = sb.toString(); log.info(str); String sign = MD5Util.MD5Encode(str).toUpperCase(); log.info(">>>>>>>>>>>>>>>>>封裝數據完成,簽名爲:{}",sign); return sign; } }
import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.TimeZone; import org.apache.commons.lang3.StringUtils; public abstract class DateTimeUtils { /** * 獲取今天日期 * * @return */ public static String getToday(String format) { if (StringUtils.isEmpty(format)) { format = "yyyyMMdd"; } return LocalDate.now().format(DateTimeFormatter.ofPattern(format)); } /** * 獲取當前時間 * * @param format * @return */ public static String getCurrentTime(String format) { if (StringUtils.isEmpty(format)) { format = "HHmmss"; } return LocalTime.now().format(DateTimeFormatter.ofPattern(format)); } /** * 將字符串轉換成LocalDate格式 * * @param format * @return * @throws ParseException */ public static LocalDate toLocalDate(String LocalDate) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); Date date = sdf.parse(LocalDate); Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); LocalDate localDate = localDateTime.toLocalDate(); return localDate; } /** * 將字符串轉換成LocalTime格式 * * @param format * @return * @throws ParseException */ public static LocalTime toLocalTime(String LocalTime) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("HHmmss"); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); Date time = sdf.parse(LocalTime); Instant instant = time.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); LocalTime localTime = localDateTime.toLocalTime(); return localTime; } }
代碼流程:頁面->controller->service;
支付頁面:
// 頁面在執行成功以前,pay方法只容許調用一次。 clickstatus=true; var sign ; var appId ; var timeStamp ; var nonceStr ; var packageStr ; var signType, var url = 'http://**/cms/wepay/'; function pay(){ if(clickstatus){ clickstatus=false; $.ajax({ type:"post", url:url+'pay', dataType:"json", data:{openId:'${openId}',totalFee:totalFee,pointAmount:pointAmount,memberId:$.cookie('memberId'),cellphone: $.cookie('cellphone'),accessToken: $.cookie('accessToken')}, success:function(data) { if(data.result_code == 'SUCCESS'){ appId = data.appid; sign = data.sign; timeStamp = data.timeStamp; nonceStr = data.nonce_str; packageStr = data.packageStr; signType = data.signType; // 調用微信支付控件 if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } }else{ alert("統一下單失敗"); } } }) } } function onBridgeReady(){ WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId":appId, "timeStamp":timeStamp, "nonceStr":nonceStr, "package": packageStr, "signType":signType, "paySign": sign },function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { window.location.href = url+'paySuccess'; } else if (res.err_msg == "get_brand_wcpay_request:fail") { window.location.href = url+'payFailure'; } else if (res.err_msg == "get_brand_wcpay_request:cancel") { alert("支付取消"); } }) }
Controller
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import com.meihe.dto.wePay.WxPaySendData; import com.meihe.service.wePay.IPreparedToPayService; @RestController @RequestMapping("wepay") public class wePayController { @Autowired private IPreparedToPayService preparedToPayService; /** * 跳轉支付界面,將code帶過去 **/ @ResponseBody @RequestMapping("toPay") public ModelAndView toPay(HttpServletRequest request,HttpServletResponse response){ ModelAndView res = new ModelAndView(); try { res = preparedToPayService.getUserOpenId(request, response); } catch (Exception e) { e.printStackTrace(); } return res; } /** * 點擊確認充值 統一下單,得到預付id(prepay_id) * @param request * @param response * @return */ @ResponseBody @RequestMapping("pay") public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response,String openId, String totalFee, String pointAmount, String memberId){ WxPaySendData data = new WxPaySendData(); try { data = preparedToPayService.prePay(request, response, openId,totalFee,pointAmount,memberId); } catch (Exception e) { e.printStackTrace(); } return data; } /** * 微信支付回調接口 * @param request * @param response * @throws Exception */ @ResponseBody @RequestMapping("callback") public void callBack(HttpServletRequest request, HttpServletResponse response) { try { preparedToPayService.callBack(request, response); } catch (Exception e) { e.printStackTrace(); } } /** * 支付成功 **/ @ResponseBody @RequestMapping("paySuccess") public ModelAndView paySuccess(HttpServletRequest request,HttpServletResponse response){ return new ModelAndView("wepay/paySuccess"); } /** * 支付失敗 **/ @ResponseBody @RequestMapping("payFailure") public ModelAndView payFailure(HttpServletRequest request,HttpServletResponse response){ return new ModelAndView("wepay/payFailure"); }
Service
import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSON; import com.meihe.dto.AjaxResult; import com.meihe.dto.wePay.UserInfo; import com.meihe.dto.wePay.WeChatConst; import com.meihe.dto.wePay.WeixinLoginUser; import com.meihe.dto.wePay.WxPaySendData; import com.meihe.model.marketing.MarketingOrder; import com.meihe.service.marketing.IMarketingOrderService; import com.meihe.service.wePay.IOauthService; import com.meihe.service.wePay.IPreparedToPayService; import com.meihe.utils.wePayUtils.HttpUtil; import com.meihe.utils.wePayUtils.WeChatUtil; import com.meihe.utils.wePayUtils.WxSign; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; @Service public class PreparedToPayServiceImpl implements IPreparedToPayService { @Autowired private IOauthService oauth = null; @Autowired private IMarketingOrderService mOrder = null; private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class); @Override public ModelAndView getUserOpenId(HttpServletRequest request,HttpServletResponse response) throws Exception { log.info(">>>>>>>>>>>>>>>>>準備獲取用戶code...."); ModelAndView modelAndView = new ModelAndView(); @SuppressWarnings("deprecation") String redirecUri = URLEncoder.encode(WeChatConst.BASE_URL + "toPay"); String code = null; if (request.getParameter("code") != null) { code = request.getParameter("code"); } if (code == null) { log.info(">>>>>>>>>>>>>>>>>用戶首次登錄,受權中...準備獲取code...."); String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +WeChatConst.APPID+"&redirect_uri="+redirecUri+ "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect"; return new ModelAndView(siteURL); } code = request.getParameter("code"); log.debug(">>>>>>>>>>>>>>>>>得到用戶code[{}]成功!準備獲取用戶信息...",code); WeixinLoginUser weixinLoginUser = getWeixinLoginUser(code,request,response); if(null == weixinLoginUser){ log.debug(">>>>>>>>>>>>>>>>>code[{}]已被使用,從新獲取code...",code); // 進入頁面後,若是用戶刷新,那麼會提示code被使用的錯誤。此判斷主要應對獲取用戶信息後刷新的操做。 String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +WeChatConst.APPID+"&redirect_uri="+redirecUri+ "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect"; return new ModelAndView(siteURL); } modelAndView.addObject("openId", weixinLoginUser.getOpenID()); request.setAttribute("openId", weixinLoginUser.getOpenID()); log.debug(">>>>>>>>>>>>>>>>>準備工做完成!跳轉至購買頁面..."); String viewName = "wepay/payIndex"; modelAndView.setViewName(viewName); return modelAndView; } /** * 獲取微信受權登錄用戶 * * @param code * @return * @throws Exception */ private WeixinLoginUser getWeixinLoginUser(String code,HttpServletRequest request,HttpServletResponse response) throws Exception { String str = oauth.getToken(code, WeChatConst.APPID,WeChatConst.APP_SECRET); String openID = (String) JSON.parseObject(str, Map.class).get("openid"); String accessToken = (String) JSON.parseObject(str, Map.class).get("access_token"); String refreshToken = (String) JSON.parseObject(str, Map.class).get("refresh_token"); if(null == openID || null == accessToken || null == refreshToken){ return null; } log.debug(">>>>>>>>>>>>>>>>>openid[{}],access_token[{}],refresh_token[{}]..轉JSON爲String中...",openID,accessToken,refreshToken); // 獲取到的用戶信息。暫時沒用。後臺暫時只保存一個openId。 // 支付只須要用戶的openId便可完成。這個方法能夠在此結束。 UserInfo userInfo = oauth.getSnsUserInfo(openID, accessToken); WeixinLoginUser weixinLoginUser = new WeixinLoginUser(); weixinLoginUser.setOpenID(openID); weixinLoginUser.setUnionID(userInfo.getUnionid()); weixinLoginUser.setHeadImageUrl(userInfo.getHeadimgurl()); weixinLoginUser.setNickName(userInfo.getNickname()); weixinLoginUser.setRefreshToken(refreshToken); log.debug(">>>>>>>>>>>>>>>>>獲取用戶信息中完成!"); return weixinLoginUser; } @Override public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response, String openId, String totalFee, String pointAmount, String memberId) throws Exception { log.debug(">>>>>>>>>>>>>>>>>預備工做完成。開始準備預付訂單信息..."); WxPaySendData result = new WxPaySendData(); MarketingOrder marketingOrder = new MarketingOrder(); // 商戶訂單號 String out_trade_no = WeChatUtil.getOut_trade_no(); // 產品價格,單位:分 Double total_fee = Double.parseDouble(totalFee)*100; // 客戶端IP String ip = HttpUtil.getRemoteIpAddr(request); // 支付成功後回調的url地址 String notify_url = WeChatConst.BASE_URL+"callback"; // 商品或支付產品的簡要描述 String body = pointAmount+"積分"; log.debug(">>>>>>>>>>>>>>>>>預付訂單信息準備完成。"); log.debug(">>>>>>>>>>>>>>>>>訂單號:[{}],價格:[{}],客戶端IP[{}],回調URL:[{}],商品支付信息:[{}]",out_trade_no,total_fee,ip,notify_url,body); //統一下單 String strResult = WeChatUtil.unifiedorder(body, out_trade_no, total_fee.intValue(), ip, notify_url,openId); log.debug(">>>>>>>>>>>>>>>>>解析統一下單XML...."); XStream stream = new XStream(new DomDriver()); stream.alias("xml", WxPaySendData.class); WxPaySendData wxReturnData = (WxPaySendData)stream.fromXML(strResult); log.debug(">>>>>>>>>>>>>>>>>統一下單XML解析完成....[{}]",wxReturnData.toString()); //二者都爲SUCCESS才能獲取prepay_id if(wxReturnData.getResult_code().equals("SUCCESS") &&wxReturnData.getReturn_code().equals("SUCCESS") ){ log.debug(">>>>>>>>>>>>>>>>>統一下單請求成功!開始準備正式支付...."); //時間戳、隨機字符串 String timeStamp = WeChatUtil.getTimeStamp(); String nonce_str = WeChatUtil.getNonceStr(); // 注:上面這兩個參數,必定要拿出來做爲後續的value,不能每步都建立新的時間戳跟隨機字符串,否則H5調支付API,會報簽名參數錯誤 result.setResult_code(wxReturnData.getResult_code()); result.setAppid(WeChatConst.APPID); result.setTimeStamp(timeStamp); result.setNonce_str(nonce_str); result.setPackageStr("prepay_id="+wxReturnData.getPrepay_id()); result.setSignType("MD5"); // 第二次簽名,將微信返回的數據再進行簽名 SortedMap<Object,Object> signMap = new TreeMap<Object,Object>(); signMap.put("appId", WeChatConst.APPID); signMap.put("nonceStr", nonce_str); // 值爲:prepay_id=xxx signMap.put("package", "prepay_id="+wxReturnData.getPrepay_id()); signMap.put("signType", "MD5"); signMap.put("timeStamp", timeStamp); log.debug(">>>>>>>>>>>>>>>>>根據預支付訂單號再次進行簽名...."); String paySign = WxSign.createSign(signMap, WeChatConst.KEY); log.debug(">>>>>>>>>>>>>>>>>簽名完成!準備生成本地支付訂單...."); result.setSign(paySign); } else { result.setResult_code("fail"); } log.debug(">>>>>>>>>>>>>>>>>開始生成一條新的本地支付訂單。"); marketingOrder.setPaymentType(1); marketingOrder.setMemberId(Integer.parseInt(memberId)); marketingOrder.setOutTradeNo(out_trade_no); marketingOrder.setTotalFee(total_fee.toString()); marketingOrder.setPointAmount(pointAmount); marketingOrder.setOpenId(openId); AjaxResult ajaxResult = mOrder.insertMarketingOrder(marketingOrder); if(!ajaxResult.getSuccess()){ result.setResult_code("fail"); } log.debug(">>>>>>>>>>>>>>>>>開始正式支付!"); return result; } @Override public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception{ log.debug(">>>>>>>>>>>>>>>>>微信開始回調本地。校驗簽名。準備校驗簽名...."); response.setContentType("text/xml;charset=UTF-8"); try { InputStream is = request.getInputStream(); String result = IOUtils.toString(is, "UTF-8"); if("".equals(result)){ response.getWriter().write("<xm><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[參數錯誤!]]></return_msg></xml>"); return ; } // 解析xml XStream stream = new XStream(new DomDriver()); stream.alias("xml", WxPaySendData.class); WxPaySendData wxPaySendData = (WxPaySendData)stream.fromXML(result); System.out.println(wxPaySendData.toString()); String appid = wxPaySendData.getAppid(); String mch_id =wxPaySendData.getMch_id(); String nonce_str = wxPaySendData.getNonce_str(); String out_trade_no = wxPaySendData.getOut_trade_no(); String total_fee = wxPaySendData.getTotal_fee(); String trade_type = wxPaySendData.getTrade_type(); String openid =wxPaySendData.getOpenid(); String return_code = wxPaySendData.getReturn_code(); String result_code = wxPaySendData.getResult_code(); String bank_type = wxPaySendData.getBank_type(); Integer cash_fee = wxPaySendData.getCash_fee(); String fee_type = wxPaySendData.getFee_type(); String is_subscribe = wxPaySendData.getIs_subscribe(); String time_end = wxPaySendData.getTime_end(); String transaction_id = wxPaySendData.getTransaction_id(); String sign = wxPaySendData.getSign(); //簽名驗證 SortedMap<Object,Object> parameters = new TreeMap<Object,Object>(); parameters.put("appid",appid); parameters.put("mch_id",mch_id); parameters.put("nonce_str",nonce_str); parameters.put("out_trade_no",out_trade_no); parameters.put("total_fee",total_fee); parameters.put("trade_type",trade_type); parameters.put("openid",openid); parameters.put("return_code",return_code); parameters.put("result_code",result_code); parameters.put("bank_type",bank_type); parameters.put("cash_fee",cash_fee); parameters.put("fee_type",fee_type); parameters.put("is_subscribe",is_subscribe); parameters.put("time_end",time_end); parameters.put("transaction_id",transaction_id); String sign2 = WxSign.createSign(parameters, WeChatConst.KEY); if(sign.equals(sign2)){ log.debug(">>>>>>>>>>>>>>>>>校驗簽名完成。正常交易。開始校驗支付狀態..."); if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){ log.debug(">>>>>>>>>>>>>>>>>校驗支付狀態完成。支付成功!"); // 業務邏輯(先判斷數據庫中訂單號是否存在,而且訂單狀態爲未支付狀態) MarketingOrder marketingOrder = new MarketingOrder(); marketingOrder.setOutTradeNo(out_trade_no); marketingOrder.setPayAmount(total_fee); AjaxResult affordOrder = mOrder.affordOrder(marketingOrder); if(!affordOrder.getSuccess()){ response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失敗]]></return_msg></xml>"); } // TODO:這個有什麼用嗎?暫未肯定... request.setAttribute("out_trade_no", out_trade_no); // 通知微信.異步確認成功.必寫.否則會一直通知後臺.八次以後就認爲交易失敗了. log.debug(">>>>>>>>>>>>>>>>>本地異步確認支付成功!交易結束!"); response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"); }else{ log.debug(">>>>>>>>>>>>>>>>>本地異步確認支付失敗!交易結束!"); response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失敗]]></return_msg></xml>"); } }else{ log.debug(">>>>>>>>>>>>>>>>>校驗簽名完成。異常交易!結束交易流程。"); response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名校驗失敗]]></return_msg></xml>"); } response.getWriter().flush(); response.getWriter().close(); return ; } catch (IOException e) { e.printStackTrace(); } } }
import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSONObject; import com.meihe.dto.wePay.UserInfo; import com.meihe.service.wePay.IOauthService; import com.meihe.utils.wePayUtils.HttpUtil; @Service public class OauthServiceImpl implements IOauthService { private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class); // private static final String CODE_URI = "http://open.weixin.qq.com/connect/oauth2/authorize"; private static final String TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/access_token"; private static final String REFRESH_TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/refresh_token"; private static final String SNS_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo"; public String getToken(String code, String appid, String secret) throws Exception { log.debug(">>>>>>>>>>>>>>>>>準備根據code獲取用戶open_id/accessToken..",code,appid,secret); Map<String,Object> params = new HashMap<String,Object>(); params.put("appid", appid); params.put("secret", secret); params.put("code", code); params.put("grant_type", "authorization_code"); String result = HttpUtil.sendGet(TOKEN_URI,params); log.debug(">>>>>>>>>>>>>>>>>獲取信息完成[{}]...",result); return result; } public String getRefreshToken(String refreshToken, String appid,String secret) throws Exception { Map<String,Object> params = new HashMap<String,Object>(); params.put("appid", appid); params.put("grant_type", "refresh_token"); params.put("refresh_token", refreshToken); return HttpUtil.sendGet(REFRESH_TOKEN_URI, params); } public UserInfo getSnsUserInfo(String openid, String accessToken)throws Exception { Map<String,Object> params = new HashMap<String,Object>(); log.info(">>>>>>>>>>>>>>>>>轉格式成功!準備根據open_id/accessToken獲取用戶信息...!"); params.put("access_token", accessToken); params.put("openid", openid); params.put("lang", "zh_CN"); log.debug(">>>>>>>>>>>>>>>>>token:[{}],openid[{}].開始鏈接微信獲取信息..",accessToken,openid); String jsonStr = HttpUtil.sendGet(SNS_USER_INFO_URL, params); log.debug(">>>>>>>>>>>>>>>>>成功獲取信息[{}]。轉JSON爲UserInfo..",jsonStr); if (StringUtils.isNotEmpty(jsonStr)) { JSONObject obj = JSONObject.parseObject(jsonStr); if (obj.get("errcode") != null) { throw new Exception(obj.getString("errmsg")); } UserInfo user = (UserInfo) JSONObject.toJavaObject(obj,UserInfo.class); log.debug(">>>>>>>>>>>>>>>>>轉UserInfo格式完成!"); return user; } return null; } }