PHP 实现手机端APP支付宝支付功能


Posted in PHP onJune 07, 2018

最近应业务需求,做了支付宝支付和微信支付,今天分享一下手机端app支付宝支付对接流程,实际开发过程是前后端分离,前端调用后端API接口,实现功能返回数据,我所用的跨挤啊为TP5,大致可以分为四步:

       1.在蚂蚁金服开放平台创建应用,签约商户,生成应用公钥和私钥;

       2.配置统一下单支付参数;

       3.整合支付宝demo类文件;

       4.创建Alipay支付类,类内创建两个方法(alipay_app:统一下单方法和alipay_notify:支付成功异步回调方法);

       第一步主要是在蚂蚁金服开放平台登录你的支付宝账号,接入支付功能,个人就选个人,服务商就选服务商,需要填写一些材料,如手机号,邮箱等,完成后就可以创建应用啦,创建应用完成后需要进行签约,只有签约之后你应用里面开放的支付功能才会生效,签约也需要填一堆信息,签约需要审核,成功后你会拿到一个2088开头partner值,这个第三步配置参数的时候需要用到,之后还要为你的应用生成公钥和私钥,这点在开放平台开发文档中有详细描述,下载生成秘钥工具,选择对应的秘钥类型,秘钥和公钥一定要保存好,这里就不多做赘述啦,到此开放平台的准备工作就结束了。

       第二步就是整合支付宝demo文件了,我这里已经整合好了,直接把代码复制到两个文件中就可以了,一个为支付类,一个为通知类:

/*此为支付类*/
class AlipayApp{
 /**
  * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  * @param $para 需要拼接的数组
  * return 拼接完成以后的字符串
  */
 function createLinkstring($para,$showQuotes = false) {
  // $arg = "";
  // while (list ($key, $val) = each ($para)) {
  // $arg.=$key."=".$val."&";
  // }
  ////去掉最后一个&字符
  // $arg = substr($arg,0,count($arg)-2);
  ////如果存在转义字符,那么去掉转义
  // if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
  // return $arg;
  $arg = "";
  $quotes = '';
  if($showQuotes){
   $quotes = '"';
  }
  foreach ($para as $key => $val) {
   if($arg == ''){
    $arg = $key.'='.$quotes.$val.$quotes;
   }else{
    $arg = $arg.'&'.$key.'='.$quotes.$val.$quotes;
   }
  }
  if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
  return $arg;
 }
 /**
  * 对数组排序
  * @param $para 排序前的数组
  * return 排序后的数组
  */
 function argSort($para) {
  ksort($para);
  reset($para);
  return $para;
 }
 /**
  * 除去数组中的空值和签名参数
  * @param $para 签名参数组
  * return 去掉空值与签名参数后的新签名参数组
  */
 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;
 }
 function query_timestamp() {
  $url = $this->alipay_gateway_new."service=query_timestamp&partner=".trim(strtolower($this->alipay_config['partner']))."&_input_charset=".trim(strtolower($this->alipay_config['input_charset']));
  $encrypt_key = "";
  $doc = new DOMDocument();
  $doc->load($url);
  $itemEncrypt_key = $doc->getElementsByTagName( "encrypt_key" );
  $encrypt_key = $itemEncrypt_key->item(0)->nodeValue;
  return $encrypt_key;
 }
 /**
  * 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
  * 注意:服务器需要开通fopen配置
  * @param $word 要写入日志里的文本内容 默认值:空值
  */
 function logResult($word='') {
  date_default_timezone_set("PRC");
  $fp = fopen("log.txt","a");
  flock($fp, LOCK_EX) ;
  fwrite($fp,"执行日期:".strftime("%Y%m%d%H%M%S",time())."\n".$word."\n");
  flock($fp, LOCK_UN);
  fclose($fp);
 }
 /**
  * 远程获取数据,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;
 }
 /**
  * 远程获取数据,GET模式
  * 注意:
  * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
  * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem'
  * @param $url 指定URL完整路径地址
  * @param $cacert_url 指定当前工作目录绝对路径
  * return 远程输出的数据
  */
 function getHttpResponseGET($url,$cacert_url) {
  $curl = curl_init($url);
  curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
  curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
  curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
  curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
  curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址
  $responseText = curl_exec($curl);
  //var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
  curl_close($curl);
  return $responseText;
 }
 /**
  * 实现多种字符编码方式
  * @param $input 需要编码的字符串
  * @param $_output_charset 输出的编码格式
  * @param $_input_charset 输入的编码格式
  * return 编码后的字符串
  */
 function charsetEncode($input,$_output_charset ,$_input_charset) {
  $output = "";
  if(!isset($_output_charset) )$_output_charset = $_input_charset;
  if($_input_charset == $_output_charset || $input ==null ) {
   $output = $input;
  } elseif (function_exists("mb_convert_encoding")) {
   $output = mb_convert_encoding($input,$_output_charset,$_input_charset);
  } elseif(function_exists("iconv")) {
   $output = iconv($_input_charset,$_output_charset,$input);
  } else die("sorry, you have no libs support for charset change.");
  return $output;
 }
 /**
  * 实现多种字符解码方式
  * @param $input 需要解码的字符串
  * @param $_output_charset 输出的解码格式
  * @param $_input_charset 输入的解码格式
  * return 解码后的字符串
  */
 function charsetDecode($input,$_input_charset ,$_output_charset) {
  $output = "";
  if(!isset($_input_charset) )$_input_charset = $_input_charset ;
  if($_input_charset == $_output_charset || $input ==null ) {
   $output = $input;
  } elseif (function_exists("mb_convert_encoding")) {
   $output = mb_convert_encoding($input,$_output_charset,$_input_charset);
  } elseif(function_exists("iconv")) {
   $output = iconv($_input_charset,$_output_charset,$input);
  } else die("sorry, you have no libs support for charset changes.");
  return $output;
 }
 /**
  * RSA签名
  * @param $data 待签名数据
  * @param $private_key 商户私钥字符串
  * return 签名结果
  */
 function rsaSign($data, $private_key) {
  //以下为了初始化私钥,保证在您填写私钥时不管是带格式还是不带格式都可以通过验证。
  $private_key=str_replace("-----BEGIN RSA PRIVATE KEY-----","",$private_key);
  $private_key=str_replace("-----END RSA PRIVATE KEY-----","",$private_key);
  $private_key=str_replace("\n","",$private_key);
  $private_key="-----BEGIN RSA PRIVATE KEY-----".PHP_EOL .wordwrap($private_key, 64, "\n", true). PHP_EOL."-----END RSA PRIVATE KEY-----";
  $res=openssl_get_privatekey($private_key);
  if($res)
  {
   openssl_sign($data, $sign,$res);
  }
  else {
   echo "您的私钥格式不正确!"."<br/>"."The format of your private_key is incorrect!";
   exit();
  }
  openssl_free_key($res);
  //base64编码
  $sign = base64_encode($sign);
  return $sign;
 }
 /**
  * RSA验签
  * @param $data 待签名数据
  * @param $alipay_public_key 支付宝的公钥字符串
  * @param $sign 要校对的的签名结果
  * return 验证结果
  */
 function rsaVerify($data, $alipay_public_key, $sign) {
  //以下为了初始化私钥,保证在您填写私钥时不管是带格式还是不带格式都可以通过验证。
  $alipay_public_key=str_replace("-----BEGIN PUBLIC KEY-----","",$alipay_public_key);
  $alipay_public_key=str_replace("-----END PUBLIC KEY-----","",$alipay_public_key);
  $alipay_public_key=str_replace("\n","",$alipay_public_key);
  $alipay_public_key='-----BEGIN PUBLIC KEY-----'.PHP_EOL.wordwrap($alipay_public_key, 64, "\n", true) .PHP_EOL.'-----END PUBLIC KEY-----';
  $res=openssl_get_publickey($alipay_public_key);
  if($res)
  {
   $result = (bool)openssl_verify($data, base64_decode($sign), $res);
  }
  else {
   echo "您的支付宝公钥格式不正确!"."<br/>"."The format of your alipay_public_key is incorrect!";
   exit();
  }
  openssl_free_key($res);
  return $result;
 }
}

/* *
 * 类名:AlipayNotify
 * 功能:支付宝通知处理类
 * 详细:处理支付宝各接口通知返回
 * 版本:1.0
 * 日期:2016-06-06
 * 说明:
 * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
 * 该代码仅供学习和研究支付宝接口使用,只是提供一个参考
 *************************注意*************************
 * 调试通知返回时,可查看或改写log日志的写入TXT里的数据,来检查通知返回是否正常
 */
class AlipayNotify {
 /**
  * HTTPS形式消息验证地址
  */
 var $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
 /**
  * HTTP形式消息验证地址
  */
 var $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
 var $alipay_config;
 function __construct($alipay_config){
  $this->alipay_config = $alipay_config;
 }
 function AlipayNotify($alipay_config) {
  $this->__construct($alipay_config);
 }
 /**
  * 获取返回时的签名验证结果
  * @param $para_temp 通知返回来的参数数组
  * @param $sign 返回的签名结果
  * @return 签名验证结果
  */
 function getSignVeryfy($para_temp, $sign) {
  $alipayapp = new \Alipayapp();
  //除去待签名参数数组中的空值和签名参数
  $para_filter = $alipayapp->paraFilter($para_temp);
  //对待签名参数数组排序
  $para_sort = $alipayapp->argSort($para_filter);
  //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
  $prestr = $alipayapp->createLinkstring($para_sort);
  $isSgin = false;
  switch (strtoupper(trim($this->alipay_config['sign_type']))) {
   case "RSA" :
    $isSgin = $alipayapp->rsaVerify($prestr, trim($this->alipay_config['alipay_public_key']), $sign);
    break;
   default :
    $isSgin = false;
  }
  return $isSgin;
 }
 /**
  * 获取远程服务器ATN结果,验证返回URL
  * @param $notify_id 通知校验ID
  * @return 服务器ATN结果
  * 验证结果集:
  * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空
  * true 返回正确信息
  * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
  */
 function getResponse($notify_id) {
  $alipayapp = new \Alipayapp();
  $transport = strtolower(trim($this->alipay_config['transport']));
  $partner = trim($this->alipay_config['partner']);
  $veryfy_url = '';
  if($transport == 'https') {
   $veryfy_url = $this->https_verify_url;
  }
  else {
   $veryfy_url = $this->http_verify_url;
  }
  $veryfy_url = $veryfy_url."partner=" . $partner . "¬ify_id=" . $notify_id;
  $responseTxt = $alipayapp->getHttpResponseGET($veryfy_url, $this->alipay_config['cacert']);
  return $responseTxt;
 }
}

       第三步配置参数:

//配置参数
public $alipay_config = array(
 //2088开头
 'partner' => '',
 //商户的私钥,此处填写原始私钥去头去尾,RSA公私钥生成:https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.nBDxfy&treeId=58&articleId=103242&docType=1
 'private_key' => '',
 //支付宝的公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm?keyType=partner
 'alipay_public_key' => '',
 //异步通知接口
 'service'=> 'mobile.securitypay.pay',
 //字符编码格式 目前支持 gbk 或 utf-8
 'input_charset' => 'utf-8',
 //签名方式 不需修改
 'sign_type' => 'RSA',
 //ca证书路径地址,用于curl中ssl校验
 //请保证cacert.pem文件在当前文件夹目录中
 'cacert' => '/cacert.pem',
 //访问模式,根据自己的服务器是否支持ssl访问,若支持请选择https;若不支持请选择http
 'transport' => 'http',
);

创建$alipa_config属性,可以放到你的配置文件中方便引入,也可以直接放到Alipay类中,里面只要前三项需要自己填写,其余不变就好啦。

        第四步就是创建Alipay类,类内定义两个方法,一个是alipay_app(统一下单),alipay_app中的一些参数需要自己补全,参数介绍点我,另一个是alipay_notify(支付成功后的异步回调)并引入第二步创建的两个类文件,本人开发用的是TP5框架,支付类放在第三方类库vendor目录下面,可以直接在控制器中用vendor()函数引入文件,例:vendor('Alipayapp.lib.alipay_notify');

//调用统一下单接口生成预支付订单并把数据返回给APP
public function alipay_app(Request $request)
{
 vendor('Alipayapp.lib.AlipayApp');
 $param = $request->param();
 $tade_no = $param['orderCode'];//订单号 调用统一下单接口需要提供一个订单号
 $order = new Order(); //实例化订单
 $ret = $order->getOrderN2($tade_no); //查询订单信息 此处为我自己的查询订单信息方法,可以替换为你自己的
 $strOrg['partner']=$this->alipay_config['partner']; // 配置的partner
 $strOrg['seller_id']=$this->alipay_config['partner']; // 此处和partner值一样即可
 $strOrg['out_trade_no']=$tade_no; // 订单号
 $strOrg['subject']=""; // 商品的标题
 $strOrg['body']="";//商品名
 $strOrg['total_fee']=$ret['money'];// 金额
 $strOrg['notify_url']="";//回调地址,填写回调方法的绝对路径
 $strOrg['service']=$this->alipay_config['service'];
 $strOrg['payment_type']="1";
 $strOrg['_input_charset']="utf-8";
 $strOrg['it_b_pay']="30m";
 $alipay = new \Alipayapp();
 //将post接收到的数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串。
 $data=$alipay->createLinkstring($strOrg,true);//,true
 //将待签名字符串使用私钥签名,且做urlencode. 注意:请求到支付宝只需要做一次urlencode.
 $rsa_sign=urlencode($alipay->rsaSign($data, $this->alipay_config['private_key']));
 //把签名得到的sign和签名类型sign_type拼接在待签名字符串后面。
 $data = $data.'&sign='.'"'.$rsa_sign.'"'.'&sign_type='.'"'.$this->alipay_config['sign_type'].'"';
 //返回给客户端,建议在客户端使用私钥对应的公钥做一次验签,保证不是他人传输。
 $datajson['mdata']=$data;
 echo json_encode($datajson);
}

//支付成功后的回调方法
public function alipay_notify()
{
 vendor('Alipayapp.lib.alipay_notify');//引入支付通知类文件
 $alipayNotify = new \AlipayNotify($this->alipay_config);
 $order = new Order(); //实例化订单
 if($alipayNotify->getResponse($_POST['notify_id'])) //判断成功之后使用getResponse方法判断是否是支付宝发来的异步通知。
 {
  if($alipayNotify->getSignVeryfy($_POST, $_POST['sign'])) {//使用支付宝公钥验签
   //获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
   //商户订单号
   $out_trade_no = $_POST['out_trade_no'];
   $ret = $order->getOrderN2($out_trade_no); //查询订单信息
   $total_amount=$ret['money']; //订单金额
   $total_fee = $_POST['total_fee']; //支付宝返回金额
   if($_POST['trade_status'] == 'TRADE_FINISHED') {
    //判断该笔订单是否在商户网站中已经做过处理
    //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
    //如果有做过处理,不执行商户的业务程序
    //注意:
    //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
    //请务必判断请求时的out_trade_no、total_fee、seller_id与通知时获取的out_trade_no、total_fee、seller_id为一致的
    if($total_amount==$total_fee){
     //这里进行数据库操作,比如修改订单状态等
    }
   }else if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
    //判断该笔订单是否在商户网站中已经做过处理,如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程
    //如果有做过处理,不执行商户的业务程序
    //注意:
    //付款完成后,支付宝系统发送该交易状态通知
    //请务必判断请求时的out_trade_no、total_fee、seller_id与通知时获取的out_trade_no、total_fee、seller_id为一致的
    if($total_amount==$total_fee){
     //这里进行数据库操作,比如修改订单状态等
    }
   }
   echo "success"; //请不要修改或删除
  }else{ //验证签名失败
   echo "sign fail";
  }
 }else{ //验证是否来自支付宝的通知失败
  echo "response fail";
 }
}

       上述getOrderN2方法是我查询订单信息用的,需要替换成你自己的查询订单方法,以上四步完成正常的话支付宝支付功能就可以实现了,需要注意的是支付功能简单的在本地测试是不可以的,需要在服务器上测试,也有说用花生壳什么的...这个我没有研究,如果没有成功查看报错提示,在对接支付时有很多坑,遇到报错不要急,逐步解决一定可以实现支付功能的,如果自己实在解决不了的可以找支付宝技术人员,如果你想查看支付宝异步通知时的返回值,可以用 file_put_contents() 函数打印返回的参数,会在你指定的路径生成一个文件,里面就是返回的值啦。

总结

以上所述是小编给大家介绍的PHP 实现手机端APP支付宝支付功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

PHP 相关文章推荐
在Windows中安装Apache2和PHP4的权威指南
Oct 09 PHP
利用Memcached在php下实现session机制 替换PHP的原生session支持
Aug 21 PHP
PHP中source #N问题的解决方法
Jan 27 PHP
php上传中文文件名乱码问题处理方案
Feb 03 PHP
PHP程序员不应该忽略的3点
Oct 09 PHP
ThinkPHP表单令牌错误的相关解决方法分析
May 20 PHP
php封装的mysqli类完整实例
Oct 18 PHP
php使用file函数、fseek函数读取大文件效率对比分析
Nov 04 PHP
php中简单的对称加密算法实现
Jan 05 PHP
PHP简单实现二维数组的矩阵转置操作示例
Nov 24 PHP
PHP的new static和new self的区别与使用
Nov 27 PHP
PHPExcel实现的读取多工作表操作示例
Apr 14 PHP
Laravel程序架构设计思路之使用动作类
Jun 07 #PHP
laravel手动创建数组分页的实现代码
Jun 07 #PHP
thinkPHP框架实现生成条形码的方法示例
Jun 06 #PHP
使用PHP访问RabbitMQ消息队列的方法示例
Jun 06 #PHP
PHP简单实现记录网站访问量功能示例
Jun 06 #PHP
Laravel框架实现利用监听器进行sql语句记录功能
Jun 06 #PHP
Laravel框架实现利用中间件进行操作日志记录功能
Jun 06 #PHP
You might like
PHP实现数组递归转义的方法
2014/08/28 PHP
PHP 实现的将图片转换为TXT
2015/10/21 PHP
CodeIgniter配置之config.php用法实例分析
2016/01/19 PHP
基于jquery的文章中所有图片width大小批量设置方法
2013/08/01 Javascript
jquery点击缩略图切换视频播放特效代码分享
2015/09/15 Javascript
JS常见问题之为什么点击弹出的i总是最后一个
2016/01/05 Javascript
JavaScript 弹出子窗体并返回结果到父窗体的实现代码
2016/05/28 Javascript
纯JS前端实现分页代码
2016/06/21 Javascript
聊一聊jQuery插件uploadify使用方法
2016/08/24 Javascript
基于JS实现仿百度百家主页的轮播图效果
2017/03/06 Javascript
JavaScript对象的浅拷贝与深拷贝实例分析
2018/07/25 Javascript
elementUI select组件默认选中效果实现的方法
2019/03/25 Javascript
Vue可自定义tab组件用法实例
2019/10/24 Javascript
element中el-container容器与div布局区分详解
2020/05/13 Javascript
通过vue.extend实现消息提示弹框的方法记录
2021/01/07 Vue.js
JavaScript 实现继承的几种方式
2021/02/19 Javascript
[01:37]DOTA2超级联赛专访ChuaN 传奇般的电竞之路
2013/06/19 DOTA
Python的Django中django-userena组件的简单使用教程
2015/05/30 Python
python线程、进程和协程详解
2016/07/19 Python
django admin添加数据自动记录user到表中的实现方法
2018/01/05 Python
python3 实现验证码图片切割的方法
2018/12/07 Python
python实现贪吃蛇游戏
2020/03/21 Python
windows系统中Python多版本与jupyter notebook使用虚拟环境的过程
2019/05/15 Python
python中的单引号双引号区别知识点总结
2019/06/23 Python
关于python字符串方法分类详解
2019/08/20 Python
python实现飞行棋游戏
2020/02/05 Python
浅谈pymysql查询语句中带有in时传递参数的问题
2020/06/05 Python
最新pycharm安装教程
2020/11/18 Python
Richards网上商店:当代时尚,遍布巴西
2019/11/03 全球购物
自动化工程专业个人应聘自荐信
2013/09/26 职场文书
仓库主管的岗位职责
2013/12/04 职场文书
追悼会上的答谢词
2014/01/10 职场文书
洗手间标语
2014/06/23 职场文书
婚礼父母致辞
2015/07/28 职场文书
2016年猴年新春致辞
2015/08/01 职场文书
go语言基础 seek光标位置os包的使用
2021/05/09 Golang