記錄支付寶手機網站(WAP)支付踩過的坑

由于蘋果審核機制變化,除了JSPatch等熱修復的應用受到影響外,另個影響較大的就是非法集成第三方支付SDK(尤其支付寶)而審核被拒。但是由于你懂的的原因,不想走IAP(In App Pay),所以當然想到了支付寶 WAP 支付。完成 WAP 支付大概花了三天多時間,但是有大概一天時間是在等簽約,所以為了讓大家和自己有需要的話快速集成,特意做一個總結。涉及 iOS(OC)JS(HTML5)以及PHP,下面進入正題。

吐槽支付寶開放平臺

之前我一直覺得只有微信的開發平臺網站比較容易搞混,(一個是微信開放平臺,另個是微信公眾平臺),但是接觸支付寶后,深深陷入各個跳轉和新舊文檔難以自拔(我記得之前沒這么亂啊,我知道網站迭代更新向后兼容不易,但是能不能稍微克制一下??)
先扔出個WAP支付開發文檔的鏈接出來,大家最好直接輸入鏈接,搜索引擎慎用。(你能明白花了很長時間研究文檔最后發現是老版本的痛苦嗎)
WAP支付開發文檔

開發步驟

準備工作
  1. 注冊商家等都不說了,很簡單
  2. 雖然之前已經簽約過 APP 支付功能,但是做手機網站支付的話還得繼續簽約手機網站支付功能,注意 APPID 是不一樣的。簽約網址在這里簽約申請,開始的時候我以為我們運營同事已經幫忙申請好,但是最后在調支付寶接口的時候老是報錯ISV權限不足,經過檢查才發現是沒有簽約手機網站支付,提交申請審核時間是一個工作日。所以大家記得提前申請。
  3. 簽約成功后,由于支付寶使用 RSA 數據加密方式,非對稱加密,所以在本機可以openssl生成應用私鑰和應用公鑰(統稱密鑰),應用私鑰在自己的代碼中加密數據的時候會用到,應用公鑰配置到支付寶后臺產生支付寶公鑰進行數據解密。注意下圖,配置手機網站支付的時候選擇左邊的平臺開放密鑰,然后找到對應的APPID產品進行公鑰配置。我一開始失誤配置成了下面的mapi網關產品密鑰,導致報錯驗簽失敗
    WX20170322-200717.png
接下來修改 OC代碼調用支付寶SDK支付換成打開HTML網頁,在網頁里面完成支付功能。

注意后面的queryString,由于支付網頁需要UID,考慮到方便快捷,沒有選擇用 WebViewJSBridge 進行數據交互,而是直接拼在鏈接后面。

// 阿里 wap 支付
- (void)wapPay {
    ZKSafariViewController *vc = [ZKSafariViewController new];
    NSString *oriUrl = @"...";
    NSString *queryStr = [NSString stringWithFormat:@"?uid=%@&fee=%@", _loginUser.uid, _total_fee];
    NSString *urlStr = [oriUrl stringByAppendingString:queryStr];
    vc.url = urlStr;
    [_applicationContext.navigationController pushViewController:vc animated:YES];
}
編寫HTML頁面,提交數據到PHP后端。(前端代碼)

代碼如下:

<form name="alipayment" id="alipayment" action='...AliPay/CreateWapPay' method=post>
// ... 一些 input 標簽,提交到服務器       
</form>
<script type="text/javascript">
    function GetDateNow() {
        let totalFee = getQueryStringArgs().total_fee,
            uid = getQueryStringArgs().uid;
         // ...
        document.getElementById("uid").value = uid;
        document.getElementById("total_fee").value = totalFee;
        // ...
    }
    GetDateNow();
    document.forms['alipayment'].submit();
</script>
集成 支付寶 PHP SDK 詳細步驟(后端代碼)
  1. 下載PHP DEMO
  2. 將SDK拖入項目文件,配置config.php文件,如下
$config = array (
  'app_id' => "...",

  //商戶私鑰,您的原始格式RSA私鑰
  'merchant_private_key' => "...",

  //異步通知地址
  'notify_url' => "http://工程公網訪問地址/alipay.trade.wap.pay-PHP-UTF-8/notify_url.php",

  //同步跳轉
  'return_url' => "http://mitsein.com/alipay.trade.wap.pay-PHP-UTF-8/return_url.php",

  //編碼格式
  'charset' => "UTF-8",

  //簽名方式
  'sign_type'=>"RSA",

  //支付寶網關
  'gatewayUrl' => "https://openapi.alipay.com/gateway.do",

  //支付寶公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。
  'alipay_public_key' => "...",
);
  1. 組裝系統參數$sysParamsapp_id, return_url, nofify_url, sign_type等),獲取業務參數$apiParams(放在biz_content字段中)并將其加密如下
$enCryptContent = encrypt($apiParams['biz_content'], $this->encryptKey);
$apiParams['biz_content'] = $enCryptContent;
  ```
將系統參數和業務參數組裝在一起進行簽名
  ```
$totalParams = array_merge($apiParams, $sysParams);
//待簽名字符串
$preSignStr = $this->getSignContent($totalParams);
//簽名
$totalParams["sign"] = $this->generateSign($totalParams, $this->signType);
  1. 然后到了關鍵的地方,利用total_params拼接表單字符串,方法如下
/**
     * 建立請求,以表單HTML形式構造(默認)
     * @param $para_temp 請求參數數組
     * @return 提交表單HTML文本
     */
    protected function buildRequestForm($para_temp) {
        
        $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->gatewayUrl."?charset=".trim($this->postCharset)."' method='POST'>";
        while (list ($key, $val) = each ($para_temp)) {
            if (false === $this->checkEmpty($val)) {
                //$val = $this->characet($val, $this->postCharset);
                $val = str_replace("'","'",$val);
                //$val = str_replace("\"",""",$val);
                $sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>";
            }
        }

        //submit按鈕控件請不要含有name屬性
        $sHtml = $sHtml."<input type='submit' value='ok' style='display:none;''></form>";
        
        $sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>";
        
        return $sHtml;
    }
  1. 執行第4步生成的表單html代碼被類AlipayTradeService進行echo到前端頁面上,包含JS自動提交腳本,所以就直接調起了支付寶支付。為了讓大家更加明白,生成的h5代碼形式如下:
<form id='alipaysubmit' name='alipaysubmit' action='https://openapi.alipay.com/gateway.do?charset=UTF-8' method='POST'>
  <input type='hidden' name='biz_content' value='{"productCode":"QUICK_WAP_PAY","body":null,"subject":null,"out_trade_no":"1234","total_amount":"100","timeout_express":"1m"}'/>
  <input type='hidden' name='app_id' value='2088711989778894'/>
  <input type='hidden' name='version' value='1.0'/>
  <input type='hidden' name='format' value='json'/>
  <input type='hidden' name='sign_type' value='RSA'/>
  <input type='hidden' name='method' value='alipay.trade.wap.pay'/>
  <input type='hidden' name='timestamp' value='2017-03-20 17:16:15'/>
  <input type='hidden' name='alipay_sdk' value='alipay-sdk-php-20161101'/>
  <input type='hidden' name='notify_url' value='http://.../notify_url.php'/>
  <input type='hidden' name='return_url' value='http://.../return_url'/>
  <input type='hidden' name='charset' value='UTF-8'/>
  <input type='hidden' name='sign' value='vgU9ROZeES3fa6CPo5onwY2auGN7N6naNI8Wo5l2U/K6LPk2stkv0Cor5Dn57Xo83GefOnoPg5A/7dNLbZjTXioPaocrPg3LteDB/EV3zYHXUPsab7dPztW+7guQDbLXI1RtaEuPm85hjO2Cur5EmP3P3sAE1XVGYJHHtLJoAbKzm/I='/>
  <input type='submit' value='ok' style='display:none;''>
</form>
<script>document.forms['alipaysubmit'].submit();</script>"

當上面的H5代碼被echo到前端頁面,會自動submit表單信息。繼續往下看

在 iOS 中實現代理進行跳轉(iOS 移動端代碼)

自動提交后,當手機有安裝支付寶的時候,在webView中實現一個協議方法即可自動跳轉到支付寶客戶端。如果沒有安裝,進入支付寶的H5收銀臺進行支付。協議方法如下

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest: (NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString* reqUrl = request.URL.absoluteString;
    if ([reqUrl hasPrefix:@"alipays://"] || [reqUrl hasPrefix:@"alipay://"]) {
      BOOL bSucc = [[UIApplication sharedApplication]openURL:request.URL];
      if (!bSucc) {
          // 未安裝支付寶 進入網頁支付
      }
      return NO;
  }
  return YES;
}
善后工作
  1. 當支付成功后,會自動跳轉到return_url頁面,這個頁面簡單展示一下支付成功信息即可。注意并不能作為交易成功的憑證。
  2. 支付成功后,除了會進入上面提到的return_url,支付寶還會異步通知notify_url,并傳遞詳細的交易信息,支付寶會根據上面傳入的異步通知地址notify_url通過POST請求的形式將支付結果作為參數通知到商戶后臺系統。在這個接口中實現驗簽和支付成功的業務邏輯代碼。

上面提到的常用的支付寶網站羅列一下

  1. 新版開發文檔入口 https://openhome.alipay.com/developmentDocument.htm
  2. 查看是否已經簽約 https://app.alipay.com/market/productIndex.htm
  3. 開放平臺密鑰管理 https://openhome.alipay.com/platform/keyManage.htm
  4. 調用支付寶網關接口https://openapi.alipay.com/gateway.do https://doc.open.alipay.com/doc2/detail.htm?treeId=203&articleId=105463&docType=1

分享幾個這個項目中比較好用的函數

  1. 創建訂單號 很簡單,月日時分秒 + 5位隨機數
/**
 * Created by ZK on 17/3/22.
 */
// 創建訂單編號
function generateOrderID(){
    var formatTime = function (date) {
        var month = date.getMonth() + 1,
            day = date.getDate(),
            hour = date.getHours(),
            minute = date.getMinutes(),
            second = date.getSeconds();
        return [month, day,hour, minute, second].map(formatNumber).join('') ;
    };
    function formatNumber(n) {
        n = n.toString();
        return n[1] ? n : '0' + n;
    }

    var nowDate = new Date(),
        dateStr = formatTime(nowDate),
        randomNum = Math.random()*1000000000,
        oriStr = dateStr + randomNum,
        orderID = oriStr.substr(0,15);

    return orderID;
}
  1. 取得 QueryString 參數值
function getQueryStringArgs() {
    var qs = (location.search.length > 0 ? location.search.substring(1) : ''),
        args = {},
        items = qs.length ? qs.split('&') : [],
        item = null,
        name = null,
        value = null,
        i = 0,
        len = items.length;
    for (i = 0; i < len; i ++) {
        item = items[i].split('=');
        name = decodeURIComponent(item[0]);
        value = decodeURIComponent(item[1]);

        if (name.length) {
            args[name] = value;
        }
    }

    return args;
}
  1. 另外一個比較好用的在PHP代碼中插入JS代碼實現彈出debug信息,如果服務器不支持write info,這招很方便。
echo '<script type="text/javascript">alert("testMsg");</script>';
// 輸出變量內容
$name = 'dev_zk';
echo '<script type="text/javascript">alert('.$name.');</script>';

多分享幾個坑

  1. JS 腳本中的代碼最好別用ES6以上的版本,除非你用babel自動轉換ES5。我就栽坑了,寫的ES6腳本在 iOSVersion <=9.0 的系統中全部失效報錯,但是在移動應用中測試 JS 的不方便你是知道的,找了好久才意識到這個問題。高估了WebKit對JS的版本支持更新速度。
  2. ISV權限不足 --- 沒有簽約對應功能。
  3. 驗簽失敗 --- 沒有在對應APPID設置正確公鑰。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容