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

paymentsuite/paypal-express_checkout-bundle

A PaymentSuite bundle that adds PayPal Express Checkout support to PHP/Symfony applications, helping you configure and integrate express checkout payment flows into your existing payment stack with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Treat this package as a reference implementation, not a dependency. It is a Symfony2 bundle: there is no Laravel service provider, no facade, no publishable config, no auto-discovered routes, and no migrations. Its last release was January 2021, it has 26 stars and zero dependents, so expect composer constraints and APIs from the Symfony 2/3/PHP 7 era.

If you want to evaluate it, do it in a throwaway Symfony app or by reading the monorepo, not by adding it to a production Laravel composer.json.

Look at these first, in this order:

  • composer.json of the split package: check the php, symfony/*, paymentsuite/payment-core-bundle, and Omnipay/Payum constraints. Run composer why-not paymentsuite/paypal-express_checkout-bundle against your Laravel app before wasting time.
  • DependencyInjection/Configuration.php and ...Extension.php: exact config keys (API username, password, signature, sandbox flag, branding, etc.). Do not assume the key names.
  • Resources/config/services.yml and Resources/config/routing.yml: service IDs, route names, and controller wiring.
  • Controller/: the Express Checkout redirect and return flow.
  • The payment method class (typically PaypalExpressCheckoutMethod or similar): it implements PaymentSuite's payment method interface and is invoked by payment-core-bundle.

The first real use case is the classic PayPal Express Checkout flow:

  1. SetExpressCheckout — create a token and redirect the buyer to PayPal.
  2. Buyer approves on PayPal and is redirected back to your returnUrl.
  3. GetExpressCheckoutDetails — fetch payer and order details.
  4. DoExpressCheckoutPayment — capture the payment.
  5. Persist the transaction ID and mark the order paid.

In Laravel, reproduce that flow with omnipay/paypal rather than porting the bundle:

composer require omnipay/paypal
// config/services.php
'paypal' => [
    'username'  => env('PAYPAL_API_USERNAME'),
    'password'  => env('PAYPAL_API_PASSWORD'),
    'signature' => env('PAYPAL_API_SIGNATURE'),
    'sandbox'   => env('PAYPAL_SANDBOX', true),
],
$gateway = Omnipay::create('PayPal_Express');
$gateway->setUsername(config('services.paypal.username'));
$gateway->setPassword(config('services.paypal.password'));
$gateway->setSignature(config('services.paypal.signature'));
$gateway->setTestMode(config('services.paypal.sandbox'));

$response = $gateway->purchase([
    'amount'      => $order->total,
    'currency'    => $order->currency,
    'returnUrl'   => route('paypal.return', $order),
    'cancelUrl'   => route('paypal.cancel', $order),
    'description' => "Order #{$order->id}",
])->send();

if (! $response->isRedirect()) {
    throw new PaymentException($response->getMessage());
}

return redirect($response->getRedirectUrl());

Implementation Patterns

Wrapper service pattern (recommended). Put the gateway behind a small Laravel service so controllers and jobs do not touch Omnipay directly:

final class PaypalExpressCheckout
{
    public function __construct(private GatewayInterface $gateway) {}

    public function start(Order $order): string
    public function complete(Request $request): PaymentResult
}

Bind it in a service provider and keep credentials in config/services.php. This is the closest equivalent to what the Symfony bundle gives you: one injectable payment method with a process()-style API.

Order correlation before redirect. Persist the order ID, amount, currency, and a unique attempt ID before sending the buyer to PayPal. On return, load the order by your own reference, not only by the PayPal token. Never trust amount, currency, or PayerID from the query string alone.

Complete the payment server-side. On the return route, call completePurchase() / DoExpressCheckoutPayment, verify ACK is Success or SuccessWithWarning, verify the amount and currency against your stored order, then transition the order. A return URL is not proof of payment.

Mirror PaymentSuite's event model. The bundle integrates with payment-core-bundle through PaymentMethodManager and events such as payment.success, payment.failed, and payment.cancelled. In Laravel, publish your own domain events (OrderPaid, PaymentFailed, PaymentCancelled) from the completion service and let listeners handle fulfillment, email, and stock. That keeps the same separation the Symfony bundle is trying to give you.

Symfony interoperability pattern. If you have an existing Symfony/PaymentSuite application and are adding a Laravel service beside it, treat the bundle's routes, config keys, and event payloads as a contract. Share order IDs and transaction references through the database, not through the bundle's container. Do not try to boot a Symfony kernel inside Laravel to reuse the bundle.

Testing workflow. Use PayPal Sandbox business and personal accounts. Expose your local return URL with php artisan serve plus a tunnel (ngrok, Expose, or Sail + tunnel). Test at least: successful payment, buyer cancellation, double-hitting the return URL, expired token, and amount mismatch. Log the raw NVP request/response with credentials redacted.

Idempotency. DoExpressCheckoutPayment can be retried, but duplicate captures and duplicate "order paid" listeners are common. Store the PayPal transaction ID with a unique constraint and guard state transitions in a transaction.

Gotchas and Tips

  • No Laravel integration exists. No ServiceProvider, no facade, no config/ publish, no route registration, no Http kernel integration. Any installation instructions you find are Symfony YAML and will not apply.
  • Symfony HttpFoundation vs Laravel's request/response. The bundle's controllers type-hint Symfony Request/Response and use Symfony sessions and flash bags. Those are not interchangeable with Illuminate\Http\Request and Laravel's session.
  • Event dispatcher mismatch. Symfony's EventDispatcher and event names do not map to Laravel's dispatcher. You will rewrite the event wiring, not reuse it.
  • Composer constraints will likely block installation. Symfony 2/3-era bundles pin framework versions and PHP versions that conflict with Laravel 9/10/11. Check with composer why-not before attempting a port.
  • Maintenance risk is real. Last release January 2021, 26 stars, zero dependents. Assume no fixes for PayPal API changes, PHP 8.2+ deprecations, or security issues. Budget for owning the code if you vendor anything.
  • PayPal Express Checkout NVP is legacy. PayPal pushes the REST Orders v2 API and Smart Payment Buttons. For new Laravel work, prefer srmklive/laravel-paypal, direct REST calls, or omnipay/paypal if you specifically need Express Checkout. Do not start a new integration on NVP just because this bundle exists.
  • Tokens are short-lived and single-use. Express Checkout tokens expire after roughly three hours and cannot be replayed. Handle "token already used" and "token expired" explicitly and send the buyer back to checkout.
  • Final amount lives in DoExpressCheckoutPayment unless you commit earlier. If you set commit=true on SetExpressCheckout, the buyer sees "Pay Now" and the amount is fixed earlier. If not, you must set and verify the final amount at capture time. Never let the client decide it.
  • Shipping and UX parameters are easy to miss. NOSHIPPING, SOLUTIONTYPE=Mark, LANDINGPAGE=Billing, BRANDNAME, and locale settings all change the PayPal experience and can silently break address collection.
  • Do not trust the return query string. token and PayerID in the URL are inputs to GetExpressCheckoutDetails, not proof of payment. Always call PayPal and verify ACK and the amount server-side.
  • Debugging tip. Omnipay exposes the underlying request and response; log getRequest()->getData() and getResponse()->getData() with secrets redacted. Most failures are credential, signature, sandbox/live mismatch, or currency not enabled on the account.
  • Extension points in Symfony (if you go that route). Override the payment method service definition, decorate the gateway, or listen to PaymentSuite events. In Laravel these become your own container bindings and domain events — there is nothing to "extend" from the bundle directly.
  • The monorepo is the source of truth. The Packagist package is a subtree split of paymentsuite/paymentsuite; read it alongside paymentsuite/payment-core-bundle, and file issues in the monorepo. The split package alone can be misleading about dependencies and configuration.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels