laravel-maib maintained by nikba
laravel-maib
A Laravel 12 package for the maib e-Commerce API — full coverage of all payment types, automatic token management, and HMAC-SHA256 callback webhook handling.
API documentation: https://docs.maibmerchants.md/e-commerce
Features
- Direct payment (one-step charge)
- Apple Pay & Google Pay (offered automatically on the hosted checkout page — no extra code)
- Two-step payment (hold → capture or cancel)
- Recurring payments (register card + server-side execute)
- One-click payments (register card + redirect execute)
- Payment refund (partial or full)
- Payment information query
- Saved card deletion
- Callback webhook handling with HMAC-SHA256 signature validation
- Laravel Events dispatched on successful/failed callbacks
- Automatic Bearer token generation, caching, and refresh
- Full PHPUnit test suite
Requirements
| Version | |
|---|---|
| PHP | ^8.2 |
| Laravel | ^12.0 |
Installation
composer require nikba/laravel-maib
Publish the configuration
php artisan vendor:publish --tag=maib-config
Environment variables
Add the following to your .env file:
MAIB_PROJECT_ID=your-project-id
MAIB_PROJECT_SECRET=your-project-secret
MAIB_SIGNATURE_KEY=your-signature-key
MAIB_ENV=sandbox
MAIB_CALLBACK_ROUTE=/maib/callback
Credentials are available in maibmerchants.md under your project settings.
Configuration
After publishing, edit config/maib.php:
return [
'project_id' => env('MAIB_PROJECT_ID'),
'project_secret' => env('MAIB_PROJECT_SECRET'),
'signature_key' => env('MAIB_SIGNATURE_KEY'),
'environment' => env('MAIB_ENV', 'production'),
'base_url' => 'https://api.maibmerchants.md/v1',
'callback_route' => env('MAIB_CALLBACK_ROUTE', '/maib/callback'),
'cache_driver' => env('MAIB_CACHE_DRIVER', null),
'cache_prefix' => 'maib_token',
];
Usage
Direct Payment
use Nikba\LaravelMaib\Facades\Maib;
use Nikba\LaravelMaib\Dto\Requests\DirectPaymentRequest;
use Nikba\LaravelMaib\Dto\PaymentItem;
use Nikba\LaravelMaib\Enums\Currency;
use Nikba\LaravelMaib\Enums\Language;
$response = Maib::directPayment(
new DirectPaymentRequest(
amount: 99.99,
currency: Currency::MDL,
clientIp: $request->ip(),
language: Language::RO,
orderId: 'ORDER-001',
description: 'Order #001',
callbackUrl: route('maib.callback'),
okUrl: route('payment.success'),
failUrl: route('payment.fail'),
items: [
new PaymentItem(id: '1', name: 'Product A', price: 99.99, quantity: 1),
],
)
);
return redirect($response->payUrl);
Apple Pay & Google Pay: no separate integration is required. When the customer is redirected to the maib checkout page (
payUrl), maib offers Apple Pay and Google Pay alongside card entry automatically, based on the customer's device and browser. They flow through the same/pay,/hold, and one-click/recurring endpoints and return the same callback payload — so any payment you initiate with this package already supports them.
Two-Step Payment (Hold → Capture)
use Nikba\LaravelMaib\Dto\Requests\CapturePaymentRequest;
use Nikba\LaravelMaib\Dto\Requests\HoldPaymentRequest;
// Step 1: Hold
$hold = Maib::hold(new HoldPaymentRequest(
amount: 99.99,
currency: Currency::MDL,
clientIp: $request->ip(),
language: Language::RO,
));
return redirect($hold->payUrl);
// Step 2: Capture (after callback confirms hold)
$capture = Maib::capture(new CapturePaymentRequest(
payId: $payId,
confirmAmount: 99.99, // optional — omit to capture full hold amount
));
Recurring Payments
use Nikba\LaravelMaib\Dto\Requests\ExecuteRecurringRequest;
use Nikba\LaravelMaib\Dto\Requests\SaveCardRecurringRequest;
// Registration (once per customer — redirect to 3DS checkout)
$save = Maib::savecardRecurring(new SaveCardRecurringRequest(
billerExpiry: '1228',
clientIp: $request->ip(),
currency: Currency::MDL,
language: Language::RO,
email: 'user@example.com',
));
return redirect($save->payUrl);
// Callback will return billerId — store it for the customer.
// Server-side execution (no customer redirect)
$response = Maib::executeRecurring(new ExecuteRecurringRequest(
billerId: $user->maib_biller_id,
amount: 29.99,
currency: Currency::MDL,
orderId: 'SUBSCRIPTION-' . now()->format('Y-m'),
));
if ($response->status === \Nikba\LaravelMaib\Enums\TransactionStatus::OK) {
// subscription renewed
}
One-Click Payments
use Nikba\LaravelMaib\Dto\Requests\ExecuteOneclickRequest;
use Nikba\LaravelMaib\Dto\Requests\SaveCardOneclickRequest;
// Registration
$save = Maib::savecardOneclick(new SaveCardOneclickRequest(
billerExpiry: '1228',
clientIp: $request->ip(),
currency: Currency::MDL,
language: Language::RO,
email: 'user@example.com',
));
return redirect($save->payUrl);
// Execution (redirects customer to checkout for 3DS confirmation)
$response = Maib::executeOneclick(new ExecuteOneclickRequest(
billerId: $user->maib_biller_id,
amount: 49.99,
currency: Currency::MDL,
clientIp: $request->ip(),
language: Language::RO,
));
return redirect($response->payUrl);
Refund
use Nikba\LaravelMaib\Dto\Requests\RefundRequest;
$response = Maib::refund(new RefundRequest(
payId: $payId,
refundAmount: 30.00, // optional — omit to refund the full amount
));
Payment Info
$info = Maib::payInfo($payId);
echo $info->status->value; // 'OK', 'FAILED', etc.
echo $info->cardNumber;
Delete Saved Card
$response = Maib::deleteCard($billerId);
Handling Callbacks
The package automatically registers the callback route at MAIB_CALLBACK_ROUTE (default: /maib/callback). This route is excluded from CSRF middleware.
Register this URL in your maibmerchants.md project settings.
Allowed callback IPs (optional firewall rule): 91.250.245.70, 91.250.245.71, 91.250.245.142
Listening for Events
The package dispatches one of three events per callback, based on the transaction status:
| Event | When | Callback status |
|---|---|---|
MaibPaymentReceived |
A charge succeeded (direct, execute, or save-card with an amount) | OK |
MaibCardSaved |
A card was registered without a charge (savecard-recurring / savecard-oneclick) — billerId is present |
PENDING + billerId |
MaibPaymentFailed |
Anything else (declined, timeout, failed, reversed, or an unknown/undocumented status) | otherwise |
// In App\Providers\EventServiceProvider (or using #[AsEventListener])
use Nikba\LaravelMaib\Events\MaibCardSaved;
use Nikba\LaravelMaib\Events\MaibPaymentFailed;
use Nikba\LaravelMaib\Events\MaibPaymentReceived;
Event::listen(MaibPaymentReceived::class, function (MaibPaymentReceived $event) {
$payload = $event->payload;
Order::where('maib_pay_id', $payload->payId)
->update([
'status' => 'paid',
'rrn' => $payload->rrn,
'approval' => $payload->approval,
'card_number' => $payload->cardNumber,
]);
});
Event::listen(MaibCardSaved::class, function (MaibCardSaved $event) {
// Store the token for future recurring / one-click charges.
User::where('id', $event->payload->orderId)
->update(['maib_biller_id' => $event->payload->billerId]);
});
Event::listen(MaibPaymentFailed::class, function (MaibPaymentFailed $event) {
Order::where('maib_pay_id', $event->payload->payId)
->update(['status' => 'failed']);
});
3-D Secure
The order-fulfilment decision is driven by status, not by threeDs. A status of OK means the payment went through — including the SKIPPED and NOTPARTICIPATED 3DS outcomes, where the card issuer chose not to challenge the cardholder. The threeDs value (a nullable ThreeDsStatus enum on the payload) is available on every event purely for your records/logging; don't gate delivery on it.
Signature validation always runs before any event is dispatched — a callback with a bad signature returns 422 and dispatches nothing. Enum fields on the payload (status, threeDs, currency) are parsed leniently, so a future undocumented value degrades to null instead of erroring the callback (which would otherwise trigger maib's retry loop).
Important: Always return HTTP 200 from your listener logic — the package's callback handler already does this. maib retries the callback at increasing intervals (
10, 60, 300, 600, 3600, 43200, 86400seconds) if it does not receive a 200 response. Implement idempotency in your listeners.
Testing
composer test
Author
Nicolai Bargan
Nikba Creative Studio
office@nikba.com
License
The MIT License (MIT). See LICENSE for details.