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

Paypalpayment Laravel Package

anouar/paypalpayment

A Laravel package for integrating PayPal payments with minimal setup. It wraps PayPal APIs for express checkout, payment processing, and IPN handling, making it easy to add secure PayPal support to PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install (pin the version, the project is frozen since 2017):

composer require anouar/paypalpayment

2. Register the provider and alias. On Laravel 5.5+ auto-discovery usually picks them up; on older or newer versions add them by hand in config/app.php:

'providers' => [
    Anouar\Paypalpayment\PaypalPaymentServiceProvider::class,
],
'aliases' => [
    'PaypalPayment' => Anouar\Paypalpayment\Facades\PaypalPayment::class,
],

3. Publish and fill the config:

php artisan vendor:publish --provider="Anouar\Paypalpayment\PaypalPaymentServiceProvider"

config/paypal.php is the first file to open — it holds client_id, secret, and the mode (sandbox / live). Feed it from .env:

PAYPAL_CLIENT_ID=...
PAYPAL_SECRET=...
PAYPAL_MODE=sandbox

4. First use case — charge a test card in sandbox. The package is a thin facade over the PayPal REST SDK object graph, so you build a payment by chaining model objects:

use PaypalPayment;

$card = PaypalPayment::creditCard()
    ->setType('visa')
    ->setNumber('4111111111111111')
    ->setExpireMonth('11')
    ->setExpireYear('2030')
    ->setCvv2('123')
    ->setFirstName('John')
    ->setLastName('Doe');

$funding = PaypalPayment::fundingInstrument()->setCreditCard($card);

$payer = PaypalPayment::payer()
    ->setPaymentMethod('credit_card')
    ->setFundingInstruments([$funding]);

$amount = PaypalPayment::amount()
    ->setCurrency('USD')
    ->setTotal('10.00');

$transaction = PaypalPayment::transaction()
    ->setAmount($amount)
    ->setDescription('Order #1');

$payment = PaypalPayment::payment()
    ->setIntent('sale')
    ->setPayer($payer)
    ->setTransactions([$transaction]);

try {
    $payment->create(PaypalPayment::getApiContext());
    $saleId = $payment->getTransactions()[0]->getRelatedResources()[0]->getSale()->getId();
} catch (\PayPal\Exception\PayPalConnectionException $e) {
    logger()->error($e->getData());
}

If that returns an approved payment with a sale id, the integration is wired correctly. Everything else is variation on this chain.

Implementation Patterns

Wrap it in a service, don't call the facade from controllers. The facade resolves the API context from config on each call, so injecting it into a single PaypalGateway class keeps your controllers clean and gives you one place to mock:

class PaypalGateway
{
    public function charge(CardData $card, Money $amount, string $description): PaymentResult
    {
        // build the object graph as above, call create(), map the response
    }
}

Validate and normalize before the gateway. Card number, expiry, CVV and currency come from a FormRequest. PayPal expects totals as strings with two decimals ('10.00', not 10 or 10.0), and expiry month/year as two-digit strings. Normalizing in the request/DTO layer avoids the most common 422 VALIDATION_ERROR responses.

Persist the PayPal identifiers, not the card. After create(), store $payment->getId() and the sale id on your orders row, along with $payment->getState(). You will need the sale id for refunds and for reconciliation against the PayPal dashboard. Never let the raw card number, CVV or expiry reach your database or logs — the package doesn't strip it for you.

Treat state as the source of truth, not the HTTP 2xx. A created payment can come back created, approved or failed. Only fulfil the order on approved; branch on the state and store the reason on failure.

Refunds follow the same pattern — resolve the sale id you stored, then:

$sale = PaypalPayment::sale()->get($saleId, PaypalPayment::getApiContext());
$refund = PaypalPayment::refund()
    ->setAmount(PaypalPayment::amount()->setCurrency('USD')->setTotal('5.00'));

$sale->refund($refund, PaypalPayment::getApiContext());

Partial refunds are supported this way; full refunds can omit the amount.

Drive sandbox vs live entirely from env. PAYPAL_MODE in .env plus config('paypal.mode') is the only switch you should need. Never hardcode credentials in the service class — that's the one thing the package makes genuinely easy to get right.

Test by mocking the gateway, not the SDK. The SDK objects are hard to fake and hit the network on create(). Bind a fake PaypalGateway in your test container and assert on the DTO you pass in; reserve real sandbox calls for a small set of integration tests.

Use it as a building block, not a billing system. There is no webhook handling, no subscription support, no idempotency keys exposed, and no 3-D Secure flow. If your product needs recurring billing, SCA-compliant EU card charges, or asynchronous webhook reconciliation, treat this package as the "charge a card once" primitive and implement the rest yourself — or pick a maintained SDK.

Gotchas and Tips

The dependency is the real risk. The package wraps paypal/rest-api-sdk-php, which PayPal deprecated years ago, and the last release was November 2017. Expect friction on PHP 8 and Laravel 9+: the service provider predates modern container conventions, and the facade alias may not register under auto-discovery. If you must use it, pin composer.json and lock the PHP version in CI, or vendor the handful of classes you actually need.

Direct card processing is region-restricted. PayPal has been winding down raw card-data APIs in favour of Braintree / Advanced Credit and Debit Card Payments (v2). Sandbox will happily accept 4111111111111111, but a live EU card requiring SCA will fail with no useful signal from this SDK. Confirm with PayPal that your account and region still permit credit_card payments before building on it.

Exceptions carry the real error. PayPalConnectionException::getData() returns the JSON body with name, message, details[].issue. Log that, not getMessage(), or you'll debug blind. Catch it narrowly — the SDK also throws PayPalMissingCredentialException on bad config and PayPalInvalidCredentialException on expired secrets.

Config caching bites after env edits. php artisan config:clear after changing PAYPAL_MODE or credentials, otherwise the facade keeps using the cached API context and you'll "successfully" charge the sandbox from staging.

Amount precision is unforgiving. PayPal rejects 10, 10.0, floats and mismatched currency. Build a small Money value object whose __toString() always returns two decimals, and make the currency an explicit argument rather than a config default.

No idempotency out of the box. Retrying a failed create() can double-charge. Since PaypalPayment::getApiContext() returns the SDK context, you can reach in and add the PayPal-Request-Id header yourself — but you have to remember to do it. Wrap creation in a try/catch that distinguishes connection timeouts (safe to retry) from validation errors (not safe).

Extension point is the API context. Anything the facade doesn't expose — custom headers, proxy settings, logging handlers — goes through the ApiContext object. Subclass your gateway to build a configured context once and reuse it, rather than letting the facade rebuild it per call.

Refund window and status. Refunds are only possible within PayPal's window (historically 180 days) and can be pending if the original funding source was a bank. Poll the refund state or reconcile via the dashboard; don't mark an order refunded purely on a 201 response.

Plan the exit. Given the maintenance status, isolate every call behind your PaypalGateway interface so swapping to paypal/paypal-server-sdk or Braintree is a single-class change rather than a hunt through controllers.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin