diff --git a/README.md b/README.md index d614b57..b41f41c 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ try { | XE3 | Upload SEPA Direct Debit Initiation, CH definitions, CORE (i.e available in Switzerland). | | YCT | Upload Credit transfer CGI (SEPA & non SEPA). | | CDD | Upload initiation of the direct debit transaction. | +| CDB | Upload initiation of the direct debit transaction for business. | | BTD | Download request files of any BTF structure. | | BTU | Upload the files to the bank. | | HVU | Download List the orders for which the user is authorized as a signatory. | diff --git a/src/Contracts/EbicsClientInterface.php b/src/Contracts/EbicsClientInterface.php index ffcd0aa..992dfa0 100644 --- a/src/Contracts/EbicsClientInterface.php +++ b/src/Contracts/EbicsClientInterface.php @@ -376,6 +376,19 @@ public function CCT(OrderDataInterface $orderData, DateTimeInterface $dateTime = */ public function CDD(OrderDataInterface $orderData, DateTimeInterface $dateTime = null): UploadOrderResult; + /** + * Upload initiation of the direct debit transaction for business. + * The CDB order type uses the protocol version H00X. + * FileFormat pain.008.001.02 + * OrderType:BTU, Service Name:SDD, Scope:SDD,Service Option:COR Container:, MsgName:pain.008 + * + * @param OrderDataInterface $orderData + * @param DateTimeInterface|null $dateTime + * + * @return UploadOrderResult + */ + public function CDB(OrderDataInterface $orderData, DateTimeInterface $dateTime = null): UploadOrderResult; + /** * Upload initiation credit transfer per Swiss Payments specification set by Six banking services. * XE2 is an upload order type that uses the protocol version H00X. diff --git a/src/EbicsClient.php b/src/EbicsClient.php index 6a01839..ad2e00b 100644 --- a/src/EbicsClient.php +++ b/src/EbicsClient.php @@ -807,6 +807,30 @@ public function CDD(OrderDataInterface $orderData, DateTimeInterface $dateTime = return $this->createUploadOrderResult($transaction, $orderData); } + /** + * @inheritDoc + * @throws Exceptions\EbicsResponseException + * @throws EbicsException + */ + public function CDB(OrderDataInterface $orderData, DateTimeInterface $dateTime = null): UploadOrderResult + { + if (null === $dateTime) { + $dateTime = new DateTime(); + } + + $transaction = $this->uploadTransaction(function (UploadTransaction $transaction) use ($dateTime, $orderData) { + $transaction->setOrderData($orderData->getContent()); + $transaction->setDigest($this->cryptService->hash($transaction->getOrderData())); + + return $this->requestFactory->createCDB( + $dateTime, + $transaction + ); + }); + + return $this->createUploadOrderResult($transaction, $orderData); + } + /** * @inheritDoc * @throws Exceptions\EbicsResponseException diff --git a/src/Factories/RequestFactory.php b/src/Factories/RequestFactory.php index 3e5545f..7f0f742 100644 --- a/src/Factories/RequestFactory.php +++ b/src/Factories/RequestFactory.php @@ -853,6 +853,8 @@ abstract public function createCCT(DateTimeInterface $dateTime, UploadTransactio abstract public function createCDD(DateTimeInterface $dateTime, UploadTransaction $transaction): Request; + abstract public function createCDB(DateTimeInterface $dateTime, UploadTransaction $transaction): Request; + abstract public function createXE2(DateTimeInterface $dateTime, UploadTransaction $transaction): Request; abstract public function createXE3(DateTimeInterface $dateTime, UploadTransaction $transaction): Request; diff --git a/src/Factories/RequestFactoryV24.php b/src/Factories/RequestFactoryV24.php index 9fc5959..4902e56 100644 --- a/src/Factories/RequestFactoryV24.php +++ b/src/Factories/RequestFactoryV24.php @@ -169,6 +169,11 @@ public function createCDD(DateTimeInterface $dateTime, UploadTransaction $transa throw new LogicException('Method not implemented yet for EBICS 2.4'); } + public function createCDB(DateTimeInterface $dateTime, UploadTransaction $transaction): Request + { + throw new LogicException('Method not implemented yet for EBICS 2.4'); + } + public function createXE2(DateTimeInterface $dateTime, UploadTransaction $transaction): Request { throw new LogicException('Method not implemented yet for EBICS 2.4'); diff --git a/src/Factories/RequestFactoryV25.php b/src/Factories/RequestFactoryV25.php index 24e523f..4228834 100644 --- a/src/Factories/RequestFactoryV25.php +++ b/src/Factories/RequestFactoryV25.php @@ -61,6 +61,7 @@ protected function addOrderType(OrderDetailsBuilder $orderDetailsBuilder, string break; case 'CCT': case 'CDD': + case 'CDB': case 'XE2': case 'XE3': case 'CIP': @@ -681,6 +682,70 @@ public function createCDD(DateTimeInterface $dateTime, UploadTransaction $transa return $request; } + /** + * @throws EbicsException + */ + public function createCDB(DateTimeInterface $dateTime, UploadTransaction $transaction): Request + { + $signatureData = new UserSignature(); + $this->userSignatureHandler->handle($signatureData, $transaction->getDigest()); + + $context = (new RequestContext()) + ->setBank($this->bank) + ->setUser($this->user) + ->setKeyring($this->keyring) + ->setDateTime($dateTime) + ->setTransactionKey($transaction->getKey()) + ->setNumSegments($transaction->getNumSegments()) + ->setSignatureData($signatureData); + + $request = $this + ->createRequestBuilderInstance() + ->addContainerSecured(function (XmlBuilder $builder) use ($context) { + $builder->addHeader(function (HeaderBuilder $builder) use ($context) { + $builder->addStatic(function (StaticBuilder $builder) use ($context) { + $builder + ->addHostId($context->getBank()->getHostId()) + ->addRandomNonce() + ->addTimestamp($context->getDateTime()) + ->addPartnerId($context->getUser()->getPartnerId()) + ->addUserId($context->getUser()->getUserId()) + ->addProduct('Ebics client PHP', 'de') + ->addOrderDetails(function (OrderDetailsBuilder $orderDetailsBuilder) { + $this + ->addOrderType($orderDetailsBuilder, 'CDB') + ->addStandardOrderParams(); + }) + ->addBankPubKeyDigests( + $context->getKeyring()->getBankSignatureXVersion(), + $this->digestResolver->digest($context->getKeyring()->getBankSignatureX()), + $context->getKeyring()->getBankSignatureEVersion(), + $this->digestResolver->digest($context->getKeyring()->getBankSignatureE()) + ) + ->addSecurityMedium(StaticBuilder::SECURITY_MEDIUM_0000) + ->addNumSegments($context->getNumSegments()); + })->addMutable(function (MutableBuilder $builder) { + $builder->addTransactionPhase(MutableBuilder::PHASE_INITIALIZATION); + }); + })->addBody(function (BodyBuilder $builder) use ($context) { + $builder->addDataTransfer(function (DataTransferBuilder $builder) use ($context) { + $builder + ->addDataEncryptionInfo(function (DataEncryptionInfoBuilder $builder) use ($context) { + $builder + ->addEncryptionPubKeyDigest($context->getKeyring()) + ->addTransactionKey($context->getTransactionKey(), $context->getKeyring()); + }) + ->addSignatureData($context->getSignatureData(), $context->getTransactionKey()); + }); + }); + }) + ->popInstance(); + + $this->authSignatureHandler->handle($request); + + return $request; + } + /** * @throws EbicsException */ diff --git a/src/Factories/RequestFactoryV3.php b/src/Factories/RequestFactoryV3.php index e261dde..e654b92 100644 --- a/src/Factories/RequestFactoryV3.php +++ b/src/Factories/RequestFactoryV3.php @@ -324,6 +324,12 @@ public function createCDD(DateTimeInterface $dateTime, UploadTransaction $transa throw new LogicException('Method not implemented yet for EBICS 3.0'); } + public function createCDB(DateTimeInterface $dateTime, UploadTransaction $transaction): Request + { + throw new LogicException('Method not implemented yet for EBICS 3.0'); + } + + public function createXE2(DateTimeInterface $dateTime, UploadTransaction $transaction): Request { throw new LogicException('Method not implemented yet for EBICS 3.0'); diff --git a/tests/EbicsClientV25Test.php b/tests/EbicsClientV25Test.php index 83d576f..2c4dcdb 100644 --- a/tests/EbicsClientV25Test.php +++ b/tests/EbicsClientV25Test.php @@ -813,6 +813,38 @@ public function testCDD(int $credentialsId, array $codes, X509GeneratorInterface $this->assertResponseOk($code, $reportText); } + /** + * @dataProvider serversDataProvider + * + * @group CDB + * + * @param int $credentialsId + * @param array $codes + * @param X509GeneratorInterface|null $x509Generator + * + * @covers + */ + public function testCDB(int $credentialsId, array $codes, X509GeneratorInterface $x509Generator = null) + { + $client = $this->setupClientV25($credentialsId, $x509Generator, $codes['CDB']['fake']); + + $this->assertExceptionCode($codes['CDB']['code']); + + $customerDirectDebit = $this->buildCustomerDirectDebit('urn:iso:std:iso:20022:tech:xsd:pain.008.001.02'); + + $cdb = $client->CDB($customerDirectDebit); + + $responseHandler = $client->getResponseHandler(); + $code = $responseHandler->retrieveH00XReturnCode($cdb->getTransaction()->getLastSegment()->getResponse()); + $reportText = $responseHandler->retrieveH00XReportText($cdb->getTransaction()->getLastSegment()->getResponse()); + $this->assertResponseOk($code, $reportText); + + $code = $responseHandler->retrieveH00XReturnCode($cdb->getTransaction()->getInitialization()->getResponse()); + $reportText = $responseHandler->retrieveH00XReportText($cdb->getTransaction()->getInitialization()->getResponse()); + + $this->assertResponseOk($code, $reportText); + } + /** * @dataProvider serversDataProvider * @@ -1039,6 +1071,7 @@ public function serversDataProvider() 'XE2' => ['code' => null, 'fake' => false], 'XE3' => ['code' => null, 'fake' => false], 'CDD' => ['code' => null, 'fake' => false], + 'CDB' => ['code' => '090003', 'fake' => false], 'CIP' => ['code' => '091005', 'fake' => false], 'HVU' => ['code' => '090003', 'fake' => false], 'HVZ' => ['code' => '090003', 'fake' => false], @@ -1084,6 +1117,7 @@ public function serversDataProvider() 'XE2' => ['code' => null, 'fake' => false], 'XE3' => ['code' => null, 'fake' => false], 'CDD' => ['code' => null, 'fake' => false], + 'CDB' => ['code' => null, 'fake' => false], 'CIP' => ['code' => null, 'fake' => false], 'HVU' => ['code' => '091006', 'fake' => false], 'HVZ' => ['code' => '091006', 'fake' => false], @@ -1130,6 +1164,7 @@ public function serversDataProvider() 'XE2' => ['code' => null, 'fake' => false], 'XE3' => ['code' => null, 'fake' => false], 'CDD' => ['code' => null, 'fake' => false], + 'CDB' => ['code' => null, 'fake' => false], 'CIP' => ['code' => '091005', 'fake' => false], 'HVU' => ['code' => '090003', 'fake' => false], 'HVZ' => ['code' => '090003', 'fake' => false], @@ -1175,6 +1210,7 @@ public function serversDataProvider() 'XE2' => ['code' => null, 'fake' => false], 'XE3' => ['code' => null, 'fake' => false], 'CDD' => ['code' => '090003', 'fake' => false], + 'CDB' => ['code' => '090003', 'fake' => false], 'CIP' => ['code' => '091005', 'fake' => false], 'HVU' => ['code' => '090003', 'fake' => false], 'HVZ' => ['code' => '090003', 'fake' => false], @@ -1220,6 +1256,7 @@ public function serversDataProvider() 'XE2' => ['code' => null, 'fake' => false], 'XE3' => ['code' => null, 'fake' => false], 'CDD' => ['code' => null, 'fake' => false], + 'CDB' => ['code' => null, 'fake' => false], 'CIP' => ['code' => '091005', 'fake' => false], 'HVU' => ['code' => '090003', 'fake' => false], 'HVZ' => ['code' => '090003', 'fake' => false], diff --git a/tests/FakerHttpClient.php b/tests/FakerHttpClient.php index cc4c64e..7784b70 100644 --- a/tests/FakerHttpClient.php +++ b/tests/FakerHttpClient.php @@ -74,6 +74,9 @@ private function fixtureOrderType(string $orderType, array $options = null): Res case 'CDD': $fileName = 'cdd.xml'; break; + case 'CDB': + $fileName = 'cdb.xml'; + break; default: throw new LogicException(sprintf('Faked order type `%s` not supported.', $orderType)); } @@ -81,7 +84,7 @@ private function fixtureOrderType(string $orderType, array $options = null): Res $fixturePath = $this->fixturesDir . '/' . $fileName; if (!is_file($fixturePath)) { - throw new LogicException('Fixtures file doe not exists.'); + throw new LogicException('Fixtures file does not exists.'); } $response = new Response();