文档章节

企业微信-企业支付开发

o
 osc_fmg49rzg
发布于 2019/03/20 15:09
字数 3456
阅读 18
收藏 0

精选30+云产品,助力企业轻松上云!>>>

一、开发前准备

  1)企业微信

  2)商户号(微信支付商户平台账号)

  3)wx-pay SDK,  JSSDK

二、开发前了解开发文档,以及相关概念。

  官方文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90280。

  先来了解企业支付需要的一些相关概念:

  1、corpid:每个企业都拥有唯一的corpid,获取此信息可在管理后台"我的企业" - "企业信息"下查看"企业ID"(需要有管理员权限)。

  2、userid:每个成员都有唯一的userid,即所谓的"账号"。在管理后台->"通讯录"->点进某个成员的详情页,可以看到。

  3、agentid:每个应用都有唯一的agentid。在管理后台->"应用与小程序"->"应用",点进某个应用,即可以看到agentid。

  4、secret:secret是企业应用里面用于报障数据安全的"钥匙",每个应用都有一个独立的访问密钥,为了保证数据的安全,secret不能泄露。

  5、access_token:access_token是企业后台去企业微信的后台获取信息时的重要票据,由corpid和secret产生。所有接口在通信时都要携带此信息用于验证接口的访问权限。

  企业支付分为:企业红包、向员工付款、向员工收款三类。如下图所示,此次主要详说向员工收款。

                      

  按文档介绍,第一步,开通企业微信专区。第二步,获取用户openid。第三步,添加JSAPI的权限验证。第四步,发起向员工收款。第五步,调用支付JSAPI完成支付。

  第一步就不讲了,因为我没有企业微信管理员账号,都是让人给配置好了,我再用的。

  将上面的步骤文字转换为图来看 ,过程就比较清晰明了了。(如下图)

          

  1)首先获取access_token。

  请求方式: GET(HTTPS)
  请求地址: https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET

  需要两个参数,corpid和corpsecret,就是前面的概念介绍的企业id和应用id。获取到的access_token有效时间为7200秒即两小时,因为获取access_token的接口有访问次数限制,所以我们需要把获

取到的access_token缓存起来。这个access_token是我们访问其他接口要用到的。(注意access_token可能会提前失效,逻辑中要判断失效重新调接口获取)。

  2)获取用户openid

  在H5页面上发起向员工收款,需要获取到用户的openid信息,但在企业微信上我们没有直接获取openid的API,需要先获取到userid,再通过userid转换为openid。获取useid需要构造网页授权链接,

通过链接获取code参数,再通过code参数获取用户userid信息。

  2.1)构造网页授权地址https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect

  这里的参数如下图说明:

        

  这里state是选填的,但是一般还是带上,企业微信会原样返回你给的参数,可以拿这个区分自己是通过哪个方法去请求网页授权的。redirect_uri需要用urlencode处理。构造好网页授权地址后,访问后,页面会跳转到redirect_uri给的地址,并带上code和原样返回的state参数。拿到code后我们就可以用code获取userid了。(注意:code只有5分钟的有效期,并且只能使用一次)

  2.2)获取访问用户身份

  请求方式:GET(HTTPS)
  请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE

  这里两个参数access_token和code就是前两步我们获取到的。

  权限说明:跳转的域名须完全匹配access_token对应应用的可信域名,否则会返回50001错误。(这个是在第一步,开通企业微信专区那儿设置的)

  请求之后,就会返回用户身份信息了,里面包含userid。返回数据格式:{"errcode": 0,"errmsg": "ok","UserId":"USERID","DeviceId":"DEVICEID"}

  2.3)userid转换为openid

  请求方式:POST(HTTPS)
  请求地址: https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid?access_token=ACCESS_TOKEN

  这里需要两个参数一个是access_token放在url地址里,另一个是userid。userid不能拼接在地址后面,需要放到body里面并且是json格式。提供一个方法来进行post请求。

 1 public static JSONObject doJsonPost(String url, JSONObject jsonObject) {
 2         HttpClient client = HttpClientBuilder.create().build();
 3         HttpPost post = new HttpPost(url);
 4         JSONObject response = null;
 5 
 6         try {
 7             StringEntity s = new StringEntity(jsonObject.toString());
 8             s.setContentEncoding("UTF-8");
 9             s.setContentType("application/json");
10             post.setEntity(s);
11             HttpResponse res = client.execute(post);
12             if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
13                 HttpEntity entity = res.getEntity();
14                 String result = EntityUtils.toString(entity);
15                 response = JSONObject.parseObject(result);
16             }
17         } catch (Exception e) {
18             throw new RuntimeException(e);
19         }
20         return response;
21     }
View Code

   请求成功返回一个JSONObject{"errcode": 0,"errmsg": "ok","openid": "oDOGms-6yCnGrRovBj2yHij5JL6E"},里面包含 openid信息。

  3)添加JSAPI权限验证

  拿到openid后,就差不多完成了发起支付的前期准备条件,不过调用JSAPI支付之前,需要对请求的JSAPI进行权限验证。在调用JSAPI的页面引入jssdk,地址为https://res2.wx.qq.com/open/js/jweixin-1.4.0.js  然后,在页面添加如下脚本。脚本所需的参数建议通过java后台代码一次性获取后传到页面赋值。

    wx.config({
          debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来(开发时设为true,发布时记得调为false)
          appId: 'appId', // 必填,企业微信的corpID
          timestamp: 'timestamp', // 必填,生成签名的时间戳
          nonceStr: 'noncestr', // 必填,生成签名的随机串
          signature: 'signature',// 必填,签名
          jsApiList: ['getBrandWCPayRequest']
    });

  appid就是企业微信的corpid,timestamp可以去当前时间的时间戳,noncestr可以用微信sdk生成一个随机字符串,signature签名,需要通过签名算法来生成了,其中又需要额外获取一个jsapi_ticket票据。

  3.1)JS-SDK签名算法

  生成签名之前必须拿到一个调用企业微信的临时票据jsapi_ticket,jsapi_ticket有效期为7200,且一小时内只能获取400次,单个应用获取不能超过100次,所以需要缓存起来备用。

  请求方式:GET(HTTPS)
  请求URL:https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN

  就一个参数access_token。请求后返回的数据格式:

  {"errcode":0,"errmsg":"ok","ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA","expires_in":7200}

  3.1.1)签名

  参与签名的参数有四个: noncestr(随机字符串), jsapi_ticket, timestamp(时间戳), url(当前网页的URL, 不包含#及其后面部分)。有两个注意点:1. 字段值采用原始值,不要进行URL转义;2. 必须严格按照如下格式拼接,不可变动字段顺序。

  jsapi_ticket=JSAPITICKET&noncestr=NONCESTR&timestamp=TIMESTAMP&url=URL

  然后对拼接出来的字符串作sha1加密即可得到signature。注意:1、url是你调用JSAPI的页面的url,包括参数,但不包括#及其后面部分。2、noncestr和timestamp必须和之前的wx.config中的nonceStr和timestamp一致。下面贴出SHA1加密方法。  

 1 public  static  String SHA1(String des){
 2         char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 3                 'a', 'b', 'c', 'd', 'e', 'f'};
 4         MessageDigest mdTemp = null;
 5         try {
 6             mdTemp = MessageDigest.getInstance("SHA1");
 7             try {
 8                 mdTemp.update(des.getBytes("UTF-8"));
 9             } catch (UnsupportedEncodingException e) {
10                 e.printStackTrace();
11             }
12         } catch (NoSuchAlgorithmException e) {
13             e.printStackTrace();
14         }
15 
16 
17         byte[] md = mdTemp.digest();
18         int j = md.length;
19         char[] buf = new char[j * 2];
20         int k = 0;
21         for (int i = 0; i < j; i++) {
22             byte byte0 = md[i];
23             buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
24             buf[k++] = hexDigits[byte0 & 0xf];
25         }
26         String sign= new String(buf);
27         return sign;
28     }
View Code

  这些参数在java后台代码获取之后,输出到页面去,就完成了JSAPI权限验证。接下来就是进行统一下单了。

  4)统一下单

  统一下单代码:  

 1 public Map<String, String> GetUnifiedOrderResult(String body, String tradeno,String totalfee,String openid){
 2 
 3         try {
 4             Map<String , String > data = new HashMap<>();
 5             data.put("body", body);//商品描述
 6             data.put("attach", body);//附加数据
 7             data.put("out_trade_no",tradeno );//自己生成的订单号,必须唯一
 8             data.put("fee_type", "CNY");//货币种类
 9             data.put("total_fee", totalfee);//付款金额
10             data.put("trade_type", "JSAPI");//交易类型
11             data.put("notify_url", "你的回调地址,不能带参数,外网可访问");
12             data.put("openid", openid);//付款用户openid
13             data.put("sign_type","MD5");//加密方式
14             unifiedOrderResult  = wxpay.unifiedOrder(data);
15             return unifiedOrderResult;
16         } catch (Exception e) {
17             //throw new Exception(String.format("UnifiedOrder response error!") );
18         }
19         return null ;
20     }
View Code

  统一下单就是调用微信接口,预下单。请求地址为:https://api.mch.weixin.qq.com/pay/unifiedorder。这个在wx-pay sdk中封装好了,我们只需要调用就行了。

  返回为xml格式的数据如下:

  <xml>
     <return_code><![CDATA[SUCCESS]]></return_code>
     <return_msg><![CDATA[OK]]></return_msg>
     <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
     <mch_id><![CDATA[10000100]]></mch_id>
     <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
     <openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid>
     <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
     <result_code><![CDATA[SUCCESS]]></result_code>
     <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
     <trade_type><![CDATA[JSAPI]]></trade_type>
  </xml>

  prepay_id为预支付交易会话标识,有效期为2小时,我们需要这个数据去请求JSAPI完成支付。建议这个数据保存下来,以便不小心没有支付成功,可以再次使用(须在两小时内支付)。

  (统一下单的微信官方文档链接为:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

  5)前端页面调用JSAPI完成支付

  在页面中调用WeixinJSBridge内置对象发起支付,注意1、这个页面和前面进行jsapi权限验证用于前面的url是同一个页面。2、WeixinJSBridge内置对象只在微信浏览器有用,在其他浏览器中无效。

//调用微信JS api 支付
    function onBridgeReady() {
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest', ${jsapiPrarmeters},
        function (res) {
                if (res.err_msg == "get_brand_wcpay_request:ok") {
                    // 使用以上方式判断前端返回,微信团队郑重提示:
                    //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                    $.toast("支付成功");
                    location.href ='支付成功后返回的页面'
                }
                else{
                    $.toast("${Ordercode}");
                    $.toast("支付失败", "forbidden");
                    history.go(-1);
                }
        });
    }
  if (typeof WeixinJSBridge == "undefined"){ 
    if( document.addEventListener ){ 
      document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); 
    }
    else if (document.attachEvent){ 
      document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
      document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); 
    } 
  }
  else{ 
    onBridgeReady(); 
  }

//这里${jsapiParameters} 我是在java服务端代码获取到,在传到页面来的,其格式如下:
{ 
"appId":"wx2421b1c4370ec43b", //企业corpid
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数 
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jgfjsdfgsdg888", //这个就是统一下单时生成的预支付交易会话标识
"signType":"MD5", //加密方式  注意此处需与统一下单的签名类型一致
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
},

  这里paySign必须按照微信要求的算法来生成。我生成jsapiParameters的方法如下

 1 public String GetJsApiParameters()
 2     {
 3         try {
 4             Long time =System.currentTimeMillis()/1000;
 5             String timestamp=time.toString();
 6             Map<String,String> jsApiParam = new HashMap<>() ;
 7             jsApiParam.put("appId", unifiedOrderResult.get("appid"));
 8             jsApiParam.put("timeStamp", timestamp);
 9             jsApiParam.put("nonceStr", unifiedOrderResult.get("nonce_str"));
10             jsApiParam.put("package", "prepay_id=" + unifiedOrderResult.get("prepay_id"));//统一下单返回的预支付交易会话标识
11             jsApiParam.put("signType", "MD5");
12             jsApiParam.put("paySign", WXPayUtil.generateSignature(jsApiParam, "key"));//kee是微信商户平台的密钥( 微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置),调用微信sdk封装好的方法进行签名
13 
14 
15             Object parameters = JSON.toJSON(jsApiParam);
16             return parameters.toString();
17         }
18         catch (Exception e){
19 
20         }
21 
22         return null;
23     }
View Code

  这个方法其实就是按这个顺序组装好字符串,"appId=你的企业corpid&timeStamp=12312312&nonceStr=NONCESTR&package=prepay_id=统一下单预付交易会话标识&signType=MD5&key=商户密钥";然后调用微信SDK的generateSignature方法进行签名。

  至此企业微信支付的主体流程就完成了。下面再说说支付完成后回调的处理,在这里遇到一个坑。

 1 @RequestMapping(value = "/wxPayNotifyUrl",method = {RequestMethod.GET,RequestMethod.POST})
 2     @ResponseBody
 3     public String wxPayNotifyUrl(HttpServletRequest request, HttpServletResponse response) {
 4 
 5         String resXml = getWXNotifyXML(request,response);
 6         return payBack(resXml);
 7 }
 8 
 9 
10 public String payBack(String notifyData) {
11         String xmlBack = "";
12         Map<String, String> notifyMap = null;
13         try {
14 
15             notifyMap = WXPayUtil.xmlToMap(notifyData);
16             log.info("回调数据转map结束"+notifyMap);// 转换成map
17             //坑就在这里,微信文档前面涉及到的签名一般默认都是MD5加密,并且微信SDK验证签名有效性的方法默认也是用MD5加密,所以因为这个回调失败了,通过日志才知道是因为验证签名有效性不通过,后来改成HMAC-SHA256签名方式才回调成功
18             if (WXPayUtil.isSignatureValid(notifyMap, WxConfig.getInstance().getKey(), WXPayConstants.SignType.HMACSHA256)) {
19                 String return_code = notifyMap.get("return_code");//状态
20                 if(return_code.equals("SUCCESS")){
21                     if(notifyMap.get("result_code").equals("SUCCESS")){
22                         String out_trade_no = notifyMap.get("out_trade_no");//订单号
23                         if (out_trade_no == null) {
24                             log.info("微信支付回调失败订单号:", notifyMap);
25                             xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml>";
26                             return xmlBack;
27                         }
28                         log.info("回调成功返回的订单号"+out_trade_no);
29                         //回调成功后对订单状态做改变
30                         String ordercode = out_trade_no;
31                         Order order = orderMapper.findByOrderCode(ordercode);
32 
33                         if (order.getOrderstatusname().equals("未付款")) {
34                             order.setOrderstatus(1);
35                             order.setOrderstatusname("已付款");
36                             orderMapper.updateOrder(order);
37                         }
38                         xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
39                         return xmlBack;
40                     }
41                 }
42             } else {
43                 xmlBack = "<xml>;" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
44                 log.info("微信支付回调签名验证失败:"+ notifyMap);
45                 return xmlBack;
46             }
47         } catch (Exception e) {
48             log.info("处理回调数据时异常", e.getStackTrace());
49             xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
50         }
51         log.info("处理回调数据结束", xmlBack);
52         return xmlBack;
53     }
54 
55 
56 public String getWXNotifyXML(HttpServletRequest request, HttpServletResponse response){
57         String resXml = "";
58         try {
59             InputStream inputStream;
60             StringBuffer sb = new StringBuffer();
61             inputStream = request.getInputStream();
62 
63             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
64             String line = null;
65             try {
66                 while ((line = reader.readLine()) != null) {
67                     sb.append(line + "\n");
68                 }
69             } catch (IOException e) {
70                 log.info("流转xml失败"+e.getStackTrace());
71                 e.printStackTrace();
72             } finally {
73                 try {
74                     reader.close();
75                     inputStream.close();
76                 } catch (IOException e) {
77                     e.printStackTrace();
78                 }
79             }
80             resXml = sb.toString();
81 
82         } catch (Exception e) {
83             log.info("流转xml失败"+ e.getStackTrace());
84         }
85         return resXml;
86     }
View Code

  为什么要回调呢,因为调用JSAPI只是把付款操作给到了微信,微信返回ok也只是表示接收到这个操作指令了,并不一定真正处理成功,所以需要回调告知处理结果。在回调方法里,一定要再次进行一次签名,和微信返回的签名数据进行对比,以防被串改过。

  回调成功后,返回给微信的数据格式如下,如果没有返回这个数据给微信,微信会回调多次,我们在代码中要能识别同一个订单的回调。

  <xml>

      <return_code><![CDATA[SUCCESS]]></return_code>

      <return_msg><![CDATA[OK]]></return_msg>
  </xml>

  

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Javen205/jfinal_qyweixin

jfinal_qyweixin 企业微信号极速开发 jfinal_qyweixin是基于 jfinal-weixin二次开发而来,只需浏览 Demo 代码即可进行极速开发。同时支持微信企业号以及企业微信 微信公众号开源项目 【weixi...

Javen205
2015/12/29
0
0
微信公众号开发系列-玩转微信开发-目录汇总

引言 最遗憾的不是把理想丢在路上,而是理想从未上路。 每一个将想法变成现实的人,都值得称赞和学习。 致正在奔跑的您! 在现在这个无处不在的互联网背景下,各种应用已不再仅仅局限于网页或...

NET快速开发框架
2019/06/20
0
0
tedi/wxpay

##还是有问题,本行为测试 ##基于nodejs微信支付接口 本人初学nodejs,代码可能有诸多不完善的地方,请大家多多指教。 QQ:85920312 Email:tedi3231@qq.com ####微信支付提供三种方式: 公众号...

tedi
2015/12/02
0
0
JeeWx-API 1.2.0 ,微信、企业微信和支付窗 SDK 三合一

JeeWx-API 1.2.0 版本发布,微信SDK、企业微信SDK和支付窗SDK三合一 JEEWX-API 是第一个JAVA版微信极速SDK,集成企业微信SDK,支付窗SDK,可以快速的基于她进行微信公众号、企业微信、支付窗...

Jeecg
2018/04/03
2.1K
0
IJPay 2.5.2 版本发布,日常迭代

IJPay 让支付触手可及,封装了微信支付、QQ支付、支付宝支付、银联支付、京东支付等常用的支付方式以及各种常用的接口。不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的...

Javen-IJPay
04/16
1.3K
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux安装redis服务器和部署

Linux安装redis和部署 第一步:下载安装包 wget http://download.redis.io/releases/redis-5.0.5.tar.gz 访问https://redis.io/download 到官网进行下载。这里下载最新的5.0.5版本. 第二步:...

osc_3ytpwpyb
20分钟前
23
0
IF函数,根据条件设定输入内容

if函数通常用于条件判断,根据判断结果执行相应命令。 1.函数解释: IF(logical_test, [value_if_true], [value_if_false]) logical_test 必需。 计算结果为 TRUE 或 FALSE 的任何值或表达式...

osc_sumf8h95
22分钟前
9
0
Pytorch自定义dataloader以及在迭代过程中返回image的name

pytorch官方给的加载数据的方式是已经定义好的dataset以及loader,如何加载自己本地的图片以及label? 形如数据格式为 image1 label1 image2 label2 ... imagen labeln 实验中我采用的数据的...

osc_l8u38961
23分钟前
6
0
灯塔

\[love\ and \ share \] 我怎么感觉变成了好东西推荐呢?算了,本来也差不多 还没写完,想到再更 有好看玩的能不能评论一下,qwq 动漫 大多是些国漫,多在\(b\)站、腾讯视频、盗版小网站能够...

osc_dc6pbw3x
24分钟前
0
0
网易首页 」 网易手机 」 正文 苹果超薄触摸显示技术专利曝光:重新定义轻薄

最近,苹果公司的新屏幕专利技术已经曝光。特别是苹果公司的新型超薄触摸技术,它可以降低显示器的结构水平,消除多余的电路,并使屏幕更薄。该专利表明,这项新技术适用于iPhone,iPad,App...

osc_opzpp18v
26分钟前
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部