tp5实现PC端支付宝支付详解

首先吐槽一下支付宝的官方文档,它只是简单介绍一下开发的流程和参数,而对于新人来说如果只看它的官方文档很多时候是看不懂的,我也是边看文档边网上查资料才把它弄懂。下面我详细介绍支付宝的电脑支付是如何实现

电脑网站支付

第一步:进入官网,在电脑网站支付下载它的demo

第二步:沙箱环境

想要实现支付宝支付,你要准备一堆东西,对于只想测试的人来说这太麻烦了,而支付宝为我们提供了沙箱环境,这里面有我们开发测试需要的东西

进入沙箱环境,在沙箱应用里下载沙箱钱包(只支持扫一扫、付款码、门店详情页功能,其余功能不提供),它是用户测试扫码支付用的

第三步:把demo放进项目

一般来说支付宝demo属于扩展文件,所以我把它放到extend目录下

然后把config.php放到application\extra(注:可以不放,我放在里面只是方便调用它配置参数)

第四步: 填写配置参数

这里的参数在沙箱环境都已经有,你就根据它的注释填写就可以

支付宝的公钥和商户私钥,你要下载”支付宝开放平台开发助手“,它可以生成秘钥,然后把应用公钥放在设置里面

这样他就生成支付宝公钥,你就把它放到alipay的里面的支付宝公钥,而应用私钥直接放到alipay的商户秘钥

这里我主要说一下同步和异步通知地址,支付宝支付成功后会执行这两个方法(注:同步是支付宝支付成功要跳转的地址),系统会把你支付的信息用POST方式异步传给你的方法。因为是异步,所以页面是没有变化的,在异步这个方法里可以写你自己的业务逻辑。比如接收值,存入数据库之类。这里有个大坑,坑了我两天,即在异步方法里是没法用session取值的,我原本想用session取用户登录id存入数据库中,后来问了师傅才知道,异步是服务器和服务器之间的交互,所以没有cookieId,没有cookieId当然没有session值。

注意:异步通知程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);

第五步: 调用支付接口

首先我们要修改extend\alipay\pagepay\Pagepay.php,因为直接调用的话会报错:

<?php
use think\Loader;
Loader::import("alipay.pagepay.service.AlipayTradeService",EXTEND_PATH);
Loader::import('alipay.pagepay.buildermodel.AlipayTradePagePayContentBuilder',EXTEND_PATH);

class Pagepay
{
    //支付入口
    public static function pay($params)
    {
        //第一步:校检参数
        self::checkParams($params);

        //第二步:构造参数
        $payRequestBuilder = new AlipayTradePagePayContentBuilder();
        $payRequestBuilder->setBody($params['t_body']);//描述
        $payRequestBuilder->setSubject($params['trade_name']);//订单名称,必填
        $payRequestBuilder->setTotalAmount($params['total_amount']);//付款金额,必填
        $payRequestBuilder->setOutTradeNo($params['out_trade_no']);//商户订单号,商户网站订单系统中唯一订单号,必填

        //第三步:获取配置
        $config = config('alipay');
        $aop = new AlipayTradeService($config);

        /**
         * 第四步:电脑网站支付请求(会自动跳转到支付页面)
         * @param $builder 业务参数,使用buildmodel中的对象生成。
         * @param $return_url 同步跳转地址,公网可以访问
         * @param $notify_url 异步通知地址,公网可以访问
         * @return $response 支付宝返回的信息
         */
        $aop->pagePay($payRequestBuilder, $config['return_url'], $config['notify_url']);
    }

    //支付检验
    private static function checkParams($params)
    {
        //商户订单号
        if(empty(trim($params['out_trade_no']))){
            self::processError("你输入的商户订单号有误!");
        }
        //订单名称
        if(empty(trim($params['trade_name']))){
            self::processError("订单名称为空!");
        }
        //付款金额
        if(floatval(trim($params['total_amount'])) <= 0){
            self::processError("付款金额有误!!");
        }
    }

    //统一错误处理接口
    private static function processError($msg)
    {
        throw new \think\Exception($msg);
    }

}

View Code

修改完之后我们就可以在控制器里调用支付宝接口:

<?php
namespace app\aliyun\controller;
use think\Controller;

//支付控制器
class Pay extends Controller
{
    public function index(){
        return $this->fetch();
    }

    //电脑支付宝接口
    public function index2(){
        //付款金额
        $total_amount = input('WIDtotal_amount');
        if($total_amount){
            $params = [
                //商户订单号,商户网站订单系统中唯一订单号,必填
                'out_trade_no'  =>  input('WIDout_trade_no'),
                //订单名称,必填
                'trade_name'       =>  input('WIDsubject'),
                //付款金额,必填
                'total_amount'  =>  $total_amount,
                //描述
                't_body'        =>  input('WIDbody'),
            ];

            import('alipay.pagepay.Pagepay',EXTEND_PATH);
            \Pagepay::pay($params);
        }
    }

    //异步通知地址
    public function notify_url(){
        /* *
         *************************页面功能说明*************************
         * 创建该页面文件时,请留心该页面文件中无任何HTML代码及空格。
         * 该页面不能在本机电脑测试,请到服务器上做测试。请确保外部可以访问该页面。
         * 如果没有收到该页面返回的 success 信息,支付宝会在24小时内按一定的时间策略重发通知
         */
        import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH);

        //$arr = $this->request->param();使用这个无法接受数据
        $arr = $_POST;
        $alipaySevice = new \AlipayTradeService(config('alipay'));
        $alipaySevice->writeLog(var_export($arr, true));
        $result = $alipaySevice->check($arr);

        /* 实际验证过程建议商户添加以下校验。
        1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
        2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
        3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
        4、验证app_id是否为该商户本身。
        */
        if ($result) {//验证成功
            //商户订单号
            $out_trade_no = $arr['out_trade_no'];
            //支付宝交易号
            $trade_no = $arr['trade_no'];
            //交易状态
            $trade_status = $arr['trade_status'];

            //TRADE_FINISHED:交易完成,  TRADE_SUCCESS:支付成功
            if ($arr['trade_status'] == 'TRADE_FINISHED' || $arr['trade_status'] == 'TRADE_SUCCESS') {
                //做逻辑处理,例如:保存数据
                file_put_contents("../extend/sites.txt",implode(",",$arr).PHP_EOL,FILE_APPEND | LOCK_EX);
            }
            echo "success";
        } else {
            //验证失败
            echo "fail";
        }
    }

    //同步跳转,支付成功,由客户的浏览器触发的一个通知
    public function return_url(){
        /* *
         * 功能:支付宝页面跳转同步通知页面
         * 版本:2.0
         * 修改日期:2017-05-01
         * 说明:
         * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。

         *************************页面功能说明*************************
         * 该页面可在本机电脑测试
         * 可放入HTML等美化页面的代码、商户业务逻辑程序代码
         */
        import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH);

        //$arr = $this->request->param();
        $arr = $_GET;
        $alipaySevice = new \AlipayTradeService(config('alipay'));
        $result = $alipaySevice->check($arr);

        /* 实际验证过程建议商户添加以下校验。
        1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
        2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
        3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
        4、验证app_id是否为该商户本身。
        */
        if ($result) {//验证成功
            //做逻辑处理,例如:保存数据
            echo "<pre>";
            print_r($arr);
            //file_put_contents("../extend/sites.txt",implode(",",$arr).PHP_EOL,FILE_APPEND | LOCK_EX);
        } else {
            //验证失败
            echo "验证失败";
        }
    }
}

到了这一步基本就完成了,点击付款就会跳转到这个页面,我们可以使用沙箱钱包扫码支付或登录沙箱账号支付

 

 

付款遇到的问题及解决方案

如果是新手的第一次接入支付宝接口,或多或少遇到一些问题,今天我就把我遇到的问题作总结

问题一:调试错误,请回到请求来源地,重新发起请求

这很有可以就是你得网关有问题,沙箱环境支付宝网关如下:

https://openapi.alipaydev.com/gateway.do

没有修改之前demo文件支付宝网关是已经写好,但默认的网关没有加dev,如果你在沙箱环境中必须加上dev

问题二:each()函数报错

支付宝支付的时候遇到的,因为php7+以上版本抛弃了each函数导致,找到extend\alipay\aop\AopClient.php

while (list ($key, $val) = each ($para_temp)) {

改为

foreach ($para_temp as $key => $val) {

 问题三:收不到异步通知自查方案-支付宝接口常见错误系列

  1.需http://或者https://格式的完整路径 
  例:https://您的域名/notify_url.php  ,支持ip地址方式。(推荐使用域名) 

  2.不能加?id=123这类自定义参数 
  错误示例:https://您的域名/notify_url.php?id=123&test=abc 

  3.必须外网可以正常访问,这个不难理解,在您的异步地址没有代码逻辑的情况下,直接访问应该是一个空白页面并且http状态是200(不支持http200以外的状态),即不能在本机电脑测试,要到服务器上做测试,同时也要确保外部可以访问该页面。

  4.不能有重定向 如:http302 

  5.异步通知,通过 POST 请求的形式将支付结果作为参数通知到商户系统,使用POST方式接收,请确保服务器路由已经开放POST通知 

支付宝社区:https://openclub.alipay.com/club/history/read/1314

 总结:

1、notify_url:通过 POST 请求的形式将支付结果作为参数通知到商户系统,使用$_POST获取数据

2、return_url:通过 GET 请求的形式,使用$_GET获取数据

3、notify_url是异步通知,return_url是同步跳转;异步无法使用Cookie和Session保存数据

 

 

 

查询交易记录

修改extend\alipay\pagepay\Query.php,因为直接调用的话会报错:

<?php
use think\Loader;
Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH);
Loader::import('alipay.pagepay.buildermodel.AlipayTradeQueryContentBuilder',EXTEND_PATH);

class Query
{
    // 商户订单号(out_trade_no) or 支付宝交易号(trade_no) 二选一
    const QUERY_TYPE = 'out_trade_no';

    public static function exec($query_no)
    {
        // 1.设置请求参数
        $RequestBuilder = new \AlipayTradeQueryContentBuilder();
        if (self::QUERY_TYPE == 'trade_no') {
            $RequestBuilder->setTradeNo(trim($query_no));
        } else {
            $RequestBuilder->setOutTradeNo($query_no);
        }

        // 2.获取配置
        $config = config('alipay');
        $aop    = new \AlipayTradeService($config);

        // 3.发起请求
        $response = $aop->Query($RequestBuilder);

        // 4.转为数组格式返回
        $response = json_decode(json_encode($response), true);

        // 5.进行结果处理
        if (!empty($response['code']) && $response['code'] != '10000') {
            self::processError('交易查询接口出错, 错误码: '.$response['code'].' 错误原因: '.$response['sub_msg']);
        }

        return $response;
    }


    /**
     * 统一错误处理接口
     * @param  string $msg 错误描述
     */
    private static function processError($msg)
    {
        throw new \think\Exception($msg);
    }
}

View Code

商户订单号和支付宝交易号二选一,控制器这里默认获取商户订单号:

    //交易查询
    public function query(){
        //商户订单号
        $trade_no = input('WIDTQout_trade_no');
        echo $trade_no;
//        return;

        if($trade_no){
            import('alipay.pagepay.Query',EXTEND_PATH);
            $response = \Query::exec($trade_no);
            print_r($response);
        }
    }

注意:商户订单号和支付宝交易号不能同时为空。 trade_no、 out_trade_no如果同时存在优先取trade_no

 

 

退款

修改extend\alipay\pagepay\Refund.php

<?php
use think\Loader;
Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH);
Loader::import('alipay.pagepay.buildermodel.AlipayTradeRefundContentBuilder',EXTEND_PATH);

class Refund
{
    /**
     * 主入口
     * @param  array $params 退款参数, 具体如下
     * @param string $params['trade_no']/$params['out_trade_no'] 商户订单号或支付宝单号其中之一
     * @param string $params['out_request_no'] 商户退款号(可选, 如同一笔交易多次退款, 则必填)
     * @param float  $params['refund_amount'] 退款金额
     */
    public static function exec($params)
    {
        // 1.校检参数
        self::checkParams($params);

        // 2.构造请求参数
        $RequestBuilder = self::builderParams($params);

        // 3.获取配置
        $config = config('alipay');
        $aop    = new \AlipayTradeService($config);

        // 4.发送请求
        $response = $aop->Refund($RequestBuilder);

        // 5.转为数组格式返回
        $response = json_decode(json_encode($response), true);

        // 6.进行结果处理
        if (!empty($response['code']) && $response['code'] != '10000') {
            self::processError('交易退款接口出错, 错误码: '.$response['code'].' 错误原因: '.$response['sub_msg']);
        }

        return $response;
    }

    /**
     * 校检参数
     */
    private static function checkParams($params)
    {
        if (empty($params['trade_no']) && empty($params['out_trade_no'])) {
            self::processError('商户订单号(out_trade_no)和支付宝单号(trade_no)不得通知为空');
        }

        if (floatval(trim($params['refund_amount'])) <= 0) {
            self::processError('退款金额(refund_amount)为大于0的数');
        }
    }

    /**
     * 构造请求参数
     */
    private static function builderParams($params)
    {
        $RequestBuilder = new \AlipayTradeRefundContentBuilder();

        // 1.判断单号类型
        if (isset($params['trade_no'])) {
            $RequestBuilder->setTradeNo($params['trade_no']);
        } else {
            $RequestBuilder->setOutTradeNo($params['out_trade_no']);
        }

        // 2.判断是否部分退款
        if (!empty($params['out_request_no'])) {
            $RequestBuilder->setOutRequestNo($params['out_request_no']);
        }

        $RequestBuilder->setRefundAmount($params['refund_amount']);
        return $RequestBuilder;
    }

    /**
     * 统一错误处理接口
     * @param  string $msg 错误描述
     */
    private static function processError($msg)
    {
        throw new \think\Exception($msg);
    }
}

View Code

商户订单号和支付宝交易号二选一,控制器这里默认商户订单号:

    //退款
    public function refund(){
        //付款金额
        $total_amount = input('WIDTRrefund_amount');
        if($total_amount){
            $params = [
                //商户订单号,商户网站订单系统中唯一订单号,必填
                'out_trade_no'      =>  input('WIDTRout_trade_no'),
                //付款金额,必填
                'refund_amount'     =>  $total_amount,
                //退款单号,如果是部分退款,必填
                'out_request_no'    =>  input('WIDTRout_request_no'),
            ];
            import('alipay.pagepay.Refund',EXTEND_PATH);
            $res = \Refund::exec($params);

            echo "<pre>";
            print_r($res);
        }
    }

退款单号(out_request_no):

1、全额退款不传,部分退款必传

2、out_request_no:标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传。也可以理解为同一笔交易退款,退款金额小于付款金额是,必须传这个参数,而且同一笔交易分多次退款的话out_request_no每次传值都不能重复,必须保证唯一性

 

 

退款查询

修改extend\alipay\pagepay\RefundQuery.php

<?php
use think\Loader;
Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH);
Loader::import('alipay.pagepay.buildermodel.AlipayTradeFastpayRefundQueryContentBuilder',EXTEND_PATH);

class RefundQuery
{
    /**
     * 主入口
     * @param  array $params 退款查询参数, 具体如下:
     * @param string $params['trade_no']/$params['out_trade_no'] 商户订单号或支付宝单号其中之一
     * @param string $params['out_request_no'] 可空, 为空时, 退款号为订单号
     */
    public static function exec($params)
    {
        // 1.校检参数
        if (empty($params['trade_no']) && empty($params['out_trade_no'])) {
            throw new \think\Exception('商户订单号(out_trade_no)和支付宝单号(trade_no)不得通知为空');
        }

        // 2.构造请求参数
        $RequestBuilder = self::builderParams($params);

        // 3.获取配置
        $config = config('alipay');
        $aop    = new \AlipayTradeService($config);

        // 4.发起请求
        $response = $aop->refundQuery($RequestBuilder);

        // 5.转为数组格式返回
        $response = json_decode(json_encode($response), true);

        // 6.进行结果处理
        if (!empty($response['code']) && $response['code'] != '10000') {
            self::processError('退款查询接口出错, 错误码为: '.$response['code'].', 错误原因为: '.$response['sub_msg']);
        }

        return $response;
    }

    /**
     * 构造请求参数
     */
    private static function builderParams($params)
    {
        $RequestBuilder = new \AlipayTradeFastpayRefundQueryContentBuilder();
        if (isset($params['trade_no'])) {
            $RequestBuilder->setTradeNo($params['trade_no']);
        } else {
            $RequestBuilder->setOutTradeNo($params['out_trade_no']);
        }

        // 如果未传退款号, 则以单号为退款号查询
        if (isset($params['out_request_no'])) {
            $RequestBuilder->setOutRequestNo($params['out_request_no']);
        } else {
            $out_request_no = isset($params['trade_no']) ? $params['trade_no'] : $params['out_trade_no'];
            $RequestBuilder->setOutRequestNo($out_request_no);
        }

        return $RequestBuilder;
    }

    /**
     * 统一错误处理接口
     * @param  string $msg 错误描述
     */
    private static function processError($msg)
    {
        throw new \think\Exception($msg);
    }
}

View Code

商户订单号和支付宝交易号二选一,控制器这里默认商户订单号:

    //退款查询
    public function refundquery(){
        //商户订单号
        $params['out_trade_no'] = input('WIDRQout_trade_no');
        //退款请求号
        $params['out_request_no'] = input('WIDRQout_request_no');

        if($params['out_trade_no']){
            import('alipay.pagepay.RefundQuery',EXTEND_PATH);
            $response = \RefundQuery::exec($params);
            echo "<pre>";
            print_r($response);
        }
    }

注意:退款请求号值(out_request_no)必传,退款时传的值,如果退款时没传则无法查询;商户订单号和支付宝交易号不能同时为空。 trade_no、 out_trade_no如果同时存在优先取trade_no

 

 

关闭交易

修改extend\alipay\pagepay\Close.php

<?php
use think\Loader;
Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH);
Loader::import('alipay.pagepay.buildermodel.AlipayTradeCloseContentBuilder',EXTEND_PATH);

class Close
{
    // 商户订单号(out_trade_no) or 支付宝交易号(trade_no) 二选一
    const QUERY_TYPE = 'out_trade_no';

    public static function exec($query_no)
    {
        // 1.构建请求参数
        $RequestBuilder = new \AlipayTradeCloseContentBuilder();
        if (self::QUERY_TYPE == 'trade_no') {
            $RequestBuilder->setTradeNo(trim($query_no));
        } else {
            $RequestBuilder->setOutTradeNo(trim($query_no));
        }

        // 2.获取配置
        $config = config('alipay');
        $aop    = new \AlipayTradeService($config);

        // 3.发起请求
        $response = $aop->Close($RequestBuilder);

        // 4.转为数组格式返回
        $response = json_decode(json_encode($response), true);

        // 5.进行结果处理
        if (!empty($response['code']) && $response['code'] != '10000') {
            self::processError('交易关闭接口出错, 错误码: '.$response['code'].' 错误原因: '.$response['sub_msg']);
        }

        return $response;
    }

    /**
     * 统一错误处理接口
     * @param  string $msg 错误描述
     */
    private static function processError($msg)
    {
        throw new \think\Exception($msg);
    }
}

View Code

商户订单号和支付宝交易号二选一,控制器这里默认商户订单号:

    //关闭交易
    public function close(){
        //商户订单号
        $out_trade_no = input('WIDTCout_trade_no');

        if($out_trade_no){
            import('alipay.pagepay.Close',EXTEND_PATH);
            $response = \Close::exec($out_trade_no);
            echo "<pre>";
            print_r($response);
        }
    }

注意:

1.商户订单号和支付宝交易号不能同时为空。 trade_no、 out_trade_no如果同时存在优先取trade_no

2.交易开始不是在点付款时,而是在扫码付款但没有支付时,这是才可以关闭交易