Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/main/java/com/razorpay/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import org.apache.commons.text.WordUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import okhttp3.HttpUrl;
Expand Down Expand Up @@ -106,7 +107,9 @@ else if(response.code()==204){
responseJson = new JSONObject(responseBody);
}
} catch (IOException e) {
throw new RazorpayException(e.getMessage());
throw new RazorpayException(e.getMessage(), statusCode);
} catch (JSONException e) {
throw new RazorpayException("Unable to parse response: " + e.getMessage(), statusCode);
}

if (statusCode < STATUS_OK || statusCode >= STATUS_MULTIPLE_CHOICE) {
Expand Down Expand Up @@ -170,7 +173,9 @@ <T extends Object> T processResponse(Response response) throws RazorpayException
responseJson = new JSONObject(responseBody);
}
} catch (IOException e) {
throw new RazorpayException(e.getMessage());
throw new RazorpayException(e.getMessage(), statusCode);
} catch (JSONException e) {
throw new RazorpayException("Unable to parse response: " + e.getMessage(), statusCode);
}

if (statusCode >= STATUS_OK && statusCode < STATUS_MULTIPLE_CHOICE) {
Expand All @@ -195,7 +200,9 @@ <T extends Entity> ArrayList<T> processCollectionResponse(Response response)
responseBody = response.body().string();
responseJson = new JSONObject(responseBody);
} catch (IOException e) {
throw new RazorpayException(e.getMessage());
throw new RazorpayException(e.getMessage(), statusCode);
} catch (JSONException e) {
throw new RazorpayException("Unable to parse response: " + e.getMessage(), statusCode);
}

String collectionName = null;
Expand All @@ -221,20 +228,21 @@ private String getEntity(JSONObject jsonObj, HttpUrl url) {
}

private void throwException(int statusCode, JSONObject responseJson) throws RazorpayException {
if (responseJson.has(ERROR)) {
if (responseJson != null && responseJson.has(ERROR)) {
JSONObject errorResponse = responseJson.getJSONObject(ERROR);
String code = errorResponse.getString(STATUS_CODE);
String description = errorResponse.getString(DESCRIPTION);
throw new RazorpayException(code + ":" + description);
throw new RazorpayException(code + ":" + description, errorResponse, statusCode);
}
throwServerException(statusCode, responseJson.toString());
String bodyForServerException = responseJson != null ? responseJson.toString() : "Invalid or empty response from server";
throwServerException(statusCode, bodyForServerException);
}

private void throwServerException(int statusCode, String responseBody) throws RazorpayException {
StringBuilder sb = new StringBuilder();
sb.append("Status Code: ").append(statusCode).append("\n");
sb.append("Server response: ").append(responseBody);
throw new RazorpayException(sb.toString());
throw new RazorpayException(sb.toString(), statusCode);
}

private Class getClass(String entity) {
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/com/razorpay/RazorpayException.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,82 @@
package com.razorpay;

import org.json.JSONObject;

public class RazorpayException extends Exception {

private transient JSONObject errorResponse;
private int statusCode;

public RazorpayException(String message) {
super(message);
this.statusCode = 0;
this.errorResponse = null;
}

public RazorpayException(String message, Throwable cause) {
super(message, cause);
this.statusCode = 0;
this.errorResponse = null;
}

public RazorpayException(Throwable cause) {
super(cause);
this.statusCode = 0;
this.errorResponse = null;
}

public RazorpayException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
this.statusCode = 0;
this.errorResponse = null;
}

public RazorpayException(String message, int statusCode) {
super(message);
this.statusCode = statusCode;
this.errorResponse = null;
}

public RazorpayException(String message, JSONObject errorResponse, int statusCode) {
super(message);
this.errorResponse = errorResponse;
this.statusCode = statusCode;
}

public JSONObject getErrorResponse() {
return errorResponse;
}

public int getStatusCode() {
return statusCode;
}

public String getCode() {
return errorResponse != null ? errorResponse.optString("code", null) : null;
}

public String getDescription() {
return errorResponse != null ? errorResponse.optString("description", null) : null;
}

public String getField() {
return errorResponse != null ? errorResponse.optString("field", null) : null;
}

public String getReason() {
return errorResponse != null ? errorResponse.optString("reason", null) : null;
}

public String getSource() {
return errorResponse != null ? errorResponse.optString("source", null) : null;
}

public String getStep() {
return errorResponse != null ? errorResponse.optString("step", null) : null;
}

public JSONObject getMetadata() {
return errorResponse != null ? errorResponse.optJSONObject("metadata") : null;
}
}
236 changes: 236 additions & 0 deletions src/test/java/com/razorpay/RazorpayExceptionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package com.razorpay;

import okhttp3.ResponseBody;
import org.json.JSONObject;
import org.junit.Test;
import org.mockito.InjectMocks;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Collections;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RazorpayExceptionTest extends BaseTest {

@InjectMocks
protected OrderClient orderClient = new OrderClient(TEST_SECRET_KEY);

private static final String ERROR_RESPONSE_JSON = "{" +
"\"error\":{" +
"\"code\":\"BAD_REQUEST_ERROR\"," +
"\"description\":\"The amount must be at least 100\"," +
"\"source\":\"business\"," +
"\"step\":\"payment_initiation\"," +
"\"reason\":\"input_validation_failed\"," +
"\"metadata\":{" +
"\"payment_id\":\"pay_EDNBKIP31Y4jl8\"," +
"\"order_id\":\"order_DBJKIP31Y4jl8\"" +
"}," +
"\"field\":\"amount\"" +
"}}";

private void mockNonJsonResponse(String rawBody) {
ResponseBody rb = mock(ResponseBody.class);
try {
when(rb.string()).thenReturn(rawBody);
} catch (IOException e) {
fail("Mock setup failed");
}
when(mockedResponse.body()).thenReturn(rb);
}

@Test
public void testStructuredErrorResponseIsAvailable() {
try {
mockResponseFromExternalClient(ERROR_RESPONSE_JSON);
mockResponseHTTPCodeFromExternalClient(400);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertNotNull(e.getErrorResponse());
JSONObject error = e.getErrorResponse();
assertEquals("BAD_REQUEST_ERROR", error.getString("code"));
assertEquals("The amount must be at least 100", error.getString("description"));
assertEquals("business", error.getString("source"));
assertEquals("payment_initiation", error.getString("step"));
assertEquals("input_validation_failed", error.getString("reason"));
assertEquals("amount", error.getString("field"));
assertTrue(error.has("metadata"));
} catch (IOException e) {
fail("Unexpected IOException");
}
}

@Test
public void testStatusCodeIsAvailable() {
try {
mockResponseFromExternalClient(ERROR_RESPONSE_JSON);
mockResponseHTTPCodeFromExternalClient(400);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertEquals(400, e.getStatusCode());
} catch (IOException e) {
fail("Unexpected IOException");
}
}

@Test
public void testMessageIsBackwardCompatible() {
try {
mockResponseFromExternalClient(ERROR_RESPONSE_JSON);
mockResponseHTTPCodeFromExternalClient(400);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertEquals("BAD_REQUEST_ERROR:The amount must be at least 100", e.getMessage());
} catch (IOException e) {
fail("Unexpected IOException");
}
}

@Test
public void testServerExceptionHasStatusCode() {
try {
mockResponseFromExternalClient("{\"unrecognized\":\"body\"}");
mockResponseHTTPCodeFromExternalClient(500);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertEquals(500, e.getStatusCode());
assertNull(e.getErrorResponse());
assertTrue(e.getMessage().contains("Status Code: 500"));
} catch (IOException e) {
fail("Unexpected IOException");
}
}

@Test
public void testNonJsonResponseDoesNotCrash() {
try {
mockNonJsonResponse("<html><body>503 Service Unavailable</body></html>");
mockResponseHTTPCodeFromExternalClient(503);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertEquals(503, e.getStatusCode());
assertNull(e.getErrorResponse());
assertTrue(e.getMessage().contains("Unable to parse response"));
}
}

@Test
public void testExceptionConstructorsDefaults() {
RazorpayException ex1 = new RazorpayException("test message");
assertEquals("test message", ex1.getMessage());
assertEquals(0, ex1.getStatusCode());
assertNull(ex1.getErrorResponse());

RazorpayException ex2 = new RazorpayException("test message", new JSONObject("{\"code\":\"ERR\"}"), 404);
assertEquals("test message", ex2.getMessage());
assertEquals(404, ex2.getStatusCode());
assertNotNull(ex2.getErrorResponse());
assertEquals("ERR", ex2.getErrorResponse().getString("code"));

RazorpayException ex3 = new RazorpayException("server error", 502);
assertEquals("server error", ex3.getMessage());
assertEquals(502, ex3.getStatusCode());
assertNull(ex3.getErrorResponse());
}

@Test
public void testExceptionIsSerializable() {
RazorpayException ex = new RazorpayException(
"BAD_REQUEST_ERROR:The amount must be at least 100",
new JSONObject(ERROR_RESPONSE_JSON).getJSONObject("error"),
400
);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ex);
oos.close();
} catch (IOException e) {
fail("RazorpayException with errorResponse should be serializable: " + e.getMessage());
}
}

@Test
public void testExceptionSerializableWithoutErrorResponse() {
RazorpayException ex = new RazorpayException("server error", 502);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ex);
oos.close();
} catch (IOException e) {
fail("RazorpayException without errorResponse should be serializable: " + e.getMessage());
}
}

@Test
public void testSafeGettersWithFullError() {
try {
mockResponseFromExternalClient(ERROR_RESPONSE_JSON);
mockResponseHTTPCodeFromExternalClient(400);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertEquals("BAD_REQUEST_ERROR", e.getCode());
assertEquals("The amount must be at least 100", e.getDescription());
assertEquals("amount", e.getField());
assertEquals("input_validation_failed", e.getReason());
assertEquals("business", e.getSource());
assertEquals("payment_initiation", e.getStep());
assertNotNull(e.getMetadata());
assertEquals("pay_EDNBKIP31Y4jl8", e.getMetadata().getString("payment_id"));
assertEquals("order_DBJKIP31Y4jl8", e.getMetadata().getString("order_id"));
} catch (IOException e) {
fail("Unexpected IOException");
}
}

@Test
public void testSafeGettersWithMissingFieldsReturnNull() {
String authErrorJson = "{\"error\":{\"code\":\"BAD_REQUEST_ERROR\",\"description\":\"Invalid API Key\"}}";
try {
mockResponseFromExternalClient(authErrorJson);
mockResponseHTTPCodeFromExternalClient(401);
mockURL(Collections.singletonList("orders"));
orderClient.create(new JSONObject("{\"amount\":50,\"currency\":\"INR\"}"));
fail("Expected RazorpayException");
} catch (RazorpayException e) {
assertEquals("BAD_REQUEST_ERROR", e.getCode());
assertEquals("Invalid API Key", e.getDescription());
assertNull(e.getField());
assertNull(e.getReason());
assertNull(e.getSource());
assertNull(e.getStep());
assertNull(e.getMetadata());
} catch (IOException e) {
fail("Unexpected IOException");
}
}

@Test
public void testSafeGettersReturnNullWhenNoErrorResponse() {
RazorpayException ex = new RazorpayException("network error", 503);
assertNull(ex.getCode());
assertNull(ex.getDescription());
assertNull(ex.getField());
assertNull(ex.getReason());
assertNull(ex.getSource());
assertNull(ex.getStep());
assertNull(ex.getMetadata());
}
}
Loading