Інтеграція платіїної системи 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, тощо
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' => 'Повернення для замовлення #12345',
]);
Тестування
У Sandbox створюються тестові аккаунти покупців та продавців. Sandbox URL: https://api-m.sandbox.paypal.com. Переключення на боевой режим — замена Client ID/Secret та URL. Час активації боевого аккаунту — миттєво після верифікації особистих даних власника.







