前言
因为老是用到还需要回顾以前的代码,所以写篇文章记录一下,同时也希望可以帮助到正好有需要的朋友。
准备
使用微信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
/** * * 获取微信用户基本信息,公众平台(微信网页授权版,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 + "&lang=zh_CN"; 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; }
请求上图代码中的getWxUserInfoUrl 返回格式如下,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支付所需要的参数,服务端代码,直接上代码
// 微信基础支付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; }
其中payJson就是页面调用微信支付所需要的参数。
结尾
至此,微信支付JSAPI方式完毕,后续有空了会把H5支付,扫码支付,app支付,企业给用户直接打钱到零钱这几种方式都搞一套这样的流程。如果有什么写的不对的地方欢迎大家留言,一起进步。