KSeF Impl and docu
This commit is contained in:
parent
26b08eb507
commit
11b28ef9c3
6 changed files with 700 additions and 168 deletions
|
|
@ -1,41 +1,174 @@
|
|||
# KSeF - Converter
|
||||
# KSeF API Integration
|
||||
|
||||
Die Idee ist es Rechnungen nach Erhalt aus cargosoft über die KSeF FA(2) API an die Behörde zu senden.
|
||||
Java integration for the Polish **Krajowy System e-Faktur (KSeF)** - the national e-invoicing system for Poland.
|
||||
|
||||
# API
|
||||
## Overview
|
||||
|
||||
Dokumentation: https://ksef-test.mf.gov.pl/docs/v2/index.html
|
||||
This integration provides secure communication with the KSeF API to upload electronic invoices. It handles the complex multi-step authentication flow and encryption requirements mandated by the Polish tax authorities.
|
||||
|
||||
Die Basis url ist: "https://ksef-test.mf.gov.pl/api/v2";
|
||||
## Components
|
||||
|
||||
Der Ablauf für die Übermittlung ist folgender:
|
||||
### 1. KSeFAuthManager
|
||||
|
||||
- vom Polnischen Goverment (bzw. AGL/Taxman) haben wir ein Secret bekommen (KSeFToken)
|
||||
- wir machen den call `/security/public-key-certificates` um Security Zertifikate zu erhalten
|
||||
- wir machen den call `/auth/challenge` um eine Challenge ID zu erhalten
|
||||
- wir machen den call `/auth/ksef-token` um eine ReferenceID und einen AccessToken zu erhalten
|
||||
- wir senden die Rechnung - mit dem öffentlichen Schlüssel verschlüsselt zusammen mit unserer ReferenceID und dem AccessToken an das Goverment.
|
||||
Manages authentication and session lifecycle for the KSeF API.
|
||||
|
||||
## Generieren einer Referenznummer.
|
||||
**Key Responsibilities:**
|
||||
|
||||
Man benötigt eine Referenznummer (Session ID). Dise kann man wie folgt generieren:
|
||||
- Multi-step authentication flow
|
||||
- RSA certificate management
|
||||
- Session key generation and encryption
|
||||
- Access token lifecycle management
|
||||
- Session reuse and validation
|
||||
|
||||
1.) "/auth/challenge" aufrufen um einen Challenge ID zu erhalten
|
||||
### 2. KSeFAPIService
|
||||
|
||||
2.) "/auth/ksef-token" ksef Token + Challenge ID senden - man bekommt die Referenznummer (Gültig 1 Stunde)
|
||||
Provides business-level methods for invoice operations.
|
||||
|
||||
## Rechnung senden
|
||||
**Key Responsibilities:**
|
||||
|
||||
API Aufruf: https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Wysylka-interaktywna/paths/~1api~1v2~1sessions~1online~1%7BreferenceNumber%7D~1invoices/post
|
||||
- Invoice encryption (AES-256-CBC)
|
||||
- Invoice upload to KSeF
|
||||
- Hash calculation and validation
|
||||
- Error handling and reporting
|
||||
|
||||
Details:
|
||||
## Authentication Flow
|
||||
|
||||
https://github.com/CIRFMF/ksef-docs/blob/main/sesja-interaktywna.md#2-wys%C5%82anie-faktury
|
||||
The KSeF API requires a sophisticated multi-step authentication process:
|
||||
|
||||
Man muss die XML Datei mit dem öffentlichen Schlüssel der Behörde vor dem Versand verschlüsseln
|
||||
```
|
||||
1. Load Public Keys
|
||||
└─> GET /security/public-key-certificates
|
||||
|
||||
### Das Rechnungsformat:
|
||||
2. Challenge Request
|
||||
└─> POST /auth/challenge
|
||||
└─> Returns: challenge + timestamp
|
||||
|
||||
XML Datei
|
||||
3. Token Authentication
|
||||
└─> POST /auth/ksef-token
|
||||
└─> Encrypts: token|timestamp with RSA-OAEP SHA-256
|
||||
└─> Returns: authToken + referenceNumber
|
||||
|
||||
https://github.com/CIRFMF/ksef-docs/blob/main/faktury/weryfikacja-faktury.md
|
||||
4. Token Redemption
|
||||
└─> POST /auth/token/redeem
|
||||
└─> Returns: accessToken
|
||||
|
||||
5. Open Interactive Session
|
||||
└─> POST /sessions/online
|
||||
└─> Generates AES-256 key + IV
|
||||
└─> Encrypts session key with RSA
|
||||
└─> Returns: sessionRefNumber + validUntil
|
||||
```
|
||||
|
||||
## Invoice Upload Flow
|
||||
|
||||
Once authenticated, invoices can be uploaded:
|
||||
|
||||
```
|
||||
1. Reuse or Create Session
|
||||
└─> Validates existing session or creates new one
|
||||
|
||||
2. Encrypt Invoice
|
||||
└─> AES-256-CBC encryption using session key
|
||||
|
||||
3. Calculate Hashes
|
||||
└─> SHA-256 of original XML
|
||||
└─> SHA-256 of encrypted XML
|
||||
|
||||
4. Upload Invoice
|
||||
└─> POST /sessions/online/{sessionRef}/invoices
|
||||
└─> Returns: referenceNumber
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
### Multi-Layer Encryption
|
||||
|
||||
- **RSA-OAEP SHA-256**: Token encryption with timestamp binding
|
||||
- **RSA-OAEP SHA-1**: Session key encryption
|
||||
- **AES-256-CBC**: Invoice content encryption
|
||||
|
||||
### Security Mechanisms
|
||||
|
||||
- **Replay Attack Protection**: Timestamp-bound tokens
|
||||
- **Session Management**: Automatic session reuse and validation
|
||||
- **Certificate Validation**: Dynamic X.509 certificate loading
|
||||
- **UTC Timezone Handling**: Prevents timezone-related vulnerabilities
|
||||
|
||||
## Usage Example
|
||||
|
||||
```java
|
||||
// Initialize Auth Manager
|
||||
KSeFAuthManager authManager = new KSeFAuthManager();
|
||||
authManager.setKsefToken("your-ksef-token");
|
||||
authManager.setKsefNip("1234567890");
|
||||
authManager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
authManager.init();
|
||||
|
||||
// Initialize API Service
|
||||
KSeFAPIService apiService = new KSeFAPIService();
|
||||
apiService.kseFAuthManager = authManager;
|
||||
|
||||
// Upload Invoice
|
||||
ItemCollection workitem = new ItemCollection();
|
||||
FileData fileData = new FileData("invoice.xml", xmlBytes, null, null);
|
||||
workitem.addFileData(fileData);
|
||||
|
||||
String referenceNumber = apiService.uploadInvoice(workitem, "invoice.xml");
|
||||
System.out.println("Invoice uploaded: " + referenceNumber);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Required environment variables:
|
||||
|
||||
```properties
|
||||
ksef.api.token=your-ksef-authentication-token
|
||||
ksef.api.nip=your-company-nip-number
|
||||
ksef.api.endpoint=https://ksef-test.mf.gov.pl/api/v2
|
||||
ksef.api.debug=false
|
||||
```
|
||||
|
||||
## Session Management
|
||||
|
||||
The `KSeFAuthManager` automatically handles session lifecycle:
|
||||
|
||||
- **Session Reuse**: Validates `sessionValidUntil` timestamp (UTC)
|
||||
- **Auto-Renewal**: Opens new session if current one expired
|
||||
- **Thread-Safe**: Uses `@Lock(LockType.WRITE)` for concurrent access
|
||||
|
||||
## Testing
|
||||
|
||||
Test environment endpoint:
|
||||
|
||||
```
|
||||
https://ksef-test.mf.gov.pl/api/v2
|
||||
```
|
||||
|
||||
Production endpoint:
|
||||
|
||||
```
|
||||
https://ksef.mf.gov.pl/api/v2
|
||||
```
|
||||
|
||||
See `KSeFAPIServiceTest.java` for complete test examples.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The implementation uses `PluginException` for error handling with two error types:
|
||||
|
||||
- `CONFIG_ERROR`: Configuration or setup issues
|
||||
- `API_ERROR`: API communication or response errors
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Jakarta EE (EJB, JSON-B)
|
||||
- Java 11+ (HttpClient, Crypto APIs)
|
||||
- Imixs Workflow (for document management)
|
||||
|
||||
## References
|
||||
|
||||
- [KSeF Official Documentation](https://www.gov.pl/web/kas/ksef)
|
||||
- [KSeF API Specification](https://ksef-test.mf.gov.pl/docs/v2/index.html)
|
||||
- [Upload Invoice](https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Wysylka-interaktywna/paths/~1api~1v2~1sessions~1online~1%7BreferenceNumber%7D~1invoices/post)
|
||||
- [Facture Details](https://github.com/CIRFMF/ksef-docs/blob/main/sesja-interaktywna.md#2-wys%C5%82anie-faktury)
|
||||
- [XML Invoice Example](https://github.com/CIRFMF/ksef-docs/blob/main/faktury/weryfikacja-faktury.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
package com.alexanderlogistics.ksef;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.imixs.workflow.FileData;
|
||||
import org.imixs.workflow.ItemCollection;
|
||||
import org.imixs.workflow.exceptions.PluginException;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.security.DeclareRoles;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.annotation.security.RunAs;
|
||||
import jakarta.ejb.Singleton;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.json.JsonObject;
|
||||
import jakarta.json.bind.Jsonb;
|
||||
import jakarta.json.bind.JsonbBuilder;
|
||||
|
||||
/**
|
||||
* THe KSeFAPIService provides methods to open an interactive session and upload
|
||||
* an invoice.
|
||||
*
|
||||
*/
|
||||
@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
|
||||
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
|
||||
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
||||
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
|
||||
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
|
||||
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
||||
@Singleton
|
||||
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
|
||||
public class KSeFAPIService {
|
||||
|
||||
private static Logger logger = Logger.getLogger(KSeFAPIService.class.getName());
|
||||
|
||||
public static final String ERROR_API = "API_ERROR";
|
||||
|
||||
@Inject
|
||||
KSeFAuthManager kseFAuthManager;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method uploads a KSeF Invoice document (XML).
|
||||
* The response of the upload is a 'referenceNumber' which is stored into the
|
||||
* item ksef.referenceNumber
|
||||
*
|
||||
* @param workitem - the workitem containing the invoice file
|
||||
* @param fileName - the name of the invoice file to upload
|
||||
* @return referenceNumber - the KSeF reference number for the uploaded invoice
|
||||
* @throws PluginException
|
||||
*/
|
||||
public String uploadInvoice(ItemCollection workitem, String fileName)
|
||||
throws PluginException {
|
||||
String referenceNumber = null;
|
||||
logger.info("├── Upload Invoice...");
|
||||
|
||||
// First open an interactive Session. The KSeFAuthManager automatically reuses
|
||||
// an existing session
|
||||
kseFAuthManager.openSession();
|
||||
|
||||
if (kseFAuthManager.getAccessToken() == null) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"API Error - missing AccessToken!");
|
||||
}
|
||||
if (kseFAuthManager.getSessionRefNumber() == null) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"API Error - missing SessionRefNumber!");
|
||||
}
|
||||
|
||||
// Get session encryption keys
|
||||
byte[] aesKeyBytes = kseFAuthManager.getSessionAesKey();
|
||||
byte[] ivBytes = kseFAuthManager.getSessionIv();
|
||||
|
||||
if (aesKeyBytes == null || ivBytes == null) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"API Error - missing session encryption keys!");
|
||||
}
|
||||
|
||||
// Load invoice file
|
||||
FileData fileData = workitem.getFileData(fileName);
|
||||
if (fileData == null) {
|
||||
throw new PluginException(KSeFAPIService.class.getSimpleName(), ERROR_API,
|
||||
"File not found: " + fileName);
|
||||
}
|
||||
|
||||
byte[] invoiceXml = fileData.getContent();
|
||||
logger.info("│ ├── XML Invoice loaded - " + invoiceXml.length + " bytes");
|
||||
|
||||
try {
|
||||
// ---------- 1) Use session AES key and IV ----------
|
||||
logger.info("│ ├── Using session AES Key...");
|
||||
SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
|
||||
// ---------- 2) Encrypt XML with AES-256-CBC ----------
|
||||
logger.info("│ ├── Encrypting invoice with AES...");
|
||||
javax.crypto.Cipher aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
|
||||
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
||||
|
||||
// ---------- 3) Calculate SHA-256 hashes ----------
|
||||
logger.info("│ ├── Calculating hashes...");
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
// Hash of original invoice
|
||||
byte[] xmlHashBytes = sha256.digest(invoiceXml);
|
||||
String invoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
||||
|
||||
// Hash of encrypted invoice
|
||||
xmlHashBytes = sha256.digest(encryptedInvoiceXml);
|
||||
String encryptedInvoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
||||
|
||||
// ---------- 4) Build request body ----------
|
||||
String jsonBody = String.format("{ " +
|
||||
"\"invoiceHash\": \"%s\", " +
|
||||
"\"invoiceSize\": %d, " +
|
||||
"\"encryptedInvoiceHash\": \"%s\", " +
|
||||
"\"encryptedInvoiceSize\": %d, " +
|
||||
"\"encryptedInvoiceContent\": \"%s\", " +
|
||||
"\"offlineMode\": false " +
|
||||
"}",
|
||||
invoiceHash,
|
||||
invoiceXml.length,
|
||||
encryptedInvoiceHash,
|
||||
encryptedInvoiceXml.length,
|
||||
Base64.getEncoder().encodeToString(encryptedInvoiceXml));
|
||||
|
||||
// ---------- 5) Prepare HTTP POST request ----------
|
||||
String uri = kseFAuthManager.getBaseURI() + "/sessions/online/"
|
||||
+ kseFAuthManager.getSessionRefNumber() + "/invoices";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
||||
if (kseFAuthManager.isDebug()) {
|
||||
logger.info("│ ├── Request body: " + jsonBody);
|
||||
}
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + kseFAuthManager.getAccessToken())
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.build();
|
||||
|
||||
// ---------- 6) Execute request ----------
|
||||
logger.info("│ ├── Sending request...");
|
||||
var response = kseFAuthManager.getHttpClient().send(request,
|
||||
java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
logger.info("│ ├── Response Code: " + response.statusCode());
|
||||
|
||||
if (kseFAuthManager.isDebug()) {
|
||||
logger.info("│ ├── Response Body: " + response.body());
|
||||
}
|
||||
|
||||
// ---------- 7) Validate response status ----------
|
||||
if (response.statusCode() != 202) {
|
||||
throw new PluginException(KSeFAPIService.class.getSimpleName(), ERROR_API,
|
||||
"Invoice upload failed: " + response.statusCode() + " - " + response.body());
|
||||
}
|
||||
|
||||
// ---------- 8) Parse response and extract reference number ----------
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
referenceNumber = jsonObject.getString("referenceNumber");
|
||||
|
||||
logger.info("│ ├── Reference Number: " + referenceNumber);
|
||||
logger.info("├── ✅ Invoice upload successful");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage());
|
||||
throw new PluginException(KSeFAPIService.class.getSimpleName(), ERROR_API,
|
||||
"Error parsing JSON response: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
|
||||
logger.severe("├── ⚠️ Encryption error: " + e.getMessage());
|
||||
throw new PluginException(KSeFAPIService.class.getSimpleName(), ERROR_API,
|
||||
"Encryption error during invoice upload: " + e.getMessage());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.severe("├── ⚠️ HTTP request error: " + e.getMessage());
|
||||
throw new PluginException(KSeFAPIService.class.getSimpleName(), ERROR_API,
|
||||
"HTTP error during invoice upload: " + e.getMessage());
|
||||
}
|
||||
|
||||
return referenceNumber;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,9 @@ 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;
|
||||
|
|
@ -16,7 +19,10 @@ 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;
|
||||
|
||||
|
|
@ -110,13 +116,16 @@ public class KSeFAuthManager {
|
|||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
// this.jsonb = JsonbBuilder.create();
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public void setKsefToken(String token) {
|
||||
this.ksefToken = Optional.ofNullable(token);
|
||||
}
|
||||
|
|
@ -141,18 +150,6 @@ public class KSeFAuthManager {
|
|||
this.debug = debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the access token
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getEncodedToken() {
|
||||
|
||||
String result = java.util.Base64.getEncoder().encodeToString(ksefToken.orElse("").getBytes());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getBaseURI() {
|
||||
return baseURI;
|
||||
}
|
||||
|
|
@ -173,6 +170,77 @@ public class KSeFAuthManager {
|
|||
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 new KSeF Session...");
|
||||
if (!hasValidSession()) {
|
||||
// open new session
|
||||
this.loadPublicKeyCertificates();
|
||||
|
||||
this.authChallenge();
|
||||
this.authKSeFToken();
|
||||
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() {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese method führt den challenge Request durch
|
||||
*
|
||||
|
|
@ -236,29 +304,36 @@ public class KSeFAuthManager {
|
|||
* @throws Exception
|
||||
*/
|
||||
@Lock(LockType.WRITE)
|
||||
public void authKSeFToken() throws Exception {
|
||||
public void authKSeFToken() throws PluginException {
|
||||
logger.info("├── KSeF API auth ksef-token...");
|
||||
String uri = baseURI + "/auth/ksef-token";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
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();
|
||||
HttpResponse<String> response = null;
|
||||
try {
|
||||
String jsonPayload = String.format(
|
||||
"{" +
|
||||
"\"challenge\": \"%s\"," +
|
||||
"\"contextIdentifier\": {" +
|
||||
" \"type\": \"Nip\"," +
|
||||
" \"value\": \"%s\"" +
|
||||
"}," +
|
||||
"\"encryptedToken\": \"%s\"" +
|
||||
"}",
|
||||
challenge, ksefNip.get(), getEncryptedToken());
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
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");
|
||||
}
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("Response: " + response.body());
|
||||
|
|
@ -293,12 +368,13 @@ public class KSeFAuthManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper Method to open an interactive session
|
||||
* 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 Exception {
|
||||
public void openInteractiveSession() throws PluginException {
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
|
|
@ -308,81 +384,88 @@ public class KSeFAuthManager {
|
|||
logger.info("├── KSeF API open interactive session...");
|
||||
String uri = baseURI + "/sessions/online";
|
||||
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 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;
|
||||
|
||||
// --- AES Key mit RSA encrypten ---
|
||||
// Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
||||
rsa.init(Cipher.ENCRYPT_MODE, this.symmetricPublicKey);
|
||||
byte[] encryptedAesKey = rsa.doFinal(aesKeyBytes);
|
||||
rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
||||
|
||||
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));
|
||||
rsa.init(Cipher.ENCRYPT_MODE, this.symmetricPublicKey);
|
||||
byte[] encryptedAesKey = rsa.doFinal(aesKeyBytes);
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Payload: " + jsonPayload);
|
||||
}
|
||||
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));
|
||||
|
||||
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();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
if (response.statusCode() != 201) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Failed to open interactive session: " + response.statusCode());
|
||||
}
|
||||
|
||||
// JSON parsen
|
||||
// Session RefNumber aus Response extrahieren und speichern
|
||||
|
||||
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 (debug) {
|
||||
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
||||
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
||||
logger.info("│ ├── Payload: " + jsonPayload);
|
||||
}
|
||||
|
||||
// --- WICHTIG: AES Schlüssel speichern, wir brauchen ihn beim Upload ---
|
||||
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());
|
||||
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());
|
||||
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
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 (debug) {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -393,10 +476,14 @@ public class KSeFAuthManager {
|
|||
* 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 Exception {
|
||||
|
||||
public String getEncryptedToken() throws PluginException {
|
||||
String result = null;
|
||||
if (ksefTokenPublicKey == null) {
|
||||
throw new IllegalStateException("Public Key not loaded! Call loadPublicKeyCertificates() first.");
|
||||
}
|
||||
|
|
@ -404,45 +491,54 @@ public class KSeFAuthManager {
|
|||
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));
|
||||
|
||||
// 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();
|
||||
|
||||
long timestampMillis = instant.toEpochMilli();
|
||||
logger.info("│ ├── Timestamp (ms): " + timestampMillis);
|
||||
|
||||
logger.info("│ ├── Timestamp (ms): " + timestampMillis);
|
||||
// 2. Format erstellen: token|timestamp
|
||||
String plaintext = ksefToken.get() + "|" + 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());
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Plaintext format: [token]|[" + timestampMillis + "]");
|
||||
}
|
||||
|
||||
// 3. RSA-OAEP Verschlüsselung mit SHA-256
|
||||
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
|
||||
String result = Base64.getEncoder().encodeToString(encrypted);
|
||||
|
||||
logger.info("│ ├── ✓ Token encrypted with RSA-OAEP");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeems the KSeF token and returns a session token
|
||||
* Redeems the KSeF token and returns a session access token to be used to open
|
||||
* an Interactive Session
|
||||
*
|
||||
* POST /auth/token/redeem
|
||||
*
|
||||
|
|
@ -450,21 +546,26 @@ public class KSeFAuthManager {
|
|||
* @throws Exception
|
||||
*/
|
||||
@Lock(LockType.WRITE)
|
||||
public JsonObject redeemToken() throws Exception {
|
||||
public JsonObject redeemToken() throws PluginException {
|
||||
|
||||
logger.info("├── KSeF API redeem token...");
|
||||
String uri = baseURI + "/auth/token/redeem";
|
||||
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();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer " + authToken)
|
||||
.POST(HttpRequest.BodyPublishers.noBody())
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request,
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
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());
|
||||
}
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
|
||||
if (debug) {
|
||||
|
|
@ -503,7 +604,7 @@ public class KSeFAuthManager {
|
|||
* @throws Exception
|
||||
*/
|
||||
@Lock(LockType.WRITE)
|
||||
public void loadPublicKeyCertificates() throws Exception {
|
||||
public void loadPublicKeyCertificates() throws PluginException {
|
||||
|
||||
logger.info("├── KSeF API load public-key-certificates...");
|
||||
String uri = baseURI + "/security/public-key-certificates";
|
||||
|
|
@ -515,7 +616,13 @@ public class KSeFAuthManager {
|
|||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
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());
|
||||
}
|
||||
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package com.alexanderlogistics.ksef;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.imixs.workflow.FileData;
|
||||
import org.imixs.workflow.ItemCollection;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class KSeFAPIServiceTest {
|
||||
|
||||
private static Logger logger = Logger.getLogger(KSeFAPIServiceTest.class.getName());
|
||||
|
||||
private KSeFAuthManager manager;
|
||||
private KSeFAPIService apiService;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
manager = new KSeFAuthManager();
|
||||
|
||||
// Test config
|
||||
manager.setKsefToken(Params.TOKEN);
|
||||
manager.setKsefNip(Params.NIP);
|
||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
manager.setDebug(false);
|
||||
|
||||
// init() ausführen
|
||||
manager.init();
|
||||
|
||||
// Open Session
|
||||
manager.openSession();
|
||||
// Create API Service and inject manager
|
||||
apiService = new KSeFAPIService();
|
||||
apiService.kseFAuthManager = manager;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||
public void testUploadInvoice() throws Exception {
|
||||
logger.info("==> Test: Upload Invoice XML");
|
||||
|
||||
ItemCollection workitem = createWorkitem();
|
||||
|
||||
// Execute upload
|
||||
String referenceNumber = apiService.uploadInvoice(workitem, "example-invoice-01.xml");
|
||||
|
||||
// Verify result
|
||||
assertNotNull(referenceNumber, "Reference number should not be null");
|
||||
assertFalse(referenceNumber.isEmpty(), "Reference number should not be empty");
|
||||
|
||||
logger.info("✅ Upload successful - Reference Number: " + referenceNumber);
|
||||
}
|
||||
|
||||
private ItemCollection createWorkitem() throws IOException {
|
||||
ItemCollection workitem = new ItemCollection();
|
||||
// ---------- 2) XML laden ----------
|
||||
Path xmlPath = Paths.get("src/test/resources/ksef/example-invoice-01.xml");
|
||||
byte[] invoiceXml = Files.readAllBytes(xmlPath);
|
||||
|
||||
FileData fileData = new FileData("example-invoice-01.xml", invoiceXml, null, null);
|
||||
workitem.addFileData(fileData);
|
||||
|
||||
return workitem;
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ public class KSeFAuthManagerTest {
|
|||
manager.setKsefToken(Params.TOKEN);
|
||||
manager.setKsefNip(Params.NIP);
|
||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
manager.setDebug(true);
|
||||
manager.setDebug(false);
|
||||
|
||||
// init() ausführen
|
||||
manager.init();
|
||||
|
|
@ -152,7 +152,10 @@ public class KSeFAuthManagerTest {
|
|||
// manager.getRefNumber() + "/invoices";
|
||||
String uri = manager.getBaseURI() + "/sessions/online/" + manager.getSessionRefNumber() + "/invoices";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
logger.info("Request: " + jsonBody);
|
||||
|
||||
if (manager.isDebug()) {
|
||||
logger.info("Request: " + jsonBody);
|
||||
}
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header("Content-Type", "application/json")
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
</Adres>
|
||||
<DaneKontaktowe>
|
||||
<Email>Lucja17@example.net</Email>
|
||||
<Telefon>86-588-03-53</Telefon>
|
||||
<Telefon>86-588-03-553</Telefon>
|
||||
</DaneKontaktowe>
|
||||
</Podmiot1>
|
||||
<Podmiot2>
|
||||
|
|
|
|||
Loading…
Reference in a new issue