美文网首页
支付宝即时到账Alipay.class.php

支付宝即时到账Alipay.class.php

作者: 三天大愚 | 来源:发表于2016-07-21 11:39 被阅读269次

最近由于公司项目需要接入支付,正好研究下支付宝接口的使用。我呢使用的是支付宝即时到账接口,通过对支付宝提供的接口DEMO的研究,决定将其进行封装整合,于是就有了这个啦!当然目前这个自定义支付宝支付类还不太完整,缺少退款方法,不过支付是没有问题的,测试也通过了的。

未完成版 Alipay.class.php
<?php
/**
 * Alipay 自定义支付宝即时到账类
 * 主要是将支付宝即时到账DEMO中的方法整合成了一个文件
 * @version 1.0 2016-07-06
 */
class Alipay {

    // 支付宝网关地址(新)
    CONST ALIPAY_GATEWAY_NEW = 'https://mapi.alipay.com/gateway.do?';
    // HTTPS形式消息验证地址
    CONST HTTPS_VERIFY_URL = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
    // HTTP形式消息验证地址
    CONST HTTP_VERIFY_URL = 'http://notify.alipay.com/trade/notify_query.do?';


    // 支付宝配置数组
    // partner       合作身份者ID 签约账号
    // seller_id     同partner
    // key           MD5密钥 安全检验码
    // notify_url    服务器异步通知页面路径
    // return_url    页面跳转同步通知页面路径
    // sign_type     签名方式
    // input_charset 字符编码格式
    // cacert        ca证书路径地址 用于curl中ssl校验
    // transport     访问模式
    // payment_type  支付类型
    // service       产品类型
    // log_path      日志路径
    // anti_phishing_key 防钓鱼时间戳
    // exter_invoke_ip 客户端的IP地址
    private $_config = array();




    /**
     * 构造函数 初始化支付宝配置数组
     * @param array $config
     */
    public function __construct($config = array()){
        
        $this->_config = $config;
        
    }

    /**
     * 同构造函数
     */
    public function Alipay($config = array()){
        
        $this->__construct($config);
        
    }


    /**
     * buildRequestForm
     * 建立请求,以表单HTML形式构造(默认)
     * @access public
     * @param $para_temp 请求参数数组
     * @param $method 提交方式。两个值可选:post、get
     * @param $button_name 确认按钮显示文字
     * @return 提交表单HTML文本
     */
    public function buildRequestForm($parameter, $method, $button_name) {
        $para_temp = array(
            'service'           => $this->_config['pay_service'],
            'partner'           => $this->_config['partner'],
            'seller_id'         => $this->_config['seller_id'],
            'payment_type'      => $this->_config['payment_type'],
            'notify_url'        => $this->_config['notify_url'],
            'return_url'        => $this->_config['return_url'],
            'anti_phishing_key' => $this->_config['anti_phishing_key'],
            'exter_invoke_ip'   => $this->_config['exter_invoke_ip'],
            '_input_charset'    => trim(strtolower($this->_config['input_charset']))
        );

        // 待请求参数数组
        $para = $this->buildRequestPara(array_merge($para_temp, $parameter));
        
        $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".self::ALIPAY_GATEWAY_NEW."_input_charset=".trim(strtolower($this->_config['input_charset']))."' method='".$method."'>";

        foreach ($para as $k => $v) {
            $sHtml.= "<input type='hidden' name='".$k."' value='".$v."'/>";
        }

        // submit按钮控件请不要含有name属性
        $sHtml = $sHtml."<input type='submit' value='".$button_name."' style='display:none;'></form>";
        
        $sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>";
        
        return $sHtml;
    }

    /**
     * refund 退款
     * @access public
     * @param array $para 请求参数数组
     * @return void
     */
    public function refund(){
        // 构造要请求的参数数组 无需改动
        $parameter = array(
            'service'           => trim($alipay_config['refund_service']),
            'partner'           => trim($alipay_config['partner']),
            'notify_url'        => trim($alipay_config['refund_notify_url']),
            'seller_user_id'    => trim($alipay_config['partner']),
            'refund_date'       => date('Y-m-d H:i:s', time()),
            'batch_no'          => $batch_no,
            'batch_num'         => $batch_num,
            'detail_data'       => $detail_data,
            '_input_charset'    => trim(strtolower($alipay_config['input_charset']))
                
        );
        // 待更新
        // 待更新
        // 待更新
        // 待更新
    }

    /**
     * buildRequestPara
     * 生成要请求给支付宝的参数数组
     * @access public
     * @param $para_temp 请求前的参数数组
     * @return 要请求的参数数组
     */
    public function buildRequestPara($para_temp) {
        // 除去待签名参数数组中的空值和签名参数
        $para_filter = $this->paraFilter($para_temp);

        // 对待签名参数数组排序
        $para_sort = $this->argSort($para_filter);

        // 生成签名结果
        $mysign = $this->buildRequestMysign($para_sort);
        
        //签名结果与签名方式加入请求提交参数组中
        $para_sort['sign'] = $mysign;
        $para_sort['sign_type'] = strtoupper(trim($this->_config['sign_type']));
        
        return $para_sort;
    }

    /**
     * buildRequestMysign 生成签名结果
     * @access public
     * @param $para_sort 已排序要签名的数组
     * return 签名结果字符串
     */
    public function buildRequestMysign($para_sort) {
        // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->createLinkString($para_sort);
        
        $mysign = '';
        switch (strtoupper(trim($this->_config['sign_type']))) {
            case 'MD5' :
                $mysign = $this->md5Sign($prestr);
                break;
            default :
                $mysign = '';
        }
        
        return $mysign;
    }

    /**
     * verify 验证
     * @access public
     * @param array $data
     * @return bool
     */
    public function verify($data = array()){
        if (empty($data)) { // 判断数组是否为空
            return false;
        } else {
            // 生成签名结果
            $isSign = $this->getSignVerify($data, $data['sign']);

            // 获取支付宝远程服务器ATN结果 验证是否是支付宝发来的消息
            $responseTxt = 'false';
            if (isset($data['notify_id'])) {
                $responseTxt = $this->getResponse($data['notify_id']);
            }

            if (preg_match("/true$/i", $responseTxt) && $isSign) {
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * getSignVerify
     * 获取返回时的签名验证结果
     * @access public
     * @param array $para_temp 通知返回来的参数数组
     * @param string $sign 返回的签名结果
     * @return bool 签名验证结果
     */
    public function getSignVerify($para_temp, $sign) {
        // 除去待签名参数数组中的空值和签名参数
        $para_filter = $this->paraFilter($para_temp);

        // 对待签名参数数组排序
        $para_sort = $this->argSort($para_filter);
        
        // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->createLinkString($para_sort);
        
        $isSgin = false;
        switch (strtoupper(trim($this->_config['sign_type']))) {
            case 'MD5' :
                $isSgin = $this->md5Sign($prestr) == $sign;
                break;
            default :
                $isSgin = false;
        }
        
        return $isSgin;
    }

    /**
     * getResponse
     * 获取远程服务器ATN结果,验证返回URL
     * @access public
     * @param $notify_id 通知校验ID
     * @return 服务器ATN结果
     * 验证结果集:
     * invalid 命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 
     * true 返回正确信息
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    public function getResponse($notify_id) {
        $transport = strtolower(trim($this->_config['transport']));
        $partner = trim($this->_config['partner']);
        $verify_url = '';
        if ($transport == 'https') {
            $verify_url = self::HTTPS_VERIFY_URL;
        } else {
            $verify_url = self::HTTP_VERIFY_URL;
        }
        $verify_url = $verify_url.'partner='.$partner.'&notify_id='.$notify_id;
        $responseTxt = $this->getHttpResponseGET($verify_url);
        
        return $responseTxt;
    }

    /**
     * createLinkString
     * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @access public
     * @param array $para 需要拼接的数组
     * @return 拼接完成以后的字符串
     */
    public function createLinkString($para = array()){
        $arg  = '';
        while (list ($key, $val) = each ($para)) {
            $arg .= $key . '=' . $val . '&';
        }
        // 去掉最后一个&字符
        // $arg = substr($arg, 0, count($arg) - 2);
        $arg = trim($arg, '&');
        
        // 如果存在转义字符,那么去掉转义
        if (get_magic_quotes_gpc()) {
            $arg = stripslashes($arg);
        }
        
        return $arg;
    }

    /**
     * paraFilter
     * 除去数组中的空值和签名参数
     * @access public
     * @param array $para 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public function paraFilter($para = array()) {
        // 去除空值及签名参数
        foreach ($para as $k => $v) {
            if ($k == 'sign' || $k == 'sign_type' || $v == '') {
                unset($para[$k]);
            }
        }

        return $para;
    }

    /**
     * argSort 对数组排序
     * @access public
     * @param array $para 排序前的数组
     * @return array 排序后的数组
     */
    public function argSort($para = array()) {
        ksort($para);
        reset($para);
        return $para;
    }

    /**
     * md5Sign 签名字符串
     * @access public
     * @param string $prestr 需要签名的字符串
     * @return string 签名结果
     */
    public function md5Sign($prestr){
        $prestr = $prestr . $this->_config['key'];
        return md5($prestr);
    }

    /**
     * logDebug
     * 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
     * 注意:服务器需要开通fopen配置
     * @access public
     * @param string $content 要写入日志里的文本内容 默认值:空值
     * @return void
     */
    public function logDebug($content = '', $level = 'debug') {
        if (strtolower($level) == 'error') $level = '失败';
        if (strtolower($level) == 'success') $level = '成功';
        if (strtolower($level) == 'debug') $level = '调试';
        $fp = fopen($this->_config['log_path'], 'a+');
        flock($fp, LOCK_EX);    // 文件锁定 避免其他人同时操作
        fwrite($fp, '#AlipayDebug# '.date('Y-m-d H:i:s')." {$level} ~ ".$content . "\n\n");
        flock($fp, LOCK_UN);
        fclose($fp);
    }

    /**
     * queryTimeStamp
     * @access public
     * 用于防钓鱼,调用接口query_timestamp来获取时间戳的处理函数
     * 注意:该功能PHP5环境及以上支持,因此必须服务器、本地电脑中装有支持DOMDocument、SSL的PHP配置环境。建议本地调试时使用PHP开发软件
     * @return string 时间戳字符串
     */
    public function queryTimeStamp() {
        $url = self::ALIPAY_GATEWAY_NEW.'service=query_timestamp&partner='.trim(strtolower($this->_config['partner'])).'&_input_charset='.trim(strtolower($this->_config['input_charset']));
        $encrypt_key = '';

        $doc = new DOMDocument();
        $doc->load($url);
        $itemEncrypt_key = $doc->getElementsByTagName( 'encrypt_key' );
        $encrypt_key = $itemEncrypt_key->item(0)->nodeValue;
        
        return $encrypt_key;
    }

    /**
     * getHttpResponsePOST
     * 远程获取数据,POST模式
     * 注意:
     * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
     * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem'
     * @access public
     * @param $url 指定URL完整路径地址
     * @param $cacert_url 指定当前工作目录绝对路径
     * @param $para 请求的数据
     * @param $input_charset 编码格式。默认值:空值
     * return 远程输出的数据
     */
    public function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') {

        if (trim($input_charset) != '') {
            $url = $url.'_input_charset='.$input_charset;
        }
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);   // SSL证书认证
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);      // 严格认证
        curl_setopt($curl, CURLOPT_CAINFO, $cacert_url);    // 证书地址
        curl_setopt($curl, CURLOPT_HEADER, 0 );             // 过滤HTTP头
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);      // 显示输出结果
        curl_setopt($curl, CURLOPT_POST,true);              // post传输数据
        curl_setopt($curl, CURLOPT_POSTFIELDS, $para);      // post传输数据
        $responseText = curl_exec($curl);
        // 如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
        // var_dump( curl_error($curl) );
        curl_close($curl);
        
        return $responseText;
    }

    /**
     * getHttpResponseGET
     * 远程获取数据,GET模式
     * 注意:
     * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
     * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem'
     * @access public
     * @param $url 指定URL完整路径地址
     * @param $cacert_url 指定当前工作目录绝对路径
     * return 远程输出的数据
     */
    public function getHttpResponseGET($url) {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
        curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
        curl_setopt($curl, CURLOPT_CAINFO, $this->_config['cacert']);//证书地址
        $responseText = curl_exec($curl);
        // 如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
        // var_dump( curl_error($curl) );
        curl_close($curl);
        
        return $responseText;
    }

}

?>
我在CI下的应用
1. libraries下MY_Alipay.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');

// 加载支付宝类
require_once APPPATH.'libraries/Payment/Alipaylib/Alipay.class.php';

/**
 * MY_Alipay 
 * @author Mike Lee
 */
class MY_Alipay extends Alipay {
    
    protected $_CI;

    public function  __construct($config = array()){
        // 获得 CI 超级对象
        $this->_CI = & get_instance();
        
        // 判断是否存在配置文件
        if (empty($config)) {
            // 加载 Alipay 配置文件
            $this->_CI->load->config('alipay', TRUE);
            $config = $this->_CI->config->item('alipay');
        }

        parent::__construct($config);
    }

    /**
     * submit 支付提交
     * @access public
     * @param string $out_trade_no 商户订单号 商户网站订单系统中唯一订单号
     * @param string $subject 订单名称
     * @param float $total_fee 付款金额
     * @param string $body 商品描述 可空
     * @return void
     * @author Mike Lee
     */
    public function paySubmit($order_code){
        $this->_CI->load->model('order_model');
        $order_info = $this->_CI->order_model->getMyOrderDetails($order_code, true);
        if ( ! is_array($order_info) || ! $order_info) return false;
        if ($order_info['pay_status'] == '1') return true;
        $subject = 'XXXX商城-商品订单';
        $total_fee = $order_info['order_amount'];
        $goods_detail_str = '';
        foreach ($order_info['goods'] as $og) {
            $goods_detail_str .= $og['goods_name'];
            $goods_detail_str .= ' 数量'.$og['num'].'件;';
        }
        // 构造要请求的参数数组
        $parameter = array(
            'out_trade_no' => $order_code,
            'subject'      => $subject,
            'total_fee'    => $total_fee,
            'body'         => $goods_detail_str
        );

        $html_text = $this->buildRequestForm($parameter, 'post', '确认');
        return $html_text;
    }

    /**
     * refundSubmit 退款提交
     * @access public
     * @param string $order_code
     * @return void
     */
    public function refundSubmit(){

    }

    /**
     * asynNotify 异步通知
     * @access public
     * @param void
     * @return void
     * @author Mike Lee
     */
    public function asynNotify(){
        $notify_data = $this->_CI->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
     * @author Mike Lee
     */
    public function syncReturn(){
        $return_data = $this->_CI->input->get(null, true);
        $verify_result = $this->verify($return_data);
        if ($verify_result) {    // 验证成功
            // 交易状态
            $trade_status = $return_data['trade_status'];
            if ($trade_status == 'TRADE_FINISHED' || $trade_status == 'TRADE_SUCCESS') {
                $this->updateOrderPay($return_data);
                return true;
            } else {
                return false;
            }
            return true;
        } else {    // 验证失败
            return false;
        }
    }

    /**
     * refundNotify 退款异步通知
     * @access public
     * @param void
     * @return void
     */
    public function refundNotify(){
        $notify_data = $this->_CI->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->orderRefund($notify_data);
            }
            echo 'success';     //请不要修改或删除

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

    /**
     * updateOrderPay 更新订单支付信息
     * @access private
     * @param string $order_code
     * @return mixed
     * @author Mike Lee
     */
    public function updateOrderPay($alipay_data){
        $this->_CI->load->model('order_model');
        // 根据订单号获取订单信息
        $order_code = $alipay_data['out_trade_no'];
        $order_info = $this->_CI->order_model->getOrderByID($order_code, true);
        if (empty($order_info) || ! isset($order_info['pay_status'])) return;
        if ($order_info['order_status'] == 0 && $order_info['pay_status'] != '1') {
            // 更新订单的支付状态及支付方式
            // pay_status => 1 表示支付成功
            // pay_method => 1 表示支付方式为支付宝支付
            $pay_info = array('pay_status' => '1', 'pay_method' => 1);
            $pay_result = $this->_CI->order_model->updateOrderPayStatus($order_code, $pay_info, true);
            // 添加订单支付信息
            // user_id 用户ID
            // order_id 订单ID
            // total_fee 支付总额
            // buyer_id 用户支付宝ID buyer_id
            $order_pay_info = array(
                'user_id'      => $order_info['user_id'],
                'order_id'     => $order_info['id'],
                'trade_no'     => $alipay_data['trade_no'],
                'total_fee'    => $alipay_data['total_fee'],
                #'total_fee'   => $alipay_data['price'],
                'seller_id'    => $alipay_data['seller_id'],
                'seller_email' => $alipay_data['seller_email'],
                'buyer_id'     => $alipay_data['buyer_id'],
                'buyer_email'  => $alipay_data['buyer_email'],
                'add_time'     => time()
            );
            $this->_CI->order_model->addPayLogForOrderAliPay($order_pay_info);
            // 添加用户收支日志
            // correlation_id 关联ID 这里为订单ID
            // action_type => 1 表示XXXX商城订单事件
            // pay_type => 1 表示支付宝支付
            $payment_log = array(
                'user_id'        => $order_info['user_id'],
                'correlation_id' => $order_info['id'],
                'action_type'    => 1,
                'pay_type'       => 1,
                'cash_fee'       => $alipay_data['total_fee'],
                'remark'         => 'XXXX商城订单:支付宝支付'.$alipay_data['total_fee'],
                'action_ip'      => $_SERVER['REMOTE_ADDR'],
                'add_time'       => time()
            );
            $this->_CI->order_model->addLogForUserPay($payment_log);
            // 添加支付宝支付日志
            $alipay_log = array(
                'user_id'        => $order_info['user_id'],
                'correlation_id' => $order_info['id'],
                'seller_id'      => $alipay_data['seller_id'],
                'seller_email'   => $alipay_data['seller_email'],
                'buyer_id'       => $alipay_data['buyer_id'],
                'buyer_email'    => $alipay_data['buyer_email'],
                'total_fee'      => $alipay_data['total_fee'],
                'trade_no'       => $alipay_data['trade_no'],
                'gmt_create'     => $alipay_data['gmt_create'],
                'gmt_payment'    => $alipay_data['gmt_payment'],
                'price'          => $alipay_data['total_fee'],
                'action_ip'      => $_SERVER['REMOTE_ADDR'],
                'add_time'       => time()
            );
            $this->_CI->order_model->addLogForAliPay($alipay_log);
        }

        return true;
    }   

}

/* End of file MY_Alipay.php */
/* Location: ./application/libraries/MY_Alipay.php */
2. 控制器Pay.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
/**
 * pay 支付控制器
 */
class Pay extends MY_Controller {

    public function __construct(){
        $this->isNeedLogin = FALSE;
        parent::__construct();
    }

    /**
     * alipaySubmit 提交支付宝支付
     * @access public
     * @param string $order_code
     * @return void
     * @author Mike Lee
     */
    public function alipaySubmit($order_code){
        $this->load->library('MY_Alipay');
        $result = $this->my_alipay->paySubmit($order_code);
        if (is_bool($result) && ! $result) {
            $this->jumpNoticePage('订单信息不存在!', site_url('main'), 'ERROR');
        }
        if (is_bool($result) && $result == true) {
            $this->jumpNoticePage('订单已支付,请不要重复支付!', site_url('user/orders'), 'ERROR');
        }

        $this->assign('html_text', $result);
        $this->display('order/alipay.html');
    }

    /**
     * aliPayNotify 支付宝异步回调
     * @access public
     * @param void
     * @return void
     * @author Mike Lee
     */
    public function aliPayNotify(){
        $this->load->library('MY_Alipay');
        $this->my_alipay->asynNotify();
    }

    /**
     * aliPayReturn 支付宝同步通知
     * @access public
     * @param void
     * @return void
     * @author Mike Lee
     */
    public function aliPayReturn(){
        $this->load->library('MY_Alipay');
        $this->my_alipay->syncReturn();
        $order_code = $this->input->get('out_trade_no');
        header('location: '.site_url('order/paysuccess/'.$order_code));
    }


}

/* End of file pay.php */
/* Location: ./apps/home/controllers/pay.php */

相关文章

网友评论

      本文标题:支付宝即时到账Alipay.class.php

      本文链接:https://www.haomeiwen.com/subject/uxtbjttx.html