kra-etims-sdk

1.1.3 • Public • Published

KRA eTims SDK

A comprehensive Node.js SDK for integrating with the Kenya Revenue Authority (KRA) Electronic Tax Invoice Management System (eTims).

npm version License: ISC

Features

  • Complete KRA eTims API integration
  • Express.js server mode for easy API endpoint exposure
  • CORS support with domain whitelisting
  • Comprehensive validation for all API requests
  • Detailed logging and error handling
  • Environment-based configuration
  • TypeScript support

Author

Shadrack Matata
Email: kisamba.debug@gmail.com
Tel: +254722854082 / +254733854082

Installation

npm install kra-etims-sdk

Quick Start

Basic Usage

const KRAeTimsSDK = require('kra-etims-sdk');

// Initialize the SDK
const sdk = new KRAeTimsSDK();

// Authenticate with KRA eTims API
async function authenticate() {
  try {
    const token = await sdk.authenticate('your_username', 'your_password');
    console.log('Authentication successful:', token);
    return token;
  } catch (error) {
    console.error('Authentication failed:', error);
  }
}

// Send a sales transaction
async function sendSalesTransaction() {
  try {
    // Authenticate first
    await authenticate();
    
    // Prepare sales transaction data
    const salesData = {
      tin: 'P000000045R',
      bhfId: '00',
      invcNo: 'INV001',
      salesTrnsItems: [
        {
          itemCd: 'ITEM001',
          itemNm: 'Test Item',
          qty: 1,
          prc: 100,
          splyAmt: 100,
          dcRt: 0,
          dcAmt: 0,
          taxTyCd: 'V',
          taxAmt: 16
        }
      ]
    };
    
    // Send sales transaction
    const result = await sdk.sendSalesTransaction(salesData);
    console.log('Sales transaction sent:', result);
  } catch (error) {
    console.error('Failed to send sales transaction:', error);
  }
}

Express Server Integration

Complete Express Integration Example

Below is a comprehensive example of integrating the KRA eTims SDK with an Express application as middleware:

const express = require('express');
const bodyParser = require('body-parser');
const morgan = require('morgan'); // Optional for logging
const KRAeTimsSDK = require('kra-etims-sdk');

// Initialize Express app
const app = express();
const port = process.env.PORT || 3000;

// Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan('dev')); // Optional for logging

// Initialize the KRA eTims SDK
const etimsSDK = new KRAeTimsSDK({
  // Optional configuration
  logLevel: 'info', // 'debug', 'info', 'warn', 'error'
  timeout: 30000, // API request timeout in ms
});

// Mount the SDK as middleware
app.use('/etims-api', etimsSDK.middleware());

// Custom routes that use the SDK directly

// Authentication example
app.post('/api/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    const token = await etimsSDK.authenticate(username, password);
    res.json({ success: true, token });
  } catch (error) {
    console.error('Authentication error:', error);
    res.status(401).json({ success: false, error: error.message });
  }
});

// Sales transaction example
app.post('/api/sales', async (req, res) => {
  try {
    // Authenticate first (or use middleware to handle this)
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    // Send sales transaction
    const result = await etimsSDK.sendSalesTransaction(req.body);
    res.json({ success: true, data: result });
  } catch (error) {
    console.error('Sales transaction error:', error);
    res.status(400).json({ success: false, error: error.message });
  }
});

// Get stock information example
app.post('/api/stock/info', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    const result = await etimsSDK.getStockMoveList(req.body);
    res.json({ success: true, data: result });
  } catch (error) {
    console.error('Stock info error:', error);
    res.status(400).json({ success: false, error: error.message });
  }
});

// Error handling middleware
app.use((err, req, res, next) => {
  console.error('Application error:', err);
  res.status(500).json({ success: false, error: 'Internal server error' });
});

// Start the server
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Authentication Middleware Example

Create a reusable authentication middleware for your Express routes:

// auth.middleware.js
const KRAeTimsSDK = require('kra-etims-sdk');
const sdk = new KRAeTimsSDK();

const authMiddleware = async (req, res, next) => {
  try {
    // You could also check for an existing token and validate it
    await sdk.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    req.etimsSDK = sdk; // Attach the authenticated SDK to the request
    next();
  } catch (error) {
    console.error('KRA eTims authentication error:', error);
    res.status(401).json({ success: false, error: 'Authentication failed' });
  }
};

module.exports = authMiddleware;

Then use it in your routes:

const express = require('express');
const authMiddleware = require('./auth.middleware');
const router = express.Router();

// Apply authentication middleware to all routes
router.use(authMiddleware);

// Now all routes have access to the authenticated SDK via req.etimsSDK
router.post('/sales/send', async (req, res) => {
  try {
    const result = await req.etimsSDK.sendSalesTransaction(req.body);
    res.json({ success: true, data: result });
  } catch (error) {
    res.status(400).json({ success: false, error: error.message });
  }
});

module.exports = router;

Setting Up as a Standalone Server

For comparison, here's how to set up the SDK as a standalone server:

const KRAeTimsSDK = require('kra-etims-sdk');

// Initialize the SDK with server option
const sdk = new KRAeTimsSDK({ server: true });

// Start the server
const port = process.env.PORT || 5000;
sdk.start(port).then(() => {
  console.log(`KRA eTims server started on port ${port}`);
}).catch(err => {
  console.error('Failed to start server:', err);
});

Environment Configuration

Create a .env file in your project root:

# API Base URLs
DEV_API_BASE_URL=https://etims-api-sbx.kra.go.ke
PROD_API_BASE_URL=https://etims-api.kra.go.ke/etims-api

# Authentication
API_USERNAME=your_username
API_PASSWORD=your_password

# Environment
NODE_ENV=development
PORT=5000

# CORS Configuration
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com

Switching Between Production and Development Endpoints

The SDK automatically selects the appropriate API base URL based on the NODE_ENV environment variable:

  • When NODE_ENV=development (or not set), the SDK uses DEV_API_BASE_URL (sandbox environment)
  • When NODE_ENV=production, the SDK uses PROD_API_BASE_URL (production environment)

Method 1: Using Environment Variables

# For development (sandbox)
NODE_ENV=development npm start

# For production
NODE_ENV=production npm start

Method 2: Setting NODE_ENV in Your Code

// Before initializing the SDK
process.env.NODE_ENV = 'production'; // or 'development'

const KRAeTimsSDK = require('kra-etims-sdk');
const sdk = new KRAeTimsSDK();

Method 3: Explicitly Setting the API Base URL

You can also override the automatic selection by explicitly setting the API base URL when initializing the SDK:

const KRAeTimsSDK = require('kra-etims-sdk');

// For development (sandbox)
const devSdk = new KRAeTimsSDK({
  apiBaseUrl: 'https://etims-api-sbx.kra.go.ke'
});

// For production
const prodSdk = new KRAeTimsSDK({
  apiBaseUrl: 'https://etims-api.kra.go.ke/etims-api'
});

CORS and Security

The SDK includes built-in CORS support with domain whitelisting to secure your API endpoints.

Configuring CORS

You can configure allowed origins in your .env file:

CORS_ALLOWED_ORIGINS=https://yourdomain.com,https://app.yourdomain.com

By default, the SDK allows requests from http://localhost:3000 and http://localhost:5000 in development.

API Endpoints and Integration Examples

This section provides comprehensive examples of how to use each endpoint in your Express application.

Authentication

POST /api/auth/token - Get authentication token

Request Body:

{
  "username": "your_username",
  "password": "your_password"
}

Express Integration Example:

const express = require('express');
const KRAeTimsSDK = require('kra-etims-sdk');
const router = express.Router();

// Initialize the SDK
const etimsSDK = new KRAeTimsSDK();

router.post('/auth', async (req, res) => {
  try {
    const { username, password } = req.body;
    
    // Call the SDK's authentication method
    const token = await etimsSDK.authenticate(username, password);
    
    // Store token in session or return to client
    req.session.etimsToken = token;
    
    res.json({
      success: true,
      message: 'Authentication successful',
      expiresAt: etimsSDK.getTokenExpiry()
    });
  } catch (error) {
    console.error('Authentication error:', error);
    res.status(401).json({
      success: false,
      message: 'Authentication failed',
      error: error.message
    });
  }
});

module.exports = router;

Initialization

POST /api/initialization/osdc-info - Initialize OSDC Info

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "dvcSrlNo": "MOVA22"
}

Express Integration Example:

router.post('/initialize', async (req, res) => {
  try {
    // Ensure authentication
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const initData = req.body;
    const result = await etimsSDK.initializeOsdcInfo(initData);
    
    res.json({
      success: true,
      message: 'OSDC initialization successful',
      data: result
    });
  } catch (error) {
    console.error('Initialization error:', error);
    res.status(400).json({
      success: false,
      message: 'Initialization failed',
      error: error.message
    });
  }
});

Basic Data Management

POST /api/basic-data/code-list - Get code list

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/code-list', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getCodeList(req.body);
    
    res.json({
      success: true,
      data: result
    });
  } catch (error) {
    console.error('Code list error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

POST /api/basic-data/item-cls-list - Get item classification list

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/item-classifications', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getItemClassificationList(req.body);
    
    // Cache the results for future use
    req.app.locals.itemClassifications = result;
    
    res.json({
      success: true,
      count: result.length,
      data: result
    });
  } catch (error) {
    console.error('Item classification error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

POST /api/basic-data/bhf-list - Get branch list

Request Body:

{
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/branches', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getBranchList(req.body);
    
    res.json({
      success: true,
      count: result.length,
      data: result
    });
  } catch (error) {
    console.error('Branch list error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

POST /api/basic-data/notice-list - Get notice list

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/notices', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getNoticeList(req.body);
    
    res.json({
      success: true,
      data: result
    });
  } catch (error) {
    console.error('Notice list error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

POST /api/basic-data/taxpayer-info - Get taxpayer info

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/taxpayer', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getTaxpayerInfo(req.body);
    
    res.json({
      success: true,
      data: result
    });
  } catch (error) {
    console.error('Taxpayer info error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

POST /api/basic-data/customer-list - Get customer list

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/customers', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getCustomerList(req.body);
    
    res.json({
      success: true,
      count: result.length,
      data: result
    });
  } catch (error) {
    console.error('Customer list error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

Sales Management

POST /api/sales/send - Send sales transaction

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "invcNo": "INV001",
  "salesTrnsItems": [
    {
      "itemCd": "ITEM001",
      "itemNm": "Test Item",
      "qty": 1,
      "prc": 100,
      "splyAmt": 100,
      "dcRt": 0,
      "dcAmt": 0,
      "taxTyCd": "V",
      "taxAmt": 16
    }
  ]
}

Express Integration Example:

router.post('/sales/create', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    // You might want to validate the sales data before sending
    const salesData = req.body;
    
    // Calculate totals if not provided
    if (!salesData.totals) {
      const items = salesData.salesTrnsItems;
      const totalAmount = items.reduce((sum, item) => sum + item.splyAmt, 0);
      const totalTax = items.reduce((sum, item) => sum + item.taxAmt, 0);
      
      salesData.totals = {
        totalAmount,
        totalTax,
        grandTotal: totalAmount + totalTax
      };
    }
    
    const result = await etimsSDK.sendSalesTransaction(salesData);
    
    // You might want to store the result in your database
    
    res.json({
      success: true,
      message: 'Sales transaction recorded successfully',
      data: result,
      receiptNo: result.receiptNo,
      timestamp: result.timestamp
    });
  } catch (error) {
    console.error('Sales transaction error:', error);
    res.status(400).json({
      success: false,
      message: 'Failed to record sales transaction',
      error: error.message
    });
  }
});

POST /api/sales/select - Get sales transaction

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101",
  "invcNo": "INV001"  // Optional
}

Express Integration Example:

router.post('/sales/history', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getSalesTransactions(req.body);
    
    res.json({
      success: true,
      count: result.length,
      data: result
    });
  } catch (error) {
    console.error('Sales history error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

// Get a specific invoice
router.get('/sales/invoice/:invoiceNo', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const params = {
      tin: process.env.KRA_TIN,
      bhfId: process.env.KRA_BHF_ID,
      lastReqDt: '20220101010101',
      invcNo: req.params.invoiceNo
    };
    
    const result = await etimsSDK.getSalesTransactions(params);
    
    if (result.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Invoice not found'
      });
    }
    
    res.json({
      success: true,
      data: result[0]
    });
  } catch (error) {
    console.error('Invoice retrieval error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

Stock Management

POST /api/stock/move-list - Get move list

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/stock/movements', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getStockMoveList(req.body);
    
    res.json({
      success: true,
      count: result.length,
      data: result
    });
  } catch (error) {
    console.error('Stock movement error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

POST /api/stock/save-master - Save stock master

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "itemCd": "ITEM001",
  "itemClsCd": "FOOD",
  "itemNm": "Test Item",
  "pkgUnitCd": "EA",
  "qtyUnitCd": "EA",
  "splyAmt": 100,
  "vatTyCd": "V"
}

Express Integration Example:

router.post('/stock/create', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const stockData = req.body;
    const result = await etimsSDK.saveStockMaster(stockData);
    
    res.json({
      success: true,
      message: 'Stock item created successfully',
      data: result
    });
  } catch (error) {
    console.error('Stock creation error:', error);
    res.status(400).json({
      success: false,
      message: 'Failed to create stock item',
      error: error.message
    });
  }
});

Purchase Management

POST /api/purchase/select - Get purchase transaction

Request Body:

{
  "tin": "P000000045R",
  "bhfId": "00",
  "lastReqDt": "20220101010101"
}

Express Integration Example:

router.post('/purchases', async (req, res) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    
    const result = await etimsSDK.getPurchaseTransactions(req.body);
    
    res.json({
      success: true,
      count: result.length,
      data: result
    });
  } catch (error) {
    console.error('Purchase history error:', error);
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

Complete Router Example

Here's how to organize all these endpoints into a complete Express router:

// routes/etims.routes.js
const express = require('express');
const KRAeTimsSDK = require('kra-etims-sdk');
const router = express.Router();

// Initialize the SDK
const etimsSDK = new KRAeTimsSDK();

// Authentication middleware
const authMiddleware = async (req, res, next) => {
  try {
    await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
    req.etimsSDK = etimsSDK;
    next();
  } catch (error) {
    res.status(401).json({
      success: false,
      message: 'KRA eTims authentication failed',
      error: error.message
    });
  }
};

// Apply authentication middleware to all routes except login
router.use((req, res, next) => {
  if (req.path === '/auth') {
    return next();
  }
  authMiddleware(req, res, next);
});

// Authentication route
router.post('/auth', async (req, res) => {
  try {
    const { username, password } = req.body;
    const token = await etimsSDK.authenticate(username, password);
    res.json({ success: true, token });
  } catch (error) {
    res.status(401).json({ success: false, error: error.message });
  }
});

// Basic data routes
router.post('/code-list', async (req, res) => {
  try {
    const result = await req.etimsSDK.getCodeList(req.body);
    res.json({ success: true, data: result });
  } catch (error) {
    res.status(400).json({ success: false, error: error.message });
  }
});

// Add other routes here...

module.exports = router;

// In your main app.js:
// const etimsRoutes = require('./routes/etims.routes');
// app.use('/api/etims', etimsRoutes);

Advanced Usage

Custom Error Handling

const { AuthenticationError, ValidationError } = require('kra-etims-sdk/errors');

try {
  // SDK operations
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Handle authentication errors
    console.error('Authentication failed:', error.message);
  } else if (error instanceof ValidationError) {
    // Handle validation errors
    console.error('Validation failed:', error.details);
  } else {
    // Handle other errors
    console.error('Operation failed:', error);
  }
}

Using with TypeScript

import KRAeTimsSDK from 'kra-etims-sdk';
import { SalesTransaction, ApiResponse } from 'kra-etims-sdk/types';

const sdk = new KRAeTimsSDK();

async function processSale(sale: SalesTransaction): Promise<ApiResponse> {
  try {
    await sdk.authenticate('username', 'password');
    return await sdk.sendSalesTransaction(sale);
  } catch (error) {
    console.error('Failed to process sale:', error);
    throw error;
  }
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the ISC License - see the LICENSE file for details.

Package Sidebar

Install

npm i kra-etims-sdk

Weekly Downloads

172

Version

1.1.3

License

ISC

Unpacked Size

74.4 kB

Total Files

21

Last publish

Collaborators

  • shadrack-matata