PHP code example of techsolutions-projects / mobile-wallet-sdk

1. Go to this page and download the library: Download techsolutions-projects/mobile-wallet-sdk 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/ */

    

techsolutions-projects / mobile-wallet-sdk example snippets


use Techsolutions\Sdk\MobileWallets\Models\Wallet;

Wallet::create([
    'name'                  => 'm-pesa',
    'display_name'          => 'M-Pesa',
    'service_provider_code' => '171717',
    'is_active'             => true,
]);

Wallet::create([
    'name'         => 'e-mola',
    'display_name' => 'E-Mola',
    'is_active'    => true,
]);

use Techsolutions\Sdk\MobileWallets\Models\Wallet;

// Obter a carteira M-Pesa activa
$wallet = Wallet::mpesa()->first();

// Obter a carteira E-Mola activa
$wallet = Wallet::emola()->first();

// Listar todas as carteiras activas
$wallets = Wallet::active()->get();

// Aceder às transacções de uma carteira
$wallet->transactions;

use Techsolutions\Sdk\MobileWallets\Models\Transaction;
use Techsolutions\Sdk\MobileWallets\Models\Wallet;

// Obter a carteira M-Pesa activa
$wallet = Wallet::mpesa()->first();

// Criar solicitação de débito na carteira móvel
$tx = Transaction::onWallet($wallet)->c2b('+258840000000', '250.00');

// Consultar
$tx->status;           // TransactionStatus::SUCCEEDED
$tx->transaction_type; // TransactionType::CUSTOMER_TO_BUSINESS
$tx->amount;           // 250.00
$tx->isSucceeded();    // true
$tx->isFailed();       // false
$tx->canRetry();       // false (já não tem tentativas)

// Scopes
Transaction::pending()->get();
Transaction::succeeded()->get();
Transaction::failed()->get();
Transaction::retryable()->get();

// Relações
$tx->wallet;    // Wallet
$tx->billable;  // Invoice, Order, etc. (polimórfico)

use Techsolutions\Sdk\MobileWallets\DTOs\GatewayResponse;

$response->isSuccessful();       // bool
$response->isFailed();           // bool
$response->transactionId;        // "f449abol7j38"
$response->statusCode;           // "INS-0"
$response->statusDescription;    // "Request processed successfully"
$response->rawResponse;          // array completo do provedor

// Resolver para o enum de códigos
$code = $response->resolveCode();  // GatewayResponseCode::M_PESA_SUCCESS
$code->isRetryable();              // false
$code->description();              // "Transacção processada com sucesso."

use Techsolutions\Sdk\MobileWallets\Models\{Wallet, Transaction};

$wallet = Wallet::mpesa()->first();

$tx = Transaction::onWallet($wallet)
    ->c2b(
        msisdn: '+258840000000',
        amount: '250.00',
        attempts: 3,
    );

// $tx é uma instância de Transaction já persistida na base de dados.
// O ProcessTransactionJob já foi despachado.

$tx = Transaction::onWallet($wallet)
    ->b2c(
        msisdn: '+258840000000',
        amount: 500.00,
        attempts: 2,
    );

->c2b('+258840000000', 250)          // inteiro
->c2b('+258840000000', 250.00)       // float
->c2b('+258840000000', '250.00')     // string EN
->c2b('+258840000000', '250,00')     // string PT
->c2b('+258840000000', '250,00MT')   // com sufixo de moeda
->c2b('+258840000000', '1.250,50 MZN') // milhar PT com moeda

use Techsolutions\Sdk\MobileWallets\Models\{Wallet, Transaction};
use Techsolutions\Sdk\MobileWallets\DTOs\GatewayResponse;
use Techsolutions\Sdk\MobileWallets\Enums\GatewayResponseCode;

$wallet = Wallet::mpesa()->firstOrFail();

$tx = Transaction::onWallet($wallet)

    // Opcional: canal WebSocket para notificações em tempo real
    ->toSocket('payments_channel_' . $userId)

    // Opcional: callback executado quando a transacção é bem-sucedida
    ->onSuccess(function (Transaction $tx, GatewayResponse $resp): void {
        $tx->billable?->update(['paid' => true]);
        // Enviar SMS de confirmação, gerar recibo, etc.
    })

    // Opcional: callback executado quando a transacção falha definitivamente
    ->onFail(function (Transaction $tx, GatewayResponse $resp): void {
        $code = $resp->resolveCode();
        if ($code === GatewayResponseCode::M_PESA_INSUFFICIENT_BALANCE) {
            // Notificar o cliente que não tem saldo
        }
    })

    // Opcional: lógica customizada para decidir se re-tenta
    ->shouldRetry(function (Transaction $tx, GatewayResponse $resp): ?bool {
        $code = $resp->resolveCode();

        // Se o código é terminal, não re-tentar
        if ($code?->isTerminal()) {
            return false;
        }

        // null = usar lógica padrão do pacote
        return null;
    })

    // Opcional: metadata arbitrária
    ->withMetadata([
        'order_id' => 42,
        'source'   => 'api',
    ])

    // Método terminal: cria a transacção e despacha o job
    ->c2b(
        msisdn: '+258840000000',
        amount: '250,00MT',
        attempts: 3,
    );

use Illuminate\Database\Eloquent\Model;
use Techsolutions\Sdk\MobileWallets\Contracts\Billable;
use Techsolutions\Sdk\MobileWallets\Traits\MwgBillable;

class Invoice extends Model implements Billable
{
    use MwgBillable;

    public function getMwgAmount(): float
    {
        return (float) $this->total;
    }

    public function getMwgDescription(): string
    {
        return "Factura #{$this->number}";
    }

    public function getMwgReference(): string
    {
        return $this->uuid;
    }

    public function getMwgMsisdn(): ?string
    {
        // Retorna null se o MSISDN deve ser fornecido no momento do pagamento
        return $this->customer_phone;
    }
}

// Com instância da Wallet
$invoice->pay($wallet, '+258840000000');
$invoice->pay($wallet, '+258840000000', TransactionType::BUSINESS_TO_CUSTOMER);
$invoice->pay($wallet, '+258840000000', TransactionType::CUSTOMER_TO_BUSINESS, attempts: 3);

// Por nome da carteira (resolve automaticamente)
$invoice->payWith('m-pesa', '+258840000000');

// Atalhos por tipo
$invoice->payC2B('m-pesa', '+258840000000');
$invoice->payB2C('e-mola', '+258860000000');

$invoice->mwsPayment($wallet)
    ->toSocket('channel_123')
    ->onSuccess(fn($tx, $r) => $invoice->update(['paid' => true]))
    ->c2b(msisdn: '+258840000000', amount: 250.00, attempts: 3);

// Ou por nome
$invoice->mwsPaymentWith('m-pesa')
    ->onFail(fn($tx, $r) => Log::error('Pagamento falhou'))
    ->c2b(msisdn: '+258840000000', amount: 250.00);

$invoice->isPaid();                // bool — tem pelo menos uma transacção com sucesso?
$invoice->hasPaymentInProgress();  // bool — tem transacção pendente/em processamento?
$invoice->totalPaid();             // float — soma de todas as transacções com sucesso
$invoice->mwsTransactions;         // Collection de Transaction
$invoice->latestMwgTransaction();  // Transaction|null

// Da transacção para o modelo
$tx->billable; // → Invoice #42

// Do modelo para as transacções
$invoice->mwsTransactions; // → Collection de Transaction

use Techsolutions\Sdk\MobileWallets\Facades\MobileWallet;

// Atalho: resolve a wallet e retorna um TransactionBuilder
MobileWallet::wallet('m-pesa')
    ->c2b(msisdn: '+258840000000', amount: 500.00);

MobileWallet::wallet('e-mola')
    ->b2c(msisdn: '+258860000000', amount: 100.00);

// Acesso directo ao driver (sem builder)
$driver = MobileWallet::resolve('m-pesa');
$response = $driver->initiateTransaction(
    msisdn: '+258840000000',
    amount: 250.00,
    reference: 'REF-001',
    type: TransactionType::CUSTOMER_TO_BUSINESS,
);

// Ambos funcionam:
MobileWallet::wallet('m-pesa');
MWS::wallet('m-pesa');

use Techsolutions\Sdk\MobileWallets\Enums\TransactionStatus;

TransactionStatus::PENDING      // 'pending'    — criada, ainda não processada
TransactionStatus::PROCESSING   // 'processing' — em processamento (tentativa em curso)
TransactionStatus::SUCCEEDED    // 'succeeded'  — concluída com sucesso
TransactionStatus::FAILED       // 'failed'     — falhou (tentativas esgotadas)
TransactionStatus::CANCELLED    // 'cancelled'  — cancelada manualmente
TransactionStatus::REFUNDED     // 'refunded'   — reembolsada

$status->label();       // "Pendente" (traduzido)
$status->isTerminal();  // true para succeeded, failed, cancelled, refunded
$status->isInProgress();// true para pending, processing

use Techsolutions\Sdk\MobileWallets\Enums\TransactionType;

TransactionType::CUSTOMER_TO_BUSINESS  // 'c2b'
TransactionType::BUSINESS_TO_CUSTOMER  // 'b2c'
TransactionType::BUSINESS_TO_BUSINESS  // 'b2b'
TransactionType::CUSTOMER_TO_CUSTOMER  // 'c2c'

$type->label(); // "Cliente para Empresa" (traduzido)

use Techsolutions\Sdk\MobileWallets\Enums\WalletDriver;

WalletDriver::M_PESA   // 'm-pesa'
WalletDriver::E_MOLA   // 'e-mola'
WalletDriver::SANDBOX  // 'sandbox'

// Resolve aliases
WalletDriver::resolve('mpesa');   // WalletDriver::M_PESA
WalletDriver::resolve('m-peza');  // WalletDriver::M_PESA
WalletDriver::resolve('emola');   // WalletDriver::E_MOLA

use Techsolutions\Sdk\MobileWallets\Enums\GatewayResponseCode;

// Resolver a partir de um código M-Pesa bruto
$code = GatewayResponseCode::fromMPesaCode('INS-5');
// → GatewayResponseCode::M_PESA_INSUFFICIENT_BALANCE

// Resolver a partir de um código E-Mola bruto de transacção (com ou sem zero à esquerda)
$code = GatewayResponseCode::fromEMolaCode('07');
// → GatewayResponseCode::E_MOLA_INVALID_MSISDN
GatewayResponseCode::fromEMolaCode('7'); // mesmo resultado

// Resolver um código de NÍVEL GATEWAY (tag <error> do BCCS)
$gw = GatewayResponseCode::fromEMolaGatewayCode('1000');
// → GatewayResponseCode::E_MOLA_GW_USERNAME_INVALID

// Propriedades
$code->isSuccess();     // false
$code->isTerminal();    // true  (MSISDN inválido — não retentar)
$code->isRetryable();   // false
$code->isEMola();       // true
$code->description();   // "Número de telefone (MSISDN) inválido." (traduzido)

// A partir do GatewayResponse
$response->resolveCode(); // GatewayResponseCode|null

// config/mobile-wallet.php
'retry' => [
    'max_attempts'       => 3,    // número máximo de tentativas
    'backoff_base'       => 2,    // segundos (base)
    'backoff_multiplier' => 2,    // factor exponencial
],
// Tentativa 1: delay 0 (imediata)
// Tentativa 2: delay 2s  (2 × 2^0)
// Tentativa 3: delay 4s  (2 × 2^1)
// Tentativa 4: delay 8s  (2 × 2^2)

Transaction::onWallet($wallet)
    ->shouldRetry(function (Transaction $tx, GatewayResponse $resp): ?bool {
        $code = $resp->resolveCode();

        // Forçar paragem imediata para saldo insuficiente
        if ($code === GatewayResponseCode::M_PESA_INSUFFICIENT_BALANCE) {
            return false;
        }

        // Forçar retry mesmo para códigos que normalmente seriam terminais
        if ($code === GatewayResponseCode::M_PESA_TRANSACTION_FAILED) {
            return true;
        }

        // null = usar lógica padrão do pacote
        return null;
    })
    ->c2b('+258840000000', '250.00', 5);

// app/Providers/EventServiceProvider.php
use Techsolutions\Sdk\MobileWallets\Events\TransactionSucceeded;
use Techsolutions\Sdk\MobileWallets\Events\TransactionFailed;

protected $listen = [
    TransactionSucceeded::class => [
        \App\Listeners\SendPaymentConfirmation::class,
        \App\Listeners\GenerateReceipt::class,
    ],
    TransactionFailed::class => [
        \App\Listeners\NotifyPaymentFailure::class,
    ],
];

namespace App\Listeners;

use Techsolutions\Sdk\MobileWallets\Events\TransactionSucceeded;

class SendPaymentConfirmation
{
    public function handle(TransactionSucceeded $event): void
    {
        $tx = $event->transaction;

        // Enviar notificação ao cliente
        Notification::send(
            $tx->billable?->customer,
            new PaymentConfirmedNotification($tx),
        );
    }
}

'webhook' => [
    'prefix' => 'mws/webhook',  // alterável
],

Transaction::onWallet($wallet)
    ->toSocket('payments_user_' . $userId)
    ->c2b('+258840000000', '250.00');

$gw = app(GatewayManager::class)->resolveForWallet('e-mola');
$gw->queryAccountBalance();                  // saldo do parceiro
$gw->queryBeneficiaryName('+258843000000');  // nome mascarado do beneficiário

use Techsolutions\Sdk\MobileWallets\Drivers\SandboxGateway;

// Simular falhas temporárias (as próximas 2 chamadas falham)
SandboxGateway::failNextCalls(2);

// Simular falha permanente
SandboxGateway::$shouldFail = true;

// Resetar
SandboxGateway::reset();

// Verificar estado
SandboxGateway::$callCount;     // número de chamadas feitas
SandboxGateway::$lastPayload;   // último payload enviado

// config/mobile-wallet.php
'wallets' => [
    'm-pesa' => [
        'driver'   => 'external',       // ← muda de 'm-pesa' para 'external'
        'gateway'  => 'eacacia',         // preset pré-configurado
        'base_url' => env('MWS_EACACIA_BASE_URL', 'https://testewallet.vitae-erp.co.mz'),
        'api_key'  => env('MWS_EACACIA_API_KEY', ''),
        'timeout'  => 30,
    ],
],

// Isto funciona tanto com driver directo como externo
Transaction::onWallet(Wallet::mpesa()->first())
    ->c2b(msisdn: '+258852444364', amount: 100.00);

$invoice->payWith('m-pesa', '+258852444364');

MobileWallet::wallet('m-pesa')->c2b('+258852444364', 100.00);

'm-pesa' => [
    'driver'   => 'external',
    'gateway'  => 'eacacia',
    'base_url' => env('MWS_EACACIA_BASE_URL', 'https://testewallet.vitae-erp.co.mz'),
    'api_key'  => env('MWS_EACACIA_API_KEY', ''),
],

'e-mola' => [
    'driver'   => 'external',
    'gateway'  => 'eacacia',
    'base_url' => env('MWS_EACACIA_BASE_URL', 'https://testewallet.vitae-erp.co.mz'),
    'api_key'  => env('MWS_EACACIA_API_KEY', ''),
],

'e-mola' => [
    'driver'    => 'external',
    'gateway'   => 'e2payments',
    'base_url'  => 'https://e2payments.explicador.co.mz',
    'api_key'   => env('MWS_E2PAYMENTS_TOKEN', ''),
    'wallet_id' => env('MWS_E2PAYMENTS_WALLET_ID', ''),
],

'm-pesa' => [
    'driver'   => 'external',
    'gateway'  => 'custom',
    'base_url' => 'https://outro-gateway.co.mz',
    'api_key'  => env('CUSTOM_API_KEY', ''),

    // Definir os endpoints para cada tipo de transacção
    'endpoints' => [
        'c2b' => '/api/v1/mpesa/c2b',
        'b2c' => '/api/v1/mpesa/b2c',
        'query' => '/api/v1/transaction/{reference}',
    ],

    // Mapear os nossos campos para os campos da API externa
    'payload_map' => [
        'msisdn'    => 'phone_number',    // o nosso 'msisdn' → o 'phone_number' deles
        'amount'    => 'amount',
        'reference' => 'transaction_ref',
    ],

    // Mapear a resposta da API externa para os nossos campos
    // Suporta dot notation para campos aninhados (ex.: 'data.txn_id')
    'response_map' => [
        'success_field'  => 'status',           // campo que indica sucesso
        'success_value'  => 'completed',        // valor que significa sucesso
        'transaction_id' => 'data.txn_id',      // onde está o ID da transacção
        'reference'      => 'data.reference',   // onde está a referência
    ],

    // Campos extras enviados em todos os pedidos
    'extra_payload' => [
        'client_id' => env('CUSTOM_CLIENT_ID', ''),
        'channel'   => 'api',
    ],

    // Tipo de autenticação: 'bearer', 'basic', 'header'
    'auth_type'        => 'bearer',
    'auth_header_name' => 'X-Api-Key',   // usado quando auth_type = 'header'
],

'wallets' => [
    'm-pesa' => [
        'driver' => 'm-pesa',     // ligação directa à Vodacom
        // ...credenciais directas...
    ],
    'e-mola' => [
        'driver'  => 'external',  // via EACACIA (sem precisar de SOAP)
        'gateway' => 'eacacia',
        'base_url' => 'https://testewallet.vitae-erp.co.mz',
    ],
],

$status = TransactionStatus::PENDING;
$status->label(); // "Pendente" (pt) / "Pending" (en)

$type = TransactionType::CUSTOMER_TO_BUSINESS;
$type->label(); // "Cliente para Empresa" (pt) / "Customer to Business" (en)

$code = GatewayResponseCode::M_PESA_INSUFFICIENT_BALANCE;
$code->description(); // "Saldo insuficiente na conta do cliente." (pt)

use Techsolutions\Sdk\MobileWallets\Drivers\SandboxGateway;

protected function setUp(): void
{
    parent::setUp();
    config(['mobile-wallet.sandbox' => true]);
    config(['mobile-wallet.queue.enabled' => false]); // síncrono nos testes
    SandboxGateway::reset();
}

public function test_payment_flow(): void
{
    $invoice = Invoice::factory()->create(['total' => 500]);
    $tx = $invoice->payWith('m-pesa', '+258840000000');

    $this->assertTrue($tx->fresh()->isSucceeded());
    $this->assertTrue($invoice->fresh()->isPaid());
}

public function test_payment_failure_with_retry(): void
{
    SandboxGateway::failNextCalls(2);

    $tx = Transaction::onWallet(Wallet::mpesa()->first())
        ->c2b('+258840000000', '100.00', 3);

    $tx->refresh();
    $this->assertTrue($tx->isSucceeded());
    $this->assertEquals(3, $tx->current_attempt);
}



declare(strict_types=1);

namespace App\MobileWallet\Drivers;

use Techsolutions\Sdk\MobileWallets\Contracts\MobileWalletGateway;
use Techsolutions\Sdk\MobileWallets\DTOs\GatewayResponse;
use Techsolutions\Sdk\MobileWallets\Enums\TransactionType;

final class MKeshGateway extends MobileWalletGateway
{
    protected function getBaseUrl(): string { /* ... */ }
    protected function getTransactionUrl(TransactionType $type): string { /* ... */ }
    protected function buildHeaders(): array { /* ... */ }
    protected function buildPayload(string $msisdn, float $amount, string $reference, TransactionType $type, array $extra = []): array { /* ... */ }
    protected function parseResponse(array $body, int $httpStatus): GatewayResponse { /* ... */ }
    protected function getQueryUrl(string $reference): string { /* ... */ }
    protected function parseQueryResponse(array $body, int $httpStatus): GatewayResponse { /* ... */ }
}

// app/Providers/AppServiceProvider.php
use Techsolutions\Sdk\MobileWallets\Support\GatewayManager;

public function boot(): void
{
    $this->app->make(GatewayManager::class)->extend('m-kesh', function ($app) {
        return new \App\MobileWallet\Drivers\MKeshGateway(
            config('mobile-wallet.wallets.m-kesh', [])
        );
    });
}

// config/mobile-wallet.php → wallets
'm-kesh' => [
    'driver'   => 'm-kesh',
    'base_url' => env('MWS_MKESH_BASE_URL'),
    // ...
],

Wallet::create(['name' => 'm-kesh', 'display_name' => 'M-Kesh', 'is_active' => true]);
bash
php artisan vendor:publish --tag=mobile-wallet
bash
php artisan vendor:publish --tag=mobile-wallet-config      # config/mobile-wallet.php
php artisan vendor:publish --tag=mobile-wallet-migrations   # database/migrations/
php artisan vendor:publish --tag=mobile-wallet-lang         # lang/vendor/mobile-wallet/
bash
php artisan migrate
bash
php artisan vendor:publish --tag=mobile-wallet-lang
bash
composer analyse