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

Laravel Paypal Laravel Package

naif/laravel-paypal

A Laravel wrapper for the PayPal REST API that makes it easy to create payments, redirect users for approval, and execute transactions. Clean, fluent methods, configurable credentials, and simple setup let you add PayPal checkout to any Laravel app quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up

composer require naif/laravel-paypal
php artisan vendor:publish --provider="Naif\PayPal\PayPalServiceProvider"

The provider/facade are auto-discovered on Laravel 5.5+. On older apps, register Naif\PayPal\PayPalServiceProvider in config/app.php and alias PayPal to Naif\PayPal\Facades\PayPal. Publishing drops a config/paypal.php — that file is your first stop: it defines the client ID, client secret, and the sandbox/live toggle. Map those to env vars (PAYPAL_CLIENT_ID, PAYPAL_SECRET, PAYPAL_MODE) and never commit the published values.

Where to look first

  1. config/paypal.php — confirm the exact key names and the mode switch before writing any code.
  2. The facade class and its single underlying client class — this package is thin, so the method signatures are the documentation. Read them; they tell you whether dates are DateTime, strings, or Carbon.
  3. PayPal's Reporting/Balances and Transaction Search REST docs — the package is a wrapper around those two endpoints, so PayPal's docs are authoritative for parameters, date formats, and limits.

First use case: a balance widget on an admin dashboard

Create a sandbox REST app at developer.paypal.com, set the three env vars, then prove the credentials work:

php artisan tinker
>>> Naif\PayPal\Facades\PayPal::getBalance();

The balance response is a list of balances entries (one per currency) inside a wrapper with account_id, as_of_time, etc. Once you see a currency-code entry come back, you have a working integration and can move on to transactions:

$transactions = PayPal::getTransactions('2024-01-01T00:00:00Z', '2024-01-31T23:59:59Z');

Expect transaction_details with fields like transaction_info.transaction_amount, transaction_event_code, transaction_status, and payer_info. This package will not do checkout, refunds, subscriptions, or webhooks — if you need those, you want the official SDK or srmklive/laravel-paypal. Treat this one strictly as a reporting reader.

Implementation Patterns

Wrap it in your own service, not the facade. The facade is fine for a prototype, but bind the client behind an interface so you can mock it in tests and swap libraries later if the package goes unmaintained:

interface PayPalReporter {
    public function balance(string $currency = 'USD'): array;
    public function transactions(Carbon $from, Carbon $to): Collection;
}

Cache the balance aggressively. Balance is a "current state" call — there is no reason to hit PayPal on every page load. Cache::remember('paypal.balance.usd', now()->addMinutes(5), fn () => ...) removes almost all rate-limit pain for a dashboard widget.

Sync transactions into your own table on a schedule. The reporting endpoints are read-only and paginated; a nightly job that pulls a bounded window and upserts by transaction_id (a.k.a. transaction_info.transaction_id) gives you a queryable local ledger, fast dashboards, and a safety net when PayPal returns a 5xx:

// app/Console/Commands/SyncPayPalTransactions.php
$window = 31; // keep well inside PayPal's max range
$from = now()->subDays($window)->startOfDay();
$to   = now()->endOfDay();

$page = 1;
do {
    $res = $this->reporter->transactions($from, $to, $page);
    Transaction::upsert($this->map($res['transaction_details']), ['paypal_transaction_id']);
    $page++;
} while ($page <= ($res['total_pages'] ?? 1));

Reconciliation is the killer app here. Pull transactions for the period, match transaction_info.transaction_amount.value against your local orders table by invoice/custom field, and surface a daily "unmatched" list. This is exactly what a small reporting wrapper buys you over hand-rolling HTTP calls.

Segment by date window. PayPal's transaction search rejects ranges beyond roughly 31 days (confirm in the docs; limits have moved before). Chunk long backfills into per-month loops and sleep a second between calls on bulk jobs.

Multi-account support via runtime config. For agencies managing several PayPal accounts, build a small factory that calls config()->set('paypal.client_id', ...) / config()->set('paypal.secret', ...) before resolving a fresh client instance — but only if the client reads config lazily. If it caches credentials on construction, use app()->forgetInstance(...) or construct the client directly with an array of credentials.

Test against sandbox, always. The mode toggle is a one-liner, but sandbox accounts start empty — seed them with the sandbox "Create Account" tools and a few test transactions, or your first getTransactions() call returns {"transaction_details": [], "total_items": 0} and looks broken when it isn't.

Gotchas and Tips

  • Empty arrays are not errors. A zero-transaction result, a missing currency in the balance list, and a 400 for a malformed date look very different — check total_items and balances rather than assuming success from "no exception".
  • Date format matters. Send ISO 8601 with T and Z (2024-01-01T00:00:00Z). Plain 2024-01-01 strings usually earn a 400 INVALID_REQUEST. Also verify whether the package converts Carbon for you before you do it twice.
  • Paginate or lose data. The default page_size is small (often 100); anything beyond a quiet day will silently truncate your sync if you only read page 1. Always loop on total_pages.
  • Token churn. Wrappers this size frequently fetch an OAuth token on every call, which doubles request count and eats rate limit. Check whether a token is cached; if not, memoize the access token yourself (or patch the client) and refresh before its expires_in.
  • 429s and 401s mean different things. 401 → bad credentials or wrong mode (sandbox creds against api.paypal.com); 429 → back off exponentially and cache. Don't retry 401s.
  • Enable the right scopes. The PayPal app needs reporting/transaction-search permissions enabled in the developer dashboard, or every call 403s with a message that doesn't obviously say "scope".
  • Balance is a list, not a number. Iterate balances and match currency_code; don't blindly take [0] — a multi-currency account will get EUR on a good day.
  • Timezone drift. PayPal returns UTC; your reconciliation logic and display layer should convert explicitly. Half-day mismatches are the most common "missing transaction" bug.
  • Log the request/response pair once. Wrap calls in a try/catch that logs the endpoint, date range, status code, and PayPal's debug_id from the body — that ID is what PayPal support asks for. Never log the client secret or full token.
  • Pin the version and check the commit history. Low star count and zero dependents mean few eyes on it; treat it as a reference implementation you may need to fork. If you hit a bug, the whole surface is two endpoints and a token call — patching it locally beats filing an issue that may go unanswered. Keep srmklive/laravel-paypal in mind as the actively maintained fallback if you later need payments beyond reporting.
  • Don't confuse it with a payment package. No createOrder, no capture, no IPN/webhook handling. If a colleague asks why checkouts fail after installing this, that's the answer.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core