THINHPHP
THINHPHP结合支付宝接口总结:
1.采用THINKPHP3.1.3版本
2.从支付宝官网下载php版本的demo,把utf-8(我用的是utf-8的编码)解压到THINKPHP框架的项目的Lib目录下的Extend目录下,删除不必要的文件,并且把证书文件拷贝到网站的根目录下(不要考虑到安全问题,因为这个证书可以在官网直接下载,也不用修改任何代码,没有必要去下载你根目录下面的证书文件,在官网可以随意下载)。准备工作已经全部完成,直接看我的募界结构:
3在ACTION下面建立AlipayAction.class.php
源码:
<?php
/**
* 支付宝接口
* @author Administrator
*
*/
class AlipayapiAction extends CommonAction{
private $_alipay_config=array();//支付宝配置
private $_userid;//登录的userid
private $_username;//登录的username
private $_mycount='[email protected]';//支付宝签约帐号
publicfunction __construct(){
parent::__construct();
/**start******加载支付宝接口类***********/
require_once './Home/Lib/Extend/Alipayapi/lib/alipay_submit.class.php';
require_once './Home/Lib/Extend/Alipayapi/lib/alipay_core.function.php';
require_once './Home/Lib/Extend/Alipayapi/lib/alipay_notify.class.php';
require_once './Home/Lib/Extend/Alipayapi/lib/alipay_md5.function.php';
/**end********加载支付宝接口类***********/
self::setalipay_config();//设置支付宝基本配置
}
/**
*设置支付宝
*/
privatefunction setalipay_config(){
/* *
* 配置文件
* 版本:3.3
* 日期:2012-07-19
* 说明:
* 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
* 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
* 提示:如何获取安全校验码和合作身份者id
* 1.用您的签约支付宝账号登录支付宝网站(www.alipay.com)
* 2.点击“商家服务”(https://b.alipay.com/order/myorder.htm)
* 3.点击“查询合作者身份(pid)”、“查询安全校验码(key)”
* 安全校验码查看时,输入支付密码后,页面呈灰色的现象,怎么办?
* 解决方法:
* 1、检查浏览器配置,不让浏览器做弹框屏蔽设置
* 2、更换浏览器或电脑,重新登录查询。
*/
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
$alipay_config['partner']= '20880XXXXX071';//合作身份者id,以2088开头的16位纯数字
$alipay_config['key']= '8XXXXXXs8ywrwxxxxxxck65xxxxhct';//安全检验码,以数字和字母组成的32位字符
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
$alipay_config['sign_type']= strtoupper('MD5');//签名方式不需修改
$alipay_config['input_charset']=strtolower('utf-8');//字符编码格式目前支持 gbk 或 utf-8
//ca证书路径地址,用于curl中ssl校验
//请保证cacert.pem文件在当前文件夹目录中
$alipay_config['cacert']= getcwd().'\\cacert.pem';
//访问模式,根据自己的服务器是否支持ssl访问,若支持请选择https;若不支持请选择http
$alipay_config['transport']= 'http';
$this->_alipay_config=$alipay_config;//设置支付宝基本配置
}
/**
* 得到支付宝配置
*/
private function getalipay_config(){
return $this->_alipay_config;
}
public function index(){
A('Public')->checklogin();
$this->_userid=$_SESSION['userid'];
$this->_username=$_SESSION['username'];
$alipay_config=self::getalipay_config();//得到支付宝基本配置
/**************************请求参数**************************/
$payment_type = "1";//支付类型,必填,不能修改,(官方文档这么说的)
//服务器异步通知页面路径
//$notify_url ="http://www.xxx.com/create_direct_pay_by_user-PHP-UTF-8/notify_url.php";
$notify_url = "http://www.xxxxx.cn/alipayapi/notifyurl";
//需http://格式的完整路径,不能加?id=123这类自定义参数
//页面跳转同步通知页面路径
//$return_url = "http://www.xxx.com/create_direct_pay_by_user-PHP-UTF-8/return_url.php";
$return_url = "http://www.xxxx.cn/alipayapi/returnurl";
//需http://格式的完整路径,不能加?id=123这类自定义参数,不能写成http://localhost/
//卖家支付宝帐户
//$seller_email =$_POST['WIDseller_email'];
$seller_email = $this->_mycount;
//必填
//商户订单号
//$out_trade_no =$_POST['WIDout_trade_no'];
$order_no=date('YmdHis').rand(1000,9999);
$out_trade_no = $order_no;
//商户网站订单系统中唯一订单号,必填
/********将订单号与用户ID写入订单表以便支付宝返回中查找*********/
$ulast_id=M('Usercheckout')->add(array('trade_no'=>$out_trade_no,'user_id'=>$this->_userid));
if (false===$ulast_id){
echo'<script>alert("生成订单失败!请重新填写!");history.back();</script>';
exit();
}
/********将订单号与用户ID写入订单表*********/
//$subject = $_POST['WIDsubject'];
$subject = '充值';//订单名称,必填(官方文档这么说的)
$total_fee = $_POST['WIDtotal_fee'];//付款金额,必填(官方文档这么说的)
//订单描述
//$body = $_POST['WIDbody'];
$remark='会员 '.getUserName($_SESSION['userid']).'在'.date('Y-m-dH:i:s').'充值'.$_POST['WIDtotal_fee'].'元';
$body =$remark;
//$show_url = $_POST['WIDshow_url'];
$show_url='http://www.xxxxx.cn/';//商品展示地址(我的设置了也不会跳转到这个页面不知道为什么)
//需以http://开头的完整路径,例如:http://www.xxx.com/myorder.html
//防钓鱼时间戳
$anti_phishing_key = "";
//若要使用请调用类文件submit中的query_timestamp函数
//客户端的IP地址
//$exter_invoke_ip = "";
$exter_invoke_ip = get_client_ip();
//非局域网的外网IP地址,如:221.0.0.1
/************************************************************/
//构造要请求的参数数组,无需改动
$parameter = array(
"service"=> "create_direct_pay_by_user",
"partner"=> trim($alipay_config['partner']),
"payment_type"=> $payment_type,
"notify_url"=> $notify_url,
"return_url"=> $return_url,
"seller_email"=> $seller_email,
"out_trade_no"=> $out_trade_no,
"subject"=> $subject,
"total_fee"=> $total_fee,
"body"=> $body,
"show_url"=> $show_url,
"anti_phishing_key"=> $anti_phishing_key,
"exter_invoke_ip"=> $exter_invoke_ip,
"_input_charset"=> trim(strtolower($alipay_config['input_charset']))
);
//建立请求
$alipaySubmit = new AlipaySubmit($alipay_config);
$html_text =$alipaySubmit->buildRequestForm($parameter,"get", "确认");//会返回javascript模拟表单提交html页面
//$this->assign('html_text',$html_text);
$this->display();
echo $html_text;//必须输出否则不会跳转
}
/**
* 支付宝异步请求
*/
/* *官方说明
* 功能:支付宝服务器异步通知页面
* 版本:3.3
* 日期:2012-07-23
* 说明:
* 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
* 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
*************************页面功能说明*************************
* 创建该页面文件时,请留心该页面文件中无任何HTML代码及空格。
* 该页面不能在本机电脑测试,请到服务器上做测试。请确保外部可以访问该页面。
* 该页面调试工具请使用写文本函数logResult,该函数已被默认关闭,见alipay_notify_class.php中的函数verifyNotify
* 如果没有收到该页面返回的 success 信息,支付宝会在24小时内按一定的时间策略重发通知
*/
public function notifyurl(){
$alipay_config=self::getalipay_config();//得到支付宝基本配置
//计算得出通知验证结果
$alipayNotify = new AlipayNotify($alipay_config);
$verify_result =$alipayNotify->verifyNotify();
if($verify_result) {//验证成功
//获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
//商户订单号
$out_trade_no = $_POST['out_trade_no'];
//支付宝交易号
$trade_no = $_POST['trade_no'];
//交易状态
$trade_status = $_POST['trade_status'];
//请在这里加上商户的业务逻辑程序代
$alipaylog=M("Alipaylog");//详细的支付宝支付记录表
$checkoutlog=M("Checkoutlog");//简单的支付记录表
$user=M('User');
$usercheckout=M('Usercheckout');//订单号与user_id的中间表,用于查找user_id
$alipaylog->startTrans();
if ($_POST['out_trade_no']){
$is_out_trade_no=$alipaylog->where('out_trade_no='.$_POST['out_trade_no'])->count();
$ucinfo=$usercheckout->where('trade_no='.$_POST['out_trade_no'])->find();
}
$data3=array(
'user_id'=>$ucinfo['user_id'],
'gmt_create'=>strtotime($_POST['gmt_create']),
'gmt_payment'=>strtotime($_POST['gmt_payment']),
'gmt_close'=>strtotime($_POST['gmt_close']),
'gmt_refund'=>strtotime($_POST['gmt_refund'])
);
unset($_POST['gmt_create']);
unset($_POST['gmt_payment']);
unset($_POST['gmt_close']);
unset($_POST['gmt_refund']);
$data2=array_merge($_POST,$data3);
if (!$is_out_trade_no){
$alist_id=$alipaylog->add($data2);
}
$clist_id=$checkoutlog->add($data2);
if ($ucinfo){
$userid=$ucinfo['user_id'];
$price=$user->where('id='.$userid)->getField('price');
$ulist_id=$user->where('id='.$userid)->setField('price',(float)$price+(float)$_POST['total_fee']);
}
//――判断业务逻辑――//
if ((false!==$alist_id)&&(false!==$clist_id)&&(false!==$ulist_id)){
if($_POST['trade_status']== 'TRADE_FINISHED') {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
$alipaylog->commit();
//注意:
//该种交易状态只在两种情况下出现
//1、开通了普通即时到账,买家付款成功后。
//2、开通了高级即时到账,从该笔交易成功时间算起,过了签约时的可退款时限(如:三个月以内可退款、一年以内可退款等)后。
//调试用,写文本函数记录程序运行情况是否正常
//logResult("这里写入想要调试的代码变量值,或其他运行的结果记录");
}else if ($_POST['trade_status']== 'TRADE_SUCCESS') {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
$alipaylog->commit();
//注意:
//该种交易状态只在一种情况下出现――开通了高级即时到账,买家付款成功后。
//调试用,写文本函数记录程序运行情况是否正常
//logResult("这里写入想要调试的代码变量值,或其他运行的结果记录");
}
echo "success";//请不要修改或删除
}else{
$alipaylog->rollback();
echo "fail";
}
}else {
//验证失败
echo "fail";
//调试用,写文本函数记录程序运行情况是否正常
//logResult("这里写入想要调试的代码变量值,或其他运行的结果记录");
}
}
/**
* 支付宝同步请求
*/
public function returnurl(){
$alipay_config=self::getalipay_config();
//计算得出通知验证结果
$alipayNotify = new AlipayNotify($alipay_config);
//$verify_result =$alipayNotify->verifyReturn();
//if($verify_result) {//验证成功
//请在这里加上商户的业务逻辑程序代码
//――请根据您的业务逻辑来编写程序(以下代码仅作参考)――
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表
//thinkphp的配置url模式为兼容模式可以用$_GET来获取?的值,实在不行可以获取到url完成路径,用?拆分。
//商户订单号
$out_trade_no= $_GET['out_trade_no'];
//支付宝交易号
$trade_no = $_GET['trade_no'];
//交易状态
$trade_status = $_GET['trade_status'];
if($_GET['trade_status']== 'TRADE_FINISHED' || $_GET['trade_status'] == 'TRADE_SUCCESS') {
//判断该笔订单是否在商户网站中已经做过处理
echo '<script>alert("支付成功!!!");location.href="'.__APP__.'/member/myaccount/";</script>';
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
}
else {
echo "trade_status=".$_GET['trade_status'];
}
//echo "验证成功<br/>";
//――请根据您的业务逻辑来编写程序(以上代码仅作参考)――
//}else {
//验证失败
//如要调试,请看alipay_notify.php页面的verifyReturn函数
//echo "验证失败";
//}
$this->display();
}
}
3.基本就是把官方的demo中的同步与异步页面移植到AlipayAction中的方法里面,连注释都没有更改,代码不再做详细解释,会php的基本都能理解,如果看不懂注释,我解释你也还是不懂。亲测成功,而且也不会因为淘宝的重复发送异步而累加,因为我有用到了订单号的判断,这个订单号存在就不再执行业务逻辑,而且也用到了事务,不会单方面更改单个数据。
我用了2个晚上才最终调试成功,开始的时候总是支付宝钱扣款成功,但是我的业务逻辑没有成功,于是详细看了一个demo的源代码,以及看了看官方的视频教程(和没看一样)和网上查看了一些资料,详细看了一下支付宝的接口验证流程,和大家分享一下,大神们不要吐槽,这只是我个人的理解。
支付宝流程:
在支付宝的异步通知中notifyurl()方法中调用了new AlipayNotify($alipay_config);这个类的这个$alipayNotify->verifyNotify();方法他是支付宝接口自身的严重呢个方法,验证是否为支付宝发送来的信息,发送的验证ID以及是否成功等。
异步调用流程以及验证详细:
4.经验心得:
a.ca证书的路径问题,刚开始的时候失败了几次,于是一步一步echo发现找不到证书,支付宝接口默认使用getcwd()方法,这个方法是找到物理磁盘的路径,即项目的根目录。
b.确保服务器开启curl扩展,异步通知的时候要用到,并且保证能够通过防火墙许可发送证书。
c.同步通知是页面的跳转,不能用session,这个页面只是显示给用户看的,我没有在这个页面里写业务逻辑(因为值这个页面可能会出现用户直接关闭等操作,导致无法完成业务逻辑,而异步通知只要支付宝服务器没有接收到success就会在24小时内分时间段不停的发送返回结果),所以也没有验证时候为淘宝服务器发送的,因为我根本就没有在这个方法里写任何业务逻辑代码,只是根据返回的finished做页面的跳转。
d.在异步通知中SESSION和COOKIE不能使用,你可以想象一下,这个页面是支付宝服务器模拟post数据向你的服务器发送的请求,能用SESSION才怪呢。而且你也不用担心THINKPHP的url模式和路由规则,支付宝是以POST方式发送给你服务器的。这时候你会遇到一个问题,就是你无法确定是你网站的哪个会用在提交支付信息。我的解决方案是在用户提交之前建立一个中间表,字段有user_id和out_trade_no(订单ID)其中out_trade_no为唯一索引,根据订单号去找user_id,另外在处理业务逻辑的时候最好在检查一次这个订单号是否已经处理了,防止重复执行业务逻辑。
本文出自 “浪子神的智慧笔记” 博客,谢绝转载!