支付宝支付总结(框架集成篇)

    由于目前所在的公司是以CI框架为主的项目开发,所以博主对CI的了解程度比其它框架要好一点。之前有篇文章简要的介绍了使用支付宝支付的示例demo,这篇文章就主要说一下支付宝在CI框架上的代码集成。

框架版本

CodeIgniter 2.0

要点

新建配置项

    默认情况下,CI中已经有一个配置文件,位于application/config目录下的config.php文件。文件内容是一个$config数组:

<?php
$config['base_url']     = '';
$config['index_page']   = '';
$config['uri_protocol']    = 'AUTO';
$config['url_suffix']   = '';
......

    自定义一个配置项的话,就要在application/config目录下建立一个文件,但是文件内容也要和config.php里的内容相仿,配置项也要放在$config的数组中,比如我新建一个支付宝的配置项alipay.php
alipay.php

    内容如下:

<?php
// 合作身份者ID,签约账号,
$config['partner']           = '';
// 收款支付宝账号 == 签约账号
$config['seller_id']         = $config['partner'];
// MD5密钥 安全检验码 查看地址:https://b.alipay.com/order/pidAndKey.htm
$config['key']               = '';
// 服务器异步通知页面路径
$config['notify_url']        = base_url()."sz_front/alipay/asyn_notify";
// 页面跳转同步通知页面路径
$config['return_url']        = base_url()."sz_front/alipay/sync_return";
//签名方式
$config['sign_type']         = strtoupper('MD5');
//字符编码格式 目前支持 gbk 或 utf-8
$config['input_charset']     = strtolower('utf-8');
//ca证书路径地址,用于curl中ssl校验
$config['cacert']            = getcwd().'\\application\\libraries\Payment\\Alipaylib\cacert.pem';
//访问模式
$config['transport']         = 'http';
// 支付类型 
$config['payment_type']      = "1";
// 产品类型(支付)
$config['service']           = "create_direct_pay_by_user";
// 产品类型(退款)
$config['refund_service']    = 'refund_fastpay_by_platform_pwd';
// 调试日志文件路径
$config['log_path']          = base_url().'application/logs/alipay/alipay-'.date('Y-m-d').'.log';

//↓↓↓↓↓↓↓↓↓↓ 配置防钓鱼信息↓↓↓↓↓↓↓↓↓↓↓

// 防钓鱼时间戳  若要使用请调用类文件submit中的query_timestamp函数
$config['anti_phishing_key'] = "";

// 客户端的IP地址 非局域网的外网IP地址,如:221.0.0.1
$config['exter_invoke_ip']   = "";

加载自定义配置项

手动加载

CI中调用自定义配置项也很简单,如下:

$this->config->load('alipay', TRUE);
$this->alipay_config = $this->config->item('alipay');

$this->config->load(filename, bool, bool )调用配置文件,有三个参数:

  1. filename 自定义配置项的文件名,比如我这里文件名是alipay.php,第一个参数写alipay就可以了;

  2. bool 这里可以填写bool类型的值,如果为TRUE的话,可以使每个配置项单独保存在以文件名为索引的数组中,在同时调用多个配置项的时候,这样做可以避免冲突;

  3. bool 如果为TRUE的话,可以屏蔽当配置文件不存在时产生的错误信息

$this->config->item(filename)从配置文件中检索元素,里面填写配置文件名。

自动加载

    自动加载,故名思议就是在程序初始化的时候就加载,类似于TP3以下的所有扩展类库一股脑全加载,不过在CI中是可以定义自动加载内容的,这个文件就在application/config目录下的autoload.php文件,打开找到:
自动加载config

array里添加alipay即可:

$autoload['config'] = array('alipay');

加载类库

    CI中的加载类库也很方便,直接就哼哼嘿哈哈就好了(智障.jpg),当然是开玩笑,加载类库方法如下:

$this->load->library( '' );

调用类库中方法的方式有两种:

  1. $this->class_name->action(); $this->类名->方法名;

  2. $class_obj = new class_name(); 这种就是php实例化类的方法,方便使用构造函数进行类初始化。

正(大)文(招)

    其实也没啥正文,就是代码部分(哈哈哈哈~张全蛋的笑声.mp3),代码中的注释我认为已经写的很清楚了,就不分割开一一叙述了:

<?php
header("content-type:text/html;charset=utf-8");
/**
 * 支付
 * @version 1.0
 * @package application
 * @subpackage application/controllers/alipay/
 */
class Alipay extends Front_Controller {

    private $alipay_config;
    public function __construct() {
        parent::__construct();
        $this->load->model('user_model');
        $this->load->model('order_info_model');
        $this->load->model('order_goods_model');
        
        $this->_site_path = "sz_front";
        //加载alipay配置文件
        $this->config->load('alipay', TRUE);
        $this->alipay_config = $this->config->item('alipay');
    }

    /**
     * 跳转到支付宝支付页面
     * @param  int $order_id 订单ID
     */
    public function index($order_id) {

        $field = array('user_id', 'order_sn', 'pay_status', 'pay_fee');
        $order_info = $this->order_info_model->get_appoint_values($field, array('order_id'=>$order_id));

        if(empty( $order_id ) || ( empty( $order_info ) && !is_array( $order_info ) ) ) {
            $this->error_js_noref('无此订单信息');exit;
        }
        //查询此订单是不是登陆者订单
        $user_id = $this->session->userdata('userid');
        if($order_info[0]['user_id'] != $user_id) $this->error_js_noref('订单数据错误');

        $goods_name = $this->order_goods_model->get_value_by_notpk('goods_name', array('order_id' => $order_id));
        $pay_info = array(
            'trade_no'   => $order_info[0]['order_sn'],//订单名称,必填
            'goods_name' => $goods_name, //商品名称
            'total_fee'  => $order_info[0]['pay_fee'],//付款金额,必填
            'remark'     => $goods_name,//商品描述,可空
            );
        //调用支付宝支付接口
        $result = $this->alipay_submit($pay_info);


        if (is_bool($result) && ! $result) {
            $this->message_js('订单信息不存在!', base_url('sz_front/myorder/orders'));
        }
        if (is_bool($result) && $result == true) {
            $this->error_js_noref('订单已支付,请不要重复支付!', base_url('sz_front/myorder/orders'));
        }
        echo $result;
    }

    /**
     * 支付宝支付参数整合并调用支付页面
     * @param  array $pay_info 商品信息
     * @return html            支付宝页面
     * 
     */
    public function alipay_submit($pay_info) {
        //整合支付参数
        $parameter = array(
            "service"           => $this->alipay_config['service'],
            "partner"           => $this->alipay_config['partner'],
            "seller_id"         => $this->alipay_config['seller_id'],
            "payment_type"      => $this->alipay_config['payment_type'],
            "notify_url"        => $this->alipay_config['notify_url'],
            "return_url"        => $this->alipay_config['return_url'],
            "anti_phishing_key" => $this->alipay_config['anti_phishing_key'],
            "exter_invoke_ip"   => $this->alipay_config['exter_invoke_ip'],
            "out_trade_no"      => $pay_info['trade_no'],
            "subject"           => $pay_info['goods_name'],
            "total_fee"         => $pay_info['total_fee'],
            "body"              => $pay_info['remark'],
            "_input_charset"    => trim(strtolower($this->alipay_config['input_charset']))
        );
        //调用支付接口类
        $this->load->library( 'Payment/Alipaylib/AlipaySubmit' );

        //实例化支付接口类并对构造函数传参
        $AlipaySubmit = new AlipaySubmit($this->alipay_config);

        //调用获取支付页面方法
        $html_text = $AlipaySubmit->buildRequestForm($parameter, "post", "确认");

        //返回支付页面
        return $html_text;
    }

    /**
    * asynNotify 异步通知
    * @access public
    * @param void
    * @return void
    */
    public function asyn_notify(){

        //调用支付接口类
        $this->load->library( 'Payment/Alipaylib/AlipayNotify' );
        // require_once APPPATH.'libraries/Payment/Alipaylib/alipay_notify.class.php';
        $alipayNotify = new AlipayNotify($this->alipay_config);
        $verify_result = $alipayNotify->verifyNotify();

        if($verify_result) {//验证成功
            
            //商户订单号
            $out_trade_no = $this->input->post('out_trade_no');
            
            //支付宝交易号
            $trade_no     = $this->input->post('trade_no');
            
            //交易状态
            $trade_status = $this->input->post('trade_status');
            $gmt_payment  = $this->input->post('gmt_payment');

            if($this->input->post('trade_status') == 'TRADE_FINISHED') {
                //判断该笔订单是否在商户网站中已经做过处理
                    //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                    //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
                    //如果有做过处理,不执行商户的业务程序
                        
                //注意:
                //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知

                //调试用,写文本函数记录程序运行情况是否正常
                $date = date('Y-m-d H:i:s', time());
                $str = "订单号:".$out_trade_no.'于'.$gmt_payment.'付款'.$trade_status.'\n';
                logResult($date.$str);
            }
            else if ($this->input->post('trade_status') == 'TRADE_SUCCESS') {

                $this->update_order_info($out_trade_no, 'asyn');

                //判断该笔订单是否在商户网站中已经做过处理
                    //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                    //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
                    //如果有做过处理,不执行商户的业务程序
                        
                //注意:
                //付款完成后,支付宝系统发送该交易状态通知

                //调试用,写文本函数记录程序运行情况是否正常
                //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录");
            }

            //——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
                
            echo "success";     //请不要修改或删除
        }
        else {
            //验证失败
            echo "fail";
            //调试用,写文本函数记录程序运行情况是否正常
            //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录");
        }
        exit;
        $notify_data = $this->input->post(null, true);
        $verify_result = $this->verify($notify_data);
        if ($verify_result) {    //验证成功
            //交易状态
            $trade_status = $notify_data['trade_status'];
            // 根据交易状态处理订单支付状态
            if ($trade_status == 'TRADE_FINISHED' || $trade_status == 'TRADE_SUCCESS') {
                $this->updateOrderPay($notify_data);
            }
            echo 'success';     //请不要修改或删除

        } else {    //验证失败
            echo 'fail';    //请不要修改或删除
        }
    }

    /**
     * syncReturn 页面跳转同步通知
     * @access public
     * @param void
     * @return void
     */
    public function sync_return(){
        // require_once APPPATH.'libraries/Payment/Alipaylib/alipay_notify.class.php';
        $this->load->library( 'Payment/Alipaylib/AlipayNotify' );
        $alipayNotify = new AlipayNotify($this->alipay_config);
        $verify_result = $alipayNotify->verifyReturn();
        if($verify_result) {
            //商户订单号
            $out_trade_no = $this->input->get('out_trade_no', TRUE);
            //支付宝交易号
            $trade_no     = $this->input->get('trade_no', TRUE);
            //交易状态
            $trade_status = $this->input->get('trade_status', TRUE);
            echo $trade_status;
            if($_GET['trade_status'] == 'TRADE_FINISHED' || $_GET['trade_status'] == 'TRADE_SUCCESS') {
                $date = date('Y-m-d H:i:s', time());
                $str = "订单号:".$out_trade_no.'于'.$gmt_payment.'付款'.$trade_status.'\n';
                echo $date.$str;
                $this->update_order_info($out_trade_no, 'sync');
                $this->message_js('支付成功!', base_url('sz_front/myorder/orders/ship'));
            } else {
                $this->message_js('支付失败!', base_url('sz_front/myorder/orders/ship'));
            }
        }else {
            $this->message_js('支付失败!', base_url('sz_front/myorder/orders/ship'));
            
            
        }
    }

    /**
     * 更新订单信息页面
     * @param  string $order_sn 订单编号
     * @param  string $callback 同步、异步
     * @author 命中水、 
     * @date(2016-11-26 am)
     */
    public function update_order_info($order_sn, $callback) {
        $search_field = array('order_id', 'user_id', 'order_status', 'pay_fee', 'pay_status');
        $order_info = $this->order_info_model->get_appoint_values($search_field, array('order_sn' => $order_sn));
        //如果未支付
        if($order_info[0]['pay_status'] != '2') {
            $my_sellid  = $this->alipay_config['seller_id'];
            $my_payfee = $order_info[0]['pay_fee'];

            if( $callback == 'sync' ) { //同步页面
                $back_sellid = $this->input->get('seller_id', TRUE);
                $back_payfee = $this->input->get('total_fee', TRUE);
                $pay_time    = $this->input->get('notify_time', TRUE);
                $pay_info_data = $this->get_request_data('GET', $order_info[0]['order_id']);
            }
            if( $callback == 'asyn') { //异步页面
                $back_sellid = $this->input->post('seller_id', TRUE);
                $back_payfee = $this->input->post('total_fee', TRUE);
                $pay_time    = $this->input->post('gmt_payment', TRUE);
                $pay_info_data = $this->get_request_data('POST', $order_info[0]['order_id']);
            }
            //验证收款支付宝账户是否与配置项一致
            if( $my_sellid != $back_sellid ) {

            }
            //验证支付订单金额是否一致  这里是单 订单支付验证
            //如果一次性提交
            if ( $my_payfee != $back_payfee ) {

            }
            $order_update = array(
                'pay_id' => 1,
                'pay_name' => '支付宝',
                'pay_status' => 2,
                'pay_time' => $pay_time,
                );

            $update_flag = $this->order_info_model->update_by_attributes($order_update, array('order_sn' => $order_sn));
            $this->load->model( 'pay_info_model' );
            
            $this->pay_info_model->insert($pay_info_data);
        }
    }

    /**
     * 整理插入付款详情表的数据
     * @param  string $request  请求方式
     * @param  int $order_id    订单ID
     * @return array           
     * @author 命中水、 
     * @date(2016-10-24)
     */
    public function get_request_data( $request, $order_id) {
        if( $request == 'POST' ) {
            $request = $this->input->post();
        }
        if( $request == 'GET' ) {
            $request = $this->input->get();
        }
        return $request_data = array(
            'order_id'      => $order_id,
            'exterface'     => $request['exterface'],
            'trade_no'      => $request['trade_no'],
            'trede_status'  => strtolower($request['trade_status']),
            'notify_time'   => strtotime($request['notify_time']),
            'notify_type'   => $request['notify_type'],
            'buyer_email'   => $request['buyer_email'],
            'buyer_id'      => $request['buyer_id'],
            'seller_email'  => $request['seller_email'],
            'seller_id'     => $request['seller_id'],
            'total_fee'     => $request['total_fee'],
            'gmt_payment'   => $request['gmt_payment'],
            'refund_status' => $request['refund_status'],
            'gmt_refund'    => $request['gmt_refund'],
            );
    }

}

支付宝类

    由于支付宝已经封装好了接口类库,我们稍微改变下,拿过来调用就好了,如何调用在上个段落中的代码中已放出,接下来就是支付宝类的存放位置了,放在application\libraries下就好了,目录结构如下(application\libraries\Payment\Alipaylib):
支付宝类库目录

Payment是为了以后方便接入其它的支付类库,比如微信、网银什么的,Alipaylib就是专门存放支付宝的类库文件夹了,这里呢改了支付类和回调类的文件名字,为了文件名和类名统一,CI调用的时候不会出错。

总结

  1. CI中调用类的时候,类名和文件名一定要统一,不然会调用不成功;

  2. 文章中中部的那一大段代码我是放在了支付controller层,有兴趣的小伙伴可以封装一下放在类库(libraries)里也可以。

附录资源

  1. CI2.0用户手册

  2. 支付宝支付总结(demo篇)

添加新评论