Current File : //opt/RZphp71/includes/Payment/DTA.php
<?php
/**
 * DTA
 *
 * DTA is a class that provides functions to create DTA files used in
 * Germany to exchange informations about money transactions with banks
 * or online banking programs.
 *
 * PHP versions 4 and 5
 *
 * This LICENSE is in the BSD license style.
 *
 * Copyright (c) 2003-2005 Hermann Stainer, Web-Gear
 * http://www.web-gear.com/
 * Copyright (c) 2008 Martin Schütte
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Hermann Stainer, Web-Gear nor the names of his
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  Payment
 * @package   Payment_DTA
 * @author    Hermann Stainer <hs@web-gear.com>
 * @author    Martin Schütte <info@mschuette.name>
 * @copyright 2003-2005 Hermann Stainer, Web-Gear
 * @copyright 2008 Martin Schütte
 * @license   http://www.debian.org/misc/bsd.license  BSD License (3 Clause)
 * @version   CVS: $Id: DTA.php,v 1.9 2009/06/04 21:20:54 mschuett Exp $
 * @link      http://pear.php.net/package/Payment_DTA
 */

/**
 * needs base class
 */
require_once 'DTABase.php';

/**
* Determines the type of the DTA file:
* DTA file contains credit payments.
*
* @const DTA_CREDIT
*/
define("DTA_CREDIT", 0);

/**
* Determines the type of the DTA file:
* DTA file contains debit payments (default).
*
* @const DTA_DEBIT
*/
define("DTA_DEBIT", 1);


/**
* Dta class provides functions to create and handle with DTA files
* used in Germany to exchange informations about money transactions with
* banks or online banking programs.
*
* Specifications:
* - http://www.ebics-zka.de/dokument/pdf/Anlage%203-Spezifikation%20der%20Datenformate%20-%20Version%202.3%20Endfassung%20vom%2005.11.2008.pdf,
*   part 1.1 DTAUS0, p. 4ff
* - http://www.bundesbank.de/download/zahlungsverkehr/zv_spezifikationen_v1_5.pdf
* - http://www.hbci-zka.de/dokumente/aenderungen/DTAUS_2002.pdf
*
* @category Payment
* @package  Payment_DTA
* @author   Hermann Stainer <hs@web-gear.com>
* @license  http://www.debian.org/misc/bsd.license  BSD License (3 Clause)
* @version  Release: @package_version@
* @link     http://pear.php.net/package/Payment_DTA
*/
class DTA extends DTABase
{
    /**
    * Type of DTA file, DTA_CREDIT or DTA_DEBIT.
    *
    * @var integer $type
    */
    var $type;

    /**
    * Sum of bank codes in exchanges; used for control fields.
    *
    * @var integer $sum_bankcodes
    * @access private
    */
    var $sum_bankcodes;

    /**
    * Sum of account numbers in exchanges; used for control fields.
    *
    * @var integer $sum_accounts
    * @access private
    */
    var $sum_accounts;

    /**
    * Constructor. The type of the DTA file must be set. One file can
    * only contain credits (DTA_CREDIT) OR debits (DTA_DEBIT).
    * This is a definement of the DTA format.
    *
    * @param integer $type Determines the type of the DTA file.
    *                       Either DTA_CREDIT or DTA_DEBIT. Must be set.
    *
    * @access public
    */
    function DTA($type)
    {
        $this->DTABase();
        $this->type = $type;

        $this->sum_bankcodes = 0;
        $this->sum_accounts  = 0;
    }

    /**
    * Set the sender of the DTA file. Must be set for valid DTA file.
    * The given account data is also used as default sender's account.
    * Account data contains
    *  name            Sender's name. Maximally 27 chars are allowed.
    *  bank_code       Sender's bank code.
    *  account_number  Sender's account number.
    *  additional_name If necessary, additional line for sender's name
    *                  (maximally 27 chars).
    *
    * @param array $account Account data for file sender.
    *
    * @access public
    * @return boolean
    */
    function setAccountFileSender($account)
    {
        $account['account_number'] =
            strval($account['account_number']);
        $account['bank_code']      =
            strval($account['bank_code']);

        if (strlen($account['name']) > 0
            && strlen($account['bank_code']) > 0
            && strlen($account['bank_code']) <= 8
            && ctype_digit($account['bank_code'])
            && strlen($account['account_number']) > 0
            && strlen($account['account_number']) <= 10
            && ctype_digit($account['account_number'])) {

            if (empty($account['additional_name'])) {
                $account['additional_name'] = "";
            }

            $this->account_file_sender = array(
                "name"            => substr($this->makeValidString($account['name']), 0, 27),
                "bank_code"       => $account['bank_code'],
                "account_number"  => $account['account_number'],
                "additional_name" => substr($this->makeValidString($account['additional_name']), 0, 27)
            );

            $result = true;
        } else {
            $result = false;
        }

        return $result;
    }

    /**
    * Adds an exchange. First the account data for the receiver of the exchange is
    * set. In the case the DTA file contains credits, this is the payment receiver.
    * In the other case (the DTA file contains debits), this is the account, from
    * which money is taken away. If the sender is not specified, values of the
    * file sender are used by default.
    *
    * Account data for receiver and sender contain
    *  name            Name. Maximally 27 chars are allowed.
    *  bank_code       Bank code.
    *  account_number  Account number.
    *  additional_name If necessary, additional line for name (maximally 27 chars).
    *
    * @param array  $account_receiver Receiver's account data.
    * @param double $amount           Amount of money in this exchange.
    *                                 Currency: EURO
    * @param array  $purposes         Array of up to 14 lines
    *                                 (maximally 27 chars each) for
    *                                 description of the exchange.
    *                                 A string is accepted as well.
    * @param array  $account_sender   Sender's account data.
    *
    * @access public
    * @return boolean
    */
    function addExchange($account_receiver, $amount, $purposes, $account_sender = array())
    {
        if (empty($account_receiver['additional_name'])) {
            $account_receiver['additional_name'] = "";
        }
        if (empty($account_sender['name'])) {
            $account_sender['name'] = $this->account_file_sender['name'];
        }
        if (empty($account_sender['bank_code'])) {
            $account_sender['bank_code'] = $this->account_file_sender['bank_code'];
        }
        if (empty($account_sender['account_number'])) {
            $account_sender['account_number'] =
                $this->account_file_sender['account_number'];
        }
        if (empty($account_sender['additional_name'])) {
            $account_sender['additional_name'] =
                $this->account_file_sender['additional_name'];
        }

        $account_receiver['account_number'] =
            strval($account_receiver['account_number']);
        $account_receiver['bank_code']      =
            strval($account_receiver['bank_code']);
        $account_sender['account_number']   =
            strval($account_sender['account_number']);
        $account_sender['bank_code']        =
            strval($account_sender['bank_code']);

        $cents = (int)(round($amount * 100));
        if (strlen($account_sender['name']) > 0
            && strlen($account_sender['bank_code']) > 0
            && strlen($account_sender['bank_code']) <= 8
            && ctype_digit($account_sender['bank_code'])
            && strlen($account_sender['account_number']) > 0
            && strlen($account_sender['account_number']) <= 10
            && ctype_digit($account_sender['account_number'])
            && strlen($account_receiver['name']) > 0
            && strlen($account_receiver['bank_code']) <= 8
            && ctype_digit($account_receiver['bank_code'])
            && strlen($account_receiver['account_number']) <= 10
            && ctype_digit($account_receiver['account_number'])
            && is_numeric($amount)
            && $cents > 0
            && $cents <= PHP_INT_MAX
            && $this->sum_amounts <= (PHP_INT_MAX - $cents)
            && ( (is_string($purposes)
                   && strlen($purposes) > 0)
                || (is_array($purposes)
                   && count($purposes) >= 1
                   && count($purposes) <= 14)
               )) {

            $this->sum_amounts   += $cents;
            $this->sum_bankcodes += $account_receiver['bank_code'];
            $this->sum_accounts  += $account_receiver['account_number'];

            if (is_string($purposes)) {
                $filtered_purposes = str_split($this->makeValidString($purposes), 27);
                $filtered_purposes = array_slice($filtered_purposes, 0, 14);
            } else {
                $filtered_purposes = array();
                foreach ($purposes as $purposeline) {
                    $filtered_purposes[] = substr($this->makeValidString($purposeline), 0, 27);
                }
            }

            $this->exchanges[] = array(
                "sender_name"              => substr($this->makeValidString($account_sender['name']), 0, 27),
                "sender_bank_code"         => $account_sender['bank_code'],
                "sender_account_number"    => $account_sender['account_number'],
                "sender_additional_name"   => substr($this->makeValidString($account_sender['additional_name']), 0, 27),
                "receiver_name"            => substr($this->makeValidString($account_receiver['name']), 0, 27),
                "receiver_bank_code"       => $account_receiver['bank_code'],
                "receiver_account_number"  => $account_receiver['account_number'],
                "receiver_additional_name" => substr($this->makeValidString($account_receiver['additional_name']), 0, 27),
                "amount"                   => $cents,
                "purposes"                 => $filtered_purposes
            );

            $result = true;
        } else {
            $result = false;
        }

        return $result;
    }

    /**
    * Returns the full content of the generated DTA file.
    * All added exchanges are processed.
    *
    * @access public
    * @return string
    */
    function getFileContent()
    {
        $content = "";

        $sum_account_numbers = 0;
        $sum_bank_codes      = 0;
        $sum_amounts         = 0;

        /**
         * data record A
         */

        // (field numbers according to ebics-zka.de specification)
        // A1 record length (128 Bytes)
        $content .= str_pad("128", 4, "0", STR_PAD_LEFT);
        // A2 record type
        $content .= "A";
        // A3 file mode (credit or debit)
        // and Customer File ("K") / Bank File ("B")
        $content .= ($this->type == DTA_CREDIT) ? "G" : "L";
        $content .= "K";
        // A4 sender's bank code
        $content .= str_pad($this->account_file_sender['bank_code'],
                        8, "0", STR_PAD_LEFT);
        // A5 only used if Bank File, otherwise NULL
        $content .= str_repeat("0", 8);
        // A6 sender's name
        $content .= str_pad($this->account_file_sender['name'],
                        27, " ", STR_PAD_RIGHT);
        // A7 date of file creation
        $content .= strftime("%d%m%y", $this->timestamp);
        // A8 free (bank internal)
        $content .= str_repeat(" ", 4);
        // A9 sender's account number
        $content .= str_pad($this->account_file_sender['account_number'],
                        10, "0", STR_PAD_LEFT);
        // A10 sender's reference number (optional)
        $content .= str_repeat("0", 10);
        // A11a free (reserve)
        $content .= str_repeat(" ", 15);
        // A11b execution date ("DDMMYYYY", optional)
        $content .= str_repeat(" ", 8);
        // A11c free (reserve)
        $content .= str_repeat(" ", 24);
        // A12 currency (1 = Euro)
        $content .= "1";

        assert(strlen($content) == 128);

        /**
         * data record(s) C
         */

        foreach ($this->exchanges as $exchange) {
            $sum_account_numbers += $exchange['receiver_account_number'];
            $sum_bank_codes      += (int) $exchange['receiver_bank_code'];
            $sum_amounts         += (int) $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);
            assert($additional_parts_number <= 15);

            // C1 record length (187 Bytes + 29 Bytes for each additional part)
            $content .= str_pad(187 + $additional_parts_number * 29,
                            4, "0", STR_PAD_LEFT);
            // C2 record type
            $content .= "C";
            // C3 first involved bank
            $content .= str_pad($exchange['sender_bank_code'],
                            8, "0", STR_PAD_LEFT);
            // C4 receiver's bank code
            $content .= str_pad($exchange['receiver_bank_code'],
                            8, "0", STR_PAD_LEFT);
            // C5 receiver's account number
            $content .= str_pad($exchange['receiver_account_number'],
                            10, "0", STR_PAD_LEFT);
            // C6 internal customer number (11 chars) or NULL
            $content .= "0" . str_repeat("0", 11) . "0";
            // C7a payment mode (text key)
            $content .= ($this->type == DTA_CREDIT) ? "51" : "05";
            // C7b additional text key
            $content .= "000";
            // C8 bank internal
            $content .= " ";
            // C9 free (reserve)
            $content .= str_repeat("0", 11);
            // C10 sender's bank code
            $content .= str_pad($exchange['sender_bank_code'],
                            8, "0", STR_PAD_LEFT);
            // C11 sender's account number
            $content .= str_pad($exchange['sender_account_number'],
                            10, "0", STR_PAD_LEFT);
            // C12 amount
            $content .= str_pad($exchange['amount'],
                            11, "0", STR_PAD_LEFT);
            // C13 free (reserve)
            $content .= str_repeat(" ", 3);
            // C14a receiver's name
            $content .= str_pad($exchange['receiver_name'],
                            27, " ", STR_PAD_RIGHT);
            // C14b delimitation
            $content .= str_repeat(" ", 8);
            /* first part/128 chars full */
            // C15 sender's name
            $content .= str_pad($exchange['sender_name'],
                            27, " ", STR_PAD_RIGHT);
            // C16 first line of purposes
            $content .= str_pad($first_purpose, 27, " ", STR_PAD_RIGHT);
            // C17a currency (1 = Euro)
            $content .= "1";
            // C17b free (reserve)
            $content .= str_repeat(" ", 2);
            // C18 number of additional parts (00-15)
            $content .= str_pad($additional_parts_number, 2, "0", STR_PAD_LEFT);

            /*
             * End of the constant part (187 chars),
             * now up to 15 extensions with 29 chars each might follow.
             */

            if (count($additional_parts) == 0) {
                // no extension, pad to fill the part to 2*128 chars
                $content .= str_repeat(" ", 256-187);
            } else {
                // The first two extensions fit into the current part:
                for ($index = 1;$index <= 2;$index++) {
                    if (count($additional_parts) > 0) {
                        $additional_part = array_shift($additional_parts);
                    } else {
                        $additional_part = array("type" => "  ",
                            "content" => ""
                            );
                    }
                    // C19/21 type of addional part
                    $content .= $additional_part['type'];
                    // C20/22 additional part content
                    $content .= str_pad($additional_part['content'],
                                    27, " ", STR_PAD_RIGHT);
                }
                // delimitation
                $content .= str_repeat(" ", 11);
            }

            // For more extensions add up to 4 more parts:
            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" => ""
                                );
                        }
                        // C24/26/28/30 type of addional part
                        $content .= $additional_part['type'];
                        // C25/27/29/31 additional part content
                        $content .= str_pad($additional_part['content'],
                                        27, " ", STR_PAD_RIGHT);
                    }
                    // C32 delimitation
                    $content .= str_repeat(" ", 12);
                }
            }
            // with 15 extensions there may be a 6th part
            if (count($additional_parts) > 0) {
                $additional_part = array_shift($additional_parts);
                // C24 type of addional part
                $content .= $additional_part['type'];
                // C25 additional part content
                $content .= str_pad($additional_part['content'],
                                27, " ", STR_PAD_RIGHT);
                // padding to fill the part
                $content .= str_repeat(" ", 128-27-2);
            }
            assert(count($additional_parts) == 0);
            assert(strlen($content) % 128 == 0);
        }

        /**
         * data record E
         */

        assert($this->sum_amounts   === $sum_amounts);
        assert($this->sum_bankcodes === $sum_bank_codes);
        assert($this->sum_accounts  === $sum_account_numbers);

        // E1 record length (128 bytes)
        $content .= str_pad("128", 4, "0", STR_PAD_LEFT);
        // E2 record type
        $content .= "E";
        // E3 free (reserve)
        $content .= str_repeat(" ", 5);
        // E4 number of records type C
        $content .= str_pad(count($this->exchanges), 7, "0", STR_PAD_LEFT);
        // E5 free (reserve)
        $content .= str_repeat("0", 13);
        // use number_format() to ensure proper integer formatting
        // E6 sum of account numbers
        $content .= str_pad(number_format($sum_account_numbers, 0, "", ""),
            17, "0", STR_PAD_LEFT);
        // E7 sum of bank codes
        $content .= str_pad(number_format($sum_bank_codes, 0, "", ""),
            17, "0", STR_PAD_LEFT);
        // E8 sum of amounts
        $content .= str_pad(number_format($sum_amounts, 0, "", ""),
            13, "0", STR_PAD_LEFT);
        // E9 delimitation
        $content .= str_repeat(" ", 51);

        assert(strlen($content) % 128 == 0);

        return $content;
    }

    /**
    * Returns an array with information about the transactions.
    * Can be used to print an accompanying document (Begleitzettel) for disks.
    *
    * @access public
    * @return array Returns an array with keys: "sender_name",
    *   "sender_bank_code", "sender_account", "sum_amounts",
    *   "sum_bankcodes", "sum_accounts", "count", "date"
    */
    function getMetaData()
    {
        $meta = parent::getMetaData();

        $meta["sum_bankcodes"] = floatval($this->sum_bankcodes);
        $meta["sum_accounts"]  = floatval($this->sum_accounts);

        return $meta;
    }
}