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

Payum Laravel Package Laravel Package

payum/payum-laravel-package

Payum Laravel Package integrates the Payum payment library with Laravel, making it easier to configure payment gateways, handle captures and notifications, and manage payment workflows through Laravel-friendly services and configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish.

composer require payum/payum-laravel-package
php artisan vendor:publish --provider="Payum\LaravelPackage\PayumServiceProvider"
php artisan migrate

This gives you three things to look at, in this order:

  1. config/payum.php — the whole package is essentially this file plus two bindings. It declares your gateways (one array entry per gateway, keyed by the name you'll use in getGateway()) and your storages.
  2. PayumServiceProvider — read register()/boot() once. It binds payum (the Payum\Core\Payum instance), a token storage, a gateway-config storage, and an HTTP request verifier. Everything the facade gives you comes from here.
  3. The migrations — payum_token (persistent security tokens for notify/capture URLs) and payum_gateway_config.

Reality check before you commit: last release was 2020-10-31 and there are zero dependents. On Laravel 9+/PHP 8 you will likely hit constraint conflicts. The pragmatic path for a new project is composer require payum/payum payum/*-gateway and copy the ~80-line service provider out of this repo. Use this package as-is only on older Laravel.

First use case: a sandbox capture.

Configure a gateway:

// config/payum.php
'gateways' => [
    'paypal' => [
        'factory'   => 'paypal_express_checkout',
        'username'  => env('PAYPAL_USERNAME'),
        'password'  => env('PAYPAL_PASSWORD'),
        'signature' => env('PAYPAL_SIGNATURE'),
        'sandbox'   => true,
    ],
],

Then drive it from a controller. Payum's control flow is exception-based: execute() either returns normally or throws a Reply object that represents an outbound HTTP action. The very first thing to internalise is that you must translate replies into Laravel responses yourself.

use Payum\Core\Payum;
use Payum\Core\Request\Capture;
use Payum\Core\Reply\HttpRedirect;

public function pay(Payum $payum)
{
    $payment = new \Payum\LaravelPackage\Model\Payment();
    $payment->setNumber(uniqid());
    $payment->setTotalAmount(1234);      // minor units: 12.34 EUR
    $payment->setCurrencyCode('EUR');
    $payment->setClientEmail(auth()->user()->email);
    $payment->save();

    try {
        $payum->getGateway('paypal')->execute(new Capture($payment));
    } catch (HttpRedirect $reply) {
        return redirect()->away($reply->getUrl());
    }

    return redirect()->route('checkout.done', $payment);
}

If that renders a PayPal page in sandbox, you've validated config, gateway factory, storage, and reply handling in one shot. Everything else is variations on this loop.

Implementation Patterns

1. Centralise reply translation. Don't repeat try/catch in every controller. One helper that maps Payum replies to Laravel responses pays for itself immediately:

class PayumResponse
{
    public static function from(callable $fn)
    {
        try {
            $fn();
            return null;
        } catch (HttpRedirect $r) {
            return redirect()->away($r->getUrl());
        } catch (HttpPost $r) {
            return response()->view('payum::post', ['url' => $r->getUrl(), 'fields' => $r->getFields()]);
        } catch (HttpResponse $r) {
            return response($r->getContent(), $r->getStatusCode(), $r->getHeaders());
        }
    }
}

HttpPost is the one people forget — several gateways return an auto-submitting HTML form (3-D Secure, some redirects). You need a tiny Blade view that POSTs on load.

2. The return/notify pair. Payment flows are two-legged. The return action is user-facing (browser comes back); the notify action is server-to-server. Generate notify URLs from the token factory so they're stateless and tamper-proof:

$token = $payum->getTokenFactory()->createNotifyToken('paypal', $payment);
return $token->getTargetUrl();   // hand this to the gateway as notify_url

Then in the notify controller, verify the request before trusting anything:

Route::post('/payum/notify', function (Payum $payum) {
    $request = \Payum\Core\Http\Request::createFromGlobals();
    $token   = app('payum.http_request_verifier')->verify($request);
    $payment = $payum->getGateway($token->getGatewayName())->execute(new Sync($token));
    // Sync pulls fresh status from the provider
});

Exclude this route from VerifyCsrfToken::$except — the gateway is the caller, not a browser session.

3. Model choices. For prototypes, Payum\LaravelPackage\Model\Payment works out of the box (it JSON-casts details). For real apps, extend it or implement PaymentInterface + DetailsAggregateInterface on your own Order model and point the Eloquent storage config at it. This is where the package earns its keep: you keep your domain model and Payum just persists details as an opaque bag.

4. Drive UI from GetHumanStatus. Don't invent your own status enum:

$status = $payum->getGateway($name)->execute(new GetHumanStatus($payment))->getValue();
// captured, authorized, pending, failed, ...

Persist it on a column so your admin screens don't re-poll the provider.

5. Gateway config in the database. The payum_gateway_config table + GatewayConfigStorage exist so you can add merchant accounts at runtime (multi-tenant SaaS, per-store credentials) without a deploy. For single-tenant apps, ignore this table entirely and keep config in config/payum.php — it's simpler and config-cacheable.

6. Custom actions and extensions for cross-cutting concerns. Logging, idempotency, metrics — attach them to the gateway rather than smearing them across controllers:

$gateway = $payum->getGateway('paypal');
$gateway->addExtension(new LoggingExtension($logger)); // implements ExtensionInterface
$gateway->addAction(new EnsureIdempotentAction());      // implements ActionInterface

Actions are the idiomatic Payum extension point; a "record every request/response" action saves hours when a provider starts behaving oddly.

7. Testing. Don't hit sandbox in CI. Build a Payum\Core\Gateway with a stub capture action, or bind a fake Payum implementation into the container and assert your controller produced the right redirect()->away(...). The exception-based API makes this pleasant — you assert on the thrown HttpRedirect, not on rendered HTML.

Gotchas and Tips

Amounts are minor units. setTotalAmount(1234) is €12.34. This is the single most common bug in Payum integrations — you'll charge 100× or 1/100× and only notice in production. Always derive from an integer column (price_cents), never from a float.

Replies are exceptions, and Laravel's handler doesn't know them. If a HttpRedirect escapes a controller uncaught, Laravel renders a 500. Either wrap every execute() or add a handler in app/Exceptions/Handler.php that maps ReplyInterface to a response centrally. The latter is less error-prone.

Token table growth. payum_token gets a row per notify URL, forever. Add a scheduled command that prunes tokens older than your longest-lived payment window. Nothing in the package does this for you.

Behind a load balancer, notify URLs are wrong. The token factory builds absolute URLs from the current request. With TrustProxies misconfigured or APP_URL unset, you'll hand providers http://internal-host/.... Set APP_URL and configure trusted proxies before going anywhere near staging.

config:cache and closures. If you put a closure in config/payum.php (easy to do for dynamic credentials), php artisan config:cache blows up. Keep config strictly scalar; use env vars or a DB lookup.

Gateway config keys are not uniform. sandbox for PayPal, testMode/sandbox depending on factory version, publishable_key vs api_key for Stripe. There is no unified schema — copy from the specific gateway's README, not from Payum's docs.

Storage + getFirstModel(). Nested requests wrap models. $capture->getModel() gives you the payment; getFirstModel() can give you the request. When in doubt, read the model off the Reply you caught.

Old dependency graph. The 2020 release pins Guzzle and symfony/http-foundation ranges that fight with modern Laravel. If composer update explodes, that's why. Forking the provider and dropping the constraint is usually faster than hunting for a compatible set.

No observability built in. There's no log driver, no debug toolbar integration. Add a Payum ExtensionInterface that logs every execute() call and the resulting status; you'll want it the first time a webhook silently no-ops.

Extension points worth knowing: ExtensionInterface (wrap every action — perfect for logging/idempotency), ActionInterface (add new capabilities to a gateway), custom TokenStorage (Redis instead of MySQL, if token lookups get hot), and custom storage implementations if Eloquent's overhead bothers you.

Verdict for day-to-day use. The core Payum library is genuinely good — the request/reply/action architecture scales from a single PayPal button to multi-gateway marketplaces with escrow. This Laravel wrapper is thin and dated but still the fastest way to get payum bound and configured in an older app. Budget a day for dependency wrangling on anything newer than Laravel 7, and expect to write your own reply-to-response layer and token pruning regardless.

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/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
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route