微信支付


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

微信支付文档

微信支付官方提供dmo

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

配置信息

商户
mch_id: xxxxx
key:xxxxxxxx

小程序:
small_appid : xxxxxxx
small_secret: xxxxx


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

微信官方开发流程图:

我厂下单支付流程图:

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

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

url: weix/getPayInfo
请求方式: POST
参数:

参数 类型 是否必传 说明 是否请求头
userid long yes 用户id yes
appType String yes 用户来源 yes
orderNo String yes 订单号 No
deviceInfo String No 设备号(对应渠道来源) No
其余参数 String No 根据业务需求增加参数 No

返回结果:

参数 类型 说明 示例
package String 此次支付请求的会话id prepay_id=1233456
timestamp long 时间戳 12334567894
nonceStr String 随机字符串 123456
signType String 加密方式 MD5
paySign String 签名 123456

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


/**
 * 获取支付信息
 * @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<String, String> 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<String,Object> 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<String, String> signParams = new TreeMap<String, String>();
        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<String, String> signParams = new TreeMap<String, String>(); 下面共有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<String, String> 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.其次就是发送模板消息。


/**
    * 支付成功以后的回调
    * @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<String, String> 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 = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                           + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                   // 处理业务 -修改订单支付状态
                   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 = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                       + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
           }
       } 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<String,String> 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());
  }

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


文章作者: coderpwh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 coderpwh !
  目录