Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 849 lines (795 sloc) 32.714 kB
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
1 <?php
2 /**
3 * DTAZV
4 *
5 * DTAZV is a class that provides functions to create DTAZV files used
6 * in Germany to exchange informations about european money transactions
7 * with banks or online banking programs.
8 *
9 * Disclaimer: this only implements a subset of DTAZV as used for a
2782e83 @mschuett release 1.3.0a2
mschuett authored
10 * "EU-Standardüberweisung" and is only tested against locally used
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
11 * accounting software.
12 * If you use this class commercially and/or for large transfer amounts
13 * then you might have to implement additional record types (V or W)
14 * and fill additional data fields for notification requirements.
15 *
d6d619c @mschuett fix broken URL
mschuett authored
16 * Implemented using the specification from 2007.
17 * Current specification from 2009 (german/english):
18 * http://www.bundesbank.de/download/meldewesen/aussenwirtschaft/vordrucke/pdf/dtazv_kunde_bank_neu.pdf
19 * http://www.bundesbank.de/download/meldewesen/aussenwirtschaft/vordrucke/pdf/dtazv_financial_inst_bbk.pdf
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
20 *
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
21 * PHP version 5
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
22 *
23 * This LICENSE is in the BSD license style.
24 *
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
25 * Copyright (c) 2008-2010 Martin Schütte
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
26 * derived from class DTA
27 * Copyright (c) 2003-2005 Hermann Stainer, Web-Gear
28 * http://www.web-gear.com/
29 * All rights reserved.
30 *
31 * Redistribution and use in source and binary forms, with or without
32 * modification, are permitted provided that the following conditions
33 * are met:
34 *
35 * Redistributions of source code must retain the above copyright
36 * notice, this list of conditions and the following disclaimer.
37 *
38 * Redistributions in binary form must reproduce the above copyright
39 * notice, this list of conditions and the following disclaimer in the
40 * documentation and/or other materials provided with the distribution.
41 *
42 * Neither the name of Hermann Stainer, Web-Gear nor the names of his
43 * contributors may be used to endorse or promote products derived from
44 * this software without specific prior written permission.
45 *
46 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
47 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
48 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
49 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
50 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
51 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
52 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
53 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
54 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
56 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
57 * POSSIBILITY OF SUCH DAMAGE.
58 *
59 * @category Payment
60 * @package Payment_DTA
2782e83 @mschuett release 1.3.0a2
mschuett authored
61 * @author Martin Schütte <info@mschuette.name>
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
62 * @author Hermann Stainer <hs@web-gear.com>
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
63 * @copyright 2008-2010 Martin Schütte
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
64 * @copyright 2003-2005 Hermann Stainer, Web-Gear
65 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
66 * @version SVN: $Id$
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
67 * @link http://pear.php.net/package/Payment_DTA
68 */
69
70 /**
2782e83 @mschuett release 1.3.0a2
mschuett authored
71 * needs base class
72 */
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
73 require_once 'DTABase.php';
74
75 /**
2782e83 @mschuett release 1.3.0a2
mschuett authored
76 * DTAZV class provides functions to create and handle with DTAZV
77 * files used in Germany to exchange informations about european
78 * money transactions with banks or online banking programs.
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
79 *
80 * @category Payment
81 * @package Payment_DTA
2782e83 @mschuett release 1.3.0a2
mschuett authored
82 * @author Martin Schütte <info@mschuette.name>
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
83 * @author Hermann Stainer <hs@web-gear.com>
2782e83 @mschuett release 1.3.0a2
mschuett authored
84 * @copyright 2008 Martin Schütte
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
85 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
607e3e7 @mschuett add filter() and correct @versions, reformat some lines
mschuett authored
86 * @version Release: @package_version@
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
87 * @link http://pear.php.net/package/Payment_DTA
88 */
89 class DTAZV extends DTABase
90 {
91 /**
f9221f6 @mschuett add DTAZV->setMaxAmount(), cf. request #16164
mschuett authored
92 * The maximum allowed amount per transfer (in cents).
93 *
94 * By default set to the maximum amount for a "EU-Standardüberweisung"
95 * that does not have to be reported.
96 *
97 * @see setMaxAmount()
98 * @var integer $max_amount
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
99 * @access protected
f9221f6 @mschuett add DTAZV->setMaxAmount(), cf. request #16164
mschuett authored
100 */
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
101 protected $max_amount;
f9221f6 @mschuett add DTAZV->setMaxAmount(), cf. request #16164
mschuett authored
102
103 /**
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
104 * Constructor.
105 *
185a78b @mschuett add parser for DTAZV
mschuett authored
106 * @param string $input Optional, a string with DTAZV data to import.
107 *
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
108 * @access public
109 */
185a78b @mschuett add parser for DTAZV
mschuett authored
110 function __construct($input = null)
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
111 {
452c8cf @mschuett first conversion to PHP5: abstraction and variable visibility
mschuett authored
112 parent::__construct();
f9221f6 @mschuett add DTAZV->setMaxAmount(), cf. request #16164
mschuett authored
113 $this->max_amount = 12500*100;
185a78b @mschuett add parser for DTAZV
mschuett authored
114
115 if (is_string($input)) {
116 try {
117 $this->parse($input);
118 } catch (Payment_DTA_FatalParseException $e) {
119 // cannot construct this object, reset everything
120 parent::__construct();
121 $this->max_amount = 12500*100;
122 $this->allerrors[] = $e;
123 } catch (Payment_DTA_Exception $e) {
124 // object is valid, but save the error
125 $this->allerrors[] = $e;
126 }
127 }
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
128 }
129
130 /**
131 * Set the sender of the DTAZV file. Must be set for valid DTAZV file.
132 * The given account data is also used as default sender's account.
133 * Account data contains
134 * name Sender's name. Maximally 35 chars are allowed.
135 * additional_name Sender's additional name (max. 35 chars)
136 * street Sender's street/PO Box (max. 35 chars)
137 * city Sender's city (max. 35 chars)
138 * bank_code Sender's bank code (BLZ, 8-digit)
139 * account_number Sender's account number (10-digit)
140 *
141 * @param array $account Account data fot file sender.
142 *
143 * @access public
144 * @return boolean
145 */
146 function setAccountFileSender($account)
147 {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
148 $account['account_number']
149 = strval($account['account_number']);
150 $account['bank_code']
151 = strval($account['bank_code']);
c866228 @mschuett - fix #16285: allow numeric values for account number fields
mschuett authored
152
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
153 if (strlen($account['name']) > 0
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
154 && strlen($account['bank_code']) > 0
155 && strlen($account['bank_code']) <= 8
156 && ctype_digit($account['bank_code'])
157 && strlen($account['account_number']) > 0
158 && strlen($account['account_number']) <= 10
159 && ctype_digit($account['account_number'])
160 ) {
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
161 if (empty($account['additional_name'])) {
162 $account['additional_name'] = "";
163 }
164 if (empty($account['street'])) {
165 $account['street'] = "";
166 }
167 if (empty($account['city'])) {
168 $account['city'] = "";
169 }
170
171 $this->account_file_sender = array(
607e3e7 @mschuett add filter() and correct @versions, reformat some lines
mschuett authored
172 "name" => $this->filter($account['name'], 35),
173 "additional_name" => $this->filter($account['additional_name'], 35),
174 "street" => $this->filter($account['street'], 35),
175 "city" => $this->filter($account['city'], 35),
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
176 "bank_code" => $account['bank_code'],
177 "account_number" => $account['account_number']
178 );
179
180 $result = true;
181 } else {
182 $result = false;
183 }
184
185 return $result;
186 }
187
188 /**
8d9d117 @mschuett split methods to reduce code complexity
mschuett authored
189 * Auxillary method to fill and normalize the account sender array.
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
190 *
8d9d117 @mschuett split methods to reduce code complexity
mschuett authored
191 * @param array $account_sender Sender's account data.
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
192 *
8d9d117 @mschuett split methods to reduce code complexity
mschuett authored
193 * @access private
194 * @return array
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
195 */
8d9d117 @mschuett split methods to reduce code complexity
mschuett authored
196 private function _exchangeFillSender($account_sender)
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
197 {
198 if (empty($account_sender['name'])) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
199 $account_sender['name']
200 = $this->account_file_sender['name'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
201 }
202 if (empty($account_sender['additional_name'])) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
203 $account_sender['additional_name']
204 = $this->account_file_sender['additional_name'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
205 }
206 if (empty($account_sender['street'])) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
207 $account_sender['street']
208 = $this->account_file_sender['street'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
209 }
210 if (empty($account_sender['city'])) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
211 $account_sender['city']
212 = $this->account_file_sender['city'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
213 }
214 if (empty($account_sender['bank_code'])) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
215 $account_sender['bank_code']
216 = $this->account_file_sender['bank_code'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
217 }
218 if (empty($account_sender['account_number'])) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
219 $account_sender['account_number']
220 = $this->account_file_sender['account_number'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
221 }
8d9d117 @mschuett split methods to reduce code complexity
mschuett authored
222 $account_sender['account_number']
223 = strval($account_sender['account_number']);
224 $account_sender['bank_code']
225 = strval($account_sender['bank_code']);
226
227 return $account_sender;
228 }
229
230 /**
231 * Auxillary method to fill and normalize the account receiver array.
232 *
233 * @param array $account_receiver Receiver's account data.
234 *
235 * @access private
236 * @return array
237 */
238 private function _exchangeFillReceiver($account_receiver)
239 {
240 if (empty($account_receiver['additional_name'])) {
241 $account_receiver['additional_name'] = "";
242 }
243 if (empty($account_receiver['street'])) {
244 $account_receiver['street'] = "";
245 }
246 if (empty($account_receiver['city'])) {
247 $account_receiver['city'] = "";
248 }
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
249
250 if (strlen($account_receiver['bank_code']) == 8) {
251 if (is_numeric($account_receiver['bank_code'])) {
252 // german BLZ -> allowed with special format
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
253 $account_receiver['bank_code']
254 = '///' . $account_receiver['bank_code'];
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
255 } else {
256 // short BIC -> fill to 11 chars
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
257 $account_receiver['bank_code']
258 = $account_receiver['bank_code'] . 'XXX';
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
259 }
260 }
261
8d9d117 @mschuett split methods to reduce code complexity
mschuett authored
262 return $account_receiver;
263 }
264
265 /**
266 * Adds an exchange.
267 *
268 * First the account data for the receiver of the exchange is set.
269 * In the case the DTA file contains credits, this is the payment receiver.
270 * In the other case (the DTA file contains debits), this is the account,
271 * from which money is taken away.
272 *
273 * If the sender is not specified, values of the file sender are used by default.
274 * Account data for sender contain
275 * name Sender's name. Maximally 35 chars are allowed.
276 * additional_name Sender's additional name (max. 35 chars)
277 * street Sender's street/PO Box (max. 35 chars)
278 * city Sender's city (max. 35 chars)
279 * bank_code Sender's bank code (8-digit BLZ)
280 * account_number Sender's account number (10-digit)
281 *
282 * Account data for receiver contain
283 * name Receiver's name. Maximally 35 chars are allowed.
284 * additional_name Receiver's additional name (max. 35 chars)
285 * street Receiver's street/PO Box (max. 35 chars)
286 * city Receiver's city (max. 35 chars)
287 * bank_code Receiver's bank code (8 or 11 char BIC)
288 * account_number Receiver's account number (up to 34 char IBAN)
289 *
290 * @param array $account_receiver Receiver's account data.
291 * @param double $amount Amount of money (Euro) in this exchange.
292 * @param array $purposes Array of up to 4 lines (max. 35 chars each)
293 * for description of the exchange.
294 * @param array $account_sender Sender's account data.
295 *
296 * @access public
297 * @return boolean
298 */
299 function addExchange($account_receiver, $amount, $purposes, $account_sender = array())
300 {
301 $account_receiver = $this->_exchangeFillReceiver($account_receiver);
302 $account_sender = $this->_exchangeFillSender($account_sender);
c866228 @mschuett - fix #16285: allow numeric values for account number fields
mschuett authored
303
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
304 /*
305 * notes for IBAN: currently only checked for length;
306 * we can use PEAR::Validate_Finance_IBAN once it
307 * gets a 'beta' or 'stable' status
308 * the minimum length of 12 is chosen arbitrarily as
309 * an additional plausibility check; currently the
310 * shortest real IBANs have 15 chars
311 */
1377f78 @mschuett release 1.3.0a3
mschuett authored
312 $cents = (int)(round($amount * 100));
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
313 if (strlen($account_receiver['name']) > 0
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
314 && strlen($account_receiver['bank_code']) == 11
315 && strlen($account_receiver['account_number']) > 12
316 && strlen($account_receiver['account_number']) <= 34
317 && strlen($account_sender['name']) > 0
318 && strlen($account_sender['bank_code']) > 0
319 && strlen($account_sender['bank_code']) <= 8
320 && ctype_digit($account_sender['bank_code'])
321 && strlen($account_sender['account_number']) > 0
322 && strlen($account_sender['account_number']) <= 10
323 && ctype_digit($account_sender['account_number'])
324 && is_numeric($amount) && $cents > 0
325 && $cents <= $this->max_amount
326 && $this->sum_amounts <= PHP_INT_MAX - $cents
327 && ((is_array($purposes) && count($purposes) >= 1 && count($purposes) <= 4)
328 || (is_string($purposes) && strlen($purposes) > 0))
329 ) {
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
330
1377f78 @mschuett release 1.3.0a3
mschuett authored
331 $this->sum_amounts += $cents;
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
332
333 if (is_string($purposes)) {
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
334 $filtered_purposes = str_split(
335 $this->makeValidString($purposes), 35
336 );
1377f78 @mschuett release 1.3.0a3
mschuett authored
337 $filtered_purposes = array_slice($filtered_purposes, 0, 14);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
338 } else {
1377f78 @mschuett release 1.3.0a3
mschuett authored
339 $filtered_purposes = array();
340 array_slice($purposes, 0, 4);
341 foreach ($purposes as $purposeline) {
607e3e7 @mschuett add filter() and correct @versions, reformat some lines
mschuett authored
342 $filtered_purposes[] = $this->filter($purposeline, 35);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
343 }
344 }
345 // ensure four lines
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
346 $filtered_purposes = array_slice(
347 array_pad($filtered_purposes, 4, ""), 0, 4
348 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
349
350 $this->exchanges[] = array(
607e3e7 @mschuett add filter() and correct @versions, reformat some lines
mschuett authored
351 "sender_name" => $this->filter($account_sender['name'], 35),
352 "sender_additional_name" => $this->filter($account_sender['additional_name'], 35),
353 "sender_street" => $this->filter($account_sender['street'], 35),
354 "sender_city" => $this->filter($account_sender['city'], 35),
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
355 "sender_bank_code" => $account_sender['bank_code'],
356 "sender_account_number" => $account_sender['account_number'],
607e3e7 @mschuett add filter() and correct @versions, reformat some lines
mschuett authored
357 "receiver_name" => $this->filter($account_receiver['name'], 35),
358 "receiver_additional_name" => $this->filter($account_receiver['additional_name'], 35),
359 "receiver_street" => $this->filter($account_receiver['street'], 35),
360 "receiver_city" => $this->filter($account_receiver['city'], 35),
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
361 "receiver_bank_code" => $account_receiver['bank_code'],
362 "receiver_account_number" => $account_receiver['account_number'],
1377f78 @mschuett release 1.3.0a3
mschuett authored
363 "amount" => $cents,
364 "purposes" => $filtered_purposes
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
365 );
366
367 $result = true;
368 } else {
369 $result = false;
370 }
371
372 return $result;
373 }
374
375 /**
376 * Returns the full content of the generated DTAZV file.
377 * All added exchanges are processed.
378 *
379 * @access public
380 * @return string
381 */
382 function getFileContent()
383 {
384 $content = "";
385
38cc7fe @mschuett fix DTAZV checksum calculation over amounts
mschuett authored
386 /* The checksum in DTAZV adds only the integer parts of all
387 * transfered amounts and is different from the sum of amounts.
388 */
389 $checksum_amounts = 0;
390 $sum_amounts = 0;
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
391
392 /**
393 * data record Q
394 */
395
396 // Q01 record length (256 Bytes)
397 $content .= "0256";
398 // Q02 record type
399 $content .= "Q";
400 // Q03 BLZ receiving this file (usually the sender's bank)
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
401 $content .= str_pad(
402 $this->account_file_sender['bank_code'], 8, "0", STR_PAD_LEFT
403 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
404 // Q04 customer number (usually the sender's account)
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
405 $content .= str_pad(
406 $this->account_file_sender['account_number'], 10, "0", STR_PAD_LEFT
407 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
408 // Q05 sender's address
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
409 $content .= str_pad(
410 $this->account_file_sender['name'], 35, " ", STR_PAD_RIGHT
411 );
412 $content .= str_pad(
413 $this->account_file_sender['additional_name'], 35, " ", STR_PAD_RIGHT
414 );
415 $content .= str_pad(
416 $this->account_file_sender['street'], 35, " ", STR_PAD_RIGHT
417 );
418 $content .= str_pad(
419 $this->account_file_sender['city'], 35, " ", STR_PAD_RIGHT
420 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
421 // Q06 date of file creation
926aae7 @mschuett wrong date field, bug #18452
mschuett authored
422 $content .= strftime("%y%m%d", $this->timestamp);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
423 // Q07 daily counter
424 // UNSURE if necessary
425 $content .= "00";
426 // Q08 execution date
926aae7 @mschuett wrong date field, bug #18452
mschuett authored
427 $content .= strftime("%y%m%d", $this->timestamp);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
428 // Q09 notification to federal bank
429 // according to specification (see above)
430 // transfers <= 12500 Euro do not have to be reported
431 $content .= "N";
432 // Q10 notification data
433 $content .= "00";
434 // Q11 notification BLZ
435 $content .= str_repeat("0", 8);
436 // Q12 reserve
437 $content .= str_repeat(" ", 68);
438
439 assert(strlen($content) == 256);
440
441 /**
442 * data record(s) T
443 */
444
445 foreach ($this->exchanges as $exchange) {
38cc7fe @mschuett fix DTAZV checksum calculation over amounts
mschuett authored
446 $sum_amounts += intval($exchange['amount']);
447 $checksum_amounts += intval($exchange['amount']/100);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
448
449 // T01 record length
450 $content .= "0768";
451 // T02 record type
452 $content .= "T";
453 // T03 sender's bank
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
454 $content .= str_pad(
455 $exchange['sender_bank_code'], 8, "0", STR_PAD_LEFT
456 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
457 // T04a currency (fixed)
458 $content .= "EUR";
459 // T04b sender's account
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
460 $content .= str_pad(
461 $exchange['sender_account_number'], 10, "0", STR_PAD_LEFT
462 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
463 // T05 execution date (optional, if != Q6)
464 $content .= str_repeat("0", 6);
2782e83 @mschuett release 1.3.0a2
mschuett authored
465 // T06 BLZ, empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
466 $content .= str_repeat("0", 8);
2782e83 @mschuett release 1.3.0a2
mschuett authored
467 // T07a currency, empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
468 $content .= str_repeat(" ", 3);
2782e83 @mschuett release 1.3.0a2
mschuett authored
469 // T07b account, empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
470 $content .= str_repeat("0", 10);
471 // T08 receiver's BIC
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
472 $content .= str_pad(
473 $exchange['receiver_bank_code'], 11, "X", STR_PAD_RIGHT
474 );
2782e83 @mschuett release 1.3.0a2
mschuett authored
475 // T09a country code, empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
476 $content .= str_repeat(" ", 3);
2782e83 @mschuett release 1.3.0a2
mschuett authored
477 // T09b receiver's bank address, empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
478 $content .= str_repeat(" ", 4*35);
479 // T10a receiver's country code --> use cc from IBAN
480 $content .= substr($exchange['receiver_account_number'], 0, 2) . ' ';
481 // T10b receiver's address
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
482 $content .= str_pad(
483 $exchange['receiver_name'], 35, " ", STR_PAD_RIGHT
484 );
485 $content .= str_pad(
486 $exchange['receiver_additional_name'], 35, " ", STR_PAD_RIGHT
487 );
488 $content .= str_pad(
489 $exchange['receiver_street'], 35, " ", STR_PAD_RIGHT
490 );
491 $content .= str_pad(
492 $exchange['receiver_city'], 35, " ", STR_PAD_RIGHT
493 );
2782e83 @mschuett release 1.3.0a2
mschuett authored
494 // T11 empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
495 $content .= str_repeat(" ", 2*35);
496 // T12 receiver's IBAN
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
497 $content .= '/' . str_pad(
498 $exchange['receiver_account_number'], 34, " ", STR_PAD_RIGHT
499 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
500 // T13 currency
501 $content .= "EUR";
502 // T14a amount (integer)
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
503 $content .= str_pad(
504 intval($exchange['amount']/100), 14, "0", STR_PAD_LEFT
505 );
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
506 // T14b amount (decimal places)
1377f78 @mschuett release 1.3.0a3
mschuett authored
507 $content .= str_pad(($exchange['amount']%100)*10, 3, "0", STR_PAD_LEFT);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
508 // T15 purpose
509 $content .= str_pad($exchange['purposes'][0], 35, " ", STR_PAD_RIGHT);
510 $content .= str_pad($exchange['purposes'][1], 35, " ", STR_PAD_RIGHT);
511 $content .= str_pad($exchange['purposes'][2], 35, " ", STR_PAD_RIGHT);
512 $content .= str_pad($exchange['purposes'][3], 35, " ", STR_PAD_RIGHT);
2782e83 @mschuett release 1.3.0a2
mschuett authored
513 // T16--T20 instruction code, empty for Standardüberweisung
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
514 $content .= str_repeat("0", 4*2);
515 $content .= str_repeat(" ", 25);
516 // T21 fees
517 $content .= "00";
518 // T22 payment type
519 $content .= "13";
520 // T23 free text for accounting
521 $content .= str_repeat(" ", 27);
522 // T24 contact details
523 $content .= str_repeat(" ", 35);
524 // T25 reporting key
525 $content .= "0";
526 // T26 reserve
527 $content .= str_repeat(" ", 51);
528 // T26 following report extension
529 $content .= "00";
530 }
531
532 assert((strlen($content) - 256) % 768 == 0);
533
534 /**
535 * data record Z
536 */
537
538 // Z01 record length
539 $content .= "0256";
540 // Z02 record type
541 $content .= "Z";
38cc7fe @mschuett fix DTAZV checksum calculation over amounts
mschuett authored
542 // Z03 sum of amounts (integer parts in T14a)
1377f78 @mschuett release 1.3.0a3
mschuett authored
543 assert($sum_amounts == $this->sum_amounts);
38cc7fe @mschuett fix DTAZV checksum calculation over amounts
mschuett authored
544 $content .= str_pad(intval($checksum_amounts), 15, "0", STR_PAD_LEFT);
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
545 // Z04 number of records type T
546 $content .= str_pad(count($this->exchanges), 15, "0", STR_PAD_LEFT);
547 // Z05 reserve
548 $content .= str_repeat(" ", 221);
549
550 assert(strlen($content) >= 512);
551 assert((strlen($content) - 512) % 768 == 0);
552
553 return $content;
554 }
f9221f6 @mschuett add DTAZV->setMaxAmount(), cf. request #16164
mschuett authored
555
556 /**
557 * Set the maximum allowed amount per transfer.
558 * Pass 0 to disable the check (will set to maximum integer value).
559 *
560 * <b>Warning</b>: Use at your own risk.
561 *
562 * Amounts > 12500 Euro usually have notification requirements.
563 *
564 * Amounts > 50000 Euro are not allowed in a "EU-Standardüberweisung",
565 * thus yielding a malformed DTAZV.
566 *
204d216 @mschuett change formatting to follow updated coding standards
mschuett authored
567 * @param integer $newmax New maximum allowed amount in Euro
568 * or 0 to disable check.
f9221f6 @mschuett add DTAZV->setMaxAmount(), cf. request #16164
mschuett authored
569 *
570 * @access public
571 * @since 1.3.2
572 * @link http://www.bundesbank.de/meldewesen/mw_aussenwirtschaft.en.php
573 * info on notification requirements
574 * @return void
575 */
576 function setMaxAmount($newmax)
577 {
578 if ((int)$newmax == 0 || $newmax > PHP_INT_MAX/100) {
579 $this->max_amount = PHP_INT_MAX;
580 } else {
581 $this->max_amount = (int)(round($newmax * 100));
582 }
583 }
185a78b @mschuett add parser for DTAZV
mschuett authored
584
585 /**
f87be8a @mschuett add "type" entry to getMetaData() array and parse DTA file creation d…
mschuett authored
586 * Returns an array with information about the transactions.
587 *
588 * @access public
589 * @return array Returns an array with keys: "sender_name",
590 * "sender_bank_code", "sender_account", "sum_amounts",
591 * "type", "sum_bankcodes", "sum_accounts", "count", "date"
592 */
593 function getMetaData()
594 {
595 $meta = parent::getMetaData();
596
597 $meta["type"] = "CREDIT";
598
599 return $meta;
600 }
601
602 /**
185a78b @mschuett add parser for DTAZV
mschuett authored
603 * Auxillary parser to consume Q records.
604 *
605 * @param string $input content of DTAZV file
606 * @param integer &$offset read offset into $input
607 *
608 * @throws Payment_DTA_Exception on unrecognized input
609 * @access private
610 * @return void
611 */
612 private function _parseQrecord($input, &$offset)
613 {
614 $Q = array();
615
616 /* field Q01+Q02 record length and type */
617 $this->checkStr($input, $offset, "0256Q");
618 /* field Q03 BLZ receiving this file */
619 $Q['bank_code'] = $this->getNum($input, $offset, 8);
620 /* field Q04 customer number */
621 $Q['account_number'] = $this->getNum($input, $offset, 10);
622 /* field Q05 sender's address */
623 $Q['name'] = rtrim($this->getStr($input, $offset, 35, true));
624 $Q['additional_name'] = rtrim($this->getStr($input, $offset, 35, true));
625 $Q['street'] = rtrim($this->getStr($input, $offset, 35, true));
626 $Q['city'] = rtrim($this->getStr($input, $offset, 35, true));
458122b @mschuett parse file creation date as timestamp
mschuett authored
627 /* field Q06 date of file creation -- use to set timestamp */
628 $Qdate_year = $this->getNum($input, $offset, 2);
926aae7 @mschuett wrong date field, bug #18452
mschuett authored
629 $Qdate_month = $this->getNum($input, $offset, 2);
630 $Qdate_day = $this->getNum($input, $offset, 2);
458122b @mschuett parse file creation date as timestamp
mschuett authored
631 $this->timestamp = mktime(
632 0, 0, 0,
633 intval($Qdate_month), intval($Qdate_day), intval($Qdate_year)
634 );
185a78b @mschuett add parser for DTAZV
mschuett authored
635 /* field Q07 daily counter -- ignored */
636 $this->getNum($input, $offset, 2);
637 /* field Q08 execution date -- ignored */
638 $this->getNum($input, $offset, 6);
639 /* field Q09 notification to federal bank */
640 $this->checkStr($input, $offset, "N");
641 /* field Q10 notification data */
642 $this->checkStr($input, $offset, "00");
643 /* field Q11 notification BLZ */
644 $this->checkStr($input, $offset, str_repeat("0", 8));
645 /* field Q12 reserve */
646 $this->checkStr($input, $offset, str_repeat(" ", 68));
647
648 $rc = $this->setAccountFileSender(
649 array(
650 "name" => $Q['name'],
651 "bank_code" => $Q['bank_code'],
652 "account_number" => $Q['account_number'],
653 "additional_name" => $Q['additional_name'],
654 )
655 );
656
657 $rc = $this->setAccountFileSender($Q);
658 if (!$rc) {
659 // should never happen
660 throw new Payment_DTA_FatalParseException(
ab85c9a @mschuett improve readability & CS
mschuett authored
661 "Cannot setAccountFileSender(), please file a bug report"
662 );
185a78b @mschuett add parser for DTAZV
mschuett authored
663 }
664 }
665
666 /**
667 * Auxillary parser to consume T records.
668 *
669 * @param string $input content of DTAZV file
670 * @param integer &$offset read offset into $input
7a082b6 @mschuett fix phpdoc and method signature
mschuett authored
671 * @param array &$checks holds checksums for validation in Z record
185a78b @mschuett add parser for DTAZV
mschuett authored
672 *
673 * @throws Payment_DTA_Exception on unrecognized input
674 * @access private
675 * @return void
676 */
677 private function _parseTrecord($input, &$offset, &$checks)
678 {
679 $Tsend = array();
680 $Trecv = array();
681
682 /* field T01+02 record length and type */
683 $this->checkStr($input, $offset, "0768T");
684 /* field T03 sender's bank */
685 $Tsend['bank_code'] = $this->getNum($input, $offset, 8);
686 /* field T04a currency */
687 $this->checkStr($input, $offset, "EUR");
688 /* field T04b sender's account */
689 $Tsend['account_number'] = $this->getNum($input, $offset, 10);
690 /* field T05 execution date -- ignored */
691 $this->getNum($input, $offset, 6);
692 /* field T06+T07a+T07b empty for Standardüberweisung */
693 $this->checkStr($input, $offset, str_repeat("0", 8));
694 $this->checkStr($input, $offset, str_repeat(" ", 3));
695 $this->checkStr($input, $offset, str_repeat("0", 10));
696 /* field T08 receiver's BIC */
697 $Trecv['bank_code'] = $this->getStr($input, $offset, 11);
698 /* field T09a+T09b empty for Standardüberweisung */
699 $this->checkStr($input, $offset, str_repeat(" ", 3+4*35));
700 /* field T10a receiver's country code -- ignored */
701 $this->getStr($input, $offset, 3);
702 /* field T10b receiver's address */
703 $Trecv['name'] = $this->getStr($input, $offset, 35);
704 $Trecv['additional_name'] = $this->getStr($input, $offset, 35);
705 $Trecv['street'] = $this->getStr($input, $offset, 35);
706 $Trecv['city'] = $this->getStr($input, $offset, 35);
707 /* field T11 empty for Standardüberweisung */
708 $this->checkStr($input, $offset, str_repeat(" ", 2*35));
709 /* field T12 receiver's IBAN */
710 $this->checkStr($input, $offset, '/');
711 $Trecv['account_number'] = $this->getStr($input, $offset, 34);
712 /* field T13 currency */
713 $this->checkStr($input, $offset, "EUR");
714 /* field T14a amount (integer) */
715 $amount_int = $this->getNum($input, $offset, 14);
716 /* field T14b amount (decimal places) */
717 $amount_dec = $this->getNum($input, $offset, 3);
718 $amount = $amount_int + $amount_dec/1000.0;
719 /* field T15 purpose */
720 $purposes = array();
721 $purposes[0] = $this->getStr($input, $offset, 35);
722 $purposes[1] = $this->getStr($input, $offset, 35);
723 $purposes[2] = $this->getStr($input, $offset, 35);
724 $purposes[3] = $this->getStr($input, $offset, 35);
725 /* field T16--T20 instruction code, empty for Standardüberweisung */
726 $this->checkStr($input, $offset, str_repeat("0", 4*2));
727 $this->checkStr($input, $offset, str_repeat(" ", 25));
728 /* field T21 fees */
729 $this->checkStr($input, $offset, "00");
730 /* field T22 payment type */
731 $this->checkStr($input, $offset, "13");
732 /* field T23 free text for accounting -- ignored */
733 $this->getStr($input, $offset, 27);
734 /* field T24 contact details -- ignored */
735 $this->getStr($input, $offset, 35);
736 /* field T25 reporting key */
737 $this->checkStr($input, $offset, "0");
738 /* field T26 reserve */
739 $this->checkStr($input, $offset, str_repeat(" ", 51));
740 /* field T26 following report extension */
741 $this->checkStr($input, $offset, "00");
742
743 /* we read the fields, now add an exchange */
744 $rc = $this->addExchange(
745 $Trecv,
746 $amount,
747 $purposes,
748 $Tsend
749 );
750 if (!$rc) {
751 // should never happen
ab85c9a @mschuett improve readability & CS
mschuett authored
752 throw new Payment_DTA_ParseException(
753 "Cannot addExchange() for transaction number ".
754 strval($this->count()+1).", please file a bug report"
755 );
185a78b @mschuett add parser for DTAZV
mschuett authored
756 }
757 $checks['amount'] += $amount_int;
758 }
759
760 /**
761 * Auxillary parser to consume Z records.
762 *
763 * @param string $input content of DTAZV file
764 * @param integer &$offset read offset into $input
7a082b6 @mschuett fix phpdoc and method signature
mschuett authored
765 * @param array $checks holds checksums for validation
185a78b @mschuett add parser for DTAZV
mschuett authored
766 *
767 * @throws Payment_DTA_Exception on unrecognized input
768 * @access private
769 * @return void
770 */
7a082b6 @mschuett fix phpdoc and method signature
mschuett authored
771 private function _parseZrecord($input, &$offset, $checks)
185a78b @mschuett add parser for DTAZV
mschuett authored
772 {
773 /* field Z01+Z02 record length and type */
774 $this->checkStr($input, $offset, "0256Z");
775 /* field Z03 sum of amounts (integer parts in T14a) */
776 $Z_check_amount = $this->getNum($input, $offset, 15);
777 /* field Z04 number of records type T */
778 $Z_check_count = $this->getNum($input, $offset, 15);
779 /* field Z05 reserve */
780 $this->checkStr($input, $offset, str_repeat(" ", 221));
781
782 if ($Z_check_count != $this->count()) {
783 throw new Payment_DTA_ChecksumException(
784 "Z record checksum mismatch for transaction count: ".
ab85c9a @mschuett improve readability & CS
mschuett authored
785 "reads $Z_check_count, expected ".$this->count()
786 );
185a78b @mschuett add parser for DTAZV
mschuett authored
787 }
788 if ($Z_check_amount != $checks['amount']) {
789 throw new Payment_DTA_ChecksumException(
790 "Z record checksum mismatch for transfer amount: ".
ab85c9a @mschuett improve readability & CS
mschuett authored
791 "reads $Z_check_amount, expected ".$checks['amount']
792 );
185a78b @mschuett add parser for DTAZV
mschuett authored
793 }
794 }
795
796 /**
797 * Parser. Read data from an existing DTAZV file content.
798 *
799 * @param string $input content of DTAZV file
800 *
801 * @throws Payment_DTA_Exception on unrecognized input
802 * @access protected
803 * @return void
804 */
805 protected function parse($input)
806 {
807 if (strlen($input) % 128) {
808 throw new Payment_DTA_FatalParseException("invalid length");
809 }
810
811 $checks = array('amount' => 0);
812 $offset = 0;
813
814 /* Q record */
815 try {
816 $this->_parseQrecord($input, $offset);
817 } catch (Payment_DTA_Exception $e) {
818 throw new Payment_DTA_FatalParseException("Exception in Q record", $e);
819 }
820
821 //do not consume input by using getStr()/getNum() here
822 while ($input[$offset + 4] == 'T') {
823 /* T record */
3def92a @mschuett fix t record skipping
mschuett authored
824 $t_start = $offset;
185a78b @mschuett add parser for DTAZV
mschuett authored
825 try {
826 $this->_parseTrecord($input, $offset, $checks);
827 } catch (Payment_DTA_Exception $e) {
828 // preserve error
829 $this->allerrors[] = new Payment_DTA_ParseException(
3def92a @mschuett fix t record skipping
mschuett authored
830 "Error in T record, in transaction number ".
ab85c9a @mschuett improve readability & CS
mschuett authored
831 strval($this->count()+1), $e
832 );
3def92a @mschuett fix t record skipping
mschuett authored
833 // skip to next record
834 $offset = $t_start + 768;
185a78b @mschuett add parser for DTAZV
mschuett authored
835 }
836 } // while
837
838 /* Z record */
839 try {
840 $this->_parseZrecord($input, $offset, $checks);
841 } catch (Payment_DTA_ChecksumException $e) {
842 throw $e;
843 } catch (Payment_DTA_Exception $e) {
844 throw new Payment_DTA_ParseException("Error in Z record", $e);
845 }
846 }
847
b7e8d21 @mschuett release 1.3.0a1
mschuett authored
848 }
Something went wrong with that request. Please try again.