微信支付

在做微信支付之前,请将微信支付文档看多遍。减少踩坑的数量,重要的事情说三遍。

微信支付文档

微信支付官方提供dmo

关于小程序的配置文件和商户配置文件,请自行申请配置好,这里不多说。

配置信息

商户
mch_id: xxxxx
key:xxxxxxxx

微信支付_第1张图片

小程序:
small_appid : xxxxxxx
small_secret: xxxxx

微信支付_第2张图片

微信统一下单接口: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

微信官方开发流程图:

微信支付_第3张图片

我厂下单支付流程图:
微信支付_第4张图片

统一下单接口(供前端调用)

我厂微信支付下单接口文档如图:

微信支付_第5张图片

支付接口信息的代码(如下):


    /**
     * 获取支付信息
     * @param params
     * @return
     */
    @RequestMapping("getPayInfo")
    @ResponseBody
    public Object getPayInfo(@RequestBody String params, HttpServletRequest request) throws Exception{
        String appType = request.getHeader("appType");
        JSONObject jsonObject = JSONObject.parseObject(params);
        WxUser wxUser = wxUserService.queryByUserId(request);
        JSONObject header = new JSONObject();
        header.put(HttpTools.USERID, wxUser.getUserid());
        header.put(HttpTools.APPTYPE, appType);
        jsonObject.put("source", appType);
        //计算价格
        String result = HttpTools.doPostSSL(ConfigUtil.getProperty("ecommercePath") + "api/pay/calcFinalPrice",                                    jsonObject, header);
        if(200 != JSONObject.parseObject(result).getInteger("code")){
            return result;
        }
        JSONObject resultJson = JSONObject.parseObject(result).getJSONObject("data");
        String openId = jsonObject.getString("openid");
        if(StringUtils.isEmpty(openId)){
            openId = wxUser.getOpenId();//ofAXE03QAYoffsj25N3MJ4BjfyEs
        }
        String orderNo = jsonObject.getString("orderNo");
        String body = resultJson.getString("body");
        String totalFee = "1";
        if(ConfigUtil.getProperty("contextPath").contains("dev") || ConfigUtil.getProperty("contextPath").contains("localhost") || ConfigUtil.getProperty("contextPath").contains("52trip")){ //测试环境
            if(resultJson.getBigDecimal("totalFee").intValue() != 1){
                //totalFee = resultJson.getBigDecimal("totalFee").divide(new BigDecimal(10))+"";
                //totalFee = resultJson.getString("totalFee");
                totalFee = "1";
            }
        }else{
            totalFee = resultJson.getString("totalFee");
        }
        String appid = ConfigUtil.getSmallAppid();
        if("mh5".equalsIgnoreCase(appType)){
            appid = ConfigUtil.getAppId();
        }
        jsonObject.put("appid",appid);
        jsonObject.put("openid",openId);
        String ip = getIpAddress(request); //客户端ip
        jsonObject.put("body", body);
        String deviceInfo = jsonObject.getString("deviceInfo");
        if(StringUtils.isEmpty(deviceInfo)){
            deviceInfo = "MIN";
        }
        jsonObject.put("out_trade_no",orderNo); //商户订单号
        jsonObject.put("total_fee",totalFee); //订单价格 单位是分
        jsonObject.put("spbill_create_ip",ip); //
        WxOrder wxOrder = new WxOrder();
        wxOrder.setOutTradeNo(orderNo); //商户订单号
        wxOrder.setAppId(appid);
        if(AppType.H5.equalsIgnoreCase(appType)) {
        	deviceInfo = AppType.H5;
        	wxOrder.setTradeType("MWEB");
        } else {
        	wxOrder.setTradeType("JSAPI");
        }
        jsonObject.put("device_info", deviceInfo);
        WxOrder wxOrder1 = wxOrderService.queryByOutTradeNo(wxOrder);
        if(null != wxOrder1){
            jsonObject.put("device_info", wxOrder1.getDeviceInfo());
        }
        String xml  = wxApiService.sendWxPayRequest(jsonObject);  //获取支付用的prepay_id
        if(xml.contains("商户订单号重复")){ //说明是跨单支付 那么订单号加后缀
            logger.info("===========处理重复下单");
            orderNo = orderNo+appType;
            jsonObject.put("out_trade_no", orderNo);
            xml = wxApiService.sendWxPayRequest(jsonObject);
        }
        Map resultMap = WXPayUtil.xmlToMap(xml);
        String prepayId = resultMap.get("prepay_id");
        if(StringUtils.isEmpty(prepayId)){  //可能重复下单  查看下数据库
            if(wxOrder1 != null){
                if(DateUtils.subInterval(wxOrder1.getCreateTime(), new Date())> 7000){ //下单超过7000秒还没有支付  那么就关闭以前的订单 在重新下单
                    xml = wxApiService.sendWxPayRequest(jsonObject); //重新下单
                    resultMap = WXPayUtil.xmlToMap(xml);
                    prepayId = resultMap.get("prepay_id");
                }else{
                    prepayId = wxOrder1.getPrepayId();
                }
            }
        }
        wxOrder.setBody(body);
        wxOrder.setTotalFee(Integer.parseInt(totalFee));//    金额
        wxOrder.setSpbillCreateIp(ip);//  客户端ip
        wxOrder.setOpenid(openId);
        wxOrder.setPrepayId(prepayId);
        wxOrder.setDeviceInfo(deviceInfo);
        wxOrder.setOutTradeNo(orderNo);
        wxOrder.setUserid(wxUser.getUserid());
        WxOrder save = wxOrderService.save(wxOrder);//保存订单信息
        if(save == null){
            return RespResult.respFailureMsg("此单号已经下单了");
        }
        Map tarmap = new HashMap();
        String timestamp = System.currentTimeMillis()/1000+"";
        tarmap.put("timestamp", timestamp);
        String nonceStr = UUID.randomUUID().toString().replace("-","");
        tarmap.put("nonceStr", nonceStr);
        try {
            tarmap.put("package", "prepay_id="+prepayId);
            tarmap.put("signType", "MD5");
            SortedMap signParams = new TreeMap();
            signParams.put("appId", appid);
            signParams.put("timeStamp", timestamp);
            signParams.put("nonceStr", nonceStr);
            signParams.put("package", "prepay_id="+prepayId);
            signParams.put("signType", "MD5");
            tarmap.put("paySign", PayCommonUtil.createSign("UTF-8",signParams));
            tarmap.put("orderNo", orderNo);
            if(AppType.H5.equalsIgnoreCase(appType)) {
            	tarmap.put("mwebUrl", resultMap.get("mweb_url"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return RespResult.respOK(tarmap);
    }


简述一下 getPayInfo 接口的逻辑

1.由于访问接口加密,hedere 里面需要带上 userid,appType ,sign 签名等

2.其次就是校验下单支付价格(另一个项目中,https方式调用),在测试环境下默认都是1分钱(微信默认的是分为单位)。

3.然后就是获取支付的prepay_id(下面的发送下单请求方法)

4.重复下单的处理,商户订单也是根据订单号组成

5.成功下单的订单写入wx_order表中

6.SortedMap signParams = new TreeMap(); 下面共有5个参数,appId,timeStamp,nonceStr,package,signType 组成的签名,官方强制要求。

7.返回签名,加密方式,时间戳等返回。

8.这时并没有真正支付,只是发起了支付了,也就是显示密码输入框。

prepay_id 的获取

在getPayInfo接口中先获取prepay_id 方法,根据官方文档图中,在获取openid的前提下,调用微信统一下单接口,可获取到prepay_id,

当然这里也可以获取得对应的支付二维码。

String xml = wxApiService.sendWxPayRequest(jsonObject);

/**
     * 发送下单请求
     *
     * @param jsonObject
     * @return
     */
    public String sendWxPayRequest(JSONObject jsonObject) {
        Map map = new HashMap<>();
        String appid = jsonObject.getString("appid");
        if(StringUtils.isEmpty(appid)){ //为空 说明是微信公众号 否则  是小程序
            appid = ConfigUtil.getAppId();
        }
        map.put("appid", appid);
        map.put("mch_id", ConfigUtil.getProperty("mch_id"));
        map.put("nonce_str", StringUtils.getUUID());
        map.put("body", jsonObject.getString("body"));
        map.put("out_trade_no", jsonObject.getString("out_trade_no"));
        map.put("total_fee", jsonObject.getString("total_fee"));
        map.put("spbill_create_ip", "127.0.0.1");
        map.put("device_info", jsonObject.getString("device_info"));
        map.put("notify_url", ConfigUtil.getProperty("contextPath") + "/weix/notify");
        String tradeType = "JSAPI";
        if(AppType.H5.equalsIgnoreCase(jsonObject.getString("source"))) {
        	
        	tradeType = "MWEB";
        	
        	// 微信外H5支付需要传场景信息字段
        	JSONObject param = new JSONObject();
        	param.put("type", "Wap");
        	param.put("wap_url", ConfigUtil.getProperty("h5.wap.url"));
        	param.put("wap_name", "xxxxx");
        	JSONObject sceneInfoObj = new JSONObject();
        	sceneInfoObj.put("h5_info", param);
        	map.put("scene_info", sceneInfoObj.toJSONString());
        	map.put("spbill_create_ip", jsonObject.getString("spbill_create_ip"));
        }
        map.put("trade_type", tradeType);//JSAPI  NATIVE
        map.put("openid", jsonObject.getString("openid"));
        map.put("sign", WXPayUtil.generateSignature(map, ConfigUtil.getProperty("key"), WXPayConstants.SignType.MD5));
        
        String xml = WXPayUtil.mapToXml(map);
         //  https://api.mch.weixin.qq.com/pay/unifiedorder
        String result = HttpTools.httpClientByPostXml(WxConstants.URL_GET_UNIFIEDORDER, xml);
        return result;
    }

1.除了获取openid以外,这是支付流程中第一次,商户系统与微信后台交互。

2.统一的下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder

3.对照文档里参数,notify_url,sign,这2个参数,一个是回调地址(/notify)也就是下面接口地址,sign 签名,获取配置呢文件中的key

值,然后工具官方提供的工具类来生成,key值是由商户平台申请而来的。

支付回调接口

1.上面下单接口 getPayInfo 中 notify_url ,微信会异步调用该接口

2.notify 参数 ,HttpServletRequest request, HttpServletResponse response 由于微信返回的是xml格式,通过IO流读取其中参数

3.对支付成功的订单更新支付信息

4.JSONObject resultJson = JSONObject.parseObject(HttpTools.doPostSSL(ConfigUtil.getProperty(“ecommercePath”) + “api/shoporder/uploadPayinfo”, jsonObject));就是订单信息的回调(微信是一个单独项目和另外一个项目分开的,都是通过htpps 方式调用),对支付成功的订单上传到聚水潭,看我们内部支付流程图。

5.其次就是发送模板消息。

微信支付_第6张图片

 /**
     * 支付成功以后的回调
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("notify")
    @ResponseBody
    public Object notify(HttpServletRequest request, HttpServletResponse response){
        String resXml = "";
        InputStream inStream;
        try {
            inStream = request.getInputStream();
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            outSteam.close();
            inStream.close();
            String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息
            Map map = WXPayUtil.xmlToMap(result);
            if (map.get("result_code").equalsIgnoreCase("SUCCESS")) {
                logger.info("微信支付----返回成功");
                if (verifyWeixinNotify(map)) {
                    //1.查询微信订单信息表
                    WxOrder wxOrder = new WxOrder();
                    wxOrder.setOutTradeNo(map.get("out_trade_no"));
                    wxOrder = wxOrderService.queryByOutTradeNo(wxOrder);
                    //2.没有更新过支付信息
                    if(wxOrder.getTransactionId() == null){
                        //3.微信回调优先主动调用  那么更新支付信息表
                        wxOrder = new WxOrder();
                        wxOrder.setOutTradeNo(map.get("out_trade_no"));
                        wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.NO_NOTIFY.name());
                        wxOrder.setTransactionId(map.get("transaction_id"));
                        wxOrderService.update(wxOrder);
                        //4.拼接订单上传参数  上传订单
                        JSONObject jsonObject = new JSONObject();
                        String out_trade_no = map.get("out_trade_no");
                        out_trade_no = out_trade_no.replace("MIN","").replace("MH5","").replace("H5", "");
                        jsonObject.put("orderNo", out_trade_no);
                        jsonObject.put("amount",new BigDecimal(map.get("total_fee")).divide(new BigDecimal(100)));
                        jsonObject.put("buyerAccount",map.get("openid"));
                        jsonObject.put("outerPayId",map.get("transaction_id"));
                        jsonObject.put("sellerAccount",map.get("appid"));
                        JSONObject resultJson = JSONObject.parseObject(HttpTools.doPostSSL(ConfigUtil.getProperty("ecommercePath") + "api/shoporder/uploadPayinfo", jsonObject));
                        //5.订单上传接口返回正确
                        if(null != resultJson && 0 == resultJson.getInteger("code")){
                            //6.更新微信订单表
                            wxOrder = new WxOrder();
                            wxOrder.setOutTradeNo(map.get("out_trade_no"));
                            wxOrder.setTransactionId(map.get("transaction_id"));
                            wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.PAY.name());
                            //7.拼接模板消息接口 准备发送模板消息
                            JSONObject data = resultJson.getJSONObject("data");
                            wxOrder.setDetail(data.getString("passingData"));
                            wxOrderService.update(wxOrder);
                        }
                    }
                    resXml = "" + ""
                            + "" + " ";
                    // 处理业务 -修改订单支付状态
                    logger.info("微信支付回调:修改的订单=" + map.get("out_trade_no"));
                }
                BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            }
            else {
                logger.info("支付失败,错误信息:" + map.get("err_code"));
                resXml = "" + ""
                        + "" + " ";
            }
        } catch (Exception e) {
            logger.error("支付回调发布异常:" + e);
            e.printStackTrace();
        }
        return resXml;
    }


手动回调

到这里整个支付流程结束,可以走通支付流程。后面还有坑,由于网络,性能等原因会导致回调延时。提供一个手动回调的接口,供前端使用。

和上面相比就多了一个团购的支付的操作流程。

  /**
     * 手动调用支付回调接口  做到即时通知
     * 1.先判断表wx_order 有没有微信支付id 有的话 不做任何操作
     * 2.没有微信id 去微信拿去数据  调用更新接口
     * 3.先注释
     * @param orderNo
     * @param type
     * @return
     */
    @RequestMapping("queryOrder")
    @ResponseBody
    public Object queryOrder(String orderNo,String type, HttpServletRequest request){
        WxUser wxUser = wxUserService.queryByUserId(request);
        //1.查询微信表支付信息
        WxOrder wxOrder = new WxOrder();
        wxOrder.setOutTradeNo(orderNo);
        wxOrder = wxOrderService.queryByOutTradeNo(wxOrder);
        if(!StringUtils.isEmpty(wxOrder.getTransactionId())){  //不为空  那么一定有更新信息(更新操作来自于微信的回调) 这个时候不做操作
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("orderNo", orderNo);
            JSONObject jsonObject1 = HttpTools.doPost(ConfigUtil.getProperty("ecommercePath") + "api/orders/isGroupOrder", jsonObject);
            if(0 == jsonObject1.getInteger("code") && jsonObject1.getBoolean("data")){
                for (int i = 0; i < 50; i++) {
                    String detail = wxOrder.getDetail();
                    if(StringUtils.isEmpty(detail)){
                        try {
                            Thread.sleep(200);
                            wxOrder = new WxOrder();
                            wxOrder.setOutTradeNo(orderNo);
                            wxOrder = wxOrderService.queryByOutTradeNo(wxOrder);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        return RespResult.respOK(wxOrder.getDetail());
                    }
                }
            }
            return RespResult.respOK(wxOrder.getDetail());
        }
        //2.拿取微信支付数据 type没有或者为MIN 从小程序拿取 否则从H5拿取
        Map map = wxApiService.queryOrder(orderNo,type);
        //3.拿取后 先更新支付表状态
        wxOrder = new WxOrder();
        wxOrder.setOutTradeNo(map.get("out_trade_no"));
        wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.NO_NOTIFY.name());
        String transaction_id = map.get("transaction_id");
        if(StringUtils.isEmpty(transaction_id)){
        	logger.error("-->queryOrder-->订单未支付:{}", map.get("out_trade_no"));
            return RespResult.responseSuccess();
        }
        wxOrder.setTransactionId(transaction_id);
        wxOrderService.update(wxOrder);
        //4.拼接参数  传给订单更改接口
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("orderNo", map.get("out_trade_no"));
        jsonObject.put("amount",new BigDecimal(map.get("total_fee")).divide(new BigDecimal(100)));
        jsonObject.put("buyerAccount",map.get("openid"));
        jsonObject.put("outerPayId",map.get("transaction_id"));
        jsonObject.put("sellerAccount",map.get("appid"));
        JSONObject resultJson = JSONObject.parseObject(HttpTools.doPostSSL(ConfigUtil.getProperty("ecommercePath") + "api/shoporder/uploadPayinfo", jsonObject));
        if(null != resultJson && 0 == resultJson.getInteger("code")){
            //订单接口接受ok  再次更新微信订单表状态
            wxOrder = new WxOrder();
            wxOrder.setOutTradeNo(map.get("out_trade_no"));
            wxOrder.setTransactionId(map.get("transaction_id"));
            wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.PAY.name());
            //拼接参数 发送模板消息
            JSONObject data = resultJson.getJSONObject("data");
            wxOrder.setDetail(data.getString("passingData"));
            wxOrderService.update(wxOrder);
        }
        return RespResult.respOK(wxOrder.getDetail());
    }

如有不足地方,请多多批评指正。下一篇会介绍退款部分。

你可能感兴趣的:(微信支付)