Интеграция платёжной системы PayPal на сайт
PayPal — крупнейшая в мире платёжная система с более чем 430 млн активных аккаунтов. Для международной торговли, особенно в направлении США и Западной Европы, кнопка PayPal на сайте — базовое требование. Поддерживает оплату балансом PayPal, банковскими картами (через аккаунт или без), а также кредитование через PayPal Credit.
SDK и режимы интеграции
PayPal предлагает JavaScript SDK, который загружает кнопки оплаты и управляет всем флоу. Серверная часть нужна для создания ордера, его захвата и обработки уведомлений.
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD"></script>
Или через npm в React/Vue проектах:
npm install @paypal/react-paypal-js
React-интеграция через PayPalScriptProvider
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
export function PayPalCheckout({ orderId }: { orderId: number }) {
return (
<PayPalScriptProvider options={{
clientId: import.meta.env.VITE_PAYPAL_CLIENT_ID,
currency: 'USD',
}}>
<PayPalButtons
style={{ layout: 'vertical', color: 'gold', shape: 'rect' }}
createOrder={async () => {
// Создаём ордер на сервере, возвращаем PayPal order ID
const res = await fetch('/api/paypal/create-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId }),
});
const data = await res.json();
return data.paypalOrderId;
}}
onApprove={async (data) => {
// После подтверждения пользователем — захватываем платёж
const res = await fetch('/api/paypal/capture-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paypalOrderId: data.orderID }),
});
const capture = await res.json();
if (capture.status === 'COMPLETED') {
window.location.href = '/payment/success';
}
}}
onError={(err) => {
console.error('PayPal error', err);
}}
/>
</PayPalScriptProvider>
);
}
Серверная часть — создание и захват ордера
use GuzzleHttp\Client;
class PayPalService
{
private string $baseUrl;
private string $accessToken;
public function __construct()
{
$this->baseUrl = env('PAYPAL_MODE') === 'live'
? 'https://api-m.paypal.com'
: 'https://api-m.sandbox.paypal.com';
$this->accessToken = $this->getAccessToken();
}
private function getAccessToken(): string
{
$response = Http::withBasicAuth(env('PAYPAL_CLIENT_ID'), env('PAYPAL_SECRET'))
->asForm()
->post("{$this->baseUrl}/v1/oauth2/token", ['grant_type' => 'client_credentials']);
return $response->json('access_token');
}
public function createOrder(Order $order): string
{
$response = Http::withToken($this->accessToken)
->post("{$this->baseUrl}/v2/checkout/orders", [
'intent' => 'CAPTURE',
'purchase_units' => [[
'reference_id' => (string) $order->id,
'amount' => [
'currency_code' => 'USD',
'value' => number_format($order->total_usd, 2, '.', ''),
],
'description' => "Order #{$order->id}",
]],
]);
return $response->json('id'); // PayPal Order ID
}
public function captureOrder(string $paypalOrderId): array
{
$response = Http::withToken($this->accessToken)
->post("{$this->baseUrl}/v2/checkout/orders/{$paypalOrderId}/capture");
return $response->json();
}
}
// Контроллер
public function createOrder(Request $request): JsonResponse
{
$order = Order::findOrFail($request->input('orderId'));
$paypalOrderId = app(PayPalService::class)->createOrder($order);
$order->update(['paypal_order_id' => $paypalOrderId]);
return response()->json(['paypalOrderId' => $paypalOrderId]);
}
public function captureOrder(Request $request): JsonResponse
{
$paypalOrderId = $request->input('paypalOrderId');
$result = app(PayPalService::class)->captureOrder($paypalOrderId);
if ($result['status'] === 'COMPLETED') {
$captureId = $result['purchase_units'][0]['payments']['captures'][0]['id'];
Order::where('paypal_order_id', $paypalOrderId)->update([
'status' => 'paid',
'capture_id' => $captureId,
]);
}
return response()->json(['status' => $result['status']]);
}
Webhook — асинхронные уведомления
PayPal отправляет события через Webhooks. Подписки настраиваются в Developer Dashboard:
public function webhook(Request $request): Response
{
// Верификация подписи через PayPal API
$verified = Http::withToken($this->accessToken)
->post("{$this->baseUrl}/v1/notifications/verify-webhook-signature", [
'auth_algo' => $request->header('PAYPAL-AUTH-ALGO'),
'cert_url' => $request->header('PAYPAL-CERT-URL'),
'transmission_id' => $request->header('PAYPAL-TRANSMISSION-ID'),
'transmission_sig' => $request->header('PAYPAL-TRANSMISSION-SIG'),
'transmission_time' => $request->header('PAYPAL-TRANSMISSION-TIME'),
'webhook_id' => env('PAYPAL_WEBHOOK_ID'),
'webhook_event' => $request->json()->all(),
])->json('verification_status') === 'SUCCESS';
if (!$verified) return response('Forbidden', 403);
$eventType = $request->json('event_type');
// PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.REFUNDED, etc.
return response('OK', 200);
}
Возвраты
Http::withToken($this->accessToken)
->post("{$this->baseUrl}/v2/payments/captures/{$captureId}/refund", [
'amount' => [
'value' => '7.50',
'currency_code' => 'USD',
],
'note_to_payer' => 'Refund for order #12345',
]);
Тестирование
В Sandbox создаются тестовые аккаунты покупателей и продавцов. Sandbox URL: https://api-m.sandbox.paypal.com. Переключение на боевой режим — замена Client ID/Secret и URL. Время активации боевого аккаунта — мгновенно после верификации личных данных владельца.







