V. Kl. Klicpery 715
50401 Nový Bydžov
606 622 826
EET implementace v PHP
Rád bych se podělil o funkční PHP skript pro odeslání EET účtenky.
Použil jsem volně dostupnou ukázku od Davida Spilky, který udělal skutečně skvělou práci - odkaz, ale skript mi bohužel nefungoval. Několik hodin jsem bojoval s opakující se chybou: Chyba : 3 - XML zpráva nevyhověla kontrole XML schématu.
Další nevýhodou bylo, že skript požaduje mít převedené certifikáty na PEM a KEY. To mi přišlo zbytečně složité. Podařilo se mi tedy skript doplnit o převod z P12 a níže uvádím funkční verzi, která vrací FIK kód. Požaduje pouze p12 certifikát a jeho heslo.
Abyste si mohli ukázku vyzkoušet, pak si stáhněte sadu certifikátu z odkazu http://www.etrzby.cz/cs/technicka-specifikace (EET_CA1_Playground_v1.zip). Z tohoto souboru použijte certifikát EET_CA1_Playground-CZ1212121218.p12 (ten jsem si uložil do složky eet_cert/eet.p12), heslo k němu je 'eet'.
Pozn.: na serveru potřebujete verzi PHP 5.4.4 a vyšší.
Celý skript si můžete zkopírovat zde:
<?
$certs = [];
$pkcs12 = file_get_contents("eet_cert/eet.p12");
if (!extension_loaded('openssl') || !function_exists('openssl_pkcs12_read')) {
echo("Rozsireni OpenSSL neni dostupne.");
exit;
}
$openSSL = openssl_pkcs12_read($pkcs12, $certs, 'eet'); //heslo
if(!$openSSL)
{
echo("Certifikat se nepodarilo vyexportovat.");
exit;
}
date_default_timezone_set("Europe/Prague");
$dir=dirname($_SERVER["SCRIPT_FILENAME"]);
$eet['url']='https://pg.eet.cz/eet/services/EETServiceSOAP/v3'; // playground
$eet['key']=$certs['pkey'];
$tmp=$certs['cert'];
$tmp=explode('CERTIFICATE-----',$tmp);
$tmp=explode('-----END',$tmp[1]);
$data["certb64"]=$tmp[0];
$bodyTemplate='<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="TheBody" xml:id="TheBody"><Trzba xmlns="http://fs.mfcr.cz/eet/schema/v3"><Hlavicka @{dat_odesl} @{overeni} @{prvni_zaslani} @{uuid_zpravy}></Hlavicka><Data @{celk_trzba} @{cerp_zuct} @{cest_sluz} @{dan1} @{dan2} @{dan3} @{dat_trzby} @{dic_popl} @{dic_poverujiciho} @{id_pokl} @{id_provoz} @{porad_cis} @{pouzit_zboz1} @{pouzit_zboz2} @{pouzit_zboz3} @{rezim} @{urceno_cerp_zuct} @{zakl_dan1} @{zakl_dan2} @{zakl_dan3} @{zakl_nepodl_dph}></Data><KontrolniKody><pkp cipher="RSA2048" digest="SHA256" encoding="base64">${pkp}</pkp><bkp digest="SHA1" encoding="base16">${bkp}</bkp></KontrolniKody></Trzba></soap:Body>';
$signatureTemplate='<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"></ec:InclusiveNamespaces></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod><ds:Reference URI="#TheBody"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod><ds:DigestValue>${digest}</ds:DigestValue></ds:Reference></ds:SignedInfo>';
$requestTemplate='<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1"><wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="TheCert">${certb64}</wsse:BinarySecurityToken><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="TheSignature">'.$signatureTemplate.'<ds:SignatureValue>${signature}</ds:SignatureValue><ds:KeyInfo Id="TheKeyInfo"><wsse:SecurityTokenReference wsu:Id="TheSecurityTokenReference"><wsse:Reference URI="#TheCert" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/></wsse:SecurityTokenReference></ds:KeyInfo></ds:Signature></wsse:Security></SOAP-ENV:Header>${soap_body}</soap:Envelope>';
// priprav si data pro uctenku
$data["dat_odesl"]='';
$data["prvni_zaslani"]='true';
$data["uuid_zpravy"]='';
$data["dic_popl"]='CZ1212121218'; //DIC ponechte, aby Vam ukazka s danym certifikatem fungovala.
$data["id_provoz"]='11';
$data["id_pokl"]='1';
$data["porad_cis"]='1';
$data["dat_trzby"]='';
$data["celk_trzba"]=price(100);
$data["rezim"]='0';
$data["pkp"]='';
$data["bkp"]='';
// generuj datumy
$data["dat_odesl"]=date("c");
if (empty($data["dat_trzby"])) $data["dat_trzby"]=date("c");
if (empty($data["uuid_zpravy"])) $data["uuid_zpravy"]=uuid_v4();// vypočti PKP a BKP
$pkpInput=$data["dic_popl"].'|'.$data["id_provoz"].'|'.$data["id_pokl"].'|'.$data["porad_cis"].'|'.$data["dat_trzby"].'|'.$data["celk_trzba"];
openssl_sign($pkpInput, $signature, $eet['key'], 'SHA256');
$data["pkp"]=base64_encode($signature);
$data["bkp"]=BKB(sha1($signature));
// sestav tělo soap zprávy
$data["soap_body"]=replaceData($bodyTemplate,$data);
// vypočti digest
$data["digest"] = base64_encode(hash('sha256',$data["soap_body"],true));
// sestav podpisovou část a podepiš
$signatureFinal=replaceData($signatureTemplate,$data);
openssl_sign($signatureFinal, $signature, $eet['key'], 'SHA256');
$data["signature"]=base64_encode($signature);
// sestav finální XML zprávu pro EET
$xmlFinal=replaceData($requestTemplate,$data);
// Odešli účtenku
$opts = array(
'http' => array(
'method' => 'POST',
'header' => "Content-Type: text/xml;charset=UTF-8\r\nSOAPAction: http://fs.mfcr.cz/eet/OdeslaniTrzby",
'content' => $xmlFinal
)
);
$context=stream_context_create($opts);
$result=file_get_contents($eet['url'], false, $context);
echo"<textarea rows=80 cols=160>$result</textarea>";
function uuid_v4(){
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0x0fff)|0x4000,mt_rand(0,0x3fff)|0x8000,mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff));
}
function price($value)
{
return number_format($value, 2, '.', '');
}
function BKB($code){
$r='';
for ($i=0;$i<40;$i++)
{
if ($i%8==0&&$i!=0) $r.= '-';
$r.=$code[$i];
}
return $r;
}
function replaceData($template,$data){
foreach ($data as $key => $value) {
$template=str_replace("\${".$key."}",$value,$template);
$template=str_replace("@{".$key."}","$key=\"$value\"",$template);
}
// odstraň prázdná pole
$template=preg_replace("/\\$\\{[a-z_0-9:]+\\}/","",$template);
$template=preg_replace("/ @\\{[a-z_0-9:]+\\}/","",$template);
return $template;
}
?>
Veškeré zásluhy budiž připsány výše zmíněnému Davidu Spilkovi, kterému tímto děkuji.
EO.CZ - nabízíme EET pokladny. Máme důkladně otestovanou a do malých provozoven doporučujeme jednoduchou pokladnu XPOS CHD 3050: https://www.eo.cz/RXEPR03001S-x-pos-registracni-pokladna-chd3050.html
Autor článku: Ing. Marián Hudec - EO Computers - EO.CZ
Zde můžete okomentovat článek nebo nám nechat vzkaz: