1. Go to this page and download the library: Download siberfx/mpesa-payment library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
siberfx / mpesa-payment example snippets
use Siberfx\MpesaPayment\Mpesa;
$mpesa = Mpesa::make([
'environment' => 'sandbox', // or 'production'
'consumer_key' => 'your-consumer-key',
'consumer_secret' => 'your-consumer-secret',
'shortcode' => '174379',
'passkey' => 'your-passkey',
'callbacks' => [
'stk' => 'https://example.com/payments/stk',
],
]);
$response = $mpesa->stk()->push(
amount: 100,
phone: '0712 345 678',
reference: 'INV-1001',
description: 'Order #1001',
);
if ($response->accepted()) {
$checkoutId = $response->checkoutRequestId(); // store it, the result arrives on your callback
}
use Siberfx\MpesaPayment\Exceptions\MpesaException;
use Siberfx\MpesaPayment\Exceptions\ValidationException;
use Siberfx\MpesaPayment\Laravel\Facades\Mpesa;
use Siberfx\MpesaPayment\Support\Phone;
final class CheckoutController
{
public function pay(Request $request, Order $order)
{
$data = $request->validate(['phone' => [' ]);
try {
$response = Mpesa::stk()->push(
amount: $payment->amount,
phone: $payment->phone,
reference: 'ORD'.$order->id, // max 12 characters
description: 'Order '.$order->id,
);
} catch (ValidationException $e) {
$payment->update(['status' => 'failed', 'failure_reason' => $e->getMessage()]);
return back()->withErrors(['phone' => $e->getMessage()]);
} catch (MpesaException $e) {
report($e);
$payment->update(['status' => 'failed', 'failure_reason' => 'Gateway unavailable']);
return back()->withErrors(['phone' => 'M-Pesa is unavailable, please try again.']);
}
$payment->update(['checkout_request_id' => $response->checkoutRequestId()]);
// The customer now sees a PIN prompt. Nothing is paid yet.
CheckStkPayment::dispatch($payment)->delay(now()->addSeconds(90));
return redirect()->route('orders.waiting', $order);
}
}
use Illuminate\Contracts\Queue\ShouldQueue;
use Siberfx\MpesaPayment\Laravel\Events\StkCallbackReceived;
final class RecordStkPayment implements ShouldQueue
{
public function handle(StkCallbackReceived $event): void
{
$callback = $event->callback;
DB::transaction(function () use ($callback) {
$payment = Payment::where('checkout_request_id', $callback->checkoutRequestId)
->lockForUpdate()
->first();
// Unknown or already processed (callbacks can be delivered more than once).
if ($payment === null || $payment->status !== 'pending') {
return;
}
if (! $callback->successful()) {
$payment->update(['status' => 'failed', 'failure_reason' => $callback->resultDescription]);
return;
}
// Never trust that the amount matches: verify it.
if ((int) $callback->amount() !== $payment->amount) {
$payment->update(['status' => 'failed', 'failure_reason' => 'Amount mismatch']);
report(new RuntimeException("STK amount mismatch for payment {$payment->id}"));
return;
}
$payment->update(['status' => 'paid', 'receipt_number' => $callback->receiptNumber()]);
$payment->order->markAsPaid();
});
}
}
use Siberfx\MpesaPayment\Exceptions\RequestException;
use Siberfx\MpesaPayment\Laravel\Facades\Mpesa;
final class CheckStkPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5;
public function __construct(public Payment $payment) {}
public function handle(): void
{
if ($this->payment->fresh()->status !== 'pending') {
return; // the callback already settled it
}
try {
$result = Mpesa::stk()->query($this->payment->checkout_request_id);
} catch (RequestException $e) {
// "The transaction is being processed" — ask again later.
$this->release(60);
return;
}
if ((string) $result->get('ResultCode') === '0') {
// Paid, but the query does not return the receipt number:
// mark it paid and reconcile the receipt from your M-Pesa statement if needed.
$this->payment->update(['status' => 'paid']);
} else {
$this->payment->update(['status' => 'failed', 'failure_reason' => $result->get('ResultDesc')]);
}
}
}
// AppServiceProvider::boot()
C2BValidation::using(function (C2BTransaction $payment) {
$invoice = Invoice::where('number', strtoupper(trim($payment->billReference)))->first();
return match (true) {
$invoice === null => C2BRejection::InvalidAccountNumber,
$payment->amount < $invoice->balance => C2BRejection::InvalidAmount,
default => true,
};
});
// Listener
Event::listen(function (C2BConfirmationReceived $event) {
$tx = $event->transaction;
// TransID is unique: use it to make the handler idempotent.
InvoicePayment::firstOrCreate(
['mpesa_transaction_id' => $tx->transactionId],
[
'invoice_number' => strtoupper(trim($tx->billReference)),
'amount' => $tx->amount,
'payer_name' => $tx->customerName(),
'paid_at' => $tx->transactedAt(),
],
);
});
use Siberfx\MpesaPayment\Enums\CommandId;
use Siberfx\MpesaPayment\Exceptions\ConnectionException;
use Siberfx\MpesaPayment\Laravel\Events\ResultReceived;
$payout = Payout::create(['uuid' => (string) Str::uuid(), 'phone' => $phone, 'amount' => 2500]);
try {
$response = Mpesa::b2c()->pay(
amount: $payout->amount,
phone: $payout->phone,
remarks: 'Commission',
command: CommandId::BusinessPayment,
originatorConversationId: $payout->uuid,
resultUrl: route('mpesa.result', ['type' => 'b2c']).'?token='.config('mpesa.security.token'),
);
$payout->update(['conversation_id' => $response->conversationId(), 'status' => 'submitted']);
} catch (ConnectionException $e) {
// Unknown outcome: do NOT send again. Check the status first (see pitfalls below).
$payout->update(['status' => 'unknown']);
}
Event::listen(function (ResultReceived $event) {
if ($event->type !== 'b2c') {
return;
}
$payout = Payout::where('uuid', $event->result->originatorConversationId)->first();
$payout?->update($event->result->successful()
? ['status' => 'paid', 'receipt' => $event->result->transactionId]
: ['status' => 'failed', 'failure_reason' => $event->result->resultDescription]);
});
// bootstrap.php
use Siberfx\MpesaPayment\Mpesa;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;
$mpesa = Mpesa::make(
;
// callback.php — the URL you configured as the "stk" callback
use Siberfx\MpesaPayment\Daraja\Callbacks\StkCallback;
use Siberfx\MpesaPayment\Exceptions\ValidationException;
if (! hash_equals(getenv('MPESA_CALLBACK_TOKEN'), $_GET['token'] ?? '')) {
http_response_code(403);
exit;
}
try {
$callback = StkCallback::fromArray(json_decode(file_get_contents('php://input'), true) ?? []);
} catch (ValidationException) {
http_response_code(422);
exit;
}
$pdo->prepare('UPDATE payments SET status = ?, receipt = ? WHERE checkout_request_id = ? AND status = "pending"')
->execute([$callback->successful() ? 'paid' : 'failed', $callback->receiptNumber(), $callback->checkoutRequestId]);
header('Content-Type: application/json');
echo json_encode(['ResultCode' => 0, 'ResultDesc' => 'Accepted']);
use Siberfx\MpesaPayment\Exceptions\RequestException;
$tz = Mpesa::openApi('tanzania');
try {
$response = $tz->c2b(
amount: 15000,
phone: '0754 123 456',
reference: 'ORDER-77',
description: 'Order 77',
conversationId: $order->uuid,
);
$order->markAsPaid($response->transactionId());
} catch (RequestException $e) {
// e.g. INS-2006 insufficient balance, INS-9 request timeout (customer did not enter the PIN)
$order->markAsFailed($e->errorCode, $e->getMessage());
}