husail/cnab-sdk

SDK for reading and writing CNAB 240, 150 and 400 files, built on top of husail/edi-sdk.

Maintainers

Package info

github.com/husail/cnab-sdk

pkg:composer/husail/cnab-sdk

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-05-24 11:16 UTC

This package is auto-updated.

Last update: 2026-05-24 11:26:32 UTC


README

PHP SDK for reading and writing CNAB 240 files, built on top of husail/edi-sdk.

Provides tools to generate, parse and validate CNAB 240 files with support for payment grouping, automatic record sequencing, and bank-specific layouts.

PHP License Status

๐Ÿ“‹ Requirements

  • PHP 8.2+
  • husail/edi-sdk ^1.0
  • symfony/yaml

๐Ÿ“ฆ Installation

composer require husail/cnab-sdk

๐Ÿฆ Supported banks and formats

Bank CNAB 240 CNAB 150 CNAB 400
Itaรบ (341) โœ… Pagamentos โ€” โ€”
Bradesco (237) ๐Ÿ”œ โ€” โ€”

๐Ÿ“ Writing a payment file โ€” Itaรบ CNAB 240

High-level API

use Husail\CnabSdk\Types\CnabDate;
use Husail\CnabSdk\Banks\Itau\Cnab240Itau;
use Husail\CnabSdk\Builder\CnabBatchBuilder;

$file = Cnab240Itau::pagamentos()
    ->fileHeader([
        'company_type'     => '2',             // 2 = CNPJ
        'company_document' => '12345678000195',
        'bank_branch'      => '00341',
        'bank_account'     => '123456789012',
        'company_name'     => 'ACME LTDA',
        'generated_date'   => CnabDate::toDate(now()), // DDMMAAAA
        'generated_time'   => CnabDate::toTime(now()), // HHMMSS
        'sequence_number'  => '000000001',
    ])
    ->batch(function (CnabBatchBuilder $batch) use ($payments) {
        $batch->header([
            'operation_type'   => 'C',         // C = Credit
            'payment_type'     => '20',        // Credit to account
            'payment_form'     => '01',
            'company_type'     => '2',
            'company_document' => '12345678000195',
            'bank_branch'      => '00341',
            'bank_account'     => '123456789012',
            'company_name'     => 'ACME LTDA',
        ]);

        foreach ($payments as $payment) {
            $batch->add('segment_a', [
                'clearing_code'      => '009',
                'recipient_bank'     => $payment->bankCode,
                'bank_branch'        => $payment->bankBranch,
                'bank_account'       => $payment->bankAccountNumber,
                'recipient_name'     => $payment->recipientName,
                'payment_date'       => CnabDate::toDate($payment->paymentDate),
                'bank_ispb'          => $payment->bankIspb,
                'transfer_type'      => '01',  // TED
                'amount'             => $payment->amount, // float: 150.75
                'recipient_document' => $payment->recipientDocument,
            ]);
        }
    })
    ->toString();

batch_code, record_sequence, record_count (batch trailer) and batch_count, record_count (file trailer) are all filled automatically.

PIX by key โ€” Segment A + Segment B PIX

PIX by key requires a segment_b_pix after each segment_a. The segment_b_pix is distinguished from the standard segment_b by the pix_key_type field at positions 15-16.

->batch(function (CnabBatchBuilder $batch) use ($payments) {
    $batch->header(['operation_type' => 'C', 'payment_type' => '45', ...]);

    foreach ($payments as $payment) {
        $batch->add('segment_a', [
            'transfer_type'      => 'PIX',
            'amount'             => $payment->amount,
            'recipient_document' => $payment->recipientDocument,
            // other fields...
        ]);

        $batch->add('segment_b_pix', [
            'pix_key_type'       => '04',          // 04 = random key (EVP)
            'recipient_type'     => '2',           // 2 = CNPJ
            'recipient_document' => $payment->recipientDocument,
            'pix_key'            => $payment->pixKey,
            'txid'               => $payment->txid, // optional
        ]);
    }
})

PIX key types (pix_key_type):

Code Type
01 Phone
02 E-mail
03 CPF / CNPJ
04 Random key (EVP)

PIX by bank account โ€” Segment A + Segment B

PIX by bank account data uses the standard segment_b (address/email complement). The parser distinguishes them automatically: positions 15-16 are spaces for standard segment_b and a key type code for segment_b_pix.

$batch->add('segment_b', [
    'recipient_type'     => '2',
    'recipient_document' => $payment->recipientDocument,
    'email'              => $payment->email, // optional
]);

Multiple batches

Each ->batch() call creates a new batch with an incremented batch code:

Cnab240Itau::pagamentos()
    ->fileHeader([...])

    ->batch(function (CnabBatchBuilder $batch) use ($tedPayments) {
        $batch->header(['operation_type' => 'C', 'payment_type' => '20', 'payment_form' => '01', ...]);
        foreach ($tedPayments as $p) {
            $batch->add('segment_a', [...]);
        }
    })

    ->batch(function (CnabBatchBuilder $batch) use ($pixPayments) {
        $batch->header(['operation_type' => 'C', 'payment_type' => '45', 'payment_form' => '01', ...]);
        foreach ($pixPayments as $p) {
            $batch->add('segment_a', [...]);
            $batch->add('segment_b', [...]);
        }
    })

    ->toString();

According to SISPAG rules, PIX payments must be in a separate file from other payment types.

Overriding auto-calculated fields

All automatically calculated fields can be overridden:

// Override batch trailer fields
$batch->trailer([
    'total_amount' => 47075,
]);

// Override file trailer fields
->fileTrailer([
    'batch_count'  => 2,
    'record_count' => 10,
])

๐Ÿ“‚ Reading a return file

The same layout covers both remessa (outgoing) and retorno (return). The file_header.file_code field distinguishes them: 1 = remessa, 2 = retorno.

use Husail\CnabSdk\Banks\Itau\Cnab240Itau;
use Husail\CnabSdk\Cnab;

$content = file_get_contents('/path/to/retorno.txt');
$layout  = Cnab240Itau::layout();

// Always validate before parsing
$validation = Cnab::validate($content, $layout);
if ($validation->fails()) {
    foreach ($validation->errors() as $error) {
        echo "Line {$error->line} [{$error->record}] {$error->field}: {$error->message}";
    }
}

$result = Cnab::parse($content, $layout);

// File header
$header = $result->first('file_header');
echo $header?->get('company_name');
echo $header?->get('file_code');    // '1' = remessa, '2' = retorno
echo $header?->get('generated_date');

// Segment A collection โ€” TED, DOC, PIX by bank account
$segmentsA = $result->records('segment_a');
$segmentsA->count();
$segmentsA->each(function ($seg) {
    echo $seg->get('recipient_name');
    echo $seg->get('amount');          // float
    echo $seg->get('occurrence_code'); // '00' = paid
    echo $seg->get('effective_date');  // filled by bank on return
});

// Filter paid segments
$paid = $segmentsA->filter(fn ($seg) => $seg->get('occurrence_code') === '00');

// Segment B PIX โ€” PIX by key complement
$result->records('segment_b_pix')->each(function ($seg) {
    echo $seg->get('pix_key_type');
    echo $seg->get('pix_key');
    echo $seg->get('occurrence_code');
});

// Segment J โ€” boletos and concessionรกrias
$result->records('segment_j')->each(function ($seg) {
    echo $seg->get('recipient_name');
    echo $seg->get('payment_amount'); // float
    echo $seg->get('occurrence_code');
});

// Segment J52 โ€” payer/beneficiary data and QR Code
$result->records('segment_j52')->each(function ($seg) {
    echo $seg->get('payer_document');
    echo $seg->get('beneficiary_name');
    echo $seg->get('pix_url');
});

Return occurrence codes (occurrence_code at positions 231-240):

Code Meaning
00 Paid / Processed
BD Invalid bank
AB Scheduling error
Others See Itaรบ SISPAG manual (v086) for the full list

๐Ÿ—‚๏ธ Grouped payments

groupPayments() reconstructs each payment with all its segments together, grouped by batch_code + record_sequence.

use Husail\CnabSdk\Banks\Itau\Cnab240Itau;
use Husail\CnabSdk\Cnab;

$result   = Cnab::parse($content, Cnab240Itau::layout());
$payments = Cnab240Itau::groupPayments($result);

$payments->count();        // total payments
$payments->pixCount();     // PIX payments only
$payments->boletoCount();  // boleto payments only

Generic access

foreach ($payments->payments() as $group) {
    $group->batchCode(); // '0001'
    $group->sequence();  // '00001'

    $group->get('segment_a')?->get('amount');
    $group->get('segment_j')?->get('barcode');
    $group->has('segment_b_pix'); // bool
}

Typed PIX payments

foreach ($payments->pixPayments() as $pix) {
    $pix->batchCode();
    $pix->sequence();
    $pix->isPixByKey(); // true when segment_b_pix is present

    // segment_a โ€” always present
    $pix->segmentA()->get('amount');         // float
    $pix->segmentA()->get('recipient_name');
    $pix->segmentA()->get('payment_date');

    // segment_b โ€” PIX by bank account complement (optional)
    $pix->segmentB()?->get('email');

    // segment_b_pix โ€” PIX by key complement (optional)
    $pix->segmentBPix()?->get('pix_key');
    $pix->segmentBPix()?->get('pix_key_type'); // '04' = random key
}

Typed boleto payments

foreach ($payments->boletoPayments() as $boleto) {
    $boleto->batchCode();
    $boleto->sequence();
    $boleto->hasPixQrCode(); // true when segment_j52 has a pix_url

    // segment_j โ€” always present
    $boleto->segmentJ()->get('barcode');
    $boleto->segmentJ()->get('payment_amount'); // float
    $boleto->segmentJ()->get('due_date');

    // segment_j52 โ€” payer/beneficiary and QR Code (optional)
    $boleto->segmentJ52()?->get('payer_name');
    $boleto->segmentJ52()?->get('beneficiary_name');
    $boleto->segmentJ52()?->get('pix_url');
}

Filter grouped payments

// Payments above R$ 1,000
$highValue = $payments->filter(
    fn ($group) => ($group->get('segment_a')?->get('amount') ?? 0) > 1000
);

// All PIX by key
$pixByKey = $payments->filter(
    fn ($group) => $group->has('segment_b_pix')
);

โœ… Validating a file

use Husail\CnabSdk\Banks\Itau\Cnab240Itau;
use Husail\CnabSdk\Cnab;

$result = Cnab::validate($content, Cnab240Itau::layout());

if ($result->passes()) {
    // safe to process
}

foreach ($result->errors() as $error) {
    echo "Line {$error->line} [{$error->record}] {$error->field}: {$error->message}";
}

๐Ÿ› ๏ธ Generic API โ€” bring your own layout

Use Cnab::write() when you have a custom YAML or JSON layout:

use Husail\CnabSdk\Cnab;

$layout = Cnab::fromYaml('/path/to/my-bank/cnab240-pagamentos.yaml');

$file = Cnab::write($layout)
    ->fileHeader([...])
    ->batch(function ($batch) use ($payments) {
        $batch->header([...]);
        foreach ($payments as $p) {
            $batch->add('segment_a', [...]);
        }
    })
    ->toString();

๐Ÿ—“๏ธ CNAB helpers

use Husail\CnabSdk\Types\CnabDate;
use Husail\CnabSdk\Types\Monetary;

// Dates
CnabDate::toDate(now());           // '08052026'
CnabDate::toTime(now());           // '143052'
CnabDate::fromDate('08052026');    // DateTimeImmutable
CnabDate::zero();                  // '00000000'
CnabDate::isZero('00000000');      // true

// Monetary values
Monetary::serialize(150.75, decimalPlaces: 2, length: 15); // '000000000015075'
Monetary::deserialize('000000000015075', decimalPlaces: 2); // 150.75
Monetary::format(150.75);                                    // 'R$ 150,75'

๐Ÿ—๏ธ Adding a new bank

Implement BankLayoutInterface and provide a YAML layout file:

use Husail\CnabSdk\Contracts\BankLayoutInterface;
use Husail\CnabSdk\Builder\CnabFileBuilder;
use Husail\EdiSdk\Drivers\YamlDriver;
use Husail\EdiSdk\Schema\FileLayout;

class Cnab240MyBank implements BankLayoutInterface
{
    public const BANK_CODE = '999';
    public const BANK_NAME = 'MY BANK SA';

    private const LAYOUT = __DIR__ . '/../../Layouts/MyBank/cnab240/pagamentos/layout.yaml';

    public static function layout(): FileLayout
    {
        return (new YamlDriver())->load(self::LAYOUT);
    }

    public static function pagamentos(): CnabFileBuilder
    {
        return new CnabFileBuilder(self::layout());
    }
}

The layout YAML follows the same structure as the Itaรบ layout. See src/Layouts/Itau/cnab240/pagamentos/layout.yaml as reference. A single layout file covers both remessa and retorno.

๐Ÿ”— How it relates to edi-sdk

cnab-sdk
โ”œโ”€โ”€ Provides: CNAB layouts (YAML), auto-counting builder, bank classes, payment grouping
โ””โ”€โ”€ Delegates to: husail/edi-sdk for write, parse and validate operations

edi-sdk
โ””โ”€โ”€ Provides: fixed-width file engine, layout drivers, sequence tree

Cnab::parse() and Cnab::validate() are thin wrappers over Edi::parse() and Edi::validate(). For full control, use the edi-sdk directly with Cnab240Itau::layout().

๐Ÿงช Testing

composer install
composer test

๐Ÿค Contributing

Contributions, issues and pull requests are welcome.
If you find a bug or have a suggestion, feel free to open an issue.

๐Ÿ“œ License

Licensed under the MIT License.