PHP下基于MVC结构实现支付宝交易接口

本文档介绍了在PHP环境下,基于MVC结构如何实现支付宝即时到账接口。通过下载支付宝提供的PHP开发包,详细解析了各个文件的作用,如配置文件、库函数、日志文件以及服务器异步通知和同步通知模板。在MVC模式下,这些组件如何协同工作以确保交易接口的正常运行。
最近因为工作需要学习使用了支付宝的“即时到账交易接口”,在学习应用的过程中走了很多的弯路,也感觉到官方说明文档中有些许不足 ,这里一并总结下来。
<h2>名词解释:</h2>
商户:通过支付宝来销售商品的商家,即支付宝接口实现者。 客户:通过支付宝购买商品的商家,即支付宝接口使用者。 同步通知: 异步通知:
<h2>支付宝接口实现原理</h2>

官方的图片很形象的表达了支付宝接口的原理: 

alipay

<h3>商户构建请求数据</h3>
首先,商户网站根据用户的操作以及实际情况在本地构造请求数据,例如客户要付款的金额、在商户网站中的唯一订单号、产品名称等等,这些数据是要在本地确定下来,然后发送给支付宝系统的。
<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>
&nbsp;
<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()-&gt;user-&gt;isGuest){
$this-&gt;redirect(LOGIN_PAGE);
}
$userid = Yii::app()-&gt;user-&gt;id;
$out_trade_no = date('Ymd') .  date( "His") . sprintf("%05d",$userid) ;
//生成产品名称
$subject = "快发充值";
//生成产品描述
$body = "这是快发充值产品";
$model = array(
"out_trade_no" =&gt; $out_trade_no,
"subject" =&gt; $subject,
"body" =&gt; $body,
);
$this-&gt;render("index", array('model' =&gt; $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-&gt;payment_type = $payment_type;
$model-&gt;out_trade_no = $out_trade_no;
$model-&gt;subject = $subject;
$model-&gt;total_fee = $total_fee;
$model-&gt;body = $body;
$model-&gt;userid = Yii::app()-&gt;user-&gt;id;
$model-&gt;insert();

/************************************************************/


//构造要请求的参数数组,无需改动
$parameter = array(
"service" =&gt; "create_direct_pay_by_user",//接口名称
"partner" =&gt; trim($alipay_config['partner']),//合作者id
"payment_type" =&gt; $payment_type,//
"notify_url" =&gt; $notify_url,
"return_url" =&gt; $return_url,//支付结束自动跳转页面http://api.test.alipay.net/atinterface/receive_return.htm 
"seller_email" =&gt; $seller_email,
"out_trade_no" =&gt; $out_trade_no,
"subject" =&gt; $subject,
"total_fee" =&gt; $total_fee,
"body" =&gt; $body,
"show_url" =&gt; $show_url,
"anti_phishing_key" =&gt; $anti_phishing_key,
"exter_invoke_ip" =&gt; $exter_invoke_ip,
"_input_charset" =&gt; trim(strtolower($alipay_config['input_charset']))//参数编码字符集
);
//建立请求
$alipaySubmit = new AlipaySubmit($alipay_config);
$html_text = $alipaySubmit-&gt;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-&gt;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" =&gt; $out_trade_no,
"trade_no" =&gt; $trade_no,
"result" =&gt; $result,
);
$this-&gt;render('return_url', array('model' =&gt; $model));
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
else {
//验证失败
//如要调试,请看alipay_notify.php页面的verifyReturn函数
$this-&gt;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-&gt;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()-&gt;updateByPk($out_trade_no,array('trade_no' =&gt; $trade_no, 'trade_status' =&gt; $trade_status, 'notify_time' =&gt; $notify_time, 'gmt_create' =&gt; $gmt_create,'gmt_payment' =&gt; $gmt_payment,'total_fee' =&gt; $total_fee,));
if(!empty($buyer_account_name)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('buyer_account_name' =&gt; $buyer_account_name));
}
if(!empty($buyer_email)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('buyer_email' =&gt; $buyer_email));
}
if(!empty($buyer_id)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('buyer_id' =&gt; $buyer_id));
}
if(!empty($gmt_close)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('gmt_close' =&gt; $gmt_close));
}
if(!empty($discount)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('discount' =&gt; $discount));
}
if(!empty($price)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('price' =&gt; $price));
}
if(!empty($number)){
AlipayLog::model()-&gt;updateByPk($out_trade_no,array('number' =&gt; $number));
}
//logResult("我在success里!n");
    }


//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
echo "success"; //请不要修改或删除

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
else {
    //验证失败
    echo "fail";


    //调试用,写文本函数记录程序运行情况是否正常
}


}//action
</pre>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值