A gateway-agnostic payments abstraction for Laravel. Charge any of your Eloquent models, collect payments through a payment gateway, and reconcile them automatically from incoming webhooks.
The package ships its own Order/Payment models, migrations, webhook routes and a
Payments facade. It is built around small contracts that your application implements,
so the rest of your code never talks to a specific provider directly.
Currently bundled gateway: MercadoPago (via puntodev/mercadopago).
- PHP
>=8.4 - Laravel
^12.0
Install via composer:
composer require puntodev/payablesPublish and run the migrations:
php artisan vendor:publish --tag=migrations
php artisan migrateThis creates the orders and payments tables.
The library models payments around a few small interfaces (Puntodev\Payables\Contracts):
| Contract | Implemented by | Purpose |
|---|---|---|
Payable |
your "thing to charge" model | Converts itself into a PaymentOrder |
PaymentOrder |
a value object you build | Checkout details: items, payer, back URLs |
PaymentOrderItem |
a value object you build | A single line item |
Merchant |
your account model (or the default) | Holds gateway credentials |
Gateway |
the package (e.g. MercadoPago) | Creates orders and processes webhooks |
GatewayPaymentOrder |
the package | Result of a checkout (redirect link, ids) |
There are also two traits to drop on your models:
HasOrders— for your payable model. Addsorders(),isPaid(),isRefunded(),paidOn().OwnsPayments— for your merchant model. Addsorders().
Note: amounts are stored in cents in the database, but the
Order/Paymentmodels expose them as decimals through accessors. Always read/writeamountthrough the models.
use Illuminate\Database\Eloquent\Model;
use Puntodev\Payables\Concerns\HasOrders;
use Puntodev\Payables\Contracts\Payable;
use Puntodev\Payables\Contracts\PaymentOrder;
class Product extends Model implements Payable
{
use HasOrders;
public function toPaymentOrder(): PaymentOrder
{
return new ProductPaymentOrder($this);
}
}Your PaymentOrder implementation describes the checkout — its items, the payer's
details, the URLs MercadoPago should redirect to, excluded payment methods and an
optional expiration. Each item implements PaymentOrderItem (amount, quantity,
currency, description).
Using a specific merchant (a model that implements Merchant and uses OwnsPayments):
use Puntodev\Payables\Facades\Payments;
$order = Payments::checkout('mercado_pago', $product, $merchant);
return redirect($order->redirectLink());Or, if you operate a single MercadoPago account, use the default merchant
(credentials read from config('mercadopago.*')):
$order = Payments::checkoutForDefaultMerchant('mercado_pago', $product);
return redirect($order->redirectLink());This persists a local Order, creates the payment preference on the gateway, and
returns a GatewayPaymentOrder exposing gateway(), id(), redirectLink() and
externalId().
The package registers webhook routes automatically (prefixed with payments by
default):
POST /payments/{gateway}/{merchantType}/{merchantId}— per-merchantPOST /payments/{gateway}— default merchant
Point your gateway's notification URL there (it is set automatically when the order is
created). Incoming notifications are handled by a queued StorePayment job that fetches
the order from the gateway and upserts the corresponding Payment, mapping it to
paid / refunded / created.
$product->isPaid(); // bool
$product->isRefunded(); // bool
$product->paidOn(); // ?CarbonPublish the config if you need to customize gateways, the route prefix or middleware:
php artisan vendor:publish --provider="Puntodev\Payables\PaymentsServiceProvider"// config/payments.php
return [
'gateways' => ['mercado_pago' => MercadoPagoGateway::class],
'prefix' => 'payments',
'middleware' => 'web',
];Implement Puntodev\Payables\Contracts\Gateway, register it in the gateways map, and
bind it in a service provider if it has constructor dependencies. The rest of your code
keeps using the Payments facade unchanged.
composer test # run the test suite
composer test-f <filter> # run a single test by name
composer test-coverage # generate an HTML coverage reportPlease see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
If you discover any security related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.