GPC export z Fio banky

Z ZděchovNET
Skočit na navigaci Skočit na vyhledávání

Úvod

Existuje mnoho druhů formátů umožňujících export dat z internetového bankovnictví. Ne každý formát má natolik pevnou strukturu, aby jej bylo možné snadno a spolehlivě zpracovávat pomocí programových prostředků. Česká banka Fio nabízí jako jednu z možností exportu formát GPC. Pokud potřebujete provádět automatické stahování dat v tomto formátu z internetového bankovnictví u zmíněné banky, tak můžete použít zde prezentované již hotové třídy. Pomocí nich lze snadno vyčítat periodicky informace o nových pohybech na účtu.

Třída GPC v gpc.php

Tato třída slouží pro dekódování GPC formátu na asociativní pole, s kterým se pak už snadno pracuje v PHP.

<?php

define('GPC_TYPE_REPORT', '074');
define('GPC_TYPE_ITEM', '075');

class GPC
{
  function ParseLine(string $Line): array
  {  
    $Line = ' '.$Line;
    $Type = substr($Line, 1, 3);                                                    
       
    if ($Type == GPC_TYPE_REPORT)
    {
      $GPCLine = array
      (
        'Type' => GPC_TYPE_REPORT,
        'AccountNumber' => substr($Line, 4, 16),
        'AccountName' => trim(substr($Line, 20, 20)),
        'OldBalanceDate' => mktime(0, 0, 0, substr($Line, 42, 2), substr($Line, 40, 2), '20'.substr($Line, 44, 2)),
        'OldBalanceValue' => (substr($Line, 60, 1).substr($Line, 46, 14)) / 100, 
        'NewBalanceValue' => (substr($Line, 75, 1).substr($Line, 61, 14)) / 100,
        'DebitValue' => (substr($Line, 90, 1).substr($Line, 76, 14)) / 100,    
        'CreditValue' => (substr($Line, 105, 1).substr($Line, 91, 14)) / 100,    
        'SequenceNumber' => intval(substr($Line, 106, 3)),
        'Date' => mktime(0, 0, 0, substr($Line, 111, 2), substr($Line, 109, 2), '20'.substr($Line, 113, 2)),
        //'DataAlignment' => substr($Line, 115, 14),
        'CheckSum' => sha1(md5($Line).$Line),
      );
    } else    
    if ($Type == GPC_TYPE_ITEM)
    {    
      $GPCLine = array
      (
        'Type' => GPC_TYPE_ITEM,
        'AccountNumber' => substr($Line, 4, 16),
        'OffsetAccount' => substr($Line, 20, 16), 
        'RecordNumber' => substr($Line, 36, 13), 
        'Value' => substr($Line, 49, 12) / 100,
        'Code' => substr($Line, 61, 1),    
        'VariableSymbol' => intval(substr($Line, 62, 10)),
        'BankCode' => substr($Line, 74, 4),
        'ConstantSymbol' => intval(substr($Line, 78, 4)),
        'SpecificSymbol' => intval(substr($Line, 82, 10)), 
        'Valut' => substr($Line, 92, 6),
        'ClientName' => substr($Line, 98, 20), 
        //'Zero' => substr($Line, 118, 1),
        'CurrencyCode' => substr($Line, 119, 4),
        'DueDate' => mktime(0, 0, 0, substr($Line, 125, 2), substr($Line, 123, 2), substr($Line, 127, 2)),
        'CheckSum' => sha1(md5($Line).$Line),
      );
    } else $GPCLine = NULL;
    
    return $GPCLine;
  }
}

Přístup přes bankovní API

Fio banka od 6.11.2012 zprovoznila své bankovní API, díky kterému je možné standardizovanou formou vyčítat informace o pohybech na účtech. Export lze provést do formátů XML, OFX, GPC, CSV, HTML, JSON a MT940. Zde je použit formát GPC. Detailní popis API najdete v API_Bankovnictvi.pdf.

  • Pro přístup přes API je nutno si vygenerovat v internetovém bankovnictví pro každý účet unikátní token.
  • Kódování GPC exportu je Windows-1250, takže se provádí konverze kódování na UTF-8

Třída Fio v fio_api.php

<?php
 
include('gpc.php');
 
class FioAPI
{
  public string $Token;
  public string $Encoding;
  
  function __construct()
  {
    $this->Encoding = 'utf-8';
  }
 
  function Import($TimeFrom, $TimeTo)
  {
    if ($this->Token == '') throw new Exception('Missing value for Token property.');
 
    $fp = fsockopen('ssl://www.fio.cz', 443, $errno, $errstr, 30);
    if (!$fp) 
    {
      throw new Exception('Connection error: '.$errstr);
    } else 
    {
      // Send request
      $RequestURL = '/ib_api/rest/periods/'.$this->Token.'/'.
        date('Y-m-d', $TimeFrom).'/'.date('Y-m-d', $TimeTo).'/transactions.gpc';
      $Request = "GET ".$RequestURL." HTTP/1.1\r\n";
      $Request .= "Host: www.fio.cz\r\n";
      $Request .= "User-Agent: PHP Script\r\n";
      $Request .= "Content-Type: text/html\r\n";
      $Request .= "Connection: Close\r\n\r\n";
      fwrite($fp, $Request);
 
      // Read response
      $Response = array();
      while (!feof($fp)) 
      {
        $Response .= trim(fgets($fp, 1024))."\n";
      }
      fclose($fp);      
      $Response = iconv('windows-1250', $this->Encoding, $Response);
      $Response = explode("\n", $Response);
 
      // Strip HTTP header
      while ($Response[0] != '') array_shift($Response);
      array_shift($Response); // Remove empty line
 
      // Parse all GPC lines
      $GPC = new GPC();
      $Result = array();
      foreach ($Response as $Index => $Line)
      {
	    if (($Index == 0) and (substr($Line, 0, strlen(GPC_TYPE_REPORT)) != GPC_TYPE_REPORT)) $this->NoValidDataError($Response);
        $GPCLine = $GPC->ParseLine($Line);
        if ($GPCLine != NULL) $Result[] = $GPCLine;
      }
      return $Result;
    }
  }
 
  function NoValidDataError(array $Response)
  {
    // Try to get error message
    // If something go wrong fio show HTML login page and display error message
	$Response = implode('', $Response);
    $ErrorMessageStart = '<div id="oldform_warning">';
    if (strpos($Response, $ErrorMessageStart) !== false) 
	{
	  $Response = substr($Response, strpos($Response, $ErrorMessageStart) + strlen($ErrorMessageStart));
	  $ErrorMessage = trim(substr($Response, 0, strpos($Response, '</div>')));
	} else $ErrorMessage = '';
	throw new Exception('No valid GPC data: '.$ErrorMessage);
  }
}

Ukázka použití v demo_fio_api.php

<?php

include('fio_api.php');

$Fio = new FioAPI();
$Fio->Token = 'unikátní vygenerovaný token';
$Records = $Fio->Import(time() - 3600 * 24 * 31 * 2, time());

echo('<!DOCTYPE html>');
echo('<html><head><meta charset="utf-8"></head><body>');
echo('<table border="1">');
foreach ($Records as $Record)
{
  echo('<tr>');
  if ($Record['Type'] == GPC_TYPE_REPORT) 
  {
    echo('<td>Jméno účtu: '.$Record['AccountName'].'</td>');
    echo('<td>Číslo účtu: '.$Record['AccountNumber'].'</td>');
    echo('<td>Ke dni '.date('j.n.Y', $Record['OldBalanceDate']).' je stav účtu '.$Record['OldBalanceValue'].' Kč</td>');
    echo('<td>Ke dni '.date('j.n.Y', $Record['Date']).' je stav účtu '.$Record['NewBalanceValue'].' Kč</td>');
    echo('<td>Suma příjmů: '.$Record['CreditValue'].' Kč</td>');
    echo('<td>Suma výdajů: '.$Record['DebitValue'].' Kč</td>');

    echo('</tr></table><br><br>');
    echo('<table border="1"><tr>');

    echo('<th>Datum</th>');
    echo('<th>Částka</th>');
    echo('<th>Účet protistrany</th>');
    echo('<th>Kód banky</th>');
    echo('<th>KS</th>');
    echo('<th>VS</th>');
    echo('<th>SS</th>');
    echo('<th>Uživatelská identifikace</th>');
  } else
  if ($Record['Type'] == GPC_TYPE_ITEM) 
  {
    echo('<td>'.date('j.n.Y', $Record['DueDate']).'</td>');
    echo('<td>'.$Record['Value'].'</td>');
    echo('<td>'.$Record['OffsetAccount'].'</td>');
    echo('<td>'.$Record['BankCode'].'</td>');
    echo('<td>'.$Record['ConstantSymbol'].'</td>');
    echo('<td>'.$Record['VariableSymbol'].'</td>');
    echo('<td>'.$Record['SpecificSymbol'].'</td>');
    echo('<td>'.$Record['ClientName'].'</td>');
  }          
  echo('</tr>');
}
echo('</table>');
echo('</body></html>');

Přístup přes přímé webové rozhraní

Přímý přístup byla jediná použitelná metoda v době, kdy neexistovalo samostatné bankovní API rozhraní. Data bylo možné získat ve formátu GPC, CSV nebo přímým rozkládáním HTML. Zde je použit export do formátu GPC.

  • Kódování webu Fio je Windows-1250, takže se provádí konverze kódování na UTF-8
  • Při zasílání formuláře se generuje aktuální čas pomocí funkce time() v parametru LOGIN_TIME podle času počítače, na kterém běží skript. Čas na vašem počítači musí být blízký času na Fio serveru. Tedy synchronizovaný přes NTP z internetu. Pokud je čas zpožděn o více než asi 3-5 minut, dojde ke zobrazení hlášení "Přihlašovací formulář byl již neplatný. Zkuste se přihlásit znovu".

Třída Fio v fio.php

<?php
 
include('gpc.php');
 
class Fio 
{
  public string $UserName;
  public string $Password;
  public string $Account;  
  public string $Encoding;
 
  function __construct()
  {
    $this->Encoding = 'utf-8';
  }

  function Import($TimeFrom, $TimeTo)
  {
    if ($this->UserName == '') throw new Exception('Missing value for UserName property.');
    if ($this->Password == '') throw new Exception('Missing value for Password property.');
    if (!is_numeric($this->Account)) throw new Exception('Missing or not numeric value for Account property.');
	
    $fp = fsockopen('ssl://www.fio.cz', 443, $errno, $errstr, 30);
    if (!$fp) 
    {
      throw new Exception('Connection error: '.$errstr);
    } else 
    {
      // Send request
      $RequestURL = "/scgi-bin/hermes/dz-pohyby.cgi?ID_ucet=".$this->Account.
        "&LOGIN_USERNAME=".$this->UserName."&SUBMIT=Odeslat&LOGIN_TIME=".time().
        "&LOGIN_PASSWORD=".$this->Password."&pohyby_DAT_od=".date('d.m.Y', $TimeFrom).
        "&pohyby_DAT_do=".date('d.m.Y', $TimeTo)."&export_gpc=1";
      $Request = "GET ".$RequestURL." HTTP/1.1\r\n";
      $Request .= "Host: www.fio.cz\r\n";
      $Request .= "User-Agent: PHP Script\r\n";
      $Request .= "Content-Type: text/html\r\n";
      $Request .= "Connection: Close\r\n\r\n";
      fwrite($fp, $Request);
 
      // Read response
      $Response = array();
      while (!feof($fp))
      {
        $Response .= trim(fgets($fp, 1024))."\n";
      }
      fclose($fp);      
      $Response = iconv('windows-1250', $this->Encoding, $Response);
      $Response = explode("\n", $Response);
 
      // Strip HTTP header
      while ($Response[0] != '') array_shift($Response);
      array_shift($Response); // Remove empty line
      
      // Parse all GPC lines
      $GPC = new GPC();
      $Result = array();
      foreach ($Response as $Index => $Line)
      {
	    if (($Index == 0) and (substr($Line, 0, strlen(GPC_TYPE_REPORT)) != GPC_TYPE_REPORT)) $this->NoValidDataError($Response);
        $GPCLine = $GPC->ParseLine($Line);
        if ($GPCLine != NULL) $Result[] = $GPCLine;
      }
      return $Result;
    }
  }
  
  function NoValidDataError(array $Response)
  {
    // Try to get error message
    // If something go wrong Fio show HTML login page and display error message
	$Response = implode('', $Response);
    $ErrorMessageStart = '<div id="oldform_warning">';
    if (strpos($Response, $ErrorMessageStart) !== false) 
	{
	  $Response = substr($Response, strpos($Response, $ErrorMessageStart) + strlen($ErrorMessageStart));
	  $ErrorMessage = trim(substr($Response, 0, strpos($Response, '</div>')));
	} else $ErrorMessage = '';
	throw new Exception('No valid GPC data: '.$ErrorMessage);
  }
}

Ukázka použití v demo_fio.php

<?php

include('fio.php');

$Fio = new Fio();
$Fio->Account = '123456789';
$Fio->UserName = 'user';
$Fio->Password = 'pass';
$Records = $Fio->Import(time() - 3600 * 24 * 31 * 2, time());

echo('<!DOCTYPE html>');
echo('<html><head><meta charset="utf-8"></head><body>');
echo('<table border="1">');
foreach ($Records as $Record)
{
  echo('<tr>');
  if ($Record['Type'] == GPC_TYPE_REPORT) 
  {
    echo('<td>Jméno účtu: '.$Record['AccountName'].'</td>');
    echo('<td>Číslo účtu: '.$Record['AccountNumber'].'</td>');
    echo('<td>Ke dni '.date('j.n.Y', $Record['OldBalanceDate']).' je stav účtu '.$Record['OldBalanceValue'].' Kč</td>');
    echo('<td>Ke dni '.date('j.n.Y', $Record['Date']).' je stav účtu '.$Record['NewBalanceValue'].' Kč</td>');
    echo('<td>Suma příjmů: '.$Record['CreditValue'].' Kč</td>');
    echo('<td>Suma výdajů: '.$Record['DebitValue'].' Kč</td>');

    echo('</tr></table><br><br>');
    echo('<table border="1"><tr>');

    echo('<th>Datum</th>');
    echo('<th>Částka</th>');
    echo('<th>Účet protistrany</th>');
    echo('<th>Kód banky</th>');
    echo('<th>KS</th>');
    echo('<th>VS</th>');
    echo('<th>SS</th>');
    echo('<th>Uživatelská identifikace</th>');
  } else
  if ($Record['Type'] == GPC_TYPE_ITEM) 
  {
    echo('<td>'.date('j.n.Y', $Record['DueDate']).'</td>');
    echo('<td>'.$Record['Value'].'</td>');
    echo('<td>'.$Record['OffsetAccount'].'</td>');
    echo('<td>'.$Record['BankCode'].'</td>');
    echo('<td>'.$Record['ConstantSymbol'].'</td>');
    echo('<td>'.$Record['VariableSymbol'].'</td>');
    echo('<td>'.$Record['SpecificSymbol'].'</td>');
    echo('<td>'.$Record['ClientName'].'</td>');
  }          
  echo('</tr>');
}
echo('</table>');
echo('</body></html>');

Pokud něco nefunguje jak má

  • Třídy jsou navrženy tak, aby při výskytu chyby vyvolaly výjimku. Pokud z popisu chyby není zřejmá příčina, je potřeba chybu ladit a do kódu přidávat dočasně ladící výpisy. Především ze zobrazení obsahu načítané stránky z webu Fio by mělo vyplynout odkud vítr vane.

Vnější odkazy