建行互聯網銀企被掃支付


背景

最近在對接建行的支付,我們做的是被掃支付,就是B掃C,一開始對方發了一個壓縮包給我,看起來挺齊全的,文檔、demo啥的都有,以為很簡單,跟微信支付寶類似,調一下接口,驗證一下就OK了。然而,事實證明我還是太年輕了。而且網絡上你能夠搜到的基本上都用不了,所以記一下博客,或許可以幫助其他人。

先說一下建行支付比較特殊的地方吧

1、官方提供的demo里面,只有Java和.Net是有真正的demo,PHP和其他語言沒有,只提供一個dll文件,幾乎沒什么用

2、計算加密串的時候,待加密的數據要轉為十六進制

3、建行通知返回的SIGN是十六進制的,要轉為十進制

4、建行提供的公鑰是DER格式的,十六進制,而 MD5withRSA 進行加密驗證的時候,要轉成PEM格式

5、建行被掃支付文檔雖然說要用POST,但是實際上只能用GET

6、退款也是很惡心的一個東西,建行的退款走接口的話只能用外聯平台退款,支付接口里面退款的描述就幾句話

 

由於筆者是用PHP進行開發的,既然官方沒有提供PHP版的demo,只能根據Java版的翻譯成PHP版的。至於退款,只能開一個外聯平台服務進行處理了

支付

可參考筆者的 Github 項目,里面包含了完整的PHP加密驗簽方法,也包含了Java版的處理

PS:Github 如果網速比較慢,可以在這里下載

提取碼:4a7u 

 

下面簡單介紹下

簽名計算流程

  1. 將所有的請求參數去掉空值,並按key升序排序
  2. 將第一步得到的數據,按key=value的形式進行拼接,用&隔開
  3. 將拼接后的字符串再拼接上"20120315201809041004"
  4. 將最后得到的字符串進行MD5加密,就是SIGN的值

加密串計算流程

  1. 把上面簽名后的結果以鍵值對的形式放入請求參數中(所有的請求參數,含空值),鍵名是SIGN
  2. 將第一步得到的請求參數,按key=value的形式進行拼接,用&隔開,得到待加密的字符串
  3. 截取公鑰的后30位,再截取這30位的前8位,得到一個8位的字符串,這個是參與加密串計算的公鑰
  4. 先將第二步得到的待加密的字符串從"utf-8"編碼轉為"utf-16",並與第三步得到的8位的公鑰用"DES-ECB"進行加密
  5. 把第四步得到的加密結果中的"+"替換為","
  6. 再對第五步的結果進行UrlEncode編碼,得到的結果就是ccbParam

驗簽流程

  1. 建行接口所有返回的參數,只取接口文檔中的"簽名源文格式"中相關的數據,作為驗簽源數據
  2. 將返回的簽名字段SIGN(十六進制),轉為十進制
  3. 建行的公鑰是DER格式的,且是十六進制,需要轉為PEM格式。將完整的公鑰轉為十進制,同時進行base64編碼,拼接上"-----BEGIN PUBLIC KEY-----"和"-----END PUBLIC KEY-----"做成pem
  4. 提取第三步得到的PEM證書的公鑰
  5. 將第一步得到的驗簽源數據,按key=value的形式進行拼接,用&隔開,作為新的源數據
  6. 使用MD5withRSA方法,將十進制的SIGN、源數據以及提取的公鑰進行驗證

代碼:

ccbPay.php

<?php
require_once './ccbUtils.php';

/**
 * 被掃支付:建行互聯網銀企被掃支付(聚合)
 * Class ccbPay
 */
class ccbPay {

    // 商戶號
    const MERCHANTID = '105910100190000';
    // 櫃台號
    const POSID = '000000000';
    // 分行號
    const BRANCHID = '610000000';
    // 建行支付公鑰
    const PUBKEY = '30819d300d06092a864886f70d010101050003818b0030818702818100a32fb2d51dda418f65ca456431bd2f4173e41a82bb75c2338a6f649f8e9216204838d42e2a028c79cee19144a72b5b46fe6a498367bf4143f959e4f73c9c4f499f68831f8663d6b946ae9fa31c74c9332bebf3cba1a98481533a37ffad944823bd46c305ec560648f1b6bcc64d54d32e213926b26cd10d342f2c61ff5ac2d78b020111';
    // 請求接口域名
    const HOST = 'https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY';

    /**
     * 建行支付,被掃
     */
    public function pay() {
        $data = [
            'MERCHANTID'   => self::MERCHANTID, // 商戶號
            'POSID'        => self::POSID, // 櫃台號
            'BRANCHID'     => self::BRANCHID, // 分行號
            'GROUPMCH'     => '', // 集團商戶信息
            'TXCODE'       => 'PAY100', // 交易碼
            'MERFLAG'      => '', // 商戶類型
            'TERMNO1'      => '', // 終端編號 1
            'TERMNO2'      => '', // 終端編號 2
            'ORDERID'      => '', // 訂單號
            'QRCODE'       => '', // 碼信息(一維碼、二維碼)
            'AMOUNT'       => '0.01', // 訂單金額,單位:元
            'PROINFO'      => '', // 商品名稱
            'REMARK1'      => '', // 備注 1
            'REMARK2'      => '', // 備注 2
            'FZINFO1'      => '', // 分賬信息一
            'FZINFO2'      => '', // 分賬信息二
            'SUB_APPID'    => '', // 子商戶公眾賬號 ID
            'RETURN_FIELD' => '', // 返回信息位圖
            'USERPARAM'    => '', // 實名支付
            'detail'       => '', // 商品詳情
            'goods_tag'    => '', // 訂單優惠標記
        ];

        $ccbUtils = new ccbUtils();
        // 計算簽名
        $sign = $ccbUtils->calSign($ccbUtils->sortParams($data));
        $data['SIGN'] = $sign;

        // 計算加密串
        $params = http_build_query($data);
        $pubKey = substr(self::PUBKEY, -30);
        $pubKey = substr($pubKey, 0, 8);
        $data['ccbParam'] = $ccbUtils->calCcbParam($params, $pubKey);

        // 獲取要請求的參數
        $requestData = $ccbUtils->getRequestData($data);

        $url = self::HOST . '?' . http_build_query($requestData);
        var_dump($url);

    }

    /**
     * 支付查詢
     */
    public function query() {
        $data = [
            'MERCHANTID'   => self::MERCHANTID, // 商戶號
            'POSID'        => self::POSID, // 櫃台號
            'BRANCHID'     => self::BRANCHID, // 分行號
            'GROUPMCH'     => '', // 集團商戶信息
            'TXCODE'       => 'PAY101', // 交易碼
            'MERFLAG'      => '', // 商戶類型
            'TERMNO1'      => '', // 終端編號 1
            'TERMNO2'      => '', // 終端編號 2
            'ORDERID'      => '', // 訂單號
            'QRYTIME'      => '', // 查詢次數 從1開始
            'QRCODE'       => '', // 碼信息(一維碼、二維碼)
            'QRCODETYPE'   => '', // 二維碼類型 如未上送 QRCODE 則此參數為必輸
            'REMARK1'      => '', // 備注 1
            'REMARK2'      => '', // 備注 2
            'SUB_APPID'    => '', // 子商戶公眾賬號 ID
            'RETURN_FIELD' => '', // 返回信息位圖
        ];
        // 與支付的區別TXCODE不一樣,需要傳QRYTIME,QRCODE和QRCODETYPE兩個需傳一個
        // 后續計算簽名和加密串跟支付類似
    }

    public function refund() {
        // 退款只能走外聯平台
    }

    /**
     * 建行返回參數sign驗簽
     */
    public function checkCcbSign() {
        // 建行返回的數據
        $returnData = [
            'RESULT' => 'Y',
            'ORDERID' => '151677281312212',
            'AMOUNT' => '0.01',
            'WAITTIME' => 'null',
            'TRACEID' => '1010115031516772964428432',
            'SIGN' => '80c3298a47b26cb9d8d708e1465c6b521edcce32b0deecab91257a3f41fc6cf39fa43afa54dc8489a04615eee9dcca1f4b52ce677f70109f29745ff34033018353b78e982cc860623b6c3df0d9c1a62ca010a019fff8544d4d8e154a010d7fc16cb590ccd87f34d8bea6added68cf1f9943fdb1d83616507a4588b68774b9fe1'
        ];
        $ccbUtils = new ccbUtils();
        $result = $ccbUtils->checkSign($ccbUtils->getCalSignData($returnData, ccbUtils::SIGN_CCB_PAY), self::PUBKEY);

        var_dump($result);

    }

}

ccbUtils.php

<?php
class ccbUtils {
    // 加密MD5 key
    const MD5KEY = '20120315201809041004';

    // 驗證簽名用到的類型,1-支付接口,2-查詢接口
    const SIGN_CCB_PAY = 1;
    const SIGN_CCB_QUERY = 2;

    /**
     * 按key升序排序,同時去掉空值
     * @param $params array
     * @return mixed
     */
    public function sortParams($params) {
        ksort($params);
        foreach ($params as $key => $value) {
            if (empty($value) && $value == '') {
                unset($params[$key]);
            }
        }

        return $params;
    }

    /**
     * 計算簽名
     * @param $params array 不含空值
     * @return string
     */
    public function calSign($params) {
        return md5(http_build_query($params) . self::MD5KEY);
    }

    /**
     * 計算ccbparam
     * @param $params string
     * @param $key string
     * @return string
     */
    public function calCcbParam($params, $key) {
        $res = openssl_encrypt (iconv("utf-8", "utf-16", $params), 'DES-ECB', $key);
        $res = str_replace('+', ',', $res);
        $res = urlencode($res);

        return $res;
    }

    /**
     * 真正請求建行接口要傳的參數
     * @param $data array
     * @return array
     */
    public function getRequestData($data) {
        return [
            'MERCHANTID' => $data['MERCHANTID'],
            'POSID'      => $data['POSID'],
            'BRANCHID'   => $data['BRANCHID'],
            'ccbParam'   => $data['ccbParam'],
        ];
    }

    /**
     * 獲取要驗證簽名的參數
     * @param $data array
     * @param $type int
     * @return array
     */
    public function getCalSignData($data, $type) {
        switch ($type) {
            case self::SIGN_CCB_PAY:
                $res = [
                    'RESULT' => $data['RESULT'],
                    'ORDERID' => $data['ORDERID'],
                    'AMOUNT' => $data['AMOUNT'],
                    'WAITTIME' => $data['WAITTIME'],
                    'TRACEID' => $data['TRACEID'],
                    'SIGN' => $data['SIGN']
                ];
                break;
            case self::SIGN_CCB_QUERY:
                $res = [
                    'RESULT' => $data['RESULT'],
                    'ORDERID' => $data['ORDERID'],
                    'AMOUNT' => $data['AMOUNT'],
                    'WAITTIME' => $data['WAITTIME'],
                    'SIGN' => $data['SIGN']
                ];
                break;
            default:
                $res = [];
                break;
        }

        return $res;
    }

    /**
     * 驗證簽名
     * @param $data array
     * @param $key string
     * @return bool
     */
    public function checkSign($data, $key) {
        if (empty($data)) {
            return false;
        }
        $sign = $data['SIGN'];
        unset($data['SIGN']);
        $data = http_build_query($data);

        $pubkey = "-----BEGIN PUBLIC KEY-----\n"
            . wordwrap(base64_encode(self::Hex2String($key)), 64, "\n", true)
            . "\n-----END PUBLIC KEY-----";
        $pkeyId = openssl_pkey_get_public($pubkey);
        $verify = openssl_verify($data, self::Hex2String($sign), $pkeyId, OPENSSL_ALGO_MD5);
        openssl_free_key($pkeyId);

        return (bool) $verify;
    }

    /**
     * 十六進制轉字符串
     * @param $hex string
     * @return string
     */
    private function Hex2String($hex)
    {
        $string = '';
        for ($i = 0; $i < strlen($hex) - 1; $i += 2) {
            $string .= chr(hexdec($hex[$i] . $hex[$i + 1]));
        }
        return $string;
    }

    /**
     * 字符串轉十六進制
     * @param $str string
     * @return string
     */
    private function String2Hex($str){
        $hex='';
        for ($i=0; $i < strlen($str); $i++){
            $hex .= dechex(ord($str[$i]));
        }
        return $hex;
    }

}

 

退款

建行退款只提供兩種方式

1、登錄商戶服務平台,手工處理退款

2、走外聯平台服務進行退款

官方給的文檔,教你搭建外聯平台都是基於Windows的,Linux的幾乎沒有,而且搭建流程非常復雜,而且你還得找一台服務器專門用來退款。Excuse me?

筆者提供一個 Github 項目,只用使用里面的 jar 包,開啟一個服務就可以處理退款請求了

退款Jar包源碼在這里:Github項目

啟動服務,綁定的是8080端口

# java -jar ccb-cloud-sdk-1.0-SNAPSHOT.jar

請求實例:

接口:http://127.0.0.1:8080/ccb/pay/refund
請求參數:
{
    "merchantId": "商戶號",
    "custId": "操作員賬號", // 登錄建行商戶平台-服務管理-操作員管理,列表里面的客戶號
    "transPwd": "操作員交易密碼", // 創建操作員時候填的
    "certPassword": "證書密碼", // 導出證書的時候填的密碼
    "txCode": "5W1004", // 參考"外聯平台商戶開發接口_V4.0.chm",退款是這個"5W1004"
    "language": "CN",
    "url": "https://merchant.ccb.com",
    "certFilePath": "/config/MC123456789.pfx", // 使用絕對路徑
    "configFilePath": "/config/config.xml", // 使用絕對路徑
    "refundNo": "序列號", // 16位以內純數字
    "refundAmt": "退款金額", // 單位:元
    "payRecordNo": "交易單號" // 交易的時候你傳給建行的單號
}
返回參數:
{
    "return_CODE": "000000", // 參考"外聯平台商戶開發接口_V4.0.chm"
    "return_MSG": "退款成功", // 參考"外聯平台商戶開發接口_V4.0.chm"
    "order_NUM": "交易單號", // 交易的時候你傳給建行的單號
    "tx_INFO": "" // 建行接口返回原文
}

退款麻煩麻煩在需要在建行商戶平台配置一個操作員賬號,此外還需要導出證書和配置,其他的基本上沒了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM