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

APP支付

APP支付步骤为:

  1. 获取支付宝的配置信息。
  2. 生成商家订单信息。
  3. 根据订单信息生成待校验数据
  4. 生成请求给支付宝的加密字符串
  5. 将待校验数据和加密字符串拼接,返回给APP。
  6. APP将得到的数据请求支付宝客户端进行支付。

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

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

    • key: 交易安全校验码。
    • app_id:支付宝分配给开发者的应用ID。
  2. 生成商家订单信息。
    这个步骤由商家自行生成。支付宝那边只需要知道的订单信息为:

    • subject: 必填。商品的标题/交易标题/订单标题/订单关键字等。
    • total_amount: 必填。订单价格。
    • out_trade_no: 必填。商户网站唯一订单号。
    • body: 非必填。交易的具体描述信息。
  3. 根据订单信息生成待校验数据
    APP支付的详细请求参数: 点击查看
  4. 生成请求给支付宝的加密字符串

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

    其中, buildRequestParaForApp 的实现为:

    1. 对待签名参数数组排序
    /*** 对数组排序* @param $para 排序前的数组* return 排序后的数组*/
    function argSort($para) {ksort($para);reset($para);return $para;
    }
    1. 生成签名结果(阿里推荐的是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;
    }
  5. 将待校验数据和加密字符串拼接,返回给APP。

    $url = "";
    foreach ($para_token as $key => $value) {$url .= $key."=".urlencode($value)."&";
    }
    return $url."sign=".urlencode($sign);
  6. APP将得到的数据请求支付宝客户端进行支付。
    APP端将拼接好的字符串拿去请求支付宝客户端即可调起支付宝进行支付。拼接好的字符串大致如下图所示:

网页版支付

网页版支付步骤为:

  1. 设置支付宝的配置信息。
  2. 向支付宝申请新订单,获取支付token。
  3. 携带token进行订单支付。

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

  1. 设置支付宝配置信息。

    /**调用授权接口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();/************************************************************/
  2. 向支付宝申请新订单,并获取订单的token。

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

    1. 构造参数:

      $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']))
      );
    2. 将构造好的请求参数,进行处理,字典排序,拼接字符串,签名:

      $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;}```
  1. 携带token进行订单支付。

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

    同样构造请求数据:

    //业务详细只需要携带步骤2的token即可。
    $req_data = '<auth_and_execute_req><request_token>' . $request_token . '</request_token></auth_and_execute_req>';
    //必填//构造要请求的参数数组,无需改动
    $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;
}
  1. 获取支付宝通知数据
    支付宝异步通知是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": "[{&quot;amount&quot;:&quot;31.00&quot;,&quot;fundChannel&quot;:&quot;ALIPAYACCOUNT&quot;}]","app_id": "APPID","seller_id": "SELLERID","notify_id": "8414394a1190f25edbbec9ba4b98642mem","seller_email": "YOUR_ALIPAY_ACCOUNT"
    }
  2. 验签数据
    验签需要支付宝的公钥

    验签和签名的流程是一样的,都是将所有除了 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转义的(如例子中的数据: [{&quot;amount&quot;:&quot;31.00&quot;,&quot;fundChannel&quot;:&quot;ALIPAYACCOUNT&quot;}]),如果直接使用该参数进行签名,则会导致签名失败。这里就需要将字符串转义了: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}] ,用转义后的参数值进行签名,通过校验。

  3. 更改订单状态

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

完毕

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

支付开发填坑记之支付宝相关推荐

  1. Java web 开发填坑记 2 -如何正确的创建一个Java Web 项目

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/72566261 本文出自[赵彦军的博客] Java web 开发填坑记 1-如何正确 ...

  2. Android项目开发填坑记-Fragment的onAttach

    背景 现在Android开发多使用一个Activity管理多个Fragment进行开发,不免需要两者相互传递数据,一般是给Fragment添加回调接口,让Activity继承并实现. 回调接口一般都写 ...

  3. 2016年微信app支付开发填坑篇

    之前开发过高德地图的,百度地图的,人家官网的资料,开发文档,官方论坛,应有尽有,特别详细.微信支付相对支付宝支付,操作繁琐了很多,而且有些文档上的说明太过专业,导致问题多多. 首先他们官网上面只有ec ...

  4. Android 项目开发填坑记 - 使用 MultiDex 解决 64K 限制

    如果移动端访问不佳,请访问 –> Github版 背景 Android 的 classLoader 在加载 APK 的时候限制了class.dex 包含的 Java 方法数,其总数不能超过655 ...

  5. Android 项目开发填坑记 - 谷歌商店上架被拒 Apps On Device

    关键词:谷歌商店被拒.数据安全表单.已安装应用.Policy Declaration.Data Safety Section.App Activity Data Type.Apps On Device ...

  6. 为微信开发填坑:微信网页支付的开发流程及填坑技巧 1

    GitChat 作者:极笔北客 原文:为微信开发填坑:微信网页支付的开发流程及填坑技巧 关注微信公众号:「GitChat 技术杂谈」 一本正经的讲技术 [不要错过文末彩蛋] 小程序作为微信之父张小龙钦 ...

  7. 为微信开发填坑:微信网页支付的开发流程及填坑技巧

    GitChat 作者:极笔北客 原文:为微信开发填坑:微信网页支付的开发流程及填坑技巧 关注微信公众号:「GitChat 技术杂谈」 一本正经的讲技术 [不要错过文末彩蛋] 小程序作为微信之父张小龙钦 ...

  8. 【SAP PO】X-DOC:SAP PO 接口配置 REST 服务对接填坑记

    X-DOC:SAP PO 接口配置 REST 服务对接填坑记 1.背景 2.PO SLD配置 3.PO https证书导入 1.背景 (1)需求背景: SAP中BOM频繁变更,技术人员在对BOM进行变 ...

  9. GoDB开发踩坑记(代码实现)

    前言 之前写了一篇GoDB开发踩坑记但是内容有些不全,所以来补充一下.所以没看过GoDB开发踩坑记的可以先看一下那篇文章. 正文 golang encode_josn--把map[string]int ...

最新文章

  1. jQuery+ajax中,让window.open不被拦截(转)
  2. unity 继承会调用start吗_Unity 继承MonoBehaviour脚本 执行顺序 详解
  3. mysql 查询条件优先级_mysql条件查询and or使用实例及优先级介绍
  4. 1.PostgreSQL的学习
  5. ×××站点的路由(隧道、接口)模式和策略模式
  6. 算法(8)-leetcode-explore-learn-数据结构-链表
  7. 使用BusyBox制作根文件系统的操作步骤
  8. HTML文档的三大构成元素,构成基础结课小结 为什么三大构成是设计的基础课程...
  9. Oracle从零开始3——复杂查询
  10. linux下mysql5.7的安装教程_linux下mysql 5.7.18安装教程 邯郸
  11. 微信公众号自动回复html,[.NET] 简单接入微信公众号开发:实现自动回复
  12. python 新式类和旧式类_python新式类和旧式类区别
  13. c# sha1签名 微信_微信公众号开发——微信JSSDK使用(踩坑)
  14. 时间操作(JavaScript版)—根据不同区时显示相应的时间
  15. java中将json转换成map
  16. centos7 wget无法解析主机域名的解决办法
  17. 如何实现幻灯片效果/图片轮播
  18. VC6.0(VC++6.0)使用教程(使用VC6.0编写C语言程序)
  19. conda加速器mamba使用
  20. 互联网日报 | 6月10日 星期四 | 腾讯云推出“云签约”解决方案;华为商城上线“鸿蒙智联”页面;“生鲜电商第一股”争夺战打响...

热门文章

  1. C#操作Word完全功略!
  2. “盗”亦有道,关于robots协议
  3. a标签,选中当前菜单后高亮
  4. python控制窗口显示隐藏
  5. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面
  6. 获取所有栈的信息,只有最上面的和最下面的,但是不能获取栈中间的activity信息...
  7. electron 使用中的注意事项
  8. [华为机试练习题]55.最大公约数 amp; 多个数的最大公约数
  9. 一个简单的登录页面,效果不错哦!
  10. OpenMeetings的安装