您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

支付开发填坑记之支付宝

时间:2019-09-19 09:37:23  来源:  作者:

主要针对在App应用和网页版的支付功能(支付宝,微信,银联)开发时,所遇到的坑。能填则填。

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

APP支付

APP支付步骤为:

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

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

  1. 获取支付宝的配置信息。
  2. 支付时需要的配置信息有:
  • key: 交易安全校验码。
  • app_id:支付宝分配给开发者的应用ID。
  1. 生成商家订单信息。
  2. 这个步骤由商家自行生成。支付宝那边只需要知道的订单信息为:
  • subject: 必填。商品的标题/交易标题/订单标题/订单关键字等。
  • total_amount: 必填。订单价格。
  • out_trade_no: 必填。商户网站唯一订单号。
  • body: 非必填。交易的具体描述信息。
  1. 根据订单信息生成待校验数据
  2. APP支付的详细请求参数: 点击查看

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

$sign = $alipaySubmit->buildRequestParaForApp($para_token);
  1. 其中, buildRequestParaForApp 的实现为:
  2. 对待签名参数数组排序
/**
 * 对数组排序
 * @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);
  1. APP将得到的数据请求支付宝客户端进行支付。
  2. 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();
 
/************************************************************/

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

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

2.构造参数:

$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;
}
  1. 字典排序:
/**
 * 对数组排序
 * @param $para 排序前的数组
 * return 排序后的数组
 */
function argSort($para) {
 ksort($para);
 reset($para);
 return $para;
}

签名:

/**
 * 生成签名结果
 * @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;
}
  1. 用构造好的参数请求支付宝后台申请新订单:
  2. 注意:请求时,必须带上SSL证书。
$sResult = getHttpResponsePOST($this->alipay_gateway_new, $this->alipay_config['cacert'],$request_data,trim(strtolower($this->alipay_config['input_charset'])));

请求函数的实现:

/**
 * 远程获取数据,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。

//URLDECODE返回的信息
$html_text = urldecode($html_text);
//解析远程模拟提交后返回的信息
$para_html_text = parseResponse($html_text);
//获取request_token
$request_token = $para_html_text['request_token'];
  1. parseResponse函数的实现:
/**
 * 解析远程模拟提交后返回的信息
 * @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进行订单支付。
  2. 成功请求token回来后,就可以向支付宝发出一次支付请求。
  3. 同样构造请求数据:
//业务详细只需要携带步骤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']))
);
  1. 将这些参数,在页面中传送给支付宝即可发起一次支付请求。
  2. 在PHP 中的实现就是将这些参数,渲染至HTML中,再将HTML中的表单提交即可。
  3. 到此,网页版的支付宝支付完成整个流程。

支付结果异步通知

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

  • 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. 获取支付宝通知数据
  2. 支付宝异步通知是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"
}
  1. 验签数据
  2. 验签需要支付宝的公钥
  3. 验签和签名的流程是一样的,都是将所有除了 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;
}
  1. 但是这里有个坑,就是返回数据中的 fund_bill_list 是经过html转义的(如例子中的数据: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}]),如果直接使用该参数进行签名,则会导致签名失败。这里就需要将字符串转义了: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}] ,用转义后的参数值进行签名,通过校验。
  2. 更改订单状态
  3. 验签完毕后,后台就可以根据实际情况进行订单状态的更改。

希望本文能帮助到您!

点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)

关注 {我},享受文章首发体验!

每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”

支付开发填坑记之支付宝

 



Tags:支付开发   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
主要针对在APP应用和网页版的支付功能(支付宝,微信,银联)开发时,所遇到的坑。能填则填。支付宝在所有支付方式中最好开发的了,因为文档比较清晰,而且开发起来也比较简单。因此,支付...【详细内容】
2019-09-19  Tags: 支付开发  点击:(151)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条