Testing guide
This guide covers best practices for testing your lomi. integration, ensuring your payment flows work correctly in both sandbox and production environments.
Test environment setup
1. Test API key
Always use your Test API Key (lomi_sk_test_...
) for development and testing. Obtain this key from your lomi. Dashboard under Developers -> API Keys. Configure your application to use this key, typically via an environment variable.
import { LomiSDK } from '@lomi/sdk'; // Your SDK package
const lomi = new LomiSDK({
apiKey: process.env.LOMI_TEST_API_KEY, // Use the TEST key from environment
baseUrl: 'https://sandbox.api.lomi.africa/v1' // Point to the sandbox URL
});
2. Test webhook secret
When testing webhooks locally or in a staging environment, create a separate webhook endpoint configuration in the lomi. Dashboard pointing to your test URL (e.g., an ngrok
URL). Use the unique Signing Secret (whsec_...
) generated for that specific test endpoint in your test environment configuration (LOMI_TEST_WEBHOOK_SECRET
).
3. Test configuration file
Consider centralizing test-specific configurations.
// test/config.ts
export const testConfig = {
merchantId: process.env.TEST_MERCHANT_ID, // Your specific Test Merchant ID
allowedProviders: ['WAVE', 'ORANGE'], // Providers enabled in your test account
testWebhookUrl: process.env.TEST_WEBHOOK_URL, // Your ngrok or test server URL
webhookSecret: process.env.LOMI_TEST_WEBHOOK_SECRET,
defaultAmount: 100, // Smallest valid amount for testing
defaultCurrency: 'XOF'
};
Integration tests
Write automated tests that interact with the lomi. sandbox API.
1. Payment flow tests
Test creating and retrieving core objects like Checkout Sessions or Payment Links.
import { LomiSDK } from '@lomi/sdk';
import { testConfig } from '../config'; // Your test config
describe('Checkout Session Flow', () => {
let lomi: LomiSDK;
beforeAll(() => {
lomi = new LomiSDK({ apiKey: process.env.LOMI_TEST_API_KEY!, baseUrl: 'https://sandbox.api.lomi.africa/v1' });
});
it('should create a checkout session successfully', async () => {
const sessionResponse = await lomi.checkoutSessions.create({
merchant_id: testConfig.merchantId!,
amount: testConfig.defaultAmount,
currency_code: testConfig.defaultCurrency,
allowed_providers: testConfig.allowedProviders,
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
metadata: { test_order_id: `int_${Date.now()}` }
});
expect(sessionResponse.data.checkout_session_id).toMatch(/^cs_test_/);
expect(sessionResponse.data.status).toBe('pending');
expect(sessionResponse.data.url).toContain('sandbox.checkout.lomi.africa');
});
it('should retrieve an existing checkout session', async () => {
// Assume cs_test_known exists from a previous step or setup
const knownSessionId = 'cs_test_xxxxxxxxxxxx';
const session = await lomi.checkoutSessions.get(knownSessionId);
expect(session.data.checkout_session_id).toEqual(knownSessionId);
// Status could be pending, completed, expired, etc.
});
});
2. Webhook handler tests
Test your webhook signature verification and event processing logic locally, without hitting the live lomi. API.
import crypto from 'crypto';
// Import your verifySignature function
import { verifySignature } from '../../src/utils/security';
describe('Webhook Signature Verification', () => {
const testSecret = 'whsec_test_secret_string';
const testPayload = JSON.stringify({ id: 'evt_test', event: 'PAYMENT_SUCCEEDED', data: {} });
const payloadBuffer = Buffer.from(testPayload, 'utf8');
it('should return true for a valid signature', () => {
const expectedSignature = crypto
.createHmac('sha256', testSecret)
.update(payloadBuffer)
.digest('hex');
const isValid = verifySignature(payloadBuffer, expectedSignature, testSecret);
expect(isValid).toBe(true);
});
it('should return false for an invalid signature', () => {
const invalidSignature = 'invalid_signature_string';
const isValid = verifySignature(payloadBuffer, invalidSignature, testSecret);
expect(isValid).toBe(false);
});
it('should return false if the secret is wrong', () => {
const expectedSignature = crypto
.createHmac('sha256', testSecret)
.update(payloadBuffer)
.digest('hex');
const wrongSecret = 'whsec_wrong_secret';
const isValid = verifySignature(payloadBuffer, expectedSignature, wrongSecret);
expect(isValid).toBe(false);
});
});
3. Error handling tests
Test how your application handles specific API errors from lomi..
describe('API Error Handling', () => {
let lomi: LomiSDK;
beforeAll(() => { /* setup lomi instance */ });
it('should handle invalid request errors (400)', async () => {
try {
await lomi.checkoutSessions.create({
merchant_id: testConfig.merchantId!,
amount: -100, // Invalid amount
currency_code: 'XXX', // Invalid currency
allowed_providers: [], // Invalid providers
success_url: 'invalid-url', // Invalid URL
cancel_url: 'invalid-url'
});
fail('Request should have failed');
} catch (error: any) {
expect(error.statusCode).toBe(400);
expect(error.message).toContain('Validation failed'); // Or specific lomi. error message
// Optionally check error.details for specific field errors
}
});
it('should handle authentication errors (401)', async () => {
const invalidLomi = new LomiSDK({ apiKey: 'lomi_sk_test_invalidkey', baseUrl: '...' });
try {
await invalidLomi.providers.list();
fail('Request should have failed');
} catch (error: any) {
expect(error.statusCode).toBe(401);
expect(error.message).toContain('Invalid API key');
}
});
});
End-to-end (E2E) testing
Simulate a full user journey involving payment.
1. Setup test environment
Use tools like Playwright or Cypress along with a testing framework. Your setup script should:
- Start your application.
- Start a webhook listener (e.g., using
ngrok
and a simple server, or a dedicated test helper). - Ensure necessary test data exists in the lomi. sandbox (e.g., test merchant, products).
2. Simulate payment flow
Your E2E test script would typically:
- Navigate through your application to initiate a payment.
- Trigger the creation of a lomi. Checkout Session via your backend.
- (Challenge) Interact with the lomi. Sandbox Checkout page. This is often difficult/flaky in automated E2E tests. Consider:
- Using specific test phone numbers/methods provided by lomi. sandbox that automatically succeed or fail.
- Having a dedicated API endpoint in your test environment that simulates the webhook callback lomi. would send upon completion (bypassing the actual sandbox UI interaction). This is often more reliable for automation.
- Wait for and verify that your application receives the expected webhook (
PAYMENT_SUCCEEDED
orCHECKOUT_COMPLETED
). - Verify that your application state updated correctly (e.g., order marked as paid, service provisioned).
describe('E2E Payment Flow', () => {
it('should complete payment and update order status', async () => {
// 1. User initiates checkout in the app UI (simulated via test actions)
const { orderId, lomiCheckoutSessionId } = await initiateCheckoutInApp();
// 2. Simulate successful payment webhook callback
// (Instead of UI interaction, call your test simulation endpoint)
await simulateLomiWebhook(lomiCheckoutSessionId, 'PAYMENT_SUCCEEDED');
// 3. Wait for app to process webhook
await waitForOrderStatusUpdate(orderId, 'PAID');
// 4. Assert final application state
const orderStatus = await getOrderStatusFromApp(orderId);
expect(orderStatus).toBe('PAID');
});
});
Test utilities
1. Webhook listener helper
Create a helper class to capture and wait for specific webhook events during tests.
import http from 'http';
import express from 'express';
interface RecordedEvent {
id: string;
type: string;
receivedAt: number;
payload: any;
}
export class TestWebhookListener {
private app = express();
private server: http.Server | null = null;
private receivedEvents: RecordedEvent[] = [];
private port = 9090; // Or choose dynamically
constructor() {
this.app.use(express.raw({ type: 'application/json' }));
this.app.post('/test-webhook', (req, res) => {
try {
// Basic validation - PRODUCTION needs full signature verification!
const payload = JSON.parse(req.body.toString());
console.log(`Test listener received event: ${payload.event}`);
this.receivedEvents.push({ id: payload.id, type: payload.event, receivedAt: Date.now(), payload });
res.status(200).json({ received: true });
} catch (e) {
console.error("Test listener error parsing body", e);
res.status(400).json({ error: "Invalid payload" });
}
});
}
start() {
this.server = this.app.listen(this.port);
console.log(`Test webhook listener started on port ${this.port}`);
return `http://localhost:${this.port}/test-webhook`; // URL to configure in lomi.
}
stop() {
this.server?.close();
console.log('Test webhook listener stopped.');
}
async waitForEvent(eventType: string, timeoutMs = 10000): Promise<RecordedEvent> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const found = this.receivedEvents.find(e => e.type === eventType);
if (found) return found;
await new Promise(r => setTimeout(r, 200)); // Poll interval
}
throw new Error(`Timeout waiting for webhook event type: ${eventType}`);
}
}
2. Test data generator
Generate consistent test data.
export class TestDataFactory {
static createCheckoutData(overrides: Partial<any> = {}) {
return {
merchant_id: process.env.TEST_MERCHANT_ID!,
amount: 100,
currency_code: 'XOF',
allowed_providers: ['WAVE'],
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
metadata: { testId: `td_${Date.now()}` },
...overrides,
};
}
// Add other data generation methods...
}
CI/CD integration
Integrate automated tests into your CI/CD pipeline (e.g., GitHub Actions).
# .github/workflows/test.yml
name: Run Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Run Unit & Integration Tests
run: npm test
env:
LOMI_TEST_API_KEY: ${{ secrets.LOMI_TEST_API_KEY }}
LOMI_TEST_WEBHOOK_SECRET: ${{ secrets.LOMI_TEST_WEBHOOK_SECRET }}
TEST_MERCHANT_ID: ${{ secrets.TEST_MERCHANT_ID }}
# Add other necessary test env vars