PHP code example of brilliantmind / mkesh

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 = MkeshConfig::fromArray([
    'username' => 'o-seu-utilizador',
    'password' => 'a-sua-senha',
    'service_provider_fri' => 'FRI:pagamKesh/USER',
    'sp_transfer_sending_fri' => 'FRI:47225552/MM',
    'transaction_prefix' => 'ACME',
]);

// 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),
        );
    }
}

use BrilliantMind\Mkesh\Laravel\Facades\Mkesh;

$id       = Mkesh::config()->newTransactionId();
$debito   = Mkesh::debit(DebitRequest::charge('258823040400', Money::of(25), $id));
$estado   = Mkesh::getTransactionStatus($id);
$callback = Mkesh::parseDebitCompleted($request->getContent());

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;
    }
}

Schedule::command('mkesh:reconciliar')->everyFifteenMinutes();

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,
);

$resposta->transactionId;          // "3171312"
$resposta->status;                 // TransactionStatus::PENDING
$resposta->approvalId;             // "470390"
$resposta->isPending();            // true

use BrilliantMind\Mkesh\Request\SpTransferRequest;

$resposta = $mkesh->transfer(SpTransferRequest::payout(
    customerMsisdn: '258823040400',
    amount: Money::of(25),
    providerTransactionId: 'XXXXX',
));

$resposta->transactionId;            // "3282002"
$resposta->providerTransactionId;    // "ACME-XXXXXX"

$estado = $mkesh->getTransactionStatus('000001');   // prefixo aplicado

$estado->financialTransactionId;   // "3171312"
$estado->status;                   // TransactionStatus::SUCCESSFUL
$estado->providerTransactionId;    // null quando FAILED
$estado->status->isSettled();      // true — pare de consultar

$callback = $mkesh->parseDebitCompleted($corpoDoPedido);

$callback->transactionId;            // "3171312"
$callback->externalTransactionId;    // "ACME000001" — o NOSSO id
$callback->referenceId;
$callback->status;                   // TransactionStatus::SUCCESSFUL
$callback->communicationChannel;     // "http-sp"
$callback->receiver->fri;            // "FRI:1360073/MM"
$callback->receiver->msisdn;
$callback->receiver->language;       // "en"
$callback->isSuccessful();

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

protected $casts = ['status' => TransactionStatus::class];

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

$table->string('payable_type')->nullable();
$table->string('payable_id')->nullable();
$table->index(['payable_type', 'payable_id']);

// 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();

// config/mkesh.php
'database' => [
    'connection' => env('MKESH_DB_CONNECTION'),   // null = ligação por omissão
    'tables' => [
        'transactions' => 'mkesh_transactions',
        'responses' => 'mkesh_responses',
    ],
],

// 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);
bash
composer vendor:publish --tag=mkesh-config
php artisan migrate
bash
php artisan vendor:publish --tag=mkesh-config
php artisan migrate
bash
php artisan vendor:publish --tag=mkesh-migrations
bash
composer vendor:publish --tag=mkesh-config
php artisan migrate
bash
php examples/mkesh-test.php debit    258823040400 25
php examples/mkesh-test.php transfer 258823040400 10
php examples/mkesh-test.php status   000001