Error Handling
Result pattern, retries, timeouts, and error best practices
Result Pattern
All SDK methods that make HTTP requests return a Result object from slang-ts:
type Result<T> =
| { status: true; data: T }
| { status: false; message: string };Always check status before accessing data:
const result = await nile.createInvoice(request);
if (result.status) {
// Safe to access result.data
console.log(result.data.invoiceId);
} else {
// Handle error
console.error(result.message);
}Error Handling by Method
collectPayment()
const payment = nile.collectPayment({ /* ... */ });
// PaymentInstance is returned synchronously
// Errors during init throw immediately (validation, missing config)
try {
const payment = nile.collectPayment({
amount: 5000,
customer: { phone: "+256700000000" },
reference: "invalid-uuid", // Throws: invalid format
});
} catch (error) {
// Validation error
console.error(error.message);
}
// Handle polling/network errors via event
payment.on("error", (err) => {
if (err.code === "TIMEOUT") {
// maxPollDuration exceeded
} else if (err.code === "NETWORK_ERROR") {
// Connection failed
}
});getStatus() and createInvoice()
const result = await nile.getStatus({ reference });
if (!result.status) {
switch (result.message) {
case "Transaction not found":
// Reference not found
break;
case "Invalid reference format":
// Validation error
break;
default:
// Unexpected error
break;
}
}wait()
try {
const tx = await payment.wait();
} catch (error) {
if (error.code === "POLL_TIMEOUT") {
// maxPollDuration exceeded
// Payment may still succeed - use getStatus() to verify
} else if (error.code === "NETWORK_ERROR") {
// Connection failed during polling
}
}Retry Behavior
Automatic Retries
The SDK retries failed HTTP requests automatically:
- Transport retries: Up to
maxRetriesattempts for network failures - Not for business errors: 4xx responses are not retried
const nile = createNilePay({
maxRetries: 3, // Default
});Retry Strategy
- First attempt
- Wait (exponential backoff)
- Second attempt
- Wait (exponential backoff)
- Third attempt
- Fail with error
Timeout Behavior
| Timeout | Config | Default | Applies To |
|---|---|---|---|
| Per-request | timeoutMs | 30s | Single HTTP request |
| Poll duration | maxPollDuration | 5 min | wait() total time |
| Poll attempts | maxPollAttempts | 150 | wait() max polls |
const nile = createNilePay({
timeoutMs: 30000, // 30 seconds per request
maxPollDuration: 300000, // 5 minutes total wait
maxPollAttempts: 150, // ~2 second intervals
});Error Codes
| Code | Description |
|---|---|
VALIDATION_ERROR | Invalid request parameters |
NETWORK_ERROR | Connection failed |
TIMEOUT | Request timed out |
POLL_TIMEOUT | wait() exceeded max duration |
INVALID_SIGNATURE | Request signature verification failed |
UNAUTHORIZED | Invalid API credentials |
Best Practices
Always Use Reference for Idempotency
// Good: Fresh UUID each payment
const reference = crypto.randomUUID();
const payment = nile.collectPayment({ reference, ... });
// Bad: Hardcoded reference
const payment = nile.collectPayment({ reference: "order-1", ... });Verify After Timeout
const payment = nile.collectPayment({ /* ... */ });
try {
await payment.wait();
} catch (error) {
if (error.code === "POLL_TIMEOUT") {
// Verify actual status
const result = await nile.getStatus({ reference: payment.reference });
if (result.status && result.data.status === "successful") {
// Payment actually succeeded
fulfillOrder();
}
}
}Chain Error Handlers
payment
.on("success", handleSuccess)
.on("failed", handleFailed)
.on("error", (err) => {
logger.error("Payment error:", err);
// Implement alerting/retry here
});Never Ignore Errors
// Bad
const result = await nile.getStatus({ reference });
console.log(result.data.status); // TypeError if status is false
// Good
const result = await nile.getStatus({ reference });
if (!result.status) {
throw new Error(`Status check failed: ${result.message}`);
}