前言
因為老是用到還需要回顧以前的代碼,是以寫篇文章記錄一下,同時也希望可以幫助到正好有需要的朋友。
準備
使用微信JSAPI支付需要的幾個重要參數:
- 登入公衆号獲得appId,appsecret,配置驗證檔案
- 公衆号設定-功能設定-網頁授權域名-添加域名,下載下傳驗證檔案MP_verify_Q0U1Chj03asdacac.txt
- 配置域名 http://域名/MP_verify_Q0U1Chj03asdacac.txt
- 登入商戶号獲得商戶号mchId,配置商戶号
- 商戶号-賬号中心-安全中心-設定apikey
- 商戶号-産品中心-開通jsapi支付
- 商戶号-産品中心-開發配置,設定JSAPI支付授權目錄
準備工作結束,到這裡我們就拿到了appId,appsecret,mchId,apikey。
開發
- 擷取使用者openId,openId為微信使用者對應一個公衆号的唯一辨別。我采用的是網頁授權的方式擷取使用者資訊(OAuth2.0)
- 使用者同意授權擷取code
- https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx2b9faf36c563c77c&redirect_uri=接口&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
- 接口為你自己寫的接口,通過上面的方式在微信浏覽器請求,微信伺服器會回調你的接口并帶上code參數,注意接口需要進行urlecode編碼。例如我的接口為http://域名/pay
@RequestMapping("pay") public Stringvote(HttpServletRequest request) { //微信回調給你的code String code = request.getParameter("code"); //因為我的業務需求,把code傳回支付頁面,通過code擷取支付參數 request.setAttribute("code", code); return "支付頁面"; }
- scope=snsapi_base隻能擷取openId,scope=snsapi_userinfo可以獲得使用者的頭像昵稱等,但是這個時候會彈框提示使用者,将擷取你的昵稱什麼的需要使用者确認。如下圖
- 通過code換取網頁授權access_token,獲得openid,直接貼代碼
請求上圖代碼中的o_auth_openid_url 傳回結果格式,OAuthInfo 為自己寫的實體類,封裝了這幾個屬性友善自己用。/** * 擷取微信使用者的openid,Access_token * * @param appid * @param secret * @param code * @return */ public static OAuthInfo getAccess_token(String appid, String secret, String code) { OAuthInfo oAuthInfo = null; String o_auth_openid_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code"; //請求o_auth_openid_url,傳回結果是json格式 String result = HttpUtils.oAuth(o_auth_openid_url); if (CommonUtil.isEmpty(result)) { return oAuthInfo; } JSONObject jsonObject = JSONObject.parseObject(result); // 如果請求成功 if (!CommonUtil.isEmpty(jsonObject)) { try { oAuthInfo = new OAuthInfo(); oAuthInfo.setAccessToken(jsonObject.getString("access_token")); oAuthInfo.setExpiresIn(jsonObject.getIntValue("expires_in")); oAuthInfo.setRefreshToken(jsonObject.getString("refresh_token")); oAuthInfo.setOpenId(jsonObject.getString("openid")); oAuthInfo.setScope(jsonObject.getString("scope")); } catch (JSONException e) { oAuthInfo = null; } } return oAuthInfo; }
{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }
- 如果不需要獲得使用者昵稱頭像資訊,在這裡我們已經拿到了openid,可以跳過第三步,如果需要繼續看,注意這裡的code必須是第一步scope=snsapi_userinfo擷取的code,因為我不需要使用者資訊,是以我用的是scope=snsapi_base
請求上圖代碼中的getWxUserInfoUrl 傳回格式如下,wxUserInfo 為自己定義的實體類,封裝了下面的參數友善自己調用。/** * * 擷取微信使用者基本資訊,公衆平台(微信網頁授權版,access_token和通用access_token不一樣,通用的有調用限制,網頁版無限制) * * @param appid * @param secret * @param code * @return */ public static WxUserInfo getWxUserInfo(String appid, String secret, String code) { WxUserInfo wxUserInfo = null; String o_auth_openid_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code"; String result = HttpUtils.oAuth(o_auth_openid_url); if (CommonUtil.isEmpty(result)) { return null; } JSONObject jsonObject = JSONObject.parseObject(result); String access_token = jsonObject.getString("access_token"); String openid = jsonObject.getString("openid"); if (CommonUtil.isEmpty(access_token) || CommonUtil.isEmpty(openid)) { return wxUserInfo; } String getWxUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + access_token + "&openid=" + openid + "&; try { result = HttpUtils.doGet(getWxUserInfoUrl); JSONObject userJson = JSONObject.parseObject(result); String errcode = userJson.getString("errcode"); if (CommonUtil.isEmpty(errcode)) { wxUserInfo = new WxUserInfo(); wxUserInfo.setOpenId(userJson.getString("openid")); wxUserInfo.setNickName(userJson.getString("nickname")); wxUserInfo.setSex(userJson.getInteger("sex")); wxUserInfo.setProvince(userJson.getString("province")); wxUserInfo.setCity(userJson.getString("city")); wxUserInfo.setCountry(userJson.getString("country")); wxUserInfo.setHeadimgUrl(userJson.getString("headimgurl")); wxUserInfo.setUnionId(userJson.getString("unionid")); } } catch (Exception e) { wxUserInfo = null; } return wxUserInfo; }
{ "openid":" OPENID", " nickname": NICKNAME, "sex":"1", "province":"PROVINCE" "city":"CITY", "country":"COUNTRY", "headimgurl": "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46", "privilege":[ "PRIVILEGE1" "PRIVILEGE2" ], "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" }
- 使用者同意授權擷取code
- 擷取openId之後,我們就可以調用微信支付,擷取JSAPI支付所需要的參數,服務端代碼,直接上代碼
其中payJson就是頁面調用微信支付所需要的參數。// 微信基礎支付url,app,web,js public final static String WX_BASE_PAYURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //使用者支付之後,微信伺服器回調你的接口通知你使用者已經支付 public final static String WEIXIN_APPPAY_NOTIFY_URL = "https://域名/wxWebPayNotify"; public static JSONObject unifiedAppOrderJsApi(Long price, String code) throws Exception { HashMap<String, Object> payParams = new HashMap<String, Object>(); //getAccess_token方法在第一步擷取code中 OAuthInfo authInfo =getAccess_token("appId","Appsecret", code); if (CommonUtil.isEmpty(authInfo) || CommonUtil.isEmpty(authInfo.getOpenId())) { return null; } System.out.println(authInfo.toString()); //公衆号appid payParams.put("appid", "appId"); //商戶号id payParams.put("mch_id","MchId"); //随機字元串 payParams.put("nonce_str","C48F31602C2D8A525DE00FA01CB91554"); //标題 payParams.put("body", "夢幻西遊點卡充值50"); //你自己的訂單号 payParams.put("out_trade_no","WX20190123110332917098E88"); //商品價格,機關分 payParams.put("total_fee", 5000); //使用者openId payParams.put("openid", authInfo.getOpenId()); //調用微信支付的服務端ip payParams.put("spbill_create_ip","服務端ip"); //支付完成之後,微信回調位址 payParams.put("notify_url", WEIXIN_APPPAY_NOTIFY_URL); //支付方式,固定值 payParams.put("trade_type", "JSAPI"); //夢幻西遊點卡充值 對應的商品編号 payParams.put("product_id",10001); String sign = getSign(payParams,"apikey"); String postXml = createPostXml(payParams, sign); String result = HttpUtils.doPost(WX_BASE_PAYURL, postXml); Map<String, String> resultMap =parseXmlToMap(result); if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) { long timestamp = System.currentTimeMillis() / 1000; payParams = new HashMap<String, Object>(); payParams.put("appId","appId"); payParams.put("package", "prepay_id=" + resultMap.get("prepay_id")); payParams.put("nonceStr", "C48F31602C2D8A525DE00FA01CB91554"); payParams.put("signType", "MD5"); payParams.put("timeStamp", timestamp); sign = getSign(payParams,"apikey"); JSONObject payJson = new JSONObject(); payJson.put("appid","appId"); payJson.put("prepayid", resultMap.get("prepay_id")); payJson.put("package", "prepay_id=" + resultMap.get("prepay_id")); payJson.put("noncestr", "C48F31602C2D8A525DE00FA01CB91554"); payJson.put("timestamp", timestamp); payJson.put("sign", sign); return payJson; } else { return null; } } /** * 組裝簽名串 * * @param params * @return */ public static String getSign(HashMap<String, Object> params, String key) { Map<String, Object> sortedParams = new TreeMap<String, Object>(params); Set<Entry<String, Object>> entrys = sortedParams.entrySet(); // 周遊排序後的字典,将所有參數按"key=value"格式拼接在一起 StringBuilder basestring = new StringBuilder(); for (Entry<String, Object> param : entrys) { basestring.append(param.getKey()).append("=").append(param.getValue()).append("&"); } String result = basestring.toString(); if (result.contains("&")) { result = result.substring(0, result.length() - 1); } result = result + "&key=" + key; // System.out.println("result---" + result); String sign = MD5.getMD5String(result).toUpperCase(); // System.out.println("sign---" + sign); return sign; } /** * 建立需要發送的post資料 * * @param params * @param sign * @return */ public static String createPostXml(HashMap<String, Object> params, String sign) { StringBuilder basestring = new StringBuilder(); basestring.append("<xml>"); for (String key : params.keySet()) { basestring.append("<").append(key).append(">").append(params.get(key)).append("</").append(key).append(">"); } basestring.append("<sign>").append(sign).append("</sign>"); basestring.append("</xml>"); return basestring.toString(); } /** * 将xml轉換為map對象 * * @param xml * @return */ public static Map<String, String> parseXmlToMap(String xml) { Map<String, String> result = new HashMap<String, String>(); try { Document dom = DocumentHelper.parseText(xml); Element root = dom.getRootElement(); Iterator<Element> iterator = root.elementIterator(); while (iterator.hasNext()) { Element element = iterator.next(); result.put(element.getName(), element.getText()); } } catch (DocumentException e) { e.printStackTrace(); } return result; }
結尾
至此,微信支付JSAPI方式完畢,後續有空了會把H5支付,掃碼支付,app支付,企業給使用者直接打錢到零錢這幾種方式都搞一套這樣的流程。如果有什麼寫的不對的地方歡迎大家留言,一起進步。