Errors
One shape, so you can branch on it.
json
{
"error": {
"type": "card_error",
"code": "card_declined",
"decline_code": "insufficient_funds",
"message": "The card has insufficient funds.",
"request_id": "req_8fK2mN4pQ6rS"
}
}Types
| Type | Meaning |
|---|---|
| invalid_request_error | The request was wrong. Fix it; retrying will not help. |
| authentication_error | The API key is missing, wrong, revoked or expired. |
| permission_error | Valid key, but not allowed to do this — e.g. live before approval. |
| card_error | The card was refused. decline_code says why. |
| idempotency_error | The key was reused with a different body, or is still in flight. |
| rate_limit_error | Too many requests. Back off and retry. |
| api_error | Our fault, or the card network was unreachable. Safe to retry. |
Handling one
ts
import { SecureProcessingError } from "@secureprocessing/node";
try {
await sp.paymentIntents.create(params);
} catch (err) {
if (err instanceof SecureProcessingError) {
if (err.type === "card_error") showCustomer(err.message);
else if (err.isRetryable) scheduleRetry();
else report(err.requestId);
}
}Quote request_id to support and we can find the exact request.
Decline codes
insufficient_funds, expired_card, incorrect_cvc, lost_card, do_not_honor and others. Anything we cannot map returns generic_decline.
Show the customer message rather than composing your own from the code. Some declines are deliberately vague — a card issuer will not always say why, and guessing on their behalf misleads the cardholder.
One distinction worth keeping
A 402 means the card was refused. A 503 gateway_unavailable means we could not reach the card network and no payment was taken. Telling a customer their card failed when it did not is both wrong and expensive, so we keep them apart and you should too.