资讯专栏INFORMATION COLUMN

支付开发填坑记之支付宝

chanjarster / 2280人阅读

摘要:原文地址支付支付步骤为获取支付宝的配置信息。将得到的数据请求支付宝客户端进行支付。端将拼接好的字符串拿去请求支付宝客户端即可调起支付宝进行支付。向支付宝申请新订单,获取支付。成功请求回来后,就可以向支付宝发出一次支付请求。

支付宝在所有支付方式中最好开发的了,因为文档比较清晰,而且开发起来也比较简单。因此,支付宝的坑是相对较少的。
原文地址

APP支付

APP支付步骤为:

获取支付宝的配置信息。

生成商家订单信息。

根据订单信息生成待校验数据

生成请求给支付宝的加密字符串

将待校验数据和加密字符串拼接,返回给APP。

APP将得到的数据请求支付宝客户端进行支付。

由于APP支付是由APP去调起支付宝支付,所以服务端需要做的事情就是将请求参数封装好之后返回APP即可。

获取支付宝的配置信息。
支付时需要的配置信息有:

key: 交易安全校验码。

app_id:支付宝分配给开发者的应用ID。

生成商家订单信息。
这个步骤由商家自行生成。支付宝那边只需要知道的订单信息为:

subject: 必填。商品的标题/交易标题/订单标题/订单关键字等。

total_amount: 必填。订单价格。

out_trade_no: 必填。商户网站唯一订单号。

body: 非必填。交易的具体描述信息。

根据订单信息生成待校验数据
APP支付的详细请求参数: 点击查看

生成请求给支付宝的加密字符串

$sign = $alipaySubmit->buildRequestParaForApp($para_token);

其中, buildRequestParaForApp 的实现为:

对待签名参数数组排序

/**
 * 对数组排序
 * @param $para 排序前的数组
 * return 排序后的数组
 */
function argSort($para) {
    ksort($para);
    reset($para);
    return $para;
}

生成签名结果(阿里推荐的是RSA2的签名方式,这里项目用的是RSA)

/**
 * RSA签名
 * @param $data 待签名数据
 * @param $private_key_path 商户私钥文件路径
 * return 签名结果
 */
function rsaSign($data, $private_key_path) {
    $priKey = file_get_contents($private_key_path);
    $res = openssl_get_privatekey($priKey);
    openssl_sign($data, $sign, $res);
    openssl_free_key($res);
    //base64编码
    $sign = base64_encode($sign);
    return $sign;
}

将待校验数据和加密字符串拼接,返回给APP。

$url = "";
foreach ($para_token as $key => $value) {
    $url .= $key."=".urlencode($value)."&";
}
return $url."sign=".urlencode($sign);

APP将得到的数据请求支付宝客户端进行支付。
APP端将拼接好的字符串拿去请求支付宝客户端即可调起支付宝进行支付。拼接好的字符串大致如下图所示:

网页版支付

网页版支付步骤为:

设置支付宝的配置信息。

向支付宝申请新订单,获取支付token。

携带token进行订单支付。

网页版的支付宝支付相对于APP调起支付宝要复杂,因为网页支付时,需要多次请求支付宝服务器获取支付的必要参数。

设置支付宝配置信息。

/**调用授权接口alipay.wap.trade.create.direct获取授权码token**/
        
    //返回格式
    private  $format = "";
    //必填,不需要修改
    
    //版本
    private $v = "";
    //必填,不需要修改
    
    //请求号
    private $req_id = "";
    //必填,须保证每次请求都是唯一
    
    //**req_data详细信息**
    
    //服务器异步通知页面路径
    private $notify_url = "";
    //需http://格式的完整路径,不允许加?id=123这类自定义参数
    
    //页面跳转同步通知页面路径
    private $call_back_url = "";
    //需http://格式的完整路径,不允许加?id=123这类自定义参数
    
    //卖家支付宝账户
    private $seller_email = "";
    //必填
    
    //商户订单号
    private $out_trade_no = "";
    //商户网站订单系统中唯一订单号,必填
    
    //订单名称
    private $subject = "";
    //必填
    
    //付款金额
    private $total_fee = "";
    //必填
    
    //请求业务参数详细
    private $req_data = "";
    //必填
    
    //配置
    private $alipay_config = array();
    
/************************************************************/

向支付宝申请新订单,并获取订单的token。

请求token的service为: alipay.wap.trade.create.direct

构造参数:

$para_token = array(
    "service" => "alipay.wap.trade.create.direct",
    //  合作者身份(partner ID)
    "partner" => trim($this->alipay_config["partner"]),
    //  APP使用的是RSA,网页版使用的是MD5
    "sec_id" => trim($this->alipay_config["sign_type"]),
    //  返回的数据格式
    "format"    => $this->format,
    //  版本号?
    "v" => $this->v,
    //  唯一的请求号
    "req_id"    => $this->req_id,
    //  请求参数
    "req_data"  => $req_data,
    //  字符集,一般为utf8即可。
    "_input_charset"    => trim(strtolower($this->alipay_config["input_charset"]))
);

将构造好的请求参数,进行处理,字典排序,拼接字符串,签名:

$para_filter = paraFilter($para_temp);
$para_sort = argSort($para_filter);
$mysign = $this->buildRequestMysign($para_sort);
//签名结果与签名方式加入请求提交参数组中
$para_sort["sign"] = $mysign;
return $para_sort;

处理:过滤值为空的数据,过滤签名类型和签名。

function paraFilter($para) {
    $para_filter = array();
    while (list ($key, $val) = each ($para)) {
        if($key == "sign" || $key == "sign_type" || $val == "")continue;
        else    $para_filter[$key] = $para[$key];
    }
    return $para_filter;
}

字典排序:

/**
 * 对数组排序
 * @param $para 排序前的数组

     */
    function argSort($para) {
        ksort($para);
        reset($para);
        return $para;
    }
    ```
    签名:
    ```php
    /**
     * 生成签名结果
     * @param $para_sort 已排序要签名的数组
     * return 签名结果字符串
     */
    function buildRequestMysign($para_sort) {
        //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = createLinkstring($para_sort);
        $mysign = "";
        switch (strtoupper(trim($this->alipay_config["sign_type"]))) {
            case "MD5" :
                //  MD5直接将密钥拼接在字符串后面再进行MD5加密。
                $mysign = md5Sign($prestr, $this->alipay_config["key"]);
                break;
            case "RSA" :
                //  RSA则是先读取商户的私钥,再用该密钥对字符串进行加密。
                $mysign = rsaSign($prestr, $this->alipay_config["private_key_path"]);
                break;
            case "0001" :
                $mysign = rsaSign($prestr, $this->alipay_config["private_key_path"]);
                break;
            default :
                $mysign = "";
        }
        
        return $mysign;
    }
    ```
3.  用构造好的参数请求支付宝后台申请新订单:

    **注意:请求时,必须带上SSL证书。**

    ```php
    $sResult = getHttpResponsePOST($this->alipay_gateway_new, $this->alipay_config["cacert"],$request_data,trim(strtolower($this->alipay_config["input_charset"])));
    ```
    请求函数的实现:
    ```php
    /**
     * 远程获取数据,POST模式
     * 注意:
     * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
     * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd()."cacert.pem"
     * @param $url 指定URL完整路径地址
     * @param $cacert_url 指定当前工作目录绝对路径
     * @param $para 请求的数据
     * @param $input_charset 编码格式。默认值:空值
     * return 远程输出的数据
     */
    function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = "") {
    
        if (trim($input_charset) != "") {
            $url = $url."_input_charset=".$input_charset;
        }
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
        curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址
        curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
        curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
        curl_setopt($curl,CURLOPT_POST,true); // post传输数据
        curl_setopt($curl,CURLOPT_POSTFIELDS,$para);// post传输数据
        $responseText = curl_exec($curl);
        //var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
        curl_close($curl);
        
        return $responseText;
    }
    ```
    处理支付宝返回的数据,并获取token。
    
    ```php
    //URLDECODE返回的信息
    $html_text = urldecode($html_text);
    //解析远程模拟提交后返回的信息
    $para_html_text = parseResponse($html_text);
    //获取request_token
    $request_token = $para_html_text["request_token"];
    ```
    parseResponse函数的实现:
    
    ```php
    /**
     * 解析远程模拟提交后返回的信息
     * @param $str_text 要解析的字符串
     * @return 解析结果
     */
    function parseResponse($str_text) {
        //以“&”字符切割字符串
        $para_split = explode("&",$str_text);
        //把切割后的字符串数组变成变量与数值组合的数组
        foreach ($para_split as $item) {
            //获得第一个=字符的位置
            $nPos = strpos($item,"=");
            //获得字符串长度
            $nLen = strlen($item);
            //获得变量名
            $key = substr($item,0,$nPos);
            //获得数值
            $value = substr($item,$nPos+1,$nLen-$nPos-1);
            //放入数组中
            $para_text[$key] = $value;
        }
        
        if( ! empty ($para_text["res_data"])) {
            //解析加密部分字符串
            if($this->alipay_config["sign_type"] == "0001") {
                $para_text["res_data"] = rsaDecrypt($para_text["res_data"], $this->alipay_config["private_key_path"]);
            }
            
            //token从res_data中解析出来(也就是说res_data中已经包含token的内容)
            $doc = new DOMDocument();
            $doc->loadXML($para_text["res_data"]);
            $para_text["request_token"] = $doc->getElementsByTagName( "request_token" )->item(0)->nodeValue;
        }
        
        return $para_text;
    }
    ```

携带token进行订单支付。

成功请求token回来后,就可以向支付宝发出一次支付请求。

同样构造请求数据:

//业务详细只需要携带步骤2的token即可。
$req_data = "" . $request_token . "";
//必填

//构造要请求的参数数组,无需改动
$parameter = array(
    "service" => "alipay.wap.auth.authAndExecute",
    //  合作者身份(partner ID)
    "partner" => trim($this->alipay_config["partner"]),
    //  签名类型
    "sec_id" => trim($this->alipay_config["sign_type"]),
    //  和步骤2一致
    "format"    => $this->format,
    "v" => $this->v,
    "req_id"    => $this->req_id,
    //  业务详细参数
    "req_data"  => $req_data,
    //  字符集,一般为utf8.
    "_input_charset"    => trim(strtolower($this->alipay_config["input_charset"]))
);

将这些参数,在页面中传送给支付宝即可发起一次支付请求。

在PHP 中的实现就是将这些参数,渲染至HTML中,再将HTML中的表单提交即可。

到此,网页版的支付宝支付完成整个流程。

支付结果异步通知

在上面,我们看到有两个参数传给了支付宝:

call_back_url: 交易成功后,支付宝页面上“返回到商家页面”的地址(同步回调)

notify_url: 交易状态变更后,支付宝通知网站的回调地址(异步通知)

对于手机网站支付产生的交易,支付宝会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。

对于App支付产生的交易,支付宝会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。

支付宝异步通知官方文档中写的比较清楚,什么时候出发通知,返回什么参数,注意事项都有,开发者可以根据自己的情况查看具体信息。

验签步骤可以移步至这里

这里就简单的用手上的项目举例说明,支付宝通知后,后台是如何进行验签和处理订单。

public function app_notifyOp(){
    $payment_api = $this->_get_payment_api();
    $payment_config = $this->_get_payment_config();
    // 支付宝是用POST方式发送通知信息
    $callback_info = $payment_api->getNotifyInfoApp($_POST);
    if($callback_info) {
        //验证成功
        if ($callback_info["order_state"]) {
            // 如果是支付成功则改变订单状态
            $result = $this->_update_order($callback_info["out_trade_no"], $callback_info["trade_no"]);
        }else{
            // 如果是退款成功则修改退订的相关状态
            $result = $this->_app_refund($callback_info["out_trade_no"], $callback_info["trade_no"], $callback_info["refund_fee"]);
        }
        if($result["state"]) {
            echo "success";die;
        }
    }
    //验证失败
    echo "fail";die;
}

获取支付宝通知数据
支付宝异步通知是POST请求,返回的数据结构如下:

{
    "total_amount": "31.00",
    "buyer_id": "ID",
    "trade_no": "TRADE_NO",
    "body": "pay_sn:580546601841783375",
    "notify_time": "2017-04-27 09:50:59",
    "subject": "580546601841783375",
    "sign_type": "RSA",
    "buyer_logon_id": "ID",
    "auth_app_id": "APPID",
    "charset": "utf-8",
    "notify_type": "trade_status_sync",
    "invoice_amount": "31.00",
    "out_trade_no": "580546601841783375_r",
    "trade_status": "TRADE_SUCCESS",
    "gmt_payment": "2017-04-27 09:50:58",
    "version": "1.0",
    "point_amount": "0.00",
    "sign": "SIGNATURE",
    "gmt_create": "2017-04-27 09:50:58",
    "buyer_pay_amount": "31.00",
    "receipt_amount": "31.00",
    "fund_bill_list": "[{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}]",
    "app_id": "APPID",
    "seller_id": "SELLERID",
    "notify_id": "8414394a1190f25edbbec9ba4b98642mem",
    "seller_email": "YOUR_ALIPAY_ACCOUNT"
}

验签数据
验签需要支付宝的公钥

验签和签名的流程是一样的,都是将所有除了 sign 以外的参数,进行字典排序,并以 key=value 的形式以 & 符号拼成字符串,再使用密钥进行签名,将得到的签名与支付宝返回的签名进行对比,完成验签过程。

function getSignVeryfy($para_temp, $sign) {
    //除去待签名参数数组中的空值和签名参数
    $para = paraFilter($para_temp);
    
    //对待签名参数数组排序
    $para = argSort($para);

    //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
    $prestr = createLinkstring($para);
    $prestr = htmlspecialchars_decode($prestr);
    $isSgin = false;
    switch (strtoupper(trim($this->alipay_config["sign_type"]))) {
        case "MD5" :
            $isSgin = md5Verify($prestr, $sign, $this->alipay_config["key"]);
            break;
        case "RSA" :
            $isSgin = rsaVerify($prestr, trim($this->alipay_config["ali_public_key_path"]), $sign);
            break;
        case "0001" :
            $isSgin = rsaVerify($prestr, trim($this->alipay_config["ali_public_key_path"]), $sign);
            break;
        default :
            $isSgin = false;
    }
    logResult($log);
    
    return $isSgin;
}

但是这里有个坑,就是返回数据中的 fund_bill_list 是经过html转义的(如例子中的数据: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}]),如果直接使用该参数进行签名,则会导致签名失败。这里就需要将字符串转义了: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}] ,用转义后的参数值进行签名,通过校验。

更改订单状态

验签完毕后,后台就可以根据实际情况进行订单状态的更改。

完毕

祝各位程序猿在开发支付宝支付时不再有坑,也希望支付宝在后续的更新中不再埋雷。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/22931.html

相关文章

  • 支付开发填坑记之微信支付

    摘要:前者集成在中,后者主要是为微信用户提供了另一种支付方式需要在微信的内置浏览器中打开页面,再调起微信支付。步骤商户后台收到用户支付单,调用微信支付统一下单接口。拿到所有参数后,就可以在页面中发起微信支付的请求了。 微信支付,支持的支付方式比较多:有扫码支付,刷卡支付,APP支付和公众号支付。其中,APP和网站上最常用的就是APP支付和公众号支付。前者集成在APP中,后者主要是为微信用户提...

    zhunjiee 评论0 收藏0
  • 支付小程序爬坑记支付与微信小程序的区别。

    摘要:上线时间问题发布审核时间,微信小时内会审核完成,但是支付宝官方公示是上线审核需要三到五个工作日,据亲测,实际支付宝审核印版不会超过小时,但是支付宝的审核相比较微信真的很严格。 前言: 最近一个月接收一个支付宝小程序项目,并进行原生开发,现将遇到的问题,爬过的坑给大家进行分享,希望读者可以少走弯路,以下介绍的内容将从大方面到细节进行展开。 废话少言,直接开始步入正题 ①:上传、发布问...

    WilsonLiu95 评论0 收藏0
  • ionic 旅途-- 一起来填坑

    摘要:在中巧用解决跳转到第三方平台时不能回调的问题比如支付在开发中遇到不少的坑,绝大部分解决了但是在我们的中如果跳转到第三方网站上时,那么问题来了此时我们的是不能监听到你在其它网站上的事件的,所以当你想要回退到我们自己上时请紧握你的蛋小编在开发这 在ionic中巧用iframe解决跳转到第三方平台时不能回调的问题-比如支付 在ionic开发中遇到不少的坑,绝大部分解决了但是在我们的app...

    qieangel2013 评论0 收藏0
  • 使用vue开发微信公众号下SPA站点的填坑之旅

    摘要:原文见我的博客,点击进入使用开发微信公众号下站点的填坑之旅本文为我创业过程中,开发项目的填坑之旅。作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱。 原文见我的博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅。作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^_^。 ...

    yeyan1996 评论0 收藏0
  • 使用 vue2.0 开发微信公众号下前后端分离的SPA站点的填坑之旅

    摘要:目前正在写一个微信公众号的小项目,记录一下遇到的问题和解决方法主要是前端。前端提交时使用,在后端再取出对应的微信支付看了下文档,以前是需要用唤起支付,而现在则是把微信内置到了微信的浏览器中。 目前正在写一个微信公众号的小项目,记录一下遇到的问题和解决方法(主要是前端)。内容持续更新中~ 主要实现 前后端分离前端为 SPA 单页面使用微信的JSSDK微信支付 技术方案 后端使用 php ...

    afishhhhh 评论0 收藏0

发表评论

0条评论

chanjarster

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<