zoho interface

This commit is contained in:
Ralph Soika 2025-04-24 17:13:19 +02:00
parent a756997247
commit c47a6ed4ac
8 changed files with 588 additions and 53 deletions

View file

@ -8,6 +8,7 @@ https://api-console.zoho.eu/
https://api-console.zoho.eu/client/1000.NLTM2KUEHI696MSLGEIANATVGGX7IN https://api-console.zoho.eu/client/1000.NLTM2KUEHI696MSLGEIANATVGGX7IN
``` ```
OrganizationID=20105697367
client ID= 1000.NLTM2KUEHI696MSLGEIANATVGGX7IN client ID= 1000.NLTM2KUEHI696MSLGEIANATVGGX7IN
client secret= ee9ba49f16e77c64abb89cea13542d2326d8d03ce8 client secret= ee9ba49f16e77c64abb89cea13542d2326d8d03ce8
``` ```

View file

@ -0,0 +1,74 @@
package com.alexanderlogistics.zoho;
/*******************************************************************************
* Imixs Workflow Technology
* Copyright (C) 2003, 2008 Imixs Software Solutions GmbH,
* http://www.imixs.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You can receive a copy of the GNU General Public
* License at http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Imixs Software Solutions GmbH - initial API and implementation
* Ralph Soika
*
*******************************************************************************/
import java.util.logging.Logger;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.office.config.ConfigController;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
/**
* Der ZohoController ist für das Admin Interface um Access Tokens zu erzeugen.
*
* @see admin/zoho.xhml
*
* @author rsoika
*
*/
@Named("zohoController")
@ApplicationScoped
public class ZohoController extends ConfigController {
public static final String ZOHO_CONFIGURATION = "ZOHO_CONFIGURATION";
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(ZohoController.class.getName());
@Inject
ZohoOAuthManager zohoOAuthManager;
public ZohoController() {
super();
setName(ZOHO_CONFIGURATION);
}
/**
* Ruft zohoOAuhtManager.updateToken auf
*/
public void updateAccessToken() {
//
try {
zohoOAuthManager.updateTokens(this.getWorkitem().getItemValueString("zoho.code"));
} catch (PluginException e) {
String message = "Failed to generate token: " + e.getMessage();
logger.warning(message);
this.getWorkitem().setItemValue("message", message);
}
}
}

View file

@ -117,9 +117,9 @@ public class ZohoOAuthGrantService {
logger.info("│ ├── location=" + location); logger.info("│ ├── location=" + location);
logger.info("│ ├── accounts-server=" + accountsServer); logger.info("│ ├── accounts-server=" + accountsServer);
zohoOAuthManager.updateTokens(code); // zohoOAuthManager.updateTokens(code);
return "Zoho Grant Token received!"; return "Zoho Callback received!";
} }
} }

View file

@ -8,18 +8,18 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Optional;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.exceptions.InvalidAccessException; import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.office.config.ConfigService;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.RunAs; import jakarta.annotation.security.RunAs;
import jakarta.ejb.EJB;
import jakarta.ejb.Lock; import jakarta.ejb.Lock;
import jakarta.ejb.LockType; import jakarta.ejb.LockType;
import jakarta.ejb.Singleton; import jakarta.ejb.Singleton;
import jakarta.inject.Inject;
import jakarta.json.bind.Jsonb; import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbBuilder;
@ -39,13 +39,11 @@ public class ZohoOAuthManager {
private String accessToken; private String accessToken;
private long accessTokenExpiry; // Timestamp (System.currentTimeMillis() + expires_in*1000) private long accessTokenExpiry; // Timestamp (System.currentTimeMillis() + expires_in*1000)
@Inject private String redirectURI = "https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant";
@ConfigProperty(name = "zoho.client.id", defaultValue = "1000.NLTM2KUEHI696MSLGEIANATVGGX7IN") public static final String ERROR_CONFIG = "CONFIG_ERROR";
Optional<String> clientID;
@Inject @EJB
@ConfigProperty(name = "zoho.client.secret", defaultValue = "ee9ba49f16e77c64abb89cea13542d2326d8d03ce8") ConfigService configService;
Optional<String> clientSecret;
private HttpClient httpClient; private HttpClient httpClient;
private Jsonb jsonb; private Jsonb jsonb;
@ -67,35 +65,25 @@ public class ZohoOAuthManager {
* *
* *
* @param code * @param code
* @throws PluginException
*/ */
@Lock(LockType.WRITE) @Lock(LockType.WRITE)
public void updateTokens(String code) { public void updateTokens(String code) throws PluginException {
logger.info("├── update Zoho tokens"); logger.info("├── update Zoho tokens");
if (!clientID.isPresent()) { refreshToken = null;
throw new InvalidAccessException("Missing client ID"); accessToken = null;
}
if (!clientSecret.isPresent()) {
throw new InvalidAccessException("Missing client secret");
}
try { try {
String redirectURI = "https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant"; ItemCollection zohoConfig = configService.loadConfiguration(ZohoController.ZOHO_CONFIGURATION);
// Korrekte URL-Konstruktion mit Query-Parametern
// String requestUrl = "https://accounts.zoho.com/oauth/v2/token" +
// "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) +
// "&client_id=" + URLEncoder.encode(clientID.get(), StandardCharsets.UTF_8) +
// "&client_secret=" + URLEncoder.encode(clientSecret.get(),
// StandardCharsets.UTF_8) +
// "&redirect_uri=" + URLEncoder.encode("http://www.zoho.com/books",
// StandardCharsets.UTF_8) +
// "&grant_type=authorization_code";
String clientID = zohoConfig.getItemValueString("zoho.clientid");
String clientSecret = zohoConfig.getItemValueString("zoho.clientsecret");
String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" +
"?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) +
"&client_id=" + URLEncoder.encode(clientID.get(), StandardCharsets.UTF_8) + "&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) +
"&client_secret=" + URLEncoder.encode(clientSecret.get(), StandardCharsets.UTF_8) + "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) +
"&redirect_uri=" + URLEncoder.encode(redirectURI, StandardCharsets.UTF_8) + "&redirect_uri=" + URLEncoder.encode(redirectURI, StandardCharsets.UTF_8) +
"&grant_type=authorization_code"; "&grant_type=authorization_code";
@ -108,36 +96,44 @@ public class ZohoOAuthManager {
.build(); .build();
HttpResponse<String> response; HttpResponse<String> response;
response = httpClient.send(request, response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString()); HttpResponse.BodyHandlers.ofString());
logger.info("│ ├── Request statusCode=" + response.statusCode()); logger.info("│ ├── Response statusCode=" + response.statusCode());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
// 1. Response-Logging für Debugging // 1. Response-Logging für Debugging
logger.info("│ ├── Request statusCode=" + response.body()); logger.info("│ ├── Response Body=" + response.body());
if (response.body().contains("error")) {
throw new PluginException(ZohoOAuthManager.class.getSimpleName(),
ZohoOAuthManager.ERROR_CONFIG,
"Ungültiger Access Code!");
}
TokenResponse tokenResponse = jsonb.fromJson(response.body(), TokenResponse tokenResponse = jsonb.fromJson(response.body(),
TokenResponse.class); TokenResponse.class);
this.accessToken = tokenResponse.getAccess_token(); accessToken = tokenResponse.getAccess_token();
logger.info("│ ├── accessToken=" + accessToken);
this.refreshToken = tokenResponse.getRefresh_token();
logger.info("│ ├── refreshToken=" + refreshToken);
this.accessTokenExpiry = System.currentTimeMillis() +
(tokenResponse.getExpires_in() * 1000);
logger.info("│ ├── accessToken=" + accessToken);
refreshToken = tokenResponse.getRefresh_token();
logger.info("│ ├── refreshToken=" + refreshToken);
logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in()); logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in());
zohoConfig.setItemValue("zoho.accessToken", accessToken);
zohoConfig.setItemValue("zoho.refreshToken", refreshToken);
zohoConfig.setItemValue("zoho.expires", tokenResponse.getExpires_in());
zohoConfig.setItemValue("message", "Access Token OK");
configService.save(zohoConfig);
} else { } else {
throw new RuntimeException("Failed to get tokens: " + throw new RuntimeException("Failed to get tokens: " +
response.statusCode() + " - " + response.body()); response.statusCode() + " - " + response.body());
} }
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); throw new PluginException(ZohoOAuthManager.class.getSimpleName(),
ZohoOAuthManager.ERROR_CONFIG, "Unable to resolve access tokens: " + e.getMessage());
} }
} }
@Lock(LockType.READ) @Lock(LockType.READ)
@ -146,4 +142,59 @@ public class ZohoOAuthManager {
return null; return null;
} }
/**
* This method updates the Access Token based on the current Refresh Token
*
* @throws IOException
*/
public void testRefreshAccessToken() throws IOException {
// logger.info("├── refresh Zoho access token");
// refreshToken =
// "1000.cf458816e97f848df8134ce1c7e923a1.5b9dbdee317e35d7f0c619e7648d55ff";
// try {
// String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" +
// "?refresh_token=" + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8) +
// "&client_id=" + URLEncoder.encode(clientID.get(), StandardCharsets.UTF_8) +
// "&client_secret=" + URLEncoder.encode(clientSecret.get(),
// StandardCharsets.UTF_8) +
// "&grant_type=refresh_token";
// logger.info("│ ├── Request URI=" + requestUrl);
// HttpRequest request = HttpRequest.newBuilder()
// .uri(URI.create(requestUrl))
// .header("Content-Type", "application/x-www-form-urlencoded")
// .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body!
// .build();
// HttpResponse<String> response;
// response = httpClient.send(request,
// HttpResponse.BodyHandlers.ofString());
// logger.info("│ ├── Response statusCode=" + response.statusCode());
// if (response.statusCode() == 200) {
// // 1. Response-Logging für Debugging
// logger.info("│ ├── Response Body=" + response.body());
// TokenResponse tokenResponse = jsonb.fromJson(response.body(),
// TokenResponse.class);
// String accessToken = tokenResponse.getAccess_token();
// logger.info("│ ├── accessToken=" + accessToken);
// logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in());
// } else {
// throw new RuntimeException("Failed to get tokens: " +
// response.statusCode() + " - " + response.body());
// }
// } catch (IOException | InterruptedException e) {
// throw new InvalidAccessException("Unable to resolve access tokens: " +
// e.getMessage());
// }
}
} }

View file

@ -23,9 +23,12 @@
<li> <li>
<h:link outcome="/pages/admin/oplist_export">OP List Export</h:link> <h:link outcome="/pages/admin/oplist_export">OP List Export</h:link>
</li> </li>
<li> <li>
<h:link outcome="/pages/admin/agl_params">Parameter</h:link> <h:link outcome="/pages/admin/agl_params">Parameter</h:link>
</li> </li>
<li>
<h:link outcome="/pages/admin/zoho">Zoho</h:link>
</li>
</ul> </ul>
</f:subview> </f:subview>

View file

@ -0,0 +1,150 @@
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:i="http://java.sun.com/jsf/composite/imixs" xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:marty="http://java.sun.com/jsf/composite/marty" template="/layout/template.xhtml">
<ui:define name="content">
<f:view>
<script type="text/javascript">
/*<![CDATA[*/
function updateStatustPanel(data) {
//initUserInput($('#userselector_id'));
if (data.status === 'success') {
// select with wildcard operator
$('[id$=status_panel]').imixsLayout();
}
}
/*]]>*/
</script>
<h:form id="import_form_id">
<div class="imixs-form">
<div class="imixs-header">
<h1>ZOHO - API </h1>
<!-- ########## Error ########## -->
<ui:include src="/error_message.xhtml" />
</div>
<div class="imixs-body">
<div class="imixs-tabs">
<ul>
<li><a href="#tab-1">Access Token </a></li>
</ul>
<div id="tab-1">
<!-- **** Export Buchunsstapel ***** -->
<div class="imixs-form-section">
<!-- API Tokens -->
<div class="imixs-form-section-2">
<dl>
<dt>Client ID</dt>
<dd>
<h:inputText required="false"
value="#{zohoController.workitem.item['zoho.clientid']}">
</h:inputText>
</dd>
</dl>
<dl>
<dt>Client Secret</dt>
<dd>
<h:inputSecret required="false" redisplay="true"
value="#{zohoController.workitem.item['zoho.clientsecret']}">
</h:inputSecret>
</dd>
</dl>
<dl>
<dt>Organization</dt>
<dd>
<h:inputText required="false"
value="#{zohoController.workitem.item['zoho.organization']}">
</h:inputText>
</dd>
</dl>
</div>
<!-- Grant Token -->
<div class="imixs-form-section-2">
<dl>
<dt>Access Code</dt>
<dd>
<h:inputSecret required="false" redisplay="true"
value="#{zohoController.workitem.item['zoho.code']}">
</h:inputSecret>
</dd>
</dl>
<p>
<span class="typcn typcn-lightbulb"></span> Fordern Sie einen ZOHO API COde
an
<ol>
<li>Melden Sie sich auf der <a href="https://api-console.zoho.eu/"
target="_blank">ZOHO API Console</a> an</li>
<li>
Legen Sie einen API Self Client an
</li>
<li>
Kopieren Sie Client ID und Client Secret sowie die Organization ID
</li>
<li>
Generieren Sie einen neuen Access Code
<br />
Scope:<br />
ZohoBooks.invoices.CREATE,ZohoBooks.invoices.READ,ZohoBooks.invoices.UPDATE,ZohoBooks.invoices.DELETE
</li>
<li>
Code über den Button "Create" anfordern und in das Feld "Access Code"
übertragen
</li>
<li>
Access Token über den Button "Access Token Generieren" speichern
</li>
</ol>
<h:commandButton actionListener="#{zohoController.updateAccessToken()}"
value="Access Token Generieren">
</h:commandButton>
<br />
<h:outputText value="#{zohoController.workitem.item['message']}" />
<br />
AccessToken:
<h:outputText value="#{zohoController.workitem.item['zoho.accesstoken']}" />
<br />
RefreshToken:
<h:outputText value="#{zohoController.workitem.item['zoho.refreshtoken']}" />
</p>
</div>
</div>
</div>
</div>
</div>
<div class="imixs-footer">
<h:outputLabel value="#{message.modified}: " />
<h:outputText value="#{zohoController.workitem.item['$modified']}">
<f:convertDateTime timeZone="#{message.timeZone}" type="both"
pattern="#{message.dateTimePattern}" />
</h:outputText>
<br />
<h:commandButton actionListener="#{zohoController.save}" value="#{message.save}">
</h:commandButton>
<h:commandButton value="#{message.close}" action="notes" />
</div>
</div>
</h:form>
</f:view>
</ui:define>
</ui:composition>

View file

@ -1,5 +1,8 @@
package com.alexanderlogistics.zoho; package com.alexanderlogistics.zoho;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
@ -35,11 +38,15 @@ public class TestUpdateTokens {
private HttpClient httpClient; private HttpClient httpClient;
private Jsonb jsonb; private Jsonb jsonb;
private String code = "1000.a6cff79178a76099a625eb41249a6c91.b3679ff9365902502d2e3ca55303d5d6"; private String redirectURI = "https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant";
private String clientID = "1000.GR5FTPZYB06HEWSFSSX4DJU3OBD4EJ"; private String clientID = "1000.GR5FTPZYB06HEWSFSSX4DJU3OBD4EJ";
private String clientSecret = "3f3e74c5212bbc924a7ebf77f887c6296db1106c47"; private String clientSecret = "3f3e74c5212bbc924a7ebf77f887c6296db1106c47";
private String code = null;
private String accessToken = null;
private String refreshToken = null;
@BeforeEach @BeforeEach
public void setup() throws PluginException, ModelException { public void setup() throws PluginException, ModelException {
logger.info("setup test environment ..."); logger.info("setup test environment ...");
@ -58,11 +65,11 @@ public class TestUpdateTokens {
public void testUpdateTokens() throws IOException { public void testUpdateTokens() throws IOException {
logger.info("├── update Zoho tokens"); logger.info("├── update Zoho tokens");
refreshToken = null;
accessToken = null;
code = "1000.48bd85a3965cf724f4691757cfd8d93b.8ae5e3ee7d70f90d307da4d289a9b067";
try { try {
String redirectURI = "https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant";
String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" +
"?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) +
"&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + "&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) +
@ -83,18 +90,22 @@ public class TestUpdateTokens {
response = httpClient.send(request, response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString()); HttpResponse.BodyHandlers.ofString());
logger.info("│ ├── Request statusCode=" + response.statusCode()); logger.info("│ ├── Response statusCode=" + response.statusCode());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
// 1. Response-Logging für Debugging // 1. Response-Logging für Debugging
logger.info("│ ├── Request statusCode=" + response.body()); logger.info("│ ├── Response Body=" + response.body());
assertNotNull(response.body());
assertFalse(response.body().contains("error"));
TokenResponse tokenResponse = jsonb.fromJson(response.body(), TokenResponse tokenResponse = jsonb.fromJson(response.body(),
TokenResponse.class); TokenResponse.class);
String accessToken = tokenResponse.getAccess_token(); accessToken = tokenResponse.getAccess_token();
assertNotNull(accessToken);
logger.info("│ ├── accessToken=" + accessToken); logger.info("│ ├── accessToken=" + accessToken);
String refreshToken = tokenResponse.getRefresh_token(); refreshToken = tokenResponse.getRefresh_token();
logger.info("│ ├── refreshToken=" + refreshToken); logger.info("│ ├── refreshToken=" + refreshToken);
logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in()); logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in());
} else { } else {
@ -108,4 +119,152 @@ public class TestUpdateTokens {
} }
/**
* Testet den Refresh Token
*
* POST:
* {Accounts_URL}/oauth/v2/token?refresh_token={refresh_token}&client_id={client_id}&client_secret={client_secret}&grant_type=refresh_token
*
* @throws IOException
*/
@Test
public void testRefreshAccessToken() throws IOException {
logger.info("├── refresh Zoho access token");
refreshToken = "1000.cf458816e97f848df8134ce1c7e923a1.5b9dbdee317e35d7f0c619e7648d55ff";
try {
String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" +
"?refresh_token=" + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8) +
"&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) +
"&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) +
"&grant_type=refresh_token";
logger.info("│ ├── Request URI=" + requestUrl);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body!
.build();
HttpResponse<String> response;
response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
logger.info("│ ├── Response statusCode=" + response.statusCode());
if (response.statusCode() == 200) {
// 1. Response-Logging für Debugging
logger.info("│ ├── Response Body=" + response.body());
TokenResponse tokenResponse = jsonb.fromJson(response.body(),
TokenResponse.class);
String accessToken = tokenResponse.getAccess_token();
logger.info("│ ├── accessToken=" + accessToken);
logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in());
} else {
throw new RuntimeException("Failed to get tokens: " +
response.statusCode() + " - " + response.body());
}
} catch (IOException | InterruptedException e) {
throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage());
}
}
/**
* Testet den eigentlichen API Zugriff mit Hilfe des zuvor generieren Access
* Token
*
* GET: https://{zohoapis_domain}/writer/api/v1/documents
*
* @see https://www.zoho.com/books/api/v3/introduction/#overview
* @throws IOException
*/
@Test
public void testGetOrganizations() throws IOException {
logger.info("├── get Zoho organizations");
accessToken = "1000.21912dc6af0906707f51d336fd98ab61.eb153952d0d86a828fcb7e18426c2c1a";
try {
String url = "https://www.zohoapis.eu/books/v3/organizations";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.header("Authorization", "Zoho-oauthtoken " + accessToken)
.build();
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString());
logger.info("│ ├── Response statusCode=" + response.statusCode());
// 1. Response-Logging für Debugging
logger.info("│ ├── Response Body=" + response.body());
if (response.statusCode() >= 300) {
throw new RuntimeException("API request failed with status code: " + response.statusCode());
}
} catch (IOException | InterruptedException e) {
throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage());
}
}
/**
* Testet den eigentlichen API Zugriff mit Hilfe des zuvor generieren Access
* Token
*
*
* curl --request GET \
* --url 'https://www.zohoapis.com/books/v3/invoices?organization_id=10234695' \
* --header 'Authorization: Zoho-oauthtoken
* 1000.41d9xxxxxxxxxxxxxxxxxxxxxxxxc2d1.8fccxxxxxxxxxxxxxxxxxxxxxxxx125f'
*
*
* @throws IOException
*/
@Test
public void testGetInvoices() throws IOException {
logger.info("├── get Zoho documents");
accessToken = "1000.21912dc6af0906707f51d336fd98ab61.eb153952d0d86a828fcb7e18426c2c1a";
try {
String url = "https://www.zohoapis.eu/books/v3/invoices?organization_id=20105697367";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.header("Authorization", "Zoho-oauthtoken " + accessToken)
.build();
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString());
logger.info("│ ├── Response statusCode=" + response.statusCode());
// 1. Response-Logging für Debugging
logger.info("│ ├── Response Body=" + response.body());
if (response.statusCode() >= 300) {
throw new RuntimeException("API request failed with status code: " + response.statusCode());
}
} catch (IOException | InterruptedException e) {
throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage());
}
}
} }

View file

@ -0,0 +1,97 @@
{
"code": 0,
"message": "success",
"invoices": [
{
"ach_payment_initiated": false,
"invoice_id": "777249000000055583",
"zcrm_potential_id": "",
"customer_id": "777249000000055569",
"zcrm_potential_name": "",
"customer_name": "Mr. Foo",
"company_name": "",
"status": "overdue",
"invoice_number": "INV-000001",
"reference_number": "23452345",
"date": "2025-04-21",
"due_date": "2025-04-21",
"due_days": "Overdue by 3 days",
"email": "ralph.soika@imixs.com",
"project_name": "",
"billing_address": {
"address": "",
"street2": "",
"city": "",
"state": "",
"zipcode": "",
"country": "",
"phone": "",
"fax": "",
"attention": ""
},
"shipping_address": {
"address": "",
"street2": "",
"city": "",
"state": "",
"zipcode": "",
"country": "",
"phone": "",
"fax": "",
"attention": ""
},
"country": "",
"phone": "",
"created_by": "Ralph Soika",
"total": 50.00,
"balance": 50.00,
"payment_expected_date": "",
"custom_fields": [],
"custom_field_hash": {},
"tags": [],
"salesperson_name": "",
"shipping_charge": 0.00,
"adjustment": 0.00,
"created_time": "2025-04-21T13:36:26+0200",
"last_modified_time": "2025-04-21T13:42:56+0200",
"updated_time": "2025-04-21T13:42:56+0200",
"is_viewed_by_client": false,
"has_attachment": true,
"client_viewed_time": "",
"is_viewed_in_mail": false,
"is_emailed": true,
"mail_first_viewed_time": "",
"mail_last_viewed_time": "",
"is_correction_invoice": false,
"color_code": "",
"current_sub_status_id": "",
"current_sub_status": "overdue",
"currency_id": "777249000000000071",
"schedule_time": "",
"currency_code": "EUR",
"currency_symbol": "€",
"template_type": "standard",
"invoice_url": "https://zohosecurepay.eu/books/imixs/secure?CInvoiceID=2-64277fbd3cd8bc86f8e581a3a7e1544618c191c094de1ae938827291b0828c92b5f796a1ec94bfb1fdcce7621ddf18bf61770563d9fd283207b7fa0e24d232a416b0b5e010c61cd7 ",
"transaction_type": "renewal",
"reminders_sent": 0,
"last_reminder_sent_date": "",
"last_payment_date": "",
"template_id": "777249000000000103",
"documents": "",
"salesperson_id": "",
"write_off_amount": 0.00,
"exchange_rate": 1.00,
"unprocessed_payment_amount": 0.00
}
],
"page_context": {
"page": 1,
"per_page": 200,
"has_more_page": false,
"report_name": "Invoices",
"applied_filter": "Status.All",
"custom_fields": [],
"sort_column": "created_time",
"sort_order": "D"
}
}