Overview

Tola is Uganda’s comprehensive financial services platform, offering mobile money, banking, and advanced payment solutions. Known for their robust infrastructure and enterprise-grade features, Tola is perfect for businesses requiring detailed reporting, reconciliation, and multiple payment channels.
Tola excels in enterprise integrations with features like transaction reconciliation, detailed reporting, multi-channel payments, and strong compliance frameworks ideal for larger businesses and financial institutions.

Coverage & Capabilities

Supported Payment Methods

Mobile Money

Networks: MTN, Airtel Currency: UGX Features: Collections, Payouts, Status

Bank Transfers

Banks: All major Ugandan banks Currency: UGX Features: Collections, Payouts, RTGS

Card Payments

Types: Visa, Mastercard Currency: UGX, USD Features: Collections, Refunds

Advanced Capabilities

  • Multi-channel Payments - Mobile money, banking, cards
  • Bulk Operations - Process thousands of transactions
  • Transaction Reconciliation - Automated settlement reports
  • Real-time Webhooks - Instant status notifications
  • Detailed Reporting - Comprehensive transaction analytics
  • Refund Processing - Full and partial refunds
  • Escrow Services - Secure transaction holding
  • Multi-currency Support - UGX and USD

Installation

npm install @fundkit/tola

Configuration

Basic Setup

import { Tola } from '@fundkit/tola';

const tola = new Tola({
  apiKey: process.env.TOLA_API_KEY!,
  merchantId: process.env.TOLA_MERCHANT_ID!,
  environment: 'sandbox', // or 'production'
});

Configuration Options

apiKey
string
required
Your Tola API key from the merchant dashboard.
merchantId
string
required
Your unique Tola merchant identifier.
environment
'sandbox' | 'production'
default:"sandbox"
Environment mode. Use sandbox for testing, production for live payments.
timeout
number
default:"45000"
Request timeout in milliseconds. Tola supports complex operations that may take longer.
retries
number
default:"3"
Number of retry attempts for failed requests.
callbackUrl
string
URL to receive transaction status callbacks and webhooks.

Advanced Configuration

const tola = new Tola({
  apiKey: process.env.TOLA_API_KEY!,
  merchantId: process.env.TOLA_MERCHANT_ID!,
  environment: 'production',

  // Extended timeouts for complex operations
  timeout: 60000,
  retries: 5,
  retryDelay: 3000,

  // Callback configuration
  callbackUrl: 'https://api.myapp.com/callbacks/tola',
  webhookUrl: 'https://api.myapp.com/webhooks/tola',
  webhookSecret: process.env.TOLA_WEBHOOK_SECRET,

  // Enterprise features
  enableReconciliation: true,
  enableDetailedReporting: true,
  enableEscrowServices: true,

  // Rate limiting (enterprise-grade)
  rateLimit: {
    requests: 500,
    window: 60000, // Per minute
    burst: 100, // Burst capacity
  },

  // Banking integration
  bankingConfig: {
    enableRTGS: true,
    enableACH: true,
    settlementAccount: process.env.TOLA_SETTLEMENT_ACCOUNT,
  },

  // Compliance and security
  enableTwoFactorAuth: true,
  enableAuditLogging: true,
  complianceLevel: 'high',

  // Monitoring
  logger: enterpriseLogger,
  logLevel: 'info',
  enableMetrics: true,
});

Getting API Credentials

Sandbox Access

Get started with Tola’s feature-rich sandbox:
1

Business Registration

Visit business.tola.ug and complete business registration
2

Account Verification

Upload business documents and complete verification process
3

Sandbox Access

Request sandbox access through the business portal
4

Get Credentials

TOLA_API_KEY=tola_test_your_api_key_here
TOLA_MERCHANT_ID=merchant_test_your_id_here

Production Onboarding

Tola’s enterprise onboarding process:
  1. Business Assessment - Detailed business review and requirements analysis 2. Compliance Review - KYC/AML verification and risk assessment (5-10 business days) 3. Technical Integration - Dedicated integration support and testing 4. Security Audit - Comprehensive security and compliance audit 5. Go-Live Certification - Final approval and production access
  2. Ongoing Support - Dedicated account management and technical support
Tola’s onboarding is more comprehensive than other providers, typically taking 2-3 weeks for full production access. This includes enhanced due diligence for enterprise features.

Usage Examples

Mobile Money Collection

import { PaymentClient } from '@fundkit/core';
import { Tola } from '@fundkit/tola';

const tola = new Tola({
  apiKey: process.env.TOLA_API_KEY!,
  merchantId: process.env.TOLA_MERCHANT_ID!,
  environment: 'sandbox',
});

const client = new PaymentClient({
  apiKey: process.env.FUNDKIT_API_KEY!,
  providers: [tola],
  environment: 'sandbox',
});

// Standard mobile money payment
const transaction = {
  amount: 50000, // 500.00 UGX
  currency: 'UGX',
  operator: 'mtn',
  accountNumber: '256779280949',
  externalId: 'invoice_12345',
  reason: 'Service subscription payment',

  // Additional Tola-specific metadata
  metadata: {
    customerName: 'John Doe',
    serviceType: 'premium_subscription',
    billingPeriod: 'monthly',
    accountType: 'business',
  },
};

try {
  const result = await client.collection(transaction);
  console.log('Payment initiated:', result);

  // Tola provides detailed response information
  console.log('Transaction details:', {
    provider: result.provider,
    transactionId: result.data.transactionId,
    reference: result.data.reference,
    estimatedCompletion: result.data.estimatedCompletion,
    fees: result.data.fees,
  });
} catch (error) {
  console.error('Payment failed:', error.message);
}

Bank Transfer Collection

// Collect payment via bank transfer
const bankTransfer = {
  amount: 1000000, // 10,000.00 UGX
  currency: 'UGX',
  paymentMethod: 'bank_transfer',
  accountNumber: 'ACC123456789', // Bank account number
  bankCode: 'STANBIC', // Bank identifier
  externalId: 'large_payment_001',
  reason: 'Large service payment',

  // Bank-specific details
  bankDetails: {
    accountName: 'ACME Corporation Ltd',
    bankName: 'Stanbic Bank Uganda',
    branchCode: 'KAMPALA001',
    swiftCode: 'SBICUGKX',
  },
};

try {
  const result = await tola.collection(bankTransfer);
  console.log('Bank transfer initiated:', result);
} catch (error) {
  console.error('Bank transfer failed:', error.message);
}

Bulk Payment Processing

// Process multiple payments efficiently
async function processBulkPayments(payments: PaymentRequest[]) {
  const bulkRequest = {
    batchId: `batch_${Date.now()}`,
    payments: payments.map((payment, index) => ({
      sequenceNumber: index + 1,
      amount: payment.amount,
      currency: payment.currency,
      operator: payment.operator,
      accountNumber: payment.accountNumber,
      externalId: payment.externalId,
      reason: payment.reason,
      metadata: payment.metadata,
    })),

    // Bulk processing options
    processingMode: 'parallel', // or 'sequential'
    failurePolicy: 'continue', // or 'stop_on_first_failure'
    notificationUrl: 'https://api.myapp.com/bulk-notifications',
  };

  try {
    const result = await tola.bulkCollection(bulkRequest);

    console.log(`Bulk batch ${result.batchId} submitted`);
    console.log(`${result.totalCount} payments queued`);
    console.log(`Estimated completion: ${result.estimatedCompletion}`);

    return result;
  } catch (error) {
    console.error('Bulk processing failed:', error.message);
    throw error;
  }
}

// Monitor bulk processing status
async function monitorBulkBatch(batchId: string) {
  const status = await tola.getBulkBatchStatus(batchId);

  console.log(`Batch ${batchId} status:`, {
    status: status.status,
    processed: status.processedCount,
    successful: status.successfulCount,
    failed: status.failedCount,
    progress: `${Math.round(status.progressPercentage)}%`,
  });

  return status;
}

Payout Operations

// Send money to recipients
const payout = {
  amount: 75000, // 750.00 UGX
  currency: 'UGX',
  operator: 'airtel',
  accountNumber: '256701234567',
  externalId: 'commission_payout_456',
  reason: 'Monthly commission payment',

  // Payout-specific details
  recipientDetails: {
    name: 'Jane Smith',
    idNumber: 'NIN1234567890',
    relationship: 'agent',
  },

  // Compliance information
  compliance: {
    payoutReason: 'commission',
    sourceOfFunds: 'business_revenue',
    beneficiaryType: 'individual',
  },
};

try {
  const result = await tola.payout(payout);
  console.log('Payout initiated:', result);
} catch (error) {
  console.error('Payout failed:', error.message);
}

Transaction Limits

Amount Limits by Payment Method

  • Minimum: 500 UGX - Maximum: 20,000,000 UGX (per transaction) - Daily limit: 50,000,000 UGX (per merchant) - Monthly limit: 500,000,000 UGX (per merchant)

Enterprise Limits

  • API Requests: 500 requests per minute (burstable to 1,000)
  • Concurrent Operations: 200 simultaneous transactions
  • Bulk Processing: 10,000 transactions per batch
  • File Upload: 100MB per reconciliation file

Advanced Features

Transaction Reconciliation

// Generate reconciliation report
async function generateReconciliationReport(date: string) {
  const report = await tola.generateReconciliation({
    date: date, // YYYY-MM-DD format
    includeTransactions: true,
    includeSettlements: true,
    includeRefunds: true,
    format: 'detailed', // 'summary' or 'detailed'
  });

  console.log('Reconciliation Report:', {
    reportId: report.reportId,
    totalTransactions: report.summary.totalTransactions,
    totalAmount: report.summary.totalAmount,
    totalFees: report.summary.totalFees,
    netSettlement: report.summary.netSettlement,
    downloadUrl: report.downloadUrl,
  });

  return report;
}

// Download reconciliation file
async function downloadReconciliationFile(reportId: string) {
  const fileBuffer = await tola.downloadReconciliationFile(reportId);

  // Save to file system
  require('fs').writeFileSync(`reconciliation_${reportId}.xlsx`, fileBuffer);

  console.log(`Reconciliation file saved: reconciliation_${reportId}.xlsx`);
}

Escrow Services

// Create escrow transaction
const escrowTransaction = {
  amount: 5000000, // 50,000.00 UGX
  currency: 'UGX',
  payerAccount: '256779280949',
  payeeAccount: '256701234567',
  externalId: 'escrow_property_sale_001',
  reason: 'Property purchase escrow',

  // Escrow-specific configuration
  escrowConfig: {
    releaseConditions: ['buyer_confirmation', 'seller_delivery'],
    timeoutDays: 30, // Auto-release after 30 days
    arbitratorId: 'arbitrator_123',
    requiresManualRelease: true,
  },

  // Milestone-based release
  milestones: [
    {
      name: 'initial_deposit',
      percentage: 20,
      condition: 'automatic',
    },
    {
      name: 'delivery_confirmation',
      percentage: 60,
      condition: 'buyer_approval',
    },
    {
      name: 'final_payment',
      percentage: 20,
      condition: 'completion_certificate',
    },
  ],
};

try {
  const escrow = await tola.createEscrow(escrowTransaction);
  console.log('Escrow created:', escrow.escrowId);
} catch (error) {
  console.error('Escrow creation failed:', error.message);
}

// Release escrow funds
async function releaseEscrowFunds(escrowId: string, milestoneId: string) {
  const release = await tola.releaseEscrow({
    escrowId: escrowId,
    milestoneId: milestoneId,
    releaseReason: 'milestone_completed',
    approverSignature: 'digital_signature_hash',
  });

  console.log('Escrow funds released:', release);
}

Detailed Analytics

// Get comprehensive analytics
async function getDetailedAnalytics(startDate: string, endDate: string) {
  const analytics = await tola.getAnalytics({
    startDate: startDate,
    endDate: endDate,
    groupBy: ['payment_method', 'currency', 'status'],
    includeHourlyBreakdown: true,
    includeGeoAnalytics: true,
    includeCustomerSegmentation: true,
  });

  console.log('Payment Analytics:', {
    overview: analytics.overview,
    trends: analytics.trends,
    performance: analytics.performance,
    segmentation: analytics.customerSegmentation,
  });

  return analytics;
}

// Real-time dashboard metrics
async function getDashboardMetrics() {
  const metrics = await tola.getDashboardMetrics();

  console.log('Real-time Metrics:', {
    transactionsToday: metrics.today.count,
    volumeToday: metrics.today.volume,
    successRate: metrics.today.successRate,
    averageAmount: metrics.today.averageAmount,
    topPaymentMethods: metrics.breakdown.paymentMethods,
    recentFailures: metrics.recent.failures,
  });

  return metrics;
}

Error Handling

Tola-Specific Errors

try {
  const result = await client.collection(transaction);
} catch (error) {
  switch (error.code) {
    case 'TOLA_INSUFFICIENT_BALANCE':
      handleInsufficientBalance(error);
      break;

    case 'TOLA_ACCOUNT_VERIFICATION_REQUIRED':
      handleAccountVerification(error);
      break;

    case 'TOLA_TRANSACTION_LIMIT_EXCEEDED':
      handleLimitExceeded(error);
      break;

    case 'TOLA_COMPLIANCE_CHECK_FAILED':
      handleComplianceIssue(error);
      break;

    case 'TOLA_SETTLEMENT_PENDING':
      handleSettlementDelay(error);
      break;

    case 'TOLA_ESCROW_CONDITION_NOT_MET':
      handleEscrowCondition(error);
      break;

    case 'TOLA_BANK_NETWORK_ERROR':
      handleBankNetworkError(error);
      break;

    default:
      handleGenericError(error);
  }
}

function handleComplianceIssue(error: TolaError) {
  console.log('Compliance check failed:', error.details);

  // Show specific compliance requirements
  if (error.details.requiredDocuments) {
    showComplianceModal(error.details.requiredDocuments);
  }

  // Provide guidance for resolution
  showComplianceGuidance(error.details.complianceLevel);
}

function handleEscrowCondition(error: TolaError) {
  console.log('Escrow condition not met:', error.details);

  // Show missing conditions
  const missingConditions = error.details.missingConditions;
  showEscrowStatus(error.details.escrowId, missingConditions);

  // Provide action items
  showEscrowActionItems(missingConditions);
}

Webhooks & Callbacks

Enterprise Webhook Configuration

const tola = new Tola({
  apiKey: process.env.TOLA_API_KEY!,
  merchantId: process.env.TOLA_MERCHANT_ID!,

  // Multiple webhook endpoints
  webhookEndpoints: [
    {
      url: 'https://api.myapp.com/webhooks/tola/payments',
      events: ['payment.completed', 'payment.failed'],
      secret: process.env.TOLA_PAYMENT_WEBHOOK_SECRET,
    },
    {
      url: 'https://api.myapp.com/webhooks/tola/reconciliation',
      events: ['reconciliation.ready', 'settlement.completed'],
      secret: process.env.TOLA_RECON_WEBHOOK_SECRET,
    },
    {
      url: 'https://api.myapp.com/webhooks/tola/escrow',
      events: ['escrow.created', 'escrow.released', 'escrow.disputed'],
      secret: process.env.TOLA_ESCROW_WEBHOOK_SECRET,
    },
  ],
});

Comprehensive Webhook Handler

import express from 'express';
import { verifyTolaWebhook } from '@fundkit/tola';

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/tola/:endpoint', async (req, res) => {
  const endpoint = req.params.endpoint;
  const signature = req.headers['x-tola-signature'];
  const timestamp = req.headers['x-tola-timestamp'];
  const payload = req.body;

  // Verify webhook authenticity
  const isValid = verifyTolaWebhook(payload, signature, timestamp, getWebhookSecret(endpoint));

  if (!isValid) {
    return res.status(400).json({ error: 'Invalid webhook signature' });
  }

  const event = JSON.parse(payload.toString());

  try {
    await processTolaWebhook(endpoint, event);
    res.status(200).json({ received: true, processed: true });
  } catch (error) {
    console.error('Webhook processing failed:', error);
    res.status(500).json({ error: 'Processing failed' });
  }
});

async function processTolaWebhook(endpoint: string, event: any) {
  switch (`${endpoint}.${event.type}`) {
    case 'payments.payment.completed':
      await handlePaymentCompleted(event.data);
      break;

    case 'reconciliation.reconciliation.ready':
      await handleReconciliationReady(event.data);
      break;

    case 'escrow.escrow.released':
      await handleEscrowReleased(event.data);
      break;

    case 'payments.bulk.batch.completed':
      await handleBulkBatchCompleted(event.data);
      break;

    default:
      console.log('Unhandled webhook event:', endpoint, event.type);
  }
}

Best Practices

Enterprise Security

// Good: Enable all security features
const tola = new Tola({
  apiKey: process.env.TOLA_API_KEY!,
  merchantId: process.env.TOLA_MERCHANT_ID!,
  enableTwoFactorAuth: true,
  enableAuditLogging: true,
  complianceLevel: 'high'
});

Use Reconciliation

// Good: Regular reconciliation
setInterval(async () => {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  
  await generateReconciliationReport(
    yesterday.toISOString().split('T')[0]
  );
}, 24 * 60 * 60 * 1000); // Daily

Monitor Performance

// Good: Real-time monitoring
const metrics = await tola.getDashboardMetrics();

if (metrics.today.successRate < 0.95) {
  await alertOpsTeam('Low success rate detected');
}

Detailed Metadata

// Good: Rich transaction metadata
const transaction = {
  // ... basic fields
  metadata: {
    customerSegment: 'enterprise',
    salesRep: 'john.doe',
    contractId: 'CONTRACT_2024_001',
    departmentCode: 'SALES_UG'
  }
};

Support & Resources

Next Steps