srmklive/paypal
A Laravel package for the PayPal REST API that also works as a standalone PHP client. Covers orders, payments, subscriptions, billing plans, invoices, payouts, disputes, and more, with PHP 8.2–8.5 and Laravel 12/13 support.
Architecture fit
\PayPal::setProvider()), publishable config/paypal.php, env-driven credentials. Standalone mode (new PayPalClient + setApiCredentials()) with no framework dependency means the same client can back a queue worker, CLI reconciliation job, or a non-Laravel service — useful if payments logic is extracted later.setClient() accepts any PSR-18 client, so an organisation-standard HTTP client (proxy, mTLS, OpenTelemetry tracing, custom retry policy) can be injected without forking. Note the built-in retry middleware applies only to the default Guzzle client — teams injecting their own client inherit the retry problem too.Integration feasibility
composer require, publish config, set six env vars, getAccessToken(), createOrder() → redirect to the payer-action link → capturePaymentOrder(). Realistically 2–5 engineer-days for a production-grade order/capture/refund flow including webhooks and tests; materially more for subscriptions, disputes, or payouts.Technical risk
PayPal-Request-Id idempotency keys managed by your code; the package does not own a request-key store. This is the single most likely source of duplicate-charge incidents.['error' => ...] unless withExceptions() is called. A team that forgets an isset($response['error']) check ships a payment flow that fails open into success paths. Recommend mandating withExceptions() in the integration standard.verifyWebHookLocally() with in-memory cert caching) removes a PayPal API roundtrip per event — a real availability and latency win — but shifts responsibility for trust-anchor rotation and cache invalidation onto your process lifecycle.Dependents: 0 contradicts 1,151 stars and the badge/download position, so treat it as a registry-scan artefact; the 2026-10-02 last-release date is inconsistent with the current date and should be checked against the actual Packagist tag before any planning assumption rests on it.Key questions for the maintainer/team
setPartnerAttributionId) or a direct merchant?Stack fit
Srmklive\PayPal\Services\PayPal or a thin internal PayPalGateway wrapper over the facade to keep the domain testable and the package swappable..env coupling.setClient() and disable the built-in retry assumptions in your runbook.Migration path
/v1/payments in January 2027, and v1 Billing Agreements with it. Any custom create-payment/execute-payment or billing-agreement code must move to Orders v2 / Subscriptions v2 inside that window.POST /v1/payments/payment + /execute → createOrder() + capturePaymentOrder(); createBillingAgreementToken()/createBillingAgreement() → addProductById() → addBillingPlanById() → setupSubscription().Compatibility
config/paypal.php usually survives; new keys are additive (timeout, connect_timeout, max_retries). payment_action and notify_url remain but are largely vestigial in Orders v2 flows — do not let them imply behaviour they no longer drive.array['error'] vs PayPalApiException with getHttpStatus(), getPayPalError()). Pick one per codebase; mixing modes is how silent failures appear.setApiCredentials() enables per-tenant or multi-account routing (marketplaces, regional entities) without a second deploy — worth designing for early if multi-currency or multi-entity is on the roadmap.Sequencing
srmklive/paypal to an exact version in composer.json.createOrder, redirect via payer-action, capturePaymentOrder, getCaptureIdFromOrder() persisted, partial and full refund.verifyWebHookLocally(), add signature-failure logging, idempotent event handler, and a replay endpoint for operational recovery.setupSubscription(), lifecycle webhook handling, dunning behaviour.Maintenance
composer.json exact pin. Budget roughly 2–4 engineer-days per quarter for currency, plus a discrete project for the January 2027 v1 sunset if legacy code exists.PayPalGateway wrapper thin so a fork or replacement is a contained change.Support
Scaling
getAccessToken() being called per request or per job creates a latency floor and a token-endpoint rate-limit risk under load. Cache tokens in your cache store and refresh with headroom; confirm current behaviour rather than assuming.Retry-After (the client does when present) and cap concurrency to PayPal's rate limits per endpoint.Failure modes to instrument
PayPal-Request-Id, monitor capture-id uniqueness.withExceptions() and alert on PayPalApiException volume.PAYPAL_MODE per environment, fail deploys where live credentials appear outside production, never allow runtime mode override from user input.Ramp-up
intent CAPTURE vs AUTHORIZE, authorize/void vs capture/refund, partial refund rules, dispute lifecycle, webhook event coverage, and settlement timing.How can I help you explore Laravel packages today?