本文主要和大家分享微信支付之公众号支付详解,随着微信支付的流行,大多产品都开发了自己的公众号、小程序等,产品的营销需要支付的支撑,最近做了个微信公号号支付,采坑无数,今天给大家分享一下,希望能帮助到大家。
可以先看一下微信支付API文档
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317853&token=&lang=zh_CN
建议先看一下微信的开发者文档,虽然有点坑 。。。。
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
应用场景
<br/>
所需jar包
<!-- 微信支付需要的jar包 --> <dependency> <groupId>xmlpull</groupId> <artifactId>xmlpull</artifactId> <version>1.1.3.1</version> </dependency> <dependency> <groupId>xpp3</groupId> <artifactId>xpp3</artifactId> <version>1.1.4c</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.3.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.3.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient-cache</artifactId> <version>4.3.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.3.2</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.3.5</version> </dependency>
公众号支付所需参数<br/>
public class WeixinMatchPayConfigure { /** * 域名 项目域名,根据需求自行配置 */ public static final String ROOTURL = WeixinPayConfigure.ROOTURL; /** * 订单域名 项目域名,根据需求自行配置 */ public static final String ORDER_ROOTURL = WeixinPayConfigure.ORDER_ROOTURL; /** * 赛事 域名 项目域名,根据需求自行配置 */ public static final String MATCHURL = "http://www.baidu.com"; /** * 公共账号id 必填 (18位数↓) */ public static final String APPID = WeixinPayConfigure.APPID; /** * 商户id 商户账号 必填 */ public static final String MCH_ID = "11111111"; /** * 应用秘钥 必填(可在微信商户平台上查找) */ public static final String APP_SECRET = "fd87878fsf87fsf8cvsd8"; /**API秘钥*/ 必填(可在微信商户平台上查找) public static final String API_KEY = "fsdfn23482njdvjw23455555"; /** * 统一下单URL 微信官方提供 */ public static final String PAY_UNIFIED_ORDER_API = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 微信公众号交易类型 (扫码支付类型:NATIVE,公众号支付类型:JSAPI) */ public static final String TRADE_TYPE = "JSAPI"; /** * 获取code的回调地址 你项目要展示的首页路径 */ public static final String REDIRECT_URI = "http://order.uxuexi.com/pay/apply.html"; /**微信H5支付结果通知页*/ public static final String NOTIFY_URL = ROOTURL + "/api/pay/weixin/notify.html"; /** * 不弹出授权页面,直接跳转,只能获取用户openid */ public static final String SCOPE = "snsapi_base"; /** * 弹出授权页面,需要用户确认,可以获取用户的较多信息 */ public static final String USERINFOSCOPE = "snsapi_userinfo"; /** * 获取微信code的url(登录授权) */ public static final String GET_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect"; /** * 获取用户的OpenId的url(必须先获得code之后调用) */ public static final String GET_OPENID_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"; /** * 微信支付成功之后的回调 */ public static final String NOTIFY_ACTIVITY_URL = WeixinPayConfigure.ORDER_ROOTURL + "/pay/wxnotify.json"; }
获取微信用户的openId<br/>
大致步骤:获取用户授权(获取code)------------->根据code获取openID(用户的基本信息)
1、配置授权域
这个则是在公众号登陆平台上面配置的↓
<br/>
2、发起API请求获取用户授权
URL:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
需要拼接三个参数:APPID、REDIRECT_URI、SCOPE,代码如下:
//项目入口,如果这个项目是在公众号中,那么公众号菜单下面配置的就是这个接口的路径↓ @At @Ok("jsp:match.entrance") @NotSso public Object entrance() { String codeUrl = WeiXinApiUrlUtil.getMatchUrl(); return codeUrl; } public static String getMatchUrl() { String url = WeixinMatchPayConfigure.GET_CODE_URL; url = url.replace("APPID", urlEnodeUTF8(WeixinMatchPayConfigure.APPID)); url = url.replace("REDIRECT_URI", WeixinMatchPayConfigure.REDIRECT_URI); url = url.replace("SCOPE", WeixinMatchPayConfigure.USERINFOSCOPE); return url; }
返回前端的是个Url路径,同个这个路径来获取微信用户授权,然后跳转我们自己的首页(REDIRECT_URI) 前端页面 <script type="text/javascript"> $(document).ready(function(){ //其实这个时候跳转的URL它会跟着一连串属性,入下图 ↓ window.location.href='${obj}'; }); </script>
3、微信用户openId是什么?<br/>
在微信用户关注公众号时,会对应的产生一个openId(openId:加密后的用户微信号),一个微信用户对应一个公众号产生的openId是唯一,当然,如果该微信号去关注另一个公众号所产生openID肯定和当前这个不一样的,OpenID 是最对『微信应用』的用户唯一值,同一个『微信开发者账号』下的不同应用中,使用同一个『微信用户』登录,此值会不一样,
废话不说,上代码↓
①、上文表明此接口请求带有参数code,那么需在这里接受code参数
@At @Ok("jsp:match.apply") @NotSso public Object apply(@Param("code") String code) { return matchPayViewService.apply(code); }
②、获取openID放到session中去
public Object apply(String code) { //创建空map Map<String, Object> map = MapUtil.map(); //获取session HttpSession session = Mvcs.getReq().getSession(); //获取session中oppenID String oppendId = ConvertUtil.obj2str(session.getAttribute("oppendId")); //非空校验oppenID if (!Util.isEmpty(oppendId)) { map.put("isWeChat", "yes"); return map; } //校验code是否为空,为空说明不是微信公众号支付 if (Util.isEmpty(code)) { map.put("isWeChat", "no"); return map; } //获取访问用户的token,(工具类1) UserInfoAccessTokenDt accessTokenDt = userInfoAccessTokenDtBaseService.getUserAccessToken(code); //获取微信用户信息,(工具类2) UserInfoDt userInfo = userInfoDtBaseService.getUserInfo(accessTokenDt); ExceptionUtil.checkEmpty(userInfo, "获取微信用户信息失败"); map.put("userInfo", userInfo); int sessionInactive = 30 * 60; //把相关数据放到session中去 session.setMaxInactiveInterval(sessionInactive); session.setAttribute("oppendId", userInfo.getOpenid()); session.setAttribute("userInfo", userInfo); map.put("isWeChat", "yes"); //获取当前用户 map.put("userId", fetchUser.getCurrentUserId()); //-----------------------------------------------------权限校验,可根据自己的项目进行业务操作 map.put("isgxltUser", gXUnicomBusinessService.isPermission(fetchUser.getCurrentUserId())); return map; }
注:上面这些是获取微信用户授权、获取用户的基本信息,公众号支付需要用户的openID,所以。。。。。。
创建预支付Url
1、创建订单
①、此处不解释
@At @Ok("jsp:match.createorder") @NotSso public Object createOrder(@Param("..") final OrderAddForm orderAddForm, final String gradeName) { return matchPayViewService.createOrder(orderAddForm, gradeName); }
②、创建订单
public Map<String, Object> createOrder(OrderAddForm orderAddForm, String gradeName) { ExceptionUtil.checkId(orderAddForm.getMatchId(), "赛事ID不能为空"); Map<String, Object> map = MapUtil.map(); orderAddForm.setCustomPrice(matchBaseService.getPrice(orderAddForm.getMatchId()).getPrice()); OrderMatchEntity order = dbDao.insert(orderAddForm.toEntity()); map.put("order", order); map.put("gradeName", gradeName); //拼接预支付订单 String result = sendReqGetPreOrder(order); //转化为JsApiParam对象,(工具类3) JsApiParam jap = dowithWxReturn(result); map.put("jap", jap); return map; }
③、拼接预支付订单参数
private String sendReqGetPreOrder(OrderMatchEntity order) { ExceptionUtil.checkEmpty(order.getId(), "订单id不能为空"); Map<String, Object> params = MapUtil.map(); params.put("appid", WeixinMatchPayConfigure.APPID); params.put("mch_id", WeixinMatchPayConfigure.MCH_ID); params.put("notify_url", WeixinMatchPayConfigure.NOTIFY_ACTIVITY_URL); params.put("trade_type", WeixinMatchPayConfigure.TRADE_TYPE);//单次订单为jsapi方式 int randomNumLength = 32; params.put("nonce_str", RandomUtil.randomString(randomNumLength)); //获取openID params.put("openid", getWxUserInfoWithEx().getOpenid()); String body = "赛事报名"; params.put("body", body); params.put("out_trade_no", order.getId()); long total_fee = AmountUtils.changeY2F(order.getCustomPrice()); params.put("total_fee", total_fee); params.put("device_info", "WEB"); //加密签名,工具类(微信支付PC端文档中有) String sign = Signature.getSign(params, WeixinMatchPayConfigure.API_KEY); params.put("sign", sign); //HttpRequest 工具类(微信支付PC端文档中有) return HttpRequest.sendPost(WeixinMatchPayConfigure.PAY_UNIFIED_ORDER_API, params); } //获取微信标识 private UserInfoDt getWxUserInfoWithEx() { Object userInfo = Mvcs.getReq().getSession().getAttribute("userInfo"); if (Util.isEmpty(userInfo)) { throw ExceptionUtil.bEx("获取你的微信身份的标识失败请重新退出再次进入"); } //类型转换 return ConvertUtil.cast(userInfo, UserInfoDt.class); }
④、处理调用微信预支付订单的返回值
private JsApiParam dowithWxReturn(String result) { //该类见(工具类3) JsApiParam jsApiParam = new JsApiParam(); Map<String, Object> weixinPrepayInfo = MapUtil.map(); try { //------------------解析XML(工具类:微信支付PC端文档中有) weixinPrepayInfo = XMLParser.getMapFromXML(result); String return_code = (String) weixinPrepayInfo.get("return_code"); if ("SUCCESS".equals(return_code)) { String prepay_id = (String) weixinPrepayInfo.get("prepay_id"); //给jsApiParam对象赋值 jsApiParam.setPrepay_id(prepay_id); jsApiParam.setPackageInfo("prepay_id=" + prepay_id); jsApiParam.setPaySign(getJsApiPaySign(jsApiParam)); return jsApiParam; } else { throw ExceptionUtil.bEx("预支付失败"); } } catch (Exception e) { ExceptionUtil.bEx("调用微信预支付接口出错"); } return jsApiParam; }
⑤、调用jsApiParam对象打回前台后,则需要调用微信内部的提供的js方法getBrandWCPayRequest
function onBridgeReady(){ //微信内部提供的js方法 WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":"${obj.jap.appId}", //公众号名称,由商户传入 "timeStamp":"${obj.jap.timeStamp}", //时间戳,自1970年以来的秒数 "nonceStr":"${obj.jap.nonceStr}", //随机串 "package":"${obj.jap.packageInfo}", "signType":"MD5", //微信签名方式: "paySign":"${obj.jap.paySign}" //微信签名 }, function(res){ // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 if (res.err_msg == "get_brand_wcpay_request:ok") { //跳转到成功页面 window.location.href = "http://lannong.uxuexi.com/register/success.html"; } else if (res.err_msg == "get_brand_wcpay_request:cancel") { WeixinJSBridge.call('closeWindow'); } else { } } ); }
⑥、调用⑤后页面则会弹出微信支付框,如图↓
<br/>
支付成功<br/>
1、支付成功后则调用成功后的回调函数
@At @Filters public void wxnotify() throws Exception { matchPayViewService.weChatPayViewService(); }
2、签名校验
public void weChatPayViewService() throws Exception { HttpServletRequest request = Mvcs.getReq(); HttpServletResponse response = Mvcs.getResp(); //获取微信响应的内容 String responseString = getWeiXinResponseContent(request); PrintWriter out = response.getWriter(); String resp = ""; String signKey = WeixinPayConfigure.API_KEY; //------------------------------签名校验↓,(工具类:微信支付PC端文档中有) boolean verify = Signature.checkIsSignValidFromResponseString(responseString, signKey); if (!verify) { logger.error("签名验证失败"); resp = "签名验证失败"; out.write(resp); out.close(); //签名失败直接返回 return; } //解析xml Map<String, Object> map = XMLParser.getMapFromXML(responseString); String result_code = ConvertUtil.obj2str(map.get("result_code")); if (!"SUCCESS".equalsIgnoreCase(result_code)) { resp = PayCommonUtil.getResponseXML("ERROR", "ERROR"); out.write(resp); out.close(); //支付失败直接返回 return; } //处理订单 resp = handleOrder(map); out.write(resp); out.close(); } //获取微信响应内容 private String getWeiXinResponseContent(HttpServletRequest request) throws IOException, UnsupportedEncodingException { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); } //获取微信调用我们notify_url的返回信息 String responseString = new String(outStream.toByteArray(), "utf-8"); outStream.close(); inStream.close(); return responseString; }
3、处理订单,此处业务不再讲解,不懂得可以去看PC端微信支付文档
@Aop("txDb") private String handleOrder(Map<String, Object> map) throws Exception { String resp = PayCommonUtil.getResponseXML("SUCCESS", "OK"); String transaction_id = ConvertUtil.obj2str(map.get("transaction_id")); String time_end = ConvertUtil.obj2str(map.get("time_end")); String out_trade_no = (String) map.get("out_trade_no"); if (Util.isEmpty(transaction_id) || Util.isEmpty(time_end) || Util.isEmpty(out_trade_no)) { resp = PayCommonUtil.getResponseXML("ERROR", "参数错误,微信支付订单号、支付完成时间、订单号均不能为空"); return resp; } OrderMatchEntity order = dbDao.fetch(OrderMatchEntity.class, ConvertUtil.obj2long(out_trade_no)); if (Util.isEmpty(order)) { resp = PayCommonUtil.getResponseXML("ERROR", "订单不存在"); return resp; } int orderStatus = order.getStatus(); if (OrderStatusEnum.FINISHED.intKey() == orderStatus) { return resp; } if (OrderStatusEnum.WAITING_PAY.intKey() == orderStatus) { //此处写你所需的业务即可 //更新订单为完成状态 //实际支付金额(分) //添加支付记录 } return resp; }
4、公众号微信支付,支付后微信会返回三种状态,如图↓
<br/>
那么,关于三种状态我们所需跳转的页面则可以在前台用js来实现<br/>
function(res){ // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 if (res.err_msg == "get_brand_wcpay_request:ok") { //跳转到成功页面 window.location.href = "http://lannong.uxuexi.com/register/success.html"; } else if (res.err_msg == "get_brand_wcpay_request:cancel") { WeixinJSBridge.call('closeWindow'); } else { } }
工具类<br/>
1、获取访问用户的token
@IocBean public class UserInfoAccessTokenDtBaseService { /** * 通过code获取用户的openId * * @param code 编号 * * @return 用户的openId */ public UserInfoAccessTokenDt getUserAccessToken(String code) { ExceptionUtil.checkEmpty(code, "用户同意授权,获取的code不能为空"); //获取用户openid的连接 String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WeixinH5PayConfigure.APPID + "&secret=" + WeixinH5PayConfigure.APP_SECRET + "&code=" + code + "&grant_type=authorization_code"; Response res = Http.get(url); String content = res.getContent(); //----------------------------------从 JSON 字符串中,根据获取某种指定类型的 JSON 对象 UserInfoAccessTokenDt accessTokenDt = JsonUtil.fromJson(content, UserInfoAccessTokenDt.class); return accessTokenDt; } }
2、获取微信用户基本信息
@IocBean public class UserInfoDtBaseService { /** * 获取用户信息 * * @param accessTokenDt 获取用户信息的token * * @return 用户信息对象 */ public UserInfoDt getUserInfo(UserInfoAccessTokenDt accessTokenDt) { ExceptionUtil.checkEmpty(accessTokenDt, "访问用户的accessToken不能为空"); String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessTokenDt.getAccess_token() + "&openid=" + accessTokenDt.getOpenid() + "&lang=zh_CN"; Response res = Http.get(url); String content = res.getContent(); //------------------------------------- 从 JSON 字符串中,根据获取某种指定类型的 JSON 对象。↓ UserInfoDt UserInfo = JsonUtil.fromJson(content, UserInfoDt.class); return UserInfo; } } @Data public class UserInfoAccessTokenDt { //TODO(注释去这个链接下找:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html) private String access_token; private String expires_in; private String refresh_token; private String openid; private String scope; private String unionid; }
3、微信公众号支付参数
@Data public class JsApiParam { /** * 公众号appid */ private String appId = WeixinH5PayConfigure.APPID; /** * 时间戳 */ private String timeStamp = System.currentTimeMillis() + ""; /** * 随机字符串 */ private String nonceStr = RandomUtil.randIntString(32); /** * 签名方式 */ private String signType = "MD5"; /** * 预支付id */ private String packageInfo; /** * 支付签名 */ private String paySign; /** * 订单号 */ private String orderNo; /** * 微信预付单号 */ private String prepay_id; }
Atas ialah kandungan terperinci 微信支付之公众号支付详解. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!