Overview
Proper error handling is essential for building reliable payment applications. The Rave Java Library returns JSON responses that include status information, error messages, and detailed error codes that help you diagnose and handle issues effectively.
Understanding Error Responses
API calls in the Rave Java Library return JSONObject instances or null when requests fail. Always check for null responses and parse the status field to determine success or failure.
The library catches exceptions internally (see ApiConnection.java:73-79, 94-96) and returns null when API calls fail, so null checking is essential.
Common Error Scenarios
Network and Connection Errors
Network failures are handled by the ApiConnection class, which may return null when connections fail:
CardCharge ch = new CardCharge();
ch.setCardno("4187427415564246")
.setCvv("828")
.setAmount("1000")
.setEmail("user@example.com")
.setTxRef("TX-" + System.currentTimeMillis());
try {
JSONObject response = ch.chargeMasterAndVerveCard();
if (response == null) {
// Network error or API unavailable
System.err.println("Failed to connect to payment gateway");
// Implement retry logic or notify user
return;
}
// Process successful response
} catch (JSONException e) {
// Handle JSON parsing errors
System.err.println("Invalid response format: " + e.getMessage());
}
Invalid Configuration
Missing or incorrect API credentials will cause authorization failures:
// Check credentials are set before processing
if (RaveConstant.PUBLIC_KEY == null || RaveConstant.SECRET_KEY == null) {
throw new IllegalStateException("API credentials not configured");
}
if (RaveConstant.ENVIRONMENT == null) {
throw new IllegalStateException("Environment not set");
}
// Now safe to proceed with charges
CardCharge ch = new CardCharge();
// ... configure charge
Always set RaveConstant.PUBLIC_KEY, RaveConstant.SECRET_KEY, and RaveConstant.ENVIRONMENT before making any API calls.
Insufficient Funds
Customer accounts may have insufficient funds:
JSONObject response = accountCharge.chargeAccount();
if (response != null && response.has("status")) {
String status = response.getString("status");
String message = response.optString("message", "");
if ("error".equals(status)) {
if (message.contains("insufficient") || message.contains("balance")) {
// Handle insufficient funds
System.out.println("Insufficient funds in account");
}
}
}
Invalid Card Details
Incorrect card information will be rejected:
JSONObject response = ch.chargeMasterAndVerveCard();
if (response != null && response.has("status")) {
String status = response.getString("status");
String message = response.optString("message", "");
if ("error".equals(status)) {
if (message.contains("invalid card") || message.contains("expired")) {
// Invalid card details
System.out.println("Card validation failed: " + message);
} else if (message.contains("cvv")) {
// CVV error
System.out.println("Invalid CVV");
}
}
}
OTP Validation Failures
Incorrect OTP codes will fail validation:
ch.setTransactionreference(transactionRef)
.setOtp(userProvidedOtp);
JSONObject validation = ch.validateCardCharge();
if (validation != null && validation.has("status")) {
String status = validation.getString("status");
if ("error".equals(status)) {
String message = validation.optString("message", "");
if (message.contains("otp") || message.contains("OTP")) {
// Invalid OTP
System.out.println("Incorrect OTP. Please try again.");
// Allow user to retry with new OTP
}
}
}
Comprehensive Error Handling Pattern
Implement a robust error handling wrapper:
Create error handler utility
public class PaymentErrorHandler {
public static PaymentResult handleResponse(JSONObject response, String operation) {
// Check for null response (network/connection error)
if (response == null) {
return PaymentResult.error(
"NETWORK_ERROR",
"Unable to connect to payment gateway"
);
}
// Parse response status
if (!response.has("status")) {
return PaymentResult.error(
"INVALID_RESPONSE",
"Malformed response from payment gateway"
);
}
String status = response.getString("status");
String message = response.optString("message", "No message provided");
if ("success".equals(status)) {
return PaymentResult.success(response);
} else if ("error".equals(status)) {
return categorizeError(message, response);
} else {
// Pending or other status
return PaymentResult.pending(response, message);
}
}
private static PaymentResult categorizeError(String message, JSONObject response) {
String lowerMessage = message.toLowerCase();
if (lowerMessage.contains("insufficient")) {
return PaymentResult.error("INSUFFICIENT_FUNDS", message);
} else if (lowerMessage.contains("invalid card") || lowerMessage.contains("expired")) {
return PaymentResult.error("INVALID_CARD", message);
} else if (lowerMessage.contains("otp")) {
return PaymentResult.error("INVALID_OTP", message);
} else if (lowerMessage.contains("timeout")) {
return PaymentResult.error("TIMEOUT", message);
} else {
return PaymentResult.error("UNKNOWN_ERROR", message);
}
}
}
Use error handler in your code
CardCharge ch = new CardCharge();
// ... configure charge
JSONObject response = ch.chargeMasterAndVerveCard();
PaymentResult result = PaymentErrorHandler.handleResponse(response, "card_charge");
switch (result.getType()) {
case SUCCESS:
// Process successful payment
break;
case INSUFFICIENT_FUNDS:
// Notify user about insufficient funds
break;
case INVALID_CARD:
// Ask user to check card details
break;
case NETWORK_ERROR:
// Retry or notify about connectivity issues
break;
default:
// Handle other errors
break;
}
Retry Strategies
Simple Retry with Exponential Backoff
public JSONObject chargeWithRetry(CardCharge charge, int maxRetries) {
int retries = 0;
int backoffMs = 1000; // Start with 1 second
while (retries < maxRetries) {
JSONObject response = charge.chargeMasterAndVerveCard();
if (response != null) {
return response; // Got a response, return it
}
// Network error, retry with backoff
retries++;
if (retries < maxRetries) {
try {
Thread.sleep(backoffMs);
backoffMs *= 2; // Exponential backoff
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}
return null; // All retries exhausted
}
Using Polling for Timeout Recovery
JSONObject response = ch.chargeMasterAndVerveCard();
if (response == null) {
// Try polling if initial request fails
System.out.println("Initial request failed, attempting to poll...");
response = ch.chargeMasterAndVerveCard(true);
}
if (response == null) {
// Even polling failed
System.err.println("Transaction could not be completed");
}
See the Polling documentation for detailed information on handling timeouts.
Best Practices
1. Always Check for Null
JSONObject response = charge.chargeMasterAndVerveCard();
if (response == null) {
// Handle null case first
return;
}
// Safe to proceed
2. Validate Response Structure
if (response.has("status") && response.has("message")) {
// Response has expected fields
} else {
// Unexpected response format
System.err.println("Unexpected response structure");
}
3. Log Errors for Debugging
if ("error".equals(status)) {
// Log full response for debugging
System.err.println("Payment error: " + response.toString(2));
// Show user-friendly message to user
String userMessage = response.optString("message", "Payment failed");
System.out.println(userMessage);
}
4. Handle JSONException
try {
String status = response.getString("status");
// ... process response
} catch (JSONException e) {
System.err.println("Error parsing response: " + e.getMessage());
// Handle malformed response
}
5. Implement Idempotency
// Use unique transaction references
String txRef = "TX-" + System.currentTimeMillis() + "-" + UUID.randomUUID();
ch.setTxRef(txRef);
// Store txRef to check transaction status later if needed
6. Don’t Expose Sensitive Details
// DON'T do this
System.out.println("Error: " + response.toString());
// DO this instead
String message = response.optString("message", "An error occurred");
System.out.println("Payment failed: " + message);
Testing Error Scenarios
Use the staging environment to test error handling:
// Configure for testing
RaveConstant.ENVIRONMENT = Environment.STAGING;
RaveConstant.PUBLIC_KEY = "FLWPUBK-xxxxx";
RaveConstant.SECRET_KEY = "FLWSECK-xxxxx";
// Test with invalid card
ch.setCardno("0000000000000000"); // Will fail
// Test with insufficient funds test card
ch.setCardno("4000000000000002"); // Insufficient funds
// Test network timeout
// (disconnect network during charge)
Check the Testing guide for more information on test cards and staging environment setup.
Next Steps