<h2>名词解释:</h2>
商户:通过支付宝来销售商品的商家,即支付宝接口实现者。 客户:通过支付宝购买商品的商家,即支付宝接口使用者。 同步通知: 异步通知:
<h2>支付宝接口实现原理</h2>
官方的图片很形象的表达了支付宝接口的原理:
首先,商户网站根据用户的操作以及实际情况在本地构造请求数据,例如客户要付款的金额、在商户网站中的唯一订单号、产品名称等等,这些数据是要在本地确定下来,然后发送给支付宝系统的。
<h3>发送请求数据</h3>
商户将数据整理好后,通过支付宝提供的接口发送给支付宝系统服务器,事实上,关于整合数据的过程,支付宝已经提供了非常实用的demo,届时直接复制,然后根据实际需要再进行补充即可。
<h3>交易进行</h3>
这是支付宝系统内部的事情,就不多赘述了……
<h3>返回响应数据</h3>
我认为这里只支付宝接口实现中一个很关键的过程,支付宝将交易结果回传是通过两种方式的,一种叫做页面回转同步通知,另一种叫做服务器异步通知。 页面回转同步通知:简单来说就是将交易结果显示给客户看,在付款完成,支付宝系统现实交易完成后,页面会在几秒之后自动跳转到商户指定的一个网址去,而交易的结果(如支付宝交易号等)会以GET方式附加到网址里面,到时商户通过自行提取,然后显示在页面中,告知客户即可。 服务器异步通知:简单来说就是将更详细的交易结果以POST的方式直接发送给商户的服务器。 我个人认为这是页面回转同步通知是用来给客户看的,服务器异步通知是用来更新商户服务器的,最好不要根据页面回转同步通知的结果来更新数据库,一来异步通知的信息量更大,信息更完善,二来把“渲染信息为页面”与“将信息更新到数据库”两个功能分开也更符合模块化思想,欢迎指正。
<h3>对响应数据进行处理</h3>
就是根据支付宝系统返回的交易结果来更新数据库了,比如记录交易时间、订单号、交易金额等等了。
<h2>实战:在MVC模式下实现支付宝接口</h2>
就把我开发接口的经验跟教训分享给大家吧。 工作中使用的是Yii框架1.1版本,基于MVC模式。
<h3>支付宝demo文件夹</h3>
我们在支付宝官网可以下到即时到账收款的开发包:
<a href="http://download.alipay.com/public/api/base/alipaydirect.zip">http://download.alipay.com/public/api/base/alipaydirect.zip</a>
这里用PHP语言,UTF-8编码的demo包,可以看到结构如下: 这个demo包放到localhost中是可以直接用的,默认就是打开index.php文件作为入口。 下面介绍各个文件: Images:这是index.php的资源文件夹,存放图片等。 lib:这是库函数文件,不需要改动,只需要仿照demo引用到各个功能模块中就可以了。 alipay.config.php:这是支付宝接口的配置文件,在里面可以填写商户合作者ID与秘钥,其他内容一般不需要改动。 cacert.pem:这好像是用来验证链接的文件,就是验证return_url跟notify_url链接是不是由支付宝系统发出的…… index.php:支付宝接口入口文件,其实就是HTML表单了,将信息提交给alipayapi.php文件。 log.txt:这是日志文件,使用支付宝自带的logResult(str)函数写入内容,如果要调试notify_url的话,这个文件会非常有用!logResult()函数在alipay_core.function.php中! notify_url.php:服务器异步通知模板文件,对服务器异步通知信息的提取基本就是按里面的步骤来走的,在MVC中可以看做是个action。 readme.txt:T_T我怎么早没看到这个文件……关于每个文件的介绍这里面都有……关于cacert.pem和库函数的使用里面都有介绍,cacert.pem的要是还看不懂的话那就没办法了,因为我也不是很懂╮(╯_╰)╭ return_url:与notify_url.php相似,区别只是面向同步通知的。
<h3>MVC结构</h3><h4>Model</h4>
在数据表中的字段有:
<pre class="lang:php decode:1 ">/**
* 表 "bill_alipay_log" 模型.
*
@out_trade_no:商户唯一订单号,str
@notify_time:通知时间,datetime
@subject:商品名称,str
@payment_type:支付类型,str
@trade_no:支付宝交易号,str
@trade_status:交易状态,str
@gmt_create:交易创建时间,datetime
@gmt_payment:交易付款时间,datetime
@gmt_close:交易关闭时间,datetime
@buyer_id:买家支付宝用户号,str
@buyer_email:买家支付宝账号,str
@buyer_account_name:买家别名支付宝账号,str
@total_fee:交易金额,decimal
@price:商品单价,decimal
@quantity:购买数量,int
@body:商品描述,str
@discount:折扣,int
*/
</pre>
<h4>Controller</h4>
控制器下有四个action: actionIndex:用来渲染填写信息的表单页面。(index.php) actionPush:用来接收用户填写的表单,并将数据发送到支付宝服务器。 actionReturnUrl:用来接收同步通知,渲染给客户看的结果页面。(return_url.php) actionNotifyUrl:用来接收异步通知,更新数据库。
<h4>View</h4>
index.php(客户填写的表单) return_url.php(给客户看的交易结果)
<h3>原理</h3>
当需要交易时,首先调用actionIndex,来生成商户订单号、商品名称、商品简介、卖家支付宝账号等信息,然后显示给用户。比如在渲染给用户的表单中,我只让用户填写交易金额一项,其他的都是后台生成的。 表单的action路径指向actionPush,actionPush的内容基本上照搬alipayapi.php中的PHP代码,根据实际需要进行增删即可。其中$return_url 值为 actionReturnUrl路径,$notify_url值为actionNotifyUrl路径。 actionReturnUrl是接收支付宝传回的同步通知的,在交易完成后会调用这个方法(如果$return_url设置的对的话),基本照搬return_url.php中的代码,然后自行增删即可,记得将最后输出的结果渲染成页面哦! actionNotifyUrl是接收服务器异步通知的,照搬notify_url.php中的代码,然后根据需要增加要获取的数据,因为我发现交易状态只有SUCCESS没有FINISH,所以就删除了判断FINISH的语句,而是改成了如果交易状态为SUCCESS的话,就更新数据库。这个模块因为没法看到输出的信息,如果有bug的话很难改,一个办法是调用自带的logResult()方法,它能将信息写到log.txt文件中(如果文件内容没有更新的话就在服务器里搜索一下log.txt……很有可能在根目录下……或者在服务器根目录下创建一个log.txt……),这样调bug的话就比较方便了。 PS:虽然官方文档中说通知会返回很多可选参数,但是很多可选参数是根本不会返回的(比如payment_close似乎在直接交易中就不会有)!我在这里被坑了好久…… PS2:最好把官方的文档看一遍啊!
<h3>代码</h3>
<pre class="lang:php decode:1 ">public function actionIndex(){
/*生成订单填写页面,页面中填写充值金额,提交到actionPush*/
/*
out_trade_no
subject
body
*/
//生成商户订单号
if(Yii::app()->user->isGuest){
$this->redirect(LOGIN_PAGE);
}
$userid = Yii::app()->user->id;
$out_trade_no = date('Ymd') . date( "His") . sprintf("%05d",$userid) ;
//生成产品名称
$subject = "快发充值";
//生成产品描述
$body = "这是快发充值产品";
$model = array(
"out_trade_no" => $out_trade_no,
"subject" => $subject,
"body" => $body,
);
$this->render("index", array('model' => $model));
}
</pre>
<pre class="lang:php decode:1 ">public function actionPush(){
/*将用户提交的信息及其他信息仿照alipayapi推送到服务器,在数据库中创建记录*/
require_once("alipay.config.php");
require_once("lib/alipay_submit.class.php");
/**************************请求参数**************************/
//支付类型
$payment_type = "1";
//必填,不能修改
//服务器异步通知页面路径
$notify_url = "http://www.kuaifa.tv/aliPay/notifyUrl/";
//需http://格式的完整路径,不能加?id=123这类自定义参数
//页面跳转同步通知页面路径
$return_url = "http://www.kuaifa.tv/aliPay/returnUrl/";
//需http://格式的完整路径,不能加?id=123这类自定义参数,不能写成http://localhost/
//卖家支付宝帐户
//$seller_email = $_POST['WIDseller_email'];
$seller_email = "kuaifa@kuaifa.tv";
//必填
//商户订单号
$out_trade_no = $_POST['WIDout_trade_no'];
//商户网站订单系统中唯一订单号,必填
//订单名称
$subject = $_POST['WIDsubject'];
//必填
//付款金额
$total_fee = $_POST['WIDtotal_fee'];
//必填
//订单描述
$body = $_POST['WIDbody'];
//商品展示地址
//$show_url = $_POST['WIDshow_url'];
$show_url = "http://www.baidu.com";
//需以http://开头的完整路径,例如:http://www.商户网址.com/myorder.html
//防钓鱼时间戳
$anti_phishing_key = "";
//若要使用请调用类文件submit中的query_timestamp函数
//客户端的IP地址
$exter_invoke_ip = "";
//非局域网的外网IP地址,如:221.0.0.1
//在数据库中创建记录
$model = new AlipayLog;
$model->payment_type = $payment_type;
$model->out_trade_no = $out_trade_no;
$model->subject = $subject;
$model->total_fee = $total_fee;
$model->body = $body;
$model->userid = Yii::app()->user->id;
$model->insert();
/************************************************************/
//构造要请求的参数数组,无需改动
$parameter = array(
"service" => "create_direct_pay_by_user",//接口名称
"partner" => trim($alipay_config['partner']),//合作者id
"payment_type" => $payment_type,//
"notify_url" => $notify_url,
"return_url" => $return_url,//支付结束自动跳转页面http://api.test.alipay.net/atinterface/receive_return.htm
"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", "确认");
echo $html_text;
}
</pre>
<pre class="lang:php decode:1 ">public function actionReturnUrl(){
/*根据支付宝的同步传输返回参数生成给用户看的网页,不涉及数据库
*return:$out_trade_no, $trade_no, $result
*
*/
require_once("alipay.config.php");
require_once("lib/alipay_notify.class.php");
//计算得出通知验证结果
$alipayNotify = new AlipayNotify($alipay_config);
$verify_result = $alipayNotify->verifyReturn();
if($verify_result) {//验证成功
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代码
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表
//商户订单号
$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') {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
$result = "支付成功!";
}
else {
$result = "失败!" . "trade_status=".$_GET['trade_status'];
}
$model = array(
"out_trade_no" => $out_trade_no,
"trade_no" => $trade_no,
"result" => $result,
);
$this->render('return_url', array('model' => $model));
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
else {
//验证失败
//如要调试,请看alipay_notify.php页面的verifyReturn函数
$this->render('fail');
}
}
</pre>
<pre class="lang:php decode:1 ">public function actionNotifyUrl(){
/* *
* 功能:支付宝服务器异步通知页面
* 版本:3.3
* 日期:2012-07-23
* 说明:
* 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
* 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
*************************页面功能说明*************************
* 创建该页面文件时,请留心该页面文件中无任何HTML代码及空格。
* 该页面不能在本机电脑测试,请到服务器上做测试。请确保外部可以访问该页面。
* 该页面调试工具请使用写文本函数logResult,该函数已被默认关闭,见alipay_notify_class.php中的函数verifyNotify
* 如果没有收到该页面返回的 success 信息,支付宝会在24小时内按一定的时间策略重发通知
*/
require_once("alipay.config.php");
require_once("lib/alipay_notify.class.php");
//计算得出通知验证结果
$alipayNotify = new AlipayNotify($alipay_config);
$verify_result = $alipayNotify->verifyNotify();
if($verify_result) {//验证成功
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
//商户订单号
if(!empty($_POST['out_trade_no'])){
$out_trade_no = $_POST['out_trade_no'];
}
//支付宝交易号
if(!empty($_POST['trade_no'])){
$trade_no = $_POST['trade_no'];
}
//交易状态
if(!empty($_POST['trade_status'])){
$trade_status = $_POST['trade_status'];
}
//通知时间
if(!empty($_POST['notify_time'])){
$notify_time = $_POST['notify_time'];
}
//交易创建时间
if(!empty($_POST['gmt_create'])){
$gmt_create = $_POST['gmt_create'];
}
//交易付款时间
if(!empty($_POST['gmt_payment'])){
$gmt_payment = $_POST['gmt_payment'];
}
//交易关闭时间
if(!empty($_POST['gmt_close'])){
$gmt_close = $_POST['gmt_close'];
}
//买家支付宝用户号
if(!empty($_POST['buyer_id'])){
$buyer_id = $_POST['buyer_id'];
}
//买家支付宝账号
if(!empty($_POST['buyer_email'])){
$buyer_email = $_POST['buyer_email'];
}
//买家支付宝别名
if(!empty($_POST['buyer_account_name'])){
$buyer_account_name = $_POST['buyer_account_name'];
}
//交易金额
if(!empty($_POST['total_fee'])){
$total_fee = $_POST['total_fee'];
}
//单价
if(!empty($_POST['price'])){
$total_fee = $_POST['price'];
}
//交易数量
if(!empty($_POST['number'])){
$total_fee = $_POST['number'];
}
//折扣
if(!empty($_POST['discount'])){
$discount = $_POST['discount'];
}
/*logResult("我验证通过了!rn");
logResult("out_trade_no:" . $out_trade_no . "rn");
logResult("trade_no" . $trade_no . "rn");
logResult("trade_status" . $trade_status . "rn");
logResult("notify_time" . $notify_time . "rn");
logResult("gmt_create" . $gmt_create . "rn");
logResult("gmt_payment" . $gmt_payment . "rn");
logResult("buyer_id" . $buyer_id . "rn");
logResult("buyer_email" . $buyer_email . "rn");
logResult("total_fee" . $total_fee . "rn");*/
if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//该种交易状态只在一种情况下出现——开通了高级即时到账,买家付款成功后。
//调试用,写文本函数记录程序运行情况是否正常
//logResult("这里写入想要调试的代码变量值,或其他运行的结果记录");
AlipayLog::model()->updateByPk($out_trade_no,array('trade_no' => $trade_no, 'trade_status' => $trade_status, 'notify_time' => $notify_time, 'gmt_create' => $gmt_create,'gmt_payment' => $gmt_payment,'total_fee' => $total_fee,));
if(!empty($buyer_account_name)){
AlipayLog::model()->updateByPk($out_trade_no,array('buyer_account_name' => $buyer_account_name));
}
if(!empty($buyer_email)){
AlipayLog::model()->updateByPk($out_trade_no,array('buyer_email' => $buyer_email));
}
if(!empty($buyer_id)){
AlipayLog::model()->updateByPk($out_trade_no,array('buyer_id' => $buyer_id));
}
if(!empty($gmt_close)){
AlipayLog::model()->updateByPk($out_trade_no,array('gmt_close' => $gmt_close));
}
if(!empty($discount)){
AlipayLog::model()->updateByPk($out_trade_no,array('discount' => $discount));
}
if(!empty($price)){
AlipayLog::model()->updateByPk($out_trade_no,array('price' => $price));
}
if(!empty($number)){
AlipayLog::model()->updateByPk($out_trade_no,array('number' => $number));
}
//logResult("我在success里!n");
}
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
echo "success"; //请不要修改或删除
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
else {
//验证失败
echo "fail";
//调试用,写文本函数记录程序运行情况是否正常
}
}//action
</pre>
本文档介绍了在PHP环境下,基于MVC结构如何实现支付宝即时到账接口。通过下载支付宝提供的PHP开发包,详细解析了各个文件的作用,如配置文件、库函数、日志文件以及服务器异步通知和同步通知模板。在MVC模式下,这些组件如何协同工作以确保交易接口的正常运行。

971

被折叠的 条评论
为什么被折叠?



