Paystack "Declined by Financial Institution" Error
When Paystack returns "Declined by financial institution" in the gateway_response, it means the customer's bank or card issuer rejected the transaction. The bank does not tell Paystack the specific reason. Common causes include: daily transaction limits, international transaction restrictions, the card being flagged for suspicious activity, or the bank's fraud detection system blocking the payment. As a merchant, you cannot override a bank decline. Your job is to detect it, message the customer clearly without embarrassing them, suggest alternative payment methods, and log the response for support.
What the Response Looks Like
When you verify a declined transaction, the Paystack API returns something like this:
{
"status": true,
"message": "Verification successful",
"data": {
"status": "failed",
"reference": "txn_abc123def456",
"amount": 500000,
"currency": "NGN",
"channel": "card",
"gateway_response": "Declined by financial institution",
"customer": {
"email": "customer@example.com"
},
"authorization": {
"bank": "First Bank of Nigeria",
"card_type": "visa",
"last4": "4081"
}
}
}
Notice that data.status is "failed" and data.gateway_response is "Declined by financial institution". The outer status: true and message: "Verification successful" just mean the verification API call itself worked. The transaction still failed.
This is a common source of bugs: developers check status === true and assume the payment succeeded. Always check data.status === "success", not the outer status.
Why Banks Decline Transactions
The customer's bank made this decision. Paystack requested the charge, and the bank said no. Here are the most common reasons (the bank does not tell Paystack which one applies):
- Daily transaction limit reached. Most Nigerian bank cards have daily spending limits. If the customer has already spent close to their limit, the next transaction gets declined.
- International transaction restrictions. Some bank cards are not enabled for online transactions, especially debit cards. The customer may need to enable online payments through their banking app.
- Fraud detection triggered. The bank's fraud system flagged the transaction as suspicious. This can happen with unusually large amounts, transactions from new devices, or purchases from unfamiliar merchants.
- Card restrictions. Some cards are restricted by the bank from certain merchant categories. For example, a card might be blocked from gambling or high-risk merchants.
- Account frozen or restricted. The customer's account has a hold or restriction placed by the bank, regulatory body, or court order.
- Technical issues at the bank. The bank's payment processing system may be experiencing intermittent issues. The same card might work if the customer tries again in a few minutes.
- Card expired or deactivated. The card may have expired or been reported lost/stolen. Paystack's response might not distinguish this from a general decline.
The important thing to understand: you cannot determine the exact reason from the gateway_response alone. The bank gives a generic decline code, and Paystack translates it into "Declined by financial institution."
What You Can Do as a Merchant
You cannot override a bank decline. But you can handle it well:
1. Show a clear, friendly message.
Do not show "Declined by financial institution" to the customer. That message is for developers. Translate it into something the customer can act on.
2. Suggest alternatives.
Offer other payment methods: bank transfer, USSD, mobile money. If the customer's card is the problem, a different payment channel bypasses the card entirely.
3. Suggest the customer contact their bank.
The bank is the only entity that can explain why the transaction was declined. Give the customer specific advice: "Contact your bank and ask them to authorize online transactions" or "Check if you have reached your daily spending limit."
4. Let them retry.
Sometimes the decline is temporary (bank system glitch, timeout). Let the customer try again with the same card or a different card. Do not block them from retrying.
5. Do NOT:
- Blame the customer ("Your card is bad")
- Assume fraud ("This transaction was flagged")
- Show technical error codes
- Automatically retry the charge without the customer's consent
- Contact the bank on the customer's behalf (you cannot, and the bank will not discuss the customer's account with you)
Customer Messaging: What to Show
Here is a messaging framework for declined transactions. Tailor these messages to your brand voice.
// Message templates for declined transactions
const declineMessages = {
// Main message
heading: 'Payment could not be completed',
body: 'Your bank did not approve this transaction. This can happen for several reasons, most of which are easy to fix.',
// Suggestions
suggestions: [
'Try again with the same card. Sometimes a retry works, especially if the first attempt timed out.',
'Try a different card if you have one.',
'Use bank transfer instead. It does not go through the card network.',
'Contact your bank and ask if there are any restrictions on your card for online payments.',
'Check if you have reached your daily transaction limit.',
],
// What NOT to say
avoid: [
'Your card was declined', // Sounds like the customer did something wrong
'Transaction failed', // Too vague
'Error processing payment', // Sounds like your system is broken
'Insufficient funds', // Embarrassing even if true (and it might not be)
],
};
For the customer-facing UI, something like this works well:
// React component example
function PaymentDeclined({ onRetry, onSwitchMethod }) {
return (
<div className="payment-declined">
<h2>Payment could not be completed</h2>
<p>
Your bank did not approve this transaction. This is usually
a temporary issue with the card or account.
</p>
<div className="actions">
<button onClick={onRetry}>Try Again</button>
<button onClick={onSwitchMethod}>Use a Different Payment Method</button>
</div>
<p className="help-text">
If this keeps happening, contact your bank and ask them to
authorize online transactions for your card.
</p>
</div>
);
}
Detecting Bank Declines in Your Code
Check the gateway_response field in the verification response to detect bank declines and handle them differently from other failures.
// Verify and handle the transaction
async function verifyTransaction(reference) {
const res = await fetch(
`https://api.paystack.co/transaction/verify/${reference}`,
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
},
}
);
const { data } = await res.json();
if (data.status === 'success') {
return { success: true, data };
}
// Categorize the failure
const gatewayResponse = data.gateway_response?.toLowerCase() || '';
if (gatewayResponse.includes('declined')) {
return {
success: false,
reason: 'bank_declined',
userMessage: 'Your bank did not approve this transaction. Please try again or use a different payment method.',
canRetry: true,
};
}
if (gatewayResponse.includes('insufficient')) {
return {
success: false,
reason: 'insufficient_funds',
userMessage: 'This payment could not be completed. Please try a different payment method.',
canRetry: true,
};
}
if (gatewayResponse.includes('expired')) {
return {
success: false,
reason: 'card_expired',
userMessage: 'The card used has expired. Please use a different card.',
canRetry: true,
};
}
// Generic failure
return {
success: false,
reason: 'unknown',
userMessage: 'This payment could not be completed. Please try again.',
gatewayResponse: data.gateway_response,
canRetry: true,
};
}
The key pattern: check data.status first, then use data.gateway_response to categorize the failure. Log the full gateway_response for your support team, but show a translated message to the customer.
Common Gateway Response Strings
Paystack returns these gateway_response strings from the bank. This is not a complete list because banks can return custom messages, but these are the ones you will see most often:
| gateway_response | What it means | What to tell the customer |
|---|---|---|
Declined by financial institution |
Generic bank decline. Could be any reason. | Your bank did not approve this payment. Try again or use a different method. |
Insufficient Funds |
Account balance too low. | This payment could not be completed. Try a different payment method. |
Card Expired |
Card past its expiry date. | The card used has expired. Please use a different card. |
Invalid Card Number |
Card number is wrong. | The card number appears to be incorrect. Please check and try again. |
Transaction not permitted to cardholder |
Bank has restricted this card from this type of transaction. | Your card is not enabled for this type of transaction. Contact your bank to enable online payments. |
Do not honor |
Generic bank refusal. No specific reason given. | Your bank did not approve this payment. Please try again or contact your bank. |
Always match gateway_response strings using case-insensitive comparison and includes() rather than exact equality. Banks sometimes return slightly different wording for the same error.
Logging Declines for Support and Analysis
Log every declined transaction with enough detail for your support team to help the customer, but not so much that you store sensitive card data.
// Log decline details
function logDeclinedTransaction(verificationData) {
const logEntry = {
timestamp: new Date().toISOString(),
reference: verificationData.reference,
amount: verificationData.amount / 100, // Convert from kobo
currency: verificationData.currency,
status: verificationData.status,
gatewayResponse: verificationData.gateway_response,
channel: verificationData.channel,
customerEmail: verificationData.customer?.email,
cardType: verificationData.authorization?.card_type,
cardLast4: verificationData.authorization?.last4,
bank: verificationData.authorization?.bank,
// Do NOT log the full card number, CVV, or expiry
};
console.log('DECLINED_TRANSACTION:', JSON.stringify(logEntry));
// Optional: send to your monitoring service
// sendToSlack('#payment-alerts', `Payment declined: ${logEntry.reference} - ${logEntry.gatewayResponse}`);
}
// Usage in your verification handler
if (data.status === 'failed') {
logDeclinedTransaction(data);
}
With this log, your support team can tell the customer: "We see a payment attempt for NGN 5,000 on your Visa card ending in 4081 at 2:34 PM. The bank declined it. Please contact First Bank and reference this time and amount."
Track your decline rate over time. If it suddenly spikes, something changed: maybe your Paystack account has an issue, or a bank is having system problems, or a specific card type is failing.
Offering Alternative Payment Methods
When a card is declined, the best thing you can do is offer the customer a different way to pay. Paystack supports multiple channels:
- Bank transfer: The customer transfers directly from their bank account. No card involved. Bypasses all card-related restrictions.
- USSD: The customer dials a code on their phone to approve the payment. Works without internet. Great for customers with basic phones or spotty connectivity.
- Mobile money: Available in Ghana and other markets. The customer pays from their mobile money wallet.
- Try a different card: If the customer has multiple cards, one might work even though the other was declined.
// Initialize a bank transfer payment as fallback
async function initiateBankTransfer(email, amount) {
const res = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
amount: amount * 100,
channels: ['bank_transfer'], // Only show bank transfer option
callback_url: 'https://yoursite.com/payment/verify',
}),
});
const data = await res.json();
return data.data.authorization_url;
}
By passing channels: ['bank_transfer'], the Paystack checkout page only shows the bank transfer option. This removes the friction of the customer trying their declined card again and gives them a clean path to complete the payment.
Verification: Confirm Your Decline Handling Works
Test your decline handling using Paystack's test cards that simulate declines:
Step 1: Simulate a decline.
Use test card 4084 0840 8408 4081 with CVV 400. This simulates a declined transaction. Complete the checkout and verify your code handles the decline correctly.
Step 2: Check the user-facing message.
After the decline, your UI should show a helpful message, not the raw gateway_response. Verify the message is clear, non-blaming, and offers alternatives.
Step 3: Check the logs.
Verify your server logged the decline with the reference, gateway_response, card last4, and bank. Confirm no sensitive card data was logged.
Step 4: Test the retry flow.
From the decline screen, click "Try Again." Verify the customer can attempt payment again without creating a new order or losing their cart.
Step 5: Test the alternative payment method flow.
From the decline screen, click "Use a Different Payment Method." Verify the customer is taken to a checkout with alternative channels available.
// Automated test for decline handling
async function testDeclineHandling() {
// Initialize a transaction
const initRes = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'test@example.com',
amount: 10000,
}),
});
const { data: initData } = await initRes.json();
console.log('Transaction initialized:', initData.reference);
// After manually completing with test card + CVV 400,
// verify the transaction
const verifyRes = await fetch(
`https://api.paystack.co/transaction/verify/${initData.reference}`,
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
},
}
);
const { data: verifyData } = await verifyRes.json();
console.log('Status:', verifyData.status);
console.log('Gateway response:', verifyData.gateway_response);
// Should show: status: "failed", gateway_response contains "declined" or similar
}
testDeclineHandling();
Key Takeaways
- ✓A "Declined by financial institution" response means the customer's bank rejected the transaction. Paystack is just relaying the bank's decision. Your code is not the problem.
- ✓Banks decline transactions for many reasons: daily limits, international restrictions, fraud flags, expired cards, insufficient funds, or account issues. The bank rarely tells Paystack the specific reason.
- ✓As a merchant, you cannot override a bank decline. You cannot call the bank on the customer's behalf. The customer must contact their bank directly to resolve the issue.
- ✓Your job is to detect the decline in the gateway_response field, show a helpful message to the customer, and suggest alternative payment methods like bank transfer, USSD, or mobile money.
- ✓Never show raw gateway_response text to customers. Messages like "Declined by financial institution" are confusing. Translate them into clear, friendly language.
- ✓Log every decline with the full Paystack response. This helps your support team when customers ask why their payment failed.
- ✓Declined transactions are normal. Even healthy businesses see decline rates of 5-15%. Do not assume your integration is broken because some payments are declined.
Frequently Asked Questions
- Can I contact the customer's bank to find out why the transaction was declined?
- No. Banks will not discuss a customer's account or transaction details with a merchant. Only the account holder (your customer) can contact their bank. The best you can do is advise the customer to call their bank and reference the date, time, and amount of the declined transaction.
- Does "Declined by financial institution" mean the customer has no money?
- Not necessarily. "Declined by financial institution" is a generic response that covers many reasons: daily limits, card restrictions, fraud flags, and more. Insufficient funds has its own separate gateway_response. Never assume the reason is financial when messaging the customer.
- Should I automatically retry a declined transaction?
- No. Never automatically retry a charge without the customer's explicit consent. The bank declined the transaction for a reason, and retrying without consent could be considered unauthorized charges. Let the customer decide whether to try again, use a different card, or choose a different payment method.
- What decline rate is normal for a Paystack integration?
- Decline rates of 5-15% are normal for Nigerian card payments. The rate varies by industry, average transaction amount, and customer demographics. If your decline rate is above 20%, check whether your integration has issues. If it suddenly spikes from a low baseline, a bank might be having system problems or your Paystack account might have a restriction.
- Is a "Do not honor" response the same as "Declined by financial institution"?
- They are similar but not identical. Both mean the bank refused the transaction. "Do not honor" is a specific ISO 8583 response code (05) that some banks return. "Declined by financial institution" is more generic. In practice, handle them the same way: inform the customer, suggest alternatives, and log the response.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon