Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Paypal Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Confirm your PHP runtime first. v7.0.0 requires PHP 8.5+. Run 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.
  2. Install on a supported runtime:
    composer require puntodev/paypal
    
  3. Publish the config and wire credentials:
    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.
  4. First use case: create an order and redirect the buyer to the approval URL.
    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.

Implementation Patterns

  • Wrap the facade in your own service. Inject a 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 flow vs. webhook flow. Use the 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.
  • Webhook endpoint pattern:
    1. Register a route outside web middleware (no CSRF, no session).
    2. Read the raw request body, verify the signature via the package's verification call.
    3. Persist the event ID in a paypal_webhook_events table with a unique index, inside a transaction, before dispatching a job.
    4. Handle PAYMENT.CAPTURE.COMPLETED, CHECKOUT.ORDER.APPROVED, and refund/dispute events in separate listeners.
  • Idempotency. Pass an idempotency key on every capture/refund call so retries after a timeout don't double-charge. Derive it from your own order UUID, not a random value.
  • Subscriptions. Persist the PayPal subscription ID on your local subscription model, then reconcile status via webhook (BILLING.SUBSCRIPTION.ACTIVATED, ...CANCELLED, PAYMENT.SALE.COMPLETED) rather than polling the API.
  • Testing. Use 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.

Gotchas and Tips

  • The PHP 8.5 requirement is the headline. This is an unusual minimum — 8.5 is not yet generally available, so v7.0.0 is effectively unreleased for most production stacks. Pin explicitly ("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.
  • Version-split your branches. If you maintain multiple apps, some on 8.4 and one on 8.5, you'll be on two package majors. Keep the wrapper interface identical across both so app code doesn't have to branch.
  • Amounts are strings. '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, '.', '').
  • Config caching. After changing .env, run php artisan config:clear. A stale cached paypal.mode silently sends sandbox orders to live credentials — or vice versa.
  • Raw body for webhook verification. Any middleware that re-encodes JSON (or a TrimStrings/ConvertEmptyStringsToNull stack applied to the webhook route) will invalidate the signature check. Exclude the route from the web group.
  • Duplicate webhooks are normal. PayPal retries. Enforce uniqueness on the event ID at the database level, not with a select first — race conditions are real under concurrent retries.
  • Sandbox drift. Sandbox behaviors (especially subscriptions and negative-testing scenarios) don't always match live. Smoke-test one low-value live transaction before launch.
  • Extension points. Prefer binding your own implementation over editing published config; the config file is overwritten only on republish, but the service provider bindings are what your tests should override.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics