diff --git a/.gitignore b/.gitignore index feac0d5..9a70eca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .php_cs .php_cs.cache - phpunit.xml - composer.lock /vendor/ +/nbproject/ diff --git a/src/Z38/SwissPayment/ISRParticipant.php b/src/Z38/SwissPayment/ISRParticipant.php index 11f93b0..664fa63 100644 --- a/src/Z38/SwissPayment/ISRParticipant.php +++ b/src/Z38/SwissPayment/ISRParticipant.php @@ -31,6 +31,15 @@ public function __construct($number) } else { throw new InvalidArgumentException('ISR participant number is not properly formatted.'); } + + if (substr($this->number, 0, 2) !== '01' + && substr($this->number, 0, 2) !== '03') { + throw new InvalidArgumentException('ISR participant number must start by 01 [CHF] or 03 [EURO].'); + } + + if (PostalAccount::calculateCheckDigit(substr($this->number, 0, 8)) !== (int) substr($this->number, -1)) { + throw new InvalidArgumentException('Postal account number has an invalid check digit.'); + } } /** diff --git a/src/Z38/SwissPayment/PostalAccount.php b/src/Z38/SwissPayment/PostalAccount.php index 09c1802..14b8363 100644 --- a/src/Z38/SwissPayment/PostalAccount.php +++ b/src/Z38/SwissPayment/PostalAccount.php @@ -73,7 +73,7 @@ public function asDom(DOMDocument $doc) return $root; } - private static function calculateCheckDigit($number) + public static function calculateCheckDigit($number) { $lookup = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]; $carry = 0; diff --git a/src/Z38/SwissPayment/TransactionInformation/ISRCreditTransfer.php b/src/Z38/SwissPayment/TransactionInformation/ISRCreditTransfer.php index 9eebb34..bc2d766 100644 --- a/src/Z38/SwissPayment/TransactionInformation/ISRCreditTransfer.php +++ b/src/Z38/SwissPayment/TransactionInformation/ISRCreditTransfer.php @@ -8,6 +8,7 @@ use Z38\SwissPayment\ISRParticipant; use Z38\SwissPayment\Money; use Z38\SwissPayment\PaymentInformation\PaymentInformation; +use Z38\SwissPayment\PostalAddressInterface; /** * ISRCreditTransfer contains all the information about a ISR (type 1) transaction. @@ -41,6 +42,10 @@ public function __construct($instructionId, $endToEndId, Money\Money $amount, IS )); } + if (self::modulo10(substr($creditorReference, 0, -1)) != (int) substr($creditorReference, -1)) { + throw new InvalidArgumentException('Invalid ISR creditor reference.'); + } + $this->instructionId = (string) $instructionId; $this->endToEndId = (string) $endToEndId; $this->amount = $amount; @@ -64,6 +69,10 @@ public function asDom(DOMDocument $doc, PaymentInformation $paymentInformation) { $root = $this->buildHeader($doc, $paymentInformation); + if (isset($this->creditorName) && isset($this->creditorAddress)) { + $root->appendChild($this->buildCreditor($doc)); + } + $creditorAccount = $doc->createElement('CdtrAcct'); $creditorAccount->appendChild($this->creditorAccount->asDom($doc)); $root->appendChild($creditorAccount); @@ -92,4 +101,35 @@ protected function appendRemittanceInformation(\DOMDocument $doc, \DOMElement $t $transaction->appendChild($remittanceInformation); } + + /** + * Permit to set optional creditor details + * + * @param string $creditorName + * @param PostalAddressInterface $creditorAddress + */ + public function setCreditorDetails($creditorName, PostalAddressInterface $creditorAddress) + { + $this->creditorName = $creditorName; + $this->creditorAddress = $creditorAddress; + } + + /** + * Creates Modulo10 recursive check digit + * + * @param string $number Number to create recursive check digit off. + * + * @return int Recursive check digit. + */ + private static function modulo10($number) + { + $moduloTable = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]; + + $next = 0; + for ($i = 0; $i < strlen($number); $i++) { + $next = $moduloTable[($next + intval(substr($number, $i, 1))) % 10]; + } + + return (int) (10 - $next) % 10; + } } diff --git a/tests/Z38/SwissPayment/Tests/ISRParticipantTest.php b/tests/Z38/SwissPayment/Tests/ISRParticipantTest.php index 457506e..dd9f654 100644 --- a/tests/Z38/SwissPayment/Tests/ISRParticipantTest.php +++ b/tests/Z38/SwissPayment/Tests/ISRParticipantTest.php @@ -40,15 +40,16 @@ public function testFormat() public function validSamples() { return [ - ['80-2-2'], - ['80-0470-3'], - ['123456789'], + ['01-394971-8'], + ['010059136'], + ['01-137-5'], ]; } public function invalidSamples() { return [ + ['01-394971-9'], ['01-7777777-2'], ['80-470-3-1'], ['12345678'], diff --git a/tests/Z38/SwissPayment/Tests/Message/CustomerCreditTransferTest.php b/tests/Z38/SwissPayment/Tests/Message/CustomerCreditTransferTest.php index b777f42..4d418a3 100644 --- a/tests/Z38/SwissPayment/Tests/Message/CustomerCreditTransferTest.php +++ b/tests/Z38/SwissPayment/Tests/Message/CustomerCreditTransferTest.php @@ -129,7 +129,7 @@ protected function buildMessage() 'instr-010', 'e2e-010', new Money\CHF(20000), // CHF 200.00 - new ISRParticipant('80-5928-4'), + new ISRParticipant('01-1439-8'), '210000000003139471430009017' ); @@ -142,6 +142,18 @@ protected function buildMessage() new PostalAccount('60-9-9') ); + $transaction12 = new ISRCreditTransfer( + 'instr-010', + 'e2e-010', + new Money\CHF(20000), // CHF 200.00 + new ISRParticipant('01-95106-8'), + '6019701803969733825' + ); + $transaction12->setCreditorDetails( + 'Fritz Bischof', + new StructuredPostalAddress('Dorfstrasse', '17', '9911', 'Musterwald') + ); + $payment = new PaymentInformation('payment-001', 'InnoMuster AG', new BIC('ZKBKCHZZ80A'), new IBAN('CH6600700110000204481')); $payment->addTransaction($transaction); $payment->addTransaction($transaction2); @@ -159,6 +171,7 @@ protected function buildMessage() $payment4 = new PaymentInformation('payment-004', 'InnoMuster AG', new BIC('POFICHBEXXX'), new IBAN('CH6309000000250097798')); $payment4->addTransaction($transaction10); + $payment4->addTransaction($transaction12); $payment5 = new PaymentInformation('payment-005', 'InnoMuster AG', new BIC('POFICHBEXXX'), new IBAN('CH6309000000250097798')); $payment5->setCategoryPurpose(new CategoryPurposeCode('SALA')); @@ -184,10 +197,10 @@ public function testGroupHeader() $xpath->registerNamespace('pain001', self::NS_URI_ROOT.self::SCHEMA); $nbOfTxs = $xpath->evaluate('string(//pain001:GrpHdr/pain001:NbOfTxs)'); - $this->assertEquals('11', $nbOfTxs); + $this->assertEquals('12', $nbOfTxs); $ctrlSum = $xpath->evaluate('string(//pain001:GrpHdr/pain001:CtrlSum)'); - $this->assertEquals('4010.00', $ctrlSum); + $this->assertEquals('4210.00', $ctrlSum); } public function testSchemaValidation() diff --git a/tests/Z38/SwissPayment/Tests/TransactionInformation/ISRCreditTransferTest.php b/tests/Z38/SwissPayment/Tests/TransactionInformation/ISRCreditTransferTest.php index 9176d48..c311f9b 100644 --- a/tests/Z38/SwissPayment/Tests/TransactionInformation/ISRCreditTransferTest.php +++ b/tests/Z38/SwissPayment/Tests/TransactionInformation/ISRCreditTransferTest.php @@ -4,6 +4,7 @@ use Z38\SwissPayment\ISRParticipant; use Z38\SwissPayment\Money; +use Z38\SwissPayment\StructuredPostalAddress; use Z38\SwissPayment\Tests\TestCase; use Z38\SwissPayment\TransactionInformation\ISRCreditTransfer; @@ -27,6 +28,21 @@ public function testInvalidAmount() ); } + /** + * @covers ::__construct + * @expectedException \InvalidArgumentException + */ + public function testInvalidCreditorReference() + { + $transfer = new ISRCreditTransfer( + 'id000', + 'name', + new Money\CHF(100), + new ISRParticipant('01-25083-7'), + '120000000000234478943216891' + ); + } + /** * @covers ::setRemittanceInformation * @expectedException \LogicException @@ -37,10 +53,28 @@ public function testSetRemittanceInformation() 'id000', 'name', new Money\CHF(100), - new ISRParticipant('10-2424-4'), + new ISRParticipant('01-25083-7'), '120000000000234478943216899' ); $transfer->setRemittanceInformation('not allowed'); } + + /** + * @covers ::setCreditorDetails + */ + public function testCreditorDetails() + { + $transfer = new ISRCreditTransfer( + 'id000', + 'name', + new Money\CHF(100), + new ISRParticipant('01-25083-7'), + '120000000000234478943216899' + ); + + $creditorName = 'name'; + $creditorAddress = new StructuredPostalAddress('foo', '99', '9999', 'bar'); + $transfer->setCreditorDetails($creditorName, $creditorAddress); + } }