最近做系統(tǒng),需要實現(xiàn)在線支付功能,毫不猶豫,選擇的是支付寶的接口支付功能。這里我用的是即時到帳的接口,具體實現(xiàn)的步驟如下:
一、下載支付寶接口包
下載地址:
https://b.alipay.com/order/productDetail.htm?productId=2012111200373124&tabId=4#ps-tabinfo-hash
具體如何下載,我就不在羅嗦了~~
很多人反映,用支付寶的接口到最后面會出現(xiàn)驗證錯誤。其實,這里需要對接口程序進行一下改造。需要添加幾個自定義函數(shù)。為了讓大家以后避免出現(xiàn)同樣的問題,我把我改造好的支付寶接口程序上傳了(Alipay)。大家可以下載下來,解壓后放到框架的Vendor目錄中即可~
二、重新整理接口包文件,這一步應該算是比較關鍵的(個人認為)
下載下來的接口包文件有很多語言的源碼,
我們選擇 create_direct_pay_by_user-PHP-UTF-8 這個名稱的接口文件。里面包括如下文件:
images文件里是支付寶相關的一些標志的圖片,我們暫不管他,lib文件很重要,是整個接口的核心類文件;
alipay.config.php是相關參數(shù)的配置文件
alipayapi.php 是支付寶接口入口文件
notify_url.php 是服務器異步通知頁面文件;
return_url.php 是頁面跳轉(zhuǎn)同步通知文件;
在ThinkPHP的框架文件下,找到Extend 進入,再進入Vendor,在Vendor文件夾下,新建文件夾Alipay,把支付寶作為第三方類庫引入。然后,復制支付寶接口文件包中l(wèi)ib文件里的所有文件。一共4個文件,如下:
現(xiàn)在對以上文件進行重命名,
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_md5.function.php");
同樣,打開Notify.php文件,把以下兩段代碼去掉
require_once("alipay_core.function.php");
require_once("alipay_md5.function.php");
為什么要去掉以上兩個文件中的這兩段代碼,因為在項目中調(diào)用接口文件的時候,我把所有4個核心文件都通過vendor來進行引入。所以,這不再需要導入。
到此,支付寶接口包相關核心類庫的整理基本完成。現(xiàn)在開始在項目中調(diào)用;
三、在項目中調(diào)用支付寶接口
調(diào)用分兩步:
1、在配置文件中Conf/Config.php文件中對支付寶相關參數(shù)進行配置:
//支付寶配置參數(shù)
'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',
//這里是頁面跳轉(zhuǎn)通知url,提交到項目的Pay控制器的returnurl方法;
'return_url'=>'http://www.xxx.com/Pay/returnurl',
//支付成功跳轉(zhuǎn)到的頁面,我這里跳轉(zhuǎn)到項目的User控制器,myorder方法,并傳參payed(已支付列表)
'successpage'=>'User/myorder?ordtype=payed',
//支付失敗跳轉(zhuǎn)到的頁面,我這里跳轉(zhuǎn)到項目的User控制器,myorder方法,并傳參unpay(未支付列表)
'errorpage'=>'User/myorder?ordtype=unpay',
),
2、新建一個PayAction控制器代碼如下:
"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=newAlipaySubmit($alipay_config);
$html_text=$alipaySubmit->buildRequestForm($parameter,"post","確認");
echo $html_text;
}
/******************************
服務器異步通知頁面方法
其實這里就是將notify_url.php文件中的代碼復制過來進行處理
*******************************/
functionnotifyurl(){
/*
同理去掉以下兩句代碼;
*/
//require_once("alipay.config.php");
//require_once("lib/alipay_notify.class.php");
//這里還是通過C函數(shù)來讀取配置項,賦值給$alipay_config
$alipay_config=C('alipay_config');
//計算得出通知驗證結(jié)果
$alipayNotify=newAlipayNotify($alipay_config);
$verify_result=$alipayNotify->verifyNotify();
if($verify_result){
//驗證成功
//獲取支付寶的通知返回參數(shù),可參考技術文檔中服務器異步通知參數(shù)列表
$out_trade_no=$_POST['out_trade_no'];//商戶訂單號
$trade_no=$_POST['trade_no'];//支付寶交易號
$trade_status=$_POST['trade_status'];//交易狀態(tài)
$total_fee=$_POST['total_fee'];//交易金額
$notify_id=$_POST['notify_id'];//通知校驗ID。
$notify_time=$_POST['notify_time'];//通知的發(fā)送時間。格式為yyyy-MM-dd HH:mm:ss。
$buyer_email=$_POST['buyer_email'];//買家支付寶帳號;
$parameter=array(
"out_trade_no"=>$out_trade_no,//商戶訂單編號;
"trade_no"=>$trade_no,//支付寶交易號;
"total_fee"=>$total_fee,//交易金額;
"trade_status"=>$trade_status,//交易狀態(tài)
"notify_id"=>$notify_id,//通知校驗ID。
"notify_time"=>$notify_time,//通知的發(fā)送時間。
"buyer_email"=>$buyer_email,//買家支付寶帳號;
);
if($_POST['trade_status']=='TRADE_FINISHED'){
//
}elseif($_POST['trade_status']=='TRADE_SUCCESS'){if(!checkorderstatus($out_trade_no)){
orderhandle($parameter);
//進行訂單處理,并傳送從支付寶返回的參數(shù);
}
}
echo"success";//請不要修改或刪除
}else{
//驗證失敗
echo"fail";
}
}
/*
頁面跳轉(zhuǎn)處理方法;
這里其實就是將return_url.php這個文件中的代碼復制過來,進行處理;
*/
functionreturnurl(){
//頭部的處理跟上面兩個方法一樣,這里不羅嗦了!
$alipay_config=C('alipay_config');
$alipayNotify=newAlipayNotify($alipay_config);//計算得出通知驗證結(jié)果
$verify_result=$alipayNotify->verifyReturn();
if($verify_result){
//驗證成功
//獲取支付寶的通知返回參數(shù),可參考技術文檔中頁面跳轉(zhuǎn)同步通知參數(shù)列表
$out_trade_no=$_GET['out_trade_no'];//商戶訂單號
$trade_no=$_GET['trade_no'];//支付寶交易號
$trade_status=$_GET['trade_status'];//交易狀態(tài)
$total_fee=$_GET['total_fee'];//交易金額
$notify_id=$_GET['notify_id'];//通知校驗ID。
$notify_time=$_GET['notify_time'];//通知的發(fā)送時間。
$buyer_email=$_GET['buyer_email'];//買家支付寶帳號;
$parameter=array(
"out_trade_no"=>$out_trade_no,//商戶訂單編號;
"trade_no"=>$trade_no,//支付寶交易號;
"total_fee"=>$total_fee,//交易金額;
"trade_status"=>$trade_status,//交易狀態(tài)
"notify_id"=>$notify_id,//通知校驗ID。
"notify_time"=>$notify_time,//通知的發(fā)送時間。
"buyer_email"=>$buyer_email,//買家支付寶帳號
);
if($_GET['trade_status']=='TRADE_FINISHED'||$_GET['trade_status']=='TRADE_SUCCESS'){
if(!checkorderstatus($out_trade_no)){
orderhandle($parameter);//進行訂單處理,并傳送從支付寶返回的參數(shù);
}
$this->redirect(C('alipay.successpage'));//跳轉(zhuǎn)到配置項中配置的支付成功頁面;
}else{
echo"trade_status=".$_GET['trade_status'];
$this->redirect(C('alipay.errorpage'));//跳轉(zhuǎn)到配置項中配置的支付失敗頁面;
}
}else{
//驗證失敗
//如要調(diào)試,請看alipay_notify.php頁面的verifyReturn函數(shù)
echo"支付失敗!";
}
}
}
?>
3、這里有幾個支付處理過程中需要用到的函數(shù),我把這些函數(shù)寫到了項目的Common/common.php中,這樣不用手動調(diào)用,即可直接使用這些函數(shù),代碼如下:
//////////////////////////////////////////////////////
//Orderlist數(shù)據(jù)表,用于保存用戶的購買訂單記錄;
/* Orderlist數(shù)據(jù)表結(jié)構(gòu);
CREATE TABLE `tb_orderlist` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) DEFAULT NULL,購買者userid
`username` varchar(255) DEFAULT NULL,購買者姓名
`ordid` varchar(255) DEFAULT NULL,訂單號
`ordtime` int(11) DEFAULT NULL,訂單時間
`productid` int(11) DEFAULT NULL,產(chǎn)品ID
`ordtitle` varchar(255) DEFAULT NULL,訂單標題
`ordbuynum` int(11) DEFAULT '0',購買數(shù)量
`ordprice` float(10,2) DEFAULT '0.00',產(chǎn)品單價
`ordfee` float(10,2) DEFAULT '0.00',訂單總金額
`ordstatus` int(11) DEFAULT '0',訂單狀態(tài)
`payment_type` varchar(255) DEFAULT NULL,支付類型
`payment_trade_no` varchar(255) DEFAULT NULL,支付接口交易號
`payment_trade_status` varchar(255) DEFAULT NULL,支付接口返回的交易狀態(tài)
`payment_notify_id` varchar(255) DEFAULT NULL,
`payment_notify_time` varchar(255) DEFAULT NULL,
`payment_buyer_email` varchar(255) DEFAULT NULL,
`ordcode` varchar(255) DEFAULT NULL, //這個字段不需要的,大家看我西面的修正補充部分的說明!
`isused` int(11) DEFAULT '0',
`usetime` int(11) DEFAULT NULL,
`checkuser` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
*/
//在線交易訂單支付處理函數(shù)
//函數(shù)功能:根據(jù)支付接口傳回的數(shù)據(jù)判斷該訂單是否已經(jīng)支付成功;
//返回值:如果訂單已經(jīng)成功支付,返回true,否則返回false;
functioncheckorderstatus($ordid){
$Ord=M('Orderlist');
$ordstatus=$Ord->where('ordid='.$ordid)->getField('ordstatus');
if($ordstatus==1){
returntrue;
}else{
returnfalse;
}
}
//處理訂單函數(shù)
//更新訂單狀態(tài),寫入訂單支付后返回的數(shù)據(jù)
functionorderhandle($parameter){
$ordid=$parameter['out_trade_no'];
$data['payment_trade_no']=$parameter['trade_no'];
$data['payment_trade_status']=$parameter['trade_status'];
$data['payment_notify_id']=$parameter['notify_id'];
$data['payment_notify_time']=$parameter['notify_time'];
$data['payment_buyer_email']=$parameter['buyer_email'];
$data['ordstatus']=1;
$Ord=M('Orderlist');
$Ord->where('ordid='.$ordid)->save($data);
}
/*-----------------------------------
2013.8.13更正
下面這個函數(shù),其實不需要,大家可以把他刪掉,
具體看我下面的修正補充部分的說明
------------------------------------*/
//獲取一個隨機且唯一的訂單號;
functiongetordcode(){
$Ord=M('Orderlist');
$numbers=range(10,99);
shuffle($numbers);
$code=array_slice($numbers,0,4);
$ordcode=$code[0].$code[1].$code[2].$code[3];
$oldcode=$Ord->where("ordcode='".$ordcode."'")->getField('ordcode');
if($oldcode){
getordcode();
}else{
return$ordcode;
}
}
四、總結(jié)幾點
1、接口包中l(wèi)ib文件中的文件復制到Vendor后,重命名為TP規(guī)范的命名規(guī)則,為的是調(diào)用方便,當然你要改成其他名稱也可以;
2、把執(zhí)行支付操作(doalipay),處理異步返回結(jié)果(notifyurl),處理跳轉(zhuǎn)返回結(jié)果(returnurl)三個支付接口的核心頁面寫到一個PayAction控制器中。
3、提交支付的頁面中,可以在提交之前先把一些參數(shù)要傳遞的內(nèi)容先通過隱藏域的方法組合好,比如金額先計算好,訂單名稱,訂單描述等先用字符串組合好。然后提交表單,這樣,在doalipay方法中只要直接構(gòu)造傳遞參數(shù),直接進行提交就行過了。
4、支付返回后的處理因為要在異步和跳轉(zhuǎn)兩個方法中都要進行相應的判斷和處理,所以,把這些判斷和處理寫到一個自定義函數(shù)中,這樣只要調(diào)用函數(shù)即可,使得代碼更加清晰明了。
5、notify_url和return_url兩種模式的返回url必須使用http://xxxxxxx這樣的絕對路徑,因為里是從支付寶平臺返回到你的項目頁面。不能使用相對路徑。
以上代碼在ThinkPHP3.0中正常使用!!
————————修正補充!!2013.08.13——————————
在第三部分中Orderlist數(shù)據(jù)表結(jié)構(gòu)中,我有一個字段是OrdCode,這個字段是我系統(tǒng)中用來發(fā)送短信給客戶的消費密碼,也就是客戶憑手機短信來消費時就要驗證這個字段。
其實,大家在做系統(tǒng)的時候,可以把這個字段忽略,可以不用他。代碼最后部分中,有一個獲取一個隨機且唯一的訂單號的函數(shù) getordcode(),這里我其實寫錯了,不是獲取訂單號,是ordcode,也就是消費密碼,這個函數(shù)也不需要。系統(tǒng)中的訂單號(ordid字段),我用的是時間戳。
在此修正!
——————–解決簽名錯誤問題 修正 13-08-16————————
有人說在在調(diào)試時,簽名出現(xiàn)無法通過的問題,產(chǎn)生問題的原因是在返回的URL地址中返回的參數(shù)中,可能存在__URL__這樣的字符串。導致無法正確過濾參數(shù)。
解決辦法:
方法1:
在向支付寶提交需要的參數(shù)時,不要使用__URL__,__PUBLIC__等TP中的模版替換變量,如果TP對這些變量解析不成功,會直接傳遞過去,所以,在這些地方直接使用原始的URL地址。
方法2:
在接口的Core文件中,加入改造后的過濾函數(shù),如下:
/**
* 除去數(shù)組中的空值和簽名參數(shù)
* @param $para 簽名參數(shù)組
* return 去掉空值與簽名參數(shù)后的新簽名參數(shù)組
*/
functionparaFilter($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;
}
functionmyparaFilter($para){
$para_filter=array();
while(list($key,$val)=each($para)){
if($key=='_URL_')continue;
else$para_filter[$key]=$para[$key];
}
return$para_filter;
}