最近做微商城,需要实现手机wap支付功能,选择的是支付宝的接口支付功能。这里是我用的是支付宝“手机网站支付”产品(注:该产品要支付宝企业账号才能申请),具体步骤如下:
一、下载支付宝接口包
https://b.alipay.com/order/productDetail.htm?productId=2013080604609688
二、重新整理接口包文件
下载下来的接口包文件有很多语言的源码
注:openssl用来生成公私钥,RSA签名才使用。这里我们用的MD5签名,所有该文件用不到。
我们选择WS_WAP_PAYWAP-PHP-UTF-8这个名称的接口文件。
它包含如下文件:
images文件里是支付宝相关的一些标志的图片,key文件保存RSA签名的公私钥文件,openssl文件也是RSA签名才用到的,所有这三个文件暂时不管他。lib文件很重要,是整个接口的核心文件;
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来进行引入。所以,这不再需要导入。
三、在项目中调用支付宝接口
调用分两步:
1、在配置文件中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‘, ),
2、新建一个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 "支付失败!"; } } } ?>
3、这里有几个支付处理过程中需要用到的函数,我把这些函数写到了项目的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); }
四、总结几点
接口包中lib文件中的文件复制到Vendor后,重命名为TP规范的命名规则,为的是调用方便,当然你要改成其他名称也可以;
2、把执行支付操作(doalipay),处理异步返回结果(notifyurl),处理跳转返回结果(returnurl)三个支付接口的核心页面写到一个PayAction控制器中。
3、提交支付的页面中,可以在提交之前先把一些参数要传递的内容先通过隐藏域的方法组合好,比如金额先计算好,订单名称,订单描述等先用字符串组合好。然后提交表单,这样,在doalipay方法中只要直接构造传递参数,直接进行提交就行过了。
4、支付返回后的处理因为要在异步和跳转两个方法中都要进行相应的判断和处理,所以,把这些判断和处理写到一个自定义函数中,这样只要调用函数即可,使得代码更加清晰明了。
5、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