Klasa
KSeFClient
umożliwia komunikację z API Krajowego Systemu e-Faktur (KSeF). Klasa zarządza nawiązywaniem sesji, uzyskaniem tokenu sesji, wysyłaniem faktury oraz zamykaniem sesji. Wykorzystuje bibliotekę cURL
do wysyłania żądań HTTP.Właściwości klasy:
$apiUrl
: Adres URL API KSeF, z którym klasa się komunikuje.$nip
: Numer identyfikacyjny podatnika (NIP), wymagany do autoryzacji.$apiKey
: Klucz API, służący do autoryzacji.$publicKeyPath
: Ścieżka do klucza publicznego (plik PEM) używanego do szyfrowania tokenu sesji.$sessionToken
: Token sesji otrzymany z KSeF, wymagany do wykonywania operacji w ramach sesji.
Metody klasy:
__construct($apiUrl, $nip, $apiKey, $publicKeyPath)
: Konstruktor klasy inicjalizujący właściwości na podstawie przekazanych parametrów.sendRequest($url, $data, $headers, $method = 'POST')
: Metoda pomocnicza, która wysyła żądania HTTP do API KSeF. Obsługuje różne metody HTTP (np.POST
,GET
), a także sprawdza status odpowiedzi oraz ewentualne błędy cURL.getChallengeAndTimestamp()
: Wysyła żądanie o wyzwanie (challenge
) i znacznik czasu (timestamp
) wymagane do autoryzacji. Odpowiedź zawiera dane w formacie JSON, które są niezbędne do utworzenia tokenu sesji.encryptToken($token, $challengeTimeMillis)
: Szyfruje token sesji przy użyciu klucza publicznego. Łączy token z wyzwaniem (challenge
) w formietoken|challengeTimeMillis
, a następnie szyfruje i koduje wynik w Base64.getKSeFSessionToken($encryptedToken, $challenge)
: Wysyła zaszyfrowany token sesji w formacie XML, aby uzyskać rzeczywisty token sesji z KSeF. Wykorzystuje DOMDocument do tworzenia struktury XML wymaganej przez API.sendInvoice($invoiceFile)
: Wysyła fakturę do systemu KSeF. Odczytuje plik faktury, generuje SHA-256 jako hash faktury i wysyła fakturę zakodowaną w Base64. W przypadku sukcesu zwraca odpowiedź z systemu.terminateSession()
: Zamyka aktywną sesję KSeF. Wysyła żądanie zamknięcia sesji z użyciemsessionToken
.
Opis funkcji:
getChallengeAndTimestamp
: Używana do otrzymania wyzwania (challenge
) i znacznika czasu, aby przygotować szyfrowanie tokenu sesji.encryptToken
: Wykorzystuje szyfrowanie asymetryczne z OpenSSL, aby zabezpieczyć token sesji. Funkcja ta jest kluczowa dla poprawnej autoryzacji w KSeF.sendInvoice
: Odpowiada za dostarczenie dokumentu faktury do KSeF.
<?php
class KSeFClient {
private $apiUrl;
private $nip;
private $apiKey;
private $publicKeyPath;
private $sessionToken;
public function __construct($apiUrl, $nip, $apiKey, $publicKeyPath) {
$this->apiUrl = $apiUrl;
$this->nip = $nip;
$this->apiKey = $apiKey;
$this->publicKeyPath = $publicKeyPath;
}
private function sendRequest($url, $data, $headers, $method = 'POST') {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
if ($curlError) {
echo "Błąd cURL: " . $curlError . "\n";
}
return ['response' => $response, 'httpCode' => $httpCode];
}
public function getChallengeAndTimestamp() {
$url = "{$this->apiUrl}/online/Session/AuthorisationChallenge";
$data = json_encode([
"contextIdentifier" => [
"type" => "onip",
"identifier" => $this->nip
]
]);
$headers = ["Content-Type: application/json", "Accept: application/json"];
$response = $this->sendRequest($url, $data, $headers);
if ($response['httpCode'] === 201) {
return json_decode($response['response'], true);
} else {
die("Błąd w uzyskiwaniu challenge: " . $response['response']);
}
}
private function encryptToken($token, $challengeTimeMillis) {
$dataToEncrypt = "$token|$challengeTimeMillis";
$encryptedToken = '';
$publicKey = file_get_contents($this->publicKeyPath);
if (openssl_public_encrypt($dataToEncrypt, $encryptedToken, $publicKey, OPENSSL_PKCS1_PADDING)) {
return base64_encode($encryptedToken);
} else {
echo "Nie udało się zaszyfrować tokenu.\n";
return false;
}
}
public function getKSeFSessionToken($encryptedToken, $challenge) {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElement('ns3:InitSessionTokenRequest');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns2', 'http://ksef.mf.gov.pl/schema/gtw/svc/types/2021/10/01/0001');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns3', 'http://ksef.mf.gov.pl/schema/gtw/svc/online/auth/request/2021/10/01/0001');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns4', 'http://ksef.mf.gov.pl/schema/gtw/svc/online/types/2021/10/01/0001');
$dom->appendChild($root);
$context = $dom->createElement('ns3:Context');
$root->appendChild($context);
$context->appendChild($dom->createElement('ns4:Challenge', $challenge));
$identifier = $dom->createElement('ns4:Identifier');
$identifier->setAttribute('xsi:type', 'ns2:SubjectIdentifierByCompanyType');
$identifier->appendChild($dom->createElement('ns2:Identifier', $this->nip));
$context->appendChild($identifier);
$tokenElement = $dom->createElement('ns4:Token', trim($encryptedToken));
$context->appendChild($tokenElement);
$url = "{$this->apiUrl}/online/Session/InitToken";
$headers = ["Content-Type: application/octet-stream", "Accept: application/json"];
$response = $this->sendRequest($url, $dom->saveXML(), $headers);
if ($response['httpCode'] === 200 || $response['httpCode'] === 201) {
$this->sessionToken = json_decode($response['response'], true)['sessionToken']['token'];
return $this->sessionToken;
} else {
echo "Błąd w uzyskiwaniu tokenu sesji.\n";
return false;
}
}
public function sendInvoice($invoiceFile) {
if (!file_exists($invoiceFile) || !is_readable($invoiceFile)) {
die("Plik faktury nie istnieje lub nie można go odczytać: $invoiceFile\n");
}
$invoiceData = file_get_contents($invoiceFile);
$hashSHA = base64_encode(hash('sha256', $invoiceData, true));
$invoiceBody = base64_encode($invoiceData);
$fSize = filesize($invoiceFile);
$body = json_encode([
"invoiceHash" => [
"fileSize" => $fSize,
"hashSHA" => ["algorithm" => "SHA-256", "encoding" => "Base64", "value" => $hashSHA]
],
"invoicePayload" => ["type" => "plain", "invoiceBody" => $invoiceBody]
]);
$headers = [
'Accept: application/json',
'SessionToken: ' . $this->sessionToken,
'Content-Type: application/json'
];
$response = $this->sendRequest("{$this->apiUrl}/online/Invoice/Send", $body, $headers, 'PUT');
$httpCode = $response['httpCode'];
if ($httpCode === 200 || $httpCode === 201) {
return json_decode($response['response'], true);
} elseif ($httpCode === 202) {
echo "Żądanie zaakceptowane do przetwarzania.\n";
return json_decode($response['response'], true);
} else {
echo "Błąd w wysyłaniu faktury.\n";
return false;
}
}
public function terminateSession() {
$url = "{$this->apiUrl}/online/Session/Terminate";
$headers = ['Accept: application/json', 'SessionToken: ' . $this->sessionToken];
$response = $this->sendRequest($url, null, $headers, 'GET');
if ($response['httpCode'] === 200) {
echo "Sesja została zakończona pomyślnie.\n";
return json_decode($response['response'], true);
} else {
echo "Błąd zamknięcia sesji.\n";
return false;
}
}
}
// Użycie klasy
$ksefClient = new KSeFClient("https://ksef-demo.mf.gov.pl/api", "nip", "ApiKey", __DIR__ . '/publicKey.pem');
$challengeData = $ksefClient->getChallengeAndTimestamp();
if ($challengeData) {
$challenge = $challengeData['challenge'];
$challengeTimeMillis = strtotime($challengeData['timestamp']) * 1000;
$encryptedToken = $ksefClient->encryptToken("xxx", $challengeTimeMillis);
if ($encryptedToken && $ksefClient->getKSeFSessionToken($encryptedToken, $challenge)) {
$ksefClient->sendInvoice(__DIR__ . '/faktura.xml');
$ksefClient->terminateSession();
}
}
?>