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

Payment Laravel Package

vanilo/payment

Vanilo Payment brings payment processing to Vanilo e-commerce apps: manage payment methods, integrate gateways, record transactions, and link payments to orders via Laravel-friendly, extensible APIs. It offers a consistent abstraction for multiple providers and custom drivers.

View on GitHub
Deep Wiki
Context7

Getting Started

vanilo/payment is the payment abstraction layer of the Vanilo e-commerce suite, not a standalone payment integration. It gives you a Payment model tied to any "payable" (usually an Order), a PaymentMethod model, a gateway registry, a return/cancel callback controller, and a set of events. The actual PSP integrations live in sibling packages (vanilo/omnipay, PayPal/Stripe addons, or your own gateway class).

Minimal install:

composer require vanilo/payment

In practice you'll almost never install this alone — pulling vanilo/framework (or the full vanilo/vanilo metapackage) brings it in, along with vanilo/contracts and vanilo/concord (model swappability) which this module depends on.

First steps after install:

  1. Run migrations. The module ships migrations for payment_methods and payments. They are auto-loaded by the service provider; just php artisan migrate. If you need to inspect/override them, publish with php artisan vendor:publish --tag=vanilo-payment (tag names vary by version — check vendor/vanilo/payment/src/Providers/ for the actual publish groups).
  2. Publish/read the config at config/payment.php. This is where gateway classes and model overrides are declared.
  3. Register a gateway. Either use an offline/built-in gateway or register your own class (see below). A PaymentMethod whose gateway string isn't in the registry will blow up when you try to pay.
  4. Create your first payment method, e.g. via tinker or an admin CRUD:
use Vanilo\Payment\Models\PaymentMethod;

PaymentMethod::create([
    'name'    => 'Pay on Delivery',
    'gateway' => 'cash',   // must match a registered gateway key
    'enabled' => true,
]);

First real use case: a checkout that shows enabled payment methods, creates a Payment against the order, hands off to the gateway, and lets the gateway's redirect/return callback finalise the order.

Implementation Patterns

Creating a payment against a payable. The payments table is polymorphic (payable_type/payable_id), so anything implementing the Payable contract works — normally Vanilo\Order\Models\Order:

use Vanilo\Payment\Models\Payment;

$payment = Payment::create([
    'payment_method_id' => $method->id,
    'amount'            => $order->total(),
    'currency'          => $order->currency(),
    'payable_type'      => $order->getMorphClass(),
    'payable_id'        => $order->id,
]);

Using getMorphClass() rather than get_class() matters if you have a morph map configured.

The gateway handshake. All gateways implement Vanilo\Payment\Contracts\PaymentGateway. The typical three-step flow is: build a PaymentRequest, ask the gateway to create it, then branch on the PaymentResponse:

use Vanilo\Payment\Payment\Requests\PaymentRequest;

$request = new PaymentRequest($payment);
$request->setReturnUrl(route('vanilo.payment.return'));
$request->setCancelUrl(route('vanilo.payment.cancel'));

$response = $payment->getGateway()->createPaymentRequest($request);

if ($response->hasRedirect()) {
    return redirect($response->getRedirectUrl());
}

if ($response->getStatus()->isPaid()) {
    // synchronous approval, e.g. cash / manual
}

Response objects expose a PaymentStatus (getStatus()), getTransactionId(), getAmountPaid(), getMessage(), and the redirect helpers. Treat getStatus() as the source of truth, not the presence of a transaction id.

Handling the callback. The module ships Vanilo\Payment\Http\Controllers\PaymentController with return and cancel actions behind the vanilo.payment.return / vanilo.payment.cancel named routes. The controller resolves the payment from the request, asks the gateway to verify status (getPaymentStatus(PaymentRequest)), persists the result, and fires the domain events. Override the routes in your app if your checkout needs a custom post-payment landing page — just point setReturnUrl() at the route you actually register.

Reacting to state changes. Wire your order lifecycle to payment events rather than polling the gateway:

use Vanilo\Payment\Events\PaymentPaid;
use Vanilo\Payment\Events\PaymentFailed;

Event::listen(PaymentPaid::class, fn ($e) => $e->payment->payable->markAsPaid());
Event::listen(PaymentFailed::class, fn ($e) => $e->payment->payable->markAsFailed($e->payment));

Because the events carry the Payment, you can always walk back to the order via $payment->getPayable().

Custom gateway. Implement PaymentGateway (and typically extend the abstract gateway base class if one is provided in your version) with at minimum createPaymentRequest() and getPaymentStatus(), optionally createRefundRequest(). Register the class in config/payment.php under the gateways array, or programmatically through the registry service the module exposes. Keep the class thin: translate Vanilo's PaymentRequest into the PSP SDK call, and map the PSP's answer back to a PaymentResponse with a PaymentStatus.

Model customisation via Concord. Every model in the module (Payment, PaymentMethod, PaymentTransaction if present) is swappable. Add your subclass to the models section of config/concord.php, extend the base class, and the module's internals will resolve your class everywhere. This is the sanctioned extension point for adding columns or business logic — do not patch the vendor package.

Testing pattern. Create an in-memory gateway stub that always returns a fixed PaymentResponse, register it in the test's config, and assert on the events and the resulting Payment::status. This keeps your checkout tests free of network calls.

Gotchas and Tips

  • Gateway key mismatches fail late and loudly. The gateway column on PaymentMethod is a plain string. A typo won't be caught until $payment->getGateway() resolves, then it throws. Validate the gateway key at PaymentMethod creation time in your admin form (an in: rule against the registry keys).
  • Currency and amount are the gateway's, not yours. Set currency explicitly on the Payment. Some PSPs reject mismatched or unsupported currencies, and the failure surfaces deep inside the gateway call rather than in validation.
  • The callback is not trustworthy on its own. Always re-verify with getPaymentStatus() before marking anything paid. Anyone can hit the return URL. Gateways with webhooks typically need a separate, idempotent endpoint — the shipped return route is a redirect handler, not a webhook receiver, so don't conflate them.
  • Make callback handling idempotent. PSPs retry, users refresh, and browsers re-POST. Guard on Payment::status already being final before dispatching your own side effects (emails, stock decrement).
  • Config caching. php artisan config:cache bakes in the gateway map. If you register gateways dynamically in a service provider that reads from the DB, that will not survive a config cache — prefer the config-file registration for anything static.
  • Migrate order matters. payments has a foreign key to payment_methods. If your app has an existing payments table from a previous version, publish the migrations and diff them before running — Vanilo has changed column names (amount_paid, transaction_id, remote_id) across major versions.
  • Events are the integration seam, not the order model. The payment module doesn't know about orders. Everything your app needs to do (close shipment, send receipt, decrement inventory) hangs off PaymentPaid/PaymentFailed/PaymentCancelled listeners in your code. If the order never flips to paid, the first place to look is whether the event listener is registered and whether the controller actually reached the "paid" branch.
  • Model swapping is global. Overriding Payment via Concord affects every package in the suite that references it. Subclass carefully: keep the base class's table name, casts, and event behaviour, and don't change $fillable in ways that break sibling packages.
  • Status transitions. PaymentStatus has more states than "paid/failed" — PENDING, AUTHORIZED, TIMEOUT, DECLINED, REFUND, PARTIALLY_REFUNDED. Gateways that do pre-authorisation return AUTHORIZED first; your order should not be marked paid until PAID. Check isPaid() / isPending() helpers rather than comparing raw integers.
  • Extension points to prefer, in order: model swap via Concord → custom PaymentGateway implementation → event listeners → route overwrite. Avoid editing vendor files; the package is small and a fork will diverge quickly.
  • Zero external dependents is a packaging fact, not a warning. This is a submodule of a monorepo-style ecosystem; its "adoption" lives in vanilo/framework and the Vanilo app skeleton, not in direct composer requires. Judge it by the quality of the Vanilo docs and the sibling gateway packages, not by its own star count.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console