Skip to main content

Error Handling

The SDK surfaces three distinct error categories. Wire all three callbacks to get complete coverage.

Error callbacks

Configure handlers in the AccruPay constructor:

import AccruPay from '@accrupay/node';
import type { GraphQLError } from 'graphql';

const accrupay = new AccruPay({
apiSecret: process.env.ACCRUPAY_API_SECRET!,

onAuthError: (error: Error) => {
// Fired on HTTP 401 or UNAUTHENTICATED GraphQL code.
// Every subsequent call will also fail — re-initialize the client.
console.error('Auth error — rotate API key:', error.message);
alertOncall('accrupay_auth_failure');
},

onGraphQLError: (errors: GraphQLError[]) => {
// Fired when response.errors[] is non-empty.
for (const err of errors) {
const code = err.extensions?.code as string | undefined;
console.error(`[${code ?? 'UNKNOWN'}] ${err.message}`);
}
},

onNetworkError: (error: Error) => {
// Fired on transport-level failure (DNS, timeout, connection refused).
// No GraphQL response was received.
metrics.increment('accrupay.network_error');
},
});

GraphQL error shape

When onGraphQLError fires, each error in the array follows the GraphQL spec:

interface GraphQLError {
message: string;
extensions?: {
code: string; // AccruPay error code
[key: string]: unknown;
};
locations?: { line: number; column: number }[];
path?: (string | number)[];
}

Error codes

extensions.codeCauseRecovery
UNAUTHENTICATEDInvalid or missing API key.Rotate the API key and re-initialize the client.
FORBIDDENValid key but insufficient permissions for the operation.Check account permissions or contact support.
NOT_FOUNDThe requested resource does not exist.Verify the ID. Do not retry with the same value.
VALIDATION_ERRORRequest payload failed schema validation.Inspect error.message for the failing field and fix the input.
INTERNAL_SERVER_ERRORUnhandled server-side error.Retry with exponential back-off; alert if it persists.

Handling by code

onGraphQLError: (errors) => {
for (const err of errors) {
const code = err.extensions?.code;
switch (code) {
case 'UNAUTHENTICATED':
// Re-initialize accrupay with a fresh apiSecret
break;
case 'FORBIDDEN':
// Surface permission error to the caller
break;
case 'NOT_FOUND':
// Return 404 to the upstream caller
break;
case 'VALIDATION_ERROR':
// Log the message and surface to the developer
console.warn('Validation failed:', err.message);
break;
case 'INTERNAL_SERVER_ERROR':
default:
console.error('AccruPay server error:', err.message);
break;
}
}
},

Transaction declines are not errors

warning

A declined or failed transaction does not throw an exception or fire onGraphQLError. The operation resolves normally — the decline is reflected in the transaction status.

Check transaction.status after every payment:

const transaction = await accrupay.transactions.clientSessions.payments.verify({
id: session.id,
});

if (['DECLINED', 'FAILED', 'ERROR'].includes(transaction.status)) {
// Normal business outcome — not an exception
const reason = transaction.providerError ?? 'No reason provided';
console.warn('Payment not successful:', reason);
// Prompt the customer to use a different payment method
}

providerError

The providerError field on a transaction contains the raw decline reason returned by the payment processor. It is useful for debugging and logging but should not be shown verbatim to end users.

// Safe for logs
console.log('Provider decline reason:', transaction.providerError);

// Map to a user-facing message instead
const userMessage = transaction.status === 'DECLINED'
? 'Your card was declined. Please try a different payment method.'
: 'Payment could not be completed. Please try again.';

BigInt serialization

Amount fields (amount, initialAmount) are bigint. JSON.stringify throws on bigint values by default.

// This throws: TypeError: Do not know how to serialize a BigInt
JSON.stringify({ amount: transaction.amount });

// Convert first
JSON.stringify({ amount: String(transaction.amount) });

// Or use a replacer
JSON.stringify(transaction, (_, v) =>
typeof v === 'bigint' ? v.toString() : v
);
tip

Keep amounts as bigint throughout your internal logic. Only convert to string or number at serialization boundaries (API responses, logs, database writes).