Package home | Report new bug | New search | Development Roadmap Status: Open | Feedback | All | Closed Since Version 1.4.3

Bug #4637 Invalid DTA File with SFIRM
Submitted: 2005-06-20 05:33 UTC Modified: 2008-12-15 20:21 UTC
From: m dot hosse at geodigital dot org Assigned: mschuett
Status: Closed Package: Payment_DTA
PHP Version: 4.3.11 OS:
Roadmaps: (Not assigned)    
Subscription  


 [2005-06-20 05:33 UTC] m dot hosse at geodigital dot org
Description: ------------ Hi i tried to import a DTA-File into SFIRM (banking software of the Sparkasse). This software intensivly checks the structure of the file and returns a very good report. The report for a file made by your class, shows one error: File-Length has to be a multiple of 128. The problem ist the part c. This has a length of 187 + 29*x. I added some spaces to reach a length of 256 and then the error has gone. So i added some code in getFileContent just before record e. Maybe you can use it. ---snip--- // Add Spaces for a content length that is a multiple of 128 $content .= str_repeat (" ",128-strlen($content) % 128); /** * data record E */ ---snap--- cu Michael

Comments

 [2005-06-20 07:02 UTC] hstainer
How did you insert the reasons for payment? One line via a string or two or more lines via an array? Could you maybe try it with at least two lines of reasons for payment? If it works, we know that the problem is located here.
 [2005-10-13 09:43 UTC] h dot hosse at geodigital dot org
Hi sorry for the delayed answer. I had to work on other projects. You are right. The problem occurs only if there is only one line of reasons for payment. With two lines my added code did not work. So i changed it a little bit. ---snip--- // Add Spaces for a content length that is a multiple of 128 if (strlen($content) % 128 != 0) $content .= str_repeat (" ",128-strlen($content) % 128); ---snap--- cu Michael
 [2005-11-03 16:10 UTC] pear at martinholtz dot de
Hi, nimmt es mir bitte nicht übel, aber da Hermann Stainer deutsch spricht und mir das leichter von der Hand geht, heute in deutsch: Wir hatten zuerst genau das selber Problem wie Michael. Seine Lösung hat hervorragend mit einer Buchung funktioniert. Leider nicht mit mehreren. Und: das bezieht sich jetzt spezielle auf Sparkassen! Hier kann man nachlesen, was Sparkassen erwarten: http://www.sskm.de/download/tipps_hilfe/sfirm32/DTAUS_Euro.pdf (von der Sparkasse München) Dies ist auch eine nette Erklärung: http://www.zahlungsverkehrsfragen.de/textsl_dta.html Und die Bundesbank meint folgendes: http://www.bundesbank.de/download/zahlungsverkehr/zv_spezifikationen_v1_0_de.pdf (S.367ff - ist im PDF aus Seite 387) Geholfen hat uns auch http://www.xpecto.de/dtauschecker/ Inzwischen glaube ich verstanden zu haben, wo das Problem liegt: Die Sparkassen halten sich nicht an die Spezifikation der Bundesbank (oder ich hab was übersehen). Die Pear-Klasse ermöglicht eine größere Anzahl an zusätzichen Erweiterungsteilen. Leider erwarten die Sparkassen immer 128 Byte Blöcke. D.h. das Anhängen von 29-Byte Blöcken geht schief. hier unsere Version des "C-Parts" in getFileContent: Achtung: ich gehe davon aus, dass diese Lösung tatsächlich nur für die Sparkassen funktioniert. Ein Telefonat mit der Sparkasse Münsterland-Ost hat leider nicht klären können, ob das Feld für die Satzlänge überhaupt berücksichtigt wird. (Wir haben jetzt noch nicht getestet, was passiert wenn wir wirklich alle möglichen Erweiterungsdatensätze gefüllt habe.) /** * data record(s) C */ foreach ($this->exchanges as $exchange) { $sum_account_numbers += $exchange['receiver_account_number']; $sum_bank_codes += $exchange['receiver_bank_code']; $sum_amounts += $exchange['amount']; $additional_purposes = $exchange['purposes']; $first_purpose = array_shift($additional_purposes); $additional_parts = array(); if (strlen($exchange['receiver_additional_name']) > 0) { $additional_parts[] = array("type" => "01", "content" => $exchange['receiver_additional_name'] ); } foreach ($additional_purposes as $additional_purpose) { $additional_parts[] = array("type" => "02", "content" => $additional_purpose ); } if (strlen($exchange['sender_additional_name']) > 0) { $additional_parts[] = array("type" => "03", "content" => $exchange['sender_additional_name'] ); } $additional_parts_number = count($additional_parts); // record length (187 Bytes + 29 Bytes for each additional part) $content .= str_pad (187 + $additional_parts_number * 29, 4, "0", STR_PAD_LEFT); // record type $content .= "C"; // first involved bank $content .= str_pad ($exchange['sender_bank_code'], 8, "0", STR_PAD_LEFT); // receiver's bank code $content .= str_pad ($exchange['receiver_bank_code'], 8, "0", STR_PAD_LEFT); // receiver's account number $content .= str_pad ($exchange['receiver_account_number'], 10, "0", STR_PAD_LEFT); // internal customer number (11 chars) or NULL $content .= "0" . str_repeat ("0", 11) . "0"; // payment mode (text key) $content .= ($this->type == DTA_CREDIT) ? "51" : "05"; // additional text key $content .= "000"; // bank internal $content .= " "; // free (reserve) $content .= str_repeat ("0", 11); // sender's bank code $content .= str_pad ($exchange['sender_bank_code'], 8, "0", STR_PAD_LEFT); // sender's account number $content .= str_pad ($exchange['sender_account_number'], 10, "0", STR_PAD_LEFT); // amount $content .= str_pad ($exchange['amount'], 11, "0", STR_PAD_LEFT); // free (reserve) $content .= str_repeat (" ", 3); // receiver's name $content .= str_pad ($exchange['receiver_name'], 27, " ", STR_PAD_RIGHT); // delimitation $content .= str_repeat (" ", 8); // sender's name $content .= str_pad ($exchange['sender_name'], 27, " ", STR_PAD_RIGHT); // first line of purposes $content .= str_pad ($first_purpose, 27, " ", STR_PAD_RIGHT); // currency (1 = Euro) $content .= "1"; // free (reserve) $content .= str_repeat (" ", 2); // amount of additional parts $content .= str_pad ($additional_parts_number, 2, "0", STR_PAD_LEFT); /* 2005.11.02 mh: * (Achtung: scheint nur für Sparkassen zu gelten?) * * ich gehe davon aus, dass die ersten zwei "additional_parts" zwingend nötig * sind. d.h. ich kommentiere die if's aus und alles wird gut. * Weiter gehe ich davon aus, dass auch die beiden nicht mitzählen bei der Angabe * der Erweiterungsblöck! * * auf http://www.infodrom.org/projects/dtaus/dtaus.php3 steht: * "So können weitere Datensätze angehängt werden. Sie müssen insgesamt jedoch * 128 Zeichen lang sein. Also vier 29-Zeichen Blöcke und anschließend mit 12 * Blanks auffüllen. Maximal vier 128-Zeichen-Records, wobei der vierte maximal * einen Erweiterungsdatensatz enthält." * */ // if (count($additional_parts) > 0) { for ($index = 1;$index <= 2;$index++) { if (count($additional_parts) > 0) { $additional_part = array_shift($additional_parts); } else { $additional_part = array("type" => " ", "content" => "" ); } // type of addional part $content .= $additional_part['type']; // additional part content $content .= str_pad ($additional_part['content'], 27, " ", STR_PAD_RIGHT); } // delimitation $content .= str_repeat (" ", 11); // } // if /* * auf http://www.infodrom.org/projects/dtaus/dtaus.php3 steht: * "So können weitere Datensätze angehängt werden. Sie müssen insgesamt jedoch * 128 Zeichen lang sein. Also vier 29-Zeichen Blöcke und anschließend mit 12 * Blanks auffüllen. Maximal vier 128-Zeichen-Records, wobei der vierte maximal * * * Das bedeutet an dieser Stelle, dass falls noch additional_parts da sind, * die Schleife für VIER zusätzliche Teile durchgeführt werden muss. * Danach kann dann nur noch ein Block hinzugefügt werden! */ if (count($additional_parts) > 0) { // auch wenn nur ein zusätzlicher Text existiert, müssen // alle 4 Blöcke im Zweifel auch mit Leerzeichen gefüllt werden for ($index = 1;$index <= 4;$index++) { if (count($additional_parts) > 0) { $additional_part = array_shift($additional_parts); } else { $additional_part = array("type" => " ", "content" => "" ); } // type of addional part $content .= $additional_part['type']; // additional part content $content .= str_pad ($additional_part['content'], 27, " ", STR_PAD_RIGHT); } // delimitation $content .= str_repeat (" ", 12); } // Falls jetzt immer noch additional_parts existieren, // darf davon nur noch einer auch ausgegeben werden: if (count($additional_parts) > 0) { $additional_part = array_shift($additional_parts); $content .= $additional_part['type']; $content .= str_pad($additional_part['content'],27,' ',STR_PAD_RIGHT); // und jetzt die restlichen zeichen auffüllen! $content .= str_repeat(' ',99); // 128 - 29 Zeichen } } // foreach exchange
 [2005-11-04 01:43 UTC] m dot hosse at geodigital dot org
Hi ich antworte mal jetzt auch in deutsch. IMO ist DTA sowieso eine rein deutsche Angelegenheit. :-) Es ist richtig, daß meine Lösung nur mit einer Überweisung korrekt funktionierte. Ich habe mir das nochmal eingehender angeschaut und bin zu folgenden Ergebnissen gekommen: 1. Die Datensatzlänge der C-Sätze muß immer durch 128 teilbar sein. 2. Es müssen keine Erweiterungssätze verwendet werden. 3. Wenn leere Erweiterungssätze verwendet werden, dann meckert das SFIRM-DTA-Prüfprogramm. Leere Erweiterungssätze sind solche, die zwar einen Typ haben, aber keine Daten übermitteln. 4. Die Satzlänge wird schon berücksichtigt. Nur gelten leere Erweiterungssätze nicht als Daten. Insofern passt es dann wieder. Ich habe diese Punkte nun mal umgesetzt und die Dateien (mit mehreren Überweisungen gegen den SFIRM-DTA-Checker und gegen den Online-Checker http://www.xpecto.de/dtauschecker/ geprüft. In beiden Fällen treten nun auch keine Warnungen mehr auf. Was wurde geändert: 1. Zusätzliche Verwendungszwecke werden nur noch ausgegeben, wenn sie nicht leer sind. foreach ($additional_purposes as $additional_purpose) { if (trim($additional_purpose) != "") $additional_parts[] = array("type" => "02", "content" => $additional_purpose); } 2. Die Erweiterung auf 128 bzw. 256 Zeichen passiert nun in der foreach-Schleife für die C-Datensätze. Hier nochmal die gesamte Methode: function getFileContent() { $content = ""; $sum_account_numbers = 0; $sum_bank_codes = 0; $sum_amounts = 0; /** * data record A */ // record length (128 Bytes) $content .= str_pad ("128", 4, "0", STR_PAD_LEFT); // record type $content .= "A"; // file mode (credit or debit) $content .= ($this->type == DTA_CREDIT) ? "G" : "L"; // Customer File ("K") / Bank File ("B") $content .= "K"; // sender's bank code $content .= str_pad ($this->account_file_sender['bank_code'], 8, "0", STR_PAD_LEFT); // only used if Bank File, otherwise NULL $content .= str_repeat ("0", 8); // sender's name $content .= str_pad ($this->account_file_sender['name'], 27, " ", STR_PAD_RIGHT); // date of file creation $content .= strftime("%d%m%y", $this->timestamp); // free (bank internal) $content .= str_repeat (" ", 4); // sender's account number $content .= str_pad ($this->account_file_sender['account_number'], 10, "0", STR_PAD_LEFT); // sender's reference number (optional) $content .= str_repeat ("0", 10); // free (reserve) $content .= str_repeat (" ", 15); // execution date ("DDMMYYYY", optional) $content .= str_repeat (" ", 8); // free (reserve) $content .= str_repeat (" ", 24); // currency (1 = Euro) $content .= "1"; /** * data record(s) C */ foreach ($this->exchanges as $exchange) { $sum_account_numbers += $exchange['receiver_account_number']; $sum_bank_codes += $exchange['receiver_bank_code']; $sum_amounts += $exchange['amount']; $additional_purposes = $exchange['purposes']; $first_purpose = array_shift($additional_purposes); $additional_parts = array(); if (strlen($exchange['receiver_additional_name']) > 0) { $additional_parts[] = array("type" => "01", "content" => $exchange['receiver_additional_name'] ); } foreach ($additional_purposes as $additional_purpose) { if (trim($additional_purpose) != "") $additional_parts[] = array("type" => "02", "content" => $additional_purpose); } if (strlen($exchange['sender_additional_name']) > 0) { $additional_parts[] = array("type" => "03", "content" => $exchange['sender_additional_name'] ); } $additional_parts_number = count($additional_parts); // record length (187 Bytes + 29 Bytes for each additional part) $content .= str_pad (187 + $additional_parts_number * 29, 4, "0", STR_PAD_LEFT); // record type $content .= "C"; // first involved bank $content .= str_pad ($exchange['sender_bank_code'], 8, "0", STR_PAD_LEFT); // receiver's bank code $content .= str_pad ($exchange['receiver_bank_code'], 8, "0", STR_PAD_LEFT); // receiver's account number $content .= str_pad ($exchange['receiver_account_number'], 10, "0", STR_PAD_LEFT); // internal customer number (11 chars) or NULL $content .= "0" . str_repeat ("0", 11) . "0"; // payment mode (text key) $content .= ($this->type == DTA_CREDIT) ? "51" : "05"; // additional text key $content .= "000"; // bank internal $content .= " "; // free (reserve) $content .= str_repeat ("0", 11); // sender's bank code $content .= str_pad ($exchange['sender_bank_code'], 8, "0", STR_PAD_LEFT); // sender's account number $content .= str_pad ($exchange['sender_account_number'], 10, "0", STR_PAD_LEFT); // amount $content .= str_pad ($exchange['amount'], 11, "0", STR_PAD_LEFT); // free (reserve) $content .= str_repeat (" ", 3); // receiver's name $content .= str_pad ($exchange['receiver_name'], 27, " ", STR_PAD_RIGHT); // delimitation $content .= str_repeat (" ", 8); // sender's name $content .= str_pad ($exchange['sender_name'], 27, " ", STR_PAD_RIGHT); // first line of purposes $content .= str_pad ($first_purpose, 27, " ", STR_PAD_RIGHT); // currency (1 = Euro) $content .= "1"; // free (reserve) $content .= str_repeat (" ", 2); // amount of additional parts $content .= str_pad ($additional_parts_number, 2, "0", STR_PAD_LEFT); if (count($additional_parts) > 0) { for ($index = 1;$index <= 2;$index++) { if (count($additional_parts) > 0) { $additional_part = array_shift($additional_parts); } else { $additional_part = array("type" => " ", "content" => "" ); } // type of addional part $content .= $additional_part['type']; // additional part content $content .= str_pad ($additional_part['content'], 27, " ", STR_PAD_RIGHT); } // delimitation $content .= str_repeat (" ", 11); } for ($part = 3;$part <= 5;$part++) { if (count($additional_parts) > 0) { for ($index = 1;$index <= 4;$index++) { if (count($additional_parts) > 0) { $additional_part = array_shift($additional_parts); } else { $additional_part = array("type" => " ", "content" => "" ); } // type of addional part $content .= $additional_part['type']; // additional part content $content .= str_pad ($additional_part['content'], 27, " ", STR_PAD_RIGHT); } // delimitation $content .= str_repeat (" ", 12); } } // Add Spaces for a content length that is a multiple of 128 if (strlen($content) % 128 != 0) $content .= str_repeat (" ",128-strlen($content) % 128); } /** * data record E */ // record length (128 bytes) $content .= str_pad ("128", 4, "0", STR_PAD_LEFT); // record type $content .= "E"; // free (reserve) $content .= str_repeat (" ", 5); // number of records type C $content .= str_pad (count($this->exchanges), 7, "0", STR_PAD_LEFT); // free (reserve) $content .= str_repeat ("0", 13); // sum of account numbers $content .= str_pad ($sum_account_numbers, 17, "0", STR_PAD_LEFT); // sum of bank codes $content .= str_pad ($sum_bank_codes, 17, "0", STR_PAD_LEFT); // sum of amounts $content .= str_pad ($sum_amounts, 13, "0", STR_PAD_LEFT); // delimitation $content .= str_repeat (" ", 51); return $content; } cu Michael
 [2008-04-01 07:59 UTC] doconnor (Daniel O'Connor)
> Die Sparkassen halten sich nicht an die Spezifikation der Bundesbank (oder ich hab was übersehen). Did this one turn out to be the savings bank incorrectly implementing the standard, or the package incorrectly outputting invalid length records? Uh, for those of us equipped with only high school level german, any chance of english translation
 [2008-05-06 10:09 UTC] narkat (Jan Ullrich)
@ doconnor: the problem just occurs when the method 'addExchange' is called with "wrong" parameters. the array which determines the purpose-lines, has to be at least 2 elements big, with just 1 purpose line this error is what renders the dta-file invalid. see the patch 'correctUsage' for "details" ;) greetings Jan
 [2008-05-06 11:40 UTC] hstainer (Hermann Stainer)
Thank you all for the useful information, great work! I will include this in the next version, which should be released next week (also see http://pear.php.net/bugs/13067)! Personally I am using Moneyplex for online banking, so if this already works with SFIRM and my tests are also successful, this should be an important improvement.
 [2008-12-15 20:21 UTC] mschuett (Martin Schütte)
I have fixed the handling of extensions and multiple purpose lines in v1.3.0a1 (will merge it into the stable 1.2 later on). Now it should work with any number of purpose lines up to 14 (the specified limit).