Laravel 13 支付集成详解

摘要

本文将介绍在 Laravel 13 中集成 Stripe 和 PayPal 支付的方法,包括:

  • Stripe 支付集成
  • PayPal 支付集成
  • 支付流程设计
  • Webhook 处理
  • 实战案例与最佳实践

本文适合希望集成支付功能的 Laravel 开发者。

1. Stripe 集成

1.1 安装 Stripe SDK

1
composer require stripe/stripe-php

1.2 配置 Stripe

1
2
3
4
5
6
// config/services.php
'stripe' => [
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
],

1.3 支付意图

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
<?php

namespace App\Services;

use Stripe\Stripe;
use Stripe\PaymentIntent;

class StripeService
{
public function __construct()
{
Stripe::setApiKey(config('services.stripe.secret'));
}

public function createPaymentIntent(int $amount, string $currency = 'usd'): array
{
$intent = PaymentIntent::create([
'amount' => $amount * 100, // 分为单位
'currency' => $currency,
'metadata' => [
'order_id' => request()->order_id,
],
]);

return [
'client_secret' => $intent->client_secret,
'intent_id' => $intent->id,
];
}
}

1.4 控制器

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

namespace App\Http\Controllers;

use App\Services\StripeService;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
public function __construct(
protected StripeService $stripe
) {}

public function createIntent(Request $request)
{
$validated = $request->validate([
'amount' => 'required|integer|min:1',
'order_id' => 'required|exists:orders,id',
]);

$intent = $this->stripe->createPaymentIntent($validated['amount']);

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

1.5 Webhook 处理

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
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Stripe\Webhook;
use App\Models\Order;

class StripeWebhookController extends Controller
{
public function handle(Request $request)
{
$payload = $request->getContent();
$sigHeader = $request->header('Stripe-Signature');
$secret = config('services.stripe.webhook_secret');

try {
$event = Webhook::constructEvent(
$payload,
$sigHeader,
$secret
);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}

switch ($event->type) {
case 'payment_intent.succeeded':
$this->handlePaymentSuccess($event->data->object);
break;

case 'payment_intent.payment_failed':
$this->handlePaymentFailed($event->data->object);
break;
}

return response()->json(['status' => 'success']);
}

protected function handlePaymentSuccess($paymentIntent)
{
$orderId = $paymentIntent->metadata->order_id;

Order::where('id', $orderId)->update([
'status' => 'paid',
'paid_at' => now(),
]);
}

protected function handlePaymentFailed($paymentIntent)
{
$orderId = $paymentIntent->metadata->order_id;

Order::where('id', $orderId)->update([
'status' => 'failed',
]);
}
}

2. PayPal 集成

2.1 安装 PayPal SDK

1
composer require paypal/rest-api-sdk-php

2.2 配置 PayPal

1
2
3
4
5
6
// config/services.php
'paypal' => [
'client_id' => env('PAYPAL_CLIENT_ID'),
'secret' => env('PAYPAL_SECRET'),
'mode' => env('PAYPAL_MODE', 'sandbox'),
],

2.3 PayPal 服务

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
<?php

namespace App\Services;

use PayPal\Rest\ApiContext;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Api\Amount;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;

class PayPalService
{
protected $apiContext;

public function __construct()
{
$this->apiContext = new ApiContext(
new OAuthTokenCredential(
config('services.paypal.client_id'),
config('services.paypal.secret')
)
);

$this->apiContext->setConfig([
'mode' => config('services.paypal.mode'),
]);
}

public function createPayment(float $amount, string $currency = 'USD'): string
{
$payer = new Payer();
$payer->setPaymentMethod('paypal');

$amountObj = new Amount();
$amountObj->setTotal($amount);
$amountObj->setCurrency($currency);

$transaction = new Transaction();
$transaction->setAmount($amountObj);

$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl(route('paypal.success'))
->setCancelUrl(route('paypal.cancel'));

$payment = new Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setTransactions([$transaction])
->setRedirectUrls($redirectUrls);

$payment->create($this->apiContext);

return $payment->getApprovalLink();
}

public function executePayment(string $paymentId, string $payerId): bool
{
$payment = Payment::get($paymentId, $this->apiContext);

$execution = new PaymentExecution();
$execution->setPayerId($payerId);

$result = $payment->execute($execution, $this->apiContext);

return $result->getState() === 'approved';
}
}

3. 实战案例

3.1 订单支付流程

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
<?php

namespace App\Services;

use App\Models\Order;
use App\Services\StripeService;

class OrderPaymentService
{
public function __construct(
protected StripeService $stripe
) {}

public function process(Order $order): array
{
$intent = $this->stripe->createPaymentIntent(
$order->total,
$order->currency
);

$order->update([
'payment_intent_id' => $intent['intent_id'],
'payment_status' => 'pending',
]);

return $intent;
}
}

4. 最佳实践

4.1 使用 Webhook

1
2
// 推荐:使用 Webhook 更新订单状态
// 不推荐:依赖前端回调

4.2 验证金额

1
2
3
4
// 服务端验证金额
if ($request->amount !== $order->total) {
throw new InvalidAmountException();
}

5. 总结

支付集成需要注意:

  1. 安全验证:Webhook 签名验证
  2. 金额验证:服务端验证金额
  3. 状态同步:及时更新订单状态
  4. 错误处理:完善的错误处理

参考资料