From 7a88512556a279dfcc8bdea2cdd4dc1c97e7b3bd Mon Sep 17 00:00:00 2001 From: Johannes Graf Date: Wed, 25 Mar 2026 00:41:18 +0100 Subject: [PATCH] add invoice file download to InvoiceChain Adds downloadFile(id) method to InvoiceChain that calls GET /invoices/{id}/file and returns the raw bytes. Adds downloadFile method to RequestContext for binary responses. --- .../lexoffice/java/sdk/RequestContext.java | 22 ++++++++++++++++++- .../java/sdk/chain/InvoiceChain.java | 17 ++++++++++++++ .../chain/InvoiceChainIntegrationTest.java | 20 +++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/focus_shift/lexoffice/java/sdk/RequestContext.java b/src/main/java/de/focus_shift/lexoffice/java/sdk/RequestContext.java index 962cf7a..4a007e5 100644 --- a/src/main/java/de/focus_shift/lexoffice/java/sdk/RequestContext.java +++ b/src/main/java/de/focus_shift/lexoffice/java/sdk/RequestContext.java @@ -10,6 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.web.client.RestTemplate; import tools.jackson.databind.json.JsonMapper; @@ -32,7 +33,7 @@ public class RequestContext { .build(); this.requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); restTemplate = new RestTemplate(requestFactory); - restTemplate.setMessageConverters(List.of(new JacksonJsonHttpMessageConverter(getJsonMapper()))); + restTemplate.setMessageConverters(List.of(new JacksonJsonHttpMessageConverter(getJsonMapper()), new ByteArrayHttpMessageConverter())); } @@ -71,6 +72,25 @@ public synchronized R execute(RestUriBuilder uriBuilder, HttpMethod metho return response.getBody(); } + public synchronized byte[] downloadFile(RestUriBuilder uriBuilder) { + + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(List.of(MediaType.APPLICATION_PDF, MediaType.ALL)); + headers.add("Authorization", "Bearer " + apiBuilder.getApiToken()); + + checkThrottlePeriod(); + + HttpEntity requestEntity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(uriBuilder.build(), HttpMethod.GET, requestEntity, byte[].class); + lastCall = System.currentTimeMillis(); + if (apiBuilder.throttleProviderPresent()) { + apiBuilder.getThrottleProvider() + .apiCalled(); + } + + return response.getBody(); + } + public synchronized void delete(RestUriBuilder uriBuilder) { HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChain.java b/src/main/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChain.java index 42dd0d4..9b32ff7 100644 --- a/src/main/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChain.java +++ b/src/main/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChain.java @@ -16,6 +16,10 @@ public Invoice get(String id) { return new Get(context).get(id); } + public byte[] downloadFile(String id) { + return new DownloadFile(context).download(id); + } + public Create create() { return new Create(context); } @@ -35,6 +39,19 @@ public Invoice get(String id) { } } + protected static class DownloadFile extends ExecutableRequestChain { + + public DownloadFile(RequestContext context) { + super(context, "/invoices"); + } + + @SneakyThrows + public byte[] download(String id) { + getUriBuilder().appendPath("/" + id + "/file"); + return getContext().downloadFile(getUriBuilder()); + } + } + public static class Create extends ExecutableRequestChain { private static final ParameterizedTypeReference TYPE_REFERENCE = new ParameterizedTypeReference() { }; diff --git a/src/test/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChainIntegrationTest.java b/src/test/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChainIntegrationTest.java index adab16b..60af787 100644 --- a/src/test/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChainIntegrationTest.java +++ b/src/test/java/de/focus_shift/lexoffice/java/sdk/chain/InvoiceChainIntegrationTest.java @@ -166,6 +166,26 @@ void createInvoice() { true))); } + @Test + void downloadInvoiceFile() { + String invoiceId = "e9066f04-8cc7-4616-93f8-ac9571ec5e11"; + byte[] pdfContent = "%PDF-1.4 fake pdf content".getBytes(); + + stubFor( + get(urlPathEqualTo("/v1/invoices/" + invoiceId + "/file")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/pdf") + .withHeader( + "Content-Disposition", + "attachment; filename=\"RE1019.pdf\"") + .withBody(pdfContent))); + + byte[] result = lexofficeApi.invoice().downloadFile(invoiceId); + + assertThat(result).isEqualTo(pdfContent); + } + @Test void createInvoiceFinalized() { stubFor(