网站导航
> 资讯中心 > 学习园地 > ThinkPHP对接支付宝当面付详细教程
ThinkPHP对接支付宝当面付详细教程
2021-10-26

导读:
作为一名小站长或开发者,网站需要接入支付功能,才能实现网站的变现盈利,一般有以下几个方案:

1、第三方支付平台: 截止2020年11月23日,我国拥有第三方支付牌照的公司共有237家,从2015年至今,先后有34家第三方支付公司支付牌照被注销。第三方支付不容易申请,基本都需要企业资格才能开通,审核比较严。

2020年第三方支付公司排名TOP50
排名    第三方支付公司    [**]PP或支付品牌
1    财付通支付科技有限公司    财付通和微信支付
2    支付宝(中国)网络技术有限公司    支付宝
3    中国银联股份有限公司    银联商务
4    平安付科技服务有限公司    壹钱包
5    快钱支付清算信息有限公司    快钱
6    苏宁消费金融有限公司    苏宁金融(苏宁支付)
7    联动优势电子商务有限公司    联动优势
8    京东数字科技控股股份有限公司    京东支付
9    拉卡拉支付股份有限公司    拉卡拉
10    通联支付网络服务股份有限公司    通联支付
11    易宝支付有限公司    易宝支付(Yeepay)
12    迅付信息科技有限公司    环迅支付
13    北京度小满支付科技有限公司    度小满支付
14    天翼电子商务有限公司    翼支付
15    中移电子商务有限公司    和包支付
16    网银在线(北京)科技有限公司    网银在线
17    联通支付有限公司    联通支付
18    上海盛付通电子支付服务有限公司    盛付通
19    北京海科融通支付服务股份有限公司    海科融通
20    易生支付有限公司    易生支付
21    随行付支付有限公司    随行付Plus
22    海南海岛一卡通支付网络有限公司    海南一卡通
23    国付宝信息科技有限公司    国付宝
24    银盛支付服务股份有限公司    银盛支付
25    网易宝有限公司    网易宝
26    连连银通电子支付有限公司    连连支付
27    北京钱袋宝支付技术有限公司    钱袋宝
28    宝付网络科技(上海)有限公司    宝付
29    杉德支付网络服务发展有限公司    杉德支付
30    智付电子支付有限公司    智付Dinpay
31    中付支付科技有限公司    中付支付
32    上海付费通信息服务有限公司    付费通
33    汇付天下有限公司    汇付天下
34    瑞银信支付技术有限公司    瑞银信
35    北京新浪支付科技有限公司    新浪支付
36    上海富友支付服务股份有限公司    富友支付
37    易智付科技(北京)有限公司    首信易支付
38    快捷通支付服务有限公司    快捷通
39    中金支付有限公司    中金支付
40    广州市汇聚支付电子科技有限公司    汇聚支付
41    得仕股份有限公司    得仕通
42    资和信电子支付有限公司    资和信
43    易联支付有限公司    易联支付
44    上海偶可贝网络科技有限公司    [**]llpay
45    优钱付(浙江)信息科技有限公司    优钱付
46    四川商通实业有限公司    四川商通
47    卡友支付服务有限公司    卡友支付
48    重庆易极付科技有限公司    易极付
49    双乾网络支付有限公司    双乾支付
50    新生支付有限公司    新生支付
2、第四方支付(聚合支付):第四方支付是相对第三方而言的,作为对第三方支付平台服务的拓展,第三方支付介于银行和商户之间,而第四方支付是介于第三方支付和商户之间,没有支付许可牌照的限制。第三方支付提供的是资金清算通道,而第四方支付提供的是支付基础上的多种衍生服务。作为连接着第三方支付机构和商户的中间商,聚合支付只是完成支付环节的信息流转和商户操作的承载,从事的是“支付、结算、清算”服务之外的“支付服务”,并不提供资金清算通道。《中国人民银行支付结算司关于开展违规“聚合支付”服务清理整治工作的通知》,将聚合支付定位于“收单外包机构”,对聚合支付划了四条明确的红线,即“四个不得”,其中之一是“不得以任何形式经手特约商户结算资金,从事或变相从事特约商户资金结算”。换句话理解,聚合支付公司只是为商户提供第三方支付通道的技术服务机构,不能沉淀资金,更不能为商户提供支付和资金清算。聚合支付本身并不违法,但如果聚合支付平台从事了结算业务,对商家的资金进行了截留,形成所谓“资金池”,就是非法行为,风险很大。正是因为风险的存在,所以很多未经国家有关主管部门批准的小型聚合支付平台都是做一段时间就跑路,就是为了规避这个风险。大家要选择第四方聚合支付平台时,一定要谨慎!

3、个人免签约系统:使用个人的支付宝或微信收款码,然后安卓监听[**]PP收款通知,实现收款。本质上是采用挂机监听的策略,但针对的是移动端支付宝或微信的收款通知消息,成本高,配置麻烦,需24小时挂台安卓手机,不免费。

对于个人开发者来说,以上方案要么没有企业资质,要么太麻烦,要么太贵,有没有更好的方案呢?答案是:当然有,那就是支付宝的当面付,下面让我们一步一步来探讨。

当面付申请条件:

1.  支持的账户类型:经过实名认证的个人/企业支付宝账号  

2.  签约申请提交资料: 

   1) 经营场所照片

     a. 有店铺门头的经营场所,需提供门头照;

     b. 无店铺门头的经营场所,需提供内景照或场景照

提醒:若未规范提交经营场景照片,商家收款将受限,商家需在30天内补全资料,否则将影响正常收款。

   2) 与实名认证的支付宝账号持有人同名的营业执照(即营业执照的法人代表与支付宝账号持有人姓名一致)

提醒:若未提供同名营业执照,可长期使用但商家收款有限额。

3.费率

服务名称    费率    服务期限
单笔费率    0.6%    1年
说明:

申请当面付其实很简单,只需要找一张店铺门面照片即可,可以到外面随便拍一张,或者网站搜索。
营业执照是可选的,不上传的话,限制单笔收款≤1000,单日收款≤5W,对于小网站来说,这个额度已经够用了。
补充:

如果你的支付宝账号在申请时,提示风险过高,那就换另一个账号。支付宝的规则系统一般会检测你账号近期的交易,看是否有风险,新注册的支付宝账号,一般都申请不了当面付。

当面付申请流程:
1、点击这里进入,登陆支付宝账户,然后点击“立即开通”。

2、填写基本信息,红色星号的必填:

经营内容选择百货零售 → 超市 → 超市,或者选其他的,建议不要选平台类、支付类、游戏类的,这些审查严格,没有企业资质和电信增值许可证是通过不了。
营业执照可以不上传。
店铺招牌,就是门店的照片,可以去外面拍一张,也可以百度搜索。
提交申请后十多分钟就可收到通过通知。


当面付开发流程:
一、准备工作
1、首先是要申请成功开发者,可以登录支付宝开放平台申请。

2、当面付申请成功后,可以在蚂蚁金服开放平台网页&移动应用中,看到我的应用列表中多了一个 “应用2.0签约20210203*********” 的应用

二、配置当面付公钥和私钥
1、利用 “支付宝开放平台开发助手” 生成RS[**]密钥(包括应用公钥和应用私钥),官方教程地址:第一步:生成密钥并上传 | 网页&移动应用

在线生成密钥:MiniU 研发工作台
2、点击进入应用2.0签约20210203*********

3、在 左侧菜单栏的“应用信息” 中设置公钥,设置“接口加签方式”

4、把前面生成的应用公钥复制进去,然后保存设置即可

三、官方[**]PI参数
网关地址:支付宝 - 网上支付 安全快速!

1、公共请求参数:

参数    类型    是否必填    最大长度    描述
app_id    String    是    32    支付宝分配给开发者的应用ID
method    String    是    128    接口名称
format    String    否    40    仅支持JSON
charset    String    是    10    请求使用的编码格式,如utf-8,gbk,gb2312等
sign_type    String    是    10    商户生成签名字符串所使用的签名算法类型,目前支持RS[**]
sign    String    是    256    商户请求参数的签名串,详见签名
timestamp    String    是    19    发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
version    String    是    3    调用的接口版本,固定为:1.0
notify_url    String    否    256    支付宝服务器主动通知商户服务器里指定的页面http/https路径。
app_auth_token    String    否    40    详见应用授权概述
biz_content    String    是    -    请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
2、请求参数:

参数    类型    是否必填    最大长度    描述
out_trade_no    String    必须    64    商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
scene    String    必须    32    支付场景 条码支付,取值:bar_code 声波支付,取值:wave_code
auth_code    String    必须    32    支付授权码
seller_id    String    可选    28    如果该值为空,则默认为商户签约账号对应的支付宝用户ID
total_amount    Price    可选    11    订单总金额,单位为元,
discountable_amount    Price    可选    11    参与优惠计算的金额,单位为元
undiscountable_amount    Price    可选    11    不参与优惠计算的金额,单位为元
subject    String    必须    256    订单标题
body    String    可选    128    订单描述
goods_detail    GoodsDetail []    可选    -    订单包含的商品列表信息,Json格式,其它说明详见商品明细说明
operator_id    String    可选    28    商户操作员编号
store_id    String    可选    32    商户门店编号
terminal_id    String    可选    32    商户机具终端编号
alipay_store_id    String    可选    32    支付宝的店铺编号
extend_params    ExtendParams    可选    -    业务扩展参数
timeout_express    String    可选    6    该笔订单允许的最晚付款时间,逾期将关闭交易。
royalty_info    RoyaltyInfo    可选    -    描述分账信息,Json格式,其它说明详见分账说明
sub_merchant    SubMerchant    可选    -    二级商户信息,当前只对特殊银行机构特定场景下使用此字段
3、公共响应参数:

参数    类型    是否必填    最大长度    描述
code    String    是    -    网关返回码,详见文档
msg    String    是    -    网关返回码描述,详见文档
sub_code    String    否    -    业务返回码,详见文档
sub_msg    String    否    -    业务返回码描述,详见文档
sign    String    是    -    签名,详见文档
4、响应参数

参数    类型    是否必填    最大长度    描述
trade_no    String    必填    64    支付宝交易号
out_trade_no    String    必填    64    商户订单号
buyer_logon_id    String    必填    100    买家支付宝账号
total_amount    Price    必填    11    交易金额
receipt_amount    String    必填    11    实收金额
buyer_pay_amount    Price    选填    11    买家付款的金额
point_amount    Price    选填    11    使用积分宝付款的金额
invoice_amount    Price    选填    11    交易中可给用户开具发票的金额
gmt_payment    Date    必填    32    交易支付时间
fund_bill_list    TradeFundBill []    必填    -    交易支付使用的资金渠道
card_balance    Price    选填    11    支付宝卡余额
store_name    String    选填    512    发生支付交易的商户门店名称
buyer_user_id    String    必填    28    买家在支付宝的用户id
discount_goods_detail    String    必填    -    本次交易支付所使用的单品券优惠的商品优惠信息
三、沙箱测试环境
如果当面付暂时还没有签约成功,可以先使用沙箱账号进行测试,方法如下:

1、登录支付宝开发平台 支付宝开放平台,开发服务 → 研发服务

2、沙箱应用里,设置RS[**]2(SH[**]256)密钥,和上面设置应用公钥是一样的。

3、设置应用网关,用于接收支付宝异步通知

四、 代码编写
可以下载官方当面付的SDK示例,下载链接:网页&移动应用学习路径 | 网页&移动应用

我大概地看了一下官方给的示例,涉及的类文件比较多,对于新手而言还是比较复杂的,所以我简化了一下,大家可以参考我以下的Thinkphp框架代码。

1、在extend扩展目录下新建一个pay文件夹,里面创建一个[**]lipay.php类,代码如下:

<?php
namespace pay;
use \think\Db;
use app\common\model\Order as OrderModel;
 
/**
 * 支付宝支付类
 */
class [**]lipay {
    //是否沙盒环境
    public $is_sandbox = true;
    //沙盒地址
    private $sandurl = 'https://openapi.alipaydev.com/gateway.do';
    //正式地址
    private $apiurl  = 'https://openapi.alipay.com/gateway.do';
    //网关地址
    private $gateway;
    //支付宝的[**]PPID
    private $appid;
    //应用私钥
    private $rsaPrivateKey = '商户设置的私钥';
    //支付宝公钥
    private $alipayPublicKey= '支付宝自动生成的公钥';
 
    private $charset = 'utf-8';
    
    public function set[**]ppid($appid)
    {
        $this->appid = $appid;
    }
    
    public function setRsaPrivateKey($rsaPrivateKey)
    {
        $this->rsaPrivateKey = $rsaPrivateKey;
    }
    
    public function set[**]lipayPublicKey($alipayPublicKey)
    {
        $this->alipayPublicKey = $alipayPublicKey;
    }
    //构造方法
    public function __construct($account){
        //如果是沙箱测试
        if($this->is_sandbox){
            $this->gateway = $this->sandurl;
            $this->appid = '2021000117612368';
            $this->rsaPrivateKey ='MIIEvgIB[**]D[**]NBgkqhkiG9w0B[**]QEF[**][**]SCBKgwggSk[**]gE[**][**]oIB[**]QDF0Bwt0712ogHbqp245rlz6Rj7LePxxhO4HoIUZhmZS7OeC1muhLK44e9kznFGT6YvOE2rasZT+EixPLifYqUML0Ecmxxvt3dr/Wr[**]IIM941ODhjCaV+CDjZYNBZmsK+nPojFNWuyZCq9ygzX43XOjk3oTNbws5/tJd[**]4fzKIhlufuvpeGL5tgzoLKxUy2oweVluIyqkhFZUs9/9vgiUwFVt/iPf4JREGJ56Vq1grOBd+ZGqrljCcx6mEOm7GGd0lcDw038L//MYICd[**]gLfmCF+aojkRm3qkcTt7bl0jYsYn[**]WqxkBvtpCWQzFKMbwVsqbOM5EUzle8csNuguXqZ3T[**]gMB[**][**]ECggE[**]QXoSacTluOgFT23ZSUPt7QUiRLo68mOLUW2fxxrIrGpXaR/+rnq0Ieh/clG3QvvIWWb8ppnwTgkoHqqnvfJMCY12sv5Q2lXTTMaUWEYqywh9M1LusZgW89Sg72yEIm5itVzNjvpiyIG182SlL9w1k9aUGNm3kPG0xuqIZlQ48n7IZSeDGg+5ic4J+U7PGaCV99Q2zq7mkuIrbot+p478oFR7aM1/24OfxdJ1eh4FZZevwmLHpwqZEhecZFGrKx6/wlzDOhaiR7PgGcjH0/q6tiw6g5TCWf[**]zBDfFl/ZdRToNP6mOe57qt7jKhQz+2Lxz0qLNdNDacyTSYIkFg9cE0QKBgQD3[**]yFhrFk0VaTN2Xt6O[**]OEn1UIf1aBPenFQfV8+tIlbL5Io3VHHmycMuctW7c2BGkp7HF9OwcYGtURQdaBRCyK[**]YnW1nbZRgLHhkFC9rLyG04/yv6pN6/8EZ4bow7aUiCGoR/mV5GehiWTjb5/thCS+enaaalTd[**]fQBGZOl7VyGwKBgQDN[**]rKycIbtXl2BEqcIqwW2nzv0R[**]8FhK7LkRy1CFI[**]eZfr1O4FiSXM/3V/EuocISCwOLWRq5Qyx7mZoMZ4FSRlQFbo9LUmYtIXtKHekwYZEpxleXJOVudRRQa3GkMvW[**]d3s1MSCGQ44jHFc/vbap6fxfR/GWHupoMOvW4N06h+qQKBg[**]FzekG5oniFPEedTHVmWNbxnK1FGjv+Ih5vicKnMo7XubJxi2HUkuzD+8mvbT8a/YcqhWwn6Z3BopjMWzc9MEnLQoUJk38pQyDq7/01t81mTapgRei2l[**]kWDWi6J38u1lQUJDzVLNzSiuv3/DOB3U0PvMj0r/L2jokxTWyOxZkb[**]oGB[**]K8DuqHyxn0lrhVeYxJXTg1Vas1gvKItXzuRqjwx2i9lnnhJ0tkO+CJFg3z0HL/e2BUYlIjDPUUMlDm+sz[**]YfHWjs440OeGHQ2vRXM6yHOaSqMlIIHkYX2lV0CHIXcqxD870W9ptJ4IYN/0kwsHSB5DGYa+Pb7dYl1GiDa3oH1Ph[**]oGB[**]PCSjFfXd45BPYDNL+4N9zS2wT5pjxlSo3Jrg3NKOqBZLS52ygZ6xe[**]NXrG7lzitJ75prUS/JyXS83SM8au0vcO1Fko9BbW7MkuKFZH7FSpOlv5/PJQ6tI55TV7gGl8l4ND7V9OSd[**]MhEBQ/uT2gmddFoRTB14MeLIpe2iIVonan';
            $this->alipayPublicKey = 'MIIBIj[**]NBgkqhkiG9w0B[**]QEF[**][**]OC[**]Q8[**]MIIBCgKC[**]QE[**]s9[**]E75V+F242HcN/SlcGUxUNv3zGpXjai4u2ZMkOL1nolr4/v4BGEsE8tLaYq6lecG/of5taQ27WbWXB5PMB3s6emesTEn5yvZPh/HqmK[**]CHi7js+Dtu123J/z0DyCukMm1ZHJe7jT9VEU9w4gIdNi64VxdaRF3ZMT6DXnboUEegyRKGmJc58h+O30P/UYqdCW/Gl+380o80e6Fs0rX33[**]ixkNTRNgnQ3n2er1Nrqan/9sfUCnqqxgpc1+GTT+vyn3x4Xwvch2pRIkcK4BFCrWVVMKVXx/icj1njTVwWm1KYrXDVYrxf4ZLgpMHyW3SfhnXFKuxvSYnvm5EHjxtNEQID[**]Q[**]B';
        }else{
            $this->gateway = $this->apiurl;
            $this->appid = $account['appid'];
            $this->rsaPrivateKey = $account['privateKey'];
            $this->alipayPublicKey = $account['publicKey'];
        }
    }
    /**
     *
     * 发起当面付
     * $params 传输的数据
     */
    public function facepay($params){
        //请求参数的集合,参考https://mp.csdn.net/editor/html/113599448
        $biz_content = [
            "scene" =>"bar_code",//支付场景 条码支付,取值:bar_code 声波支付,取值:wave_code(必须)
            "out_trade_no" =>$params['pay_id'],//唯一标识,订单编号(必须)
            "total_amount" =>$params['money'],//订单金额(可选)
            "subject" =>"购买商品",//商品名称
            //"seller_id" =>'',    //如果该值为空,则默认为商户签约账号对应的支付宝用户ID    
            "timeout_express" =>"5m",//该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天
         ];
         //公共请求参数
        $param = [
            'app_id' => $this->appid,//支付宝分配给开发者的应用ID
            'method' => 'alipay.trade.precreate',//接口名称
            //'format' => 'JSON',//仅支持JSON
            'charset' => 'utf-8',//请求使用的编码格式
            'sign_type' => 'RS[**]2',//商户生成签名字符串所使用的签名算法类型,目前支持RS[**]2和RS[**],推荐使用RS[**]2
            'sign' => '',//商户请求参数的签名串
            'timestamp' => date('Y-m-d H:i:s', time()),//发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
            'version' => '1.0',//调用的接口版本,固定为1.0
            'notify_url' => $params['notify_url'],//异步通知地址,支付宝服务器主动通知商户服务器里指定的页面http/https路径
            //'app_auth_token' => '',//app_auth_token
            'biz_content' => json_encode($biz_content),    //请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递
        ];
        //组合生成签名参数
        $signdata = [];
        $signdata['app_id'] = $param['app_id'];
        $signdata['method'] = $param['method'];
        $signdata['charset'] = $param['charset'];
        $signdata['sign_type'] = $param['sign_type'];
        $signdata['timestamp'] = $param['timestamp'];
        $signdata['version'] = $param['version'];
        $signdata['notify_url'] = $param['notify_url'];
        $signdata['biz_content'] = $param['biz_content'];
        //生成签名
        $sign = $this->generateSign($signdata, 'RS[**]2');
        $param['sign'] = $sign;
        //echo "<pre>";
        //var_dump($param);die;
        //发起请求
        $content = $this->curlPost($this->gateway,$param);
        $alipayData  = json_decode($content, true);    
        //公共响应参数
        $responseData = $alipayData['alipay_trade_precreate_response'];
        if($responseData['code'] == 10000){
            //生成成功,返回结果给前端
            $data = [];
            $data['out_trade_no'] = $responseData['out_trade_no'];
            $data['qr_code'] = $responseData['qr_code'];
            return ['code' => 1 , 'msg' => '成功' , 'data' => $data];
 
        }else {
            //file_put_contents(LOG_P[**]TH .'alipayFacepay.log', 'err code:' . $responseData['code'] . ', err msg:' . $responseData['msg'] . '\r\n', FILE_[**]PPEND);
            return ['code' => 0 , 'msg' => '错误码:' . $responseData['code'] . ',错误信息:' . $responseData['msg']];
        }
    }
 
 
    /**
     * 支付宝支付通知
     * @param $data 通知的数据
     */
    public function notify($data){
        $falg=false;
        //验证签名
        $param = $data;
        unset($param['sign']);
        unset($param['sign_type']);
        $rst = $this->rsaCheck($param, $data['sign'] , $data['sign_type']);
        if(!$rst){
            file_put_contents(LOG_P[**]TH .'alipaynotify.log', '验签失败\r\n' , FILE_[**]PPEND );
            return false;
        }
        //查询支付订单状态
        try{
            $rst = $this->orderquery($data, 'TR[**]DE_SUCCESS');
        } catch (\Exception $e) {
            printLog("查询支付订单状态失败:".$e);
        }
        if($rst){
            $falg=true;
        }else {
            file_put_contents(LOG_P[**]TH .'alipaynotify.log', '查询订单状态错误\r\n', FILE_[**]PPEND);
            $falg=false;
        }
        return $falg;
    }
 
    /**
     *
     * 支付查询接口 
     * @param  data   支付宝响应的参数集合
     * @param  status 要验证的状态  
     * W[**]IT_BUYER_P[**]Y 交易创建等待买家付款 
     * TR[**]DE_CLOSED   未付款交易超时关闭或支付完成后全额退款  
     * TR[**]DE_SUCCESS  交易支付成功 
     * TR[**]DE_FINISHED 交易结束不可退款
     */
    public function orderquery($data , $status){
        $biz_content = [
            'out_trade_no' => $data['out_trade_no'],
            'trade_no' => $data['trade_no'],
            //'org_pid' => '',
        ];
 
        $param = [
            'app_id' => $this->appid,
            'method' => 'alipay.trade.query',
            'charset' => 'utf-8',
            'sign_type' => 'RS[**]2',
            'sign' => '',
            'timestamp' => date('Y-m-d H:i:s',  time()),
            'version' => '1.0',
            'biz_content' => json_encode($biz_content),
        ];
 
        //组合签名数组
        $signdata = [];
        $signdata['app_id'] = $param['app_id'];
        $signdata['method'] = $param['method'];
        $signdata['charset'] = $param['charset'];
        $signdata['sign_type'] = $param['sign_type'];
        $signdata['timestamp'] = $param['timestamp'];
        $signdata['version'] = $param['version'];
        $signdata['biz_content'] = $param['biz_content'];
 
        //生成签名
        $sign = $this->generateSign($signdata, 'RS[**]2');
        $param['sign'] = $sign;
        $content = $this->curlPost($this->gateway,$param);
        $alipayData  = json_decode($content, true);    
        //公共响应参数
        $responseData = $alipayData['alipay_trade_query_response'];
        if($responseData['code'] == 10000){
            if($responseData['trade_status'] == $status){
                return true;
            }else {
                return false;
            }
        }else {
            return false;
        }
    }
 
    public function generateSign($params, $signType = "RS[**]") {
        return $this->sign($this->getSignContent($params), $signType);
    }
 
    public function getSignContent($params) {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
                // 转换成目标字符集
                $v = $this->characet($v, $this->charset);
                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }
        unset ($k, $v);
        return $stringToBeSigned;
    }
 
    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    function characet($data, $targetCharset) {
        if (!empty($data)) {
            $fileType = $this->charset;
            if (strcasecmp($fileType, $targetCharset) != 0) {
                $data = mb_convert_encoding($data, $targetCharset, $fileType);
                //$data = iconv($fileType, $targetCharset.'//IGNORE', $data);
            }
        }
        return $data;
    }
 
 
     /**
      *
      * 校验$value是否非空
      */
    protected function checkEmpty($value) {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;
        return false;
    }
 
 
    /**
     *
     * 签名函数
     */
    protected function sign($data, $signType = "RS[**]") {
        $priKey=$this->rsaPrivateKey;
        $res = "-----BEGIN RS[**] PRIV[**]TE KEY-----\n" .
            wordwrap($priKey, 64, "\n", true) .
            "\n-----END RS[**] PRIV[**]TE KEY-----";
        ($res) or die('您使用的私钥格式错误,请检查RS[**]私钥配置');
        if ("RS[**]2" == $signType) {
            //OPENSSL_[**]LGO_SH[**]256是php5.4.8以上版本才支持
            openssl_sign($data, $sign, $res, version_compare(PHP_VERSION,'5.4.0', '<') ? SH[**]256 : OPENSSL_[**]LGO_SH[**]256); 
        } else {
            openssl_sign($data, $sign, $res);
        }
        $sign = base64_encode($sign);
        return $sign;
    }
 
    /**
     *
     * 验签函数
     */
    public function rsaCheck($data, $sign,$type = 'RS[**]'){
        $public_key = $this->alipayPublicKey;
        $search = [
            "-----BEGIN PUBLIC KEY-----",
            "-----END PUBLIC KEY-----",
            "\n",
            "\r",
            "\r\n"
        ];
        $public_key=str_replace($search,"",$public_key);
        $public_key=$search[0] . PHP_EOL . wordwrap($public_key, 64, "\n", true) . PHP_EOL . $search[1];
        $res=openssl_get_publickey($public_key);
        if($res)
        {
            if($type == 'RS[**]'){
                $result = (bool)openssl_verify($this->getSignContent($data), base64_decode($sign), $res);
            }elseif($type == 'RS[**]2'){
                $result = (bool)openssl_verify($this->getSignContent($data), base64_decode($sign), $res,OPENSSL_[**]LGO_SH[**]256);
            }
            openssl_free_key($res);
        }else{
            return false;
        }
        return true;
    }
    /*
     * curl发送post请求,并返回请求头信息
     * url:       访问路径
     * postData:  要传递的post数据
     * refcode:   是否返回请求码
     * refheader: 是否返回请求头信息
     * */
    function curlPost($url, $postData, $refcode = false, $refheader = false) {
        $curl = curl_init();
        //设置提交的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置获取的信息以文件流的形式返回,而不是直接输出
        curl_setopt($curl, CURLOPT_RETURNTR[**]NSFER, 1);
        //忽略证书(关闭https验证)
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        //设置post方式提交
        curl_setopt($curl, CURLOPT_POST, 1);
        //设置post数据
        $postFields = http_build_query($postData);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $postFields);
        //添加请求头信息
        $headers = addHttpHeader($url);
        curl_setopt($curl, CURLOPT_HTTPHE[**]DER, $headers);
        //在尝试连接时等待的秒数
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
        //设置超时时间,最大执行时间超时时间(单位:s)
        curl_setopt($curl, CURLOPT_TIMEOUT, 300);
        //是否返回请求头信息(http协议头)
        if ($refheader) {
            curl_setopt($curl, CURLOPT_HE[**]DER, 1);
            //追踪句柄的请求字符串(允许查看请求header)
            curl_setopt($curl, CURLINFO_HE[**]DER_OUT, true);
        } else {
            curl_setopt($curl, CURLOPT_HE[**]DER, 0);
        }
        //执行命令
        $result = curl_exec($curl);
        //转换字符编码
        $result = mb_convert_encoding($result, 'utf-8', 'UTF-8,GBK,GB2312,BIG5');
        //解决返回的json字符串中返回了BOM头的不可见字符(某些编辑器默认会加上BOM头)
        $result = trim($result,chr(239).chr(187).chr(191));
        //获取状态码
        $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        //关闭URL请求
        curl_close($curl);
        //是否返回请求码
        if ($refcode) {
            $html = array("httpcode" => $httpcode, "result" => $result);
            return $html;
        } else {
            return $result;
        }
    }
}
?>
2、在app\common目录创建一个Pay.php支付类,代码如下:

<?php
namespace app\common;
use think\Db;
use think\Exception;
/**
 * 支付类
 */
class Pay {
 public $account = array(
            "appid"      => "支付宝的[**]PPID",
            "privateKey" => "商户设置的私钥",
            "publicKey"  => "支付宝自动生成的公钥"
        );
 public function startPay($params){
        //创建支付宝对象
        $alipayobj= new \pay\[**]lipay($this->account);
        $result=$alipayobj->facepay($params);
        ($result['code']==0) && exit('支付通道维护,'.$result['msg']);        
        //接收支付宝返回的订单编号和支付二维码
        $params['out_trade_no'] = $result['data']['out_trade_no'];
        $params['qr_code'] = $result['data']['qr_code'];
        return $this->buildRequestForm(url('@Index/Index/facepay'),$params);
    }
 
    /**
     * 服务器回调
     */
    public function notify_callback($params){
        //查询数据库订单状态
        $order = OrderModel::where('number', $params['out_trade_no'])->find();
        if(!$order){
            die('不存在编号['.$params['out_trade_no'].']的订单!');
        }
        //防止恶意刷新加钱
        if ($order['status'] == 1) { 
            //直接返回给上游 success 或者 OK 之类的
            exit('success');
        }
        
        //创建支付宝对象
         $alipayobj= new \pay\[**]lipay($this->account);
        //调用异步通知验证
        $verifyresult=$alipayobj->notify($params);
        if ($verifyresult) {
            //实际付款金额
            $money = (float)$params['total_amount'];
            //这里完成网站的业务逻辑
            exit('success');
        }else{            
            exit('fail'); //返回失败 继续补单
        }
    }
    /**
     * 建立跳转请求表单
     * @param string $url 数据提交跳转到的URL
     * @param array $data 请求参数数组
     * @param string $method 提交方式:post或get 默认post
     * @return string 提交表单的HTML文本
     */
    function buildRequestForm($url, $data, $method = 'post', $button_name = '确定', $show = false) {
        $html = "<form id='requestForm' name='requestForm' action='" . $url . "' method='" . $method . "'>";
        while (list($key, $val) = each($data)) {
            $html.= "<input type='hidden' name='" . $key . "' value='" . $val . "' />";
        }
        $display = $show ? "style='display:block;'" : "style='display:none;'";
        $html.= "<input type='submit' value='" . $button_name . "' " . $display . "></form>";
        $html.= "<script>document.forms['requestForm'].submit();</script>";
        return $html;
    }
    
}
?>
3、controller控制器里发起支付请求

<?php
namespace app\index\controller;
use think\Controller;
use think\Cache;
use think\Session;
use think\Request;
use app\common\Pay;
 
class Index extends Controller

    //接口提交
    public function submit() {
        $array=array(
            "pay_id" => "订单编号", 
            "money"  => "订单金额",
            "notify_url"  => "异步通知地址",
        );
        $res  = (new Pay)->startPay($array);
        exit($res);
    }
    //当面付页面
    public function facepay(){
        $data=input('');
        !isset($data['pay_id']) && $this->error('订单编号不能为空');
        empty(trim($data['pay_id'])) && $this->error('订单编号不能为空');
        !isset($data['money']) && $this->error('金额不能为空');
        empty(trim($data['money'])) && $this->error('金额不能为空');
        !isset($data['notify_url']) && $this->error('异步通知地址不能为空');
        empty(trim($data['notify_url'])) && $this->error('异步通知地址不能为空');        
        //生成二维码
        $qrcode='https://my.tv.sohu.com/user/a/wvideo/getQRCode.do?width=200&height=200&text='.trim($data['qr_code']);
        $this->assign('money',trim($data['money']));
        $this->assign('qrcode',$qrcode);
        return view();
    }
}
到这里其实已经差不多了,但还有几个功能需要完成:

显示当面付二维码的页面facepay.html,即facepay()方法的视图。
支付宝当面付是没有同步通知功能的,所以要自己解决,解决思路就是在facepay.html页面使用js定时器,定时查询订单状态,如果订单完成,就跳转到同步通知页面。
最后附上我开发的效果图:

————————————————
版权声明:本文为CSDN博主「祝君圆梦」的原创文章,遵循CC 4.0 BY-S[**]版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq15577969/article/details/113599448