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 相关文章推荐
提升PHP速度全攻略
Oct 09 PHP
PHP生成静态页面详解
Dec 05 PHP
PHP Google的translate API代码
Dec 10 PHP
PHP5.3.1 不再支持ISAPI
Jan 08 PHP
php中截取中文字符串的代码小结
Jul 17 PHP
基于PHP常用字符串的总结(待续)
Jun 07 PHP
具有时效性的php加密解密函数代码
Jun 19 PHP
网页上facebook分享功能具体实现
Jan 26 PHP
php自动识别文字编码并转换为目标编码的方法
Aug 08 PHP
SESSION存放在数据库用法实例
Aug 08 PHP
CI框架无限级分类+递归的实现代码
Nov 01 PHP
Yii 框架入口脚本示例分析
May 19 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
实现dedecms全站URL静态化改造的代码
2007/03/29 PHP
php操作mysqli(示例代码)
2013/10/28 PHP
php中count获取多维数组长度的方法
2014/11/03 PHP
PHP加密解密类实例代码
2016/07/20 PHP
iOS10推送通知开发教程
2016/09/19 PHP
jQuery获得内容和属性方法及示例
2013/12/02 Javascript
鼠标移入移出事件改变图片的分辨率的两种方法
2013/12/17 Javascript
JavaScript中的值类型详细介绍
2014/12/29 Javascript
jQuery遍历页面所有CheckBox查看是否被选中的方法
2015/04/14 Javascript
jQuery实现网站添加高亮突出显示效果的方法
2015/06/26 Javascript
小巧强大的jquery layer弹窗弹层插件
2015/12/06 Javascript
Spring MVC中Ajax实现二级联动的简单实例
2016/07/06 Javascript
javascript实现消灭星星小游戏简单版
2016/11/15 Javascript
jquery实现input框获取焦点的简单实例
2017/01/26 Javascript
vue.js开发环境安装教程
2017/03/17 Javascript
Angularjs自定义指令Directive详解
2017/05/27 Javascript
详解webpack2+node+react+babel实现热加载(hmr)
2017/08/24 Javascript
使用Jenkins部署React项目的方法步骤
2019/03/11 Javascript
js事件触发操作实例分析
2019/06/21 Javascript
js 图片懒加载的实现
2020/10/21 Javascript
python的三目运算符和not in运算符使用示例
2014/03/03 Python
Python面向对象之Web静态服务器
2019/09/03 Python
深入浅出CSS3 background-clip,background-origin和border-image教程
2011/01/27 HTML / CSS
HTML5 Web 存储详解
2016/09/16 HTML / CSS
HTML5实现Notification API桌面通知功能
2016/03/02 HTML / CSS
美国电视购物:QVC
2017/02/06 全球购物
美国知名眼镜网站:Target Optical
2020/04/04 全球购物
计算机专业毕业生推荐信
2013/11/25 职场文书
酒店副总经理岗位职责范本
2014/02/04 职场文书
艾滋病宣传标语
2014/06/25 职场文书
工会趣味活动方案
2014/08/18 职场文书
领导班子三严三实对照检查材料
2014/09/25 职场文书
财务整改报告范文
2014/11/05 职场文书
Redis IP地址的绑定的实现
2021/05/08 Redis
python 如何将两个实数矩阵合并为一个复数矩阵
2021/05/19 Python
Spring Boot 排除某个类加载注入IOC的操作
2021/08/02 Java/Android