Overview
๐ Table of Contents¶
๐๏ธ Shared Foundations
- [Core Interface](#-core-interface) - [Idempotency & Retry](#-idempotency--retry) - [Gateway Capabilities](#-gateway-capabilities) - [Exception Classes](#-exception-classes)๐ Payment Providers
- [๐ฐ Paystack](#-paystack) โ Full API coverage (11 interfaces) - [๐ Flutterwave](#-flutterwave) โ Full API coverage (9 interfaces) - [๐ Seerbit](#-seerbit) โ Full API coverage (5 interfaces) - [โก Stripe](#-stripe) โ Core + Subscriptions + Payment Links - [๐ฆ Other Gateways](#-other-gateways) โ Monnify, Squad, OPay, Remita, Budpay, Mono, Interswitch, PayPal, M-Pesa, MTN MoMo, Razorpay, Paddle๐ ๏ธ Services & Utilities
- [๐ Balance Aggregator](#-balance-aggregation-service) - [๐ Payment Recorder](#-payment-recorder)๐๏ธ Shared Foundations¶
These interfaces and features are available across multiple gateways. Each driver implements only the interfaces it supports.
๐ฏ Core Interface¶
Namespace:
Alphabalex\PaymentMadeEasy\Contracts\PaymentDriverInterfaceAvailable on: All 16 gateways
Every driver implements this interface. These methods provide a consistent API regardless of the underlying gateway.
initializePayment(array $data): array¶
Initialize a payment and get a checkout URL or authorization details.
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string |
โ | Customer email |
amount |
float |
โ | Amount in major currency units (e.g. 5000.00 for โฆ5,000) |
reference |
string |
โ | Unique reference โ auto-generated if omitted |
callback_url |
string |
โ | Redirect URL after payment |
currency |
string |
โ | ISO 4217 currency code โ defaults to config |
metadata |
array |
โ | Custom key/value pairs |
channels |
array |
โ | Payment channels to show (gateway-specific) |
split_code |
string |
โ | Transaction split code (Paystack) |
subaccount |
string |
โ | Subaccount code (Paystack) |
use Alphabalex\PaymentMadeEasy\Facades\Payment;
$response = Payment::initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'reference' => 'ORDER_123',
'callback_url' => 'https://yoursite.com/callback',
'metadata' => ['order_id' => 123],
]);
// $response['data']['authorization_url'] โ redirect customer here
verifyPayment(string $reference): array¶
$response = Payment::driver('paystack')->verifyPayment('ORDER_123');
// $response['data']['status'] === 'success'
getPayment(string $reference): array¶
Get payment details by reference.
refundPayment(string $reference, ?float $amount = null): array¶
Refund a payment โ null for full refund, amount for partial.
Payment::driver('paystack')->refundPayment('ORDER_123', 2500.00); // partial
Payment::driver('paystack')->refundPayment('ORDER_123'); // full
getTransactions(array $params = []): array¶
$transactions = Payment::driver('paystack')->getTransactions([
'per_page' => 50,
'page' => 1,
'status' => 'success',
'from' => '2025-01-01',
'to' => '2025-12-31',
]);
pay(array $data, ?callable $onPaid = null): array¶
Convenience method available on all 15 drivers. Handles multi-step payment flows behind a single call.
| Parameter | Type | Description |
|---|---|---|
data |
array |
Payment data (same as initializePayment plus driver-specific extras) |
onPaid |
callable? |
Optional callback โ receives raw gateway response on success, can return OTP string (Interswitch) |
Return structure:
| Field | Type | Description |
|---|---|---|
response |
array |
Raw gateway response |
success |
bool |
Payment confirmed successful |
status |
string |
completed ยท redirect ยท sdk ยท processing ยท otp_required ยท 3ds_required ยท failed |
reference |
string |
Transaction reference |
auth_url / link / checkout_url |
string? |
Checkout URL (redirect-type drivers) |
client_secret / order_id |
string? |
Client secret for JS SDK (Stripe, Razorpay) |
checkout_request_id / reference_id |
string? |
Async polling ID (Interswitch, MPesa, MTN MoMo) |
// Hosted checkout
$result = Payment::driver('paystack')->pay([
'email' => 'customer@example.com',
'amount' => 5000.00,
]);
// Redirect to $result['auth_url']
// Card-on-file charge
$result = Payment::driver('paystack')->pay([
'email' => 'customer@example.com',
'amount' => 5000.00,
'authorization_code' => 'AUTH_xxxxx',
], function (array $response) {
// called on success
});
// Auto-verify after redirect callback
$result = Payment::driver('paystack')->pay([
'email' => 'customer@example.com',
'amount' => 5000.00,
'_auto_verify' => true,
'reference' => 'ORDER_123',
]);
// Interswitch OTP flow
$result = Payment::driver('interswitch')->pay(
['email' => 'customer@example.com', 'amount' => 5000.00],
fn (array $initResponse) => $otpFromUser
);
// $result['status'] === 'otp_required' | 'completed' | 'failed' | '3ds_required'
Status values by driver:
| Status | Drivers |
|---|---|
completed |
All (after successful verify or direct charge) |
redirect |
Paystack, Flutterwave, Seerbit, Squad, OPay, Budpay, Remita, PayPal, Paddle |
sdk |
Stripe, Razorpay |
processing |
MPesa, MTN MoMo |
otp_required |
Interswitch (when responseCode is T0) |
3ds_required |
Interswitch, Paystack, Flutterwave (Visa 3D Secure) |
failed |
All (on error or declined) |
๐ Idempotency & Retry¶
Built into AbstractPaymentDriver::makeRequest() โ applies to all gateways automatically.
Automatic Idempotency Keys¶
Every mutating request (POST, PUT, PATCH) automatically includes an Idempotency-Key header to prevent duplicate charges.
Priority resolution:
| Priority | Source | Example |
|---|---|---|
| 1 | Explicit idempotency_key option |
'idempotency_key' => 'payout_001' |
| 2 | Header already set | headers: ['Idempotency-Key' => 'custom'] |
| 3 | Auto-generated UUID | idem_a1b2c3d4-e5f6-... |
// Automatic โ gets a generated key
Payment::driver('paystack')->initializePayment([...]);
// Custom key โ for retry-safe operations
Payment::driver('paystack')->transfer([...], 'idempotency_key' => 'payout_001');
Exponential Backoff Retry¶
| What's Retried | What's NOT Retried |
|---|---|
5xx server errors (500, 502, 503, 504) |
4xx client errors (400, 401, 403, 404) |
429 Too Many Requests |
POST/PUT/PATCH without an idempotency key |
| Connection errors (timeouts, DNS failures) |
Backoff formula: delay = min(base ร 2^(attempt-1), max) ร random(0, 1)
// config/payment-gateways.php
'gateways' => [
'paystack' => [
// ...
'retry_attempts' => 3, // default: 3 (0 = disabled)
'retry_base_delay' => 500, // default: 500ms
'retry_max_delay' => 5000, // default: 5000ms (5s)
],
],
๐ง Gateway Capabilities¶
Check what a gateway supports at runtime:
use Alphabalex\PaymentMadeEasy\GatewayCapabilities;
use Alphabalex\PaymentMadeEasy\Contracts\BalanceDriverInterface;
// Check if a specific gateway supports an interface
$supported = GatewayCapabilities::driverImplements('paystack', BalanceDriverInterface::class); // true
// List all gateways that support an interface
$gateways = GatewayCapabilities::gatewaysImplementing(BalanceDriverInterface::class);
// ['paystack', 'flutterwave']
// Get all capabilities for a gateway
$capabilities = GatewayCapabilities::optionalCapabilities('paystack');
| Interface | Description |
|---|---|
PaymentDriverInterface |
Core payment methods (all gateways) |
SubscriptionDriverInterface |
Plans and subscriptions |
DisbursementDriverInterface |
Transfers and payouts |
VirtualAccountDriverInterface |
Dedicated virtual accounts |
PaymentLinkDriverInterface |
Shareable payment links |
CustomerDriverInterface |
Customer CRUD and management |
DisputeDriverInterface |
Disputes and chargebacks |
BalanceDriverInterface |
Account balance queries |
SettlementDriverInterface |
Settlement history |
SubaccountDriverInterface |
Subaccount management (Paystack) |
SplitDriverInterface |
Transaction splits (Paystack) |
ChargeAuthorizationDriverInterface |
Charge stored authorizations |
โ ๏ธ Exception Classes¶
| Exception | Used For |
|---|---|
PaymentException |
Base exception โ all payment errors extend this |
SubscriptionException |
Subscription/plan operation failures |
DisbursementException |
Transfer/disbursement failures |
CustomerException |
Customer management failures |
DisputeException |
Dispute/chargeback failures |
BalanceException |
Balance query failures |
SettlementException |
Settlement query failures |
WebhookException |
Webhook signature/payload validation failures |
All exceptions extend PaymentException โ \RuntimeException.
๐ฐ Paystack¶
Nigeria ยท Ghana ยท Kenya ยท South Africa ยท Cรดte d'Ivoire ยท US
Base URL:
https://api.paystack.coInterfaces: 12 total โ every available interface
โ Feature Matrix¶
| Feature | Status | Key Methods |
|---|---|---|
| ๐ณ One-Time Payments | โ | initializePayment, verifyPayment, getTransactions |
| ๐ Subscriptions | โ | createPlan, createSubscription, pauseSubscription, cancelSubscription |
| ๐ค Customers | โ | createCustomer, validateCustomer, setCustomerRiskAction |
| ๐ Charge Authorization | โ | chargeAuthorization, listAuthorizations |
| ๐ Transaction Details | โ | getTransactionTimeline, getTransactionTotals, exportTransactions |
| ๐ฆ Disbursements | โ | transfer, bulkTransfer, finalizeTransfer, OTP management |
| ๐ง Virtual Accounts | โ | createVirtualAccount, listVirtualAccounts |
| ๐ Payment Links | โ | createPaymentLink, sendPaymentRequest |
| โ๏ธ Disputes | โ | listDisputes, addDisputeEvidence, getDisputeUploadUrl |
| ๐ฐ Balance | โ | getBalance, getLedger |
| ๐ Settlements | โ | listSettlements, getSettlementTransactions |
| ๐ข Subaccounts | โ | createSubaccount, updateSubaccount |
| โ๏ธ Transaction Splits | โ | createSplit, addSubaccountToSplit |
| ๐ธ Refunds | โ | refundPayment, listRefunds, retryRefund |
๐ณ Core Payments¶
$driver = Payment::driver('paystack');
$response = $driver->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'reference' => 'ORDER_123',
'callback_url' => 'https://yoursite.com/callback',
'metadata' => ['order_id' => '123'],
'channels' => ['card', 'bank', 'ussd'],
]);
// Redirect to $response['data']['authorization_url']
$verification = $driver->verifyPayment('ORDER_123');
$payment = $driver->getPayment('ORDER_123');
$txns = $driver->getTransactions([
'per_page' => 50,
'status' => 'success',
]);
๐ Transaction Details¶
$timeline = $driver->getTransactionTimeline('12345');
$totals = $driver->getTransactionTotals([
'from' => '2025-01-01',
'to' => '2025-12-31',
]);
$export = $driver->exportTransactions([
'from' => '2025-01-01',
'to' => '2025-12-31',
]);
๐ Subscriptions & Plans¶
// Plans
$plan = $driver->createPlan([
'name' => 'Pro Monthly',
'amount' => 5000.00,
'interval' => 'monthly',
'currency' => 'NGN',
'description' => 'Full access',
'trial_days' => 7,
]);
$planCode = $plan['data']['plan_code'];
$driver->getPlan($planCode);
$driver->updatePlan($planCode, ['name' => 'Pro Monthly (Updated)']);
$driver->listPlans(['per_page' => 20]);
$driver->deletePlan($planCode);
// Subscriptions
$sub = $driver->createSubscription([
'customer' => 'customer@example.com',
'plan' => $planCode,
'start_date' => '2025-06-01T00:00:00.000Z',
]);
$subCode = $sub['data']['subscription_code'];
$driver->getSubscription($subCode);
$driver->listSubscriptions(['plan' => $planCode]);
$driver->listCustomerSubscriptions('customer@example.com');
$driver->pauseSubscription($subCode);
$driver->resumeSubscription($subCode);
$driver->cancelSubscription($subCode);
๐ค Customer Management¶
$customer = $driver->createCustomer([
'email' => 'customer@example.com',
'first_name' => 'John',
'last_name' => 'Doe',
'phone' => '+2348012345678',
]);
$code = $customer['data']['customer_code'];
$customer = $driver->getCustomer('CUS_xxxxx');
$customers = $driver->listCustomers(['per_page' => 50]);
$driver->updateCustomer($code, ['first_name' => 'Jane']);
// Validate identity (bank account)
$driver->validateCustomer($code, [
'country' => 'NG',
'type' => 'bank_account',
'account_number' => '0123456789',
'bvn' => '20012345677',
'bank_code' => '058',
'first_name' => 'John',
'last_name' => 'Doe',
]);
// Risk management
$driver->setCustomerRiskAction($code, 'allow'); // whitelist
$driver->setCustomerRiskAction($code, 'deny'); // blacklist
$driver->setCustomerRiskAction($code, 'default'); // reset
๐ Charge Authorization (Recurring Payments)¶
$response = $driver->chargeAuthorization([
'email' => 'customer@example.com',
'amount' => 5000.00,
'authorization_code' => 'AUTH_xxxxx',
'reference' => 'RECURRING_001',
'currency' => 'NGN',
]);
$auth = $driver->getAuthorization('AUTH_xxxxx');
$auths = $driver->listAuthorizations(['per_page' => 50]);
$driver->deactivateAuthorization('AUTH_xxxxx');
๐ฆ Disbursements & Transfers¶
$account = $driver->resolveAccountNumber([
'account_number' => '0123456789',
'bank_code' => '044',
]);
$recipient = $driver->createTransferRecipient([
'name' => 'Jane Doe',
'account_number' => '0123456789',
'bank_code' => '044',
'currency' => 'NGN',
]);
$rCode = $recipient['data']['recipient_code'];
$transfer = $driver->transfer([
'amount' => 10000.00,
'recipient' => $rCode,
'reason' => 'Salary payout',
'reference' => 'PAYOUT_001',
]);
$driver->bulkTransfer([
['amount' => 5000.00, 'recipient' => 'RCP_abc', 'reference' => 'B1'],
['amount' => 3000.00, 'recipient' => 'RCP_def', 'reference' => 'B2'],
]);
$driver->verifyTransfer('PAYOUT_001');
$driver->getTransfer('TRF_xxxxx');
$driver->listTransfers(['per_page' => 50]);
// OTP management
$driver->finalizeTransfer('TRF_xxxxx', '123456');
$driver->disableTransferOtp(['code' => '123456']);
$driver->resendTransferOtp('TRF_xxxxx');
// Banks
$banks = $driver->listBanks(['country' => 'nigeria']);
๐ง Virtual Accounts¶
$va = $driver->createVirtualAccount([
'email' => 'customer@example.com',
'name' => 'Jane Doe',
'bvn' => '12345678901',
'preferred_bank' => 'wema-bank',
]);
$accountNumber = $va['data']['account_number'];
$driver->getVirtualAccount($va['data']['id']);
$driver->listVirtualAccounts(['active' => true]);
$driver->deactivateVirtualAccount($va['data']['id']);
๐ Payment Links & Requests¶
$link = $driver->createPaymentLink([
'name' => 'Product Launch',
'amount' => 2500.00,
'description' => 'Early-bird ticket',
'currency' => 'NGN',
]);
$url = $link['data']['link'];
$driver->getPaymentLink($link['data']['id']);
$driver->listPaymentLinks();
$driver->updatePaymentLink($link['data']['id'], ['amount' => 3000.00]);
$driver->disablePaymentLink($link['data']['id']);
// Payment request extensions
$driver->sendPaymentRequest('PRQ_xxxxx', 'email');
$pdf = $driver->getPaymentRequestPdf('PRQ_xxxxx');
โ๏ธ Disputes¶
$disputes = $driver->listDisputes(['status' => 'pending']);
$dispute = $driver->getDispute('2867');
$driver->updateDispute('2867', ['refund_amount' => 1002]);
$driver->addDisputeEvidence('2867', [
'customer_email' => 'customer@example.com',
'customer_name' => 'John Doe',
'customer_phone' => '+2348012345678',
'service_details' => 'Service delivered on 2025-01-15',
]);
$upload = $driver->getDisputeUploadUrl('2867');
$txDisputes = $driver->getTransactionDisputes('12345');
$driver->respondToDisputeMessage('2867', ['message' => 'Delivered']);
๐ฐ Balance & Settlements¶
$balance = $driver->getBalance(); // all currencies
$ngn = $driver->getBalance('NGN'); // specific currency
$ledger = $driver->getLedger(); // includes pending
$settlements = $driver->listSettlements(['status' => 'success']);
$txns = $driver->getSettlementTransactions('3090024', ['per_page' => 50]);
๐ข Subaccounts¶
$subaccount = $driver->createSubaccount([
'business_name' => 'Vendor Store',
'settlement_bank' => '058',
'account_number' => '0123456047',
'percentage_charge' => 30.0,
'description' => 'Vendor marketplace account',
]);
$code = $subaccount['data']['subaccount_code'];
$driver->listSubaccounts();
$driver->getSubaccount($code);
$driver->updateSubaccount($code, ['business_name' => 'Updated Name']);
โ๏ธ Transaction Splits¶
$split = $driver->createSplit([
'name' => 'Marketplace Split',
'type' => 'percentage',
'currency' => 'NGN',
'subaccounts' => [
['subaccount' => 'ACCT_vendor1', 'share' => 40],
['subaccount' => 'ACCT_vendor2', 'share' => 30],
],
'bearer_type' => 'all-proportional',
]);
$splitCode = $split['data']['split_code'];
$driver->listSplits();
$driver->getSplit($splitCode);
$driver->updateSplit($splitCode, ['bearer_type' => 'all']);
$driver->addSubaccountToSplit($splitCode, 'ACCT_vendor3', 20);
$driver->removeSubaccountFromSplit($splitCode, 'ACCT_vendor2');
// Use with payments
$driver->initializePayment([
'email' => 'customer@example.com',
'amount' => 10000.00,
'split_code' => $splitCode,
]);
๐ธ Refunds¶
$refund = $driver->refundPayment('ORDER_123', 2500.00); // partial
$driver->refundPayment('ORDER_123'); // full
$driver->getRefund('REF_xxxxx');
$driver->listRefunds(['per_page' => 50]);
$driver->retryRefund('REF_xxxxx');
๐ Webhooks¶
| Event | Description |
|---|---|
charge.success |
Payment completed |
charge.failed |
Payment failed |
charge.pending |
Payment pending |
refund.created / refund.processed |
Refund events |
subscription.create / subscription.disable |
Subscription lifecycle |
transfer.success / transfer.failed / transfer.reversed |
Transfer events |
dispute.created / dispute.resolved |
Dispute events |
๐ Configuration¶
PAYSTACK_PUBLIC_KEY=pk_live_xxxxx
PAYSTACK_SECRET_KEY=sk_live_xxxxx
PAYSTACK_CALLBACK_URL=https://yoursite.com/payment/callback
PAYSTACK_WEBHOOK_SECRET=your_paystack_webhook_secret
---¶
๐ Flutterwave¶
Nigeria ยท Ghana ยท Kenya ยท South Africa ยท US ยท UK ยท 30+ countries
Base URL:
https://api.flutterwave.com/v3Interfaces: 9 total
โ Feature Matrix¶
| Feature | Status | Key Methods |
|---|---|---|
| ๐ณ One-Time Payments | โ | initializePayment, verifyPayment, getTransactions |
| ๐ Subscriptions | โ | createPlan, createSubscription, pauseSubscription, cancelSubscription |
| ๐ค Customers | โ | createCustomer, getCustomer, listCustomers, updateCustomer |
| ๐ Charge Authorization | โ | chargeAuthorization, listAuthorizations, deactivateAuthorization |
| ๐ Transaction Details | โ | getTransactionFees, getTransactionRefs, resendTransactionHook |
| ๐ฆ Disbursements | โ | transfer, bulkTransfer, verifyTransfer, retryTransfer, getTransferById |
| ๐ง Virtual Accounts | โ | createVirtualAccount, listVirtualAccounts |
| ๐ Payment Links & Pages | โ | createPaymentLink, createPaymentPage, listPaymentPages |
| โ๏ธ Chargebacks | โ | listChargebacks, acceptChargeback, rejectChargeback |
| ๐ฐ Balance | โ | getBalance, getBalanceByCurrency |
| ๐ Settlements | โ | listSettlements, getSettlement |
| ๐ค Beneficiaries | โ | createBeneficiary, getBeneficiary, listBeneficiaries, deleteBeneficiary |
| ๐ธ Refunds | โ | refundPayment, listRefunds, getRefund |
๐ณ Core Payments¶
$driver = Payment::driver('flutterwave');
$response = $driver->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'name' => 'John Doe',
'phone' => '+2348123456789',
'currency' => 'NGN',
]);
// Redirect to $response['data']['link']
$verification = $driver->verifyPayment('ORDER_123');
$payment = $driver->getPayment('ORDER_123');
$txns = $driver->getTransactions(['per_page' => 50]);
๐ Transaction Details¶
$fees = $driver->getTransactionFees([
'amount' => 5000,
'currency' => 'NGN',
]);
$refs = $driver->getTransactionRefs('ORDER_123');
$driver->resendTransactionHook('ORDER_123');
๐ Subscriptions & Plans¶
// Plans
$plan = $driver->createPlan([
'name' => 'Pro Monthly',
'amount' => 5000.00,
'interval' => 'monthly',
'currency' => 'NGN',
]);
$planCode = $plan['data']['id'];
$driver->getPlan($planCode);
$driver->updatePlan($planCode, ['name' => 'Pro Monthly (Updated)']);
$driver->listPlans();
$driver->deletePlan($planCode);
// Subscriptions
$sub = $driver->createSubscription([
'customer' => 'customer@example.com',
'plan' => $planCode,
]);
$subCode = $sub['data']['id'];
$driver->getSubscription($subCode);
$driver->listSubscriptions(['plan' => $planCode]);
$driver->listCustomerSubscriptions('customer@example.com');
$driver->pauseSubscription($subCode);
$driver->resumeSubscription($subCode);
$driver->cancelSubscription($subCode);
๐ค Customer Management¶
$customer = $driver->createCustomer([
'email' => 'customer@example.com',
'first_name' => 'John',
'last_name' => 'Doe',
'phone' => '+2348012345678',
]);
$cId = $customer['data']['id'];
$customer = $driver->getCustomer($cId);
$customers = $driver->listCustomers(['per_page' => 50]);
$driver->updateCustomer($cId, ['first_name' => 'Jane']);
๐ Charge Authorization (Recurring Payments)¶
$response = $driver->chargeAuthorization([
'email' => 'customer@example.com',
'amount' => 5000.00,
'authorization_code' => 'AUTH_xxxxx',
'currency' => 'NGN',
]);
$auths = $driver->listAuthorizations(['per_page' => 50]);
$driver->deactivateAuthorization('AUTH_xxxxx');
๐ฆ Disbursements & Transfers¶
$account = $driver->resolveAccountNumber([
'account_number' => '0123456789',
'bank_code' => '044',
]);
$recipient = $driver->createTransferRecipient([
'name' => 'Jane Doe',
'account_number' => '0123456789',
'bank_code' => '044',
'currency' => 'NGN',
]);
$rCode = $recipient['data']['recipient_code'];
$transfer = $driver->transfer([
'amount' => 10000.00,
'recipient' => $rCode,
'reference' => 'PAYOUT_001',
]);
$driver->bulkTransfer([
['amount' => 5000.00, 'recipient' => 'RCP_abc', 'reference' => 'B1'],
['amount' => 3000.00, 'recipient' => 'RCP_def', 'reference' => 'B2'],
]);
$driver->verifyTransfer('PAYOUT_001');
$driver->getTransfer('1');
$driver->retryTransfer('1');
$driver->listTransfers(['per_page' => 50]);
// Fees & rates
$fees = $driver->getTransferFees(['amount' => 5000, 'currency' => 'NGN']);
$rates = $driver->getTransferRates(['amount' => 100, 'currency' => 'USD']);
// Banks
$banks = $driver->listBanks();
๐ง Virtual Accounts¶
$va = $driver->createVirtualAccount([
'email' => 'customer@example.com',
'bvn' => '12345678901',
]);
$accountNumber = $va['data']['account_number'];
$driver->getVirtualAccount($va['data']['id']);
$driver->listVirtualAccounts();
$driver->deactivateVirtualAccount($va['data']['id']);
๐ Payment Links & Pages¶
// Payment Links
$link = $driver->createPaymentLink([
'title' => 'Product Launch',
'amount' => 2500.00,
'currency' => 'NGN',
]);
$url = $link['data']['link'];
$driver->getPaymentLink($link['data']['id']);
$driver->listPaymentLinks();
$driver->updatePaymentLink($link['data']['id'], ['amount' => 3000.00]);
$driver->disablePaymentLink($link['data']['id']);
// Payment Pages
$page = $driver->createPaymentPage([
'title' => 'Donation Page',
'amount' => 0,
'currency' => 'NGN',
]);
$driver->listPaymentPages();
$driver->getPaymentPage('1');
โ๏ธ Chargebacks (Disputes)¶
$chargebacks = $driver->listChargebacks();
$chargeback = $driver->getChargeback('1');
$driver->acceptChargeback('1');
$driver->rejectChargeback('1');
$driver->updateChargebackEvidence('1', ['evidence' => 'Proof of delivery']);
// DisputeDriverInterface methods
$disputes = $driver->listDisputes();
$driver->getDispute('1');
$driver->updateDispute('1', ['refund_amount' => 500]);
๐ฐ Balance & Settlements¶
// Balance
$balance = $driver->getBalance(); // default currency
$usd = $driver->getBalanceByCurrency('USD');
$ledger = $driver->getLedger();
// Settlements
$settlements = $driver->listSettlements(['per_page' => 50]);
$settlement = $driver->getSettlement('1');
๐ค Beneficiaries¶
$beneficiary = $driver->createBeneficiary([
'bank_code' => '044',
'account_number' => '0123456789',
'name' => 'Jane Doe',
'currency' => 'NGN',
]);
$driver->getBeneficiary('1');
$driver->listBeneficiaries();
$driver->deleteBeneficiary('1');
๐ธ Refunds¶
$refund = $driver->refundPayment('ORDER_123', 2500.00); // partial
$driver->refundPayment('ORDER_123'); // full
$driver->listRefunds();
$driver->getRefund('REF_xxxxx');
๐ Webhooks¶
| Event | Description |
|---|---|
charge.completed |
Payment completed |
charge.failed |
Payment failed |
charge.created |
Payment initiated |
transfer.completed |
Transfer completed |
transfer.failed |
Transfer failed |
transfer.pending |
Transfer pending |
refund.completed |
Refund processed |
subscription.enable |
Subscription activated |
subscription.disable |
Subscription cancelled |
subscription.not_renew |
Subscription expired |
dedicated_account.assign.image |
VA assigned |
chargeback.created / chargeback.resolved |
Chargeback events |
settlements |
Settlement events |
๐ Configuration¶
FLUTTERWAVE_PUBLIC_KEY=FLWPUBK-xxxxx
FLUTTERWAVE_SECRET_KEY=FLWSECK-xxxxx
FLUTTERWAVE_ENCRYPTION_KEY=your_flutterwave_encryption_key
FLUTTERWAVE_CALLBACK_URL=https://yoursite.com/payment/callback
FLUTTERWAVE_WEBHOOK_SECRET=your_flutterwave_webhook_secret
---¶
๐ Seerbit¶
Nigeria ยท Ghana ยท Kenya ยท Other African markets
Base URL:
https://seerbitapi.com/api/v2Interfaces: 5 total
โ Feature Matrix¶
| Feature | Status | Key Methods |
|---|---|---|
| ๐ณ One-Time Payments | โ | initializePayment, verifyPayment, getTransactions |
| ๐ Subscriptions | โ | createPlan, createSubscription, chargeSubscription |
| ๐ฆ Disbursements | โ | transfer, bulkTransfer, verifyTransfer, listTransfers |
| ๐ง Virtual Accounts | โ | createVirtualAccount, listVirtualAccounts |
| ๐ Payment Links | โ | createPaymentLink, listPaymentLinks |
| ๐ Card Operations | โ | tokenizeCard, chargeCard, authorizeCard, validateOtp |
| ๐ฑ Mobile Money | โ | initializeMobilePayment, getMobileNetworks |
| ๐ฆ Order Payments | โ | createOrder |
| โ๏ธ Disputes | โ | listDisputes, getDispute, resolveDispute |
| ๐ฐ Balance | โ | getBalance, getLedger |
| ๐ธ Refunds | โ | refundPayment, listRefunds, getRefund, createRefund |
| ๐ Encryption | โ | encryptKey |
๐ณ Core Payments¶
$driver = Payment::driver('seerbit');
$response = $driver->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'currency' => 'NGN',
'name' => 'John Doe',
'phone' => '+2348123456789',
]);
// Redirect to $response['data']['payments']['link']
$verification = $driver->verifyPayment('ORDER_123');
$payment = $driver->getPayment('ORDER_123');
$txns = $driver->getTransactions();
๐ Card Operations¶
Direct card payment APIs beyond the standard checkout flow:
// Tokenize a card for recurring payments
$response = $driver->tokenizeCard([
'card_number' => '4187427415564246',
'expiry_month' => '12',
'expiry_year' => '2030',
'cvv' => '123',
'email' => 'customer@example.com',
]);
$token = $response['data']['token'];
// Charge a card directly (non-3D secure)
$driver->chargeCard([
'card_number' => '4187427415564246',
'expiry_month' => '12',
'expiry_year' => '2030',
'cvv' => '123',
'amount' => 5000.00,
'email' => 'customer@example.com',
'pin' => '1234', // optional
]);
// Charge using a token
$driver->chargeCard([
'token' => $token,
'amount' => 5000.00,
'email' => 'customer@example.com',
]);
// Authorize a card (3D secure)
$response = $driver->authorizeCard([
'amount' => 5000.00,
'email' => 'customer@example.com',
'token' => $token,
]);
// Redirect to $response['data']['payments']['redirectLink']
// Validate OTP
$driver->validateOtp([
'otp' => '123456',
'transaction_reference' => 'TRF_xxxxx',
'amount' => 5000.00,
]);
// Capture / Cancel pre-authorized payment
$driver->capturePayment([
'transaction_reference' => 'TRF_xxxxx',
'amount' => 5000.00,
]);
$driver->cancelPayment([
'transaction_reference' => 'TRF_xxxxx',
]);
๐ฑ Mobile Money¶
$response = $driver->initializeMobilePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'phone' => '256771234567',
'network_code' => 'MTN', // optional
]);
$networks = $driver->getMobileNetworks();
๐ฆ Order Payments¶
$response = $driver->createOrder([
'email' => 'customer@example.com',
'amount' => 5000.00,
'description' => 'Order #123',
'items' => [
['name' => 'Product A', 'quantity' => 2, 'amount' => 2500.00],
],
]);
๐ Subscriptions & Plans¶
$plan = $driver->createPlan([
'name' => 'Pro Monthly',
'amount' => 5000.00,
'interval' => 'monthly',
]);
$planCode = $plan['data']['planCode'];
$driver->getPlan($planCode);
$driver->listPlans();
$driver->updatePlan($planCode, ['planName' => 'Pro Monthly (Updated)']);
$driver->deletePlan($planCode);
$sub = $driver->createSubscription([
'email' => 'customer@example.com',
'plan' => $planCode,
]);
// Seerbit-specific subscription extensions
$driver->chargeSubscription([
'subscription_id' => 'SUB_xxxxx',
'amount' => 5000.00,
]);
$driver->getCustomerSubscriptions('CUST_xxxxx');
$driver->getMerchantSubscriptions();
๐ฆ Disbursements & Payouts¶
// Single payout
$transfer = $driver->transfer([
'amount' => 10000.00,
'bank_code' => '044',
'recipient_code' => '0123456789', // account number
'reason' => 'Salary payout',
'currency' => 'NGN',
]);
// Bulk payouts
$driver->bulkTransfer([
['amount' => 5000.00, 'bank_code' => '044', 'recipient_code' => '0123456789'],
['amount' => 3000.00, 'bank_code' => '058', 'recipient_code' => '0987654321'],
]);
$driver->verifyTransfer('SEERBIT_REF_001');
$driver->listTransfers(['page' => 0, 'perPage' => 50]);
// Banks
$banks = $driver->listBanks();
$account = $driver->resolveAccountNumber([
'account_number' => '0123456789',
'bank_code' => '044',
]);
๐ง Virtual Accounts¶
$va = $driver->createVirtualAccount([
'email' => 'customer@example.com',
'name' => 'Jane Doe',
'bvn' => '12345678901',
'preferred_bank' => '058',
'currency' => 'NGN',
]);
$driver->getVirtualAccount($va['data']['reference']);
$driver->listVirtualAccounts();
$driver->deactivateVirtualAccount($va['data']['reference']);
๐ Payment Links¶
$link = $driver->createPaymentLink([
'name' => 'Product Launch',
'amount' => 2500.00,
]);
$driver->getPaymentLink($link['data']['linkId']);
$driver->listPaymentLinks();
$driver->updatePaymentLink($link['data']['linkId'], ['amount' => 3000.00]);
$driver->disablePaymentLink($link['data']['linkId']);
โ๏ธ Disputes¶
$disputes = $driver->listDisputes(['page' => 0, 'size' => 10]);
$dispute = $driver->getDispute('DSP_001');
$driver->updateDispute('DSP_001', ['refundAmount' => 1000]);
$driver->addDisputeEvidence('DSP_001', ['evidence' => 'Proof of delivery']);
$driver->resolveDispute('DSP_001', 'accept');
$driver->resolveDispute('DSP_001', 'reject');
๐ฐ Balance¶
$balance = $driver->getBalance(); // all currencies
$ngn = $driver->getBalance('NGN'); // specific currency
$ledger = $driver->getLedger(); // ledger balance
๐ธ Refunds¶
$driver->refundPayment('ORDER_123', 2500.00);
// Merchant API for advanced refund management
$refunds = $driver->listRefunds(['page' => 0, 'size' => 10]);
$refund = $driver->getRefund('REF_001');
$driver->createRefund([
'paymentReference' => 'ORDER_001',
'refundAmount' => 2500.00,
'refundReason' => 'Customer request',
]);
๐ Encryption¶
๐ Webhooks¶
| Event | Description |
|---|---|
SUCCESS |
Payment completed |
FAILED |
Payment failed |
ABANDONED |
Payment abandoned |
๐ Configuration¶
SEERBIT_PUBLIC_KEY=your_seerbit_public_key
SEERBIT_SECRET_KEY=your_seerbit_secret_key
SEERBIT_BASE_URL=https://seerbitapi.com/api/v2
SEERBIT_CALLBACK_URL=https://yoursite.com/payment/callback
SEERBIT_WEBHOOK_SECRET=your_seerbit_webhook_secret
---¶
โก Stripe¶
Global ยท 135+ currencies
Base URL:
https://api.stripe.com/v1Interfaces:PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทPaymentLinkDriverInterfacePackage: Requiresstripe/stripe-php:^17.4
โ Feature Matrix¶
| Feature | Status | Key Methods |
|---|---|---|
| ๐ณ One-Time Payments | โ | initializePayment, verifyPayment |
| ๐ Subscriptions | โ | createPlan (Product+Price), createSubscription, cancelSubscription |
| ๐ Payment Links | โ | createPaymentLink, listPaymentLinks |
Note: Stripe maps plans to Product + Price.
createPlan()returns a Price ID inplan_codeโ pass that tocreateSubscription(). Supported intervals:day,week,month,year.
// Core payments
$response = Payment::driver('stripe')->initializePayment([
'email' => 'customer@example.com',
'amount' => 50.00,
'currency' => 'usd',
]);
// Subscriptions
$plan = Payment::driver('stripe')->createPlan([
'name' => 'Pro Monthly',
'amount' => 29.99,
'currency' => 'usd',
'interval' => 'month',
]);
$priceId = $plan['data']['plan_code'];
Payment::driver('stripe')->createSubscription([
'email' => 'customer@example.com',
'plan_code' => $priceId,
]);
๐ Configuration¶
STRIPE_PUBLIC_KEY=pk_live_xxxxx
STRIPE_SECRET_KEY=sk_live_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
STRIPE_CALLBACK_URL=https://yoursite.com/payment/callback
---¶
๐ฆ Other Gateways¶
These gateways provide core payment capabilities through the shared
PaymentDriverInterface.
๐ผ Monnify¶
Nigeria ยท Virtual accounts + disbursements + Direct Debit mandates + card tokenization Interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทChargeAuthorizationDriverInterfaceยทDisbursementDriverInterfaceยทVirtualAccountDriverInterfaceยทBalanceDriverInterfaceยทSettlementDriverInterfaceยทPaymentLinkDriverInterface
Authentication: Automatic Bearer token via Basic Auth (API key + Secret key).
Direct Debit Mandates (Subscriptions)¶
// Create mandate
$sub = Payment::driver('monnify')->createSubscription([
'email' => 'customer@example.com',
'name' => 'Jane Doe',
'amount' => 5000.00,
'bank_code' => '044',
'account_number' => '0123456789',
'mandate_type' => 'CLOSED_FLEXIBLE',
'frequency' => 'MONTHLY',
]);
$mandateRef = $sub['data']['mandate_reference'];
// Customer authorizes via authorization link, then:
Payment::driver('monnify')->cancelSubscription($mandateRef);
Payment::driver('monnify')->pauseSubscription($mandateRef);
Payment::driver('monnify')->getSubscription($mandateRef);
Card Tokenization (Charge Auth)¶
// After first card payment, store the cardToken from transaction response
// Then charge it directly:
Payment::driver('monnify')->chargeAuthorization([
'authorization_code' => 'card_token_from_first_payment',
'amount' => 5000.00,
'email' => 'customer@example.com',
]);
// Payments
Payment::driver('monnify')->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'currency' => 'NGN',
'name' => 'John Doe',
'description' => 'Order #123',
'reference' => 'ORDER_123',
]);
// Virtual accounts (reserved bank accounts)
$va = Payment::driver('monnify')->createVirtualAccount([
'email' => 'customer@example.com',
'name' => 'Jane Doe',
'bvn' => '12345678901',
'currency_code' => 'NGN',
'contract_code' => config('payment-gateways.gateways.monnify.contract_code'),
'reference' => 'VA_' . uniqid(),
'split_percentages' => [],
]);
// Disbursements
Payment::driver('monnify')->transfer([
'amount' => 10000.00,
'account_number' => '0123456789',
'bank_code' => '044',
'narration' => 'Salary payout',
'reference' => 'MNFY_PAYOUT_001',
'wallet_account_number' => config('payment-gateways.gateways.monnify.wallet_account_number'),
]);
MONNIFY_API_KEY=your_monnify_api_key
MONNIFY_SECRET_KEY=your_monnify_secret_key
MONNIFY_CONTRACT_CODE=your_monnify_contract_code
MONNIFY_WALLET_ACCOUNT_NUMBER=your_monnify_wallet_account
MONNIFY_CALLBACK_URL=https://yoursite.com/payment/callback
MONNIFY_WEBHOOK_SECRET=your_monnify_webhook_secret
๐ข Squad (GTCo)¶
Nigeria ยท Payments + disbursements + virtual accounts Interfaces:
PaymentDriverInterfaceยทDisbursementDriverInterfaceยทVirtualAccountDriverInterface
Payment::driver('squad')->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'currency' => 'NGN',
'reference' => 'TXN_' . uniqid(),
]);
Payment::driver('squad')->transfer([
'account_number' => '0123456789',
'bank_code' => '044',
'amount' => 5000.00,
'currency' => 'NGN',
'reference' => 'SQ_PAYOUT_001',
'narration' => 'Salary',
]);
Payment::driver('squad')->createVirtualAccount([
'customer_identifier' => 'customer_001',
'email' => 'customer@example.com',
'name' => 'Jane Doe',
]);
SQUAD_SECRET_KEY=your_squad_secret_key
SQUAD_PUBLIC_KEY=your_squad_public_key
SQUAD_BENEFICIARY_ACCOUNT=your_squad_beneficiary_account
SQUAD_CALLBACK_URL=https://yoursite.com/payment/callback
SQUAD_WEBHOOK_SECRET=your_squad_webhook_secret
๐ OPay¶
Nigeria ยท Card payments + bank transfers + virtual accounts Interfaces:
PaymentDriverInterfaceยทDisbursementDriverInterfaceยทVirtualAccountDriverInterfaceยทBalanceDriverInterfaceAuth: Private Key HMAC-SHA512 signing โ handled internally. Supports card payments, bank transfers, USSD, OPay wallet, and virtual accounts.
// One-time payment
$payment = Payment::driver('opay')->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'currency' => 'NGN',
'reference' => 'OPAY_' . uniqid(),
]);
// Redirect to $payment['data']['checkout_url']
Payment::driver('opay')->verifyPayment($payment['data']['reference']);
// Refund
Payment::driver('opay')->refundPayment([
'reference' => 'OPAY_001',
'amount' => 2500.00,
]);
// Disbursements
Payment::driver('opay')->transfer([
'amount' => 5000.00,
'currency' => 'NGN',
'account_number' => '0123456789',
'bank_code' => '044',
'narration' => 'Payout',
'reference' => 'OPAY_PAYOUT_001',
]);
// Bulk transfers
Payment::driver('opay')->bulkTransfer([
'currency' => 'NGN',
'source' => 'balance',
'batch_reference' => 'OPAY_BATCH_001',
'entries' => [
['amount' => 5000.00, 'account_number' => '0123456789', 'bank_code' => '044', 'narration' => 'Payout'],
],
]);
// Virtual accounts
Payment::driver('opay')->createVirtualAccount([
'name' => 'Jane Doe',
'email' => 'customer@example.com',
'phone' => '+2348012345678',
'reference' => 'VA_' . uniqid(),
'bvn' => '12345678901',
'bank_code' => '044',
'currency' => 'NGN',
'expiration' => '365',
]);
// Beneficiaries
Payment::driver('opay')->createBeneficiary([
'name' => 'John Doe',
'account_number' => '0123456789',
'bank_code' => '044',
'currency' => 'NGN',
]);
// Balance
$balance = Payment::driver('opay')->getBalance('NGN');
$ledger = Payment::driver('opay')->getLedger('NGN');
$statement = Payment::driver('opay')->getWalletStatement('NGN');
Webhooks: HMAC-SHA512 signed.
OPAY_MERCHANT_ID=your_opay_merchant_id
OPAY_PRIVATE_KEY=your_opay_private_key
OPAY_BASE_URL=https://live.opayng.com
OPAY_ENVIRONMENT=production
OPAY_CURRENCY=NGN
OPAY_CALLBACK_URL=https://yoursite.com/payment/callback
OPAY_WEBHOOK_SECRET=your_opay_webhook_secret
๐ต Remita¶
Nigeria ยท RRR-based payments + Direct Debit mandates + disbursements Interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทDisbursementDriverInterfaceยทChargeAuthorizationDriverInterfaceยทSplitDriverInterfaceยทPaymentLinkDriverInterfaceAuth: SHA512 hash of
merchantId + serviceTypeId + orderId + amount + apiKeyin the Authorization header.
Payments (RRR flow)¶
$result = Payment::driver('remita')->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'description' => 'Invoice #1001',
'reference' => 'ORDER_1001',
]);
// Redirect to $result['authorization_url']
Payment::driver('remita')->verifyPayment($result['RRR']);
Subscriptions (Direct Debit Mandates)¶
Remita uses mandates for recurring billing โ customer provides bank details, merchant sends debit instructions.
// Create mandate (subscription)
$sub = Payment::driver('remita')->createSubscription([
'email' => 'customer@example.com',
'payer_name' => 'Jane Doe',
'amount' => 5000.00,
'bank_code' => '044',
'account_number' => '0123456789',
'start_date' => '2026-01-01',
'end_date' => '2026-12-31',
'max_debits' => 12,
]);
$mandateId = $sub['data']['mandate_id'];
// Send debit instruction
Payment::driver('remita')->chargeAuthorization([
'authorization_code' => $mandateId,
'amount' => 5000.00,
]);
// Pause (cancel debit instruction)
Payment::driver('remita')->pauseSubscription($mandateId);
// Cancel (stop mandate)
Payment::driver('remita')->cancelSubscription($mandateId);
// Get mandate status
Payment::driver('remita')->getSubscription($mandateId);
Split Payments (LineItems)¶
Remita supports splits via lineItems in the RRR generation payload:
Payment::driver('remita')->initializePayment([
'email' => 'customer@example.com',
'amount' => 10000.00,
'line_items' => [
['lineItemsId' => 'VEN1', 'beneficiaryName' => 'Vendor 1', 'beneficiaryAccount' => '0123456789', 'bankCode' => '044', 'beneficiaryAmount' => '6000', 'deductFeeFrom' => '1'],
['lineItemsId' => 'VEN2', 'beneficiaryName' => 'Vendor 2', 'beneficiaryAccount' => '9876543210', 'bankCode' => '033', 'beneficiaryAmount' => '4000', 'deductFeeFrom' => '0'],
],
]);
Payment Links¶
Remita payment links are RRR-based hosted checkout pages:
$link = Payment::driver('remita')->createPaymentLink([
'amount' => 5000.00,
'name' => 'Product Launch',
'description' => 'Early-bird ticket',
]);
// $link['data']['url'] โ shareable checkout URL
Disbursements¶
Payment::driver('remita')->transfer([
'amount' => 10000.00,
'account_number' => '0123456789',
'bank_code' => '044',
'narration' => 'Salary payout',
]);
Payment::driver('remita')->bulkTransfer([
'batchRef' => 'BATCH_001',
'narration' => 'Payroll',
'transfers' => [
['amount' => 5000.00, 'account_number' => '0123456789', 'bank_code' => '044', 'name' => 'Jane Doe'],
],
]);
REMITA_API_KEY=your_remita_api_key
REMITA_MERCHANT_ID=your_remita_merchant_id
REMITA_SERVICE_TYPE_ID=your_remita_service_type_id
REMITA_SOURCE_ACCOUNT=your_remita_source_account
REMITA_CALLBACK_URL=https://yoursite.com/payment/callback
๐ฃ Mono¶
Nigeria ยท One-time DirectPay and Direct Debit mandate collections Shared interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทChargeAuthorizationDriverInterfaceยทBalanceDriverInterfaceMono-specific endpoints: Customers, DirectPay, mandate operations, settlement payout queries, split sub-accounts, Mono Disburse, and Owo/WhatsApp payments.Auth:
mono-sec-keyheader with the Payments app secret key. For sandbox use Mono's test secret key. The package accepts amounts in major currency units and converts to kobo for amount-bearing requests.
One-time DirectPay¶
$driver = Payment::driver('mono');
$checkout = $driver->initializePayment([
'email' => 'customer@example.com',
'name' => 'Jane Doe',
'amount' => 5000.00,
'method' => 'account', // account, transfer, or whatsapp
'reference' => 'ORDER_123',
'callback_url' => 'https://yoursite.com/payment/callback',
'meta' => ['order_id' => 123],
]);
// Redirect to $checkout['data']['mono_url'].
// Verify before fulfilling the order; then query/filter transaction history.
$payment = $driver->verifyPayment('ORDER_123');
$payments = $driver->getTransactions(['page' => 1, 'status' => 'successful', 'limit' => 100]);
// Mono's documented refund endpoint performs full refunds only.
$driver->refundPayment('ORDER_123');
$driver->initiateRefund('ORDER_123', 'wallet'); // optional source: wallet (omit for pending payout)
DirectPay accepts method values account (default), transfer, or whatsapp. It may also include a linked account ID (account), institution details (institution, including an id and optional auth_method), or a split configuration in initializePayment(). Percentage split values remain percentages; fixed values and max caps are provided in major units and converted to kobo.
Customers and Direct Debit mandates¶
Create a Mono customer before mandate authorization; Mono requires KYC details for this endpoint:
$customer = $driver->createCustomer([
'email' => 'jane@example.com', 'first_name' => 'Jane', 'last_name' => 'Doe',
'phone' => '08012345678', 'address' => 'Lagos', 'bvn' => '12345678901',
]);
$customerId = $customer['data']['id'];
// Hosted mandate authorization via the widget flow.
$mandate = $driver->initializeMandate([
'customer_id' => $customerId,
'amount' => 200000.00, // variable mandate total cap (or fixed per-debit amount)
'debit_type' => 'variable', // or fixed
'mandate_type' => 'emandate', // or sweep
'start_date' => '2026-10-01',
'end_date' => '2027-10-01',
]);
// Redirect to $mandate['data']['mono_url'].
// Alternatively, create a mandate directly with your own authorization experience.
$created = $driver->createSubscription([
'customer_id' => $customerId, 'amount' => 200000.00,
'account_number' => '0123456789', 'bank_code' => '044',
'debit_type' => 'variable', 'mandate_type' => 'emandate',
'start_date' => '2026-10-01', 'end_date' => '2027-10-01',
]);
$mandateId = $created['data']['mandate_id'];
$driver->chargeAuthorization([
'authorization_code' => $mandateId, 'amount' => 5000.00,
'reference' => 'DEBIT_001', 'narration' => 'Subscription payment',
]);
$driver->getMandateDebit($mandateId, 'DEBIT_001');
$driver->listMandateDebits($mandateId);
$driver->pauseSubscription($mandateId);
$driver->resumeSubscription($mandateId);
$driver->cancelSubscription($mandateId);
$driver->checkBalance($mandateId, 5000.00); // sufficiency check; โฆ10 fee, amount must be at least โฆ200
$driver->getMandateBalance($mandateId); // actual balance; โฆ50 fee
$driver->getCustomerTransactions($customerId, ['period' => 'last12months', 'page' => 1]);
// Fixed mandates are scheduled by Mono; provide their required schedule:
$driver->initializeMandate([
'customer_id' => $customerId, 'amount' => 5000.00, 'debit_type' => 'fixed',
'start_date' => '2026-10-01', 'end_date' => '2027-10-01',
'frequency' => 'monthly', 'initial_debit_date' => '2026-11-01',
'retrial_frequency' => 2, 'grace_period' => 3, 'minimum_due' => 1000.00,
]);
// Partial Sweep is for variable sweep mandates only and requires Mono account enablement.
$driver->initializeMandate([
'customer_id' => $customerId, 'amount' => 200000.00, 'debit_type' => 'variable',
'mandate_type' => 'sweep', 'allow_partial_sweep' => true,
]);
$driver->setPartialSweep($mandateId, true); // Toggle an existing eligible mandate, if enabled for your account
For fixed mandates, frequency, initial_debit_date, retrial_frequency, grace_period, and minimum_due are required schedule fields. interval is optional for custom frequencies; initial_debit_amount is optional. Fixed mandates are scheduled by Mono rather than triggered with the debit endpoint. Partial Sweep is only supported for variable sweep mandates.
The CustomerDriverInterface shared contract is not implemented because Mono's customer create method requires additional KYC fields; Mono-specific methods createCustomer, getCustomer, listCustomers, getCustomerTransactions, updateCustomer, and deleteCustomer are available instead. Mono does not provide payout transfers or the generic DisbursementDriverInterface.
Split accounts, payout queries, and banks¶
$subaccount = $driver->createSplitSubaccount([
'nip_code' => '000000', 'account_number' => '0123456789',
]);
$subaccounts = $driver->listSplitSubaccounts();
$banks = $driver->listBanks();
// View settlements and the transactions included in a payout.
$payouts = $driver->getPayouts(['status' => 'successful']);
$payoutTransactions = $driver->getPayoutTransactions('payout-id', [
'account' => 'account-id', 'status' => 'successful',
]);
Mono Disburse¶
Disburse requires a Disburse app and separately entitled secret key. Mono currently documents this product as live-only. This driver accepts total_amount and distribution amount in major currency units and converts them to kobo.
$source = $driver->createDisbursementSourceAccount([
'app' => 'disburse-app-id', 'account_number' => '0123456789',
'bank_code' => '044', 'email' => 'business@example.com',
]);
$driver->listDisbursementSourceAccounts();
$driver->getDisbursementSourceAccount($source['data']['id']);
$driver->updateDisbursementSourceAccountStatus($source['data']['id'], 'enable');
$batch = $driver->createDisbursement([
'reference' => 'PAYROLL_001', 'source' => 'mandate', 'account' => $source['data']['id'],
'type' => 'instant', 'total_amount' => 5000.00,
'distribution' => [[
'reference' => 'SALARY_001', 'recipient_email' => 'staff@example.com',
'account' => ['account_number' => '1234567890', 'bank_code' => '044'],
'amount' => 5000.00, 'narration' => 'Salary',
]],
]);
$driver->listDisbursements();
$driver->getDisbursement($batch['data']['id']);
$driver->transitionDisbursement($batch['data']['id'], 'trigger'); // or cancel
$driver->addDisbursementDistributions($batch['data']['id'], [[
'reference' => 'BONUS_001', 'account_number' => '0123456789',
'bank_code' => '058', 'amount' => 100.00,
]]);
$driver->updateDisbursementDistribution($batch['data']['id'], 'distribution-id', ['amount' => 120.00]);
$driver->deleteDisbursementDistribution($batch['data']['id'], 'distribution-id');
$driver->listDisbursementDistributions($batch['data']['id']);
$driver->getDisbursementDistribution($batch['data']['id'], 'distribution-id');
Owo / WhatsApp payments¶
Owo requires an Owo-enabled app/key. This feature includes user status, beneficiary link lifecycle, one-time or recurring fund requests, and payment history. Supply request amounts in kobo as required by Mono's Owo API.
$driver->getOwoUserStatus('2348012345678');
$driver->linkOwoBeneficiary([
'phone' => '2348012345678',
'fund_beneficiary' => [
'name' => 'Jane Doe', 'nip_code' => '000013', 'account_number' => '0123456789',
],
]);
$driver->unlinkOwoBeneficiary('beneficiary-id');
$driver->getOwoBeneficiary('beneficiary-id');
$driver->listOwoBeneficiaries(['phone' => '2348012345678', 'limit' => 15]);
$fundRequest = $driver->createOwoFundRequest([
'phone' => '2348012345678', 'reference' => 'SAVINGS_001',
'type' => 'recurring', 'description' => 'Weekly savings', 'amount' => 100000,
'currency' => 'NGN',
'schedule' => ['period' => 'week', 'interval' => 1, 'start_date' => '2026-10-07T00:00:00Z'],
]);
$driver->listOwoFundRequests(['phone' => '2348012345678']);
$driver->getOwoFundRequest('fund-request-id');
$driver->listOwoFundRequestPayments('fund-request-id');
$driver->getOwoFundRequestPayment('fund-request-id', 'payment-id');
Separate API credentials can be configured with MONO_DISBURSE_SECRET_KEY and MONO_OWO_SECRET_KEY; each falls back to MONO_SECRET_KEY if unset. DirectPay/Direct Debit, payout queries and customer methods use MONO_SECRET_KEY.
Mono webhook signature verification uses mono-webhook-secret. Configure Mono to send DirectPay, Direct Debit, Disburse, and Owo webhook events, and deduplicate deliveries using event_id.
MONO_SECRET_KEY=your_mono_secret_key
MONO_DISBURSE_SECRET_KEY=your_disburse_app_secret_key
MONO_OWO_SECRET_KEY=your_owo_app_secret_key
MONO_BASE_URL=https://api.withmono.com
MONO_CALLBACK_URL=https://yoursite.com/payment/callback
MONO_WEBHOOK_SECRET=your_mono_webhook_secret
๐ก Budpay¶
Nigeria ยท Dual-header HMAC auth Interfaces:
PaymentDriverInterfaceยทDisbursementDriverInterfaceยทVirtualAccountDriverInterfaceAuth: Bearer token +
Encryptionheader (HMAC-SHA512) โ handled internally.
Payment::driver('budpay')->initializePayment([
'email' => 'customer@example.com',
'amount' => 5000.00,
'reference' => 'BDP_' . uniqid(),
'currency' => 'NGN',
]);
Payment::driver('budpay')->transfer([
'amount' => 5000.00,
'currency' => 'NGN',
'account_number' => '0123456789',
'bank_code' => '044',
'narration' => 'Payout',
'reference' => 'BDP_PAYOUT_001',
]);
BUDPAY_SECRET_KEY=your_budpay_secret_key
BUDPAY_PUBLIC_KEY=your_budpay_public_key
BUDPAY_CALLBACK_URL=https://yoursite.com/payment/callback
BUDPAY_WEBHOOK_SECRET=your_budpay_webhook_secret
๐ Interswitch (Webpay)¶
Nigeria ยท OAuth2 + kobo amounts + OTP authentication Interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทDisbursementDriverInterfaceยทPaymentLinkDriverInterfaceAuth: OAuth2
client_credentialsโ fetched and cached automatically. Amounts in kobo. Currency uses ISO 4217 numeric codes (e.g.566for NGN).Native methods:
authenticateOtp()ยทtokenizeCard()ยทchargeRecurring()โ these map directly to IPG v3 API endpoints.
pay() โ convenience OTP flow¶
Chains initializePayment โ authenticateOtp automatically when the gateway returns responseCode T0 (OTP required) or S0 (3DS redirect):
$result = Payment::driver('interswitch')->pay(
['email' => 'customer@example.com', 'amount' => 5000.00],
function (array $initResponse): ?string {
return $this->requestOtpFromUser($initResponse);
}
);
if ($result['status'] === 'completed') {
// Payment approved (responseCode '00')
} elseif ($result['status'] === 'otp_required') {
// OTP resolver returned null โ user hasn't provided OTP
} elseif ($result['status'] === '3ds_required') {
// Visa 3D Secure โ redirect to $result['response']['paymentUrl']
} else {
// Failed โ check $result['response']['responseCode']
}
Native IPG methods¶
// OTP authentication (Verve/Mastercard)
$result = Payment::driver('interswitch')->authenticateOtp([
'paymentId' => '12345',
'otp' => '123456',
'transactionId' => 'ISW_TXN_001',
]);
// Card tokenization for recurring billing
$token = Payment::driver('interswitch')->tokenizeCard([
'pan' => '5061020100000000009',
'expiry' => '10/25',
'cvv' => '123',
'amount' => 100,
'email' => 'customer@example.com',
'pin' => '1111',
]);
// Charge a tokenized card
$charge = Payment::driver('interswitch')->chargeRecurring([
'token' => 'isw_token_xxx',
'amount' => 5000.00,
'email' => 'customer@example.com',
]);
Subscriptions, Disbursements & Payment Links¶
// Plans
Payment::driver('interswitch')->createPlan([
'name' => 'Pro Monthly',
'amount' => 5000.00,
'interval' => 'monthly',
]);
// Transfer
Payment::driver('interswitch')->transfer([
'amount' => 10000.00,
'account_number' => '0123456789',
'bank_code' => '044',
'narration' => 'Salary payout',
]);
// Payment link
Payment::driver('interswitch')->createPaymentLink([
'name' => 'Product Launch',
'amount' => 2500.00,
'description' => 'Early-bird ticket',
]);
INTERSWITCH_CLIENT_ID=your_interswitch_client_id
INTERSWITCH_CLIENT_SECRET=your_interswitch_client_secret
INTERSWITCH_MERCHANT_CODE=your_interswitch_merchant_code
INTERSWITCH_PAYABLE_CODE=your_interswitch_payable_code
INTERSWITCH_TERMINAL_ID=your_interswitch_terminal_id
INTERSWITCH_CALLBACK_URL=https://yoursite.com/payment/callback
INTERSWITCH_WEBHOOK_SECRET=your_interswitch_webhook_secret
๐ฃ PayPal¶
Global ยท OAuth2 + Payouts Interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทDisbursementDriverInterfaceAuth: OAuth2
client_credentialsโ token fetched and cached automatically. Supports payments, billing subscriptions, and Payouts (bulk disbursements).
$order = Payment::driver('paypal')->initializePayment([
'amount' => 50.00,
'currency' => 'USD',
'email' => 'customer@example.com',
]);
// Redirect to $order['links'][n]['href'] where rel == 'approve'
Payment::driver('paypal')->verifyPayment($order['id']);
Webhooks: RSA-SHA256 certificate-based signing (not HMAC).
PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_CLIENT_SECRET=your_paypal_client_secret
PAYPAL_CURRENCY=USD
PAYPAL_BASE_URL=https://api.paypal.com
PAYPAL_CALLBACK_URL=https://yoursite.com/payment/callback
PAYPAL_CANCEL_URL=https://yoursite.com/payment/cancel
๐ฑ M-Pesa (Safaricom Kenya)¶
Kenya ยท Full Daraja API Interfaces:
PaymentDriverInterfaceยทBalanceDriverInterfaceยทDisbursementDriverInterfaceยทSubscriptionDriverInterfaceFlow: STK Push (collections), B2C (payouts), B2B (merchant transfers), Ratiba (standing orders).
// STK Push โ prompt on customer's phone
$response = Payment::driver('mpesa')->initializePayment([
'phone' => '254712345678', // international format, no +
'amount' => 500, // KES whole number
]);
$checkoutRequestId = $response['data']['checkout_request_id'];
Payment::driver('mpesa')->verifyPayment($checkoutRequestId);
// B2C payout โ send money to customer
Payment::driver('mpesa')->transfer([
'recipient_code' => '254712345678',
'amount' => 1000,
'reason' => 'Refund for order #123',
]);
// Account balance
Payment::driver('mpesa')->getBalance();
// Standing order (Ratiba)
Payment::driver('mpesa')->createSubscription([
'phone' => '254712345678',
'amount' => 2000,
'frequency' => 'Monthly',
]);
// Register C2B callback URLs (run once)
Payment::driver('mpesa')->registerC2BUrls([
'confirmation_url' => 'https://yoursite.com/mpesa/confirm',
'validation_url' => 'https://yoursite.com/mpesa/validate',
]);
// Dynamic QR code
Payment::driver('mpesa')->generateQRCode([
'amount' => 500,
'phone' => '254712345678',
]);
Note: M-Pesa does not send HMAC signatures โ secure callback URLs via HTTPS + IP allowlisting.
MPESA_CONSUMER_KEY=your_mpesa_consumer_key
MPESA_CONSUMER_SECRET=your_mpesa_consumer_secret
MPESA_SHORTCODE=your_mpesa_shortcode
MPESA_PASSKEY=your_mpesa_passkey
MPESA_INITIATOR_NAME=your_mpesa_initiator
MPESA_SECURITY_CREDENTIAL=your_mpesa_security_credential
MPESA_CALLBACK_URL=https://yoursite.com/webhooks/payment-gateways/mpesa
MPESA_RESULT_URL=https://yoursite.com/webhooks/payment-gateways/mpesa
MPESA_TIMEOUT_URL=https://yoursite.com/webhooks/payment-gateways/mpesa
๐ค MTN MoMo¶
Uganda ยท Ghana ยท MTN markets ยท Mobile Money Interfaces:
PaymentDriverInterfaceยทDisbursementDriverInterfaceFlow: Two products (Collections + Disbursements) with separate API credentials. Push sent to customer's wallet โ asynchronous.
$response = Payment::driver('mtnmomo')->initializePayment([
'phone' => '256771234567', // MSISDN, no +
'amount' => 1000,
'currency' => 'UGX',
]);
$referenceId = $response['data']['reference'];
Payment::driver('mtnmomo')->verifyPayment($referenceId);
// Disbursement
Payment::driver('mtnmomo')->transfer([
'phone' => '256771234567',
'amount' => 1000,
'currency' => 'UGX',
'narration' => 'Salary payout',
]);
MTNMOMO_COLLECTION_USER_ID=your_mtnmomo_collection_user_id
MTNMOMO_COLLECTION_API_KEY=your_mtnmomo_collection_api_key
MTNMOMO_COLLECTION_SUBSCRIPTION_KEY=your_mtnmomo_collection_subscription_key
MTNMOMO_DISBURSEMENT_USER_ID=your_mtnmomo_disbursement_user_id
MTNMOMO_DISBURSEMENT_API_KEY=your_mtnmomo_disbursement_api_key
MTNMOMO_DISBURSEMENT_SUBSCRIPTION_KEY=your_mtnmomo_disbursement_subscription_key
MTNMOMO_ENVIRONMENT=sandbox
MTNMOMO_CURRENCY=EUR
MTNMOMO_CALLBACK_URL=https://yoursite.com/webhooks/payment-gateways/mtnmomo
๐ด Razorpay¶
India ยท Paise amounts + payouts Interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทDisbursementDriverInterfaceยทPaymentLinkDriverInterfaceAuth: Basic Auth (
key_id:key_secret) on every request. Amounts in paise (1 INR = 100 paise) โ handled internally.
$order = Payment::driver('razorpay')->initializePayment([
'email' => 'customer@example.com',
'amount' => 499.00,
'currency' => 'INR',
'name' => 'Jane Doe',
'phone' => '+919876543210',
]);
// Pass $order['id'], $order['data']['key_id'] to Razorpay checkout.js
RAZORPAY_KEY_ID=rzp_live_xxxxx
RAZORPAY_KEY_SECRET=your_razorpay_key_secret
RAZORPAY_ACCOUNT_NUMBER=your_razorpay_x_account_number
RAZORPAY_CURRENCY=INR
RAZORPAY_CALLBACK_URL=https://yoursite.com/payment/callback
RAZORPAY_WEBHOOK_SECRET=your_razorpay_webhook_secret
๐๏ธ Paddle¶
Global ยท Merchant of Record (SaaS / digital goods) Interfaces:
PaymentDriverInterfaceยทSubscriptionDriverInterfaceยทPaymentLinkDriverInterfacePlans map to Prices, payments to Transactions, payment links to Paddle's native Payment Links.
$transaction = Payment::driver('paddle')->initializePayment([
'amount' => 29.99,
'currency' => 'USD',
'description' => 'Pro plan',
'email' => 'customer@example.com',
]);
// Redirect to $transaction['data']['authorization_url']
Webhooks: HMAC-SHA256 with
Paddle-Signature: ts=...;h1=...format.
PADDLE_API_KEY=your_paddle_api_key
PADDLE_CLIENT_TOKEN=your_paddle_client_token
PADDLE_WEBHOOK_SECRET=your_paddle_webhook_secret
PADDLE_CURRENCY=USD
PADDLE_BASE_URL=https://api.paddle.com
PADDLE_CALLBACK_URL=https://yoursite.com/payment/callback
---¶
๐ Balance Aggregation Service¶
Namespace:
Alphabalex\PaymentMadeEasy\Services\BalanceAggregator
Query balances across all configured gateways in a single call.
use Alphabalex\PaymentMadeEasy\Services\BalanceAggregator;
$aggregator = app(BalanceAggregator::class);
// All gateways
$result = $aggregator->aggregate();
// Filter by currency
$result = $aggregator->aggregate(currency: 'NGN');
// Specific gateways
$result = $aggregator->aggregate(gateways: ['paystack', 'flutterwave']);
Response:
[
'gateways' => [
'paystack' => [
'status' => 'success',
'data' => [['currency' => 'NGN', 'balance' => 500000.0]],
],
'flutterwave' => [
'status' => 'success',
'data' => [['currency' => 'NGN', 'balance' => 300000.0]],
],
'squad' => [
'status' => 'skipped',
'data' => [],
'error' => 'Gateway does not support balance queries',
],
],
'totals' => ['NGN' => 800000.0],
'queried_at' => '2025-08-21T12:00:00+00:00',
'successful' => 2,
'failed' => 0,
'skipped' => 1,
]
Helpers:
$gateways = $aggregator->getBalanceGateways(); // ['paystack', 'flutterwave']
$supported = $aggregator->gatewaySupportsBalance('paystack'); // true
Features:
- ๐ Graceful error handling โ individual gateway failures don't break aggregation
- ๐ Auto-discovery โ finds all gateways implementing BalanceDriverInterface
- ๐ฑ Currency filtering โ query specific currency across all gateways
- ๐ Multi-currency totals โ sums balances per currency
- โ๏ธ Balance normalization โ handles different response formats
- ๐ Subunit conversion โ converts kobo/cents to major units automatically
๐ Payment Recorder¶
Namespace:
Alphabalex\PaymentMadeEasy\Services\PaymentRecorder
Persist payment activity to your database when PAYMENT_RECORDING_ENABLED=true.
use Alphabalex\PaymentMadeEasy\Services\PaymentRecorder;
$recorder = app(PaymentRecorder::class);
// Record a transaction
$transaction = $recorder->recordTransaction('paystack', $requestData, $gatewayResponse);
// Update status after verification/webhook
$recorder->updateTransactionStatus('ORDER_001', 'successful', 'PS_GATEWAY_REF');
// Record a transfer
$transfer = $recorder->recordTransfer('paystack', $transferData, $transferResponse);
// Log webhook event
$recorder->logWebhookEvent('paystack', $webhookEvent);
// Handle refund webhook
$recorder->handleRefundWebhook('paystack', 'ORDER_001', 50.0, 'NGN', $rawPayload, 're_abc123');
With PAYMENT_RECORDING_AUTO_WEBHOOK_EVENTS=true, listeners also persist RefundProcessed, DisputeCreated, and ChargebackCreated events.
Models:
| Model | Table | Purpose |
|---|---|---|
PaymentTransaction |
payment_transactions |
One-time payment records |
PaymentRefund |
payment_refunds |
Refund history (multi-refund support) |
PaymentDispute |
payment_disputes |
Dispute / chargeback events |
PaymentSubscription |
payment_subscriptions |
Subscription / billing records |
PaymentTransfer |
payment_transfers |
Disbursement / transfer records |
PaymentWebhookLog |
payment_webhook_logs |
Audit log for incoming webhooks |
// Scope helpers
PaymentTransaction::successful()->gateway('paystack')->get();
PaymentSubscription::active()->forEmail('user@example.com')->get();
PaymentTransfer::successful()->gateway('mtnmomo')->get();
// Refund reporting
$transaction->refunds;
$transaction->totalRefundedAmount();