微信小程序開發(fā)-微信支付之免密支付(自動扣費)一 小程序+java接口

微信小程序開發(fā)之---微信免密支付

很多時候我們都要用到免密支付焕刮,也就是常說的自動扣款,但是如何才能自動扣費席噩,這一塊微信api也沒有詳細說明模捂,微信普通的支付官方說的很清楚捶朵,但是免密支付可就少了。狂男。這類的支付的東西很多综看,比如滴滴打車、App Store微信支付并淋、二維碼乘車等寓搬。

下面是我做的一個項目,小程序县耽、后臺接口、數(shù)據(jù)庫構(gòu)建镣典。二維碼乘車兔毙,微信小程序生成二維碼,公交機器上面安裝卡機掃碼扣費兄春。上線四個月澎剥,我們用戶量幾十萬,每天掃碼訂單數(shù)兩萬多赶舆,金額十來萬哑姚。


image.png
image.png
image.png
image.png
image.png
圖片發(fā)自簡書App

需要準備的東西:

    1祭饭、申請微信小程序號。
    2叙量、申請微信商戶號(商戶開通委托代扣倡蝙,好像需要私下找微信走流程,我用的賬號是甲方提供的)绞佩。
    3寺鸥、下載微信開發(fā)者工具。

按照下面圖片進入開發(fā)者工具品山,創(chuàng)建好你的項目胆建,根據(jù)圖片選工具自動回給你創(chuàng)建項目目錄,這一點就不詳細說了肘交。

一些發(fā)送的參數(shù)和結(jié)果我不會詳細說笆载,可以看下下面鏈接詳細說明姑曙,這是我們和微信合作粘舟,微信人員給的api內(nèi)部文檔,免密支付(自動扣費)api文檔地址:https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=18_3&index=7

其實有了這個鏈接看看基本差不多懂了蟆技!魄懂。沿侈。。市栗。缀拭。

接著往下看吧。

免密支付首先要和用戶簽訂免密支付協(xié)議填帽。怎么簽蛛淋?

首先需要用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發(fā)起簽約請求,這個是簽約的方法究西,
不過首先要把 簽約小程序放在 app.json中 這是微信新的規(guī)則

  "navigateToMiniProgramAppIdList": [
    "wxbd687630cd02ce1d",  簽約小程序
    "wx5e73c65404eee268"   微信代付 用戶還款小程序
  ]

wxml

//bindGetUserInfo 是我的邏輯函數(shù)窗慎,不需要看,
    <button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" class="btn_wxzf">立即綁定</button>

js

    //發(fā)起簽約請求 data里面?zhèn)髦凳潜仨殏鞯膸醉椪瑳]強制要求的我沒傳
    var me = this;
    //裝作參數(shù)
    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(在商戶號里面設(shè)置)
    contract_code: this.globalData.contract_code, //簽約碼捉邢,商家生成,商戶側(cè)須唯一
    contract_display_account: this.turnNickName(this.globalData.userInfo["nickName"]||""), //簽約用戶名稱商膊,我這里用的是用戶微信名字(怎么獲取下面有)本來我想用手機號的伏伐,但是獲取手機號需要注冊或者是微信api獲取需要用戶點擊同意,甲方說用戶多操作一步用戶體驗不好晕拆。藐翎。。
    notify_url: "https://www.***.com/contractNotify",// 簽約成功與否微信返回數(shù)據(jù)的接收地址
    request_serial: ((new Date()).getTime() - 1526353000000),//商戶請求簽約時的序列號純數(shù)字,長度不超過12位
    timestamp: parseInt((new Date()).getTime() / 1000) + "" //時間戳 
    };
    //簽名 MD5加密
    data.sign = util.genSign(data, this.globalData.key);
    //開始發(fā)起簽約
    wx.navigateToMiniProgram({
        appId: 'wxbd687630cd02ce1d', //固定值实幕,這個是填寫微信官方簽約小程序的id
        extraData: data,
        path: 'pages/index/index',
        success(res) {
            wx.setStorageSync('contract_id', "");
            me.globalData.contract_id = "";
            // 成功跳轉(zhuǎn)到簽約小程序 
        },
        fail(res) {
            console.log(res);
            // 未成功跳轉(zhuǎn)到簽約小程序 
        }
    });

簽約后會返回一些簽約信息吝镣,在app.js文件中 onShow函數(shù)中獲取。
這里面需要注意的是昆庇,用戶點擊的是返回還是點擊左上角的X末贾。。整吆。 我是當用戶點擊簽約按鈕時候拱撵,就開啟一個定時器去監(jiān)控,onShow里面取后臺數(shù)據(jù)庫查詢簽約狀態(tài)表蝙,(微信會把簽約結(jié)果異步通知到notify_url拴测,在里面存到數(shù)據(jù)庫就行)

App({
    onShow(res) {
        if (res.scene === 1038) { // 場景值1038:從被打開的小程序返回
            const { appId, extraData } = res.referrerInfo
            if (appId == 'wxbd687630cd02ce1d') { // appId為wxbd687630cd02ce1d:從簽約小程序跳轉(zhuǎn)回來
                if (typeof extraData == 'undefined'){
                    // TODO  ***用戶擊左上角的返回***  
                    // 客戶端小程序不確定簽約結(jié)果,需要向商戶側(cè)后臺請求確定簽約結(jié)果  
                    return;
                }
                if(extraData.return_code == 'SUCCESS'){
                    // TODO
                    // 客戶端小程序簽約成功府蛇,需要向商戶側(cè)后臺請求確認簽約結(jié)果
                    var contract_id = extraData.contract_id
                    return;
                } else {
                    // TODO
                    // 簽約失敗
                    return;
                }
            }
        }
    }
})
notify_url函數(shù)集索,用來接收用戶簽約微信返回的結(jié)果xml

注意:只要收到微信通知,你就必須返回結(jié)果

失敾憧纭:
        <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>
    // 接收發(fā)起免密簽約后小程序返回的結(jié)果接口
    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");//查詢狀態(tài)
        String success=successEl.getStringValue();
        Element return_msgEl;
        String return_msg="";
        if(success.equals("SUCCESS")){
            Element resultCodeEl=root.element("result_code");//查詢狀態(tài)
            String resultCode=resultCodeEl.getStringValue();
            if(resultCode.equals("SUCCESS")){
                Element contractCodeEl=root.element("contract_code");//查詢狀態(tài)
                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();
                //申明儲存數(shù)據(jù)對象
                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存
                //開始儲存
                //先查詢有沒有务荆,存放查詢結(jié)果
                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";
                }
                //判斷查詢結(jié)果 如果有就修改 如果沒有就插入
                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");//查詢狀態(tài)
            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
           })
           //已授權(quán)開始綁定
           this.bindBtnBangDing();
       }else{
           wx.showToast({
           title: '您未同意授權(quán)',
           image: '../../images/shangxin.png',
           duration: 1000,
           mask: true
           });
       }
       }
   },

到以上伴网,簽約已經(jīng)成功了蓬推,當需要扣款時,就可以用用戶的contract_id進行扣款了(如:掃碼乘車澡腾,滴滴打車等沸伏,我們是掃碼乘車用),不論什么時候扣动分,都能直接成功毅糟,用戶會收到一條微信支付消息。

那么如何扣款澜公? 接下來就是java干的事情了姆另,當需要扣用戶費用時候,后臺服務調(diào)用申請扣款接口即可坟乾。

組裝一些必要的參數(shù)迹辐,然后向微信API發(fā)起扣款請求即可,那么需要什么參數(shù)甚侣?可以看最上面我放的鏈接明吩!詳細的接口介紹!

請求appid殷费、商戶號印荔、隨機字符串、簽名(我用的是MD5)宗兼、商品描述躏鱼、商戶訂單號、總金額殷绍、終端IP(發(fā)起扣款電腦ip染苛,運行程序的服務器地址)、回調(diào)通知url(接收XML扣款結(jié)果地址)主到、交易類型(填PAP微信委托代 扣支付)茶行、委托代扣協(xié)議id(用戶簽約的contract_id) 這些是必須傳的,沒有強制要求的我們傳登钥,我懶畔师。

我用的是java寫的。

注:以下這些微信API需要傳輸?shù)脑敿殔?shù)看上面我放的鏈接牧牢。我只傳了一些必須傳的值看锉。

首先解釋下一些全局變量是什么:
    // 一些微信接口api地址-----
    // 請求扣款
    private String wxPayApplyUrl = "https://api.mch.weixin.qq.com/transit/pay/payapply";
    // 查詢openid
    private String code2OpenidUrl="https://api.weixin.qq.com/sns/jscode2session";
    // 查詢訂單狀態(tài)
    private String wxQueryOrderUrl="https://api.mch.weixin.qq.com/pay/paporderquery";
    // 查詢用戶狀態(tài)
    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";
    // 發(fā)起退款
    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="***";//接收返回結(jié)果的地址
    private final String appKey="***";//小程序app的key
    private final String signKey="***";//簽名的KEY姿锭,每個商戶號一個,可以重置伯铣,但是需要嚴格保密呻此。

注:注意用戶打開小程序最好查詢一下用戶狀態(tài)有沒有支付能力,有沒有欠費(用戶如果錢不夠腔寡,微信會幫用戶墊資一次焚鲜,需要用戶去微信支付點擊還款,如果不還款放前,用戶就使用不了免密支付)

查詢用戶狀態(tài)函數(shù), 查看用戶是否欠費忿磅。有沒有支付能力。
    private Map<String,Object> _queryState(String openid, String contract_id){
        //將所有參數(shù)組裝成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);
        //獲取簽名結(jié)果
        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();
        //  調(diào)用請求微信接口函數(shù) 并return結(jié)果
        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 發(fā)生異常");
            results.put("message", ex);
            return results;
        }
        Element root = document.getRootElement();
        Element successEl=root.element("return_code");//查詢狀態(tài)
        String success=successEl.getStringValue();
        //開始判斷結(jié)果
        if(success.equals("SUCCESS")){//判斷查詢狀態(tài)
            Element resultCodeEL=root.element("result_code"); //業(yè)務結(jié)果
            String resultCode=resultCodeEL.getStringValue();
            if( resultCode.equals("SUCCESS") ){//判斷業(yè)務結(jié)果
                //存放結(jié)果數(shù)據(jù)
                Map<String,Object> item= new HashMap<>();
                //用戶狀態(tài)
                Element userStateEl=root.element("user_state");
                String userState=userStateEl.getStringValue();
                String contractState="";
                if( userState.equals("NORMAL") ){//允許
                    item.put("user_state", 0);
                    //簽約狀態(tài) 0 簽約中  1 解約
                    Element contractStateEl=root.element("contract_state");
                    contractState=contractStateEl.getStringValue();
                }else if( userState.equals("BLOCKED") ){//不允許
                    item.put("user_state", 1);
                    //簽約狀態(tài) 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;
    }

發(fā)起扣款函數(shù),簽名我用的是MD5加密簽名,這里需要注意的是簽名的key是你商戶號里面的key虫几,注意保密锤灿。簽名之前,參數(shù)需要排好序123 辆脸、abc等不多說但校。注意,給微信傳的必須是XML字符串

至于md5加密方法網(wǎng)上可以找啡氢,這個是我的
    //首先状囱,這是我的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"));

        }

    }

    //其次,這是我的加密函數(shù) 倘是。傳入排序后map亭枷,生成簽名
    public String getSign(Map<String, Object> signMap){
        //組裝md5簽名字符串
        String sign="";
        for(String nameKey:signMap.keySet()){
            sign += nameKey+"="+signMap.get(nameKey)+"&";
        }
        sign+="key="+signKey;
        //調(diào)用md5簽名方法
        return MD5Utils.md5(sign).toUpperCase();
    }

這個是我組裝發(fā)起扣款的函數(shù),前面獲取各種參數(shù)每個程序不同我沒放進去搀崭。
這里面需要注意:contract_id 是用戶免密簽約時微信給的叨粘, 但是如果用戶解約了、在簽約這個值會重新變瘤睹,所有我扣款時每次找微信查升敲,以防出錯。 你可以用你用戶簽約時你在數(shù)據(jù)庫存的轰传。
這里說一下驴党,請求微信查詢contract_id時,我們用戶有幾十萬获茬,其中有一兩個用戶信息獲取contract_id時候微信接口返回請求超時港庄,讓我們重復請求倔既,但是依舊是超時,所以從我們數(shù)據(jù)庫獲取這兩個用戶簽約信息攘轩。 信息保存很重要吧叉存。码俩。
api:"https://api.mch.weixin.qq.com/transit/pay/payapply"

發(fā)起扣款接口
        // 傳入組裝好的的參數(shù)度帮,請求微信接口發(fā)起扣款申請
    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,微信返回錯誤 此錯誤是 系統(tǒng)超時 提示讓重復查詢 但是重復也一直查不到稿存,所有去數(shù)據(jù)庫查 如果查到就賦值笨篷,下面回去查用戶狀態(tài)。
            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("數(shù)據(jù)庫中無此用戶contract_id!");
                    }
                }catch (Exception e){
                    throw new Exception("從數(shù)據(jù)庫查詢contract_id失敗!");
                }
            }else{
                throw new Exception("微信查詢contract_id失敗!");
            }
        }
        // **** 開始儲存訂單到數(shù)據(jù)庫 支付結(jié)果等微信回執(zhí)中更改
        try{
            dbExecutor.insert("com.sutpc.base.dao.PayDao.saveOrder", paramMap);
        }catch (Exception e){
            rootLogger.error("doWxPay-----存儲失敯曷摹率翅!");
            rootLogger.error(e);
        }
        // **** 將所有參數(shù)組裝成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);
        //調(diào)用組合簽名字符串函數(shù),獲取簽名結(jié)果
        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);
        // ****: 調(diào)用請求微信接口函數(shù) 并return結(jié)果
        HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
        return httpsClientUtil.doPostXml(wxPayApplyUrl, xml);
    }
自己寫的發(fā)送請求方法袖迎,每個人每個庫都不一樣冕臭。
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;
    }
這個請求返回的結(jié)果也是一個XML,那么你就要解析這個結(jié)果燕锥,我用的是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
    //開始判斷結(jié)果
    if (success.equals("SUCCESS")) {//判斷握手結(jié)果
        Element resultCodeEl = root.element("result_code");
        String resultCode = resultCodeEl.getStringValue();
        if (resultCode.equals("SUCCESS")) {//判斷業(yè)務結(jié)果
            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);

            // **** 付款失敗更新訂單狀態(tài)
            // 如果錯誤狀態(tài)是 ORDERPAID(訂單已支付 訂單號重復)就不更新。
            if (!errCode.equals("ORDERPAID")) {
                //付款失敗 更新訂單狀態(tài)以及錯誤碼
                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);
        // **** 請求失敗更新訂單狀態(tài)
        // 如果錯誤狀態(tài)是 ORDERPAID(訂單已支付 訂單號重復)就不更新归形。
        if (!msg.equals("ORDERPAID")) {
            //付款失敗 更新訂單狀態(tài)以及錯誤碼
            itemParam.put("trade_state", success);
            itemParam.put("err_code", msg);
            sqlSession.update("com.sutpc.dao.PayDao.updateOrder", itemParam);

            //付款失敗 開始儲存付款失敗訂單
            this.saveFailOrder(itemParam);
        }
    }
發(fā)起扣款時托慨,填寫的notify_url接口函數(shù) 用來接收請求微信扣款后,微信異步返回的結(jié)果通知xml
    // 接收發(fā)起扣款后小程序返回的結(jié)果接口
    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");//查詢狀態(tài)
        String success=successEl.getStringValue();
        Element return_msgEl;
        String return_msg="";
        if(success.equals("SUCCESS")){
            Element resultCodeEl=root.element("result_code");//查詢狀態(tài)
            String resultCode=resultCodeEl.getStringValue();
            Element tradeStateEl=root.element("trade_state");//訂單狀態(tài) 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();
            //以防掃碼時候存訂單沒存上 所有多放點數(shù)據(jù) 一遍插入時候使用 正常情況肯定都是修改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);//轉(zhuǎn)成int
            //申明儲存數(shù)據(jù)對象
            Map<String,Object> orderData = new HashMap<String,Object>();
            orderData.put("trade_state",tradeState);//訂單狀態(tài)
            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------解析結(jié)果時發(fā)送異常暇榴!");
                    rootLogger.error(wxResponseText);
                    rootLogger.error(e);
                    return "success";
                }
            }
            // TODO 如果付款失敗厚棵,且錯誤狀態(tài)是 ORDERPAID(訂單已支付 訂單號重復)就不更新。
            if(err_code.equals("ORDERPAID")){
                return "success";
            }
            orderData.put("err_code",err_code);//如果付款失敗記錄原因
            orderData.put("err_code_des",err_code_des);//如果付款失敗記錄原因
            //開始儲存
            //先查詢有沒有蔼紧,存放查詢結(jié)果
            List<Map<String, Object>> queryList = null;
            try{
                queryList = dbExecutor.query("com.sutpc.dao.PayDao.selectOrder", orderData);
            }catch (Exception e){
                rootLogger.error("payNotify-----query---查詢訂單時發(fā)生未知異常婆硬!");
                rootLogger.error(e);
                return "success";
            }
            //判斷查詢結(jié)果 如果有就修改 如果沒有就插入
            if( queryList==null || queryList.size()==0 ){
                try{
                    dbExecutor.insert("com.sutpc.dao.PayDao.saveOrder", orderData);
                }catch (Exception e){
                    rootLogger.error("payNotify---insert-----添加訂單時發(fā)生異常!");
                    rootLogger.error(e);
                    return "success";
                }
            }else{
                try{
                    dbExecutor.update("com.sutpc.dao.PayDao.updateOrder", orderData);
                }catch (Exception e){
                    rootLogger.error("payNotify----update---修改訂單支付狀態(tài)時發(fā)送異常奸例!");
                    rootLogger.error(e);
                    return "success";
                }
            }
        }else{
            return_msgEl=root.element("return_msg");//查詢狀態(tài)
            return_msg=return_msgEl.getStringValue();
        }

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

扣款的就這些彬犯,你根據(jù)我上面放的api文檔鏈接看步驟來就行,實在看不懂抄我的這個也行哩至,自己弄好參數(shù)傳過來躏嚎。
注:不論是簽約還是發(fā)起扣款申請,里面填寫的notify_url你必須寫好接口接收微信異步發(fā)送回來的數(shù)據(jù)菩貌,而且只要收到后卢佣,就必須回復,不然微信會一直高頻率的給你發(fā)箭阶,直到你的服務器爆炸虚茶。戈鲁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嘹叫,隨后出現(xiàn)的幾起案子婆殿,更是在濱河造成了極大的恐慌,老刑警劉巖罩扇,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婆芦,死亡現(xiàn)場離奇詭異,居然都是意外死亡喂饥,警方通過查閱死者的電腦和手機消约,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來员帮,“玉大人或粮,你說我怎么就攤上這事±谈撸” “怎么了氯材?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長硝岗。 經(jīng)常有香客問我氢哮,道長,這世上最難降的妖魔是什么辈讶? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任命浴,我火速辦了婚禮,結(jié)果婚禮上贱除,老公的妹妹穿的比我還像新娘生闲。我一直安慰自己,他們只是感情好月幌,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布碍讯。 她就那樣靜靜地躺著,像睡著了一般扯躺。 火紅的嫁衣襯著肌膚如雪捉兴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天录语,我揣著相機與錄音倍啥,去河邊找鬼。 笑死澎埠,一個胖子當著我的面吹牛虽缕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒲稳,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼氮趋,長吁一口氣:“原來是場噩夢啊……” “哼伍派!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起剩胁,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诉植,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昵观,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晾腔,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年索昂,在試婚紗的時候發(fā)現(xiàn)自己被綠了建车。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡椒惨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出潮罪,到底是詐尸還是另有隱情康谆,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布嫉到,位于F島的核電站沃暗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏何恶。R本人自食惡果不足惜孽锥,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望细层。 院中可真熱鬧惜辑,春花似錦、人聲如沸疫赎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捧搞。三九已至抵卫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胎撇,已是汗流浹背介粘。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晚树,地道東北人姻采。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像题涨,于是被迫代替她去往敵國和親偎谁。 傳聞我的和親對象是個殘疾皇子总滩,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355