Payment failures fall into three categories:
| Category | Cause | response_status | Retryable? |
|---|---|---|---|
| Decline | Issuer refused the transaction | declined | Usually no — ask for a different card |
| Error | Processor or gateway issue | error | Yes — retry with backoff |
| Network failure | Timeout, DNS, connection reset | (no response) | Yes — retry with backoff |

Use exponential backoff with jitter to avoid overwhelming the API during outages.
import time, random, requests, os
API_KEY = os.environ["PARADISE_API_KEY"]
BASE = "https://api.dev.paradisegateway.net"
def send_transaction(payload, max_retries=3):
for attempt in range(max_retries):
try:
resp = requests.post(
f"{BASE}/v1/transactions",
headers={
"Content-Type": "application/json",
"APIKEY": API_KEY,
},
json=payload,
timeout=30,
)
result = resp.json()
if result.get("response_status") == "approved":
return {"success": True, "transaction": result}
if result.get("response_status") == "declined":
return {
"success": False,
"retryable": False,
"reason": result.get("response_message", "Declined"),
"correlation_id": result.get("correlation_id"),
}
if resp.status_code >= 400 and resp.status_code < 500:
return {
"success": False,
"retryable": False,
"reason": f"Client error: {resp.status_code}",
"correlation_id": result.get("correlation_id"),
}
except requests.exceptions.Timeout:
pass # will retry
except requests.exceptions.ConnectionError:
pass # will retry
wait = (2 ** attempt) + random.uniform(0, 1)
print(f"Attempt {attempt + 1} failed, retrying in {wait:.1f}s...")
time.sleep(wait)
return {"success": False, "retryable": False, "reason": "Max retries exceeded"}Log every failure with the correlation_id so support can trace the request.
import logging
logger = logging.getLogger("payments")
def log_failure(result, payload):
logger.error(
"Payment failed: reason=%s correlation_id=%s amount=%s type=%s",
result.get("reason"),
result.get("correlation_id"),
payload.get("amount"),
payload.get("type"),
)| Failure type | User-facing message |
|---|---|
| Decline | "Your payment was declined. Please try a different payment method." |
| Temporary error | "We're having trouble processing your payment. Please try again." |
| Timeout (all retries exhausted) | "We couldn't reach the payment processor. Please try again later." |
| Validation error (4xx) | "There's an issue with your payment details. Please check and retry." |
Security
Never expose response_code, response_message, processor details, or stack traces to the customer. Log them server-side and show generic, friendly messages.
| HTTP status | response_status | Action |
|---|---|---|
| 200 | approved | Fulfill order |
| 200 | declined | Ask for different payment method |
| 200 | error | Retry with backoff |
| 400 | — | Fix the request (missing/invalid fields) |
| 401 | — | Check API key |
| 404 | — | Check the endpoint or resource ID |
| 422 | — | Validation failed (for example, invalid card number) |
| 5xx | — | Retry with backoff |
| (no response) | — | Retry with backoff |
- Handle declined transactions — detailed decline handling
- ACH returns — ACH-specific failure patterns
- Error handling concepts
- Integration best practices