微信支付第二弹(公众号支付)

背景
随着微信支付的流行,大多产品都开发了自己的公众号、小程序等,产品的营销需要支付的支撑,最近做了个微信公号号支付,采坑无数,今天给大家分享一下
可以先看一下微信支付API文档
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317853&token=&lang=zh_CN

建议先看一下微信的开发者文档,虽然有点坑 。。。。
应用场景

所需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>

公众号支付所需参数

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

大致步骤:获取用户授权(获取code)------------->根据code获取openID(用户的基本信息)

1、配置授权域
这个则是在公众号登陆平台上面配置的↓

2、发起API请求获取用户授权
需要拼接三个参数: 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是什么?

在微信用户关注公众号时,会对应的产生一个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 {
                    }
               }
           ); 
    }

⑥、调用⑤后页面则会弹出微信支付框,如图↓


支付成功
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、公众号微信支付,支付后微信会返回三种状态,如图↓


那么,关于三种状态我们所需跳转的页面则可以在前台用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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值