Error Handling
Error reasons, parseError, throw vs. result, retries, and timeouts
Error Reasons
All SDK errors carry a reason, a machine-readable label you branch on instead of parsing HTTP codes or message text.
type SdkErrorReason =
| "AUTH" // invalid or missing key, bad signature, scope
| "VALIDATION" // input the server rejected
| "LIMIT" // account or KYC transaction limits exceeded
| "RATE_LIMIT" // too many requests
| "ACCOUNT" // merchant account missing or not active
| "PROVIDER" // payment provider rejected the operation
| "DUPLICATE" // reference belongs to another account; retry with a new one
| "NOT_FOUND" // referenced transaction does not exist
| "INTERNAL" // unexpected server error
| "NETWORK" // this machine is offline
| "SERVICES_DOWN" // Nylon Pay did not complete the request
| "TIMEOUT"; // request exceeded the configured timeoutEach reason maps to a retryable hint:
| Reason | Retryable | Meaning |
|---|---|---|
AUTH | No | Fix credentials, then retry |
VALIDATION | No | Correct the input, then retry |
LIMIT | No | Contact support about account limits |
RATE_LIMIT | Yes | Back off and retry |
ACCOUNT | No | Contact support about account status |
PROVIDER | Yes | Retry after a delay |
DUPLICATE | No | Use a new reference |
NOT_FOUND | No | Transaction or reference does not exist |
INTERNAL | Yes | Retry after a delay |
NETWORK | Yes | Restore this machine's connection |
SERVICES_DOWN | Yes | Retry when Nylon Pay recovers |
TIMEOUT | Yes | Retry the request |
Offline and Nylon down
Pass onError when you create the SDK client. It handles errors from every
operation on the client, including offline calls and calls while Nylon Pay
looks down.
import { createNylonPay } from '@nile-squad/nylonpay-ts';
const nylonpay = createNylonPay({
apiKey: 'npk_test_...',
apiSecret: 'nps_test_...',
onError: (error) => {
if (error.reason === 'SERVICES_DOWN') {
console.error('Pause payment attempts:', error.message);
return;
}
console.error(error.reason, error.message);
},
});NETWORK means this machine is offline. SERVICES_DOWN means Nylon Pay did
not complete the request. Compare the reason, not message text. The same error
reaches payment.on("error") for collectPayment and makePayout. Result
operations return the error for parseError or parse_error.
Pause the operation and retry when the connection or Nylon Pay service recovers.
Handler failures do not change the operation result. Validation errors raised before a network call remain local throws.
parseError Utility
parseError decodes the error string from a Result.Err into a structured SdkError object.
import { parseError } from '@nile-squad/nylonpay-ts';
const result = await nylonpay.getStatus({ reference: crypto.randomUUID() });
if (!result.isOk) {
const error = parseError(result.error);
console.log(error.reason); // "AUTH" | "NOT_FOUND" | "LIMIT" | ...
console.log(error.message); // Human-readable description
console.log(error.retryable); // Whether retrying may help
}Branch on error.reason, never on message text or HTTP status codes. Unrecognized errors default to INTERNAL.
Throw vs. Result
The SDK uses two error patterns depending on the operation.
Operations that throw
collectPayment() and makePayout() throw only on client-side validation errors (zero amount, empty required fields, invalid items, missing bank details). These are programmer errors caught before any network call.
try {
const payment = await nylonpay.collectPayment({ /* ... */ });
payment.on('success', ({ transaction }) => fulfill(transaction));
} catch (err) {
// err.reason === "VALIDATION"
console.error('Invalid input:', err.message);
}Server-side initiation failures
If a transaction fails to start (invalid key, scope or limit rejection,
provider rejection, network error, timeout), collectPayment and makePayout
return a PaymentInstance emitting an "error" event. The transaction never
started, so there is nothing to poll.
const payment = await nylonpay.collectPayment({ /* ... */ });
payment.on('error', ({ error, reason, retryable }) => {
console.error('Could not start payment:', error, 'reason:', reason);
});
payment.on('success', ({ transaction }) => fulfill(transaction));Operations returning Result
All other operations (getStatus, getTransaction, verifyPhone, createInvoice, collectPaymentAndResolve, makePayoutAndResolve) return a Result. Decode the Err with parseError.
const result = await nylonpay.getStatus({ reference: crypto.randomUUID() });
if (result.isOk) {
console.log(result.value.status);
} else {
const error = parseError(result.error);
console.error(error.reason, error.message);
}PaymentInstance error event
Errors during the polling lifecycle (network failures, timeouts, reference mismatches) and server-side initiation failures (auth, limit, provider rejection) surface through the "error" event, not exceptions. The EventData carries reason and retryable for programmatic handling.
payment.on('error', ({ error, reason, retryable }) => {
console.error('Payment lifecycle error:', error, 'reason:', reason, 'retryable:', retryable);
});Result Pattern
All SDK methods returning Result follow this shape:
type Result<T, E> = { isOk: true; value: T } | { isOk: false; error: E };Always check isOk before accessing value:
const result = await nylonpay.createInvoice({ /* ... */ });
if (result.isOk) {
console.log('Invoice URL:', result.value.paymentLink);
} else {
const error = parseError(result.error);
console.error(error.reason, error.message);
}Retry Behavior
Automatic Retries
The SDK retries failed HTTP requests automatically:
- Transport retries: Up to
maxRetriesattempts for network failures - Business errors are not retried: client errors return immediately
const nylonpay = createNylonPay({
maxRetries: 3, // Default
});Retry Strategy
- First attempt
- Wait
- Second attempt
- Wait
- Third attempt
- Fail with error
Timeout Behavior
| Timeout | Config | Default | Applies To |
|---|---|---|---|
| Per-request | timeoutMs | about 90 sec | Single HTTP request |
| Poll duration | maxPollDurationMs | none | Optional cap on the total wait() time |
| Poll attempts | maxPollAttempts | none | Optional cap on wait() checks |
const nylonpay = createNylonPay({
timeoutMs: 90000, // about 90 seconds
maxPollDurationMs: 300000,
maxPollAttempts: 150,
});Best Practices
Always Use Reference for Idempotency
// Good: Fresh UUID each payment
const reference = crypto.randomUUID();
const payment = await nylonpay.collectPayment({ reference, ... });
// Bad: Hardcoded reference
const payment = await nylonpay.collectPayment({ reference: 'order-1', ... });Catch Initiation Errors Separately
const payment = await nylonpay.collectPayment({ /* ... */ });
payment.on('error', ({ error, reason, retryable }) => {
if (reason === 'AUTH') {
// Fix credentials
} else if (reason === 'LIMIT') {
// Contact support about account limits
} else if (retryable) {
// Retry after delay
}
});Verify After Timeout
const tx = await payment.wait();
if (tx) {
fulfillOrder(tx);
} else {
const result = await nylonpay.getStatus({ reference: crypto.randomUUID() });
if (result.isOk && result.value.status === 'successful') {
fulfillOrder();
}
}Never Ignore Errors
const result = await nylonpay.getStatus({ reference: crypto.randomUUID() });
if (!result.isOk) {
const error = parseError(result.error);
throw new Error(`Status check failed: [${error.reason}] ${error.message}`);
}See Also
- Configuration, initialize
onError - Rate Limits, handle temporary request limits
- Payment Events, handle payment lifecycle errors
- Types,
SdkErrorandEventData