zoho interface
This commit is contained in:
parent
a756997247
commit
c47a6ed4ac
8 changed files with 588 additions and 53 deletions
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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!";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@
|
||||||
<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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue