PHPackages                             flavytech/laravel-etims - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. flavytech/laravel-etims

ActiveLibrary[Payment Processing](/categories/payments)

flavytech/laravel-etims
=======================

Production-grade Laravel SDK for Kenya's KRA eTIMS / Gava Connect API.

v2.0.2(1mo ago)491[1 PRs](https://github.com/flavian-ndunda/laravel-etims/pulls)MITPHPPHP ^8.1CI failing

Since May 26Pushed 3w ago1 watchersCompare

[ Source](https://github.com/flavian-ndunda/laravel-etims)[ Packagist](https://packagist.org/packages/flavytech/laravel-etims)[ Docs](https://github.com/flavian-ndunda/laravel-etims)[ RSS](/packages/flavytech-laravel-etims/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (15)Versions (8)Used By (0)

🇰🇪 Laravel eTIMS SDK
====================

[](#-laravel-etims-sdk)

[![Latest Version](https://camo.githubusercontent.com/2004bcb6e7d91e85ccf793d3c080a8fab12884dd91464f6edb559581c25e041d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f666c617679746563682f6c61726176656c2d6574696d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/flavytech/laravel-etims)[![PHP Version](https://camo.githubusercontent.com/e4fa9a2e9bf493f2e2579e8f3d9bb4ae9d39b12669c705d5631f5e16ece9a84e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e332532422d626c75652e7376673f7374796c653d666c61742d737175617265)](https://php.net)[![Laravel Version](https://camo.githubusercontent.com/0a380de582a2ee19bcc931027c69e414749e1f70b4fa4f01fdb275b871c90866/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c61726176656c2d31312532422d7265642e7376673f7374796c653d666c61742d737175617265)](https://laravel.com)[![Tests](https://github.com/flavytech/laravel-etims/actions/workflows/tests.yml/badge.svg)](https://github.com/flavytech/laravel-etims/actions)[![License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)

> **Production-grade Laravel SDK for Kenya's KRA eTIMS / Gava Connect API.**Built for POS systems, ERP platforms, and SaaS products operating in Kenya and East Africa.

---

Why This SDK?
-------------

[](#why-this-sdk)

Integrating with KRA eTIMS is non-trivial in real production environments:

- The API goes down. Your invoices must not be lost.
- Networks in Kenya are intermittent. Retries must be smart, not dumb.
- POS systems process hundreds of transactions per hour. Your queue must be resilient.
- You may serve multiple businesses from one Laravel installation. Multi-tenancy must be first-class.

This SDK handles all of that so you can focus on your product.

---

Features
--------

[](#features)

FeatureStatusInvoice submission (sync + async)✅ Phase 1Auth token management + auto-refresh✅ Phase 1Exponential backoff retry✅ Phase 1Idempotency protection✅ Phase 1Audit trail (DB)✅ Phase 1Laravel event system integration✅ Phase 1Sandbox/production mode switching✅ Phase 1Testing fake + assertion API✅ Phase 1KRA PIN validation✅ Phase 1Dead letter queue + failed invoice recovery✅ Phase 1Multi-tenant SaaS support✅ Phase 1Stock synchronization🔜 Phase 2Credit/debit note handling🔜 Phase 2Webhook handling🔜 Phase 2QR code + thermal receipt support🔜 Phase 3Branch management🔜 Phase 3---

Installation
------------

[](#installation)

```
composer require flavytech/laravel-etims
```

Publish the configuration and migrations:

```
php artisan vendor:publish --tag=etims-config
php artisan vendor:publish --tag=etims-migrations
php artisan migrate
```

---

Configuration
-------------

[](#configuration)

Add these variables to your `.env` file:

```
# Mode: sandbox | production
ETIMS_MODE=sandbox

# Your KRA-issued credentials
ETIMS_PIN=P000000000A
ETIMS_BRANCH_ID=00
ETIMS_DEVICE_SERIAL=your-device-serial
ETIMS_SECRET=your-api-secret

# Queue (recommended: use Redis)
ETIMS_QUEUE_CONNECTION=redis
ETIMS_QUEUE_NAME=etims
ETIMS_MAX_TRIES=5

# Optional tuning
ETIMS_TIMEOUT=30
ETIMS_LOG_CHANNEL=daily
```

> **Never commit real credentials to version control.** Use `.env` only.

---

Quick Start
-----------

[](#quick-start)

### Submitting an Invoice Synchronously

[](#submitting-an-invoice-synchronously)

Use this when you need an immediate KRA response (e.g. a POS checkout flow):

```
use Flavytech\Etims\Facades\Etims;
use Flavytech\Etims\DTOs\InvoiceDTO;
use Flavytech\Etims\DTOs\InvoiceLineDTO;

$invoice = InvoiceDTO::make([
    'invcNo'       => 'INV-2024-001',
    'tpin'         => config('etims.credentials.pin'),
    'custTpin'     => 'P000000000B',    // buyer's KRA PIN
    'custNm'       => 'Acme Ltd',
    'totAmt'       => 11600.00,
    'vatAmt'       => 1600.00,
    'taxblAmt'     => 10000.00,
    'salesDt'      => now()->toDateString(),
    'cfmDt'        => now()->toDateString(),
    'rcptTyCd'     => 'S',              // S = Sale
    'pmtTyCd'      => '01',
    'itemList'     => [
        InvoiceLineDTO::make([
            'itemSeq'    => 1,
            'itemCd'     => 'ITEM-001',
            'itemNm'     => 'Widget Pro',
            'qty'        => 2,
            'prc'        => 5000.00,
            'taxblAmt'   => 10000.00,
            'taxAmt'     => 1600.00,
            'totAmt'     => 11600.00,
            'taxTyCd'    => 'A',       // A = Standard 16% VAT
        ]),
    ],
]);

$response = Etims::submitInvoice($invoice);

if ($response->isSuccessful()) {
    // Print the KRA receipt number and QR code
    $receiptNumber = $response->receiptNumber;
    $qrCode        = $response->qrCode;
}
```

`InvoiceDTO::make()` accepts both the original snake\_case keys and the KRA field names shown above.

### Queuing an Invoice (Recommended for Most Cases)

[](#queuing-an-invoice-recommended-for-most-cases)

This is the preferred approach for production. The invoice is dispatched to a durable background job with automatic retries:

```
// Fire and forget — returns immediately
Etims::queueInvoice($invoice);

// Your application continues while KRA processes in the background.
// Listen to events to know the outcome:
```

Listen to the outcome events in your `EventServiceProvider`:

```
protected $listen = [
    \Flavytech\Etims\Events\InvoiceSubmitted::class => [
        \App\Listeners\GenerateKraReceipt::class,    // generate receipt PDF
        \App\Listeners\UpdateOrderStatus::class,     // mark order as fiscalized
    ],
    \Flavytech\Etims\Events\InvoiceFailed::class => [
        \App\Listeners\AlertOperationsTeam::class,   // notify via Slack/email
        \App\Listeners\FlagOrderForReview::class,    // mark in your system
    ],
    \Flavytech\Etims\Events\InvoiceQueued::class => [
        \App\Listeners\ShowPendingStatus::class,     // update POS UI immediately
    ],
];
```

### Validating a KRA PIN

[](#validating-a-kra-pin)

```
$result = Etims::validatePin('P000000000B');

if ($result->isValid()) {
    echo "Buyer: {$result->taxpayerName}";
} else {
    // PIN not found in KRA registry
    return back()->withError('Invalid buyer PIN. Please verify and try again.');
}
```

### Checking Invoice Status

[](#checking-invoice-status)

```
$status = Etims::getInvoiceStatus('INV-2024-001');

if ($status->isSuccessful()) {
    echo "Receipt: {$status->receiptNumber}";
} elseif ($status->isPending()) {
    echo "KRA is still processing this invoice.";
}
```

### Working with Failed Invoices

[](#working-with-failed-invoices)

```
// Get all permanently failed invoices for review
$failedInvoices = Etims::failedInvoices();

foreach ($failedInvoices as $invoice) {
    echo "{$invoice->invoice_number}: {$invoice->failure_reason}";
}

// Re-queue a failed invoice after fixing the issue
Etims::retryFailedInvoice($invoice->id);
```

---

Invoice Types
-------------

[](#invoice-types)

CodeDescriptionUse Case`S`SaleStandard sales invoice`R`Credit NoteRefund / reversal of a sale`D`Debit NoteAdditional charge on a saleTax Type Codes
--------------

[](#tax-type-codes)

CodeDescriptionVAT Rate`A`Standard rated16%`B`Zero rated0%`C`VAT exemptN/A`D`Non-VATableN/A`E`Excisable (with VAT)16% + ExcisePayment Type Codes
------------------

[](#payment-type-codes)

CodeDescription`CASH`Cash payment`CREDIT`Credit terms`MPESA`M-Pesa mobile money`BANK`Bank transfer`CHEQUE`Cheque`OTHER`Other payment methods---

Multi-Tenant SaaS Setup
-----------------------

[](#multi-tenant-saas-setup)

When serving multiple KRA-registered businesses from one Laravel installation:

**Step 1:** Enable multi-tenancy in config:

```
// config/etims.php
'multi_tenancy' => [
    'enabled'         => true,
    'tenant_resolver' => \App\Services\EtimsTenantResolver::class,
],
```

**Step 2:** Implement the `TenantResolverContract`:

```
use Flavytech\Etims\Contracts\TenantResolverContract;

class EtimsTenantResolver implements TenantResolverContract
{
    public function resolve(): array
    {
        // Resolve from your tenancy system
        $tenant = app('currentTenant'); // e.g. spatie/laravel-multitenancy

        return [
            'pin'           => $tenant->kra_pin,
            'branch_id'     => $tenant->etims_branch_id,
            'device_serial' => $tenant->etims_device_serial,
            'secret'        => decrypt($tenant->etims_secret), // store encrypted!
            'mode'          => $tenant->etims_mode,            // per-tenant sandbox/prod
        ];
    }

    public function tenantId(): string|int
    {
        return app('currentTenant')->id;
    }
}
```

**Step 3:** Bind it in your `AppServiceProvider`:

```
$this->app->bind(
    \Flavytech\Etims\Contracts\TenantResolverContract::class,
    \App\Services\EtimsTenantResolver::class
);
```

That's it. Every SDK call now automatically uses the correct credentials for the active tenant.

---

Testing
-------

[](#testing)

The SDK provides a first-class fake for testing without any real HTTP calls.

```
use Flavytech\Etims\Facades\Etims;

beforeEach(function () {
    Etims::fake();
});

it('fiscalizes an order on checkout', function () {
    $order = Order::factory()->create(['total' => 11600]);

    $this->post("/orders/{$order->id}/checkout");

    Etims::assertInvoiceSubmitted("INV-{$order->id}");
});

it('queues the invoice for background processing', function () {
    Queue::fake();

    Etims::queueInvoice(makeTestInvoice('INV-001'));

    Etims::assertInvoiceQueued('INV-001');
});

it('handles KRA downtime gracefully', function () {
    Etims::fake()->failWith(
        new \Flavytech\Etims\Exceptions\EtimsApiException('KRA is down', 503)
    );

    // Your app should still return 200 — it queues for retry
    $this->post('/checkout/1')->assertStatus(202);
});

it('validates correct invoice data', function () {
    $fake = Etims::fake();

    $this->post('/checkout/1');

    $fake->assertSubmittedMatching(
        fn($invoice) => $invoice->totalAmount === 11600.00
            && $invoice->invoiceType === 'S'
    );
});

it('rejects an invalid buyer PIN', function () {
    Etims::fake()->withInvalidPins(['P999999999Z']);

    $response = Etims::validatePin('P999999999Z');

    expect($response->isValid())->toBeFalse();
});
```

### Stubbing Specific Responses

[](#stubbing-specific-responses)

```
use Flavytech\Etims\DTOs\InvoiceResponseDTO;

$stubbedResponse = InvoiceResponseDTO::fromKraResponse([
    'resultCd' => '000',
    'resultMsg' => 'OK',
    'data' => [
        'rcptNo'    => 'RCPT-MY-TEST',
        'qrCodeUrl' => 'https://test.kra.go.ke/qr/test',
    ],
]);

Etims::fake()->respondTo('INV-SPECIFIC-001', $stubbedResponse);

$response = Etims::submitInvoice($invoice); // returns RCPT-MY-TEST
```

---

Running the SDK Test Suite
--------------------------

[](#running-the-sdk-test-suite)

```
composer test
composer test-coverage
composer analyse   # PHPStan static analysis
composer format    # PHP CS Fixer
```

---

Queue Worker Setup
------------------

[](#queue-worker-setup)

Run a dedicated worker for the eTIMS queue in production:

```
# Supervisor config for dedicated eTIMS worker
php artisan queue:work redis \
    --queue=etims \
    --tries=5 \
    --backoff=10,30,60,120,300 \
    --timeout=60 \
    --sleep=3
```

For failed job monitoring:

```
# View failed eTIMS jobs
php artisan queue:failed | grep etims

# Retry all failed jobs
php artisan queue:retry all
```

---

Architecture Overview
---------------------

[](#architecture-overview)

```
Facade (Etims::)
    └── EtimsManager         (orchestration, idempotency, events, multi-tenancy)
            └── EtimsClient  (API contract implementation)
                    └── EtimsHttpClient  (HTTP, auth tokens, retries, logging)
                                └── KRA Gava Connect API

Async path:
    Etims::queueInvoice() → SubmitInvoiceJob → Queue Worker → EtimsClient

```

---

Error Handling Reference
------------------------

[](#error-handling-reference)

ExceptionCauseRetryable`EtimsApiException`KRA API error or network failureDepends on HTTP status`EtimsAuthException`Invalid credentials or expired tokenNo — fix credentials`EtimsValidationException`Invalid DTO data (client-side)No — fix data`EtimsIdempotencyException`Duplicate invoice detectedNo — already submitted`EtimsConfigException`Missing or invalid SDK configNo — fix config---

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

Contributing
------------

[](#contributing)

Contributions welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

---

> Built with care for the Kenyan and East African developer ecosystem. 🇰🇪

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance92

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 87.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

7

Last Release

57d ago

Major Versions

v1.1.5 → v2.0.02026-05-26

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.1.2PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/24292126?v=4)[Flavian Ndunda](/maintainers/flavian-ndunda)[@flavian-ndunda](https://github.com/flavian-ndunda)

---

Top Contributors

[![flavian-ndunda](https://avatars.githubusercontent.com/u/24292126?v=4)](https://github.com/flavian-ndunda "flavian-ndunda (14 commits)")[![simiyu-samuel](https://avatars.githubusercontent.com/u/98589218?v=4)](https://github.com/simiyu-samuel "simiyu-samuel (2 commits)")

---

Tags

laravelinvoiceposERPtaxkenyakraetimsfiscalgava-connect

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/flavytech-laravel-etims/health.svg)

```
[![Health](https://phpackages.com/badges/flavytech-laravel-etims/health.svg)](https://phpackages.com/packages/flavytech-laravel-etims)
```

###  Alternatives

[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M133](/packages/roots-acorn)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M136](/packages/laravel-pulse)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)[flarum/core

Delightfully simple forum software.

211.4M2.4k](/packages/flarum-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
