最近需要做微信公眾號支付,網上找了大堆的代碼,大多都只說了個原理,自己踩了太多坑,所有的坑,都會再下面的文章中標注,代碼我也貼上最全的(叫我雷鋒)!!!
第一步:配置支付授權目錄
你需要有將你公司的微信公眾號開通支付(審核要等個幾天),登錄后找到 微信支付-->開發配置,你會看到如下圖所示:
說明一下:配置支付授權目錄,就是當你再H5界面調起支付控件進行支付時,要對你支付的post請求進行校驗(再不懂看下面的調起微信支付控件代碼):
如果出現錯誤,支付界面一閃而過(有調起支付控件的跡象),你看不出報什么錯,你可以打印出微信返回的res:alert(JSON.stringify(res));
function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":appId, //公眾號名稱,由商戶傳入 "paySign":sign, //微信簽名 "timeStamp":timeStamp, //時間戳,自1970年以來的秒數 "nonceStr":nonceStr , //隨機串 "package":packageStr, //預支付交易會話標識 "signType":signType //微信簽名方式 }, function(res){ alert(JSON.stringify(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,但並不保證它絕對可靠。 } ); }
以下信息你都能從公眾好+商戶號中獲得:
public interface WeChatConst { //公眾號支付APPID //String APPID = "上圖中的appId"; //公眾號支付AppSecret //String APP_SECRET = "上圖中的secret"; //公眾號支付商戶號 String MCH_ID = "xxxx"; //商戶后台配置的一個32位的key,位置:微信商戶平台-賬戶中心-API安全 String KEY = "xxxxx"; //交易類型 String TRADE_TYPE = "JSAPI"; }
注:每個微信公眾號對應一個商戶號,當用戶關注你的公眾號后,如果在你的公眾號里面要進行支付操作,那么他支付的軟妹幣就流入到了你這個商戶號里去了
上面代碼中的 公眾號支付商戶號 哪里獲得?進入微信商戶號,登錄后找到導航欄的 帳戶中心--->商戶信息【就是上面接口WeChatConst中說的 公眾號支付商戶號 !!!】
KEY是啥? 同理,商戶號中 賬戶中心--API安全 ↓↓↓ 長度32位哦。。。。
做完上面公眾號配置跟商戶號配置,就開始寫代碼啦。。。。。。不知道上面這些步驟的,找起來真的累。。。。
第一步:授權(可以是用戶進你的界面就直接做,也可以用戶點擊界面中的某個按鈕再授權,主要是拿openId)
一開始我覺得不需要這個openId,取了干啥?難道后面的下單一定要?開發文檔里說了,trade_type=JSAPI時(即公眾號支付),此參數必傳,此參數為微信用戶在商戶對應appid下的唯一標識,好吧,老實點。。。。。
/** 跳轉支付界面,將code帶過去 **/ @RequestMapping("toPay.do") public ModelAndView toPay(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView modelAndView = new ModelAndView(); logger.debug("玩家准備填寫充值信息了:" + HttpUtil.buildOriginalURL(request)); //重定向Url String redirecUri = URLEncoder.encode(GlobalThreadLocal.getSiteConfig().getBasePath() + "/wxOfficialAccountsPay/toInputAccountInfo.do"); //用於獲取成員信息的微信返回碼 String code = null; if( request.getParameter("code")!=null ){ code =request.getParameter("code"); } if( code == null) { //授權 return authorization(redirecUri); } code =request.getParameter("code"); // 獲取用戶信息 WeixinLoginUser weixinLoginUser = getWeixinLoginUser(code); modelAndView.addObject("openId",des.getEncString(weixinLoginUser.getOpenID())); // 跳轉到支付界面 String viewName = "/wxOfficialAccountsPay/pay"; modelAndView.setViewName(viewName); return modelAndView; }
/** * 授權方法 * @param redirecUri 重定向鏈接 * * */ private ModelAndView authorization(String redirecUri) { String siteURL="redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +GlobalThreadLocal.getSiteConfig().getWeixin_appId() +"&redirect_uri="+redirecUri+"&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect"; logger.debug("授權路徑:[ "+siteURL+" ]"); return new ModelAndView(siteURL); }
注:GlobalThreadLocal.getSiteConfig().getWeixin_appId():是我配置的全局的appId
(就是前面公眾號圖中的的appId,我放一個接口來存這些信息,是給你們一個事例。。。) GlobalThreadLocal.getSiteConfig().getWeixin_appSecret() :公眾號的Secret
或許你會說為什么用scope=snsapi_userinfo 這個值(用戶界面操作有感知的授權)而不用 scope=snsapi_base ? 因為你用后者,在進行授權的時候,會報redirec_Uri出錯,反正我遇到了,到現在也沒搞懂。。。。

根據code,獲取用戶授權信息
/** * 獲取微信授權登陸用戶 * @param code * @return * @throws Exception */ private WeixinLoginUser getWeixinLoginUser(String code) throws Exception { logger.debug("由code獲取授權用戶信息"); Oauth oauth = new Oauth(); // 由code獲取access_token等信息 String str = oauth.getToken(code, GlobalThreadLocal.getSiteConfig().getWeixin_appId(), GlobalThreadLocal.getSiteConfig().getWeixin_appSecret()); // 解析返回的json數據,獲取所需的信息 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"); // 用openid,access_token獲取用戶的信息,返回userinfo對象 UserInfo userInfo = oauth.getSnsUserInfo(openID, accessToken); // 將用戶信息放入登錄session中 WeixinLoginUser weixinLoginUser = new WeixinLoginUser(); weixinLoginUser.setOpenID(openID); weixinLoginUser.setUnionID(userInfo.getUnionid()); weixinLoginUser.setHeadImageUrl(userInfo.getHeadimgurl()); weixinLoginUser.setNickName(userInfo.getNickname()); weixinLoginUser.setRefreshToken(refreshToken); // int siteID = GlobalThreadLocal.getSiteConfig().getSiteId(); weixinLoginUser.setSiteID(siteID); // 返回weixinLoginUser對象 return weixinLoginUser; }
用戶信息封裝類:
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 String getOpenID() { return openID; } public void setOpenID(String openID) { this.openID = openID; } public String getUnionID() { return unionID; } public void setUnionID(String unionID) { this.unionID = unionID; } public String getHeadImageUrl() { return headImageUrl; } public void setHeadImageUrl(String headImageUrl) { this.headImageUrl = headImageUrl; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } public String getRefreshToken() { return refreshToken; } public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } public int getSiteID() { return siteID; } public void setSiteID(int siteID) { this.siteID = siteID; } }
授權做完,openId拿到了,跳轉到支付界面,如下:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>支付界面</title> <script type="text/javascript" src="../public/jquery/jquery-2.0.3.min.js"></script> </head> <body> <input type="button" value="pay" onclick="pay()"/> <script> var prepay_id ; var sign ; var appId ; var timeStamp ; var nonceStr ; var packageStr ; var signType ; function pay(){ var url = '${ctx}/wxOfficialAccountsPay/pay.do'; $.ajax({ type:"post", url:url, dataType:"json", data:{openId:'${openId}'}, 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; //調起微信支付控件 callpay(); }else{ alert("統一下單失敗"); } } }); } function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":appId, //公眾號名稱,由商戶傳入 "paySign":sign, //微信簽名 "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> </body> </html>
點擊支付按鈕,進入下面一步,耐心點,繼續往下看,我寫到這里,感覺也弄了挺久了,心情好,繼續 2333
第三步:統一下單(支付前,需要獲取一系列支付參數,在這步做)
/** * 點擊確認充值 統一下單,獲得預付id(prepay_id) * @param request * @param response * @return */ @ResponseBody @RequestMapping({"pay"}) public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response,String openId){ WxPaySendData result = new WxPaySendData(); try { //商戶訂單號 String out_trade_no = WeChatUtil.getOut_trade_no(); //產品價格,單位:分 Integer total_fee = 1; //客戶端ip String ip = HttpUtil.getIpAddr(request); //支付成功后回調的url地址 String notify_url = "http://你的域名/odao-weixin-site/wxOfficialAccountsPay/callback.do"; //統一下單 String strResult = WeChatUtil.unifiedorder("testPay", out_trade_no, total_fee, ip, notify_url,openId); //解析xml XStream stream = new XStream(new DomDriver()); stream.alias("xml", WxPaySendData.class); WxPaySendData wxReturnData = (WxPaySendData)stream.fromXML(strResult); //兩者都為SUCCESS才能獲取prepay_id if( wxReturnData.getResult_code().equals("SUCCESS") && wxReturnData.getReturn_code().equals("SUCCESS") ){ //業務邏輯,寫入訂單日志(你自己的業務) ..... String timeStamp = WeChatUtil.getTimeStamp();//時間戳 String nonce_str = WeChatUtil.getNonceStr();//隨機字符串 注:上面這兩個參數,一定要拿出來作為后續的value,不能每步都創建新的時間戳跟隨機字符串,不然H5調支付API,會報簽名參數錯誤 result.setResult_code(wxReturnData.getResult_code()); result.setAppid(GlobalThreadLocal.getSiteConfig().getWeixin_appId()); result.setTimeStamp(timeStamp); result.setNonce_str(nonce_str); result.setPackageStr("prepay_id="+wxReturnData.getPrepay_id()); result.setSignType("MD5"); WeChatUtil.unifiedorder(.....) 下單操作中,也有簽名操作,那個只針對統一下單,要區別於下面的paySign //第二次簽名,將微信返回的數據再進行簽名 SortedMap<Object,Object> signMap = new TreeMap<Object,Object>(); signMap.put("appId", GlobalThreadLocal.getSiteConfig().getWeixin_appId()); signMap.put("timeStamp", timeStamp); signMap.put("nonceStr", nonce_str); signMap.put("package", "prepay_id="+wxReturnData.getPrepay_id()); //注:看清楚,值為:prepay_id=xxx,別直接放成了wxReturnData.getPrepay_id() signMap.put("signType", "MD5"); String paySign = WxSign.createSign(signMap, WeChatConst.KEY);//支付簽名 result.setSign(paySign); }else{ result.setResult_code("fail"); } } catch (Exception e) { e.printStackTrace(); } return result; }
統一下單封裝方法:
public class WeChatUtil { private static final Logger logger = Logger.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 IOException */ public static String unifiedorder(String body,String out_trade_no,Integer total_fee,String ip,String notify_url,String openId)throws IOException { /** * 設置訪問路徑 */ HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder"); String nonce_str = getNonceStr();//隨機數據 SortedMap<Object,Object> parameters = new TreeMap<Object,Object>(); /** * 組裝請求參數 * 按照ASCII排序 */ parameters.put("appid",GlobalThreadLocal.getSiteConfig().getWeixin_appId() ); parameters.put("body", body); parameters.put("mch_id", WeChatConst.MCH_ID ); parameters.put("nonce_str", nonce_str); parameters.put("out_trade_no", out_trade_no); parameters.put("notify_url", notify_url); parameters.put("spbill_create_ip", ip); parameters.put("total_fee",total_fee.toString() ); parameters.put("trade_type",WeChatConst.TRADE_TYPE ); parameters.put("openid", openId); String sign = WxSign.createSign(parameters, WeChatConst.KEY); /** * 組裝XML */ StringBuilder sb = new StringBuilder(""); sb.append("<xml>"); setXmlKV(sb,"appid",GlobalThreadLocal.getSiteConfig().getWeixin_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,"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); setXmlKV(sb,"openid",openId); sb.append("</xml>"); StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//這個處理是為了防止傳中文的時候出現簽名錯誤 httppost.setEntity(reqEntity); DefaultHttpClient httpclient = new DefaultHttpClient(); HttpResponse response = httpclient.execute(httppost); String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8")); return strResult; } //獲得隨機字符串 public static String getNonceStr(){ Random random = new Random(); return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8"); } //插入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()) + RandomChars.getRandomNumber(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; } public static void main(String[] args) { System.out.println(getOut_trade_no()); } }
簽名封裝類(網上很多,原理差不多):
public class WxSign { /** * 創建簽名 * @param parameters * @param key * @return */ @SuppressWarnings("rawtypes") public static String createSign(SortedMap<Object,Object> parameters,String key){ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet();//所有參與傳參的參數按照accsii排序(升序) 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(), "UTF-8").toUpperCase(); return sign; } }
支付參數封裝類(還算全,夠用了,這個appid,是小寫的 i):
/package com.odao.weixin.site.cases2017.wxpay.entity;
/**
* 微信支付參數
* @author wangfj
*
*/
public class WxPaySendData {
//公眾賬號ID
private String appid;
//附加數據
private String attach;
//商品描述
private String body;
//商戶號
private String mch_id;
//隨機字符串
private String nonce_str;
//通知地址
private String notify_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;
private String ip;
private Integer coupon_count;
private Integer coupon_fee;
private Integer coupon_fee_0;
private String coupon_type_0;
private String coupon_id_0;
public String getCoupon_type_0() {
return coupon_type_0;
}
public void setCoupon_type_0(String coupon_type_0) {
this.coupon_type_0 = coupon_type_0;
}
public String getCoupon_id_0() {
return coupon_id_0;
}
public void setCoupon_id_0(String coupon_id_0) {
this.coupon_id_0 = coupon_id_0;
}
public Integer getCoupon_fee_0() {
return coupon_fee_0;
}
public void setCoupon_fee_0(Integer coupon_fee_0) {
this.coupon_fee_0 = coupon_fee_0;
}
public Integer getCoupon_fee() {
return coupon_fee;
}
public void setCoupon_fee(Integer coupon_fee) {
this.coupon_fee = coupon_fee;
}
public Integer getCoupon_count() {
return coupon_count;
}
public void setCoupon_count(Integer coupon_count) {
this.coupon_count = coupon_count;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getBank_type() {
return bank_type;
}
public void setBank_type(String bank_type) {
this.bank_type = bank_type;
}
public Integer getCash_fee() {
return cash_fee;
}
public void setCash_fee(Integer cash_fee) {
this.cash_fee = cash_fee;
}
public String getFee_type() {
return fee_type;
}
public void setFee_type(String fee_type) {
this.fee_type = fee_type;
}
public String getIs_subscribe() {
return is_subscribe;
}
public void setIs_subscribe(String is_subscribe) {
this.is_subscribe = is_subscribe;
}
public String getTime_end() {
return time_end;
}
public void setTime_end(String time_end) {
this.time_end = time_end;
}
public String getTransaction_id() {
return transaction_id;
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getTotal_fee() {
return total_fee;
}
public void setTotal_fee(String total_fee) {
this.total_fee = total_fee;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getPackageStr() {
return packageStr;
}
public void setPackageStr(String packageStr) {
this.packageStr = packageStr;
}
@Override
public String toString() {
return "WxPaySendData [appid=" + appid + ", attach=" + attach
+ ", body=" + body + ", mch_id=" + mch_id + ", nonce_str="
+ nonce_str + ", notify_url=" + notify_url + ", out_trade_no="
+ out_trade_no + ", total_fee=" + total_fee + ", trade_type="
+ trade_type + ", spbill_create_ip=" + spbill_create_ip
+ ", openid=" + openid + ", sign=" + sign + ", prepay_id="
+ prepay_id + ", signType=" + signType + ", timeStamp="
+ timeStamp + ", packageStr=" + packageStr + ", return_code="
+ return_code + ", return_msg=" + return_msg + ", result_code="
+ result_code + ", bank_type=" + bank_type + ", cash_fee="
+ cash_fee + ", fee_type=" + fee_type + ", is_subscribe="
+ is_subscribe + ", time_end=" + time_end + ", transaction_id="
+ transaction_id + ", ip=" + ip + ", coupon_count="
+ coupon_count + ", coupon_fee=" + coupon_fee
+ ", coupon_fee_0=" + coupon_fee_0 + ", coupon_type_0="
+ coupon_type_0 + ", coupon_id_0=" + coupon_id_0 + "]";
}
}
好了,做完上面,你已經在微信支付控件里輸入了密碼,支付完了,跟着我的步伐,別亂。。。。
第四步:微信支付回調(還記得上面第三部中配置的notify_url個字段嗎,忘記了翻上去看看,為什么要做這一步?你需要告訴微信,你支付成功了,然后你可以在回調函數中寫你的業務邏輯。。。)
/** * 支付回調接口 * @param request * @return */ @RequestMapping("/callback") public void callBack(HttpServletRequest request, HttpServletResponse response){ 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(); //double money = DBUtil.getDBDouble(DBUtil.getDBInt(wxPaySendData.getTotal_fee())/100.0); 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);
//以下4個參數針對優惠券(鼓勵金之類的)這個坑真的弄了好久
parameters.put("coupon_count",wxPaySendData.getCoupon_count());
parameters.put("coupon_fee",wxPaySendData.getCoupon_fee());
parameters.put("coupon_id_0",wxPaySendData.getCoupon_id_0());
parameters.put("coupon_fee_0",wxPaySendData.getCoupon_fee_0()); String sign2 = WxSign.createSign(parameters, WeChatConst.KEY); if(sign.equals(sign2)){//校驗簽名,兩者需要一致,防止別人繞過支付操作,不付錢直接調用你的業務,不然,哈哈,你老板會很開心的 233333.。。。 if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){ //業務邏輯(先判斷數據庫中訂單號是否存在,並且訂單狀態為未支付狀態) //do something ... //request.setAttribute("out_trade_no", out_trade_no); //通知微信.異步確認成功.必寫.不然會一直通知后台.八次之后就認為交易失敗了. response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"); }else{ response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失敗]]></return_msg></xml>"); } }else{ //通知微信.異步確認成功.必寫.不然會一直通知后台.八次之后就認為交易失敗了. 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(); } }
如果上面的簽名校驗,你兩個簽名怎么都對不上,那么點這里:https://pay.weixin.qq.com/wiki/tools/signverify/ 如圖配置你上面的參數,再在控制台看你打印的簽名跟這個是不是一直,不一致肯定是上面的參數值有問題(在這里尤其注意那個金額 total_fee )。。。

好了,到了這里,我想整個流程你應該理解了,把上面的代碼COPY到你的項目里,我再補一下具體的工具類。。。如下:
/** * md5加密算法實現 */ public class MD5Util { private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; /** * md5加密 * * @param text 需要加密的文本 * @return */ public static String encode(String text) { try {// aes rsa MessageDigest md = MessageDigest.getInstance("MD5"); byte[] result = md.digest(text.getBytes()); // 對文本進行加密 // b // 000000..0000011111111 StringBuilder sb = new StringBuilder(); for (byte b : result) { int i = b & 0xff ; // 取字節的后八位有效值 String s = Integer.toHexString(i); if (s.length() < 2) { s = "0" + s; } sb.append(s); } // 加密的結果 return sb.toString(); } catch (NoSuchAlgorithmException e) { // 找不到該算法,一般不會進入這里 e.printStackTrace(); } return ""; } 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 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]; } }
好了,看到這里,我相信你也差不多大功告成了,出去溜達溜達。。。。然后。。。。