在 Laravel 项目中同时支持微信支付和支付宝支付,核心思路是把差异化的渠道逻辑封装起来,让上层业务只依赖统一的支付接口。这样做既能减少重复代码,也方便后续接入更多支付平台。

一、环境准备与扩展包安装
Laravel 本身不提供支付网关,但社区有成熟的 SDK 可用。微信支付可选用 yansongda/pay,支付宝同样支持该包,它能覆盖下单、退款、查询和回调。通过 Composer 安装后,需要在 config 目录增加配置文件,把应用的 app_id、商户号、密钥以及回调地址集中管理。
安装命令如下,建议在 PHP 8.0 以上版本使用,避免依赖报错:
composer require yansongda/pay:~3.0 php artisan vendor:publish --provider="YansongdaPayPayServiceProvider"
发布完成后,打开 config/pay.php,你会看到 wechat 和 alipay 两个数组。把从商户平台拿到的证书路径和密钥填进去,尤其注意微信的 apiv3_key 和支付宝的 ali_public_key 不要写反。配置错误是最常见的对接失败原因。
二、定义统一的支付网关接口
为了让控制器不感知具体渠道,我们先定义一个 PaymentGateway 契约。任何支付实现都要提供 pay、notify、query 三个方法,业务层只调用接口,不关心背后是微信还是支付宝。
下面是一份简化的接口与微信实现的代码示例,支付宝实现结构类似,只是内部调用 SDK 的方式不同:
<?php
interface PaymentGateway
{
public function pay(array $order): array;
public function notify(): mixed;
public function query(string $tradeNo): array;
}
class WechatGateway implements PaymentGateway
{
public function pay(array $order): array
{
$pay = YansongdaPayPay::wechat(config('pay.wechat'));
// 调用 SDK 的网页支付,返回跳转链接
return $pay->web($order)->toArray();
}
public function notify(): mixed
{
$pay = YansongdaPayPay::wechat(config('pay.wechat'));
return $pay->callback();
}
public function query(string $tradeNo): array
{
$pay = YansongdaPayPay::wechat(config('pay.wechat'));
return $pay->find($tradeNo)->toArray();
}
}
在 AppServiceProvider 中利用容器绑定具体实现,可以通过配置动态切换。例如 config('app.payment_default') 为 wechat 就解析 WechatGateway,为 alipay 就解析 AlipayGateway。这样控制器里只需注入 PaymentGateway,完全不用写 if 判断。
这种设计还有个好处:单元测试时可以用 MockGateway 替代真实渠道,避免测试时真正发起支付请求,保证用例稳定且快速。
三、处理异步回调与订单状态更新
微信和支付宝都会在用户支付完成后向回调地址发请求,但数据格式不同。微信是 XML,支付宝是表单或 JSON。由于我们已经在网关内封装了 notify,控制器只需统一接收并校验,然后触发订单完成事件。
回调处理代码示例如下,注意必须使用网关返回的数据,不要直接信任客户端传参:
<?php
Route::post('/payment/notify', function (PaymentGateway $gateway) {
$data = $gateway->notify();
// 此处 $data 已由 SDK 完成签名验签
$tradeNo = $data['out_trade_no'] ?? '';
$order = Order::where('trade_no', $tradeNo)->first();
if ($order && $order->status !== 'paid') {
$order->status = 'paid';
$order->save();
event(new OrderPaid($order));
}
// 返回成功响应,微信要返回特定 XML,支付宝要返回 success
return $gateway->success();
});
很多初学者会把回调逻辑写进控制器并手动解析 XML,结果微信和支付宝各写一套,后期升级证书时极易漏改。通过网关封装,SDK 已经处理了验签和格式转换,你只需要关心业务状态。
另外,务必在路由中排除 CSRF 校验,因为第三方服务器不会带 Laravel 的 token。可以在 VerifyCsrfToken 中间件的 $except 数组加入回调路径,否则会一直返回 419 错误。
四、前端唤起支付与小结
网页支付通常由后端生成支付链接或表单,前端跳转即可。若是微信 JSAPI 或支付宝手机网站,返回的是表单 HTML,直接输出给浏览器就能拉起。统一接口让前端无需区分渠道,只请求 /api/pay 并带上渠道参数。
示例控制器方法:
<?php
public function createPay(Request $request, PaymentGateway $gateway)
{
$order = [
'out_trade_no' => time() . rand(1000, 9999),
'total_amount' => '0.01',
'subject' => '测试商品',
];
$result = $gateway->pay($order);
// 微信返回链接,支付宝返回表单,统一返回给前端处理
return response()->json($result);
}
整体来看,Laravel 集成双支付并不复杂,难点在于把渠道差异隔离在网关层。只要接口抽象得当,后续增加银联或数字人民币也只是新增一个实现类,业务代码零改动。建议把证书和密钥放进环境变量,不要提交到代码仓库,保障资金安全。