最近作微商城,须要实现手机wap支付功能,选择的是支付宝的接口支付功能。这里是我用的是支付宝“手机网站支付”产品(注:该产品要支付宝企业帐号才能申请),具体步骤以下:php
1、下载支付宝接口包html
https://b.alipay.com/order/productDetail.htm?productId=2013080604609688node
2、从新整理接口包文件thinkphp
下载下来的接口包文件有不少语言的源码api
注:openssl用来生成公私钥,RSA签名才使用。这里咱们用的MD5签名,全部该文件用不到。数组
咱们选择WS_WAP_PAYWAP-PHP-UTF-8这个名称的接口文件。服务器
它包含以下文件:框架
images文件里是支付宝相关的一些标志的图片,key文件保存RSA签名的公私钥文件,openssl文件也是RSA签名才用到的,全部这三个文件暂时无论他。lib文件很重要,是整个接口的核心文件;curl
alipay.config.php是相关参数的配置文件异步
alipayapi.php是支付宝接口入口文件
notify_url.php是服务器异步通知页面文件
return_url.php是页面跳转同步通知文件
在ThinkPHP的框架文件下,找到Extend进入,再进入Vendor,在Vendor文件夹下,新建文件夹Alipay,把支付宝做为第三方类库引入。而后,复制支付宝接口文件包中的lib文件里的文件。
一共5个文件,以下:
alipay_rsa.function.php文件不须要。
如今对以上文件进行重命名,
alipay_core.function.php重命名为:Corefunction.php
alipay_md5.function.php重命名为:Md5function.php
alipay_notify.class.php重命名为:Notify.php
alipay_submit.class.php重命名为:Submit.php
而后,打开Submit.php文件,把如下代码去掉
require_once("alipay_core.function.php"); require_once("alipay_rsa.function.php"); require_once("alipay_md5.function.php");
一样,打开Notify.php文件,把一下代码去掉
require_once("alipay_core.function.php"); require_once("alipay_rsa.function.php"); require_once("alipay_md5.function.php");
为何要去掉以上两个文件中的这段代码,由于在项目中调用接口文件的时候,我把全部4个核心文件都经过vendor来进行引入。因此,这再也不须要导入。
3、在项目中调用支付宝接口
调用分两步:
一、在配置文件中Conf/Config.php文件中对支付宝相关参数进行配置:
//支付宝配置参数 'alipay_config'=>array( ' partner' =>'20********50', //这里是你在成功申请支付宝接口后获取到的PID; 'key'=>'9t***********ie',//这里是你在成功申请支付宝接口后获取到的Key 'sign_type'=>strtoupper('MD5'), 'input_charset'=> strtolower('utf-8'), 'cacert'=> getcwd().'\\cacert.pem', 'transport'=> 'http', ), //以上配置项,是从接口包中alipay.config.php 文件中复制过来,进行配置; 'alipay' =>array( //这里是卖家的支付宝帐号,也就是你申请接口时注册的支付宝帐号 'seller_email'=>'pay@xxx.com', //这里是异步通知页面url,提交到项目的Pay控制器的notifyurl方法; 'notify_url'=>'http://www.xxx.com/Pay/notifyurl', //这里是页面跳转通知url,提交到项目的Pay控制器的returnurl方法; 'return_url'=>'http://www.xxx.com/Pay/returnurl', //支付成功跳转到的页面,我这里跳转到项目的User控制器,myorder方法,并传参payed(已支付列表) 'successpage'=>'User/myorder?ordtype=payed', //支付失败跳转到的页面,我这里跳转到项目的User控制器,myorder方法,并传参unpay(未支付列表) 'errorpage'=>'User/myorder?ordtype=unpay', ),
二、新建一个PayAction控制器代码以下:
<?php header("Content-Type:text/html;charset=utf-8"); class PayAction extends Action{ //在类初始化方法中,引入相关类库 public function _initialize() { vendor('Alipay.Corefunction'); vendor('Alipay.Md5function'); vendor('Alipay.Notify'); vendor('Alipay.Submit'); } //doalipay方法 /*该方法其实就是将接口文件包下alipayapi.php的内容复制过来 而后进行相关处理 */ public function doalipay(){ /********************************************************* 把alipayapi.php中复制过来的以下两段代码去掉, 第一段是引入配置项, 第二段是引入submit.class.php这个类。 为何要去掉?? 第一,配置项的内容已经在项目的Config.php文件中进行了配置,咱们只需用C函数进行调用便可; 第二,这里调用的submit.class.php类库咱们已经在PayAction的_initialize()中已经引入;因此这里再也不须要; *****************************************************/ // require_once("alipay.config.php"); // require_once("lib/alipay_submit.class.php"); //这里咱们经过TP的C函数把配置项参数读出,赋给$alipay_config; $alipay_config=C('alipay_config'); /**************************调用受权接口alipay.wap.trade.create.direct获取受权码token**************************/ //返回格式 $format = "xml"; //必填,不须要修改 //返回格式 $v = "2.0"; //必填,不须要修改 //请求号 $req_id = date('Ymdhis'); //必填,须保证每次请求都是惟一 //**req_data详细信息** //服务器异步通知页面路径 $notify_url = C('alipay.notify_url'); //需http://格式的完整路径,不容许加?id=123这类自定义参数 //页面跳转同步通知页面路径 $call_back_url = C('alipay.return_url'); //需http://格式的完整路径,不容许加?id=123这类自定义参数 //操做中断返回地址 $merchant_url = "http://www.***.com/User/index"; //用户付款中途退出返回商户的地址。需http://格式的完整路径,不容许加?id=123这类自定义参数 //卖家支付宝账户 //$seller_email = $_POST['WIDseller_email']; $seller_email = $_GET['WIDseller_email']; //必填 //商户订单号 //$out_trade_no = $_POST['WIDout_trade_no']; $out_trade_no = $_GET['WIDout_trade_no']; //商户网站订单系统中惟一订单号,必填 //订单名称 //$subject = $_POST['WIDsubject']; $subject = $_GET['WIDsubject']; //必填 //付款金额 //$total_fee = $_POST['WIDtotal_fee']; $total_fee = $_GET['WIDtotal_fee']; //请求业务参数详细 $req_data = '<direct_trade_create_req><notify_url>' . $notify_url . '</notify_url><call_back_url>' . $call_back_url . '</call_back_url><seller_account_name>' . $seller_email . '</seller_account_name><out_trade_no>' . $out_trade_no . '</out_trade_no><subject>' . $subject . '</subject><total_fee>' . $total_fee . '</total_fee><merchant_url>' . $merchant_url . '</merchant_url></direct_trade_create_req>'; //必填 //构造要请求的参数数组,无需改动 $para_token = array( "service" => "alipay.wap.trade.create.direct", "partner" => trim($alipay_config['partner']), "sec_id" => trim($alipay_config['sign_type']), "format" => $format, "v" => $v, "req_id" => $req_id, "req_data" => $req_data, "_input_charset" => trim(strtolower($alipay_config['input_charset'])) ); //创建请求 $alipaySubmit = new AlipaySubmit($alipay_config); $html_text = $alipaySubmit->buildRequestHttp($para_token); //URLDECODE返回的信息 $html_text = urldecode($html_text); //解析远程模拟提交后返回的信息 $para_html_text = $alipaySubmit->parseResponse($html_text); //获取request_token $request_token = $para_html_text['request_token']; /**************************根据受权码token调用交易接口alipay.wap.auth.authAndExecute**************************/ //业务详细 $req_data = '<auth_and_execute_req><request_token>' . $request_token . '</request_token></auth_and_execute_req>'; //必填 //构造要请求的参数数组,无需改动 $parameter = array( "service" => "alipay.wap.auth.authAndExecute", "partner" => trim($alipay_config['partner']), "sec_id" => trim($alipay_config['sign_type']), "format" => $format, "v" => $v, "req_id" => $req_id, "req_data" => $req_data, "_input_charset" => trim(strtolower($alipay_config['input_charset'])) ); //创建请求 $alipaySubmit = new AlipaySubmit($alipay_config); $html_text = $alipaySubmit->buildRequestForm($parameter, 'get', '确认'); echo $html_text; } /****************************** 服务器异步通知页面方法 其实这里就是将notify_url.php文件中的代码复制过来进行处理 *******************************/ function notifyurl(){ /* 同理去掉如下两句代码; */ //require_once("alipay.config.php"); //require_once("lib/alipay_notify.class.php"); //这里仍是经过C函数来读取配置项,赋值给$alipay_config $alipay_config=C('alipay_config'); //计算得出通知验证结果 $alipayNotify = new AlipayNotify($alipay_config); $verify_result = $alipayNotify->verifyNotify(); if($verify_result) { $notify_data = $_POST['notify_data']; //获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表 //解析notify_data //注意:该功能PHP5环境及以上支持,需开通curl、SSL等PHP配置环境。建议本地调试时使用PHP开发软件 $doc = new DOMDocument(); $doc->loadXML($notify_data); if( ! empty($doc->getElementsByTagName( "notify" )->item(0)->nodeValue) ) { //商户订单号 $out_trade_no = $doc->getElementsByTagName( "out_trade_no" )->item(0)->nodeValue; //支付宝交易号 $trade_no = $doc->getElementsByTagName( "trade_no" )->item(0)->nodeValue; //交易状态 $trade_status = $doc->getElementsByTagName( "trade_status" )->item(0)->nodeValue; if($trade_status == 'TRADE_FINISHED' || $trade_status == 'TRADE_SUCCESS') { if(!checkorderstatus($out_trade_no)){ orderhandle($out_trade_no); //进行订单处理,并传送从支付宝返回的参数; } $this->redirect(C('alipay.successpage'));//跳转到配置项中配置的支付成功页面; } else { echo "trade_status=".$trade_status; $this->redirect(C('alipay.errorpage'));//跳转到配置项中配置的支付失败页面; } echo "success"; //请不要修改或删除 } }else { //验证失败 echo "fail"; } } /* 页面跳转处理方法; 这里其实就是将return_url.php这个文件中的代码复制过来,进行处理; */ function returnurl(){ //头部的处理跟上面两个方法同样,这里不罗嗦了! $alipay_config=C('alipay_config'); $alipayNotify = new AlipayNotify($alipay_config);//计算得出通知验证结果 $verify_result = $alipayNotify->verifyReturn(); if($verify_result) { //验证成功 //获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表 //商户订单号 $out_trade_no = $_GET['out_trade_no']; //支付宝交易号 $trade_no = $_GET['trade_no']; //交易状态 $result = $_GET['result']; if($trade_status == 'TRADE_FINISHED' || $trade_status == 'TRADE_SUCCESS') { if(!checkorderstatus($out_trade_no)){ orderhandle($out_trade_no); //进行订单处理,并传送从支付宝返回的参数; } $this->redirect(C('alipay.successpage'));//跳转到配置项中配置的支付成功页面; }else { echo "trade_status=".$trade_status; $this->redirect(C('alipay.errorpage'));//跳转到配置项中配置的支付失败页面; } }else { //验证失败 //如要调试,请看alipay_notify.php页面的verifyReturn函数 echo "支付失败!"; } } } ?>
三、这里有几个支付处理过程当中须要用到的函数,我把这些函数写到了项目的Common/common.php中,这样不用手动调用,便可直接使用这些函数代码以下:
////////////////////////////////////////////////////// //Orderlist数据表,用于保存用户的购买订单记录; /* Orderlist数据表结构; CREATE TABLE `tb_item_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userid` int(11) DEFAULT NULL,购买者userid `username` varchar(255) DEFAULT NULL,购买者姓名 `orderid` varchar(255) DEFAULT NULL,订单号 `ordertime` int(11) DEFAULT NULL,订单时间 `support_time` int(11) DEFAULT NULL,支付时间 PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; */ //在线交易订单支付处理函数 //函数功能:根据支付接口传回的数据判断该订单是否已经支付成功; //返回值:若是订单已经成功支付,返回true,不然返回false; function checkorderstatus($ordid){ $item_order=M('item_order'); $ordstatus=$item_order->where('orderId='.$ordid)->getField('status'); if($ordstatus>=2){ return true; }else{ return false; } } //处理订单函数 //更新订单状态,写入订单支付后返回的数据 function orderhandle($ordid){ $data['support_time']=time(); $data['status'] =2; $item_order=M('item_order'); $item_order->where('orderId='.$ordid)->save($data); }
4、总结几点
接口包中lib文件中的文件复制到Vendor后,重命名为TP规范的命名规则,为的是调用方便,固然你要改为其余名称也能够;
二、把执行支付操做(doalipay),处理异步返回结果(notifyurl),处理跳转返回结果(returnurl)三个支付接口的核心页面写到一个PayAction控制器中。
三、提交支付的页面中,能够在提交以前先把一些参数要传递的内容先经过隐藏域的方法组合好,好比金额先计算好,订单名称,订单描述等先用字符串组合好。而后提交表单,这样,在doalipay方法中只要直接构造传递参数,直接进行提交就行过了。
四、支付返回后的处理由于要在异步和跳转两个方法中都要进行相应的判断和处理,因此,把这些判断和处理写到一个自定义函数中,这样只要调用函数便可,使得代码更加清晰明了。
五、notify_url和return_url两种模式的返回url必须使用http://xxxxxxx这样的绝对路径,由于里是从支付宝平台返回到你的项目页面。不能使用相对路径。
以上代码在ThinkPHP3.0中正常使用!!
--------------------解决签名错误问题 修正 13-08-16------------------------
有人说在在调试时,签名出现没法经过的问题,产生问题的缘由是在返回的URL地址中返回的参数中,可能存在__URL__这样的字符串。致使没法正确过滤参数。
解决办法:
方法1:
在向支付宝提交须要的参数时,不要使用__URL__,__PUBLIC__等TP中的模版替换变量,若是TP对这些变量解析不成功,会直接传递过去,因此,在这些地方直接使用原始的URL地址。
方法2:
在接口的Core文件中,加入改造后的过滤函数,以下:
/** * 除去数组中的空值和签名参数 * @param $para 签名参数组 * return 去掉空值与签名参数后的新签名参数组 */ function paraFilter($para) { $para_filter = array(); while (list ($key, $val) = each ($para)) { if($key == "sign" || $key == "sign_type" || $key == '_URL_' || $val == "")continue; //添加了$key == '_URL_' else $para_filter[$key] = $para[$key]; } return $para_filter; } function myparaFilter($para) { $para_filter = array(); while (list ($key, $val) = each ($para)) { if($key == '_URL_')continue; else $para_filter[$key] = $para[$key]; } return $para_filter; }
注:本文参考thinkphp官网的一篇文章修改而来,http://www.thinkphp.cn/code/240.html