成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

微信支付服務(wù)端開發(fā)總結(jié)

馬忠志 / 3388人閱讀

摘要:前言最近應(yīng)公司業(yè)務(wù)需求,把微信支付完成了,當然已經(jīng)順利上線。第三步查詢訂單該接口提供所有微信支付訂單的查詢,商戶可以通過該接口主動查詢訂單狀態(tài),完成下一步的業(yè)務(wù)邏輯。

前言

最近應(yīng)公司業(yè)務(wù)需求,把微信支付完成了,當然已經(jīng)順利上線。但是開發(fā)的過程是也是踩了很多坑,下面我就先說說開發(fā)流程,以及在開發(fā)中遇到的大大小小的坑。

開發(fā)流程

首先,看一下微信開方平臺關(guān)于支付的一個時序圖,如下:

微信支付時序圖
https://pay.weixin.qq.com/wiki/doc/api/app/app.php

商戶系統(tǒng)和微信支付系統(tǒng)主要交互說明:
步驟1:用戶在商戶APP中選擇商品,提交訂單,選擇微信支付。
步驟2:商戶后臺收到用戶支付單,調(diào)用微信支付統(tǒng)一下單接口。參見【統(tǒng)一下單API】。
步驟3:統(tǒng)一下單接口返回正常的prepay_id,再按簽名規(guī)范重新生成簽名后,將數(shù)據(jù)傳輸給APP。參與簽名的字段名為appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式為Sign=WXPay
步驟4:商戶APP調(diào)起微信支付。api參見本章節(jié)【app端開發(fā)步驟說明】
步驟5:商戶后臺接收支付通知。api參見【支付結(jié)果通知API】
步驟6:商戶后臺查詢支付結(jié)果。,api參見【查詢訂單API】

這里我講解的服務(wù)端的開發(fā),那我們就看服務(wù)端需要做什么工作。

第一步 統(tǒng)一下單

商戶系統(tǒng)先調(diào)用該接口在微信支付服務(wù)后臺生成預(yù)支付交易單,返回正確的預(yù)支付交易回話標識后再在APP里面調(diào)起支付。
首先,準備請求的參數(shù)
代碼如下:

private SortedMap prepareOrder(String ip, String orderId,
            int price) {
        Map oparams = ImmutableMap. builder()
                .put("appid", ConfigUtil.APPID)//應(yīng)用號
                .put("body", WeixinConstant.PRODUCT_BODY)// 商品描述
                .put("mch_id", ConfigUtil.MCH_ID)// 商戶號
                .put("nonce_str", PayCommonUtil.CreateNoncestr())// 16隨機字符串(大小寫字母加數(shù)字)
                .put("out_trade_no", orderId)// 商戶訂單號
                .put("total_fee", "1")// 銀行幣種支付的錢錢啦
                .put("spbill_create_ip", ip)// IP地址
                .put("notify_url", ConfigUtil.NOTIFY_URL) // 微信回調(diào)地址
                .put("trade_type", ConfigUtil.TRADE_TYPE)// 支付類型 APP
                .build();
        return MapUtils.sortMap(oparams);
    }

接下來將這些請求參數(shù)格式化成XML格式的數(shù)據(jù) like this


   wx2421b1c4370ec43b
   支付測試
   APP支付測試
   10000100
   1add1a30ac87aa2db72f57a2375d8fec
   http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php
   1415659990
   14.23.150.211
   1
   APP
   0CB01533B8C1EF103065174F50BCA001

請求統(tǒng)一下單地址 https://api.mch.weixin.qq.com/pay/unifiedorder
代碼(部分代碼,完整的代碼請見我的)github

String requestXML = PayCommonUtil.getRequestXml(parameters);// 生成xml格式字符串
String responseStr = HttpUtil.httpsRequest(
ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);// 帶上post

完成之后將微信返回的數(shù)據(jù)進行解析,取出APP客戶端需要的數(shù)據(jù),用于喚起微信支付。
代碼

    /**
     * 生成訂單完成,返回給android,ios喚起微信所需要的參數(shù)。
     * 
     * @param resutlMap
     * @return
     * @throws UnsupportedEncodingException
     */
    private SortedMap buildClientJson(
            Map resutlMap) throws UnsupportedEncodingException {
        // 獲取微信返回的簽名

        /**
         * backObject.put("appid", appid);
         * 
         * backObject.put("noncestr", payParams.get("noncestr"));
         * 
         * backObject.put("package", "Sign=WXPay");
         * 
         * backObject.put("partnerid", payParams.get("partnerid"));
         * 
         * backObject.put("prepayid", payParams.get("prepayid"));
         * 
         * backObject.put("appkey", this.appkey);
         * 
         * backObject.put("timestamp",payParams.get("timestamp"));
         * 
         * backObject.put("sign",payParams.get("sign"));
         */
        Map params = ImmutableMap. builder()
                .put("appid", ConfigUtil.APPID)
                .put("noncestr", PayCommonUtil.CreateNoncestr())
                .put("package", "Sign=WXPay")
                .put("partnerid", ConfigUtil.MCH_ID)
                .put("prepayid", resutlMap.get("prepay_id"))
                .put("timestamp", DateUtils.getTimeStamp()).build();//取10位時間戳
        // key ASCII排序
        SortedMap sortMap = MapUtils.sortMap(params);
        sortMap.put("package", "Sign=WXPay");
        // paySign的生成規(guī)則和Sign的生成規(guī)則同理
        String paySign = PayCommonUtil.createSign("UTF-8", sortMap);
        sortMap.put("sign", paySign);
        return sortMap;
    }

整個統(tǒng)一下訂單的邏輯就完成了。這里小結(jié)一下:

請求參數(shù)需要按照參數(shù)的key進行字母的ASCII碼進行排序,由于我使用的是map數(shù)據(jù)結(jié)構(gòu),這里提供一個對map集合中的key元素進行排序的工具類

/**
     * 對map根據(jù)key進行排序 ASCII 順序
     * 
     * @param 無序的map
     * @return
     */
    public static SortedMap sortMap(Map map) {
        List> infoIds = new ArrayList>(
                map.entrySet());
        // 排序
        Collections.sort(infoIds, new Comparator>() {
            public int compare(Map.Entry o1,
                    Map.Entry o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        SortedMap sortmap = new TreeMap();
        for (int i = 0; i < infoIds.size(); i++) {
            String[] split = infoIds.get(i).toString().split("=");
            sortmap.put(split[0], split[1]);
        }
        return sortmap;
    }

對排序后的數(shù)據(jù)進行MD5簽名,微信服務(wù)端會進行校驗,防止數(shù)據(jù)在網(wǎng)絡(luò)傳輸過程中被篡改。

拿到微信響應(yīng)的數(shù)據(jù),首先要做的事,也是對獲取的數(shù)據(jù)進行簽名校驗,理由同上。

需要注意的一點,返回給app客戶端的數(shù)據(jù)的key一定是小寫,這點微信的api是沒有說明白的,之前和客戶端聯(lián)調(diào)時耽誤了很多時間,這也是微信支付被很多開發(fā)者吐槽的地方
api比較難用^-^

注意小細節(jié):返回給客戶端時時間戳要是10位的,太長ios那邊會越界,支付不成功。

第二步 調(diào)起支付

支付成功后,微信就會調(diào)用你填寫的notify_url的方法,本人微信支付的開發(fā)配置中說明了我的notify_url為http://ip:port/weixin
/pay/callback/pay.action
對后臺通知交互時,如果微信收到商戶的應(yīng)答不是成功或超時,微信認為通知失敗,微信會通過一定的策略(如 30 分鐘共 8 次)定期重新發(fā)起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。由于存在重新収送后臺通知的情況,因此同樣的通知可能會多次収送給商戶系統(tǒng)。 商戶系統(tǒng)必須能夠正確處理重復(fù)的通知。推薦的做法是,當收到通知進行處理時,首先檢查對應(yīng)業(yè)務(wù)數(shù)據(jù)的狀態(tài),判斷該通知是否已經(jīng)處理過,如果沒有處理過再進行處理,如果處理過直接返回結(jié)果成功。在對業(yè)務(wù)數(shù)據(jù)進行狀態(tài)檢查和處理之前,要采用數(shù)據(jù)鎖進行幵収控制,以避免凼數(shù)重入造成的數(shù)據(jù)混亂。判斷完成后,我們需要通知微信,我們收到信息了,不然微信就會通過一定的策略定期重新發(fā)起通知。

/**
     * 微信回調(diào)告訴微信支付結(jié)果 注意:同樣的通知可能會多次發(fā)送給此接口,注意處理重復(fù)的通知。
     * 對于支付結(jié)果通知的內(nèi)容做簽名驗證,防止數(shù)據(jù)泄漏導(dǎo)致出現(xiàn)“假通知”,造成資金損失。
     * 
     * @param params
     * @return
     */
    public String callback(HttpRequest request) {
        try {
            String responseStr = parseWeixinCallback(request);
            Map map = XMLUtil.doXMLParse(responseStr);
            // 校驗簽名 防止數(shù)據(jù)泄漏導(dǎo)致出現(xiàn)“假通知”,造成資金損失
            if (!PayCommonUtil.checkIsSignValidFromResponseString(responseStr)) {
                logger.error("微信回調(diào)失敗,簽名可能被篡改");
                return PayCommonUtil.setXML("FAIL", "invalid sign");
            }
            if (WeixinConstant.FAIL.equalsIgnoreCase(map.get("result_code")
                    .toString())) {
                logger.error("微信回調(diào)失敗");
                return PayCommonUtil.setXML("FAIL", "weixin pay fail");
            }
            if (WeixinConstant.SUCCESS.equalsIgnoreCase(map.get("result_code")
                    .toString())) {
                //獲取應(yīng)用服務(wù)器需要的數(shù)據(jù)進行持久化操作
                String outTradeNo = (String) map.get("out_trade_no");
                String transactionId = (String) map.get("transaction_id");
                String totlaFee = (String) map.get("total_fee");
                Integer totalPrice = Integer.valueOf(totlaFee);
                if (PayApp.theApp.isDebug()) {// 測試時候支付一分錢,買入價值6塊的20分鐘語音
                    totalPrice = 6;
                }
                boolean isOk = updateDB(outTradeNo, transactionId, totalPrice,
                        2);
                // 告訴微信服務(wù)器,我收到信息了,不要在調(diào)用回調(diào)action了
                if (isOk) {
                    return PayCommonUtil.setXML(WeixinConstant.SUCCESS, "OK");
                } else {
                    return PayCommonUtil
                            .setXML(WeixinConstant.FAIL, "pay fail");
                }
            }
        } catch (Exception e) {
            logger.debug("支付失敗" + e.getMessage());
            return PayCommonUtil.setXML(WeixinConstant.FAIL,
                    "weixin pay server exception");
        }
        return PayCommonUtil.setXML(WeixinConstant.FAIL, "weixin pay fail");
    }

小結(jié):

當在本地做開發(fā)時,微信回調(diào)是不方便的,這里提供一種比較快速的方法,不過前提是有云服務(wù)器。用ssh建立反向通道。

步驟如下:
(1) ssh -R 9999:localhost:9000 ubuntu@myserver_ip_address,輸入密碼;

(2) server上查看一下是否監(jiān)聽了9999端口,netstat -anltp | grep 9999;

ubuntu@VM-39-45-ubuntu:~$ netstat -anltp | grep 9999
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:9999          0.0.0.0:*               LISTEN      -
tcp6       0      0 ::1:9999                :::*                    LISTEN      -
(3) 在本地9000上開啟web服務(wù);
(4) 當微信回調(diào)公網(wǎng)服務(wù)器時就會被代理到本地9000端口對應(yīng)的web服務(wù);

這樣就可以在本地調(diào)試了,是不是很方便呢。

2.回調(diào)邏輯中記得,將重要數(shù)據(jù)在應(yīng)用服務(wù)器進行持久化哦。

第三步 查詢訂單

該接口提供所有微信支付訂單的查詢,商戶可以通過該接口主動查詢訂單狀態(tài),完成下一步的業(yè)務(wù)邏輯。

需要調(diào)用查詢接口的情況:
◆ 當商戶后臺、網(wǎng)絡(luò)、服務(wù)器等出現(xiàn)異常,商戶系統(tǒng)最終未接收到支付通知;
◆ 調(diào)用支付接口后,返回系統(tǒng)錯誤或未知交易狀態(tài)情況;
◆ 調(diào)用被掃支付API,返回USERPAYING的狀態(tài);
◆ 調(diào)用關(guān)單或撤銷接口API之前,需確認支付狀態(tài);

需要提供兩個參數(shù)
outTradeNo 商戶訂單號
transactionId 微信訂單號
二選一
請求接口 https://api.mch.weixin.qq.com/pay/orderquery
代碼:

/**
     * 封裝查詢請求數(shù)據(jù)
     * @param outTradeNo 
     * @param transactionId
     * @return
     */
    private SortedMap prepareQueryData(String outTradeNo,
            String transactionId) {
        Map queryParams = null;
        // 微信的訂單號,優(yōu)先使用
        if (null == outTradeNo || outTradeNo.length() == 0) {
            queryParams = ImmutableMap. builder()
                    .put("appid", ConfigUtil.APPID)
                    .put("mch_id", ConfigUtil.MCH_ID)
                    .put("transaction_id", transactionId)
                    .put("nonce_str", PayCommonUtil.CreateNoncestr()).build();
        } else {
            queryParams = ImmutableMap. builder()
                    .put("appid", ConfigUtil.APPID)
                    .put("mch_id", ConfigUtil.MCH_ID)
                    .put("out_trade_no", outTradeNo)
                    .put("nonce_str", PayCommonUtil.CreateNoncestr()).build();
        }
        // key ASCII 排序
        SortedMap sortMap = MapUtils.sortMap(queryParams);
        // 簽名
        String createSign = PayCommonUtil.createSign("UTF-8", sortMap);
        sortMap.put("sign", createSign);
        return sortMap;
    }

下一步對微信響應(yīng)的數(shù)據(jù)進行解析,檢查支付的狀態(tài)
代碼如下

/**
     * 查詢訂單狀態(tài)
     * 
     * @param params
     *            訂單查詢參數(shù)
     * @return
     */
    public HttpResult checkOrderStatus(SortedMap params) {
        if (params == null) {
            return HttpResult.error(1, "查詢訂單參數(shù)不能為空");
        }
        try {
            String requestXML = PayCommonUtil.getRequestXml(params);// 生成xml格式字符串
            String responseStr = HttpUtil.httpsRequest(
                    ConfigUtil.CHECK_ORDER_URL, "POST", requestXML);// 帶上post
            SortedMap responseMap = XMLUtil
                    .doXMLParse(responseStr);// 解析響應(yīng)xml格式字符串

            // 校驗響應(yīng)結(jié)果return_code
            if (WeixinConstant.FAIL.equalsIgnoreCase(responseMap.get(
                    "return_code").toString())) {
                return HttpResult.error(1, responseMap.get("return_msg")
                        .toString());
            }
            // 校驗業(yè)務(wù)結(jié)果result_code
            if (WeixinConstant.FAIL.equalsIgnoreCase(responseMap.get(
                    "result_code").toString())) {
                return HttpResult.error(2, responseMap.get("err_code")
                        .toString() + "=" + responseMap.get("err_code_des"));
            }
            // 校驗簽名
            if (!PayCommonUtil.checkIsSignValidFromResponseString(responseStr)) {
                logger.error("訂單查詢失敗,簽名可能被篡改");
                return HttpResult.error(3, "簽名錯誤");
            }
            // 判斷支付狀態(tài)
            String tradeState = responseMap.get("trade_state").toString();
            if (tradeState != null && tradeState.equals("SUCCESS")) {
                return HttpResult.success(0, "訂單支付成功");
            } else if (tradeState == null) {
                return HttpResult.error(4, "獲取訂單狀態(tài)失敗");
            } else if (tradeState.equals("REFUND")) {
                return HttpResult.error(5, "轉(zhuǎn)入退款");
            } else if (tradeState.equals("NOTPAY")) {
                return HttpResult.error(6, "未支付");
            } else if (tradeState.equals("CLOSED")) {
                return HttpResult.error(7, "已關(guān)閉");
            } else if (tradeState.equals("REVOKED")) {
                return HttpResult.error(8, "已撤銷(刷卡支付");
            } else if (tradeState.equals("USERPAYING")) {
                return HttpResult.error(9, "用戶支付中");
            } else if (tradeState.equals("PAYERROR")) {
                return HttpResult.error(10, "支付失敗");
            } else {
                return HttpResult.error(11, "未知的失敗狀態(tài)");
            }
        } catch (Exception e) {
            logger.error("訂單查詢失敗,查詢參數(shù) = {}", JSONObject.toJSONString(params));
            return HttpResult.success(1, "訂單查詢失敗");
        }
    }

整個流程就是這樣的,呵呵呵...好久沒寫博客有點手生了。對于代碼中很多工具類,這里就不一一貼出來了. Fork me on Github thanks !

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64855.html

相關(guān)文章

  • 探討一下常見支付系統(tǒng)的對外接口

    摘要:相比起來,支付寶的下單動作由于是在前端調(diào)用的,因此,站點需要將自己的訂單信息返回到客戶端,然后又客戶端發(fā)起調(diào)用支付寶的下單接口,這樣一來,如果安全加密等做的不到位,很容易被惡意用戶篡改信息。 作為一個具備用戶交易能力的網(wǎng)站,豐富它的支付渠道對于獲客和提高日活都有不可估量的積極作用。算起來,我接觸過的支付系統(tǒng)也有幾十個了,在這里總結(jié)一下我所接觸過的支付系統(tǒng)對外接口的設(shè)計方案。 1. 支付...

    warnerwu 評論0 收藏0
  • 小程序微信支付開發(fā)流程記錄

    摘要:附微信支付流程微信支付流程和小程序的支付流程基本一致,需要注意兩點需要在微信商戶平臺配置支付目錄,只有跳轉(zhuǎn)到了支付目錄的地址,才能發(fā)起微信支付。 我所在公司需要開發(fā)一款商城小程序,里面需要用到微信支付,我負責里面的下單功能,從小程序端到后臺的支付流程都是我自己開發(fā)的,由于我們組沒有人有開發(fā)微信支付的經(jīng)驗,很多東西都還不怎么明白,但是沒辦法,只能我自己琢磨,寫完之后總感覺有bug,但是不...

    whjin 評論0 收藏0
  • 開發(fā)支付寶小程序無從下手?我們給你創(chuàng)造了一條捷徑

    摘要:即日起至月日公測活動期間,成功參與新版公測活動并接入支付寶小程序的用戶,可獲得個人版套餐個月價值元的免費使用資格。計劃的第一站我們選擇了支付寶小程序。支付寶以及其他平臺的小程序,在這個時代里,更加需要無服務(wù)器的開發(fā)方式。 作為國內(nèi)首家專注于小程序領(lǐng)域的后端云服務(wù),知曉云正式開啟 3.0 計劃——全平臺 Serverless 服務(wù)。 「知曉云」cloud.minapp.com,誕生于 2...

    biaoxiaoduan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<