1. Go to this page and download the library: Download brilliantmind/mkesh 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/ */
brilliantmind / mkesh example snippets
use BrilliantMind\Mkesh\Laravel\Events\MkeshTransactionSettled;
use Illuminate\Support\Facades\Event;
Event::listen(function (MkeshTransactionSettled $event) {
if ($event->isSuccessful()) {
$event->payable()?->marcarComoPaga(); // a sua encomenda
}
});
use BrilliantMind\Mkesh\Laravel\Facades\Mkesh;
$transaccao = Mkesh::charge('258821234567', '25.00', $encomenda);
// O pacote monta FRI:258821234567/MSISDN sozinho:
DebitRequest::charge('258821234567', Money::of(25), $id);
// Equivalente explícito:
new DebitRequest(fromFri: Fri::msisdn('258821234567'), ...);
// ❌ a segunda tentativa desta encomenda falha
$id = $config->applyPrefix("ENC-{$encomenda->id}");
// ✅ único por tentativa
$id = $config->newTransactionId("ENC-{$encomenda->id}-" . Str::random(6));
// ✅ ou simplesmente, deixando o pacote gerar
$id = $config->newTransactionId();
if (!preg_match('/^2588[23]\d{7}$/', $msisdn)) {
// não é um número mKesh
}
// Só passa o número do cliente. O tofri sai da configuração.
$mkesh->debit(DebitRequest::charge('258821234567', Money::of(25), $id));
use BrilliantMind\Mkesh\Config\MkeshConfig;
use BrilliantMind\Mkesh\MkeshClient;
$config = new MkeshConfig(
username: 'o-seu-utilizador',
password: 'a-sua-senha',
serviceProviderFri: 'FRI:pagamKesh/USER', // creditada no débito (C2B)
transactionPrefix: 'ACME',
callbackUrl: 'https://a-sua-app.co.mz/api/mkesh/callback',
spTransferSendingFri: 'FRI:47225552/MM', // debitada no pagamento (B2C)
);
$mkesh = MkeshClient::create($config);
// config/mkesh.php
'route' => [
'enabled' => true, // false = registo o meu
'path' => 'api/mkesh/callback',
'middleware' => [], // deixe vazio: sem sessão, sem CSRF
],
use BrilliantMind\Mkesh\Laravel\Facades\Mkesh;
$transaccao = Mkesh::charge(
msisdn: '258821234567',
amount: '25.00',
payable: $encomenda, // opcional: liga o pagamento ao seu registo
);
$transaccao->status; // TransactionStatus::PENDING
$transaccao->external_transaction_id; // "ACME9F2C…" — guarde para suporte
public function __construct(private readonly MkeshPayments $mkesh) {}
// app/Providers/AppServiceProvider.php, no boot()
use BrilliantMind\Mkesh\Laravel\Events\MkeshTransactionSettled;
use Illuminate\Support\Facades\Event;
Event::listen(function (MkeshTransactionSettled $event) {
if ($event->isSuccessful()) {
$event->payable()?->marcarComoPaga();
} else {
$event->payable()?->marcarComoFalhada($event->transaction->error_message);
}
});
// POST /pagamentos → inicia
return response()->json([
'referencia' => $transaccao->external_transaction_id,
'mensagem' => 'Confirme o pagamento no seu telemóvel.',
], 202);
// GET /pagamentos/{referencia} → o frontend consulta este de 3 em 3 segundos
$t = MkeshTransaction::query()->forExternalId($referencia)->firstOrFail();
return response()->json([
'estado' => $t->status->value, // PENDING | SUCCESSFUL | FAILED
'concluido'=> $t->isSettled(),
'erro' => $t->error_message,
]);
namespace App\Services;
use BrilliantMind\Mkesh\MkeshClient;
use BrilliantMind\Mkesh\Request\DebitRequest;
use BrilliantMind\Mkesh\ValueObject\Money;
final class CheckoutService
{
public function __construct(
private readonly MkeshClient $mkesh,
) {
}
public function pagar(string $msisdn, string $valor): void
{
$id = $this->mkesh->config()->newTransactionId();
// grave $id na sua base de dados AQUI, antes de enviar
$resposta = $this->mkesh->debit(
DebitRequest::charge($msisdn, Money::of($valor), $id),
);
}
}
namespace App\Http\Controllers;
use BrilliantMind\Mkesh\Laravel\MkeshPayments;
use BrilliantMind\Mkesh\Enum\ErrorCode;
use BrilliantMind\Mkesh\Exception\ErrorResponseException;
use BrilliantMind\Mkesh\Exception\TransportException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
final class PagamentoController extends Controller
{
public function __construct(
private readonly MkeshPayments $pagamentos,
) {
}
public function store(Request $request): JsonResponse
{
$dados = $request->validate([
'msisdn' => ['THORIZATION_CURRENT_BALANCE_TOO_LOW => 'Saldo insuficiente.',
ErrorCode::ACCOUNTHOLDER_NOT_ACTIVE => 'Conta mKesh inactiva.',
default => 'Não foi possível processar o pagamento.',
},
], 422);
}
report($e);
return response()->json(['mensagem' => 'Serviço indisponível.'], 502);
} catch (TransportException $e) {
// CUIDADO: pode ter passado do lado deles. Não reenvie às cegas —
// o job de reconciliação vai apurar o estado real.
report($e);
return response()->json(['mensagem' => 'Sem resposta do MKESH.'], 504);
}
return response()->json([
'referencia' => $transaccao->external_transaction_id,
'estado' => $transaccao->status->value,
'mensagem' => 'Confirme o pagamento no seu telemóvel.',
], 202);
}
}
// config/mkesh.php → 'route' => ['enabled' => false]
use BrilliantMind\Mkesh\Laravel\CallbackHandler;
public function __invoke(Request $request, CallbackHandler $handler): Response
{
$resultado = $handler->handle($request->getContent());
return new Response(
$resultado->body(),
$resultado->httpStatus,
['Content-Type' => $resultado->contentType()],
);
}
// config/mkesh.php
'reconcile' => [
'enabled' => true,
'delay' => 120, // segundos até à primeira verificação
],
namespace App\Console\Commands;
use BrilliantMind\Mkesh\Laravel\Jobs\ReconcileMkeshTransaction;
use BrilliantMind\Mkesh\Laravel\Models\MkeshTransaction;
use BrilliantMind\Mkesh\Enum\TransactionStatus;
use Illuminate\Console\Command;
final class ReconciliarMkesh extends Command
{
protected $signature = 'mkesh:reconciliar {--minutos=10}';
protected $description = 'Consulta o estado dos débitos ainda pendentes';
public function handle(): int
{
$pendentes = MkeshTransaction::query()
->where('type', MkeshTransaction::TYPE_DEBIT)
->where('status', TransactionStatus::PENDING)
->where('created_at', '<', now()->subMinutes((int) $this->option('minutos')))
->get();
foreach ($pendentes as $transaccao) {
ReconcileMkeshTransaction::dispatch($transaccao->getKey());
}
$this->info("{$pendentes->count()} transacções enviadas para reconciliação.");
return self::SUCCESS;
}
}
use BrilliantMind\Mkesh\Config\MkeshConfig;
use BrilliantMind\Mkesh\MkeshClient;
use GuzzleHttp\Psr7\HttpFactory;
use GuzzleHttp\Psr7\Response;
$http = new class (new Response(200, [], $xmlDeResposta)) implements \Psr\Http\Client\ClientInterface {
public function __construct(private $resposta) {}
public function sendRequest(\Psr\Http\Message\RequestInterface $r): \Psr\Http\Message\ResponseInterface
{
return $this->resposta;
}
};
$factory = new HttpFactory();
$mkesh = new MkeshClient($config, $http, $factory, $factory);
// No teste, substitua o singleton do container:
$this->app->instance(MkeshClient::class, $mkesh);
use BrilliantMind\Mkesh\Request\DebitRequest;
use BrilliantMind\Mkesh\ValueObject\Fri;
use BrilliantMind\Mkesh\ValueObject\Money;
$resposta = $mkesh->debit(DebitRequest::charge(
customerMsisdn: '258823040400',
amount: Money::of(25), // 25 MZN
externalTransactionId: '000001',
));
// Forma completa, com todos os parâmetros:
$pedido = new DebitRequest(
fromFri: Fri::msisdn('258823040400'), // quem paga
amount: Money::of(25, 'MZN'),
externalTransactionId: '000001',
toFri: null, // omissão: serviceProviderFri da config
referenceId: null, // omissão: igual ao externalTransactionId
fromMessage: null,
toMessage: null,
);
use BrilliantMind\Mkesh\Callback\CallbackResponse;
$ack = CallbackResponse::success();
$ack->toXml(); // o corpo a devolver
CallbackResponse::CONTENT_TYPE; // "text/xml; charset=utf-8"
$callback = $mkesh->parseInitiateTransferCompleted($corpoDoPedido);
$callback->financialTransactionId;
$callback->externalTransactionId;
$callback->status->isSuccessful();
$callback->receiver->msisdn;
// responda com o mesmo <ResponseCode>SUCCESS</ResponseCode>
use BrilliantMind\Mkesh\Enum\TransactionStatus;
TransactionStatus::PENDING; // à espera da aprovação do cliente
TransactionStatus::SUCCESSFUL; // dinheiro movido
TransactionStatus::FAILED;
TransactionStatus::UNKNOWN; // valor não reconhecido
// O diagrama do provedor escreve SUCCESS/FAILURE, os payloads escrevem
// SUCCESSFUL/FAILED — ambos são aceites.
TransactionStatus::fromWire('SUCCESS'); // SUCCESSFUL
TransactionStatus::fromWire('failure'); // FAILED
TransactionStatus::fromWire('seja o que for'); // UNKNOWN
$status->isPending();
$status->isSuccessful();
$status->isFailed();
$status->isSettled(); // true se SUCCESSFUL ou FAILED — pare de consultar
use BrilliantMind\Mkesh\Enum\ErrorCode;
$codigo = $e->code(); // ErrorCode; $e->getErrorCode() dá a string crua
$codigo->isRetryable(); // transitório — repita o mesmo pedido mais tarde
$codigo->isDuplicate(); // id já usado; consulte o estado, NÃO reenvie
$codigo->isCustomerFault(); // sem saldo / inactivo / PIN errado / expirou
$codigo->isExpired(); // a janela de aprovação passou
$codigo->isAuthFailure(); // credenciais erradas ou IP não autorizado
$codigo->description(); // do catálogo completo de 670 códigos
use BrilliantMind\Mkesh\Enum\CallbackResponseCode;
CallbackResponseCode::SUCCESS; // o único documentado pelo provedor
CallbackResponseCode::FAILURE;
CallbackResponse::of(CallbackResponseCode::SUCCESS)->toXml();
use BrilliantMind\Mkesh\Enum\FriType;
use BrilliantMind\Mkesh\ValueObject\Fri;
use BrilliantMind\Mkesh\ValueObject\Money;
FriType::MSISDN; // FRI:258823040400/MSISDN — número de telemóvel
FriType::USER; // FRI:pagamKesh/USER — conta de service provider
FriType::MOBILE_MONEY; // FRI:1360073/MM — conta mobile money
Fri::msisdn('258823040400');
Fri::user('pagamKesh');
Fri::mobileMoney('1360073');
Fri::fromString('FRI:258823040400/MSISDN');
(string) $fri; // "FRI:258823040400/MSISDN"
// O valor é guardado como string normalizada, para não apanhar erros de
// vírgula flutuante ao serializar o XML.
Money::of(25); // 25 MZN
Money::of('25.50', 'MZN');
Money::of(25.5)->amount; // "25.5"
use BrilliantMind\Mkesh\Enum\ErrorCode;
use BrilliantMind\Mkesh\Exception\ErrorResponseException;
use BrilliantMind\Mkesh\Exception\MkeshException;
try {
$mkesh->debit($pedido);
} catch (ErrorResponseException $e) {
$e->getErrorCode(); // "ACCOUNTHOLDER_WITH_FRI_NOT_FOUND"
$e->getDescription(); // "Account holder with given FRI could not be found"
$e->getArguments(); // ["fri" => "FRI:258823040420/MSISDN"]
$e->getArgument('fri');
$e->getRawXml(); // corpo original, para auditoria
$e->code(); // ErrorCode (ver secção 8.2)
// is() aceita enum ou string, indiferentemente
$e->is(ErrorCode::TRANSACTION_NOT_FOUND);
$e->is('TRANSACTION_NOT_FOUND');
$e->is(ErrorCode::AMOUNT_INVALID, ErrorCode::INVALID_CURRENCY);
} catch (MkeshException $e) {
// qualquer outra falha do pacote (transporte, config, parsing…)
}
use BrilliantMind\Mkesh\Error\ErrorCodes;
ErrorCodes::description('TRANSACTION_NOT_FOUND');
ErrorCodes::has('REFERENCE_ID_ALREADY_IN_USE');
ErrorCodes::all(); // array<string, string>
ReconcileMkeshTransaction::dispatch($transaccao->getKey()); // string, não int
// Na sua encomenda:
public function mkeshTransactions(): MorphMany
{
return $this->morphMany(MkeshTransaction::class, 'payable');
}
// E do outro lado:
$transaccao->payable; // a sua Encomenda / Factura / Subscrição
$transaccao->payable()->associate($encomenda)->save();
// Transições que respeitam a idempotência
$transaccao->settle(TransactionStatus::SUCCESSFUL, '3171312'); // false se já liquidada
$transaccao->fail($errorResponseException); // grava código + descrição
// Estado
$transaccao->isPending(); $transaccao->isSettled();
$transaccao->isDebit(); $transaccao->isTransfer();
// Scopes
MkeshTransaction::query()->debits()->pending()->get();
MkeshTransaction::query()->stale(10)->get(); // pendentes há mais de 10 min
MkeshTransaction::query()->forExternalId('ACME000001')->first();
$transaccao->responses; // tudo o que o MKESH disse sobre ela
$resposta->transaction; // relação inversa
// Registar, sem montar o array à mão
MkeshResponse::logCallback($callback, $corpoBruto, $transaccao);
MkeshResponse::logUnparsable($corpoBruto); // corpo que não deu para ler
MkeshResponse::logError($excepcao, 'debitresponse', $transaccao);
MkeshResponse::query()->inbound()->latest()->get();
$client = new MkeshClient($config, $psr18Client, $requestFactory, $streamFactory);