微信小程序开发-微信支付之免密支付(自动扣费)一 小程序+java接口

微信小程序开发之---微信免密支付

很多时候我们都要用到免密支付,也就是常说的自动扣款,但是如何才能自动扣费,这一块微信api也没有详细说明,微信普通的支付官方说的很清楚,但是免密支付可就少了。。这类的支付的东西很多,比如滴滴打车、App Store微信支付、二维码乘车等。

下面是我做的一个项目,小程序、后台接口、数据库构建。二维码乘车,微信小程序生成二维码,公交机器上面安装卡机扫码扣费。上线四个月,我们用户量几十万,每天扫码订单数两万多,金额十来万。


image.png
image.png
image.png
image.png
image.png
图片发自简书App

需要准备的东西:

    1、申请微信小程序号。
    2、申请微信商户号(商户开通委托代扣,好像需要私下找微信走流程,我用的账号是甲方提供的)。
    3、下载微信开发者工具。

按照下面图片进入开发者工具,创建好你的项目,根据图片选工具自动回给你创建项目目录,这一点就不详细说了。

一些发送的参数和结果我不会详细说,可以看下下面链接详细说明,这是我们和微信合作,微信人员给的api内部文档,免密支付(自动扣费)api文档地址:https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=18_3&index=7

其实有了这个链接看看基本差不多懂了!。。。。。

接着往下看吧。

免密支付首先要和用户签订免密支付协议。怎么签?

首先需要用code查询用户openid。

如何获取code?

1、小程序用户登录就行。

   //登录
       wx.login({
           success: function (res) {
               //res.code 
               然后就请求你的后台接口获取。
           }, 
           fail: function () {

           }
       })

2、通过用户登录获取的code,请求微信接口获取openid。 注 一个code只能用一次。
我用的是java获取用户openid 小程序不行,因为小程序请求必须填写安全url域名,微信的域名不能填写进去。所有放弃用小程序获取吧

    private Map<String, Object> _getOpenId(String code){
        Map<String, Object> ret = new HashMap<>();
        Map<String, String> urlData= new HashMap<String, String>();
        urlData.put("appid",appid);//小程序id
        urlData.put("secret",appKey);//小程序key
        urlData.put("grant_type","authorization_code");//固定值这样写就行
        urlData.put("js_code",code);//小程序传过来的code
        HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
        Object data_deserialize = null;
        try {
            //code2OpenidUrl "https://api.weixin.qq.com/sns/jscode2session";
            String dataStr = httpsClientUtil.doGet(code2OpenidUrl, urlData);
            data_deserialize = JSONUtil.deserialize(dataStr);
        }catch(Exception ex){
            ret.put("success", false);
            ret.put("msg", "_getOpenId_未知异常");
            ret.put("message", ex);
            return ret;
        }
        Map<String, String> data=  (Map<String, String>)data_deserialize;
        if( data.containsKey("errcode") ){
            ret.put("success", false);
            ret.put("msg", data.containsKey("errcode"));
            ret.put("message", data.containsKey("errmsg"));
        }else{
            ret.put("success", true);
            ret.put("result",data);
        }
        return ret;
    }

好了,开始签约吧。签约只需要小程序即可,不需要后台做什么。

首先需要app发起签约请求,这个是签约的方法,
不过首先要把 签约小程序放在 app.json中 这是微信新的规则

  "navigateToMiniProgramAppIdList": [
    "wxbd687630cd02ce1d",  签约小程序
    "wx5e73c65404eee268"   微信代付 用户还款小程序
  ]

wxml

//bindGetUserInfo 是我的逻辑函数,不需要看,
    <button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" class="btn_wxzf">立即绑定</button>

js

    //发起签约请求 data里面传值是必须传的几项,没强制要求的我没传
    var me = this;
    //装作参数
    this.globalData.contract_code = this.genID(5);
    var data = {
    mch_id: this.globalData.mch_id,//你的商户号
    appid: this.globalData.appid,//小程序appid
    plan_id: this.globalData.plan_id,//你的商户签约模板id(在商户号里面设置)
    contract_code: this.globalData.contract_code, //签约码,商家生成,商户侧须唯一
    contract_display_account: this.turnNickName(this.globalData.userInfo["nickName"]||""), //签约用户名称,我这里用的是用户微信名字(怎么获取下面有)本来我想用手机号的,但是获取手机号需要注册或者是微信api获取需要用户点击同意,甲方说用户多操作一步用户体验不好。。。
    notify_url: "https://www.***.com/contractNotify",// 签约成功与否微信返回数据的接收地址
    request_serial: ((new Date()).getTime() - 1526353000000),//商户请求签约时的序列号纯数字,长度不超过12位
    timestamp: parseInt((new Date()).getTime() / 1000) + "" //时间戳 
    };
    //签名 MD5加密
    data.sign = util.genSign(data, this.globalData.key);
    //开始发起签约
    wx.navigateToMiniProgram({
        appId: 'wxbd687630cd02ce1d', //固定值,这个是填写微信官方签约小程序的id
        extraData: data,
        path: 'pages/index/index',
        success(res) {
            wx.setStorageSync('contract_id', "");
            me.globalData.contract_id = "";
            // 成功跳转到签约小程序 
        },
        fail(res) {
            console.log(res);
            // 未成功跳转到签约小程序 
        }
    });

签约后会返回一些签约信息,在app.js文件中 onShow函数中获取。
这里面需要注意的是,用户点击的是返回还是点击左上角的X。。。 我是当用户点击签约按钮时候,就开启一个定时器去监控,onShow里面取后台数据库查询签约状态,(微信会把签约结果异步通知到notify_url,在里面存到数据库就行)

App({
    onShow(res) {
        if (res.scene === 1038) { // 场景值1038:从被打开的小程序返回
            const { appId, extraData } = res.referrerInfo
            if (appId == 'wxbd687630cd02ce1d') { // appId为wxbd687630cd02ce1d:从签约小程序跳转回来
                if (typeof extraData == 'undefined'){
                    // TODO  ***用户击左上角的返回***  
                    // 客户端小程序不确定签约结果,需要向商户侧后台请求确定签约结果  
                    return;
                }
                if(extraData.return_code == 'SUCCESS'){
                    // TODO
                    // 客户端小程序签约成功,需要向商户侧后台请求确认签约结果
                    var contract_id = extraData.contract_id
                    return;
                } else {
                    // TODO
                    // 签约失败
                    return;
                }
            }
        }
    }
})
notify_url函数,用来接收用户签约微信返回的结果xml

注意:只要收到微信通知,你就必须返回结果

失败:
        <xml>
            <return_code><![CDATA[FAIL]]></return_code>
            <return_msg><![CDATA[..]]></return_msg>
        </xml>
    成功:
        <xml>
            <return_code><![CDATA[SUCCESS]]></return_code>
            <return_msg><![CDATA[OK]]></return_msg>
        </xml>
    // 接收发起免密签约后小程序返回的结果接口
    public String contractNotify() throws Exception{
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = request.getReader();
        char[]buff = new char[1024];
        int len;
        while((len = reader.read(buff)) != -1) {
            sb.append(buff,0, len);
        }
        String wxResponseText = sb.toString();
//      rootLogger.info("收到contractNotify");
        //开始解析xml字符串
        Document document = DocumentHelper.parseText(wxResponseText);
        Element root = document.getRootElement();
        Element successEl=root.element("return_code");//查询状态
        String success=successEl.getStringValue();
        Element return_msgEl;
        String return_msg="";
        if(success.equals("SUCCESS")){
            Element resultCodeEl=root.element("result_code");//查询状态
            String resultCode=resultCodeEl.getStringValue();
            if(resultCode.equals("SUCCESS")){
                Element contractCodeEl=root.element("contract_code");//查询状态
                String contractCode=contractCodeEl.getStringValue();
                Element openidEl=root.element("openid");//openid
                String openid=openidEl.getStringValue();
                Element contractIDEl=root.element("contract_id");//签约id
                String contractID=contractIDEl.getStringValue();
                Element operateTimeEl=root.element("operate_time");//签约操作时间
                String operateTime=operateTimeEl.getStringValue();
                Element changTypeEl=root.element("change_type");//签约操作时间
                String changType=changTypeEl.getStringValue();
                //申明储存数据对象
                Map<String,String> contractData = new HashMap<String,String>();
                contractData.put("openid",openid);
                contractData.put("contract_id",contractID);
                contractData.put("contract_code",contractCode);
                contractData.put("create_time",operateTime);//如果是插入就当create_time存
                contractData.put("update_time",operateTime);//如果是修改就当update_time存
                contractData.put("chang_type",changType);//如果是修改就当update_time存
                //开始储存
                //先查询有没有,存放查询结果
                List<Map<String, Object>> queryList = null;
                try{
                    queryList = dbExecutor.query("com.sutpc.dao.PayDao.selectContract", contractData);//list.get(0).get(contract_id);
                }catch (Exception e){
                    rootLogger.error("contractNotify--query----查询失败!");
                    rootLogger.error(e);
                    return "success";
                }
                //判断查询结果 如果有就修改 如果没有就插入
                if( queryList==null || queryList.size()==0 ){
                    try{
                        dbExecutor.insert("com.sutpc.dao.PayDao.saveContract", contractData);
                    }catch (Exception e){
                        rootLogger.error("contractNotify---insert---储存失败!");
                        rootLogger.error(e);
                        return "success";
                    }
                }else{
                    try{
                        dbExecutor.update("com.sutpc.dao.PayDao.updateContract", contractData);
                    }catch (Exception e){
                        rootLogger.error("contractNotify---update---储存失败!");
                        rootLogger.error(e);
                        return "success";
                    }
                }
            }
        }else{
            return_msgEl=root.element("return_msg");//查询状态
            return_msg=return_msgEl.getStringValue();
        }

//      rootLogger.info("完成  contracttify");
        return "success";
    }
其中用到的用户微信名称,是通过微信api获取的 获取用户信息
   wxml***
   <button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" class="btn_wxzf">立即绑定</button>
   js***
   //如果用户第一次使用我们程序,获取用户信息必须要用户点击按钮同意后才行,以后就不需要了,商家直接获取就行
   bindGetUserInfo: function (e) {
       if (!app.globalData.userInfo){
       //新用户判断是否同意绑定
       if (e.detail.userInfo){
           app.globalData.userInfo = e.detail.userInfo;
           this.setData({
           userInfo: e.detail.userInfo,
           hasUserInfo: true
           })
           //已授权开始绑定
           this.bindBtnBangDing();
       }else{
           wx.showToast({
           title: '您未同意授权',
           image: '../../images/shangxin.png',
           duration: 1000,
           mask: true
           });
       }
       }
   },

到以上,签约已经成功了,当需要扣款时,就可以用用户的contract_id进行扣款了(如:扫码乘车,滴滴打车等,我们是扫码乘车用),不论什么时候扣,都能直接成功,用户会收到一条微信支付消息。

那么如何扣款? 接下来就是java干的事情了,当需要扣用户费用时候,后台服务调用申请扣款接口即可。

组装一些必要的参数,然后向微信API发起扣款请求即可,那么需要什么参数?可以看最上面我放的链接!详细的接口介绍!

请求appid、商户号、随机字符串、签名(我用的是MD5)、商品描述、商户订单号、总金额、终端IP(发起扣款电脑ip,运行程序的服务器地址)、回调通知url(接收XML扣款结果地址)、交易类型(填PAP微信委托代 扣支付)、委托代扣协议id(用户签约的contract_id) 这些是必须传的,没有强制要求的我们传,我懒。

我用的是java写的。

注:以下这些微信API需要传输的详细参数看上面我放的链接。我只传了一些必须传的值。

首先解释下一些全局变量是什么:
    // 一些微信接口api地址-----
    // 请求扣款
    private String wxPayApplyUrl = "https://api.mch.weixin.qq.com/transit/pay/payapply";
    // 查询openid
    private String code2OpenidUrl="https://api.weixin.qq.com/sns/jscode2session";
    // 查询订单状态
    private String wxQueryOrderUrl="https://api.mch.weixin.qq.com/pay/paporderquery";
    // 查询用户状态
    private String wxQueryStateUrl="https://api.mch.weixin.qq.com/transit/pay/querystate";
    // 查询contract_id
    private String wxQueryContractUrl="https://api.mch.weixin.qq.com/papay/querycontract";
    // 发起退款
    private String wxRefundUrl="https://api.mch.weixin.qq.com/secapi/pay/refund";
    // 查询退款
    private String wxSelectRefund="https://api.mch.weixin.qq.com/pay/refundquery";
    // 请求微信接口固定常量
    private final String mch_id="***"; //商户号
    private final String plan_id="***";//签约模板id号
    private final String appid="***";//小程序号
    private final String trade_type="PAP";//这个填固定值
    private final String notify_url="***";//接收返回结果的地址
    private final String appKey="***";//小程序app的key
    private final String signKey="***";//签名的KEY,每个商户号一个,可以重置,但是需要严格保密。

注:注意用户打开小程序最好查询一下用户状态有没有支付能力,有没有欠费(用户如果钱不够,微信会帮用户垫资一次,需要用户去微信支付点击还款,如果不还款,用户就使用不了免密支付)

查询用户状态函数, 查看用户是否欠费。有没有支付能力。
    private Map<String,Object> _queryState(String openid, String contract_id){
        //将所有参数组装成map排序
        Map<String, Object> signMap = new TreeMap<>(
                new Comparator<String>() {
                    public int compare(String obj1, String obj2) {
                        // 升序排序
                        return obj1.compareTo(obj2);
                    }
                }
        );
        signMap.put("appid",appid);
        signMap.put("mch_id",mch_id);
        //生成随机字符串
        String nonce_str= MD5Utils.getNonceStr(32);
        signMap.put("nonce_str",nonce_str);
        signMap.put("contract_id",contract_id);
        signMap.put("sign_type","MD5");
        signMap.put("openid",openid);
        //获取签名结果
        String sign=getSign(signMap);
        //拼接XML
        StringBuilder XML = new StringBuilder();
        XML.append("<xml>");
        XML.append("<appid><![CDATA["+appid+"]]></appid>");
        XML.append("<mch_id><![CDATA["+mch_id+"]]></mch_id>");
        XML.append("<nonce_str><![CDATA["+nonce_str+"]]></nonce_str>");
        XML.append("<contract_id><![CDATA["+contract_id+"]]></contract_id>");
        XML.append("<sign_type><![CDATA[MD5]]></sign_type>");
        XML.append("<sign><![CDATA["+sign+"]]></sign>");
        XML.append("<openid><![CDATA["+openid+"]]></openid>");
        XML.append("</xml>");
        String xml = XML.toString();
        //  调用请求微信接口函数 并return结果
        HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
        Document document = null;
        try {
            String wxResponseText = httpsClientUtil.doPostXml(wxQueryStateUrl, xml);
            //开始解析xml字符串,验证签名我就不写这里了,删除了,太长了!你们注意别忘记验证消息安全性。
            document = DocumentHelper.parseText(wxResponseText);
        }catch (Exception ex){
            results.put("success",false);
            results.put("msg", "_queryState 发生异常");
            results.put("message", ex);
            return results;
        }
        Element root = document.getRootElement();
        Element successEl=root.element("return_code");//查询状态
        String success=successEl.getStringValue();
        //开始判断结果
        if(success.equals("SUCCESS")){//判断查询状态
            Element resultCodeEL=root.element("result_code"); //业务结果
            String resultCode=resultCodeEL.getStringValue();
            if( resultCode.equals("SUCCESS") ){//判断业务结果
                //存放结果数据
                Map<String,Object> item= new HashMap<>();
                //用户状态
                Element userStateEl=root.element("user_state");
                String userState=userStateEl.getStringValue();
                String contractState="";
                if( userState.equals("NORMAL") ){//允许
                    item.put("user_state", 0);
                    //签约状态 0 签约中  1 解约
                    Element contractStateEl=root.element("contract_state");
                    contractState=contractStateEl.getStringValue();
                }else if( userState.equals("BLOCKED") ){//不允许
                    item.put("user_state", 1);
                    //签约状态 0 签约中  1 解约
                    Element contractStateEl=root.element("contract_state");
                    contractState=contractStateEl.getStringValue();
                }else if( userState.equals("OVERDUE") ){//用户欠费
                    item.put("user_state", 2);
                    contractState="0";
                }
                item.put("contract_state", contractState);
                results.put("result",item);
                results.put("success", true);
            }else if( resultCode.equals("FAIL") ){
                Element errCodeEl=root.element("err_code");//错误代码
                String errCode=errCodeEl.getStringValue();
                Element errCodeDesEl=root.element("err_code_des");//错误代码描述
                String errCodeDes=errCodeDesEl.getStringValue();
                results.put("success",false);
                results.put("msg",errCode);
                results.put("message", errCodeDes);
            }
        }else if(success.equals("FAIL")){
            Element msgEl=root.element("return_msg");//错误原因
            String msg=msgEl.getStringValue();
            results.put("success",false);
            results.put("msg",msg);
        }
        return results;
    }

发起扣款函数,签名我用的是MD5加密签名,这里需要注意的是签名的key是你商户号里面的key,注意保密。签名之前,参数需要排好序123 、abc等不多说。注意,给微信传的必须是XML字符串

至于md5加密方法网上可以找,这个是我的
    //首先,这是我的MD5加密类
    package com.sutpc.utils;

    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Random;

    public class MD5Utils {
        
        private static final String DIGEST_ALGORITHM = "MD5";
        private static final char HEX_DIGITS[] = 
            {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
        
        public static final String md5(String msg) {
            if (msg == null) {
                return null;
            }
            MessageDigest md5 = null;
            try {
                md5 = MessageDigest.getInstance(DIGEST_ALGORITHM);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            if (md5 == null) {
                return null;
            }
            try {
                md5.update(msg.getBytes("utf-8"));
            }catch(Exception ex){}
            byte[] digest = md5.digest();
            return byte2Hex(digest);
            
        }
        public static String getNonceStr(int length){
            String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            Random random = new Random();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < length; i++) {
                int number = random.nextInt(base.length());
                sb.append(base.charAt(number));
            }
            return sb.toString();
        }
        private static final String byte2Hex(byte[] b) {
            int j = b.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = b[i];
                str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
                str[k++] = HEX_DIGITS[byte0 & 0xf];
            }
            return new String(str);
        }
        
        /**
        * @param args
        */
        public static void main(String[] args) {
            System.out.println(MD5Utils.md5(null));
            System.out.println(MD5Utils.md5("Ngis_Admin_3975528"));
            System.out.println(MD5Utils.md5("111111"));
            System.out.println(MD5Utils.md5(""));
            System.out.println(MD5Utils.md5("123"));
            System.out.println(MD5Utils.md5("12dsafasf3"));
            System.out.println(MD5Utils.md5("全是有工在以有"));
            System.out.println(MD5Utils.md5("全是有工在以有"));
            System.out.println(MD5Utils.md5("1"));

        }

    }

    //其次,这是我的加密函数 。传入排序后map,生成签名
    public String getSign(Map<String, Object> signMap){
        //组装md5签名字符串
        String sign="";
        for(String nameKey:signMap.keySet()){
            sign += nameKey+"="+signMap.get(nameKey)+"&";
        }
        sign+="key="+signKey;
        //调用md5签名方法
        return MD5Utils.md5(sign).toUpperCase();
    }

这个是我组装发起扣款的函数,前面获取各种参数每个程序不同我没放进去。
这里面需要注意:contract_id 是用户免密签约时微信给的, 但是如果用户解约了、在签约这个值会重新变,所有我扣款时每次找微信查,以防出错。 你可以用你用户签约时你在数据库存的。
这里说一下,请求微信查询contract_id时,我们用户有几十万,其中有一两个用户信息获取contract_id时候微信接口返回请求超时,让我们重复请求,但是依旧是超时,所以从我们数据库获取这两个用户签约信息。 信息保存很重要吧。。
api:"https://api.mch.weixin.qq.com/transit/pay/payapply"

发起扣款接口
        // 传入组装好的的参数,请求微信接口发起扣款申请
    public String doWxPay(Map<String, Object> paramMap) throws Exception {
        // ****  从传入对象中获取值
        String sign_type="MD5";
        String sign;
        String body="公交乘车代扣";
        String nonce_str= MD5Utils.getNonceStr(32);//随机字符串
        String start_time= (String) paramMap.get("start_time");//下车时间
        String end_time= (String) paramMap.get("end_time");//下车时间
        String line_name= (String) paramMap.get("lineName");//线路名称
        String openid= (String) paramMap.get("openid");//订单号
        String out_trade_no= (String) paramMap.get("orderNo");
        int total_fee = (int) paramMap.get("total_fee");//微信传输单位为分
        String spbill_create_ip= InetAddress.getLocalHost().getHostAddress();//获取本机ip
        String scene_info;
        String trade_scene;
        if( paramMap.containsKey("start_stationName") && paramMap.containsKey("end_stationName") ){
                  //我们有两种场景 地铁和公交 你们看情况用
            String start_stationName=line_name +"("+(String) paramMap.get("start_stationName");
            if(start_stationName.length()>32){
                start_stationName=start_stationName.substring(0,32);
            }
            String end_stationName= (String) paramMap.get("end_stationName") + ")";
            if(end_stationName.length()>32){
                end_stationName=end_stationName.substring(0,32);
            }
            //地铁场景 上车时间+下车时间+上车站点+下车站点
            trade_scene="METRO";
            //扣款用的是下车时间
            scene_info="{\"scene_info\":{\"start_time\":\""+start_time+"\",\"end_time\":\""+end_time+"\",\"start_station\":\""+start_stationName+"\",\"end_station\":\""+end_stationName+"\"}}";
        }else{
            //公交场景 上车时间+线路名称
            trade_scene="BUS";
            scene_info="{\"scene_info\":{\"start_time\":\""+end_time+"\",\"line_name\":\""+line_name+"\"}}";
        }
        // **** 开始获取contract_code
        String contract_id;
        Map<String, Object>  contract_query_rtn = _queryContract(openid);
        if((boolean)contract_query_rtn.get("success")){
            contract_id = (String)contract_query_rtn.get("contract_id");
        }else{
            //用openid查contract,微信返回错误 此错误是 系统超时 提示让重复查询 但是重复也一直查不到,所有去数据库查 如果查到就赋值,下面回去查用户状态。
            if(contract_query_rtn.get("message").equals("SYSTEM ERROR")){
                Map<String,String> selectData= new HashMap<String, String>();
                selectData.put("openid",openid);
                List<Map<String, Object>> queryList = null;
                try{
                    queryList = dbExecutor.query("com..selectContract", selectData);
                    if(queryList.size()>0){
                        contract_id= (String) queryList.get(0).get("contract_id");
                    }else{
                        throw new Exception("数据库中无此用户contract_id!");
                    }
                }catch (Exception e){
                    throw new Exception("从数据库查询contract_id失败!");
                }
            }else{
                throw new Exception("微信查询contract_id失败!");
            }
        }
        // **** 开始储存订单到数据库 支付结果等微信回执中更改
        try{
            dbExecutor.insert("com.sutpc.base.dao.PayDao.saveOrder", paramMap);
        }catch (Exception e){
            rootLogger.error("doWxPay-----存储失败!");
            rootLogger.error(e);
        }
        // **** 将所有参数组装成map排序
        Map<String, Object> signMap = new TreeMap<String, Object>(
        new Comparator<String>() {
            public int compare(String obj1, String obj2) {
                // 升序排序
                return obj1.compareTo(obj2);
            }
        });
        signMap.put("sign_type",sign_type);
        signMap.put("body",body);
        signMap.put("nonce_str",nonce_str);
        signMap.put("out_trade_no",out_trade_no);
        signMap.put("total_fee",total_fee);
        signMap.put("spbill_create_ip",spbill_create_ip);
        signMap.put("contract_id",contract_id);
        signMap.put("scene_info",scene_info);
        signMap.put("mch_id",mch_id);
        signMap.put("appid",appid);
        signMap.put("notify_url",notify_url);
        signMap.put("trade_type",trade_type);
        signMap.put("trade_scene",trade_scene);
        //调用组合签名字符串函数,获取签名结果
        sign=getSign(signMap);
        // ****: 开始拼接xml
        StringBuilder XML = new StringBuilder();
        XML.append("<xml>");
        XML.append("<mch_id><![CDATA["+mch_id+"]]></mch_id>");
        XML.append("<appid><![CDATA["+appid+"]]></appid>");
        XML.append("<notify_url><![CDATA["+notify_url+"]]></notify_url>");
        XML.append("<trade_type><![CDATA["+trade_type+"]]></trade_type>");
        XML.append("<trade_scene><![CDATA["+trade_scene+"]]></trade_scene>");
        XML.append("<nonce_str><![CDATA["+nonce_str+"]]></nonce_str>");
        XML.append("<sign_type><![CDATA["+sign_type+"]]></sign_type>");
        XML.append("<sign><![CDATA["+sign+"]]></sign>");
        XML.append("<body><![CDATA["+body+"]]></body>");
        XML.append("<out_trade_no><![CDATA["+out_trade_no+"]]></out_trade_no>");
        XML.append("<total_fee><![CDATA["+total_fee+"]]></total_fee>");
        XML.append("<spbill_create_ip><![CDATA["+spbill_create_ip+"]]></spbill_create_ip>");
        XML.append("<contract_id><![CDATA["+contract_id+"]]></contract_id>");
        XML.append("<scene_info><![CDATA["+scene_info+"]]></scene_info>");
        XML.append("</xml>");
        String xml = XML.toString();
        rootLogger.info(xml);
        // ****: 调用请求微信接口函数 并return结果
        HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
        return httpsClientUtil.doPostXml(wxPayApplyUrl, xml);
    }
自己写的发送请求方法,每个人每个库都不一样。
public String doPostXml(String url, String xml) throws Exception{
        String result = null;
        HttpClient httpClient = new SSLClient();
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Content-Type","text/xml;charset=UTF-8");
        StringEntity stringEntity = new StringEntity(xml, "UTF-8");
        stringEntity.setContentEncoding("UTF-8");

        httpPost.setEntity(stringEntity);
        HttpResponse response = httpClient.execute(httpPost);
        if(response != null){
            HttpEntity resEntity = response.getEntity();
            if(resEntity != null){
                result = EntityUtils.toString(resEntity, "UTF-8");
            }
        }
        return result;
    }
这个请求返回的结果也是一个XML,那么你就要解析这个结果,我用的是java的包 dom4j 非常好用。
    //开始解析xml字符串
    Document document = DocumentHelper.parseText(wxResponseText);
    Element root = document.getRootElement();
    Element successEl = root.element("return_code");
    String success = successEl.getStringValue();
    Element msgEl = root.element("return_msg");
    String msg = msgEl.getStringValue();//getTextTrim
    //开始判断结果
    if (success.equals("SUCCESS")) {//判断握手结果
        Element resultCodeEl = root.element("result_code");
        String resultCode = resultCodeEl.getStringValue();
        if (resultCode.equals("SUCCESS")) {//判断业务结果
            item.put("success", true);
        } else {
            Element errCodeEl = root.element("err_code");//错误代码
            String errCode = errCodeEl.getStringValue();
            Element errCodeDesEl = root.element("err_code_des");//错误代码描述
            String errCodeDes = errCodeDesEl.getStringValue();
            item.put("success", false);
            item.put("msg", errCode);

            // **** 付款失败更新订单状态
            // 如果错误状态是 ORDERPAID(订单已支付 订单号重复)就不更新。
            if (!errCode.equals("ORDERPAID")) {
                //付款失败 更新订单状态以及错误码
                itemParam.put("trade_state", resultCode);
                itemParam.put("err_code", errCode);
                itemParam.put("err_code_des", errCodeDes);
                sqlSession.update("com..updateOrder", itemParam);

                //付款失败 开始储存付款失败订单
                this.saveFailOrder(itemParam);
            }
        }
    } else {
        item.put("success", false);
        item.put("msg", msg);
        // **** 请求失败更新订单状态
        // 如果错误状态是 ORDERPAID(订单已支付 订单号重复)就不更新。
        if (!msg.equals("ORDERPAID")) {
            //付款失败 更新订单状态以及错误码
            itemParam.put("trade_state", success);
            itemParam.put("err_code", msg);
            sqlSession.update("com.sutpc.dao.PayDao.updateOrder", itemParam);

            //付款失败 开始储存付款失败订单
            this.saveFailOrder(itemParam);
        }
    }
发起扣款时,填写的notify_url接口函数 用来接收请求微信扣款后,微信异步返回的结果通知xml
    // 接收发起扣款后小程序返回的结果接口
    public String payNotify() throws Exception{
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = request.getReader();
        char[]buff = new char[1024];
        int len;
        while((len = reader.read(buff)) != -1) {
            sb.append(buff,0, len);
        }
        String wxResponseText = sb.toString();
//      rootLogger.info("收到paynotify");
        //开始解析xml字符串
        Document document = DocumentHelper.parseText(wxResponseText);
        Element root = document.getRootElement();
        Element successEl=root.element("return_code");//查询状态
        String success=successEl.getStringValue();
        Element return_msgEl;
        String return_msg="";
        if(success.equals("SUCCESS")){
            Element resultCodeEl=root.element("result_code");//查询状态
            String resultCode=resultCodeEl.getStringValue();
            Element tradeStateEl=root.element("trade_state");//订单状态 SUCCESS 成功 PAY_FAIL 失败
            String tradeState=tradeStateEl.getStringValue();
            Element orderNoEl=root.element("out_trade_no");//商户订单号
            String orderNo=orderNoEl.getStringValue();
            Element transactionIdEl=root.element("transaction_id");//微信订单号time_end
            String transactionId=transactionIdEl.getStringValue();
            Element timeEndEl=root.element("time_end");//time_end
            String timeEnd=timeEndEl.getStringValue();
            //以防扫码时候存订单没存上 所有多放点数据 一遍插入时候使用 正常情况肯定都是修改updateOrder 但是以防万一
            Element openidEl=root.element("openid");//time_end
            String openid=openidEl.getStringValue();
            Element totalFeeEl=root.element("total_fee");//金额
            String totalFee=totalFeeEl.getStringValue();
            int totalFeeInt=Integer.parseInt(totalFee);//转成int
            //申明储存数据对象
            Map<String,Object> orderData = new HashMap<String,Object>();
            orderData.put("trade_state",tradeState);//订单状态
            orderData.put("orderNo",orderNo);//订单号
            orderData.put("transactionId",transactionId);//微信付款订单号
            orderData.put("pay_time",timeEnd);//正常应该是修改
            orderData.put("openid",openid);//用户id
            orderData.put("total_fee",totalFeeInt);//最终付款金额
            String err_code="";
            String err_code_des="";
            if( resultCode.equals("FAIL") ){
                try{
                    Element err_codeEl=root.element("err_code");//商户订单号
                    err_code=err_codeEl.getStringValue();
                    Element err_code_desEl=root.element("err_code_des");//微信订单号time_end
                    err_code_des=err_code_desEl.getStringValue();
                }catch (Exception e){
                    rootLogger.error("payNotify------解析结果时发送异常!");
                    rootLogger.error(wxResponseText);
                    rootLogger.error(e);
                    return "success";
                }
            }
            // TODO 如果付款失败,且错误状态是 ORDERPAID(订单已支付 订单号重复)就不更新。
            if(err_code.equals("ORDERPAID")){
                return "success";
            }
            orderData.put("err_code",err_code);//如果付款失败记录原因
            orderData.put("err_code_des",err_code_des);//如果付款失败记录原因
            //开始储存
            //先查询有没有,存放查询结果
            List<Map<String, Object>> queryList = null;
            try{
                queryList = dbExecutor.query("com.sutpc.dao.PayDao.selectOrder", orderData);
            }catch (Exception e){
                rootLogger.error("payNotify-----query---查询订单时发生未知异常!");
                rootLogger.error(e);
                return "success";
            }
            //判断查询结果 如果有就修改 如果没有就插入
            if( queryList==null || queryList.size()==0 ){
                try{
                    dbExecutor.insert("com.sutpc.dao.PayDao.saveOrder", orderData);
                }catch (Exception e){
                    rootLogger.error("payNotify---insert-----添加订单时发生异常!");
                    rootLogger.error(e);
                    return "success";
                }
            }else{
                try{
                    dbExecutor.update("com.sutpc.dao.PayDao.updateOrder", orderData);
                }catch (Exception e){
                    rootLogger.error("payNotify----update---修改订单支付状态时发送异常!");
                    rootLogger.error(e);
                    return "success";
                }
            }
        }else{
            return_msgEl=root.element("return_msg");//查询状态
            return_msg=return_msgEl.getStringValue();
        }

//      rootLogger.info("完成  paynotify");
        return "success";
        //return       "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
    }

扣款的就这些,你根据我上面放的api文档链接看步骤来就行,实在看不懂抄我的这个也行,自己弄好参数传过来。
注:不论是签约还是发起扣款申请,里面填写的notify_url你必须写好接口接收微信异步发送回来的数据,而且只要收到后,就必须回复,不然微信会一直高频率的给你发,直到你的服务器爆炸。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270