1. 程式人生 > >淺析微信支付:統一下單介面

淺析微信支付:統一下單介面

本文是【淺析微信支付】系列文章的第五篇,主要講解如何呼叫統一下單介面生成預支付單及調起支付頁面。

淺析微信支付系列已經更新四篇了喲~,沒有看過的朋友們可以看一下哦。

上面是本文的前置文章,有前面幾篇文章的基礎以後看會更加明瞭,如果已經看過的小夥伴可以忽略。

1、什麼是[統一下單介面]?

首先我們要明白這個問題,需要先行看一下微信的官方文件:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

官方解釋如下:

除被掃支付場景以外,商戶系統先呼叫該介面在微信支付服務後臺生成預支付交易單,返回正確的預支付交易會話標識後再按掃碼、JSAPI、APP等不同場景生成交易串調起支付。

什麼意思?簡單理解:就是說我們要在調起微信支付視窗之前,需要先生成一個 預支付交易單,這個單子相當於和我們自身系統的 支付交易單 一一對應,也就是我們每次支付需要記錄的訂單支付交易單。

從上面我們可以得到,在呼叫此介面之前,首先,我們系統中肯定已經需要有以下步驟:訂單提交 -> 生成訂單 -> 生成訂單對應的支付單 -> 呼叫統一下單介面

好了,假設系統現在已經生成支付交易單,準備呼叫統一下單介面,我們來看一下具體的實現方式。

PS:呼叫統一下單介面時,需要注意的是必須傳入非同步接收微信支付結果通知的回撥地址,通知url必須為外網可訪問的url,不能攜帶引數。示例如下:

https://xxx.com/v1/weixin/pay/wxnotify

2、呼叫介面

示例程式碼:

/**
 * [微信支付統一下單] - 儲存呼叫的相關記錄 <p>
 * [微信支付小程式] - 返回支付喚醒引數
 * @param payment 付款物件
 * @param user 當前使用者
 * @return map
 * @throws Exception e
 *
 * @author yclimb
 * @date 2018/6/15
 */
public Map<String, String> saveWxPayUnifiedOrder(Payment payment, User user) throws Exception {
    if (payment == null || user == null) {
        return null;
    }

    // 1.呼叫微信統一下單介面
    WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance());
    Map<String, String> resultMap = wxPay.unifiedOrder(...);

    // 1.1.記錄付款流水
    ...

    // 下單失敗,進行處理
    if (WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RETURN_CODE)) ||
            WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RESULT_CODE))) {

        // 處理結果返回,無需繼續執行
        resultMap.put(WXPayConstants.RESULT_CODE, WXPayConstants.FAIL);
        resultMap.put(WXPayConstants.ERR_CODE_DES, resultMap.get(WXPayConstants.RETURN_MSG));
        return resultMap;
    }

    // 1.2.獲取prepay_id、nonce_str
    String prepay_id = resultMap.get("prepay_id");
    String nonce_str = resultMap.get("nonce_str");

    // 2.根據微信統一下單介面返回資料組裝微信支付引數,返回結果
    return wxPay.chooseWXPayMap(prepay_id, nonce_str);
}

以上為一個呼叫統一下單的介面程式碼,主要在於以下兩句:

// 例項化一個微信支付物件,使用單例配置的方式
WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance());

// 直接呼叫統一下單介面
Map<String, String> resultMap = wxPay.unifiedOrder(...);

微信支付物件WXPay統一下單介面:


/**
 * 作用:統一下單<br>
 * 場景:商戶在小程式中先呼叫該介面在微信支付服務後臺生成預支付交易單,返回正確的預支付交易後調起支付。
 * 介面連結:URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
 * 是否需要證書:否
 * 介面文件地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
 *
 * @param notify_url       公眾號使用者openid
 * @param body             商品簡單描述,該欄位請按照規範傳遞,例:騰訊充值中心-QQ會員充值
 * @param out_trade_no     商戶系統內部訂單號,要求32個字元內,只能是數字、大小寫字母_-|*且在同一個商戶號下唯一
 * @param total_fee        訂單總金額,傳入引數單位為:元
 * @param spbill_create_ip APP和網頁支付提交使用者端ip,Native支付填呼叫微信支付API的機器IP
 * @param goods_tag        訂單優惠標記,用於區分訂單是否可以享受優惠
 * @param detail           商品詳情 ,單品優惠活動該欄位必傳
 * @param timeStart        訂單生成時間,格式為yyyyMMddHHmmss
 * @param timeExpire       訂單失效時間,格式為yyyyMMddHHmmss,如2009年12月27日9點10分10秒錶示為20091227091010
 * @return API返回資料
 * @throws Exception e
 */
public Map<String, String> unifiedOrder(String notify_url, String openid, String body, String out_trade_no, String total_fee, String spbill_create_ip, String goods_tag, String detail, Date timeStart, Date timeExpire) throws Exception {

    /** 構造請求引數資料 **/
    Map<String, String> data = new HashMap<>();

    // 欄位名  變數名 必填  型別  示例值 描述
    // 標價幣種 fee_type    否   String(16)  CNY 符合ISO 4217標準的三位字母程式碼,預設人民幣:CNY,詳細列表請參見貨幣型別
    data.put("fee_type", WXPayConstants.FEE_TYPE_CNY);
    // 通知地址 notify_url  是   String(256) http://www.weixin.qq.com/wxpay/pay.php  非同步接收微信支付結果通知的回撥地址,通知url必須為外網可訪問的url,不能攜帶引數。
    data.put("notify_url", notify_url);
    // 交易型別 trade_type  是   String(16)  JSAPI   小程式取值如下:JSAPI,詳細說明見引數規定
    data.put("trade_type", WXPayConstants.TRADE_TYPE);
    // 使用者標識 openid  否   String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o    trade_type=JSAPI,此引數必傳,使用者在商戶appid下的唯一標識。openid如何獲取,可參考【獲取openid】。
    data.put("openid", openid);
    // 商品描述 body    是   String(128) 騰訊充值中心-QQ會員充值 商品簡單描述,該欄位請按照規範傳遞,具體請見引數規定
    data.put("body", body);
    // 商戶訂單號    out_trade_no    是   String(32)  20150806125346  商戶系統內部訂單號,要求32個字元內,只能是數字、大小寫字母_-|*且在同一個商戶號下唯一。詳見商戶訂單號
    data.put("out_trade_no", out_trade_no);
    // 標價金額 total_fee   是   Int 88  訂單總金額,單位為分,詳見支付金額
    // 預設單位為分,系統是元,所以需要*100
    data.put("total_fee", String.valueOf(new BigDecimal(total_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue()));
    // 終端IP spbill_create_ip    是   String(16)  123.12.12.123   APP和網頁支付提交使用者端ip,Native支付填呼叫微信支付API的機器IP。
    data.put("spbill_create_ip", spbill_create_ip);

    /** 以下引數為非必填引數 **/
    // 訂單優惠標記   goods_tag   否   String(32)  WXG 訂單優惠標記,使用代金券或立減優惠功能時需要的引數,說明詳見代金券或立減優惠
    if (StringUtils.isNotBlank(goods_tag)) {
        data.put("goods_tag", goods_tag);
    }
    // 商品詳情 detail  否   String(6000)        商品詳細描述,對於使用單品優惠的商戶,改欄位必須按照規範上傳,詳見“單品優惠引數說明”
    if (StringUtils.isNotBlank(detail)) {
        data.put("detail", detail);
        // 介面版本號 新增欄位,介面版本號,區分原介面,預設填寫1.0。入參新增version後,則支付通知介面也將返回單品優惠資訊欄位promotion_detail,請確保支付通知的簽名驗證能通過。
        data.put("version", "1.0");
    }
    // 裝置號  device_info 否   String(32)  013467007045764 自定義引數,可以為終端裝置號(門店號或收銀裝置ID),PC網頁或公眾號內支付可以傳"WEB"
    data.put("device_info", "WEB");

    // 交易起始時間   time_start  否   String(14)  20091225091010  訂單生成時間,格式為yyyyMMddHHmmss,如2009年12月25日9點10分10秒錶示為20091225091010。其他詳見時間規則
    data.put("time_start", DateTimeUtil.getTimeShortString(timeStart));
    // 交易結束時間   time_expire 否   String(14)  20091227091010  訂單失效時間,格式為yyyyMMddHHmmss,如2009年12月27日9點10分10秒錶示為20091227091010。
    // 訂單失效時間是針對訂單號而言的,由於在請求支付的時候有一個必傳引數prepay_id只有兩小時的有效期,所以在重入時間超過2小時的時候需要重新請求下單介面獲取新的prepay_id。其他詳見時間規則,建議:最短失效時間間隔大於1分鐘
    data.put("time_expire", DateTimeUtil.getTimeShortString(timeExpire));
    /*// 商品ID   product_id  否   String(32)  12235413214070356458058 trade_type=NATIVE時(即掃碼支付),此引數必傳。此引數為二維碼中包含的商品ID,商戶自行定義。
    data.put("product_id", null);
    // 指定支付方式   limit_pay   否   String(32)  no_credit   上傳此引數no_credit--可限制使用者不能使用信用卡支付
    data.put("limit_pay", null);
    // 附加資料 attach  否   String(127) 深圳分店    附加資料,在查詢API和支付通知中原樣返回,可作為自定義引數使用。
    data.put("attach", null);*/

    /** 以下五個引數,在 this.fillRequestData 方法中會自動賦值 **/
    /*// 小程式ID  appid   是   String(32)  wxd678efh567hg6787  微信分配的小程式ID
    data.put("appid", WXPayConstants.APP_ID);
    // 商戶號  mch_id  是   String(32)  1230000109  微信支付分配的商戶號
    data.put("mch_id", WXPayConstants.MCH_ID);
    // 隨機字串    nonce_str   是   String(32)  5K8264ILTKCH16CQ2502SI8ZNMTM67VS    隨機字串,長度要求在32位以內。推薦隨機數生成演算法
    data.put("nonce_str", nonce_str);
    // 簽名型別 sign_type   否   String(32)  MD5 簽名型別,預設為MD5,支援HMAC-SHA256和MD5。
    data.put("sign_type", WXPayConstants.MD5);
    // 簽名   sign    是   String(32)  C380BEC2BFD727A4B6845133519F3AD6    通過簽名演算法計算得出的簽名值,詳見簽名生成演算法
    data.put("sign", sign);*/

    // 微信統一下單介面請求地址
    Map<String, String> resultMap = this.unifiedOrder(data);

    WXPayUtil.getLogger().info("wxPay.unifiedOrder:" + resultMap);

    return resultMap;
}

以上程式碼詳細說明了每個欄位的含義,具體的程式碼可以看一下作者的github,文末有對應的地址。

下面說一個特殊情況,在我們支付的時候,有時候使用者會取消支付,等一段時間再重新調起,這時候,需要用到另外一個方法,那就是二次支付,所以,在我們資料庫中,必須儲存兩個欄位,用於二次支付時使用:預支付IDprepay_id、隨機字串nonce_str,此兩個引數可以生成微信支付調起時需要的驗證簽名。

以下為二次呼叫時的程式碼:

/**
 * 根據付款單號查詢微信付款引數
 * @param relationId 交易編號
 * @param type 支付型別
 * @return payment
 *
 * @author yclimb
 * @date 2018/6/28
 */
public Map<String, String> queryChooseWXPayMapByRelationId(Integer relationId, String type) throws Exception {

        // 微信支付物件
        WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance());

        // 1.查詢付款物件
        Payment payment = this.queryPaymentByRelationId(relationId, type);

        // 2.根據微信統一下單介面返回資料組裝微信支付引數,返回結果
        return wxPay.chooseWXPayMap(payment.getPrepayId(), payment.getNonceStr());
    }

此時我們已經呼叫微信統一下單介面成功,併為我們返回了需要的引數,下一步需要組裝為微信支付調起時前端需要的引數。

生成支付簽名

下面為組裝支付簽名的程式碼:

/**
 * 作用:生成微信支付所需引數,微信支付二次簽名<br>
 * 場景:根據微信統一下單介面返回的 prepay_id 生成微信支付所需的引數
 * 介面文件地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
 *
 * @param prepay_id 預支付id
 * @param nonce_str 隨機字串
 * @return 支付方法呼叫所需引數map
 * @throws Exception e
 */
public Map<String, String> chooseWXPayMap(String prepay_id, String nonce_str) throws Exception {

    // 支付方法呼叫所需引數map
    Map<String, String> chooseWXPayMap = new HashMap<>();
    chooseWXPayMap.put("appId", config.getAppID());
    chooseWXPayMap.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));
    chooseWXPayMap.put("nonceStr", nonce_str);
    chooseWXPayMap.put("package", "prepay_id=" + prepay_id);
    chooseWXPayMap.put("signType", WXPayConstants.MD5);

    WXPayUtil.getLogger().info("wxPay.chooseWXPayMap:" + chooseWXPayMap.toString());

    // 生成支付簽名
    String paySign = WXPayUtil.generateSignature(chooseWXPayMap, config.getKey());
    chooseWXPayMap.put("paySign", paySign);

    WXPayUtil.getLogger().info("wxPay.paySign:" + paySign);

    return chooseWXPayMap;
}

組裝好需要的引數以後,就可以調起微信支付視窗了,如果是微信公眾號支付,需要使用以下的方式調起微信支付:

function weixinConfig(appid, timestamp, noncestr, signature) {
    wx.config({
        debug : false, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
        // debug : true, 
        appId :appid, // 必填,公眾號的唯一標識
        timestamp : timestamp, // 必填,生成簽名的時間戳
        nonceStr : noncestr, // 必填,生成簽名的隨機串
        signature : signature,// 必填,簽名,見附錄1
        jsApiList: [
            'checkJsApi',
            'chooseWXPay' // 必須增加此引數,使用者微信支付功能
        ]
        // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
    });
}

wx.chooseWXPay({
    appId: appId,
    timestamp: timeStamp, // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp欄位均為小寫。但最新版的支付後臺生成簽名使用的timeStamp欄位名需大寫其中的S字元
    nonceStr: nonceStr, // 支付簽名隨機串,不長於 32 位
    package: package, // 統一支付介面返回的prepay_id引數值,提交格式如:prepay_id=\*\*\*)
    signType: signType, // 簽名方式,預設為'SHA1',使用新版支付需傳入'MD5'
    paySign: paySign, // 支付簽名
    success: function (res) {
        // 支付成功後的回撥函式
        alert('支付成功!');
    },
    cancel: function (res) {
        // 支付取消
    },
    fail: function (res) {
        // 支付失敗
        alert("支付失敗!");
    }
});

PS:小程式呼叫方法類似,引數一致。

結語

以上就是微信支付統一下單介面的呼叫方式了,具體的原始碼可以參考作者github,最好在開發之前先通讀一遍微信官方文件,此時再使用作者原始碼開發事半功倍,更易理解。

預告:下一篇文章,作者將講 支付結果通知,敬請期待!!!

​如果想要提前一覽原始碼的小夥伴,可以先看看我的 github,地址如下:
https://github.com/YClimb/wxpay-sdk/blob/master/README.md

加作者私人微信,作者微訊號如下 yclimb,標明 微信支付 可拉入微信支付討論群與小夥伴一起探討哦,一定要標明 微信支付 哦~

到此本文就結束了,關注公眾號檢視更多推送!!!

關注我的公眾號