Laravel 最优雅的支付系统

摘要

本文全面解析 Laravel 支付系统的构建方法,包括架构设计、多支付网关集成、最佳实践和扩展功能。通过本文,您将学习:

  • Laravel 支付系统的核心架构和设计模式
  • 如何使用 Omnipay、Laravel Pay 和 Cashier 等流行支付包
  • 构建统一支付网关抽象层的具体实现
  • 支付系统的安全考虑、性能优化和可维护性
  • 如何集成多种支付方式(支付宝、微信支付、PayPal 等)
  • 订阅支付、支付分账和多币种支持等高级功能

本文适合希望在 Laravel 项目中构建或优化支付系统的开发者,提供了详细的技术指南和实战示例。

1. 引言

在现代 Web 应用中,支付系统是电子商务和订阅服务的核心组件。一个优雅的支付系统不仅要功能完善,还要易于集成、扩展和维护。Laravel 作为 PHP 生态中最流行的框架之一,提供了丰富的工具和包来构建高质量的支付系统。

本文将介绍如何在 Laravel 中构建一个最优雅的支付系统,包括架构设计、常用包推荐、具体实现和最佳实践。如果您还不熟悉 Laravel 的基本概念,可以参考我们的 Laravel 12 教程 - Web 开发实战入门 文章,了解 Laravel 的核心功能和使用方法。

2. Laravel 支付系统架构设计

2.1 Laravel 支付系统核心组件

一个优雅的 Laravel 支付系统应该包含以下核心组件:

  • 支付网关抽象层:统一不同支付渠道的接口
  • 订单管理:处理订单创建、状态更新等
  • 支付记录:跟踪每笔交易的详细信息
  • 退款处理:处理退款请求和状态更新
  • 通知处理:处理支付渠道的异步通知
  • 支付验证:验证支付的有效性和安全性

2.2 Laravel 支付系统架构模式

推荐使用以下架构模式:

  • 策略模式:用于不同支付方式的实现,通过统一接口隔离不同支付网关的具体实现细节
  • 工厂模式:用于创建不同支付网关实例,根据配置或用户选择动态创建相应的支付网关
  • 观察者模式:用于处理支付状态变化的事件,如支付成功后发送通知、更新订单状态等
  • 仓库模式:用于数据访问层的抽象,简化数据操作并提高可测试性

设计模式应用示例

策略模式 + 工厂模式组合使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 策略接口
interface PaymentGatewayInterface {
public function pay(array $params);
public function refund(array $params);
}

// 具体策略实现
class AlipayGateway implements PaymentGatewayInterface {
public function pay(array $params) {
// 支付宝支付实现
}

public function refund(array $params) {
// 支付宝退款实现
}
}

class WechatGateway implements PaymentGatewayInterface {
public function pay(array $params) {
// 微信支付实现
}

public function refund(array $params) {
// 微信退款实现
}
}

// 工厂类
class PaymentGatewayFactory {
public static function create($gateway) {
switch ($gateway) {
case 'alipay':
return new AlipayGateway();
case 'wechat':
return new WechatGateway();
default:
throw new \InvalidArgumentException("Unsupported gateway: {$gateway}");
}
}
}

观察者模式应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 支付状态变化事件
class PaymentStatusChanged {
public $payment;
public $oldStatus;
public $newStatus;

public function __construct($payment, $oldStatus, $newStatus) {
$this->payment = $payment;
$this->oldStatus = $oldStatus;
$this->newStatus = $newStatus;
}
}

// 事件监听器
class PaymentNotificationListener {
public function handle(PaymentStatusChanged $event) {
if ($event->newStatus === 'succeeded') {
// 发送支付成功通知
$this->sendNotification($event->payment);
}
}

protected function sendNotification($payment) {
// 实现通知逻辑
}
}

// 注册监听器
Event::listen(PaymentStatusChanged::class, PaymentNotificationListener::class);

3. Laravel 推荐的支付包

3.1 Laravel Omnipay 集成

Omnipay 是一个功能强大的 PHP 支付处理库,提供了统一的支付网关接口。

特点

  • 支持 100+ 种支付网关
  • 统一的 API 接口
  • 易于扩展和定制
  • 活跃的社区支持

安装

1
composer require league/omnipay

3.2 Laravel Pay 集成

Laravel Pay 是基于 Omnipay 开发的 Laravel 支付扩展包,提供了更符合 Laravel 风格的 API。

特点

  • 符合 Laravel 优雅风格
  • 支持多种支付方式
  • 内置支付记录管理
  • 提供简洁的配置方式

安装

1
composer require yansongda/laravel-pay

3.3 Laravel Cashier 集成

Cashier 是 Laravel 官方提供的订阅计费管理包,专为处理订阅服务设计。

特点

  • 官方维护,与 Laravel 深度集成
  • 支持 Stripe 和 Paddle
  • 内置订阅管理功能
  • 自动处理发票和付款

安装

1
2
3
4
5
# Stripe 版本
composer require laravel/cashier

# Paddle 版本
composer require laravel/cashier-paddle

4. 构建优雅的 Laravel 支付系统

4.1 Laravel 支付系统目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
app/
├── Http/
│ ├── Controllers/
│ │ └── PaymentController.php
│ ├── Middleware/
│ └── Requests/
│ └── PaymentRequest.php
├── Models/
│ ├── Order.php
│ ├── Payment.php
│ └── Refund.php
├── Services/
│ └── Payment/
│ ├── Gateways/
│ │ ├── AlipayGateway.php
│ │ ├── WechatGateway.php
│ │ └── PayPalGateway.php
│ ├── Contracts/
│ │ └── GatewayInterface.php
│ ├── Factory.php
│ ├── PaymentService.php
│ └── Events/
│ ├── PaymentCreated.php
│ ├── PaymentSucceeded.php
│ └── PaymentFailed.php
└── Providers/
└── PaymentServiceProvider.php

4.2 Laravel 支付网关抽象层

首先,定义支付网关接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Services\Payment\Contracts;

interface GatewayInterface
{
/**
* Laravel 支付 - 创建支付
*/
public function create(array $params);

/**
* Laravel 支付 - 处理支付回调
*/
public function handleCallback($data);

/**
* Laravel 支付 - 退款
*/
public function refund(array $params);

/**
* Laravel 支付 - 查询支付状态
*/
public function query(array $params);
}

然后,实现具体的支付网关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php

namespace App\Services\Payment\Gateways;

use App\Services\Payment\Contracts\GatewayInterface;
use Omnipay\Omnipay;

class AlipayGateway implements GatewayInterface
{
protected $gateway;

public function __construct()
{
$this->gateway = Omnipay::create('Alipay_AopApp');
$this->gateway->setAppId(config('payment.alipay.app_id'));
$this->gateway->setPrivateKey(config('payment.alipay.private_key'));
$this->gateway->setAppId(config('payment.alipay.app_id'));
$this->gateway->setAlipayPublicKey(config('payment.alipay.public_key'));
$this->gateway->setSignType('RSA2');
$this->gateway->setNotifyUrl(route('payment.alipay.notify'));
$this->gateway->setReturnUrl(route('payment.alipay.return'));
}

public function create(array $params)
{
$order = [
'out_trade_no' => $params['order_no'],
'total_amount' => $params['amount'],
'subject' => $params['subject'],
'body' => $params['body'] ?? $params['subject'],
];

$response = $this->gateway->purchase($order)->send();

return $response->getData();
}

public function handleCallback($data)
{
// 处理支付宝回调
$response = $this->gateway->completePurchase([
'request_params' => $data
])->send();

if ($response->isSuccessful() && $response->isTradeStatusOk()) {
return [
'success' => true,
'order_no' => $data['out_trade_no'],
'transaction_id' => $data['trade_no'],
'amount' => $data['total_amount'],
];
}

return ['success' => false];
}

public function refund(array $params)
{
$response = $this->gateway->refund([
'out_trade_no' => $params['order_no'],
'refund_amount' => $params['amount'],
'out_request_no' => $params['refund_no'],
])->send();

return $response->getData();
}

public function query(array $params)
{
$response = $this->gateway->query([
'out_trade_no' => $params['order_no'],
])->send();

return $response->getData();
}
}

4.3 Laravel 支付工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Services\Payment;

use App\Services\Payment\Gateways\AlipayGateway;
use App\Services\Payment\Gateways\WechatGateway;
use App\Services\Payment\Gateways\PayPalGateway;

class Factory
{
public static function make($gateway)
{
switch (strtolower($gateway)) {
case 'alipay':
return new AlipayGateway();
case 'wechat':
return new WechatGateway();
case 'paypal':
return new PayPalGateway();
default:
throw new \InvalidArgumentException("Unsupported gateway: {$gateway}");
}
}
}

4.4 Laravel 支付服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<?php

namespace App\Services\Payment;

use App\Services\Payment\Events\PaymentCreated;
use App\Services\Payment\Events\PaymentSucceeded;
use App\Services\Payment\Events\PaymentFailed;
use App\Models\Order;
use App\Models\Payment;

class PaymentService
{
/**
* Laravel 支付 - 创建支付
*/
public function createPayment($orderId, $gateway, $amount, $extra = [])
{
$order = Order::findOrFail($orderId);

$payment = Payment::create([
'order_id' => $order->id,
'gateway' => $gateway,
'amount' => $amount,
'order_no' => $this->generateOrderNo(),
'status' => 'pending',
'extra' => $extra,
]);

event(new PaymentCreated($payment));

$gatewayInstance = Factory::make($gateway);
$result = $gatewayInstance->create([
'order_no' => $payment->order_no,
'amount' => $amount,
'subject' => $order->subject,
'body' => $order->body,
...$extra,
]);

$payment->update(['payload' => $result]);

return [
'payment' => $payment,
'gateway_data' => $result,
];
}

/**
* Laravel 支付 - 处理支付回调
*/
public function handleCallback($gateway, $data)
{
$gatewayInstance = Factory::make($gateway);
$result = $gatewayInstance->handleCallback($data);

if ($result['success']) {
$payment = Payment::where('order_no', $result['order_no'])->first();

if ($payment && $payment->status === 'pending') {
$payment->update([
'status' => 'succeeded',
'transaction_id' => $result['transaction_id'],
'completed_at' => now(),
]);

$payment->order->update(['status' => 'paid']);

event(new PaymentSucceeded($payment));
}
} else {
event(new PaymentFailed($data));
}

return $result;
}

/**
* Laravel 支付 - 处理退款
*/
public function refund($paymentId, $amount, $reason = '')
{
$payment = Payment::findOrFail($paymentId);

if ($payment->status !== 'succeeded') {
throw new \Exception('Only succeeded payments can be refunded');
}

$refundNo = $this->generateRefundNo();

$gatewayInstance = Factory::make($payment->gateway);
$result = $gatewayInstance->refund([
'order_no' => $payment->order_no,
'amount' => $amount,
'refund_no' => $refundNo,
'reason' => $reason,
]);

$refund = $payment->refunds()->create([
'refund_no' => $refundNo,
'amount' => $amount,
'reason' => $reason,
'status' => $result['success'] ? 'succeeded' : 'failed',
'payload' => $result,
]);

if ($result['success']) {
$payment->update(['refunded_amount' => $payment->refunded_amount + $amount]);
}

return $refund;
}

/**
* Laravel 支付 - 生成订单号
*/
protected function generateOrderNo()
{
return date('YmdHis') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}

/**
* Laravel 支付 - 生成退款单号
*/
protected function generateRefundNo()
{
return 'REFUND_' . date('YmdHis') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}
}

4.5 Laravel 支付模型定义

Order 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
protected $fillable = [
'user_id',
'subject',
'body',
'amount',
'status',
'extra',
];

protected $casts = [
'extra' => 'array',
];

public function payments()
{
return $this->hasMany(Payment::class);
}
}

Payment 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Payment extends Model
{
const STATUS_PENDING = 'pending';
const STATUS_SUCCEEDED = 'succeeded';
const STATUS_FAILED = 'failed';
const STATUS_REFUNDED = 'refunded';

protected $fillable = [
'order_id',
'gateway',
'order_no',
'transaction_id',
'amount',
'refunded_amount',
'status',
'payload',
'extra',
'completed_at',
];

protected $casts = [
'payload' => 'array',
'extra' => 'array',
'completed_at' => 'datetime',
];

public function order()
{
return $this->belongsTo(Order::class);
}

public function refunds()
{
return $this->hasMany(Refund::class);
}
}

Refund 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Refund extends Model
{
const STATUS_PENDING = 'pending';
const STATUS_SUCCEEDED = 'succeeded';
const STATUS_FAILED = 'failed';

protected $fillable = [
'payment_id',
'refund_no',
'amount',
'reason',
'status',
'payload',
'completed_at',
];

protected $casts = [
'payload' => 'array',
'completed_at' => 'datetime',
];

public function payment()
{
return $this->belongsTo(Payment::class);
}
}

4.6 Laravel 支付控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

namespace App\Http\Controllers;

use App\Http\Requests\PaymentRequest;
use App\Services\Payment\PaymentService;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
protected $paymentService;

public function __construct(PaymentService $paymentService)
{
$this->paymentService = $paymentService;
}

public function create(PaymentRequest $request)
{
$data = $request->validated();

$result = $this->paymentService->createPayment(
$data['order_id'],
$data['gateway'],
$data['amount'],
$data['extra'] ?? []
);

return response()->json($result);
}

public function alipayNotify(Request $request)
{
$result = $this->paymentService->handleCallback('alipay', $request->all());

return $result['success'] ? 'success' : 'fail';
}

public function wechatNotify(Request $request)
{
$data = $request->all();
$result = $this->paymentService->handleCallback('wechat', $data);

return $result['success'] ?
'<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>' :
'<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>';
}

public function refund(Request $request)
{
$refund = $this->paymentService->refund(
$request->input('payment_id'),
$request->input('amount'),
$request->input('reason')
);

return response()->json($refund);
}
}

4.7 Laravel 支付事件定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

namespace App\Services\Payment\Events;

use App\Models\Payment;
use Illuminate\Queue\SerializesModels;

class PaymentCreated
{
use SerializesModels;

public $payment;

public function __construct(Payment $payment)
{
$this->payment = $payment;
}
}

class PaymentSucceeded
{
use SerializesModels;

public $payment;

public function __construct(Payment $payment)
{
$this->payment = $payment;
}
}

class PaymentFailed
{
use SerializesModels;

public $data;

public function __construct($data)
{
$this->data = $data;
}
}

4.8 Laravel 支付服务提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\Payment\PaymentService;

class PaymentServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->singleton(PaymentService::class, function ($app) {
return new PaymentService();
});
}

/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__.'/../Config/payment.php' => config_path('payment.php'),
]);
}
}

4.9 Laravel 支付配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

return [
'alipay' => [
'app_id' => env('ALIPAY_APP_ID'),
'private_key' => env('ALIPAY_PRIVATE_KEY'),
'public_key' => env('ALIPAY_PUBLIC_KEY'),
'sandbox' => env('ALIPAY_SANDBOX', false),
],

'wechat' => [
'app_id' => env('WECHAT_APP_ID'),
'mch_id' => env('WECHAT_MCH_ID'),
'key' => env('WECHAT_KEY'),
'cert_path' => env('WECHAT_CERT_PATH'),
'key_path' => env('WECHAT_KEY_PATH'),
'sandbox' => env('WECHAT_SANDBOX', false),
],

'paypal' => [
'client_id' => env('PAYPAL_CLIENT_ID'),
'secret' => env('PAYPAL_SECRET'),
'sandbox' => env('PAYPAL_SANDBOX', false),
],
];

5. Laravel 支付系统最佳实践

5.1 Laravel 支付系统安全考虑

  • 使用 HTTPS:确保所有支付相关的请求都通过 HTTPS 传输,防止中间人攻击
  • 验证签名:严格验证支付网关的回调签名,使用官方提供的 SDK 或验证工具
  • 防重放攻击:使用唯一的订单号和时间戳防止重复支付,设置合理的订单过期时间
  • 数据加密:敏感信息如支付凭证应加密存储,使用 Laravel 的加密功能或环境变量
  • 权限控制:限制支付相关操作的访问权限,使用 Laravel 的中间件和策略进行授权
  • 输入验证:对所有支付相关的输入进行严格验证,使用 Laravel 的表单验证功能
  • 日志记录:记录所有支付操作和异常,便于审计和问题排查
  • 定期安全审计:定期检查支付系统的安全配置和依赖包的安全更新

安全实现示例

签名验证实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 在支付回调处理中验证签名
public function verifySignature($data, $signature) {
// 获取配置的密钥
$key = config('payment.' . $this->gateway . '.key');

// 根据支付网关类型选择不同的签名验证方法
switch ($this->gateway) {
case 'alipay':
return $this->verifyAlipaySignature($data, $signature, $key);
case 'wechat':
return $this->verifyWechatSignature($data, $signature, $key);
default:
return false;
}
}

// 支付宝签名验证
protected function verifyAlipaySignature($data, $signature, $key) {
// 移除 sign 参数
unset($data['sign']);
unset($data['sign_type']);

// 按照支付宝要求排序参数
ksort($data);

// 生成待签名字符串
$stringToSign = http_build_query($data, '', '&');

// 使用 RSA2 算法验证签名
$publicKey = openssl_pkey_get_public($key);
$result = openssl_verify($stringToSign, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256);

openssl_free_key($publicKey);
return $result === 1;
}

5.2 Laravel 支付系统性能优化

  • 使用队列:将支付处理和通知处理放入队列,使用 Laravel 的队列系统异步处理
  • 缓存配置:缓存支付网关配置,减少重复加载,使用 Laravel 的缓存系统
  • 数据库索引:为支付表的关键字段添加索引,如订单号、交易号、状态等
  • 批量操作:对于批量退款等操作,使用批量处理,减少数据库查询次数
  • 连接池:对于高并发场景,使用数据库连接池和 Redis 连接池
  • 代码优化:优化支付处理逻辑,减少不必要的计算和查询
  • 资源限制:合理设置 PHP 和服务器的资源限制,避免性能瓶颈

性能优化实现示例

使用队列处理支付通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 在支付成功事件监听器中使用队列
class PaymentSucceededListener implements ShouldQueue
{
use InteractsWithQueue;

public function handle(PaymentSucceeded $event)
{
$payment = $event->payment;

// 发送邮件通知
Mail::to($payment->order->user->email)->send(
new PaymentSuccessMail($payment)
);

// 发送短信通知
$this->sendSmsNotification($payment);

// 更新订单状态
$payment->order->update(['status' => 'paid']);
}

protected function sendSmsNotification($payment)
{
// 实现短信发送逻辑
}
}

// 注册队列监听器
Event::listen(PaymentSucceeded::class, PaymentSucceededListener::class);

缓存支付网关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在支付网关工厂中使用缓存
class PaymentGatewayFactory
{
public static function create($gateway)
{
// 尝试从缓存获取配置
$configKey = "payment.gateway.{$gateway}";
$config = Cache::remember($configKey, 3600, function () use ($gateway) {
return config("payment.{$gateway}");
});

// 根据配置创建支付网关
switch ($gateway) {
case 'alipay':
return new AlipayGateway($config);
case 'wechat':
return new WechatGateway($config);
default:
throw new \InvalidArgumentException("Unsupported gateway: {$gateway}");
}
}
}

5.3 Laravel 支付系统可维护性

  • 代码规范:遵循 Laravel 代码风格和 PSR 规范
  • 文档完善:为支付系统编写详细的文档
  • 日志记录:记录所有支付相关的操作和错误
  • 监控告警:监控支付系统的运行状态和异常
  • 测试覆盖:编写单元测试和集成测试

6. Laravel 集成多种支付方式

6.1 Laravel 支付前端集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div class="payment-methods">
<div
v-for="method in paymentMethods"
:key="method.id"
class="payment-method"
@click="selectPaymentMethod(method)"
>
<img :src="method.icon" :alt="method.name">
<span>{{ method.name }}</span>
</div>

<button v-if="selectedMethod" @click="submitPayment" class="submit-btn">
确认支付
</button>
</div>
</template>

<script>
export default {
data() {
return {
paymentMethods: [
{ id: 'alipay', name: '支付宝', icon: '/icons/alipay.png' },
{ id: 'wechat', name: '微信支付', icon: '/icons/wechat.png' },
{ id: 'paypal', name: 'PayPal', icon: '/icons/paypal.png' },
],
selectedMethod: null,
orderId: 1,
amount: 99.99,
};
},
methods: {
selectPaymentMethod(method) {
this.selectedMethod = method;
},
async submitPayment() {
try {
const response = await axios.post('/api/payments', {
order_id: this.orderId,
gateway: this.selectedMethod.id,
amount: this.amount,
});

const { payment, gateway_data } = response.data;

// 根据支付方式处理跳转或唤起支付
if (this.selectedMethod.id === 'alipay') {
// 支付宝 H5 支付
window.location.href = gateway_data.url;
} else if (this.selectedMethod.id === 'wechat') {
// 微信支付,显示二维码
this.showWechatQrCode(gateway_data.code_url);
} else if (this.selectedMethod.id === 'paypal') {
// PayPal 支付
window.location.href = gateway_data.redirect_url;
}
} catch (error) {
console.error('支付失败:', error);
}
},
showWechatQrCode(codeUrl) {
// 显示微信支付二维码
this.$alert(
`<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(codeUrl)}" />`,
'微信支付',
{
html: true,
confirmButtonText: '已支付',
callback: (action) => {
if (action === 'confirm') {
this.checkPaymentStatus();
}
}
}
);
},
async checkPaymentStatus() {
// 轮询检查支付状态
// ...
},
},
};
</script>

6.2 Laravel 支付后端路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PaymentController;

Route::prefix('api')->group(function () {
Route::post('payments', [PaymentController::class, 'create']);
Route::post('payments/refund', [PaymentController::class, 'refund']);
});

// 支付回调路由
Route::prefix('payment')->group(function () {
Route::post('alipay/notify', [PaymentController::class, 'alipayNotify']);
Route::get('alipay/return', [PaymentController::class, 'alipayReturn']);
Route::post('wechat/notify', [PaymentController::class, 'wechatNotify']);
Route::post('paypal/notify', [PaymentController::class, 'paypalNotify']);
});

7. Laravel 支付扩展功能

7.1 Laravel 订阅支付

对于需要定期扣费的订阅服务,可以使用 Laravel Cashier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
use Billable;
}

// 创建订阅
$user->newSubscription('default', 'price_monthly')->create($paymentMethod);

// 取消订阅
$user->subscription('default')->cancel();

// 恢复订阅
$user->subscription('default')->resume();

7.2 Laravel 支付分账

对于需要分账的场景,可以扩展支付网关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

class AlipayGateway extends Gateway
{
// ...

public function transfer(array $params)
{
$response = $this->gateway->transfer([
'out_biz_no' => $params['order_no'],
'trans_amount' => $params['amount'],
'payee_type' => $params['type'],
'payee_account' => $params['account'],
'payer_show_name' => $params['payer_name'],
'payee_real_name' => $params['payee_name'],
])->send();

return $response->getData();
}
}

7.3 Laravel 支付多币种支持

对于需要支持多币种的场景,可以在支付服务中添加币种转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

class PaymentService
{
// ...

public function createPayment($orderId, $gateway, $amount, $currency = 'CNY', $extra = [])
{
// 币种转换
if ($currency !== 'CNY' && in_array($gateway, ['alipay', 'wechat'])) {
$amount = $this->convertCurrency($amount, $currency, 'CNY');
}

// 后续处理...
}

protected function convertCurrency($amount, $from, $to)
{
// 调用汇率转换 API 或使用固定汇率
// ...
return $convertedAmount;
}
}

8. Laravel 支付系统总结

构建一个优雅的 Laravel 支付系统需要考虑以下几个方面:

  1. 架构设计:使用合理的设计模式和目录结构,确保系统的可扩展性和可维护性
  2. 网关抽象:通过抽象层统一不同支付渠道的接口,简化集成和切换
  3. 安全性:严格验证支付回调,防止欺诈和攻击
  4. 用户体验:提供流畅的支付流程和清晰的错误提示
  5. 监控和日志:记录所有支付相关的操作和错误,便于排查问题
  6. 测试覆盖:编写充分的测试用例,确保系统的稳定性

通过本文介绍的方案,你可以构建一个功能完善、易于扩展、安全可靠的 Laravel 支付系统,为你的应用提供优雅的支付体验。

9. Laravel 支付参考资料

官方文档

第三方支付库

支付网关文档

相关教程

技术博客和资源

安全资源