連連支付升級記


date: 2016-9-18 14:34

我一直持有一個觀點:如果投入時間還沒有把事情做好,一定要好好反思

1 背景

接到一個升級 連連支付 的任務(wù),但是卻拖了很長時間,事后想想,很多地方的處理都可以優(yōu)化

2 單個支付方式概覽

  1. 支付(有用戶交互) = 發(fā)起支付 + (同步 + 異步 + 查詢)
  2. 發(fā)起支付:構(gòu)建自提交表單
  3. 同步:從支付渠道回到商家頁面(最好使用查詢確認(rèn)支付結(jié)果)
  4. 異步:支付渠道異步通知商家支付家國
  5. 查詢:做好輪詢策略

技術(shù)關(guān)鍵字:http + sign

2.1 http

本質(zhì):http 請求
關(guān)鍵:https、method(get、post)、params(type、length、format,care money)、sign、pay result、header、autosubmit form、response

2.2 簽名

  1. 簽名方式:對稱加密與非對稱加密;md5 算法與 rsa 算法;PEM format 密鑰格式
  2. 構(gòu)造簽名:需要簽名的參數(shù)、拼接簽名串、加密、驗簽

3 接入流程

自己總結(jié)了一份流程,希望對之后接入支付有幫助:

  1. 理解需求很重要:大部分來自 PM(這次要支持單筆 1w 以上),過一過測試 case,以及隱藏的需求,別把之前的功能整掛了
  2. 對接群中多提問
  3. 獲取開發(fā)指南和 demo,并開發(fā)指南為準(zhǔn),并謹(jǐn)記,2者可能都有坑,要多問
  4. 接通每個接口(發(fā)起、同步、異步、查詢),完成自測
  5. 對接測試環(huán)境和上線相關(guān)
  6. 提交測試并繼續(xù)跟進(jìn),可能會有隱藏(或者奇葩)的 bug

4 簡單示例

4.1 以下代碼基于 php 實現(xiàn),其他語言同理

關(guān)于 rsa 密鑰的格式,可以去搜索相關(guān)文章了解下,各語言相關(guān)函數(shù)可能會有限制,注意查看文檔

// openssl_get_privatekey() 等函數(shù)需要使用 PEM format 格式密鑰
function l_pem_format($key, $type){
    $tmp = $type ? 'RSA PRIVATE':'PUBLIC';
    $str = "-----BEGIN $tmp KEY-----\n";
    $len = strlen($key);
    $i = 0;
    while ($i <= $len) {
        $str .= substr($key, $i, 64)."\n";
        $i += 64;
    }
    $str .= "-----END $tmp KEY-----\n";

    return $str;
}

// 默認(rèn) json encode 是會轉(zhuǎn)中文等為 unicode(\u2345 這種類型)
// 這樣會導(dǎo)致2個問題:有些請求不允許傳入 \ 這樣特殊字符;增加數(shù)據(jù)量
json_encode($str, JSON_UNESCAPED_UNICODE);

// 處理金額,是否為整數(shù),是否需要保留 2 位小數(shù)
$money_order = rtrim(rtrim(sprintf('%.2f', $money_order  / 100), '0'), '.');

// name_goods 字段有長度限制
$i = 40>>1;
while (strlen($name_goods)>=40) {
    $name_goods = mb_substr($name_goods,0,$i,'utf-8');
    $i >>=1;
}

// 處理特殊字符
str_replace('\\', '', $str); // 可以傳入數(shù)組,替換多個字符

// 構(gòu)建自提交表單,需要注意:是否制定 header、input 帶 hidden
$sHtml = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>';
$sHtml .= "<form id='llpaysubmit' name='llpaysubmit' action='" . self::llpay_gateway_new . "' method='POST'>";
foreach ($para_sort as $k => $v) {
    $sHtml .= "<input type='hidden' name='{$k}' value='{$v}'/>";
}
$sHtml .= "<script>document.forms['llpaysubmit'].submit();</script>";
$sHtml .= '</body></html>';
echo $sHtml;

// 獲取接口數(shù)據(jù)
$data = file_get_contents("php://input"); // 返回原生數(shù)據(jù),支持 get/post
$data = $_POST; // 獲取 post 數(shù)據(jù),會自動解析到數(shù)組中,推薦使用

// 簡單打日志,建議封裝日志類,封裝上時間戳
file_put_contents('log.txt','noti-'.$data."\n\n", FILE_APPEND);

// 使用 php header() 實現(xiàn)重定向
header('location: '. $url);die;

4.2 mark 一個以前接入米大師(應(yīng)用寶)的例子:

說明:

  1. 此處代碼為 Job 類方法并實現(xiàn)異步隊列接口,Controller 接收請求數(shù)據(jù),傳入并調(diào)用此方法
  2. 此方法包含 測試、正式環(huán)境切換;使用 https 并添加 cookie;輪詢;日志
// 指定 cookie
$cookie_get = 'session_id=openid;session_type=kp_actoken;org_loc=%25mpay%25get_balance_m'; // 對方文檔錯誤,雙方溝通才確定
// 查詢余額接口
$url = 'https://ysdk.qq.com/mpay/get_balance_m'; // 現(xiàn)網(wǎng)
$secret = '';
// $url = 'https://ysdktest.qq.com/mpay/get_balance_m'; // 沙箱
// $secret = '';
$arr = $input; // 此處框架層處理輸入數(shù)據(jù)
unset($arr['billno']);
$arr['sig'] = QqSnsSigCheck::makeSig('get', '/v3/r/mpay/get_balance_m', $arr, $secret); // 此處使用了 demo 的方法,但是 demo 簽名寫錯了,最后讀文檔發(fā)現(xiàn)的
$url = $url. '?'. http_build_query($arr);

$opts = [
    'ssl' => ["verify_peer"=>false, "verify_peer_name"=>false],
    'http' => ['header' => "Cookie: {$cookie_get}\r\n"]
];
$context = stream_context_create($opts);
\Log::info('sdk qq-query: '. $url);

// 2分鐘之內(nèi)間隔15秒多次調(diào)用,直到查到當(dāng)前充值已到賬
$time = 0;
while ($time<120) {
    $data = file_get_contents($url, false, $context);
    $data = json_decode($data, true);
    // 查詢到游戲幣
    if($data['ret']==0){
        \Log::info('sdk qq-query: ', $data);
        if($data['balance']>0) break;
    }
    \Log::info('sdk qq-unpaid: ', $data);
    DB::table('orders')->where('id', $order_id)->update(['status'=>7, 'updated_at'=>date('Y-m-d H:i:s')]);
    sleep(15);
    $time +=15;
}
if($time>=120){
    \Log::info('sdk qq-query-timeout: ', $data);
    DB::table('orders')->where('id', $order_id)->update(['status'=>7, 'updated_at'=>date('Y-m-d H:i:s')]);
    return;
}

// 后面是扣款接口,和上面類似

5 入坑指南

  1. 產(chǎn)品說連連那邊的人說只要改一下 url 和換一下簽名商戶號等配置就 ok 了,最重要的是,我也信了
  2. 當(dāng)發(fā)現(xiàn)上面的路走不通,就想使用 demo,然后采用原始的方法:對比文件差異,覆蓋并修改配置
  3. 做了上面 2 個無用功(唯一的用途可能就是多閱讀了一下 demo 的代碼),遇到問題就到討論組里面問
  4. 第三步涉及到 2 個公司之間溝通,溝通成本高,一個問題可能很久才能回復(fù),甚至跨天,最后終于定位到:demo 有問題
  5. 忍無可忍之下根據(jù)文檔重寫 發(fā)起支付 接口 并順利調(diào)試通,然后被告知要走上線流程
  6. 按照對方要求填好文檔之后,被告知 同步調(diào)用、異步通知 接口不通
  7. 繼續(xù)閱讀文檔重寫這 2 個接口,同步走上線流程
  8. 加急下成功上線,但是導(dǎo)致了連連沒有走正常上線流程:只驗證支付成功,沒有驗證同步調(diào)用、異步通知 接口
  9. 測試發(fā)現(xiàn) 同步調(diào)用、異步通知 報錯,只能推遲上線時間
  10. 戰(zhàn)線太長(前后 2 個星期,并采用直接重寫的方式)以及雙開(同時接連連 wap 和 web),忽略了同步調(diào)用、異步通知 2 個接口以及 2 個不同版本的差異,導(dǎo)致編碼出錯
  11. 加班按照上面的 接入流程 進(jìn)行二次 coding,終于成功
  12. 測試?yán)^續(xù)測試發(fā)現(xiàn)隱藏 bug,同步修復(fù)

總結(jié):

  1. 流程很重要,流程是為了減少犯錯的幾率,開發(fā)最容易犯的流程錯誤就是開發(fā)完認(rèn)為萬事大吉,不自測就直接提交給測試
  2. 再次重申:流程很重要??梢钥闯錾厦孀隽撕芏酂o用功,最開始以為改改配置就好,不行就用 demo 替換,結(jié)果聯(lián)調(diào)下來發(fā)現(xiàn) demo 問題很多,最后根據(jù)文檔重寫,如果一開始按照 接入流程 來做,完全可以順利提測
  3. 后臺開發(fā)要用好 日志,我這次犯的錯誤是不熟悉框架的日志方法而不打日志
  4. 還是盡量減少雙開這樣的情況,我犯的錯誤是以為 wap 版和 web 版基本類似,同步和異步接口類似,導(dǎo)致很多改變的地方?jīng)]有調(diào)整,最后出錯
  5. 其他小坑:溝通之后才知道要走上線流程;走上線流程才知道風(fēng)控參數(shù)很變態(tài);測試發(fā)現(xiàn)有一個參數(shù)有長度限制,以前沒有處理;測試使用的密鑰是 string 而不是 PEM format,定位了一圈才知道 還有 PEM format 這東東

最后,提供一些參考:

  1. 百度腦圖,幫助大家理解:http://naotu.baidu.com/file/9776e7b350f96cdebf263b0b7a3387c5?token=35e207d88cd851c2
  2. php 中 $_POST,$HTTP_RAW_POST_DATA 和 php://input 的區(qū)別:http://blog.wpjam.com/m/post-http_raw_post_data-php-input/。注意$_POST,和 http post 提交的數(shù)據(jù)格式有關(guān)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容