puntodev/paypal
A Laravel package that wraps PayPal payment APIs, letting you add checkout flows, payment processing, and transaction handling to your application through a simple, framework-friendly integration.
php -v and composer why-not php 8.5 before bumping. If you're on 8.4 (currently the norm), stay on ^6.0 — composer require puntodev/paypal:^6.0 — and treat v7 as blocked until your runtime, CI image, and hosting platform all ship 8.5.composer require puntodev/paypal
php artisan vendor:publish --tag=paypal-config
Then set PAYPAL_CLIENT_ID, PAYPAL_SECRET, and PAYPAL_MODE=sandbox in .env. Keep sandbox and live credentials in separate env blocks — never let a staging .env inherit live keys.use PuntoDev\PayPal\Facades\PayPal;
$order = PayPal::createOrder([
'intent' => 'CAPTURE',
'purchase_units' => [[
'amount' => ['currency_code' => 'EUR', 'value' => '49.90'],
]],
]);
return redirect($order['links']['approve']['href'] ?? $order->approveUrl());
Look first at the package's config file and its Facade/service provider — that tells you what is bound in the container, which is what you'll mock in tests.
PayPalGateway interface into controllers/listeners rather than calling PayPal:: statically everywhere. It makes swapping sandbox fakes and unit-testing checkout flows far simpler.return_url/cancel_url for UX feedback, but treat the webhook as the source of truth for order state. Don't fulfill an order just because the browser hit your success page.web middleware (no CSRF, no session).paypal_webhook_events table with a unique index, inside a transaction, before dispatching a job.PAYMENT.CAPTURE.COMPLETED, CHECKOUT.ORDER.APPROVED, and refund/dispute events in separate listeners.BILLING.SUBSCRIPTION.ACTIVATED, ...CANCELLED, PAYMENT.SALE.COMPLETED) rather than polling the API.Http::fake() to stub PayPal REST responses, or the package's fake if it ships one; assert you redirect on success and that your webhook listener is idempotent when the same payload is replayed twice."puntodev/paypal": "^6.0") so a composer update doesn't try to pull v7 and fail resolution. Watch your Docker base image, CI matrix, and any shared hosting PHP version.'value' => 49.90 vs '49.90' will bite you — PayPal expects decimal strings, and float formatting can produce 49.9 or locale-dependent separators. Format with number_format($cents / 100, 2, '.', '')..env, run php artisan config:clear. A stale cached paypal.mode silently sends sandbox orders to live credentials — or vice versa.TrimStrings/ConvertEmptyStringsToNull stack applied to the webhook route) will invalidate the signature check. Exclude the route from the web group.select first — race conditions are real under concurrent retries.How can I help you explore Laravel packages today?