├── .idea ├── .name ├── php.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── misc.xml └── ing-card.iml ├── .gitignore ├── composer.phar ├── convert.php ├── composer.json ├── LICENSE ├── README.md ├── composer.lock └── Commands └── ConvertCommand.php /.idea/.name: -------------------------------------------------------------------------------- 1 | ing-card -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | vendor 3 | *.940 4 | *.TXT 5 | -------------------------------------------------------------------------------- /composer.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roydejong/ing-card-to-mt940/HEAD/composer.phar -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /convert.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new ConvertCommand()); 11 | $application->run(); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "softwarepunt/ingmt940", 3 | "description": "Converts CSV data to MoneyBird-compatible MT940 format", 4 | "minimum-stability": "stable", 5 | "license": "proprietary", 6 | "authors": [ 7 | { 8 | "name": "Roy de Jong", 9 | "email": "roy@softwarepunt.nl" 10 | } 11 | ], 12 | "require": { 13 | "symfony/console": "^3.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { "SoftwarePunt\\IngCard\\": "" } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Roy de Jong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.idea/ing-card.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ingbanknetservice CSV -> ING MT940 2 | === 3 | 4 | What? 5 | --- 6 | 7 | **Convert a CSV file, exported from ING Banknet Service (ingbanknetservice.nl), to MT940 format.** 8 | 9 | It currently converts the following information into the MT940 file: 10 | 11 | - Transaction dates 12 | - Transaction booking dates 13 | - Transaction amounts 14 | - Credit card transaction references 15 | - Recipient (names only) 16 | 17 | Why? 18 | --- 19 | We needed MT940 format to import into our bookkeeping software (MoneyBird) to process bank mutations. ING does not currently support this for their business cards. 20 | 21 | How? 22 | --- 23 | If you have an ING Business Card, this utility can work for you. 24 | 25 | **To export the CSV:** 26 | 27 | 1. If you haven't done so already, create an account on ingbanknetservice.nl 28 | 2. Sign in and select your card. Go to the "Transactions" tab. 29 | 3. Select a period, pick the comma-seperated (*Door komma's gescheiden tekst*) file format, and click "Download" 30 | 31 | **To use this utility:** 32 | 33 | 1. Download or clone the repository contents 34 | 2. Run ``composer install`` from the terminal to install dependencies 35 | 3. Execute the command: `php convert.php convert [] []` 36 | 37 | **Parameters:** 38 | All parameters are optional. 39 | 40 | - ``filename``: A custom filename. Defaults to `Transacties.TXT`. Relative to the current working directory. 41 | - ``iban``: A custom IBAN that will be used to identify your credit card, if useful to you. Defaults to `NL24INGB0001111111`. 42 | 43 | Notes 44 | --- 45 | 46 | - You'll need [composer](https://getcomposer.org/download/) and [php-cli](https://www.google.nl/search?q=install+php+cli) installed to use this tool. 47 | - The exported MT940 is not perfect. Some fields are not correctly formatted (such as the counterparty data), and e.g. the final SUM is missing. But it works for our purposes. 48 | - Both a starting and final balance of €0 is reported. 49 | - [MT940 format specifications](https://www.ing.nl/media/ING_ming_mt940s_24_juli_tcm162-46356.pdf) 50 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "692b9b72b33a60811b763b13216c5922", 8 | "content-hash": "e9323d69b66f089db4152ae033aaa971", 9 | "packages": [ 10 | { 11 | "name": "symfony/console", 12 | "version": "v3.0.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/symfony/console.git", 16 | "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/symfony/console/zipball/ebcdc507829df915f4ca23067bd59ee4ef61f6c3", 21 | "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.5.9", 26 | "symfony/polyfill-mbstring": "~1.0" 27 | }, 28 | "require-dev": { 29 | "psr/log": "~1.0", 30 | "symfony/event-dispatcher": "~2.8|~3.0", 31 | "symfony/process": "~2.8|~3.0" 32 | }, 33 | "suggest": { 34 | "psr/log": "For using the console logger", 35 | "symfony/event-dispatcher": "", 36 | "symfony/process": "" 37 | }, 38 | "type": "library", 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "3.0-dev" 42 | } 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "Symfony\\Component\\Console\\": "" 47 | }, 48 | "exclude-from-classmap": [ 49 | "/Tests/" 50 | ] 51 | }, 52 | "notification-url": "https://packagist.org/downloads/", 53 | "license": [ 54 | "MIT" 55 | ], 56 | "authors": [ 57 | { 58 | "name": "Fabien Potencier", 59 | "email": "fabien@symfony.com" 60 | }, 61 | { 62 | "name": "Symfony Community", 63 | "homepage": "https://symfony.com/contributors" 64 | } 65 | ], 66 | "description": "Symfony Console Component", 67 | "homepage": "https://symfony.com", 68 | "time": "2015-12-22 10:39:06" 69 | }, 70 | { 71 | "name": "symfony/polyfill-mbstring", 72 | "version": "v1.0.1", 73 | "source": { 74 | "type": "git", 75 | "url": "https://github.com/symfony/polyfill-mbstring.git", 76 | "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" 77 | }, 78 | "dist": { 79 | "type": "zip", 80 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", 81 | "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", 82 | "shasum": "" 83 | }, 84 | "require": { 85 | "php": ">=5.3.3" 86 | }, 87 | "suggest": { 88 | "ext-mbstring": "For best performance" 89 | }, 90 | "type": "library", 91 | "extra": { 92 | "branch-alias": { 93 | "dev-master": "1.0-dev" 94 | } 95 | }, 96 | "autoload": { 97 | "psr-4": { 98 | "Symfony\\Polyfill\\Mbstring\\": "" 99 | }, 100 | "files": [ 101 | "bootstrap.php" 102 | ] 103 | }, 104 | "notification-url": "https://packagist.org/downloads/", 105 | "license": [ 106 | "MIT" 107 | ], 108 | "authors": [ 109 | { 110 | "name": "Nicolas Grekas", 111 | "email": "p@tchwork.com" 112 | }, 113 | { 114 | "name": "Symfony Community", 115 | "homepage": "https://symfony.com/contributors" 116 | } 117 | ], 118 | "description": "Symfony polyfill for the Mbstring extension", 119 | "homepage": "https://symfony.com", 120 | "keywords": [ 121 | "compatibility", 122 | "mbstring", 123 | "polyfill", 124 | "portable", 125 | "shim" 126 | ], 127 | "time": "2015-11-20 09:19:13" 128 | } 129 | ], 130 | "packages-dev": [], 131 | "aliases": [], 132 | "minimum-stability": "stable", 133 | "stability-flags": [], 134 | "prefer-stable": false, 135 | "prefer-lowest": false, 136 | "platform": [], 137 | "platform-dev": [] 138 | } 139 | -------------------------------------------------------------------------------- /Commands/ConvertCommand.php: -------------------------------------------------------------------------------- 1 | setName('convert') 16 | ->setDescription('Convert a ingbanknetservice CSV file to a MT940 file') 17 | ->addArgument( 18 | 'filename', 19 | InputArgument::OPTIONAL, 20 | 'The file to read and covert, relative to the current working directory' 21 | ) 22 | ->addArgument( 23 | 'iban', 24 | InputArgument::OPTIONAL, 25 | 'A custom account number to use (IBAN), defaults to NL24INGB0001111111 otherwise' 26 | ); 27 | } 28 | 29 | const C_DATE_BOOKED = 0; 30 | const C_DATE_OCCURED = 1; 31 | const C_SUPPLIER_NAME = 2; 32 | const C_SUPPLIER_PLACE = 3; 33 | const C_SUPPLIER_STATE = 4; 34 | const C_SUPPLIER_POSTCODE = 5; 35 | const C_MCC_SIC = 6; 36 | const C_MCC_DESCR = 7; 37 | const C_AMOUNT_ORIGINAL_EUROS = 8; 38 | const C_AMOUNT_ORIGINAL_CENTS = 9; 39 | const C_CURRENCY = 10; 40 | const C_EXCHANGE_RATE_NUMBERS = 11; 41 | const C_EXCHANGE_RATE_DECIMALS = 12; 42 | const C_AMOUNT_INVOICED_EUROS = 13; 43 | const C_AMOUNT_INVOICED_CENTS = 14; 44 | const C_MEMO = 15; 45 | const C_DEBIT_CREDIT = 16; 46 | const C_REFERENCE = 17; 47 | const C_DATE_OVERVIEW = 18; 48 | const C_NAME_ON_CARD = 19; 49 | const C_CARD_NUMBER = 20; 50 | 51 | protected function execute(InputInterface $input, OutputInterface $output) 52 | { 53 | // Determine path 54 | $filename = $input->getArgument('filename'); 55 | 56 | if (empty($filename)) 57 | { 58 | $filename = 'Transacties.TXT'; 59 | } 60 | 61 | // Read file 62 | $output->writeln('Parsing file: ' . $filename . '...'); 63 | 64 | if (!file_exists($filename)) 65 | { 66 | throw new \Exception('File not found: ' . $filename); 67 | } 68 | 69 | $parsed = array_map('str_getcsv', file($filename)); 70 | array_shift($parsed); // remove header 71 | 72 | if (!is_array($parsed) || count($parsed) <= 1) 73 | { 74 | throw new \Exception('Not a valid CSV file, or file does not contain any records: ' . $filename); 75 | } 76 | 77 | $output->writeln('Discovered ' . count($parsed). ' transactions in file.'); 78 | 79 | // Determine file period 80 | $dateFromTs = PHP_INT_MAX; 81 | $dateToTs = -PHP_INT_MAX; 82 | 83 | foreach ($parsed as $transaction) 84 | { 85 | $trxDate = $transaction[self::C_DATE_OCCURED]; 86 | $trxTs = strtotime($trxDate); 87 | 88 | if ($trxTs < $dateFromTs) 89 | { 90 | $dateFromTs = $trxTs; 91 | } 92 | 93 | if ($trxTs > $dateToTs) 94 | { 95 | $dateToTs = $trxTs; 96 | } 97 | } 98 | 99 | $output->writeln(sprintf('Transaction period reflected in file: %s - %s.', date('d-m-Y', $dateFromTs), date('d-m-Y', $dateToTs))); 100 | 101 | $output->writeln(''); 102 | $output->writeln('Starting conversion to MT940 file format (ING version)...'); 103 | 104 | // Begin: ING-specific headers 105 | $export = ''; 106 | $export .= '{1:F01INGBNL2ABXXX0000000000}' . PHP_EOL; 107 | $export .= '{2:I940INGBNL2AXXXN}' . PHP_EOL; 108 | $export .= '{4:' . PHP_EOL; 109 | 110 | // START MT940 MESSAGE 111 | $iban = $input->getArgument('iban'); 112 | 113 | if (empty($iban)) 114 | { 115 | $iban = 'NL24INGB0001111111'; 116 | } 117 | 118 | $export .= sprintf(':20:%s', uniqid('SPC')) . PHP_EOL; // Transaction reference, unique per file 119 | $export .= sprintf(':25:%s%s', $iban, 'EUR') . PHP_EOL; // Account number + currency code 120 | $export .= sprintf(':28C:%s', '00000') . PHP_EOL; // Document number, unused even by ING 121 | $export .= sprintf(':60F:%s%s%s%s', 'C', date('ymd', $dateFromTs), 'EUR', '0,00') . PHP_EOL; // Starting balance Credit-Date-Currency-Amount 122 | 123 | $debitTrxCount = 0; 124 | $debitAmountTotal = 0; 125 | 126 | foreach ($parsed as $trx) 127 | { 128 | $debitTrxCount++; 129 | 130 | $transactionTs = strtotime($trx[self::C_DATE_OCCURED]); 131 | $bookedTs = strtotime($trx[self::C_DATE_BOOKED]); 132 | $trxAmount = $trx[self::C_AMOUNT_INVOICED_EUROS] . ',' . $trx[self::C_AMOUNT_INVOICED_CENTS]; 133 | $recipient = $trx[self::C_SUPPLIER_NAME]; 134 | 135 | $debitAmountTotal += floatval($trxAmount); 136 | 137 | $output->writeln(sprintf(' -> Payment to %s on %s: €%s', $recipient, date('m-d-Y', $transactionTs), $trxAmount)); 138 | 139 | // 1. Transaction line 140 | $export .= ':61:'; // Transaction start tag 141 | $export .= date('ymd', $transactionTs); // Transaction date YYMMDD 142 | $export .= date('md', $bookedTs); // Booked date MMDD 143 | $export .= 'D'; // Credit/debit 144 | $export .= $trxAmount; // Transaction amount 145 | $export .= 'N' . 'TRF'; // Swift code (N) for TRANSFER (TRF) 146 | $export .= 'EREF'; // Payment reference (betalingskenmerk) 147 | $export .= '//' . substr($trx[self::C_REFERENCE], 0, 16); // ING TRX Ref 148 | $export .= PHP_EOL; 149 | $export .= '/TRCD/00100/'; // ING Transaction Code (00100=SEPA Credit Transfer) 150 | $export .= PHP_EOL; 151 | 152 | // 2. Transaction details 153 | $export .= ':86:'; 154 | $export .= sprintf('/EREF/%s/', $trx[self::C_REFERENCE]); // Payment ref 155 | $export .= sprintf('/CNTP/%s/', $trx[self::C_SUPPLIER_NAME]); // Counterparty 156 | $export .= PHP_EOL; 157 | } 158 | 159 | $export .= sprintf(':62F:%s%s%s%s', 'C', date('ymd', $dateToTs), 'EUR', '0,00') . PHP_EOL; // Final balance Credit-Date-Currency-Amount 160 | $export .= sprintf(':64:%s%s%s%s', 'C', date('ymd', $dateToTs), 'EUR', '0,00') . PHP_EOL; // Actual Final balance Credit-Date-Currency-Amount 161 | $export .= sprintf(':65:%s%s%s%s', 'C', date('ymd', $dateToTs), 'EUR', '0,00') . PHP_EOL; // Future balance Credit-Date-Currency-Amount 162 | 163 | // END: Message closure 164 | $export .= '-}'; 165 | 166 | $outFile = sprintf('export_ing_card_%s.940', date('ymd', $dateFromTs)); 167 | file_put_contents($outFile, $export); 168 | 169 | $actualFile = getcwd() . '/' . $outFile; 170 | 171 | $output->writeln(''); 172 | $output->writeln("Wrote export file: {$actualFile}"); 173 | } 174 | } --------------------------------------------------------------------------------