This article mainly shares with you the detailed explanation of WeChat payment official account payment.With the popularity of WeChat payment, most products have developed their own official accounts, mini programs, etc. The marketing of products needs the support of payment. Recently, we have done I set up a WeChat official account to pay, and there are countless pitfalls. I will share it with you today, hoping it can help everyone.
You can take a look at the WeChat payment API documentation first
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
It is recommended to take a look at the developer documentation of WeChat first, although it is a bit confusing. . . .
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
Application scenarios
Required jar package
xmlpull xmlpull 1.1.3.1 xpp3 xpp3 1.1.4c com.thoughtworks.xstream xstream 1.4.7 org.apache.httpcomponents fluent-hc 4.3.5 org.apache.httpcomponents httpclient 4.3.5 org.apache.httpcomponents httpclient-cache 4.3.5 org.apache.httpcomponents httpcore 4.3.2 org.apache.httpcomponents httpmime 4.3.5
Parameters required for public account payment
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"; }
Get WeChat users openId
大致步骤:获取用户授权(获取code)------------->根据code获取openID(用户的基本信息)
1、配置授权域
这个则是在公众号登陆平台上面配置的↓
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) 前端页面
3、微信用户openId是什么?
在微信用户关注公众号时,会对应的产生一个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 Mapmap = 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 MapcreateOrder(OrderAddForm orderAddForm, String gradeName) { ExceptionUtil.checkId(orderAddForm.getMatchId(), "赛事ID不能为空"); Map 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不能为空"); Mapparams = 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(); MapweixinPrepayInfo = 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 { } } ); }
⑥、调用⑤后页面则会弹出微信支付框,如图↓
支付成功
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 Mapmap = 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(Mapmap) 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、公众号微信支付,支付后微信会返回三种状态,如图↓
那么,关于三种状态我们所需跳转的页面则可以在前台用js来实现
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 { } }
工具类
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; }
The above is the detailed content of Detailed explanation of WeChat payment official account payment. For more information, please follow other related articles on the PHP Chinese website!