date: 2016-9-18 14:34
我一直持有一個觀點:如果投入時間還沒有把事情做好,一定要好好反思
1 背景
接到一個升級 連連支付 的任務(wù),但是卻拖了很長時間,事后想想,很多地方的處理都可以優(yōu)化
2 單個支付方式概覽
- 支付(有用戶交互) = 發(fā)起支付 + (同步 + 異步 + 查詢)
- 發(fā)起支付:構(gòu)建自提交表單
- 同步:從支付渠道回到商家頁面(最好使用查詢確認(rèn)支付結(jié)果)
- 異步:支付渠道異步通知商家支付家國
- 查詢:做好輪詢策略
技術(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 簽名
- 簽名方式:對稱加密與非對稱加密;md5 算法與 rsa 算法;PEM format 密鑰格式
- 構(gòu)造簽名:需要簽名的參數(shù)、拼接簽名串、加密、驗簽
3 接入流程
自己總結(jié)了一份流程,希望對之后接入支付有幫助:
- 理解需求很重要:大部分來自 PM(這次要支持單筆 1w 以上),過一過測試 case,以及隱藏的需求,別把之前的功能整掛了
- 對接群中多提問
- 獲取開發(fā)指南和 demo,并開發(fā)指南為準(zhǔn),并謹(jǐn)記,2者可能都有坑,要多問
- 接通每個接口(發(fā)起、同步、異步、查詢),完成自測
- 對接測試環(huán)境和上線相關(guān)
- 提交測試并繼續(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)用寶)的例子:
說明:
- 此處代碼為 Job 類方法并實現(xiàn)異步隊列接口,Controller 接收請求數(shù)據(jù),傳入并調(diào)用此方法
- 此方法包含 測試、正式環(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 入坑指南
- 產(chǎn)品說連連那邊的人說只要改一下 url 和換一下簽名商戶號等配置就 ok 了,最重要的是,我也信了
- 當(dāng)發(fā)現(xiàn)上面的路走不通,就想使用 demo,然后采用原始的方法:對比文件差異,覆蓋并修改配置
- 做了上面 2 個無用功(唯一的用途可能就是多閱讀了一下 demo 的代碼),遇到問題就到討論組里面問
- 第三步涉及到 2 個公司之間溝通,溝通成本高,一個問題可能很久才能回復(fù),甚至跨天,最后終于定位到:demo 有問題
- 忍無可忍之下根據(jù)文檔重寫 發(fā)起支付 接口 并順利調(diào)試通,然后被告知要走上線流程
- 按照對方要求填好文檔之后,被告知 同步調(diào)用、異步通知 接口不通
- 繼續(xù)閱讀文檔重寫這 2 個接口,同步走上線流程
- 加急下成功上線,但是導(dǎo)致了連連沒有走正常上線流程:只驗證支付成功,沒有驗證同步調(diào)用、異步通知 接口
- 測試發(fā)現(xiàn) 同步調(diào)用、異步通知 報錯,只能推遲上線時間
- 戰(zhàn)線太長(前后 2 個星期,并采用直接重寫的方式)以及雙開(同時接連連 wap 和 web),忽略了同步調(diào)用、異步通知 2 個接口以及 2 個不同版本的差異,導(dǎo)致編碼出錯
- 加班按照上面的 接入流程 進(jìn)行二次 coding,終于成功
- 測試?yán)^續(xù)測試發(fā)現(xiàn)隱藏 bug,同步修復(fù)
總結(jié):
- 流程很重要,流程是為了減少犯錯的幾率,開發(fā)最容易犯的流程錯誤就是開發(fā)完認(rèn)為萬事大吉,不自測就直接提交給測試
- 再次重申:流程很重要??梢钥闯錾厦孀隽撕芏酂o用功,最開始以為改改配置就好,不行就用 demo 替換,結(jié)果聯(lián)調(diào)下來發(fā)現(xiàn) demo 問題很多,最后根據(jù)文檔重寫,如果一開始按照 接入流程 來做,完全可以順利提測
- 后臺開發(fā)要用好 日志,我這次犯的錯誤是不熟悉框架的日志方法而不打日志
- 還是盡量減少雙開這樣的情況,我犯的錯誤是以為 wap 版和 web 版基本類似,同步和異步接口類似,導(dǎo)致很多改變的地方?jīng)]有調(diào)整,最后出錯
- 其他小坑:溝通之后才知道要走上線流程;走上線流程才知道風(fēng)控參數(shù)很變態(tài);測試發(fā)現(xiàn)有一個參數(shù)有長度限制,以前沒有處理;測試使用的密鑰是 string 而不是 PEM format,定位了一圈才知道 還有 PEM format 這東東
最后,提供一些參考:
- 百度腦圖,幫助大家理解:http://naotu.baidu.com/file/9776e7b350f96cdebf263b0b7a3387c5?token=35e207d88cd851c2
- 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)