Skip to content

Commit 02c1ed5

Browse files
committed
Add support for Bank Statements
Bank Statement models and methods added Bank Feed Connection models & methods merged into BankFeed package BankFeedTests created.
1 parent f688d00 commit 02c1ed5

26 files changed

Lines changed: 2337 additions & 103 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/target/
2+
/src/main/resources/
23
.classpath
34
.project
45
.settings

README.md

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ For those using maven, add the dependency and repository to your pom.xml
1414
<dependency>
1515
<groupId>com.xero</groupId>
1616
<artifactId>xero-java-sdk</artifactId>
17-
<version>1.2.0</version>
17+
<version>1.3.0</version>
1818
</dependency>
1919

2020
<repositories>
@@ -412,6 +412,122 @@ List<Invoice> InvoiceList24hour = client.getInvoices(cal.getTime(),null,null);
412412
System.out.println("How many invoices modified in last 24 hours?: " + InvoiceList24hour.size());
413413
```
414414

415+
**BankFeed Endpoints**
416+
417+
Currently, BankFeed endpoints (FeedConnection & Statements) is limited beta financial institutions who are engaged with Xero. Once these endpoints have been enabled for your Xero Partner App, use the following pattern to make API calls.
418+
419+
```java
420+
import com.xero.api.*;
421+
import com.xero.api.ApiClient;
422+
import com.xero.models.bankfeeds.*;
423+
import com.xero.models.bankfeeds.Statements;
424+
import com.xero.models.bankfeeds.FeedConnection.AccountTypeEnum;
425+
426+
import com.fasterxml.jackson.core.type.TypeReference;
427+
import org.threeten.bp.LocalDate;
428+
429+
// Get Xero API Resource - DEMONSTRATION ONLY get token from Cookie
430+
TokenStorage storage = new TokenStorage();
431+
String token = storage.get(request,"token");
432+
String tokenSecret = storage.get(request,"tokenSecret");
433+
434+
// Initialize the BankFeedApi object and set the token & secret
435+
ApiClient apiClientForBankFeeds = new ApiClient(config.getBankFeedsUrl(),null,null,null);
436+
BankFeedsApi bankFeedsApi = new BankFeedsApi(apiClientForBankFeeds);
437+
bankFeedsApi.setOAuthToken(token, tokenSecret);
438+
Map<String, String> params = null;
439+
440+
// Get ALL Feed Connections
441+
try {
442+
FeedConnections fc = bankFeedsApi.getFeedConnections(null);
443+
System.out.println("Total Banks found: " + fc.getItems().size());
444+
} catch (Exception e) {
445+
System.out.println(e.toString());
446+
}
447+
448+
// Get one Feed Connection
449+
try {
450+
FeedConnections fc = bankFeedsApi.getFeedConnections(null);
451+
FeedConnection oneFC = bankFeedsApi.getFeedConnection("123456789",null);
452+
System.out.println("One Bank: " + oneFC.getAccountName());
453+
} catch (Exception e) {
454+
System.out.println(e.toString());
455+
}
456+
457+
try {
458+
FeedConnection newBank = new FeedConnection();
459+
newBank.setAccountName("SDK Bank " + SampleData.loadRandomNum());
460+
newBank.setAccountNumber("1234" + SampleData.loadRandomNum());
461+
newBank.setAccountType(AccountTypeEnum.BANK);
462+
newBank.setAccountToken("foobar" + SampleData.loadRandomNum());
463+
newBank.setCurrency("GBP");
464+
465+
FeedConnections arrayFeedConnections = new FeedConnections();
466+
arrayFeedConnections.addItemsItem(newBank);
467+
468+
FeedConnections fc1 = bankFeedsApi.createFeedConnections(arrayFeedConnections, null);
469+
System.out.println("New Bank with status: " + fc1.getItems().get(0).getStatus());
470+
} catch (Exception e) {
471+
System.out.println(e.toString());
472+
}
473+
474+
475+
// Create Bank Statement
476+
// Create One Statement
477+
try {
478+
Statements arrayOfStatements = new Statements();
479+
Statement newStatement = new Statement();
480+
LocalDate stDate = LocalDate.of(2018, 9, 01);
481+
newStatement.setStartDate(stDate);
482+
LocalDate endDate = LocalDate.of(2018, 9, 15);
483+
newStatement.endDate(endDate);
484+
StartBalance stBalance = new StartBalance();
485+
stBalance.setAmount("100");
486+
stBalance.setCreditDebitIndicator(CreditDebitIndicator.CREDIT);
487+
newStatement.setStartBalance(stBalance);
488+
489+
EndBalance endBalance = new EndBalance();
490+
endBalance.setAmount("300");
491+
endBalance.setCreditDebitIndicator(CreditDebitIndicator.CREDIT);
492+
newStatement.endBalance(endBalance);
493+
494+
FeedConnections fc = bankFeedsApi.getFeedConnections(null);
495+
newStatement.setFeedConnectionId(fc.getItems().get(0).getId().toString());
496+
497+
StatementLine newStatementLine = new StatementLine();
498+
newStatementLine.setAmount("50");
499+
newStatementLine.setChequeNumber("123");
500+
newStatementLine.setDescription("My new line");
501+
newStatementLine.setCreditDebitIndicator(CreditDebitIndicator.CREDIT);
502+
newStatementLine.setReference("Foobar");
503+
newStatementLine.setPayeeName("StarLord");
504+
newStatementLine.setTransactionId("1234");
505+
LocalDate postedDate = LocalDate.of(2017, 9, 05);
506+
newStatementLine.setPostedDate(postedDate);
507+
508+
StatementLines arrayStatementLines = new StatementLines();
509+
arrayStatementLines.add(newStatementLine);
510+
511+
newStatement.setStatementLines(arrayStatementLines);
512+
arrayOfStatements.addItemsItem(newStatement);
513+
Statements rStatements = bankFeedsApi.createStatements(arrayOfStatements, params);
514+
515+
System.out.println("New Bank Statement Status: " + rStatements.getItems().get(0).getStatus());
516+
517+
} catch (Exception e) {
518+
// Error throw is of type Statements - it contains an array of Errors.
519+
TypeReference<Statements> typeRef = new TypeReference<Statements>() {};
520+
Statements statementErrors = apiClientForBankFeeds.getObjectMapper().readValue(e.getMessage(), typeRef);
521+
System.out.println(statementErrors.getItems().get(0).getErrors().get(0).getDetail());
522+
}
523+
524+
525+
526+
527+
```
528+
529+
530+
415531
**Exception Handling**
416532

417533
Below is an example of how how to handle errors.

pom.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<groupId>com.xero</groupId>
55
<artifactId>xero-java-sdk</artifactId>
66
<packaging>jar</packaging>
7-
<version>1.2.0</version>
7+
<version>1.3.0</version>
88
<name>Xero-Java SDK</name>
99
<url>http://maven.apache.org</url>
1010
<dependencies>
@@ -45,7 +45,20 @@
4545
<artifactId>junit</artifactId>
4646
<version>${junit-version}</version>
4747
<scope>test</scope>
48+
<exclusions>
49+
<exclusion>
50+
<groupId>org.hamcrest</groupId>
51+
<artifactId>hamcrest-core</artifactId>
52+
</exclusion>
53+
</exclusions>
4854
</dependency>
55+
<!-- This will get hamcrest-core automatically -->
56+
<dependency>
57+
<groupId>org.hamcrest</groupId>
58+
<artifactId>hamcrest-library</artifactId>
59+
<version>1.3</version>
60+
<scope>test</scope>
61+
</dependency>
4962
<dependency>
5063
<groupId>org.apache.httpcomponents</groupId>
5164
<artifactId>httpclient</artifactId>

src/main/java/com/xero/api/ApiClient.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.google.api.client.http.HttpRequestInitializer;
1212
import com.google.api.client.http.HttpTransport;
1313
import com.google.api.client.json.Json;
14-
import com.xero.api.client.*;
1514

1615
import java.io.IOException;
1716
import java.io.OutputStream;

src/main/java/com/xero/api/Config.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public interface Config {
1818

1919
String getAssetsUrl();
2020

21-
String getFeedConnectionsUrl();
21+
String getBankFeedsUrl();
2222

2323
String getRequestTokenUrl();
2424

src/main/java/com/xero/api/JsonConfig.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ public class JsonConfig implements Config {
2424
private String API_ENDPOINT_URL = "https://api.xero.com/api.xro/2.0/";
2525
private String FILES_ENDPOINT_URL = "https://api.xero.com/files.xro/1.0/";
2626
private String ASSETS_ENDPOINT_URL = "https://api.xero.com/assets.xro/1.0";
27-
private String FEEDCONNECTIONS_ENDPOINT_URL = "https://api.xero.com/bankfeeds.xro/1.0";
27+
private String BANKFEEDS_ENDPOINT_URL = "https://api.xero.com/bankfeeds.xro/1.0";
2828
private String REQUEST_TOKEN_URL = "https://api.xero.com/oauth/RequestToken";
2929
private String AUTHENTICATE_URL = "https://api.xero.com/oauth/Authorize";
3030
private String ACCESS_TOKEN_URL = "https://api.xero.com/oauth/AccessToken";
3131
private String API_ENDPOINT_STEM = "/api.xro/2.0/";
3232
private String FILES_ENDPOINT_STEM = "/files.xro/1.0/";
3333
private String ASSETS_ENDPOINT_STEM = "/assets.xro/1.0/";
34-
private String FEEDCONNECTIONS_ENDPOINT_STEM = "/bankfeeds.xro/1.0/";
34+
private String BANKFEEDS_ENDPOINT_STEM = "/bankfeeds.xro/1.0/";
3535
private String REQUEST_TOKEN_STEM = "/oauth/RequestToken";
3636
private String AUTHENTICATE_STEM = "/oauth/Authorize";
3737
private String ACCESS_TOKEN_STEM = "/oauth/AccessToken";
@@ -110,8 +110,8 @@ public String getAssetsUrl() {
110110
}
111111

112112
@Override
113-
public String getFeedConnectionsUrl() {
114-
return FEEDCONNECTIONS_ENDPOINT_URL;
113+
public String getBankFeedsUrl() {
114+
return BANKFEEDS_ENDPOINT_URL;
115115
}
116116

117117
@Override
@@ -131,7 +131,7 @@ public String getAccessTokenUrl() {
131131

132132
@Override
133133
public String getUserAgent() {
134-
return USER_AGENT + " " + CONSUMER_KEY + " [Xero-Java-1.2.0]";
134+
return USER_AGENT + " " + CONSUMER_KEY + " [Xero-Java-1.3.0]";
135135
}
136136

137137
@Override
@@ -336,11 +336,11 @@ private void load() {
336336
ASSETS_ENDPOINT_URL = API_BASE_URL + ASSETS_ENDPOINT_STEM;
337337
}
338338

339-
if (jsonObject.containsKey("FeedConnectionsEndpointPath")) {
340-
String feedConnectionsEndpointPath = (String) jsonObject.get("feedConnectionsEndpointPath");
341-
FEEDCONNECTIONS_ENDPOINT_URL = API_BASE_URL + feedConnectionsEndpointPath;
339+
if (jsonObject.containsKey("BankFeedsEndpointPath")) {
340+
String BankFeedsEndpointPath = (String) jsonObject.get("BankFeedsEndpointPath");
341+
BANKFEEDS_ENDPOINT_URL = API_BASE_URL + BankFeedsEndpointPath;
342342
} else {
343-
FEEDCONNECTIONS_ENDPOINT_URL = API_BASE_URL + FEEDCONNECTIONS_ENDPOINT_STEM;
343+
BANKFEEDS_ENDPOINT_URL = API_BASE_URL + BANKFEEDS_ENDPOINT_STEM;
344344
}
345345

346346
if (jsonObject.containsKey("RequestTokenPath")) {

src/main/java/com/xero/api/OAuthRequestResource.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,13 @@
4444
import org.apache.logging.log4j.LogManager;
4545
import org.apache.logging.log4j.Logger;
4646

47+
import com.fasterxml.jackson.core.type.TypeReference;
48+
import com.fasterxml.jackson.databind.JsonNode;
49+
import com.fasterxml.jackson.databind.ObjectMapper;
4750
import com.google.api.client.auth.oauth.OAuthSigner;
4851
import com.google.api.client.http.GenericUrl;
52+
import com.xero.api.exception.XeroExceptionHandler;
53+
import com.xero.models.bankfeeds.Statements;
4954

5055
public class OAuthRequestResource {
5156
private String token;
@@ -237,7 +242,9 @@ public final Map<String, String> execute() throws IOException,XeroApiException
237242
if(logger.isInfoEnabled()){
238243
logger.info("------------------ POST: BODY -------------------");
239244
logger.info(this.body);
245+
240246
}
247+
241248
httppost.setEntity(new StringEntity(this.body, "utf-8"));
242249
this.createParameters().intercept(httppost,url);
243250
httppost.setConfig(requestConfig.build());
@@ -293,17 +300,20 @@ public final Map<String, String> execute() throws IOException,XeroApiException
293300
String content = "";
294301
entity = response.getEntity();
295302
if(entity != null) {
296-
content = EntityUtils.toString(entity);
303+
content = EntityUtils.toString(entity);
297304
}
305+
298306
int code = response.getStatusLine().getStatusCode();
299-
if (code == 204) {
307+
308+
if (code == 204) {
300309
content = "<Response><Status>DELETED</Status></Response>";
301310
}
302311
if (code != 200 && code != 201 && code != 202 && code != 204) {
303312
Header rateHeader = response.getFirstHeader("x-rate-limit-problem");
304313
if (rateHeader != null) {
305314
content += "&rate=" + rateHeader.getValue().toLowerCase();
306315
}
316+
307317
XeroApiException e = new XeroApiException(code,content);
308318
throw e;
309319
}
@@ -368,4 +378,14 @@ public OAuthParameters createParameters()
368378
public void setProxy(String host, int port, boolean httpsEnabled) {
369379

370380
}
381+
382+
public static boolean isJSONValid(String jsonInString ) {
383+
try {
384+
final ObjectMapper mapper = new ObjectMapper();
385+
mapper.readTree(jsonInString);
386+
return true;
387+
} catch (IOException e) {
388+
return false;
389+
}
390+
}
371391
}

src/main/java/com/xero/api/XeroApiException.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
package com.xero.api;
22

3+
import com.xero.api.exception.XeroExceptionHandler;
34
import com.xero.model.ApiException;
5+
import com.xero.models.bankfeeds.Statements;
46

7+
import java.io.IOException;
58
import java.util.HashMap;
69
import java.util.Map;
710

11+
import org.json.simple.JSONObject;
12+
import org.json.simple.parser.JSONParser;
13+
import org.json.simple.parser.ParseException;
14+
15+
import com.fasterxml.jackson.core.type.TypeReference;
16+
import com.fasterxml.jackson.databind.JsonNode;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
19+
820
public class XeroApiException extends RuntimeException {
921

1022
private static final long serialVersionUID = 1L;
@@ -20,6 +32,7 @@ public XeroApiException(int responseCode) {
2032

2133
public XeroApiException(int responseCode, String message) {
2234
super(responseCode + " response: " + message);
35+
2336
this.responseCode = responseCode;
2437
this.message = message;
2538
}
@@ -59,4 +72,5 @@ public Map<String, String> getMessages() {
5972
public ApiException getApiException() {
6073
return apiException;
6174
}
75+
6276
}

src/main/java/com/xero/api/XeroClient.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.xero.api.exception.XeroExceptionHandler;
55
import com.xero.api.jaxb.XeroJAXBMarshaller;
66
import com.xero.model.*;
7+
import com.xero.models.assets.*;
78

89
import javax.xml.bind.JAXBElement;
910

@@ -1920,4 +1921,41 @@ public ByteArrayInputStream getAttachmentContentById(String endpoint, String gui
19201921
return getInputStream(endpoint + "/" + guid + "/Attachments/" + attachmentid, null, null, accept);
19211922
}
19221923

1924+
//Assets
1925+
public Assets getAssets() throws XeroApiException, ParseException {
1926+
1927+
try {
1928+
Map<String, String> params = new HashMap<String, String>();
1929+
1930+
params.put("Status","REGISTERED");
1931+
String resource = "https://api.xero.com/assets.xro/1.0/Assets";
1932+
String response = this.get2(resource, null, params);
1933+
1934+
TypeReference<Assets> typeRef = new TypeReference<Assets>() {};
1935+
ApiClient apiClient = new ApiClient();
1936+
1937+
return apiClient.getObjectMapper().readValue(response, typeRef);
1938+
1939+
} catch (IOException e) {
1940+
throw xeroExceptionHandler.convertException(e);
1941+
}
1942+
}
1943+
1944+
protected String get2(String resource, Date modifiedAfter, Map<String, String> params) throws IOException {
1945+
1946+
OAuthRequestResource req = new OAuthRequestResource(config, signerFactory, resource, "GET", null, params,"application/json");
1947+
req.setToken(token);
1948+
req.setTokenSecret(tokenSecret);
1949+
if (modifiedAfter != null) {
1950+
req.setIfModifiedSince(modifiedAfter);
1951+
}
1952+
1953+
try {
1954+
Map<String, String> resp = req.execute();
1955+
Object r = resp.get("content");
1956+
return r.toString();
1957+
} catch (IOException ioe) {
1958+
throw xeroExceptionHandler.convertException(ioe);
1959+
}
1960+
}
19231961
}

0 commit comments

Comments
 (0)