laravel 支付寶流程
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', '[email protected]')->name('payment.alipay.return');
});
});
Route::post('payment/alipay/notify', '[email protected]')->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 預設就是帶有域名的完整地址。