Skip to content
Open
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
4 changes: 4 additions & 0 deletions src/main/java/com/gocardless/errors/ApiErrorResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ private ApiErrorResponse(String message, ErrorType type, String documentationUrl
this.errors = errors;
}

public static ApiErrorResponse fromMessage(String message, int code) {
return new ApiErrorResponse(message, ErrorType.GOCARDLESS, null, null, code, null);
}

String getMessage() {
return message;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/gocardless/http/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private <T> T parseResponseBody(ApiRequest<T> request, Response response) {
private GoCardlessException handleErrorResponse(Response response) {
try {
String responseBody = response.body().string();
return responseParser.parseError(responseBody);
return responseParser.parseError(responseBody, response.code());
} catch (IOException e) {
throw new GoCardlessNetworkException("Failed to read response body", e);
}
Expand Down
16 changes: 14 additions & 2 deletions src/main/java/com/gocardless/http/ResponseParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,20 @@ <T> ListResponse<T> parsePage(String responseBody, String envelope, TypeToken<Li
return new ListResponse<>(ImmutableList.copyOf(items), meta, linked);
}

GoCardlessApiException parseError(String responseBody) {
ApiErrorResponse error = parseSingle(responseBody, "error", ApiErrorResponse.class);
GoCardlessApiException parseError(String responseBody, int statusCode) {
JsonElement json;
try {
json = new JsonParser().parse(responseBody);
} catch (JsonSyntaxException e) {
throw new MalformedResponseException(responseBody);
}
JsonElement errorElement = json.getAsJsonObject().get("error");
if (errorElement.isJsonPrimitive()) {
ApiErrorResponse error =
ApiErrorResponse.fromMessage(errorElement.getAsString(), statusCode);
return GoCardlessErrorMapper.toException(error);
}
ApiErrorResponse error = gson.fromJson(errorElement, ApiErrorResponse.class);
return GoCardlessErrorMapper.toException(error);
}
}
5 changes: 2 additions & 3 deletions src/main/java/com/gocardless/resources/ScenarioSimulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ private ScenarioSimulator() {
* `pending_submission` state. Not compatible with Autogiro, BECS NZ, and PAD mandates, which do
* not expire.</li>
* <li>`mandate_transferred`: Transitions a mandate through to `transferred`, having been
* submitted to the banks, set up successfully and then moved to a new bank account due to the
* customer using the UK's Current Account Switching Service (CASS). It must start in the
* `pending_submission` state. Only compatible with Bacs mandates.</li>
* submitted to the banks, set up successfully and then moved to a new bank account due. It must
* start in the `pending_submission` state. Only compatible with Bacs and SEPA mandates.</li>
* <li>`mandate_transferred_with_resubmission`: Transitions a mandate through `transferred` and
* resubmits it to the banks, can be caused be the UK's Current Account Switching Service (CASS)
* or when a customer contacts GoCardless to change their bank details. It must start in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public BillingRequestTemplateGetRequest get(String identity) {
}

/**
*
*
*/
public BillingRequestTemplateCreateRequest create() {
return new BillingRequestTemplateCreateRequest(httpClient);
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/gocardless/services/CustomerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,20 @@ protected boolean hasBody() {
* Returns a [cursor-paginated](#api-usage-cursor-pagination) list of your customers.
*/
public static final class CustomerListRequest<S> extends ListRequest<S, Customer> {
private ActionRequired actionRequired;
private CreatedAt createdAt;
private Currency currency;
private SortDirection sortDirection;
private SortField sortField;

/**
* Boolean indicating whether the customer has any actions required.
*/
public CustomerListRequest<S> withActionRequired(ActionRequired actionRequired) {
this.actionRequired = actionRequired;
return this;
}

/**
* Cursor pointing to the start of the desired set.
*/
Expand Down Expand Up @@ -428,6 +437,9 @@ public CustomerListRequest<S> withHeader(String headerName, String headerValue)
protected Map<String, Object> getQueryParams() {
ImmutableMap.Builder<String, Object> params = ImmutableMap.builder();
params.putAll(super.getQueryParams());
if (actionRequired != null) {
params.put("action_required", actionRequired);
}
if (createdAt != null) {
params.putAll(createdAt.getQueryParams());
}
Expand Down Expand Up @@ -458,6 +470,18 @@ protected TypeToken<List<Customer>> getTypeToken() {
return new TypeToken<List<Customer>>() {};
}

public enum ActionRequired {
@SerializedName("true")
TRUE, @SerializedName("false")
FALSE, @SerializedName("unknown")
UNKNOWN;

@Override
public String toString() {
return name().toLowerCase();
}
}

public enum Currency {
@SerializedName("AUD")
AUD, @SerializedName("CAD")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,25 @@ public MandateImportEntryCreateRequest withMandate(Mandate mandate) {
return this;
}

/**
* This field is ACH specific, sometimes referred to as [SEC
* code](https://www.moderntreasury.com/learn/sec-codes).
*
* This is the way that the payer gives authorisation to the merchant. web: Authorisation is
* Internet Initiated or via Mobile Entry (maps to SEC code: WEB) telephone: Authorisation
* is provided orally over telephone (maps to SEC code: TEL) paper: Authorisation is
* provided in writing and signed, or similarly authenticated (maps to SEC code: PPD)
*
*/
public MandateImportEntryCreateRequest withMandateAuthorisationSource(
Mandate.AuthorisationSource authorisationSource) {
if (mandate == null) {
mandate = new Mandate();
}
mandate.withAuthorisationSource(authorisationSource);
return this;
}

/**
* Key-value store of custom data. Up to 3 keys are permitted, with key names up to 50
* characters and values up to 500 characters.
Expand Down Expand Up @@ -856,9 +875,26 @@ public Links withMandateImport(String mandateImport) {
}

public static class Mandate {
private AuthorisationSource authorisationSource;
private Map<String, String> metadata;
private String reference;

/**
* This field is ACH specific, sometimes referred to as [SEC
* code](https://www.moderntreasury.com/learn/sec-codes).
*
* This is the way that the payer gives authorisation to the merchant. web:
* Authorisation is Internet Initiated or via Mobile Entry (maps to SEC code: WEB)
* telephone: Authorisation is provided orally over telephone (maps to SEC code: TEL)
* paper: Authorisation is provided in writing and signed, or similarly authenticated
* (maps to SEC code: PPD)
*
*/
public Mandate withAuthorisationSource(AuthorisationSource authorisationSource) {
this.authorisationSource = authorisationSource;
return this;
}

/**
* Key-value store of custom data. Up to 3 keys are permitted, with key names up to 50
* characters and values up to 500 characters.
Expand All @@ -877,6 +913,19 @@ public Mandate withReference(String reference) {
this.reference = reference;
return this;
}

public enum AuthorisationSource {
@SerializedName("web")
WEB, @SerializedName("telephone")
TELEPHONE, @SerializedName("paper")
PAPER, @SerializedName("unknown")
UNKNOWN;

@Override
public String toString() {
return name().toLowerCase();
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public OutboundPaymentService(HttpClient httpClient) {
}

/**
*
*
*/
public OutboundPaymentCreateRequest create() {
return new OutboundPaymentCreateRequest(httpClient);
Expand Down
29 changes: 21 additions & 8 deletions src/test/java/com/gocardless/http/ResponseParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void shouldParsePage() throws IOException {
public void shouldParseInvalidApiUsageError() throws IOException {
URL resource = Resources.getResource("fixtures/invalid_api_usage.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 400);
assertThat(exception).isInstanceOf(InvalidApiUsageException.class);
assertThat(exception.getType()).isEqualTo(INVALID_API_USAGE);
assertThat(exception.getMessage()).isEqualTo("Invalid document structure");
Expand All @@ -88,7 +88,7 @@ public void shouldParseInvalidApiUsageError() throws IOException {
public void shouldParseInvalidStateError() throws IOException {
URL resource = Resources.getResource("fixtures/invalid_state.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 409);
assertThat(exception).isInstanceOf(InvalidStateException.class);
assertThat(exception.getType()).isEqualTo(INVALID_STATE);
assertThat(exception.getMessage()).isEqualTo("Bank account already exists");
Expand All @@ -108,7 +108,7 @@ public void shouldParseInvalidStateError() throws IOException {
public void shouldParseValidationFailedError() throws IOException {
URL resource = Resources.getResource("fixtures/validation_failed.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 422);
assertThat(exception).isInstanceOf(ValidationFailedException.class);
assertThat(exception.getType()).isEqualTo(VALIDATION_FAILED);
assertThat(exception.getMessage()).isEqualTo(
Expand All @@ -133,7 +133,7 @@ public void shouldParseValidationFailedError() throws IOException {
public void shouldParseInternalError() throws IOException {
URL resource = Resources.getResource("fixtures/internal_error.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 500);
assertThat(exception).isInstanceOf(GoCardlessInternalException.class);
assertThat(exception.getType()).isEqualTo(GOCARDLESS);
assertThat(exception.getMessage()).isEqualTo("THE BEES THEY'RE IN MY EYES");
Expand All @@ -145,19 +145,32 @@ public void shouldParseInternalError() throws IOException {
assertThat(exception.getErrors()).isEmpty();
}

@Test
public void shouldParseStringError() throws IOException {
URL resource = Resources.getResource("fixtures/string_error.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody, 400);
assertThat(exception).isInstanceOf(GoCardlessInternalException.class);
assertThat(exception.getType()).isEqualTo(GOCARDLESS);
assertThat(exception.getMessage()).isEqualTo("bank_authorisation_expired");
assertThat(exception.getErrorMessage()).isEqualTo("bank_authorisation_expired");
assertThat(exception.getCode()).isEqualTo(400);
assertThat(exception.getErrors()).isEmpty();
}

@Test
public void shouldHandleNonJsonResponse() throws IOException {
exception.expect(MalformedResponseException.class);
URL resource = Resources.getResource("fixtures/non_json_response.html");
String responseBody = Resources.toString(resource, UTF_8);
parser.parseError(responseBody);
parser.parseError(responseBody, 500);
}

@Test
public void shouldHandleAuthenticationError() throws IOException {
URL resource = Resources.getResource("fixtures/unauthorized_error.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 401);
assertThat(exception).isInstanceOf(AuthenticationException.class);
assertThat(exception.getMessage()).isEqualTo("Unauthorized");
assertThat(exception.getDocumentationUrl())
Expand All @@ -174,7 +187,7 @@ public void shouldHandleAuthenticationError() throws IOException {
public void shouldHandleRateLimitError() throws IOException {
URL resource = Resources.getResource("fixtures/rate_limit_exceeded.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 429);
assertThat(exception).isInstanceOf(RateLimitException.class);
assertThat(exception.getMessage()).isEqualTo("Rate limit exceeded");
assertThat(exception.getDocumentationUrl())
Expand All @@ -191,7 +204,7 @@ public void shouldHandleRateLimitError() throws IOException {
public void shouldHandlePermissionException() throws IOException {
URL resource = Resources.getResource("fixtures/insufficient_permission.json");
String responseBody = Resources.toString(resource, UTF_8);
GoCardlessApiException exception = parser.parseError(responseBody);
GoCardlessApiException exception = parser.parseError(responseBody, 403);
assertThat(exception).isInstanceOf(PermissionException.class);
assertThat(exception.getMessage()).isEqualTo("Insufficient permissions");
assertThat(exception.getDocumentationUrl()).isEqualTo(
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/fixtures/string_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"error": "bank_authorisation_expired"}