nowo-tech / sepa-payment-bundle
Symfony bundle for SEPA payment management: IBAN validation, mandates, credit transfer and direct debit generation
Installs: 38
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Type:symfony-bundle
pkg:composer/nowo-tech/sepa-payment-bundle
Requires
- php: >=8.1 <8.6
- digitick/sepa-xml: ^3.0
- symfony/config: ^6.0 || ^7.0 || ^8.0
- symfony/console: ^6.0 || ^7.0 || ^8.0
- symfony/dependency-injection: ^6.0 || ^7.0 || ^8.0
- symfony/http-kernel: ^6.0 || ^7.0 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpunit/phpunit: ^10.0
- symfony/yaml: ^6.0 || ^7.0 || ^8.0
README
Symfony bundle for SEPA payment management: IBAN validation, mandate management, SEPA Credit Transfer and SEPA Direct Debit generation.
Features
- ✅ IBAN Validation: Complete IBAN validation according to ISO 13616 standard
- ✅ IBAN Utilities: Format, normalize, extract country code, check digits, and BBAN
- ✅ CCC to IBAN Conversion: Convert Spanish CCC (Código Cuenta Cliente) to IBAN format
- ✅ BIC Validation: Validate BIC (Business Identifier Code) format
- ✅ Credit Card Validation: Validate credit card numbers using Luhn algorithm and detect card types (Visa, Mastercard, Amex, Discover, etc.)
- ✅ Identifier Generation: Generate unique identifiers for messages, payments, and transactions
- ✅ SEPA XML Parser: Parse and validate SEPA XML files
- ✅ SEPA Mandates: Manage SEPA Direct Debit mandates with full support
- ✅ Credit Transfer (Remesas de Pago): Generate SEPA Credit Transfer XML files (pain.001.001.03 format) using Digitick\Sepa library
- ✅ Direct Debit (Remesas de Cobro): Generate SEPA Direct Debit XML files (pain.008.001.02 format) using Digitick\Sepa library
- ✅ Array-based API: Generate both types of remesas from simple array format
- ✅ Object-based API: Generate remesas using typed objects for better type safety
- ✅ Multiple Transactions: Support for batch payments in a single remesa
- ✅ Full Validation: Automatic validation of IBANs before XML generation
- ✅ Type Safety: Full type hints and strict types throughout
- ✅ Console Commands: CLI tools for IBAN validation, CCC conversion, and credit card validation
Installation
composer require nowo-tech/sepa-payment-bundle
Then, register the bundle in your config/bundles.php:
<?php return [ // ... Nowo\SepaPaymentBundle\NowoSepaPaymentBundle::class => ['all' => true], ];
Usage
IBAN Validation
use Nowo\SepaPaymentBundle\Validator\IbanValidator; $validator = new IbanValidator(); // Validate IBAN if ($validator->isValid('ES9121000418450200051332')) { echo "Valid IBAN"; } // Normalize IBAN (remove spaces, uppercase) $normalized = $validator->normalize('es91 2100 0418 4502 0005 1332'); // Returns: ES9121000418450200051332 // Format IBAN (add spaces every 4 characters) $formatted = $validator->format('ES9121000418450200051332'); // Returns: ES91 2100 0418 4502 0005 1332 // Extract components $countryCode = $validator->getCountryCode('ES9121000418450200051332'); // ES $checkDigits = $validator->getCheckDigits('ES9121000418450200051332'); // 91 $bban = $validator->getBban('ES9121000418450200051332'); // 21000418450200051332 // Calculate check digits $checkDigits = $validator->calculateCheckDigits('ES0021000418450200051332'); // Returns: 91
CCC to IBAN Conversion
use Nowo\SepaPaymentBundle\Converter\CccConverter; use Nowo\SepaPaymentBundle\Validator\IbanValidator; $converter = new CccConverter(new IbanValidator()); // Convert CCC to IBAN $iban = $converter->cccToIban('21000418450200051332'); // Returns: ES9121000418450200051332 // Validate CCC format if ($converter->isValidCcc('21000418450200051332')) { echo "Valid CCC"; } // Extract components $bankCode = $converter->getBankCode('21000418450200051332'); // 2100 $branchCode = $converter->getBranchCode('21000418450200051332'); // 0418 $accountNumber = $converter->getAccountNumber('21000418450200051332'); // 450200051332
BIC Validation
use Nowo\SepaPaymentBundle\Validator\BicValidator; $validator = new BicValidator(); // Validate BIC if ($validator->isValid('CAIXESBBXXX')) { echo "Valid BIC"; } // Normalize BIC (remove spaces, uppercase) $normalized = $validator->normalize('caixesbb xxx'); // Returns: CAIXESBBXXX // Extract components $bankCode = $validator->getBankCode('CAIXESBBXXX'); // CAIX $countryCode = $validator->getCountryCode('CAIXESBBXXX'); // ES $locationCode = $validator->getLocationCode('CAIXESBBXXX'); // BB $branchCode = $validator->getBranchCode('CAIXESBBXXX'); // XXX (or null if not present)
Identifier Generation
use Nowo\SepaPaymentBundle\Generator\IdentifierGenerator; $generator = new IdentifierGenerator(); // Generate message identifier $messageId = $generator->generateMessageId(); // Returns: MSG-20240115143022-a1b2c3d4 // Generate payment information identifier $paymentInfoId = $generator->generatePaymentInfoId(); // Returns: PMT-20240115143022-a1b2c3d4 // Generate end-to-end identifier $endToEndId = $generator->generateEndToEndId(); // Returns: E2E-20240115143022-a1b2c3d4 // Generate mandate identifier $mandateId = $generator->generateMandateId(); // Returns: MANDATE-20240115143022-a1b2c3d4 // Generate custom identifier with prefix $customId = $generator->generateCustomId('CUSTOM'); // Returns: CUSTOM-20240115143022-a1b2c3d4 // Generate with custom prefix $messageId = $generator->generateMessageId('MY-MSG'); // Returns: MY-MSG-20240115143022-a1b2c3d4
SEPA XML Parser
use Nowo\SepaPaymentBundle\Parser\RemesaParser; $parser = new RemesaParser(); // Parse SEPA Credit Transfer XML $xml = file_get_contents('remesa.xml'); $data = $parser->parseCreditTransfer($xml); // Access parsed data $messageId = $data['messageId']; $creationDate = $data['creationDate']; $initiatingPartyName = $data['initiatingPartyName']; $paymentInfoId = $data['paymentInfoId']; $numberOfTransactions = $data['numberOfTransactions']; $controlSum = $data['controlSum']; $transactions = $data['transactions']; // Validate XML format if ($parser->isValidCreditTransfer($xml)) { echo "Valid SEPA Credit Transfer XML"; }
Credit Card Validation
use Nowo\SepaPaymentBundle\Validator\CreditCardValidator; $validator = new CreditCardValidator(); // Validate credit card number (using Luhn algorithm) if ($validator->isValid('4532015112830366')) { echo "Valid credit card"; } // Normalize card number (remove spaces and dashes) $normalized = $validator->normalize('4532 0151 1283 0366'); // Returns: 4532015112830366 // Format card number (add spaces every 4 digits) $formatted = $validator->format('4532015112830366'); // Returns: 4532 0151 1283 0366 // Detect card type $cardType = $validator->getCardType('4532015112830366'); // Returns: 'visa' (or 'mastercard', 'amex', 'discover', 'diners_club', 'jcb', 'unknown') // Get BIN (Bank Identification Number - first 6 digits) $bin = $validator->getBin('4532015112830366'); // Returns: 453201 // Get last 4 digits $lastFour = $validator->getLastFour('4532015112830366'); // Returns: 0366 // Mask card number (show only last 4 digits) $masked = $validator->mask('4532015112830366'); // Returns: ************0366 // Validate for specific card type if ($validator->isValidForType('4532015112830366', CreditCardValidator::TYPE_VISA)) { echo "Valid Visa card"; }
SEPA Mandates
use Nowo\SepaPaymentBundle\Model\Mandate\Mandate; $mandate = new Mandate( 'MANDATE-001', // Mandate identifier new \DateTime('2024-01-15'), // Signature date 'ES9121000418450200051332', // Debtor IBAN 'John Doe', // Debtor name 'CORE', // Mandate type (CORE, B2B) 'FRST' // Sequence type (FRST, RCUR, OOFF, FNAL) ); $mandate->setDebtorBic('CAIXESBBXXX'); $mandate->setSequenceType('RCUR'); // For recurring payments $mandate->setActive(true);
Generating SEPA Credit Transfer (Remesa de Pago)
Credit transfers (remesas de pago) are used to send money from the debtor (payer) to the creditor (beneficiary).
use Nowo\SepaPaymentBundle\Validator\IbanValidator; use Nowo\SepaPaymentBundle\Model\Remesa\RemesaData; use Nowo\SepaPaymentBundle\Generator\RemesaGenerator; use Nowo\SepaPaymentBundle\Model\Remesa\Transaction; // Create remesa data $remesaData = new RemesaData( 'MSG-001', // Message ID (unique) new \DateTime('2024-01-15 10:00:00'), // Creation date 'My Company', // Initiating party name 'PMT-001', // Payment info ID 'ES9121000418450200051332', // Creditor IBAN 'My Company Name', // Creditor name new \DateTime('2024-01-20') // Requested execution date ); $remesaData->setCreditorBic('CAIXESBBXXX'); $remesaData->setBatchBooking(true); // Add transactions $transaction1 = new Transaction( 'E2E-001', // End-to-end ID (unique per transaction) 100.50, // Amount 'EUR', // Currency (ISO 4217) 'GB82WEST12345698765432', // Debtor IBAN 'John Doe' // Debtor name ); $transaction1->setDebtorBic('WESTGB22'); $transaction1->setRemittanceInformation('Invoice 12345'); $remesaData->addTransaction($transaction1); // Add more transactions if needed $transaction2 = new Transaction( 'E2E-002', 200.75, 'EUR', 'FR1420041010050500013M02606', 'Jane Smith' ); $remesaData->addTransaction($transaction2); // Generate XML $ibanValidator = new IbanValidator(); $generator = new RemesaGenerator($ibanValidator); $xml = $generator->generate($remesaData); // Save to file file_put_contents('remesa.xml', $xml);
Generating SEPA Direct Debit (Remesa de Cobro)
Direct debits (remesas de cobro) are used to collect money from the debtor by the creditor based on a SEPA mandate.
Using Array Format (Recommended)
use Nowo\SepaPaymentBundle\Generator\DirectDebitGenerator; use Nowo\SepaPaymentBundle\Validator\IbanValidator; $generator = new DirectDebitGenerator(new IbanValidator()); $data = [ 'reference' => 'MSG-001', // Message ID (unique) 'bankAccountOwner' => 'My Company', // Initiating party name 'paymentInfoId' => 'PMTINF-1', // Payment info ID 'dueDate' => new \DateTime('2024-01-20'), // Due date 'creditorName' => 'My Company Name', // Creditor name 'creditorIban' => 'ES9121000418450200051332', // Creditor IBAN 'creditorBic' => 'CAIXESBBXXX', // Creditor BIC (optional) 'seqType' => 'RCUR', // Sequence type: FRST, RCUR, OOFF, FNAL 'creditorId' => 'ES98ZZZ09999999999', // SEPA identifier 'localInstrumentCode' => 'CORE', // CORE or B2B 'transactions' => [ [ 'amount' => 100.50, // Amount (in currency units) 'debtorIban' => 'GB82WEST12345698765432', // Debtor IBAN 'debtorName' => 'John Doe', // Debtor name 'debtorMandate' => 'MANDATE-001', // Mandate identifier 'debtorMandateSignDate' => new \DateTime('2024-01-15'), // Mandate sign date 'endToEndId' => 'E2E-001', // End-to-end ID 'remittanceInformation' => 'Invoice 12345', // Remittance info (optional) 'debtorBic' => 'WESTGB22', // Debtor BIC (optional) // You can add any additional custom fields here // They will be stored in additionalData and can be used in applyAdditionalData() ], // More transactions... ], ]; $xml = $generator->generateFromArray($data); file_put_contents('direct_debit.xml', $xml);
Using Object Format
use Nowo\SepaPaymentBundle\Model\DirectDebit\DirectDebitData; use Nowo\SepaPaymentBundle\Generator\DirectDebitGenerator; use Nowo\SepaPaymentBundle\Model\DirectDebit\DirectDebitTransaction; use Nowo\SepaPaymentBundle\Validator\IbanValidator; $directDebitData = new DirectDebitData( 'MSG-001', // Message ID 'My Company', // Initiating party name 'PMTINF-1', // Payment info ID new \DateTime('2024-01-20'), // Due date 'My Company Name', // Creditor name 'ES9121000418450200051332', // Creditor IBAN 'RCUR', // Sequence type 'ES98ZZZ09999999999', // Creditor ID 'CORE' // Local instrument code ); $directDebitData->setCreditorBic('CAIXESBBXXX'); $transaction = new DirectDebitTransaction( 100.50, // Amount 'GB82WEST12345698765432', // Debtor IBAN 'John Doe', // Debtor name 'MANDATE-001', // Mandate identifier new \DateTime('2024-01-15'), // Mandate sign date 'E2E-001' // End-to-end ID ); $transaction->setRemittanceInformation('Invoice 12345'); $transaction->setDebtorBic('WESTGB22'); // Optional: Set debtor BIC // You can also add additional custom data $transaction->setAdditionalField('customField', 'customValue'); // Or set multiple additional fields at once $transaction->setAdditionalData(['field1' => 'value1', 'field2' => 'value2']); $directDebitData->addTransaction($transaction); $generator = new DirectDebitGenerator(new IbanValidator()); $xml = $generator->generate($directDebitData);
Using with Dependency Injection
The bundle registers services automatically using Symfony service attributes. All services are autowired and can be injected via constructor:
use Nowo\SepaPaymentBundle\Validator\IbanValidator; use Nowo\SepaPaymentBundle\Generator\RemesaGenerator; use Nowo\SepaPaymentBundle\Generator\DirectDebitGenerator; use Nowo\SepaPaymentBundle\Validator\CreditCardValidator; use Nowo\SepaPaymentBundle\Model\Remesa\RemesaData; class MyService { public function __construct( private IbanValidator $ibanValidator, private RemesaGenerator $remesaGenerator, private DirectDebitGenerator $directDebitGenerator, private CreditCardValidator $creditCardValidator ) { } public function generateRemesaPago(): string { $remesaData = new RemesaData(/* ... */); return $this->remesaGenerator->generate($remesaData); } public function generateRemesaCobro(array $data): string { return $this->directDebitGenerator->generateFromArray($data); } }
Service Aliases
Services are also available via their service aliases for explicit service retrieval:
use Symfony\Component\DependencyInjection\Attribute\Autowire; class MyService { public function __construct( #[Autowire('nowo_sepa_payment.generator.direct_debit_generator')] private DirectDebitGenerator $directDebitGenerator ) { } }
The DirectDebitGenerator service is registered with the alias nowo_sepa_payment.generator.direct_debit_generator and is available as a public service for dependency injection.
Console Commands
The bundle provides console commands for common operations:
Validate IBAN
php bin/console nowo:sepa:validate-iban ES9121000418450200051332
This command validates an IBAN and displays detailed information:
- Normalized IBAN
- Formatted IBAN
- Country code
- Check digits
- BBAN
- Validation result
Convert CCC to IBAN
php bin/console nowo:sepa:ccc-to-iban 21000418450200051332
This command converts a Spanish CCC to IBAN format and displays:
- Original CCC
- Generated IBAN
- Bank code
- Branch code
- Account number
Validate Credit Card
php bin/console sepa:validate-credit-card 4532015112830366
This command validates a credit card number and displays detailed information:
- Normalized card number
- Formatted card number
- Masked card number
- Validation result (Luhn algorithm)
- Card type (Visa, Mastercard, Amex, etc.)
- BIN (Bank Identification Number)
- Last 4 digits
- Card length
The command accepts card numbers with or without spaces and dashes:
php bin/console sepa:validate-credit-card "4532 0151 1283 0366"
php bin/console sepa:validate-credit-card 4532-0151-1283-0366
Configuration
The bundle works out of the box with default settings. No configuration file is required - the bundle uses sensible defaults.
Optional Configuration
If you want to customize the default currency, create config/packages/nowo_sepa_payment.yaml:
nowo_sepa_payment: default_currency: EUR # Default currency code (ISO 4217)
Requirements
- PHP >= 8.1, < 8.6
- Symfony >= 6.0 || >= 7.0 || >= 8.0
- digitick/sepa-xml ^3.0 (automatically installed as a dependency)
Demo Projects
The bundle includes demo projects for different Symfony versions. Each demo has its own docker-compose.yml and can be run independently:
- Symfony 6.4 Demo:
demo/demo-symfony6/(Port 8001 by default) - Symfony 7.0 Demo:
demo/demo-symfony7/(Port 8001 by default) - Symfony 8.0 Demo:
demo/demo-symfony8/(Port 8001 by default)
Demo Endpoints
Each demo application includes the following endpoints to showcase bundle functionality:
Validators:
/validate-iban?iban=ES9121000418450200051332- Validate IBAN and display detailed information/validate-bic?bic=ESPBESMM- Validate BIC and extract components/validate-credit-card?card=4532015112830366- Validate credit card number using Luhn algorithm
Converters:
/convert-ccc?ccc=21000418450200051332- Convert Spanish CCC to IBAN format
Generators:
/generate-identifier- Generate various types of identifiers (message, payment, end-to-end, mandate)/demo-mandate- Demo SEPA mandate creation/demo-remesa-pago- Generate and download SEPA Credit Transfer XML/demo-remesa-cobro- Generate and download SEPA Direct Debit XML
Quick Start with Docker
cd demo make up-symfony6 make install-symfony6 # Access at: http://localhost:8001
See demo/README.md for more details.
Development
Using Docker (Recommended)
# Start the container make up # Install dependencies make install # Run tests make test # Run tests with coverage make test-coverage # Run all QA checks make qa
Without Docker
composer install
composer test
composer test-coverage
composer qa
Testing
The bundle has comprehensive test coverage with 100% code coverage. All tests are located in the tests/ directory and cover:
- Validators:
IbanValidator,BicValidator,CreditCardValidator - Converters:
CccConverter - Generators:
RemesaGenerator,DirectDebitGenerator,IdentifierGeneratorDirectDebitGeneratorincludes extensive test coverage for all code paths:- Array-based generation with various data types
- Validation of required fields
- Optional fields handling
- Edge cases (empty transactions, amount conversion, etc.)
- Models:
RemesaData,Transaction,DirectDebitData,DirectDebitTransaction,Mandate - Parsers:
RemesaParser - Commands: All console commands
Running Tests
# Run all tests composer test # Run tests with coverage report composer test-coverage # View coverage report open coverage/index.html
Code Quality
The bundle uses PHP-CS-Fixer to enforce code style (PSR-12).
# Check code style composer cs-check # Fix code style composer cs-fix
CI/CD
The bundle uses GitHub Actions for continuous integration:
- Tests: Runs on PHP 8.1, 8.2, 8.3, 8.4, and 8.5 with Symfony 6.4, 7.0, and 8.0
- Code Style: Automatically fixes code style on push
- Coverage: Validates 100% code coverage requirement
- Dependabot: Automatically updates dependencies
See .github/workflows/ci.yml for details.
License
The MIT License (MIT). Please see LICENSE for more information.
Author
Created by Héctor Franco Aceituno at Nowo.tech
Contributing
Please read docs/CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
Branching Strategy
See docs/BRANCHING.md for information about our branching strategy and workflow.
Deprecated Fields
See docs/DEPRECATED_FIELDS.md for information about fields that are no longer allowed in SEPA Direct Debit transactions (e.g., postal addresses).
Changelog
See docs/CHANGELOG.md for a list of changes and version history.