Skip to content

CFONB 240 transfers

CFONB 240 files hold batches of transfers: one record of 240 characters per line, its first two characters being the record code.

  1. 31header
  2. 34transaction, repeated
  3. 39total
One transfer: its records, in the order of the file.
Record Parsed into Reached with
31 Header $transfer->getHeader()
34 Transaction $transfer->getTransactions()
39 Total $transfer->getTotal()

Cfonb240Reader::parse() takes the contents of the file and returns a list of Transfer:

use Silarhi\Cfonb\Banking\Transfer;
use Silarhi\Cfonb\Cfonb240Reader;
$reader = new Cfonb240Reader();
foreach ($reader->parse(file_get_contents('/path/to/cfonb240-file.txt')) as $transfer) {
assert($transfer instanceof Transfer);
}

With this file:

transfers.txt
3100000120181220E 300661077100020030401 300661077100020030401
3400000220211220E0066149890000122140469732OFFICE NATL DU AAAAAAAA 300661077100020030401ZZZZZZZZ 211220 000000171200
3400000320211220E0066149890000122140469732OFFICE NATL DU AAAAAAAA 300661077100020030401ZZZZZZZZ 211220 000000287280
3900000420211220E 300661077100020030401 300661077100020030401 000000465270
3100000120211220E 300661077100020030401 300661077100020030401
3400000220221220E0066300030302100020116383CHIC WWW WWWWWWW 300661077100020030401ZZZZ ZZZZ F2012017 221220 000000011760
3900000320221220E 300661077100020030401 300661077100020030401 000000063330
cfonb240.php
<?php
use Silarhi\Cfonb\Cfonb240Reader;
$reader = new Cfonb240Reader();
foreach ($reader->parse(file_get_contents('transfers.txt')) as $transfer) {
$header = $transfer->getHeader();
echo sprintf(
"Transfer %s, account %s %s %s\n",
$header->getPrevTransactionFileDate()->format('Y-m-d'),
$header->getRecipientBankCode1(),
$header->getRecipientCounterCode1(),
$header->getRecipientAccountNumber1()
);
foreach ($transfer->getTransactions() as $transaction) {
echo sprintf(
" #%d %s %-24s %8.2f %s\n",
$transaction->getSequenceNumber(),
$transaction->getSettlementDate()->format('Y-m-d'),
$transaction->getRecipientName1(),
$transaction->getTransactionAmount(),
$transaction->getRecipientName2()
);
}
$total = $transfer->getTotal();
echo sprintf(" Total #%d: %.2f\n", $total->getSequenceNumber(), $total->getTotalAmount());
}
Output
Transfer 2020-12-18, account 30066 10771 00020030401
#2 2020-12-21 OFFICE NATL DU AAAAAAAA 1712.00 ZZZZZZZZ
#3 2020-12-21 OFFICE NATL DU AAAAAAAA 2872.80 ZZZZZZZZ
Total #4: 4652.70
Transfer 2020-12-21, account 30066 10771 00020030401
#2 2020-12-22 CHIC WWW WWWWWWW 117.60 ZZZZ ZZZZ
Total #3: 633.30

The same, with more fields:

foreach ($reader->parse($content) as $transfer) {
// Transfer header
$header = $transfer->getHeader();
echo "Transfer Date: " . $header->getPrevTransactionFileDate()->format('Y-m-d') . "\n";
echo "Bank Code: " . $header->getRecipientBankCode1() . "\n";
echo "Account: " . $header->getRecipientAccountNumber1() . "\n";
// Transactions of the transfer
foreach ($transfer->getTransactions() as $transaction) {
echo "Transaction Date: " . $transaction->getSettlementDate()->format('Y-m-d') . "\n";
echo "Amount: " . $transaction->getTransactionAmount() . "\n";
echo "Recipient: " . $transaction->getRecipientName1() . "\n";
echo "Reference: " . $transaction->getPresenterReference() . "\n";
}
// Transfer total
$total = $transfer->getTotal();
echo "Total Amount: " . $total->getTotalAmount() . "\n";
echo "Sequence Number: " . $total->getSequenceNumber() . "\n";
}

All the fields, with their positions in the line: CFONB 240 objects.

  • a header line (31) starts a new transfer.
  • a transaction line (34) is added to the current transfer.
  • a total line (39) is set on the current transfer, which is then added to the list and closed: the next transaction or total line needs a new header.
  • an empty line is ignored; a line of spaces or tabs throws a ParseException.

So every transfer returned has a header and a total: getHeader() and getTotal() never throw on them. Any other order throws a ParseException: a transaction or a total line outside a transfer (before the first header, or between a total and the next header), a header line while a transfer is still open, a transfer still open at the end of the file:

Unable to attach the transaction with sequence number 2: missing "31" header record before it
Unable to attach the total with sequence number 3: missing "31" header record before it
Unable to start a transfer with the "31" header record of sequence number 1: the previous transfer has no "39" total record
Unable to close the transfer of the "31" header record of sequence number 1: missing "39" total record at the end of the file

Transaction and total amounts are 12 digits in cents, read as floats with 2 decimals: 000000171200 is 1712.00. Unlike CFONB 120 amounts, they have no sign: they are never negative. The reader does not check that the total is the sum of the transactions.

Dates are typed DateTimeInterface and are DateTimeImmutable objects at midnight, read from DDMMYY, like the CFONB 120 dates: a date that does not exist, such as 320420, throws a ParseException. getInitialTransactionSettlementDate() is null when blank or zero-filled (000000); the other dates are mandatory, and 000000 there throws a ParseException.

parse() accepts the same $strict argument as Cfonb120Reader::parse(), but no CFONB 240 rule depends on it: malformed lines and records out of order always throw a ParseException.

use Silarhi\Cfonb\Cfonb240Reader;
use Silarhi\Cfonb\Exceptions\ParseException;
$reader = new Cfonb240Reader();
try {
$content = file_get_contents('/path/to/cfonb240-file.txt');
foreach ($reader->parse($content) as $transfer) {
echo "Processing transfer from: " . $transfer->getHeader()->getRecipientBankCode1() . "\n";
// Process transactions...
}
} catch (ParseException $e) {
echo "Failed to parse CFONB file: " . $e->getMessage() . "\n";
}

HeaderUnavailableException and TotalUnavailableException are only thrown by a Transfer built by hand, without setHeader() or setTotal(): see exceptions.