官方文檔
準備工做:已經過微信認證的公衆號,必須經過ICP備案域名(不然會報支付失敗)
借鑑了不少大神的文章,在此先謝過了javascript
整個支付流程,看懂就很好寫了php
在微信公衆平臺設置您的公衆號支付支付目錄,設置路徑見下圖。公衆號支付在請求支付的時候會校驗請求來源是否有在公衆平臺作了配置,因此必須確保支付目錄已經正確的被配置,不然將驗證失敗,請求支付不成功。html
支付受權目錄就是指支付方法的請求全路徑前端
開發公衆號支付時,在統一下單接口中要求必傳用戶openid,而獲取openid則須要您在公衆平臺設置獲取openid的域名,只有被設置過的域名纔是一個有效的獲取openid的域名,不然將獲取失敗。具體界面以下圖所示:java
受權暫時參考個人另外一篇的文章,點我參考 ,根據實際需求,我是採用靜默受權web
/** * 受權進入支付頁面 * * @param request * @param response * @param url * @return * @throws Exception */ @RequestMapping(value = "prePayPage", method = { RequestMethod.GET }) public String jsPay(HttpServletRequest request, HttpServletResponse response) throws Exception { AuthAccessToken authAccessToken = null; String code = request.getParameter("code");//可用redis保存 if(code==null){ return null; } String state = request.getParameter("state"); if(state.equals(MD5Util.MD5Encode("ceshi", ""))){ AuthTokenParams authTokenParams = new AuthTokenParams(); authTokenParams.setAppid("your appid"); authTokenParams.setSecret(""your secret"); authTokenParams.setCode(code); authAccessToken = oAuthService.getAuthAccessToken(authTokenParams, ACCESS_TOKEN_URL); } if(authAccessToken!=null){ logger.info("正在支付的openid=" + authAccessToken.getOpenid()); } return "system/wxpay/testpay"; }
AuthTokenParams.javaajax
/** * 獲取受權請求token的請求參數 * @author phil * @date 2017年7月2日 * */ public class AuthTokenParams extends AbstractParams { private String appid; //公衆號的惟一標識 private String secret; //公衆號的appsecret private String code; //填寫第一步獲取的code參數 private String grant_type = "authorization_code"; public AuthTokenParams() { super(); } public AuthTokenParams(String appid, String secret, String code, String grant_type) { super(); this.appid = appid; this.secret = secret; this.code = code; this.grant_type = grant_type; } /** * 參數組裝 * @return */ public Map<String, String> getParams() { Map<String, String> params = new TreeMap<String, String>(); params.put("appid", this.appid); params.put("secret", this.secret); params.put("code", this.code); params.put("grant_type", this.grant_type); return params; } /get、set方法 }
支付頁面的bodyredis
<script type="text/javascript"> var prepay_id ; var paySign ; var appId ; var timeStamp ; var nonceStr ; var packageStr ; var signType ; function pay(){ var url = '${ctx}/wxpay/jsPay'; $.ajax({ type:"post", url:url, dataType:"json", data:{openId:'${openId}'}, success:function(data) { if(data.resultCode == 'SUCCESS'){ appId = data.appId; paySign = data.paySign; timeStamp = data.timeStamp; nonceStr = data.nonceStr; packageStr = data.packageStr; signType = data.signType; callpay(); }else{ alert("統一下單失敗"); } } }); } function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":appId, //公衆號名稱,由商戶傳入 "paySign":paySign, //微信簽名 "timeStamp":timeStamp, //時間戳,自1970年以來的秒數 "nonceStr":nonceStr , //隨機串 "package":packageStr, //預支付交易會話標識 "signType":signType //微信簽名方式 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { //window.location.replace("index.html"); alert('支付成功'); }else if(res.err_msg == "get_brand_wcpay_request:cancel"){ alert('支付取消'); }else if(res.err_msg == "get_brand_wcpay_request:fail" ){ alert('支付失敗'); } //使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回 ok,但並不保證它絕對可靠。 } ); } function callpay(){ 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(); } } </script>
統一下單的官方文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 備註:若是獲取prepay_id基本上支付成功了spring
/** * 微信內H5調起支付 * * @param request * @param response * @param openId * @return * @throws Exception */ @ResponseBody @RequestMapping("jspay") public JsPayResult jsPay(HttpServletRequest request, HttpServletResponse response, String openId) throws Exception { JsPayResult result = null; logger.info("****正在支付的openId****" + openId); // 統一下單 String out_trade_no = PayUtil.createOutTradeNo(); int total_fee = 1; // 產品價格1分錢,用於測試 String spbill_create_ip = HttpReqUtil.getRemortIP(request); logger.info("支付IP" + spbill_create_ip); String nonce_str = PayUtil.createNonceStr(); // 隨機數據 //參數組裝 UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams(); unifiedOrderParams.setAppid(WeChatConfig.APP_ID);// 必須 unifiedOrderParams.setMch_id(WeChatConfig.MCH_ID);// 必須 unifiedOrderParams.setOut_trade_no(out_trade_no);// 必須 unifiedOrderParams.setBody("支付測試");// 必須 unifiedOrderParams.setTotal_fee(total_fee); // 必須 unifiedOrderParams.setNonce_str(nonce_str); // 必須 unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必須 unifiedOrderParams.setTrade_type("JSAPI"); // 必須 unifiedOrderParams.setOpenid(openId); unifiedOrderParams.setNotify_url(WeChatConfig.NOTIFY_URL);// 異步通知url // 統一下單 請求的Xml(正常的xml格式) String unifiedXmL = wechatPayService.abstractPayToXml(unifiedOrderParams);////簽名併入service // 返回<![CDATA[SUCCESS]]>格式的XML String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.POST_METHOD,WeChatConfig.UNIFIED_ORDER_URL, null, unifiedXmL); // 進行簽名校驗 if (SignatureUtil.checkIsSignValidFromWeiXin(unifiedOrderResultXmL)) { String timeStamp = PayUtil.createTimeStamp(); //統一下單響應 UnifiedOrderResult unifiedOrderResult = XmlUtil.getObjectFromXML(unifiedOrderResultXmL, UnifiedOrderResult.class); /**** 用map方式進行簽名 ****/ // SortedMap<Object, Object> signMap = new TreeMap<Object, // Object>(); // signMap.put("appId", WeiXinConfig.APP_ID); // 必須 // signMap.put("timeStamp", timeStamp); // signMap.put("nonceStr", nonceStr); // signMap.put("signType", "MD5"); // signMap.put("package", "prepay_id=" + prepay_id); // String paySign = SignatureUtil.createSign(signMap, key, SystemConfig.CHARACTER_ENCODING); result = new JsPayResult(); result.setAppId(WeChatConfig.APP_ID); result.setTimeStamp(timeStamp); result.setNonceStr(unifiedOrderResult.getNonce_str());//直接用返回的 /**** prepay_id 2小時內都有效,再次支付方法本身重寫 ****/ result.setPackageStr("prepay_id=" + unifiedOrderResult.getPrepay_id()); /**** 用對象進行簽名 ****/ String paySign = SignatureUtil.createSign(result, WeChatConfig.API_KEY, SystemConfig.CHARACTER_ENCODING); result.setPaySign(paySign); result.setResultCode("SUCCESS"); } else { logger.info("簽名驗證錯誤"); } /**** 返回對象給頁面 ****/ return result; }
JsPayResult.javaapache
package com.phil.wechatpay.model.resp; import com.phil.wechatpay.model.rep.JsPayParams; /** * 微信內H5返回結果 * @author phil * @date 2017年6月27日 * */ public class JsPayResult extends JsPayParams { /** * */ private static final long serialVersionUID = 392188712101246402L; private String errMsg; private String resultCode; public String getErrMsg() { return errMsg; } public void setErrMsg(String errMsg) { this.errMsg = errMsg; } public String getResultCode() { return resultCode; } public void setResultCode(String resultCode) { this.resultCode = resultCode; } }
JsPayParams.java
package com.phil.wechatpay.model.rep; import java.io.Serializable; /** * 微信內H5調起支付參數 * @author phil * @date 2017年6月27日 * */ public class JsPayParams implements Serializable{ /** * */ private static final long serialVersionUID = 8255883197124904824L; private String appId; // 公衆號id private String timeStamp; // 時間戳 格式1414561699 private String nonceStr; // 隨機字符串 private String packageStr; // package參數 訂單詳情擴展字符串 prepay_id=*** private String signType = "MD5"; // 簽名方式 private String paySign; // 簽名 public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getPackageStr() { return packageStr; } public void setPackageStr(String packageStr) { this.packageStr = packageStr; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } public String getPaySign() { return paySign; } public void setPaySign(String paySign) { this.paySign = paySign; } }
支付通知結果官方文檔 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
package com.phil.wechatpay.controller; import java.io.BufferedOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.phil.common.result.ResultState; import com.phil.common.util.HttpReqUtil; import com.phil.common.util.SignatureUtil; import com.phil.common.util.XmlUtil; import com.phil.wechatpay.model.resp.PayNotifyResult; /** * 微信支付結果通知(統一下單參數的notify_url) * @author phil * @date 2017年6月27日 * */ @Controller @RequestMapping("/wxpay/") public class WechatPayNotifyController { static final Logger logger = Logger.getLogger(WechatPayNotifyController.class); @ResponseBody @RequestMapping("notify") public ResultState notify(HttpServletRequest request, HttpServletResponse response) throws Exception { ResultState resultState = new ResultState(); logger.info("開始處理支付返回的請求"); String resXml = ""; // 反饋給微信服務器 String notifyXml = HttpReqUtil.inputStreamToStrFromByte(request.getInputStream());// 微信支付系統發送的數據(<![CDATA[product_001]]>格式) logger.debug("微信支付系統發送的數據"+notifyXml); // 驗證簽名 if (SignatureUtil.checkIsSignValidFromWeiXin(notifyXml)) { PayNotifyResult notify = XmlUtil.getObjectFromXML(notifyXml, PayNotifyResult.class); logger.debug("支付結果" + notify.toString()); if ("SUCCESS".equals(notify.getResult_code())) { resultState.setErrcode(0);// 表示成功 resultState.setErrmsg(notify.getResult_code()); /**** 業務邏輯 保存openid之類的****/ // 通知微信.異步確認成功.必寫.否則會一直通知後臺.八次以後就認爲交易失敗了 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { resultState.setErrcode(-1);// 支付失敗 resultState.setErrmsg(notify.getErr_code_des()); logger.info("支付失敗,錯誤信息:" + notify.getErr_code_des()); resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" + notify.getErr_code_des() + "]]></return_msg>" + "</xml> "; } } else { resultState.setErrcode(-1);// 支付失敗 resultState.setErrmsg("簽名驗證錯誤"); logger.info("簽名驗證錯誤"); 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(); return resultState; } }
ResultState.java
package com.phil.common.result; import java.io.Serializable; /** * 微信API返回狀態 * * @author phil * @date 2017年7月2日 * */ public class ResultState implements Serializable { /** * */ private static final long serialVersionUID = 1692432930341768342L; //@SerializedName("errcode") private int errcode; // 狀態 //@SerializedName("errmsg") private String errmsg; //信息 public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } }