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 Bundle Laravel Package

ylly/paypal-bundle

A Symfony bundle that integrates PayPal payments into your application, providing ready-made services and configuration for handling PayPal API credentials, payment flows, and related operations so you can wire up checkout functionality without writing the integration from scratch.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install with Composer:

    composer require ylly/paypal-bundle
    

    Note the package name (ylly/paypal-bundle) differs from the repository name (BeelabPaypalBundle) — expect the bundle class to be Beelab\PaypalBundle\BeelabPaypalBundle.

  2. Register the bundle. On Symfony 3 / legacy apps:

    // app/AppKernel.php
    $bundles = [ /* ... */ new Beelab\PaypalBundle\BeelabPaypalBundle() ];
    

    On Symfony 4+ (if it works at all):

    // config/bundles.php
    return [ /* ... */ Beelab\PaypalBundle\BeelabPaypalBundle::class => ['all' => true] ];
    
  3. Configure credentials in config.yml / config/packages/beelab_paypal.yaml. Expect keys roughly like:

    beelab_paypal:
        username: "%paypal_username%"
        password: "%paypal_password%"
        signature: "%paypal_signature%"
        sandbox: true
    

    Confirm the exact key names by opening DependencyInjection/Configuration.php in vendor/ylly/paypal-bundle/ — do this first, before writing any application code, because this bundle predates autowiring and its config shape is unlikely to be documented.

  4. Look at the service definition (Resources/config/services.xml) to find the exposed service ID (typically something like beelab_paypal.paypal or beelab_paypal.service). That class is your entire API surface — the whole bundle is a thin wrapper around PayPal's legacy NVP/Express Checkout endpoints.

  5. First use case: Express Checkout redirect. From a controller, call the equivalent of setExpressCheckout() with amount, currency, and return/cancel URLs, then redirect the user to the PayPal token URL returned in the response. This is the smallest end-to-end slice you can wire up to validate that credentials and the sandbox flag are correct.

Implementation Patterns

The canonical three-call flow. Express Checkout is always:

  1. SetExpressCheckout — you send amount/currency + ReturnURL/CancelURL, PayPal returns a TOKEN. Redirect the user to https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=....
  2. GetExpressCheckoutDetails — on the return URL, exchange the TOKEN for payer details (email, shipping address, name).
  3. DoExpressCheckoutPayment — finalize with the same TOKEN + PAYERID and the final amount.

Wrap all three in one application service rather than calling the bundle from three controllers:

class CheckoutManager
{
    private $paypal;
    private $session;

    public function start(Order $order): string
    {
        $response = $this->paypal->setExpressCheckout(
            $order->getTotal(),
            'EUR',
            $this->router->generate('checkout_return', [], true),
            $this->router->generate('checkout_cancel', [], true)
        );
        $this->session->set('paypal_token', $response['TOKEN']);

        return $this->paypal->getRedirectUrl($response['TOKEN']);
    }
}

Store the token server-side, not just in the query string. The token you receive back is untrusted input. Persist it in the session (or on the Order entity) at SetExpressCheckout time and compare on return. This also protects against replaying a DoExpressCheckoutPayment call.

Persist the checkout state before redirecting. Never create the order after PayPal returns — you'll lose the basket if the user abandons. Mark the order pending and transition it only after DoExpressCheckoutPayment returns a success ACK.

Idempotency for the return URL. Users hit refresh. Gate the finalize step on the order status (pending → paid) using a DB transaction or a lock so a second DoExpressCheckoutPayment for the same token can't double-charge.

Sandbox toggle as a single config flag, not per-environment logic in code. Keep sandbox: "%kernel.debug%" or an explicit env var, and let the bundle pick the endpoint. Don't branch on URLs in your own code.

Response handling is array-shaped, not exception-based. These wrappers generally return the parsed NVP response as an associative array with an ACK key (Success, SuccessWithWarning, Failure, FailureWithWarning). Centralize the check:

private function assertOk(array $response): void
{
    if (!in_array($response['ACK'] ?? '', ['Success', 'SuccessWithWarning'], true)) {
        throw new PaymentException($response['L_LONGMESSAGE0'] ?? 'Unknown PayPal error');
    }
}

Log the raw NVP exchange in dev. Since the bundle gives you almost nothing in terms of diagnostics, wrap the service in a decorator that logs the request params (redacting credentials) and the raw response. This is the single highest-value integration tip for this package.

Gotchas and Tips

It has zero traction — treat it as unmaintained. 0 stars, 0 dependents, 0 downloads. Assume no security releases, stale Symfony compatibility, and no fixes for the PayPal side of things. Budget for vendoring the ~200 lines of relevant code into your own project instead of relying on composer require long-term. That is almost certainly the lower-risk path.

The underlying API is legacy. NVP/Express Checkout is a deprecated product; PayPal pushes the REST Checkout v2 API and JS SDK. You are integrating against a sunset API through an abandoned wrapper. If this is anything more than a throwaway prototype, evaluate paypal/rest-api-sdk-php (also deprecated) or, better, PayPal's current Checkout SDK. Pin your expectations accordingly.

Naming mismatch. The repo is BeelabPaypalBundle, the package is ylly/paypal-bundle. Autodiscovery won't help you; the bundle may not declare extra.symfony flex config. Expect manual registration.

No autowiring. Services are almost certainly defined in XML with explicit IDs and legacy *.class parameters. In a modern Symfony app you'll need a services.yaml alias to inject by type:

Beelab\PaypalBundle\Paypal: '@beelab_paypal.paypal'

Check the actual service ID in Resources/config/.

Amounts are strings with two decimals. NVP expects AMT=10.00, not 10 or 10.0. Passing a float will occasionally "work" but you'll get 10413 (currency/amount mismatch) errors intermittently. Format with number_format($amount, 2, '.', '').

Currency codes must match the merchant account. If your PayPal sandbox business account is USD-only, EUR calls fail with a cryptic 10605 (currency not supported). Set up the sandbox account's currency explicitly.

L_* error keys are per-item. L_SHORTMESSAGE0 / L_LONGMESSAGE0 don't always exist; top-level L_ERRORCODE0 may be absent when ACK=Failure. Always guard with ??.

Return URLs must be absolute and HTTPS in live mode. http://localhost works in sandbox only for some flows; the ReturnURL must be reachable by PayPal's servers. Use ngrok or a staging domain for testing the return leg — otherwise you'll debug the redirect loop forever.

SSL/TLS and cURL. Older bundles hardcode verify_peer behavior or call file_get_contents on the NVP endpoint. On modern PHP/cURL with strict CA bundles you may hit an SSL failure that looks like a PayPal outage. Check the HTTP client the bundle uses before blaming credentials.

Debugging loop: sandbox → call SetExpressCheckout → redirect → return URL. If you get a Failure ACK, the response array is your error message. Log it fully. The three most common causes, in order: wrong signature, wrong currency, malformed amount.

Extension point: since the bundle is thin, don't fork it. Write your own PaypalClient interface, implement it once with the bundle, and swap in an official SDK implementation later. This confines the blast radius of the eventual replacement to a single class.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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