我想将表格与 PayPal 集成。这意味着,当有人填写表格、选择付款方式并填写付款详细信息时,他们的卡余额将会减少。我不想使用 PayPal 的标准结帐,而是使用高级结帐,以便用户无需拥有 PayPal 帐户即可付款。
我创建了如下代码。
web.php
Route::get('/', function () {
return view('welcome');
});
Route::post('/create-order', [PaymentController::class, 'createOrder'])->name('create.order');
Route::post('/capture-order', [PaymentController::class, 'captureOrder'])->name('capture.order');
PaymentController.php
<?php
namespace App\Http\Controllers;
use App\Services\PayPalService;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
private PayPalService $paypal;
public function __construct(PayPalService $paypal)
{
$this->paypal = $paypal;
}
public function createOrder(Request $request)
{
try {
$order = $this->paypal->createOrder($request);
return response()->json($order);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
public function captureOrder(Request $request)
{
try {
$result = $this->paypal->captureOrder($request->orderId);
return response()->json($result);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
}
PayPalService.php
<?php
namespace App\Services;
use App\Models\User;
use App\Models\Order;
use App\Models\Ticket;
use Core\Request\Request;
use Illuminate\Support\Facades\Http;
class PayPalService
{
private string $clientId;
private string $secret;
private string $apiUrl;
public function __construct()
{
$this->clientId = config('services.paypal.client_id');
$this->secret = config('services.paypal.secret');
$this->apiUrl = config('services.paypal.api_url');
}
private function getAccessToken(): string
{
$response = Http::withBasicAuth($this->clientId, $this->secret)
->asForm()
->post("{$this->apiUrl}/v1/oauth2/token", [
'grant_type' => 'client_credentials',
]);
if (!$response->successful()) {
throw new \Exception('Failed to get PayPal access token');
}
return $response->json()['access_token'];
}
public function createOrder($request)
{
$accessToken = $this->getAccessToken();
$ticket = Ticket::first();
$user = User::updateOrCreate([
'email' => $request->email,
], [
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt('password'),
]);
$order = Order::create([
'ticket_id' => $ticket->id,
'user_id' => $user->id,
'order_code' => uniqid(),
'amount' => $ticket->price,
'payment_method' => 'paypal',
]);
$response = Http::withToken($accessToken)
->post("{$this->apiUrl}/v2/checkout/orders", [
'intent' => 'CAPTURE',
'purchase_units' => [[
'amount' => [
'value' => number_format($ticket->price, 2, '.', ''),
'currency_code' => 'USD',
],
]],
]);
if (!$response->successful()) {
throw new \Exception('Failed to create PayPal order');
}
return $response->json();
}
public function captureOrder($orderId)
{
$accessToken = $this->getAccessToken();
$response = Http::withToken($accessToken)
->post("{$this->apiUrl}/v2/checkout/orders/{$orderId}/capture");
// \Log::info(['captureOrder' => $response]);
if (!$response->successful()) {
throw new \Exception('Failed to capture PayPal order');
}
return $response->json();
}
}
welcome.blade.php
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel PayPal</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3">
<h1 class="text-center">
Laravel PayPal
</h1>
<div class="card mb-3">
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">Full name</label>
<input type="text" class="form-control" id="name" name="name">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email">
</div>
</div>
</div>
<div id="paypal-button"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://www.paypal.com/sdk/js?client-id={{ env('PAYPAL_CLIENT_ID') }}&components=buttons"></script>
<script>
paypal.Buttons({
createOrder: async () => {
const response = await fetch('{{ route('create.order') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
},
body: JSON.stringify(
{
name: document.getElementById('name').value,
email: document.getElementById('email').value,
}
)
});
const order = await response.json();
return order.id;
},
onApprove: async (data) => {
const response = await fetch('{{ route('capture.order') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
},
body: JSON.stringify({ orderId: data.orderID })
});
const result = await response.json();
alert('Payment successful: ' + result.id);
},
}).render('#paypal-button');
</script>
</body>
</html>
我在访问 capture.order 端点时失败了。我该如何解决这个问题?
->post("{$this->apiUrl}/v2/checkout/orders/{$orderId}/capture");
尝试添加
,""
或其他指定空主体的方式