记录支付宝手机网站(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阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容