advanced-ussd-builder
Version:
Advanced USSD Menu Builder with persistent state and navigation
1,144 lines (965 loc) • 31.7 kB
Markdown
# Advanced USSD Builder
<div align="center">




**A powerful TypeScript library for building interactive USSD menus with persistent state management**
[Installation](#installation) • [Quick Start](#quick-start) • [Documentation](#core-concepts) • [Examples](#usage-examples) • [API](#api-reference)
</div>
## 📋 Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Provider Support](#provider-support)
- [Core Concepts](#core-concepts)
- [UssdMenu](#ussdmenu)
- [UssdBuilder](#ussdbuilder)
- [Session Management](#session-management)
- [Usage Examples](#usage-examples)
- [Render Menu](#render-menu-display-options)
- [Render Menu with Function Handler](#render-menu-with-function-handler)
- [Basic Menu](#basic-menu)
- [Dynamic Menus](#dynamic-menus)
- [Proxy Menu](#proxy-menu-external-service-integration)
- [Handler Menus](#handler-menus)
- [Conditional Menu Rendering](#conditional-menu-rendering)
- [API Reference](#api-reference)
- [Provider Integration](#provider-integration)
- [MTN](#mtn)
- [Nalo](#nalo)
- [Telecel](#telecel)
- [Airtel-Tigo](#airtel-tigo)
- [Cross-Switch](#cross-switch)
- [Custom Provider](#custom-provider)
- [Advanced Features](#advanced-features)
- [Session State Management](#session-state-management)
- [Middleware Support](#middleware-support)
- [Authentication Handler](#authentication-handler)
- [Proxy Service Integration](#proxy-service-integration)
- [Proxy Configuration Options](#proxy-configuration-options)
- [Request Formats](#request-formats)
- [XML Payload Support](#xml-payload-support)
- [Backend Implementation Examples](#backend-implementation-examples)
- [Use Cases](#use-cases)
- [Path-based Handler Modules](#path-based-handler-modules)
- [License](#license)
## ✨ Features
- 🚀 **Multi-Provider Support** - Works with MTN, Nalo, Telecel, Airtel-Tigo, Cross-Switch, and Custom providers
- 🔐 **Built-in Authentication** - PIN-based authentication with customizable handlers
- 🔌 **Middleware Support** - Request/response interceptors for logging, validation, and transformation
- 🎨 **Custom Provider Setup** - Easy integration with any USSD gateway through flexible configuration
- 🔀 **Advanced Proxy Support** - Forward requests to external USSD services with automatic network detection, flexible request formats (telco-specific or normalized), and custom transformations
- 💾 **Persistent State Management** - Redis-powered session management for stateful interactions
- 📱 **Dynamic Menu Generation** - Create menus programmatically with render and handler functions
- 🔄 **Navigation History** - Track and navigate through menu paths seamlessly
- 🎯 **TypeScript Support** - Full type definitions for better development experience
- ⚡ **High Performance** - Optimized for speed with minified production builds
- 🔧 **Flexible Configuration** - Customizable session prefixes, navigation keys, and more
## 📦 Installation
```bash
npm install advanced-ussd-builder
```
or using yarn:
```bash
yarn add advanced-ussd-builder
```
### Prerequisites
- Node.js >= 14.x
- Redis server (for session management)
- TypeScript (for development)
## 🚀 Quick Start
```typescript
import { UssdBuilder, UssdMenu, iUssdMenu, iUssdMenuOptions, iMTNUSSDArgs } from 'advanced-ussd-builder';
// Define menu configuration
const menuOptions: iUssdMenuOptions = {
provider: 'mtn',
require_pin: false,
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
redis_connection_url: 'redis://localhost:6379',
state: {
user_id: '',
session_data: {}
}
};
// Create handler menus with path-based handlers
const registrationMenu: iUssdMenu = {
title: 'Register',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'root_menu',
handler: path.join(__dirname, '/handlers/registration.js')
};
// Create handler menus with function handlers
const balanceMenu: iUssdMenu = {
title: 'Check Balance',
type: 'handler',
handler_type: 'function',
handler: async (args) => {
const balance = await getUserBalance(args.msisdn);
return `Your balance is $${balance}`;
}
};
// Create menu array
const menus = [registrationMenu, balanceMenu];
// Initialize USSD Builder with root menu
const ussdBuilder = new UssdBuilder(
menuOptions,
new UssdMenu('Welcome to MyApp', menus)
);
// Process USSD request (Express.js example)
app.post('/ussd/mtn', async (req, res) => {
const ussdArgs = req.body as iMTNUSSDArgs;
const sendResponse = (response) => {
return res.json(response);
};
await ussdBuilder.pipe<'mtn'>(ussdArgs, sendResponse);
});
```
## 🌐 Provider Support
Advanced USSD Builder supports multiple telecom providers out of the box:
| Provider | Status | Request Interface | Response Interface |
|----------|--------|------------------|-------------------|
| MTN | ✅ Supported | `iMTNUSSDArgs` | `iMTNUssdCallback` |
| Nalo | ✅ Supported | `iNaloUSSDArgs` | `iNaloUssdCallback` |
| Telecel | ✅ Supported | `iTelecelUssdArgs` | `iTelecelUssdCallBack` |
| Airtel-Tigo | ✅ Supported | `iAirtelTigoUssdArgs` | `iAirtelTigoUssdCallBack` |
| Cross-Switch | ✅ Supported | `iCrossSwitchUssdArgs` | `iCrossSwitchUssdCallBack` |
| Custom | ✅ Supported | Custom Interface | Custom Response |
## 📚 Core Concepts
### UssdMenu
The `UssdMenu` class represents a menu container that can hold multiple menu items. Each menu item follows the `iUssdMenu` interface structure.
```typescript
// Create a root menu with multiple options
const menuItems: iUssdMenu[] = [
{
title: 'Option 1',
type: 'handler',
handler_type: 'function',
handler: async (args) => {
return 'Response for option 1';
}
},
{
title: 'Option 2',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'default',
handler: './handlers/option2.js'
}
];
const rootMenu = new UssdMenu('Select an option:', menuItems);
```
### UssdBuilder
The main orchestrator that manages menus, sessions, and request processing. It requires both configuration options and a root menu.
```typescript
const options: iUssdMenuOptions = {
provider: 'mtn', // or 'nalo', 'telecel', 'airtel-tigo', 'cross-switch', 'custom'
require_pin: false,
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
redis_connection_url: 'redis://localhost:6379',
state: {}, // Custom state object
middleware: async (args) => {
// Optional middleware
},
authentication_handler: async (args) => {
// Optional authentication
return true;
}
};
const builder = new UssdBuilder(options, rootMenu);
```
### Session Management
Sessions are automatically managed using Redis with the following structure:
- **Session Key**: `{prefix}:{session_id}`
- **Session Data**: Stores navigation path, current menu, and custom data
- **TTL**: Configurable time-to-live for session expiration
## 💡 Usage Examples
### Render Menu (Display Options)
```typescript
// Render menu displays a list of options to the user
const renderMenus: iUssdMenu[] = [
{
title: 'Account Services',
type: 'render', // This will display as an option
render: {
success_message: 'Select an option:',
error_message: 'Invalid selection'
}
},
{
title: 'Transfer Money',
type: 'render',
render: {
success_message: 'Choose transfer type:'
}
},
{
title: 'Buy Airtime',
type: 'render',
render: {
success_message: 'Select amount:'
}
}
];
const ussdBuilder = new UssdBuilder(
options,
new UssdMenu('Main Menu\n1. Account Services\n2. Transfer Money\n3. Buy Airtime', renderMenus)
);
```
### Render Menu with Function Handler
```typescript
// When type is 'render' with a handler, the handler controls the response
const dynamicRenderMenu: iUssdMenu = {
title: 'Account List',
type: 'render',
handler_type: 'function',
handler: async (args) => {
// Dynamically generate menu options
const accounts = await getUserAccounts(args.msisdn);
const menuOptions = accounts.map((acc, idx) =>
`${idx + 1}. ${acc.name} - ${acc.balance}`
).join('\n');
// Handler returns the complete response
return {
message: `Your Accounts:\n${menuOptions}\n\nSelect account:`,
continue_session: true
};
}
// No render object needed when handler is provided
};
```
### Basic Menu
```typescript
const bankingMenus: iUssdMenu[] = [
{
title: 'Check Balance',
type: 'handler',
handler_type: 'function',
handler: async (args) => {
const balance = await getBalance(args.msisdn);
return `Your balance: $${balance}`;
}
},
{
title: 'Transfer Funds',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'root_menu',
handler: './handlers/transfer.js'
},
{
title: 'Mini Statement',
type: 'handler',
handler_type: 'function',
handler: async (args) => {
const statement = await getStatement(args.msisdn);
return statement;
}
}
];
const ussdBuilder = new UssdBuilder(
options,
new UssdMenu('Welcome to Banking Services', bankingMenus)
);
```
### Dynamic Menus
```typescript
// Dynamically create menus based on user data
const createDynamicMenu = async (userPhone: string) => {
const user = await getUserData(userPhone);
const menuItems: iUssdMenu[] = [];
// Add different menus based on user status
if (user.isRegistered) {
menuItems.push({
title: 'My Account',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'root_menu',
handler: './handlers/account.js'
});
} else {
menuItems.push({
title: 'Register',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'root_menu',
handler: './handlers/registration.js'
});
}
return new UssdMenu('Welcome', menuItems);
};
// Use in request handler
app.post('/ussd', async (req, res) => {
const rootMenu = await createDynamicMenu(req.body.msisdn);
const builder = new UssdBuilder(options, rootMenu);
await builder.pipe<'mtn'>(req.body, (response) => res.json(response));
});
```
### Proxy Menu (External Service Integration)
**New in v2.4+**: Enhanced proxy support with automatic network detection and flexible request formats.
#### Basic Proxy Configuration
```typescript
// Simple proxy to external service
const basicProxy: iUssdMenu = {
title: 'Loan Services',
type: 'proxy',
proxy_config: {
target_url: 'https://loan-service.example.com/ussd',
timeout: 25000,
headers: {
'API-Key': process.env.LOAN_API_KEY
}
}
};
```
#### Telco-Specific Endpoints (Default Behavior)
```typescript
// Forward requests in original telco format to telco-specific endpoints
const telcoSpecificProxy: iUssdMenu = {
title: 'Payment Service',
type: 'proxy',
proxy_config: {
target_url: 'https://payment.example.com/v1/ussd/mtn', // MTN-specific endpoint
// forward_original_format: true (default)
// Forwards MTN request format directly to the endpoint
}
};
```
#### Unified Endpoint with Network Detection
```typescript
// Use single endpoint for all networks with automatic network detection
const unifiedProxy: iUssdMenu = {
title: 'Unified Service',
type: 'proxy',
proxy_config: {
target_url: 'https://api.example.com/ussd', // Single endpoint for all networks
forward_original_format: false, // Use normalized format
// Network is automatically detected from phone number
}
};
// The proxy sends normalized request with network field:
// {
// msisdn: '0241234567',
// session_id: 'session-123',
// user_input: '1',
// service_code: '*123#',
// is_new_request: false,
// provider: 'mtn',
// network: 'MTN', // Auto-detected from phone number prefix
// session_data: {...},
// original_request: {...}
// }
```
#### Advanced Proxy with Transformations
```typescript
const advancedProxy: iUssdMenu = {
title: 'Insurance Portal',
type: 'proxy',
proxy_config: {
target_url: 'https://insurance.example.com/ussd',
forward_original_format: false,
timeout: 20000,
retry_attempts: 2,
retry_delay: 1500,
session_bridge: true, // Share session state
headers: {
'Authorization': `Bearer ${process.env.TOKEN}`,
'X-Partner-ID': 'partner-123'
},
// Custom request transformation
transform_request: (data) => ({
phone: data.msisdn,
network: data.network, // Includes auto-detected network
session: data.session_id,
input: data.user_input,
metadata: {
provider: data.provider,
timestamp: Date.now()
}
}),
// Custom response transformation
transform_response: (response) => ({
message: response.text,
require_feedback: response.continue
})
}
};
```
#### Custom Proxy Handler
```typescript
// Use custom function instead of HTTP request
const customProxy: iUssdMenu = {
title: 'Custom Service',
type: 'proxy',
proxy_config: {
proxy_handler: async (builder, user_input) => {
// Custom logic here
const result = await customServiceCall(builder.session, user_input);
return {
message: result.display,
require_feedback: result.needsInput
};
}
}
};
```
#### Network Detection
The library automatically detects the network from Ghana phone numbers:
- **MTN**: 024, 054, 055, 059, 025
- **Telecel**: 020, 050
- **AirtelTigo**: 026, 056, 027, 057, 028
```typescript
// Network detection works with various formats
getNetworkFromPhoneNumber('0241234567'); // Returns: 'MTN'
getNetworkFromPhoneNumber('233241234567'); // Returns: 'MTN'
getNetworkFromPhoneNumber('+233241234567'); // Returns: 'MTN'
getNetworkFromPhoneNumber('0201234567'); // Returns: 'TELECEL'
getNetworkFromPhoneNumber('0561234567'); // Returns: 'AIRTEL-TIGO'
```
// Combine local and proxy menus
const mainMenus: iUssdMenu[] = [
{
title: 'My Account',
type: 'handler',
handler_type: 'path',
handler: './handlers/account.js'
},
...proxyMenus // External services appear as regular menu items
];
const ussdBuilder = new UssdBuilder(
options,
new UssdMenu('Select Service:', mainMenus)
);
```
### Handler Menus
```typescript
// Simple handler
const balanceHandler = new UssdMenu('check_balance', async (args) => {
const balance = await getBalance(args.msisdn);
return `Your balance: $${balance}\n\nPress 0 to go back`;
});
// Handler with module path
const complexHandler = new UssdMenu('process_transfer', './handlers/transfer.js');
builder.register_menu(balanceHandler);
builder.register_menu(complexHandler);
```
### Paginated Menus
Pagination is automatic when menu items exceed the configured limit:
```typescript
const builder = new UssdBuilder({
redis_url: 'redis://localhost:6379',
max_menu_items: 5 // Show 5 items per page
});
const largeMenu = new UssdMenu('products', 'Select Product');
// Adding more than 5 items will trigger pagination
for (let i = 1; i <= 20; i++) {
largeMenu.add_child(`product_${i}`, `Product ${i}`);
}
```
## 📖 API Reference
### UssdBuilder
#### Constructor Options
```typescript
interface UssdBuilderOptions {
redis_url: string; // Redis connection URL
session_prefix?: string; // Session key prefix for multi-instance isolation (default: 'ussd')
session_ttl?: number; // Session TTL in seconds (default: 120 seconds / 2 minutes)
max_menu_items?: number; // Max items per page (default: 5)
end_session_text?: string; // Custom end session message
}
```
#### Methods
| Method | Description | Parameters | Returns |
|--------|-------------|------------|---------|
| `pipe<T>(args, callback)` | Process USSD request | `args`: provider args, `callback`: response handler | Promise<void> |
| `getSession()` | Get current session | - | iUssdSession |
| `saveSession()` | Save session to Redis | - | Promise<void> |
| `endSession()` | End current session | - | Promise<void> |
### UssdMenu
#### Constructor
```typescript
new UssdMenu(
id: string,
title: string | Function | undefined,
opts?: {
custom_input?: boolean;
module_path?: string;
}
)
```
#### Menu Item Types
| Property | Description | Type |
|----------|-------------|------|
| `title` | Menu item display text | string |
| `type` | Menu type | 'render' \| 'handler' \| 'proxy' |
| `handler_type` | Handler type (for handler menus) | 'path' \| 'function' |
| `handler` | Handler implementation | string \| Function |
| `handler_name_in_destination` | Exported function name in module | string |
| `proxy_config` | Proxy configuration (for proxy menus) | ProxyConfig object |
## 🔌 Provider Integration
### MTN
```typescript
app.post('/ussd/mtn', async (req, res) => {
const ussdArgs = req.body as iMTNUSSDArgs;
// Response callback
const sendResponse = <iMTNUssdCallback>(response: iMTNUssdCallback) => {
return res.json(response);
};
// Configure and build menu
const options: iUssdMenuOptions = {
provider: 'mtn',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL,
state: { /* custom state */ }
};
const menus: iUssdMenu[] = [/* your menus */];
const ussdBuilder = new UssdBuilder(
options,
new UssdMenu('Welcome', menus)
);
await ussdBuilder.pipe<'mtn'>(ussdArgs, sendResponse);
});
```
### Nalo
```typescript
app.post('/ussd/nalo', async (req, res) => {
const ussdArgs = req.body as iNaloUSSDArgs;
const sendResponse = <iNaloUssdCallback>(response: iNaloUssdCallback) => {
return res.json(response);
};
const options: iUssdMenuOptions = {
provider: 'nalo',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL
};
const ussdBuilder = new UssdBuilder(options, rootMenu);
await ussdBuilder.pipe<'nalo'>(ussdArgs, sendResponse);
});
```
### Telecel
```typescript
app.post('/ussd/telecel', async (req, res) => {
const ussdArgs = req.body as iTelecelUssdArgs;
const sendResponse = <iTelecelUssdCallBack>(response: iTelecelUssdCallBack) => {
return res.json(response);
};
const options: iUssdMenuOptions = {
provider: 'telecel',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL
};
const ussdBuilder = new UssdBuilder(options, rootMenu);
await ussdBuilder.pipe<'telecel'>(ussdArgs, sendResponse);
});
```
### Airtel-Tigo
```typescript
app.post('/ussd/at', async (req, res) => {
const ussdArgs = req.body as iAirtelTigoUssdArgs;
const sendResponse = <iAirtelTigoUssdCallBack>(response: iAirtelTigoUssdCallBack) => {
return res.json(response);
};
const options: iUssdMenuOptions = {
provider: 'airtel-tigo',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL
};
const ussdBuilder = new UssdBuilder(options, rootMenu);
await ussdBuilder.pipe<'airtel-tigo'>(ussdArgs, sendResponse);
});
```
### Cross-Switch
```typescript
app.post('/ussd/cross-switch', async (req, res) => {
const ussdArgs = req.body as iCrossSwitchUssdArgs;
const sendResponse = <iCrossSwitchUssdCallBack>(response: iCrossSwitchUssdCallBack) => {
return res.json(response);
};
const options: iUssdMenuOptions = {
provider: 'cross-switch',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL
};
const ussdBuilder = new UssdBuilder(options, rootMenu);
await ussdBuilder.pipe<'cross-switch'>(ussdArgs, sendResponse);
});
```
### Custom Provider
The library supports custom providers for any USSD gateway not listed above:
```typescript
// Custom provider implementation
app.post('/ussd/custom', async (req, res) => {
const customArgs = {
msisdn: req.body.phoneNumber,
session_id: req.body.sessionId,
msg: req.body.userInput,
// Map your custom fields
};
const sendResponse = (response) => {
// Transform response to your custom format
const customResponse = {
text: response.message || response.msg,
continueSession: response.msgtype !== false,
sessionId: response.session_id
};
return res.json(customResponse);
};
const options: iUssdMenuOptions = {
provider: 'custom',
service_code: req.body.serviceCode,
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL,
make_provider_response: false, // Handle response formatting manually
state: {}
};
const menus: iUssdMenu[] = [/* your menus */];
const ussdBuilder = new UssdBuilder(
options,
new UssdMenu('Welcome', menus)
);
await ussdBuilder.pipe<'custom'>(customArgs, sendResponse);
});
```
## 🚀 Advanced Features
### Session State Management
```typescript
// Pass state through options that persists across menus
const options: iUssdMenuOptions = {
provider: 'mtn',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL,
state: {
user_id: '12345',
user_name: 'John Doe',
account_type: 'premium',
// Any custom data you need
}
};
// Access state in handlers
const menuHandler: iUssdMenu = {
title: 'Account Info',
type: 'handler',
handler_type: 'function',
handler: async (args) => {
// Access state passed through options
const userId = args.state?.user_id;
const userName = args.state?.user_name;
return `Welcome ${userName}, your ID is ${userId}`;
}
};
```
### Middleware Support
```typescript
const options: iUssdMenuOptions = {
provider: 'mtn',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: false,
redis_connection_url: REDIS_URL,
middleware: async (args) => {
// Log every request
console.log('USSD Request:', args);
// Validate user
const user = await validateUser(args.msisdn);
if (!user) {
throw new Error('Unauthorized');
}
// Add user to args
args.user = user;
}
};
```
### Authentication Handler
```typescript
const options: iUssdMenuOptions = {
provider: 'mtn',
service_code: '*123#',
next_page_key: '#',
back_page_key: '0',
require_pin: true, // Enable PIN requirement
redis_connection_url: REDIS_URL,
authentication_handler: async (args) => {
// Custom authentication logic
const isValid = await validateUserPin(args.msisdn, args.input);
return isValid;
}
};
```
### Proxy Service Integration
#### Proxy Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `target_url` | string | - | URL of the target USSD service (required unless using proxy_handler) |
| `forward_original_format` | boolean | true | Forward telco-specific format or use normalized format |
| `timeout` | number | 25000 | Request timeout in milliseconds |
| `retry_attempts` | number | 1 | Number of retry attempts for failed requests |
| `retry_delay` | number | 1000 | Delay between retries in milliseconds |
| `session_bridge` | boolean | true | Share session state with target service |
| `headers` | object | {} | Additional HTTP headers to send |
| `transform_request` | function | - | Custom request transformation function |
| `transform_response` | function | - | Custom response transformation function |
| `proxy_handler` | function | - | Custom handler function instead of HTTP request |
#### Request Formats
##### 1. Original Telco Format (default)
```typescript
// Forwards the exact format from the telco
const telcoProxy: iUssdMenu = {
title: 'Service',
type: 'proxy',
proxy_config: {
target_url: 'https://api.example.com/v1/ussd/mtn',
forward_original_format: true // default
}
};
// MTN format example:
// {
// sessionId: 'session-123',
// messageType: '1',
// msisdn: '233241234567',
// ussdString: '1',
// serviceCode: '*123#',
// cellId: 'cell-id',
// imsi: 'imsi-id',
// ...
// }
```
##### 2. Normalized Format with Network Detection
```typescript
// Sends normalized format with auto-detected network
const normalizedProxy: iUssdMenu = {
title: 'Service',
type: 'proxy',
proxy_config: {
target_url: 'https://api.example.com/ussd', // Single endpoint
forward_original_format: false
}
};
// Normalized format:
// {
// msisdn: '0241234567',
// session_id: 'session-123',
// user_input: '1',
// service_code: '*123#',
// is_new_request: false,
// provider: 'mtn',
// network: 'MTN', // Auto-detected from phone number
// session_data: {...},
// original_request: {...} // Original telco request
// }
```
#### Backend Implementation Examples
##### Single Endpoint with Network Routing
```javascript
// Express.js example
app.post('/ussd', (req, res) => {
const { network, msisdn, user_input, session_id } = req.body;
// Route based on detected network
switch(network) {
case 'MTN':
return handleMTN(req, res);
case 'TELECEL':
return handleTelecel(req, res);
case 'AIRTEL-TIGO':
return handleAirtelTigo(req, res);
default:
return handleUnknown(req, res);
}
});
```
##### Separate Telco Endpoints
```javascript
// MTN endpoint - receives MTN format
app.post('/v1/ussd/mtn', (req, res) => {
const { sessionId, msisdn, ussdString, messageType } = req.body;
// Handle MTN-specific format
});
// Telecel endpoint - receives Telecel format
app.post('/v1/ussd/telecel', (req, res) => {
const { sessionid, msisdn, msg, type } = req.body;
// Handle Telecel-specific format
});
```
#### XML Payload Support
The proxy handler automatically preserves XML payloads when forwarding requests to target endpoints. This is particularly important for Telecel and Airtel-Tigo providers which use XML formats.
##### XML Preservation Features
- **Automatic Detection**: Content-type headers are automatically detected and preserved
- **Raw Body Forwarding**: XML bodies are forwarded as-is without conversion to JSON
- **Provider Support**: Full support for Telecel and Airtel-Tigo XML formats
- **Response Parsing**: XML responses are properly parsed and converted to the expected format
##### Example: XML Provider Proxy
```typescript
// Telecel XML format proxy
const telecelProxy: iUssdMenu = {
title: 'Telecel Service',
type: 'proxy',
proxy_config: {
target_url: 'https://api.example.com/v1/ussd/telecel',
forward_original_format: true // Preserves XML format
}
};
// When receiving Telecel XML:
// <request>
// <msisdn>233241234567</msisdn>
// <sessionid>session-123</sessionid>
// <type>1</type>
// <msg>1</msg>
// </request>
// The XML is forwarded as-is to the target endpoint
```
##### Example: Airtel-Tigo XML Support
```typescript
// Airtel-Tigo XML format proxy
const airtelTigoProxy: iUssdMenu = {
title: 'Airtel-Tigo Service',
type: 'proxy',
proxy_config: {
target_url: 'https://api.example.com/v1/ussd/airtel-tigo',
forward_original_format: true // Preserves XML format
}
};
// Airtel-Tigo XML with nested structure:
// <dataSet>
// <param>
// <name>sessionId</name>
// <value>session-123</value>
// </param>
// <param>
// <name>msisdn</name>
// <value>233241234567</value>
// </param>
// </dataSet>
```
##### Backend Handling for XML
```javascript
// Express.js with XML body parser
const xmlParser = require('express-xml-bodyparser');
app.use(xmlParser());
app.post('/v1/ussd/telecel', (req, res) => {
// req.body contains parsed XML
const { msisdn, sessionid, msg, type } = req.body.request;
// Process and return XML response
res.set('Content-Type', 'text/xml');
res.send(`
<response>
<message>Response message</message>
<type>1</type>
</response>
`);
});
```
#### Use Cases
- **Third-party Integration**: Connect to external USSD services (loans, insurance, payments)
- **Microservices Architecture**: Route to different backend services
- **Partner Applications**: Seamlessly integrate partner USSD applications
- **Load Balancing**: Distribute requests across multiple backends
- **Network Portability**: Handle users who port numbers between networks
- **Testing**: Easy testing with different network configurations
- **XML Service Integration**: Seamlessly integrate with XML-based USSD services
```typescript
// Complete example with all features
const advancedProxyMenu: iUssdMenu = {
title: 'Payment Gateway',
type: 'proxy',
proxy_config: {
target_url: process.env.PAYMENT_API_URL,
forward_original_format: false, // Use normalized format
timeout: 25000,
retry_attempts: 2,
retry_delay: 1000,
session_bridge: true,
headers: {
'Authorization': `Bearer ${process.env.AUTH_TOKEN}`,
'X-Partner-ID': 'partner-123'
},
transform_request: (data) => ({
phoneNumber: data.msisdn,
network: data.network, // Includes auto-detected network
sessionId: data.session_id,
userInput: data.user_input,
metadata: {
provider: data.provider,
detectedNetwork: data.network,
timestamp: Date.now()
}
}),
transform_response: (response) => ({
message: response.display_text,
require_feedback: response.await_input === true,
session_data: response.session_state
})
}
};
```
### Path-based Handler Modules
```typescript
// menu-handler.js - External handler module
export const root_menu = async (args) => {
// Handler logic
const userData = await fetchUserData(args.msisdn);
return `Welcome ${userData.name}`;
};
export const process_payment = async (args) => {
// Another handler in same module
return 'Payment processed';
};
// Usage in menu definition
const menus: iUssdMenu[] = [
{
title: 'User Info',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'root_menu', // Function to call
handler: path.join(__dirname, '/handlers/menu-handler.js')
},
{
title: 'Process Payment',
type: 'handler',
handler_type: 'path',
handler_name_in_destination: 'process_payment', // Different function
handler: path.join(__dirname, '/handlers/menu-handler.js')
}
];
```
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 👤 Author
### Mohammed Bashiru
- GitHub: [ ](https://github.com/MohammedBashiru/advanced-ussd-builder)
## 🙏 Acknowledgments
- Redis for reliable session management
- TypeScript for type safety
- Jest for testing framework
- All contributors and users of this library
## 📞 Support
- 🌐 Website: [www.bashtech.solutions](https://www.bashtech.solutions)
- 📧 Email: <info@bashtech.solutions>
- 🐛 Issues: [GitHub Issues](https://github.com/MohammedBashiru/advanced-ussd-builder/issues)
- 💬 Discussions: [GitHub Discussions](https://github.com/MohammedBashiru/advanced-ussd-builder/discussions)
---
Made with ❤️ by the BashTech Solutions Team