1. 引入支付库
yansongda/pay
这个库封装了支付宝和微信支付的接口,通过这个库我们就不需要去关注不同支付平台的接口差异,使用相同的方法、参数来完成支付功能,节省开发时间。
首先通过 composer
引入这个包:
$ composer require yansongda/pay
2. 配置参数
我们创建一个新的配置文件来保存支付所需的参数:
$ touch config/pay.php
config/pay.php
<?php
return [
'alipay' => [
'app_id' => '',
'ali_public_key' => '',
'private_key' => '',
'log' => [
'file' => storage_path('logs/alipay.log'),
],
],
'wechat' => [
'app_id' => '',
'mch_id' => '',
'key' => '',
'cert_client' => '',
'cert_key' => '',
'log' => [
'file' => storage_path('logs/wechat_pay.log'),
],
],
];
现在这些参数先留空,后面的章节我们再填入具体的值。
3. 容器
容器是现代 PHP 开发的一个重要概念,Laravel 就是在容器的基础上构建的。我们将支付操作类实例注入到容器中,在以后的代码里就可以直接通过 app('alipay')
来取得对应的实例,而不需要每次都重新创建。
我们通常在 AppServiceProvider
的 register()
方法中往容器中注入实例:
app/Providers/AppServiceProvider.php
use Monolog\Logger;
use Yansongda\Pay\Pay;
.
.
.
public function register()
{
// 往服务容器中注入一个名为 alipay 的单例对象
$this->app->singleton('alipay', function () {
$config = config('pay.alipay');
// 判断当前项目运行环境是否为线上环境
if (app()->environment() !== 'production') {
$config['mode'] = 'dev';
$config['log']['level'] = Logger::DEBUG;
} else {
$config['log']['level'] = Logger::WARNING;
}
// 调用 Yansongda\Pay 来创建一个支付宝支付对象
return Pay::alipay($config);
});
$this->app->singleton('wechat_pay', function () {
$config = config('pay.wechat');
if (app()->environment() !== 'production') {
$config['log']['level'] = Logger::DEBUG;
} else {
$config['log']['level'] = Logger::WARNING;
}
// 调用 Yansongda\Pay 来创建一个微信支付对象
return Pay::wechat($config);
});
}
代码解析:
$this->app->singleton()
往服务容器中注入一个单例对象,第一次从容器中取对象时会调用回调函数来生成对应的对象并保存到容器中,之后再去取的时候直接将容器中的对象返回。app()->environment()
获取当前运行的环境,线上环境会返回production
。对于支付宝,如果项目运行环境不是线上环境,则启用开发模式,并且将日志级别设置为DEBUG
。由于微信支付没有开发模式,所以仅仅将日志级别设置为DEBUG
。
4. 测试
接下来我们来测试一下刚刚注入到容器中的实例,进入 tinker:
$ php artisan tinker
然后分别输入 app('alipay')
和 app('wechat_pay')
>>> app('alipay')
>>> app('wechat_pay')
可以看到返回了 Yansongda\Pay\Gateways\Alipay
和 Yansongda\Pay\Gateways\Wechat
类型的对象,说明注入成功。
5 支付宝 支付配置
###配置参数
接下来将这些参数放到配置文件中:
config/pay.php
'app_id' => '你在支付宝沙箱看到的appid',
'ali_public_key' => '支付宝沙箱显示的公钥',
'private_key' => '刚刚生成的私钥',
支付测试
接下来我们要试一下能否正常跳转到支付宝的支付界面,在路由文件中新增一个临时的路由:
routes/web.php
Route::get('alipay', function() {
return app('alipay')->web([
'out_trade_no' => time(),
'total_amount' => '1',
'subject' => 'test subject - 测试',
]);
});
6. 支付回调
支付宝的支付回调分为 前端回调 和 服务器回调。
- 前端回调 是指当用户支付成功之后支付宝会让用户浏览器跳转回项目页面并带上支付成功的参数,也就是说前端回调依赖于用户浏览器,如果用户在跳转之前关闭浏览器,将无法收到前端回调。
- 服务器回调 是指支付成功之后支付宝的服务器会用订单相关数据作为参数请求项目的接口,不依赖用户浏览器。
因此我们判断支付是否成功要以服务器端回调为准。
我们需要在 PaymentController
中新增两个方法,分别用于处理前端和服务器端回调。
app/Http/Controllers/PaymentController.php
.
.
.
// 前端回调页面
public function alipayReturn()
{
// 校验提交的参数是否合法
$data = app('alipay')->verify();
dd($data);
}
// 服务器端回调
public function alipayNotify()
{
$data = app('alipay')->verify();
\Log::debug('Alipay notify', $data->all());
}
代码解析:
app('alipay')->verify()
用于校验提交的参数是否合法,支付宝的前端跳转会带有数据签名,通过校验数据签名可以判断参数是否被恶意用户篡改。同时该方法还会返回解析后的参数。dd($data);
输出解析后的数据,我们要先看看会返回什么再决定如何处理。\Log::debug('Alipay notify', $data->all());
由于服务器端的请求我们无法看到返回值,使用dd
就不行了,所以需要通过日志的方式来保存。
接下来将这两个方法注册到路由:
routes/web.php
.
.
.
Route::group(['middleware' => 'auth'], function () {
.
.
.
Route::group(['middleware' => 'email_verified'], function () {
.
.
.
Route::get('payment/alipay/return', 'PaymentController@alipayReturn')->name('payment.alipay.return');
});
});
Route::post('payment/alipay/notify', 'PaymentController@alipayNotify')->name('payment.alipay.notify');
服务器端回调的路由不能放到带有
auth
中间件的路由组中,因为支付宝的服务器请求不会带有认证信息。
接下来我们把这两个回调地址配置到支付宝的支付实例里:
app/Providers/AppServiceProvider.php
.
.
.
$this->app->singleton('alipay', function () {
$config = config('pay.alipay');
$config['notify_url'] = route('payment.alipay.notify');
$config['return_url'] = route('payment.alipay.return');
.
.
.
});
.
.
.
notify_url
代表服务器端回调地址,return_url
代表前端回调地址。
注意:回调地址必须是完整的带有域名的 URL,不可以是相对路径。使用
route()
函数生成的 URL 默认就是带有域名的完整地址。