CI/CD
This guide covers best practices for integrating lomi. with your CI/CD pipeline, ensuring reliable deployments and automated testing.
Environment setup
1. Environment variables
# .env.ci
LOMI_API_KEY=lomi_sk_test_... # Use a dedicated test key
LOMI_WEBHOOK_SECRET=whsec_... # Test webhook secret
LOMI_API_URL=https://sandbox.api.lomi.africa/v1 # Point to sandbox
LOMI_ENV=test
2. Secrets management
Ensure your CI/CD environment securely loads required secrets. Never commit secrets directly to your repository.
// config/secrets.ts
export function loadSecrets(): void {
const requiredSecrets = [
'LOMI_API_KEY',
'LOMI_WEBHOOK_SECRET'
];
for (const secret of requiredSecrets) {
if (!process.env[secret]) {
console.warn(`Warning: Missing recommended secret: ${secret}`);
// Depending on your setup, you might throw an error:
// throw new Error(`Missing required secret: ${secret}`);
}
}
}
// Call loadSecrets() early in your application or test setup
GitHub Actions
1. Test workflow
Run your integration tests against the lomi. sandbox environment on pushes and pull requests.
name: Test
on:
push:
branches: [ main, develop ] # Trigger on main and develop branches
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # Use latest version
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18' # Use a current LTS version
cache: 'npm' # Cache npm dependencies
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
env:
LOMI_API_KEY: ${{ secrets.LOMI_TEST_API_KEY }} # Use GitHub secrets
LOMI_WEBHOOK_SECRET: ${{ secrets.LOMI_TEST_WEBHOOK_SECRET }}
LOMI_API_URL: 'https://sandbox.api.lomi.africa/v1'
LOMI_ENV: test
2. Deploy workflow
Deploy your application using live keys only when pushing to your main branch.
name: Deploy
on:
push:
branches: [ main ] # Only trigger on pushes to main
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Optional: Define a GitHub environment for protection rules
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
# Example: Deploy to AWS (replace with your deployment provider)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1 # Your region
- name: Install, Build, and Deploy
run: |
npm ci
npm run build
npm run deploy # Your deployment script
env:
LOMI_API_KEY: ${{ secrets.LOMI_PROD_API_KEY }}
LOMI_WEBHOOK_SECRET: ${{ secrets.LOMI_PROD_WEBHOOK_SECRET }}
LOMI_API_URL: 'https://api.lomi.africa/v1'
LOMI_ENV: production
Automated testing
1. Pre-deployment checks
Before deploying, run checks to ensure basic connectivity and configuration.
// scripts/pre-deploy-check.ts
import { LomiSDK } from '@lomi/sdk'; // Assuming your SDK package name
async function runPreDeploymentChecks(): Promise<void> {
console.log('Running pre-deployment checks...');
const apiKey = process.env.LOMI_API_KEY;
const apiUrl = process.env.LOMI_API_URL;
if (!apiKey || !apiUrl) {
throw new Error('LOMI_API_KEY and LOMI_API_URL must be set');
}
const lomi = new LomiSDK({
apiKey: apiKey,
baseUrl: apiUrl
});
try {
// 1. Verify API connectivity (e.g., fetch providers)
console.log('Checking API connectivity...');
const providers = await lomi.providers.list();
if (!providers || providers.data.length === 0) {
throw new Error('Failed to fetch providers or no providers available.');
}
console.log(`Successfully fetched ${providers.data.length} providers.`);
// 2. Optional: Test webhook endpoint connectivity if applicable
// Note: The SDK might not have a direct webhook test method.
// You might need a custom check or rely on integration tests.
// Example placeholder:
// console.log('Checking webhook endpoint...');
// const webhookTestResult = await checkMyWebhookEndpoint();
// if (!webhookTestResult.success) throw new Error('Webhook endpoint check failed');
console.log('Pre-deployment checks passed!');
} catch (error) {
console.error('Pre-deployment check failed:', error);
process.exit(1); // Exit with error code
}
}
runPreDeploymentChecks();
2. Integration tests
Write tests that simulate user flows involving lomi. interactions.
// tests/integration/payment.test.ts
import { LomiSDK } from '@lomi/sdk';
// Assume setup/teardown logic exists elsewhere
describe('Payment Integration', () => {
let lomi: LomiSDK;
beforeAll(() => {
// Ensure required env vars are set for tests
if (!process.env.LOMI_TEST_API_KEY || !process.env.TEST_MERCHANT_ID) {
throw new Error('Missing test environment variables');
}
lomi = new LomiSDK({
apiKey: process.env.LOMI_TEST_API_KEY,
baseUrl: 'https://sandbox.api.lomi.africa/v1'
});
// await setupTestEnvironment(); // Your test setup
});
it('should process a test payment end-to-end', async () => {
// Create checkout session
const sessionResponse = await lomi.checkoutSessions.create({
merchant_id: process.env.TEST_MERCHANT_ID!, // Use a test merchant ID
amount: 1000, // Min amount for testing
currency_code: 'XOF',
allowed_providers: ['WAVE'], // Use a testable provider
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
metadata: { test_order_id: `e2e_${Date.now()}` }
});
const sessionId = sessionResponse.data.checkout_session_id;
expect(sessionId).toBeDefined();
// Simulate payment (requires a test helper or specific endpoint)
// This part is highly dependent on lomi.'s testing capabilities
// await lomi.testing.simulatePayment(sessionId, 'succeeded');
// Verify success (allow time for processing)
// await new Promise(r => setTimeout(r, 5000)); // Wait if needed
// const updatedSession = await lomi.checkoutSessions.get(sessionId);
// expect(updatedSession.data.status).toBe('completed');
}, 30000); // Increase timeout for E2E test
});
Deployment strategies
Choose a strategy that minimizes risk during deployment.
1. Blue-green deployment
Maintain two identical production environments (Blue and Green). Deploy to the inactive environment, test, then switch traffic.
// scripts/deploy-blue-green.ts
async function blueGreenDeploy(newVersion: string): Promise<void> {
const currentActive = await getActiveEnvironment(); // e.g., 'blue'
const inactiveEnv = currentActive === 'blue' ? 'green' : 'blue';
// 1. Deploy to inactive environment
console.log(`Deploying version ${newVersion} to ${inactiveEnv}...`);
await deployToEnvironment(inactiveEnv, newVersion);
// 2. Run health checks on inactive environment
console.log(`Running health checks on ${inactiveEnv}...`);
const health = await checkHealth(inactiveEnv);
if (!health.ok) {
console.error(`Health check failed for ${inactiveEnv}. Rolling back deployment.`);
// await rollbackDeployment(inactiveEnv); // Optional rollback
throw new Error('Health check failed on inactive environment');
}
// 3. Switch traffic
console.log(`Switching traffic to ${inactiveEnv}...`);
await switchTraffic(inactiveEnv);
// 4. Monitor the new active environment
console.log(`Monitoring ${inactiveEnv}...`);
// await monitorDeployment(inactiveEnv);
// 5. Optional: Tear down the old environment after a period
}
2. Canary deployment
Gradually roll out the new version to a small subset of users/traffic before releasing it fully.
// scripts/deploy-canary.ts
async function canaryDeploy(newVersion: string): Promise<void> {
// 1. Deploy new version alongside current version with limited traffic (e.g., 10%)
console.log(`Deploying canary version ${newVersion} with 10% traffic...`);
await deployCanaryVersion(newVersion, '10%');
// 2. Monitor metrics (error rates, latency) for a defined period
console.log('Monitoring canary metrics...');
const metricsOk = await monitorCanaryMetrics({ duration: '1h', errorThreshold: 0.05 });
if (!metricsOk) {
console.error('Canary metrics failed. Rolling back canary...');
await rollbackCanary(newVersion);
throw new Error('Canary deployment failed metrics check');
}
// 3. Gradually increase traffic to the new version
console.log('Canary metrics OK. Gradually increasing traffic...');
await scaleCanaryTraffic(newVersion, '50%');
await monitorCanaryMetrics({ duration: '30m' }); // Monitor again
await scaleCanaryTraffic(newVersion, '100%'); // Full rollout
// 4. Decommission the old version
console.log('Canary deployment successful. Decommissioning old version...');
await decommissionOldVersion();
}
Monitoring
Continuously monitor your integration health.
1. Health checks
Set up automated checks for API connectivity and critical functions.
// monitoring/healthCheck.ts
import { LomiSDK } from '@lomi/sdk';
// import db from './database'; // Your database connection
export async function checkServiceHealth(): Promise<{ status: string, errors: string[] }> {
const errors: string[] = [];
const lomi = new LomiSDK({ apiKey: process.env.LOMI_API_KEY!, baseUrl: process.env.LOMI_API_URL! });
try {
// Check lomi. API status (e.g., list providers)
await lomi.providers.list();
} catch (error: any) {
errors.push(`lomi. API check failed: ${error.message}`);
}
// try {
// // Check Database health
// await db.raw('SELECT 1');
// } catch (error: any) {
// errors.push(`Database check failed: ${error.message}`);
// }
// Check essential internal services...
return {
status: errors.length === 0 ? 'healthy' : 'unhealthy',
errors: errors
};
}
2. Metrics collection
Track key metrics related to payments and webhooks.
// monitoring/metrics.ts
export function collectMetrics(): Record<string, any> {
// Example metrics (implementation depends on your monitoring tools)
return {
// Payment metrics
payments_processed_total: getTotalPayments(),
payments_succeeded_rate: getSuccessRate(),
payment_api_latency_ms: getAverageApiLatency(),
// Webhook metrics
webhooks_received_total: getTotalWebhooksReceived(),
webhook_verification_failures_total: getWebhookVerificationFailures(),
webhook_processing_latency_ms: getAverageWebhookProcessingTime(),
// System metrics
// system_error_rate: getErrorRate(),
// system_cpu_usage: getCPUUsage(),
};
}
Rollback procedures
Have a plan to quickly revert to a previous stable version if a deployment introduces issues.
1. Automated rollback
Integrate rollback steps into your deployment workflow.
// scripts/rollback.ts
async function automaticRollback(failedDeploymentId: string, previousVersion: string): Promise<void> {
console.log(`Rolling back deployment ${failedDeploymentId} to version ${previousVersion}...`);
try {
// 1. Stop traffic to the failed version (if applicable)
// await stopTraffic(failedDeploymentId);
// 2. Redeploy the previous stable version
await deployToEnvironment('production', previousVersion);
// 3. Verify rollback success
const health = await checkHealth('production');
if (!health.ok) {
throw new Error('Rollback verification failed');
}
console.log(`Rollback to ${previousVersion} successful and verified.`);
// 4. Notify team
// await notifyTeam(`Rollback completed for deployment ${failedDeploymentId}. Restored version ${previousVersion}.`);
} catch (error: any) {
console.error(`Automatic rollback failed: ${error.message}`);
// await notifyTeam(`CRITICAL: Automatic rollback failed for deployment ${failedDeploymentId}. Manual intervention required.`);
throw error; // Re-throw to signal CI failure
}
}