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