UNPKG

kra-etims-sdk

Version:

Kenya Revenue Authority (KRA) Electronic Tax Invoice Management System (eTims) API Integration SDK

984 lines (797 loc) 23 kB
# KRA eTims SDK A comprehensive Node.js SDK for integrating with the Kenya Revenue Authority (KRA) Electronic Tax Invoice Management System (eTims). [![npm version](https://img.shields.io/npm/v/kra-etims-sdk.svg)](https://www.npmjs.com/package/kra-etims-sdk) [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/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 ```bash npm install kra-etims-sdk ``` ## Quick Start ### Basic Usage ```javascript 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: ```javascript 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: ```javascript // 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: ```javascript 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: ```javascript 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 ```bash # For development (sandbox) NODE_ENV=development npm start # For production NODE_ENV=production npm start ``` #### Method 2: Setting NODE_ENV in Your Code ```javascript // 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: ```javascript 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:** ```json { "username": "your_username", "password": "your_password" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "dvcSrlNo": "MOVA22" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "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:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101", "invcNo": "INV001" // Optional } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "itemCd": "ITEM001", "itemClsCd": "FOOD", "itemNm": "Test Item", "pkgUnitCd": "EA", "qtyUnitCd": "EA", "splyAmt": 100, "vatTyCd": "V" } ``` **Express Integration Example:** ```javascript 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:** ```json { "tin": "P000000045R", "bhfId": "00", "lastReqDt": "20220101010101" } ``` **Express Integration Example:** ```javascript 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: ```javascript // 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 ```javascript 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 ```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.