Skip to content
Last updated

Recipe: Handle declined transactions

Why transactions are declined

A decline means the card issuer or processor refused the transaction. Common reasons:

ReasonWhat happenedWhat to do
Insufficient fundsCustomer's balance is too lowAsk the customer to use a different card
Invalid card numberPAN failed Luhn check or is not recognizedPrompt the customer to re-enter card details
Expired cardThe card's expiry date has passedAsk for an updated card
CVV mismatchCVV does not match the issuer's recordsAsk the customer to re-enter CVV
AVS mismatchBilling address does not matchAsk the customer to verify their billing address
Fraud suspectIssuer flagged the transactionDo not retry — contact the customer
Processor errorTemporary issue at the processorRetry after a delay

Recognizing a decline

Check response_status and response_code on every transaction response.

{
  "id": "TRANSACTION-02LGEHRR1BZSPA6WRY1T3H4CD",
  "type": "sale",
  "amount": 5000,
  "transaction_state": "failed",
  "response_status": "declined",
  "response_code": "05",
  "response_message": "Transaction declined by the issuer.",
  "avs_result_code": "n",
  "cvv_result": "N",
  "correlation_id": "c3d4e5f6-a7b8-9012-cdef-123456789abc"
}

Decision flowchart

image

Code example: Decline handling

import time, requests, os

API_KEY = os.environ["PARADISE_API_KEY"]
BASE = "https://api.dev.paradisegateway.net"

def process_with_retry(payload, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.post(
            f"{BASE}/v1/transactions",
            headers={"Content-Type": "application/json", "APIKEY": API_KEY},
            json=payload,
        )
        result = resp.json()

        if result["response_status"] == "approved":
            return {"success": True, "transaction": result}

        if result["response_status"] == "declined":
            return {
                "success": False,
                "reason": "declined",
                "message": result.get("response_message", "Transaction declined"),
                "avs": result.get("avs_result_code"),
                "cvv": result.get("cvv_result"),
            }

        # error — retry with backoff
        wait = 2 ** attempt
        print(f"Error on attempt {attempt + 1}, retrying in {wait}s...")
        time.sleep(wait)

    return {"success": False, "reason": "error", "message": "Max retries exceeded"}

What to show the customer

ScenarioUser-facing message
Declined — insufficient funds"Your card was declined. Please try a different payment method."
Declined — invalid card"We couldn't process your card. Please check the number and try again."
Declined — AVS mismatch"Your billing address doesn't match. Please verify and retry."
Error — temporary"Something went wrong. Please try again in a moment."
Never expose raw error details

Do not show response_code, response_message, or processor details to the customer. Use friendly messages and log the details server-side.

Testing declines in sandbox

Use these test card numbers to simulate specific scenarios:

Card numberExpected result
4000000000000002Declined
5200000000000007Declined
4111111111111111Approved

See Quick reference — test cards for the full list.

Next steps