office-alexander-logistics/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAuthManager.java
2025-11-15 15:28:31 +01:00

774 lines
30 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.alexanderlogistics.ksef;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.MGF1ParameterSpec;
import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Optional;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.workflow.exceptions.InvalidAccessException;
import org.imixs.workflow.exceptions.PluginException;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.RunAs;
import jakarta.ejb.Lock;
import jakarta.ejb.LockType;
import jakarta.ejb.Singleton;
import jakarta.inject.Inject;
import jakarta.json.JsonObject;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
/**
* Der KSeFAuthManager stellt Methoden bereit um einen Access Token für das KSeF
* System in Polen zu erstellen.
*
*
*/
@Singleton
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
public class KSeFAuthManager {
private static Logger logger = Logger.getLogger(KSeFAuthManager.class.getName());
public static final String ENV_KSEF_API_TOKEN = "ksef.api.token";
public static final String ENV_KSEF_API_NIP = "ksef.api.nip";
public static final String ENV_KSEF_API_ENDPOINT = "ksef.api.endpoint";
public static final String ENV_KSEF_API_DEBUG = "ksef.api.debug";
public static final String ERROR_CONFIG = "CONFIG_ERROR";
public static final String ERROR_API = "API_ERROR";
@Inject
@ConfigProperty(name = ENV_KSEF_API_TOKEN)
Optional<String> ksefToken;
@Inject
@ConfigProperty(name = ENV_KSEF_API_NIP)
Optional<String> ksefNip;
@Inject
@ConfigProperty(name = ENV_KSEF_API_ENDPOINT)
Optional<String> ksefEndpoint;
@Inject
@ConfigProperty(name = ENV_KSEF_API_DEBUG, defaultValue = "false")
boolean debug;
private HttpClient httpClient;
private String baseURI = "";
// public keys
private String symmetricCertBase64;
private String ksefTokenCertBase64;
private PublicKey symmetricPublicKey;
private PublicKey ksefTokenPublicKey;
// Auth information
private String challenge = "";
private String authRefNumber = null;
private String authToken = null;
private String challengeTimestamp = null;
// Session information
private String accessToken = null;
private String sessionRefNumber;
private String sessionValidUntil;
private byte[] sessionAesKey;
private byte[] sessionIv;
@PostConstruct
void init() {
if (!ksefToken.isPresent()) {
throw new InvalidAccessException("Missing environment parameter ksef.api.token!");
}
if (!ksefEndpoint.isPresent()) {
throw new InvalidAccessException("Missing environment parameter ksef.api.endpoint!");
}
if (!ksefNip.isPresent()) {
throw new InvalidAccessException("Missing environment parameter ksef.api.nip!");
}
baseURI = ksefEndpoint.get();
if (baseURI.endsWith("/")) {
baseURI = baseURI.substring(0, baseURI.length() - 1);
}
httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
}
public HttpClient getHttpClient() {
return httpClient;
}
public boolean isDebug() {
return debug;
}
public void setKsefToken(String token) {
this.ksefToken = Optional.ofNullable(token);
}
public String getAccessToken() {
return accessToken;
}
public void setKsefNip(String nip) {
this.ksefNip = Optional.ofNullable(nip);
}
public void setKsefEndpoint(String endpoint) {
this.ksefEndpoint = Optional.ofNullable(endpoint);
}
public String getSessionRefNumber() {
return sessionRefNumber;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public String getBaseURI() {
return baseURI;
}
public String getAuthRefNumber() {
return authRefNumber;
}
public String getAuthToken() {
return authToken;
}
public PublicKey getSymmetricPublicKey() {
return symmetricPublicKey;
}
public String getChallengeValue() {
return challenge;
}
public byte[] getSessionAesKey() {
return sessionAesKey;
}
public byte[] getSessionIv() {
return sessionIv;
}
/**
* This method opens a new interactive session to upload invoices.
* you can call hasValidSession() to verify if a session already exists.
*
* @throws PluginException
*/
public void openSession() throws PluginException {
logger.info("├── open Session...");
if (!hasValidSession()) {
logger.info("│ ├── open new interactive KSeF Session...");
// open new session
this.loadPublicKeyCertificates();
this.authChallenge();
this.authKSeFToken();
this.checkTokenStatus();
this.redeemToken();
this.openInteractiveSession();
} else {
logger.info("│ ├── reuse existing KSeF Session...");
}
}
/**
* Verifies if sessionValidUntil and sessionRefNumber is valid
*
* @return true if a valid session exists, false otherwise
*/
public boolean hasValidSession() {
return false;
// // Check if session reference number exists
// if (sessionRefNumber == null || sessionRefNumber.isEmpty()) {
// return false;
// }
// // Check if valid until timestamp exists
// if (sessionValidUntil == null || sessionValidUntil.isEmpty()) {
// return false;
// }
// try {
// // Parse the validUntil timestamp (ISO 8601 format with UTC)
// DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
// Instant validUntilInstant = Instant.from(formatter.parse(sessionValidUntil));
// // Get current time in UTC
// Instant now = Instant.now();
// // Check if session is still valid
// boolean isValid = now.isBefore(validUntilInstant);
// if (debug && isValid) {
// logger.info("├── Session is still valid until: " + sessionValidUntil);
// } else if (debug) {
// logger.info("├── Session expired at: " + sessionValidUntil);
// }
// return isValid;
// } catch (Exception e) {
// // If parsing fails, assume session is invalid
// logger.warning("├── ⚠️ Error parsing sessionValidUntil timestamp: " +
// e.getMessage());
// return false;
// }
}
/**
* Helper Method to read the certificates
*
* @throws Exception
*/
@Lock(LockType.WRITE)
public void loadPublicKeyCertificates() throws PluginException {
logger.info("├── ♻️ KSeF API load public-key-certificates...");
String uri = baseURI + "/security/public-key-certificates";
if (debug) {
logger.info("│ ├── Endpoint: " + uri);
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = null;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Unable to send request for KSeF certificates endpoint: " + e.getMessage());
}
if (debug) {
logger.info("│ ├── Response Code = " + response.statusCode());
logger.info("Response: " + response.body());
}
if (response.statusCode() != 200) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Unexpected response from KSeF certificates endpoint: " + response.statusCode());
}
// JSON parsen
try (Jsonb jsonb = JsonbBuilder.create()) {
var certArray = jsonb.fromJson(response.body(), JsonObject[].class);
for (JsonObject entry : certArray) {
// usage = ["KsefTokenEncryption"] oder ["SymmetricKeyEncryption"]
var usages = entry.getJsonArray("usage");
if (usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) {
ksefTokenCertBase64 = entry.getString("certificate");
if (debug) {
logger.info("│ ├── ✓ KsefTokenEncryption certificate found.");
} // Public Key extrahieren
ksefTokenPublicKey = extractPublicKeyFromCertificate(ksefTokenCertBase64);
logger.info("│ ├── ✓ RSA PublicKey successfully extracted.");
}
if (usages.stream().anyMatch(v -> v.toString().contains("SymmetricKeyEncryption"))) {
symmetricCertBase64 = entry.getString("certificate");
if (debug) {
logger.info("│ ├── ✓ SymmetricKeyEncryption certificate found.");
}
// Public Key extrahieren
symmetricPublicKey = extractPublicKeyFromCertificate(symmetricCertBase64);
logger.info("│ ├── ✓ Symetric PublicKey successfully extracted.");
}
}
if (ksefTokenPublicKey == null) {
logger.warning("│ ├── ⚠️ No certificate with usage 'ksefTokenPublicKey' found!");
}
if (symmetricPublicKey == null) {
logger.warning("│ ├── ⚠️ No certificate with usage 'symmetricPublicKey' found!");
}
} catch (Exception e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Error parsing certificates JSON: " + e.getMessage());
}
}
/**
* Diese method führt den challenge Request durch
*
* @throws Exception
*/
// @Lock(LockType.READ)
@Lock(LockType.WRITE)
public void authChallenge() throws PluginException {
logger.info("├── 🛅 KSeF API Challenge...");
String uri = baseURI + "/auth/challenge";
if (debug) {
logger.info("│ ├── Endpoint: " + uri);
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri)) // nur Endpoint checken
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.noBody()) //
.build();
HttpResponse<String> response;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (debug) {
logger.info("│ ├── Response Code = " + response.statusCode());
logger.info("Response: " + response.body());
}
// Prüfen, dass wir eine API-Antwort bekommen, kein HTML
if (response.statusCode() >= 500) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG,
"Unable to access KSeF API: response=" + response.statusCode());
}
// Challenge aus Response extrahieren und speichern
if (response.statusCode() == 200) {
try (Jsonb jsonb = JsonbBuilder.create()) {
// Annahme: response.body() ist ein String mit JSON-Inhalt
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
challenge = jsonObject.getString("challenge");
challengeTimestamp = jsonObject.getString("timestamp");
logger.info("│ ├── ✓ challenge: " + challenge);
logger.info("│ ├── ✓ timestamp: " + challengeTimestamp);
} catch (Exception e) {
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Error parsing JSON response: " + e.getMessage());
}
}
} catch (IOException | InterruptedException e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"API Error: Unable to parse KSeF API response");
}
}
/**
* Helper method to request the KSeF Auth Token and referenceNumber
*
* @throws Exception
*/
@Lock(LockType.WRITE)
public void authKSeFToken() throws PluginException {
logger.info("├── 🛃 KSeF API auth ksef-token...");
String uri = baseURI + "/auth/ksef-token";
if (debug) {
logger.info("│ ├── Endpoint: " + uri);
}
HttpResponse<String> response = null;
try {
String jsonPayload = String.format(
"{" +
"\"challenge\": \"%s\"," +
"\"contextIdentifier\": {" +
" \"type\": \"Nip\"," +
" \"value\": \"%s\"" +
"}," +
"\"encryptedToken\": \"%s\"" +
"}",
challenge, ksefNip.get(), getEncryptedToken());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"API Error: Unable to request ksef-token");
}
if (debug) {
logger.info("│ ├── Response Code = " + response.statusCode());
logger.info("Response: " + response.body());
}
// Prüfen, dass wir eine API-Antwort bekommen
if (response.statusCode() >= 500) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG,
"Unable to access KSeF API: response=" + response.statusCode());
}
// JSON parsen und Werte speichern
if (response.statusCode() == 200 || response.statusCode() == 202) {
try (Jsonb jsonb = JsonbBuilder.create()) {
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
// Reference Number speichern
authRefNumber = jsonObject.getString("referenceNumber");
logger.info("│ ├── ✓ referenceNumber: " + authRefNumber);
// Authentication Token speichern
JsonObject authTokenObject = jsonObject.getJsonObject("authenticationToken");
authToken = authTokenObject.getString("token");
if (debug) {
logger.info("│ ├── ✓ authenticationToken: " + authToken);
}
} catch (Exception e) {
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Error parsing JSON response: " + e.getMessage());
}
}
}
/**
* Helper Method to open an interactive session. The method fetches a
* sessionRefNumber which is mandatory to upload an invoice.
*
* @throws Exception
*/
@Lock(LockType.WRITE)
public void openInteractiveSession() throws PluginException {
if (accessToken == null) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Error - missing access token!");
}
logger.info("├── 🔐 KSeF API open interactive session...");
String uri = baseURI + "/sessions/online";
if (debug) {
logger.info("│ ├── Endpoint: " + uri);
}
HttpResponse<String> response = null;
try {
// --- AES Schlüssel erzeugen ---
SecureRandom rnd = new SecureRandom();
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
rnd.nextBytes(aesKeyBytes);
byte[] ivBytes = new byte[16]; // 128-bit IV
rnd.nextBytes(ivBytes);
// --- AES Key mit RSA encrypten ---
Cipher rsa;
rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
rsa.init(Cipher.ENCRYPT_MODE, this.symmetricPublicKey);
byte[] encryptedAesKey = rsa.doFinal(aesKeyBytes);
String jsonPayload = String.format(
"{" +
"\"formCode\": {" +
" \"systemCode\": \"FA (3)\"," +
" \"schemaVersion\": \"1-0E\"," +
" \"value\": \"FA\"" +
"}," +
"\"encryption\": {" +
" \"encryptedSymmetricKey\": \"%s\"," +
" \"initializationVector\": \"%s\"" +
"}" +
"}",
Base64.getEncoder().encodeToString(encryptedAesKey),
Base64.getEncoder().encodeToString(ivBytes));
if (debug) {
logger.info("│ ├── Payload: " + jsonPayload);
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
// .header("Accept-Charset", "UTF-8")
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + accessToken)
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (debug) {
logger.info("│ ├── Response Code = " + response.statusCode());
logger.info("Response: " + response.body());
}
if (response.statusCode() != 201) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Failed to open interactive session: " + response.statusCode());
}
// parse JSON
try (Jsonb jsonb = JsonbBuilder.create()) {
// Annahme: response.body() ist ein String mit JSON-Inhalt
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
sessionRefNumber = jsonObject.getString("referenceNumber");
sessionValidUntil = jsonObject.getString("validUntil");
if (true) {
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
}
// IMPORTANT: Store the AES key - this key is mandatory to upload a invoice
this.sessionAesKey = aesKeyBytes;
this.sessionIv = ivBytes;
} catch (Exception e) {
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Error parsing JSON response: " + e.getMessage());
}
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | InterruptedException
| InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
logger.severe("├── ⚠️ API Error: " + e.getMessage());
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"API Error openInteractiveSession: " + e.getMessage());
}
}
/**
* Verschlüsselt den KSeF-Token mit RSA-OAEP (SHA-256) für die Authentifizierung
*
* Format: token|timestamp_in_milliseconds
*
* @return Base64-kodierter verschlüsselter Token
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws Exception
*/
public String getEncryptedToken() throws PluginException {
String result = null;
if (ksefTokenPublicKey == null) {
throw new IllegalStateException("Public Key not loaded! Call loadPublicKeyCertificates() first.");
}
if (challengeTimestamp == null || challengeTimestamp.isEmpty()) {
throw new IllegalStateException("Challenge timestamp is missing! Call authChallenge() first.");
}
try {
// 1. Timestamp parsen - KSeF liefert zu viele Dezimalstellen
// Format: "2025-11-14T14:18:35.606507+00:00"
// Flexibler Parser der verschiedene Dezimalstellen akzeptiert
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
Instant instant = Instant.from(formatter.parse(challengeTimestamp));
long timestampMillis = instant.toEpochMilli();
logger.info("│ ├── Timestamp (ms): " + timestampMillis);
// 2. Format erstellen: token|timestamp
String plaintext = ksefToken.get() + "|" + timestampMillis;
if (debug) {
logger.info("│ ├── Plaintext format: [token]|[" + timestampMillis + "]");
}
// 3. RSA-OAEP Verschlüsselung mit SHA-256
Cipher cipher;
cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
"SHA-256",
"MGF1",
MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, ksefTokenPublicKey, oaepParams);
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 4. Base64 kodieren
result = Base64.getEncoder().encodeToString(encrypted);
logger.info("│ ├── ✓ Token encrypted with RSA-OAEP");
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"API Error - unable to getEncryptedToken: " + e.getMessage());
}
return result;
}
/**
* Redeems the KSeF token and returns a session access token to be used to open
* an Interactive Session
*
* POST /auth/token/redeem
*
* @return JsonObject with referenceNumber and sessionToken
* @throws Exception
*/
@Lock(LockType.WRITE)
public JsonObject redeemToken() throws PluginException {
logger.info("├── 🛂 KSeF API redeem token...");
String uri = baseURI + "/auth/token/redeem";
if (debug) {
logger.info("│ ├── Endpoint: " + uri);
}
HttpResponse<String> response = null;
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + authToken)
.POST(HttpRequest.BodyPublishers.noBody())
.build();
response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"API Error - unable to redeemToken: " + e.getMessage());
}
if (debug) {
logger.info("│ ├── Response Code = " + response.statusCode());
logger.info("│ ├── Response: " + response.body());
}
if (response.statusCode() >= 400) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(),
ERROR_API,
"Token redeem failed: " + response.statusCode()
+ " - " + response.body());
}
try (Jsonb jsonb = JsonbBuilder.create()) {
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
// Access Token extrahieren und speichern
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
this.accessToken = accessTokenObj.getString("token");
logger.info("│ └── ☑️ Access Token received");
return jsonObject;
} catch (Exception e) {
logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage());
throw new PluginException(KSeFAuthManager.class.getSimpleName(),
ERROR_API,
"Error parsing response: " + e.getMessage());
}
}
private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception {
byte[] der = java.util.Base64.getDecoder().decode(base64Cert);
java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509");
var certificate = (java.security.cert.X509Certificate) factory
.generateCertificate(new java.io.ByteArrayInputStream(der));
return certificate.getPublicKey();
}
/**
* This method verifies the actual status of the authentication process for the
* current auth token (this is the token received from the start of the auth
* procedure)
*
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Uzyskiwanie-dostepu/paths/~1api~1v2~1auth~1%7BreferenceNumber%7D/get
*
* @throws PluginException
*/
public void checkTokenStatus() throws PluginException {
logger.info("├── 🌡️ KSeF API check auth status...");
String uri = baseURI + "/auth/" + authRefNumber;
if (debug) {
logger.info("│ ├── Endpoint: " + uri);
}
HttpResponse<String> response = null;
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + authToken)
.GET()
.build();
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"API Error: Unable to request auth status: " + e.getMessage());
}
if (true) {
logger.info("│ ├── Response Code = " + response.statusCode());
logger.info("Response: " + response.body());
}
try (Jsonb jsonb = JsonbBuilder.create()) {
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
Boolean isRedeemed = jsonObject.containsKey("isTokenRedeemed") ? jsonObject.getBoolean("isTokenRedeemed")
: null;
String lastTokenRefreshDate = jsonObject.containsKey("lastTokenRefreshDate")
? jsonObject.getString("lastTokenRefreshDate")
: null;
String refreshTokenValidUntil = jsonObject.containsKey("refreshTokenValidUntil")
? jsonObject.getString("refreshTokenValidUntil")
: null;
logger.info("│ ├── lastTokenRefreshDate: " + lastTokenRefreshDate);
if (isRedeemed) {
logger.info("│ ├── ✓ isTokenRedeemed: " + isRedeemed);
logger.info("│ ├── ✓ refreshTokenValidUntil: " + refreshTokenValidUntil);
} else {
logger.info("│ ├── 𐄂 isTokenRedeemed: " + isRedeemed);
}
} catch (Exception e) {
logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage());
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
"Error parsing JSON response: " + e.getMessage());
}
}
}