From 84381831b1c036695610e972c711d5f622378bfa Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 18:30:24 +0200 Subject: [PATCH 01/24] [UAS-SCOUT] Update CI configuration for PHP 8.3 and 8.4 support - Enhanced CI workflow to support multiple PHP versions (8.3 and 8.4). - Updated actions to their latest versions for improved performance and security. - Adjusted PHPUnit and Composer steps to align with the new PHP setup. --- .github/workflows/ci.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d5f141..9d1bd61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,14 +1,15 @@ name: CI - on: [push] - jobs: build-test: runs-on: ubuntu-latest + strategy: + matrix: + php-version: ['8.3', '8.4'] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup .env run: cp .env.example .env @@ -18,13 +19,21 @@ jobs: sed -i "s/BLUEM_SENDER_ID=S/BLUEM_SENDER_ID=${{ secrets.BLUEM_SENDER_ID }}/g" .env sed -i "s/BLUEM_TEST_ACCESS_TOKEN=/BLUEM_TEST_ACCESS_TOKEN=${{ secrets.BLUEM_TEST_ACCESS_TOKEN }}/g" .env + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, intl + coverage: xdebug + - name: Run composer - uses: php-actions/composer@v5 + uses: php-actions/composer@v6 + with: + php_version: ${{ matrix.php-version }} - name: PHPUnit Tests uses: php-actions/phpunit@v3 with: - version: 9.5 - php_version: 8.1 + php_version: ${{ matrix.php-version }} bootstrap: ./vendor/autoload.php configuration: ./.github/workflows/phpunit.xml From 0f957c0dee8f970621a083717c2255b67bf329d4 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 18:33:04 +0200 Subject: [PATCH 02/24] [UAS-SCOUT] Add PHPCS linting step to CI configuration This commit introduces a PHPCS linting step to the CI pipeline, ensuring that code adheres to PSR12 standards. This enhancement aims to maintain code quality and consistency across the project. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d1bd61..81bfd5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,9 @@ jobs: with: php_version: ${{ matrix.php-version }} + - name: PHPCS Lint + run: vendor/bin/phpcs --standard=PSR12 src tests + - name: PHPUnit Tests uses: php-actions/phpunit@v3 with: From baf1b0a77c143e958ed34e5ad778dae03002267b Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 18:34:09 +0200 Subject: [PATCH 03/24] [UAS-SCOUT] Update PHPUnit action to version 4 and set version to 9.5 Updated the PHPUnit GitHub Action to version 4 and specified the PHPUnit version to 9.5 in the CI configuration. This ensures compatibility with the latest features and improvements in PHPUnit. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81bfd5c..3fb1274 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,9 @@ jobs: run: vendor/bin/phpcs --standard=PSR12 src tests - name: PHPUnit Tests - uses: php-actions/phpunit@v3 + uses: php-actions/phpunit@v4 with: php_version: ${{ matrix.php-version }} + version: 9.5 bootstrap: ./vendor/autoload.php configuration: ./.github/workflows/phpunit.xml From 79659d051a2de425a4488b6633d5ddfa7d6d1700 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 18:36:37 +0200 Subject: [PATCH 04/24] [UAS-SCOUT] Run PHPCS & PHPCBF: Standardize code formatting and spacing across multiple files This commit addresses inconsistencies in code formatting, including spacing around operators and method definitions. The changes enhance code readability and maintainability. No functional changes were made. Testing was conducted to ensure that all existing functionalities remain intact after the formatting adjustments. --- src/Bluem.php | 13 ++--- src/Contexts/BluemContext.php | 2 +- src/Contexts/IBANCheckContext.php | 1 + src/Contexts/IdentityContext.php | 3 +- src/Contexts/MandatesContext.php | 3 +- src/Contexts/PaymentsContext.php | 3 +- .../InvalidBluemConfigurationException.php | 2 +- .../InvalidBluemRequestException.php | 2 +- src/Exceptions/InvalidContextException.php | 1 + src/Extensions/IPAPI.php | 4 +- src/Helpers/BIC.php | 1 + src/Helpers/BluemConfiguration.php | 4 +- src/Helpers/BluemCurrency.php | 2 +- src/Helpers/BluemIdentityCategoryList.php | 2 +- src/Helpers/BluemMaxAmount.php | 3 +- src/Interfaces/BluemContextInterface.php | 1 + src/Interfaces/BluemRequestInterface.php | 1 + src/Interfaces/BluemResponseInterface.php | 1 + src/Interfaces/WebhookInterface.php | 2 +- src/Requests/BluemRequest.php | 3 +- src/Requests/EmandateBluemRequest.php | 2 +- src/Requests/EmandateStatusBluemRequest.php | 1 + src/Requests/IBANBluemRequest.php | 1 + src/Requests/IdentityBluemRequest.php | 1 + src/Requests/IdentityStatusBluemRequest.php | 4 +- src/Requests/PaymentBluemRequest.php | 18 +++---- src/Requests/PaymentStatusBluemRequest.php | 4 +- src/Responses/BluemResponse.php | 3 +- src/Responses/ErrorBluemResponse.php | 1 + src/Responses/IBANNameCheckBluemResponse.php | 1 + src/Responses/IdentityStatusBluemResponse.php | 1 + .../IdentityTransactionBluemResponse.php | 1 + src/Responses/MandateStatusBluemResponse.php | 1 + .../MandateTransactionBluemResponse.php | 1 + src/Responses/PaymentStatusBluemResponse.php | 4 +- .../PaymentTransactionBluemResponse.php | 1 + src/Responses/StatusBluemResponse.php | 1 + src/Responses/TransactionBluemResponse.php | 2 +- .../BluemConfigurationValidator.php | 33 +++++++------ src/Validators/BluemXMLValidator.php | 1 + src/Validators/WebhookSignatureValidation.php | 9 ++-- src/Validators/WebhookValidator.php | 1 + src/Validators/WebhookValidatorInterface.php | 1 + src/Validators/WebhookXMLValidator.php | 1 + src/Validators/WebhookXmlValidation.php | 1 + src/Webhook.php | 48 +++++++++---------- tests/Integration/BluemGenericTestCase.php | 11 +++-- tests/Integration/BluemMainTest.php | 5 +- tests/Integration/IPAPITest.php | 3 +- tests/Integration/IdentityRequestTest.php | 2 +- tests/Integration/MandateRequestTest.php | 3 +- tests/Integration/PaymentRequestTest.php | 4 +- tests/Integration/WebhookTest.php | 4 +- tests/Unit/BluemConfigurationTest.php | 1 - tests/Unit/BluemTest.php | 3 +- 55 files changed, 137 insertions(+), 96 deletions(-) diff --git a/src/Bluem.php b/src/Bluem.php index c1e61dc..7d71cb3 100644 --- a/src/Bluem.php +++ b/src/Bluem.php @@ -78,7 +78,7 @@ class Bluem */ public function __construct(mixed $rawConfig) { - if ($rawConfig ===null) { + if ($rawConfig === null) { throw new InvalidBluemConfigurationException('No configuration given'); } @@ -205,10 +205,11 @@ public function CreateMandateID(string $order_id, string $customer_id): string public function PerformRequest(BluemRequestInterface $transaction_request): BluemResponseInterface { $validator = new BluemXMLValidator(); - if (! $validator->validate( - $transaction_request->RequestContext(), - $transaction_request->XmlString() - ) + if ( + ! $validator->validate( + $transaction_request->RequestContext(), + $transaction_request->XmlString() + ) ) { return new ErrorBluemResponse( "Error: Request is not formed correctly. More details: " . @@ -306,7 +307,7 @@ public function PerformRequest(BluemRequestInterface $transaction_request): Blue return new ErrorBluemResponse('Unexpected / erroneous response (code ' . $response_status . ')'); } } catch (Throwable $throwable) { - return new ErrorBluemResponse('HTTP Request Error'. $throwable->getMessage()); + return new ErrorBluemResponse('HTTP Request Error' . $throwable->getMessage()); // @todo improve request return exceptions; add our own exception type } } diff --git a/src/Contexts/BluemContext.php b/src/Contexts/BluemContext.php index 8d2d22c..6fd4374 100644 --- a/src/Contexts/BluemContext.php +++ b/src/Contexts/BluemContext.php @@ -8,13 +8,13 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Contexts; use Bluem\BluemPHP\Interfaces\BluemContextInterface; abstract class BluemContext implements BluemContextInterface { - public function __construct(public array $BICs = []) { } diff --git a/src/Contexts/IBANCheckContext.php b/src/Contexts/IBANCheckContext.php index b1a2ed8..463c914 100644 --- a/src/Contexts/IBANCheckContext.php +++ b/src/Contexts/IBANCheckContext.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Contexts; class IBANCheckContext extends BluemContext diff --git a/src/Contexts/IdentityContext.php b/src/Contexts/IdentityContext.php index 9da4702..91a2a38 100644 --- a/src/Contexts/IdentityContext.php +++ b/src/Contexts/IdentityContext.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Contexts; use Bluem\BluemPHP\Helpers\BIC; @@ -56,7 +57,7 @@ public function addPaymentMethodDetails(array $details = []): void { $validationErrors = $this->validateDetails(); if ($validationErrors !== []) { - throw new RuntimeException('Invalid details given: '. implode(', ', $validationErrors)); + throw new RuntimeException('Invalid details given: ' . implode(', ', $validationErrors)); } $this->paymentMethodDetails = $details; diff --git a/src/Contexts/MandatesContext.php b/src/Contexts/MandatesContext.php index 7a5e484..6786866 100644 --- a/src/Contexts/MandatesContext.php +++ b/src/Contexts/MandatesContext.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Contexts; use Bluem\BluemPHP\Helpers\BIC; @@ -78,7 +79,7 @@ public function addPaymentMethodDetails(array $details = []): void { $validationErrors = $this->validateDetails(); if ($validationErrors !== []) { - throw new RuntimeException('Invalid details given: '. implode(', ', $validationErrors)); + throw new RuntimeException('Invalid details given: ' . implode(', ', $validationErrors)); } $this->paymentMethodDetails = $details; diff --git a/src/Contexts/PaymentsContext.php b/src/Contexts/PaymentsContext.php index cc1f448..464aa68 100644 --- a/src/Contexts/PaymentsContext.php +++ b/src/Contexts/PaymentsContext.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Contexts; use Bluem\BluemPHP\Helpers\BIC; @@ -75,7 +76,7 @@ public function addPaymentMethodDetails(array $details = []): void { $validationErrors = $this->validateDetails(); if ($validationErrors !== []) { - throw new RuntimeException('Invalid details given: '. implode(', ', $validationErrors)); + throw new RuntimeException('Invalid details given: ' . implode(', ', $validationErrors)); } $this->paymentMethodDetails = $details; diff --git a/src/Exceptions/InvalidBluemConfigurationException.php b/src/Exceptions/InvalidBluemConfigurationException.php index 1cea7db..2f25d1e 100644 --- a/src/Exceptions/InvalidBluemConfigurationException.php +++ b/src/Exceptions/InvalidBluemConfigurationException.php @@ -8,11 +8,11 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Exceptions; use Exception; class InvalidBluemConfigurationException extends Exception { - } diff --git a/src/Exceptions/InvalidBluemRequestException.php b/src/Exceptions/InvalidBluemRequestException.php index 66fd9f3..f676dbb 100644 --- a/src/Exceptions/InvalidBluemRequestException.php +++ b/src/Exceptions/InvalidBluemRequestException.php @@ -8,11 +8,11 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Exceptions; use Exception; class InvalidBluemRequestException extends Exception { - } diff --git a/src/Exceptions/InvalidContextException.php b/src/Exceptions/InvalidContextException.php index 45fc9df..a67a7bc 100644 --- a/src/Exceptions/InvalidContextException.php +++ b/src/Exceptions/InvalidContextException.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Exceptions; use Exception; diff --git a/src/Extensions/IPAPI.php b/src/Extensions/IPAPI.php index b5a46e3..375f04d 100644 --- a/src/Extensions/IPAPI.php +++ b/src/Extensions/IPAPI.php @@ -1,4 +1,5 @@ * @@ -34,7 +35,8 @@ public function CheckIsNetherlands(string $ip = ""): bool $result = $this->QueryIP($ip); // if we encountered an error, return true for now - if (isset($result['success']) + if ( + isset($result['success']) && $result['success'] === false ) { return true; diff --git a/src/Helpers/BIC.php b/src/Helpers/BIC.php index c614c1f..0a72284 100644 --- a/src/Helpers/BIC.php +++ b/src/Helpers/BIC.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Helpers; class BIC diff --git a/src/Helpers/BluemConfiguration.php b/src/Helpers/BluemConfiguration.php index 8a6ad56..1664a58 100644 --- a/src/Helpers/BluemConfiguration.php +++ b/src/Helpers/BluemConfiguration.php @@ -1,4 +1,5 @@ * @@ -6,7 +7,6 @@ * with this source code in the file LICENSE. */ - namespace Bluem\BluemPHP\Helpers; use Bluem\BluemPHP\Exceptions\InvalidBluemConfigurationException; @@ -127,7 +127,7 @@ private function _assumeBrandID(string $service, string $brandID): string } $prefix = str_replace($available_services, '', $brandID); - return $prefix.ucfirst($service); + return $prefix . ucfirst($service); } /** diff --git a/src/Helpers/BluemCurrency.php b/src/Helpers/BluemCurrency.php index 93eb4e4..bfd7f78 100644 --- a/src/Helpers/BluemCurrency.php +++ b/src/Helpers/BluemCurrency.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Helpers; use RuntimeException; @@ -15,7 +16,6 @@ class BluemCurrency implements Stringable { - private const string EURO_CURRENCY = 'EUR'; private const string US_DOLLAR_CURRENCY = 'USD'; diff --git a/src/Helpers/BluemIdentityCategoryList.php b/src/Helpers/BluemIdentityCategoryList.php index 4c0042f..edbfa07 100644 --- a/src/Helpers/BluemIdentityCategoryList.php +++ b/src/Helpers/BluemIdentityCategoryList.php @@ -8,11 +8,11 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Helpers; class BluemIdentityCategoryList { - /** * @var string[] $categories */ diff --git a/src/Helpers/BluemMaxAmount.php b/src/Helpers/BluemMaxAmount.php index 71000d5..c3fd93b 100644 --- a/src/Helpers/BluemMaxAmount.php +++ b/src/Helpers/BluemMaxAmount.php @@ -8,11 +8,11 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Helpers; class BluemMaxAmount implements \Stringable { - public BluemCurrency $currency; public function __construct( @@ -20,7 +20,6 @@ public function __construct( string $currencyCode ) { try { - $this->currency = new BluemCurrency($currencyCode); } catch (\Exception) { $this->currency = new BluemCurrency(); diff --git a/src/Interfaces/BluemContextInterface.php b/src/Interfaces/BluemContextInterface.php index b217734..24cf7fa 100644 --- a/src/Interfaces/BluemContextInterface.php +++ b/src/Interfaces/BluemContextInterface.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Interfaces; interface BluemContextInterface diff --git a/src/Interfaces/BluemRequestInterface.php b/src/Interfaces/BluemRequestInterface.php index e2b7a3d..9a758da 100644 --- a/src/Interfaces/BluemRequestInterface.php +++ b/src/Interfaces/BluemRequestInterface.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Interfaces; use Bluem\BluemPHP\Requests\BluemRequest; diff --git a/src/Interfaces/BluemResponseInterface.php b/src/Interfaces/BluemResponseInterface.php index c74ab5c..25c11d9 100644 --- a/src/Interfaces/BluemResponseInterface.php +++ b/src/Interfaces/BluemResponseInterface.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Interfaces; interface BluemResponseInterface diff --git a/src/Interfaces/WebhookInterface.php b/src/Interfaces/WebhookInterface.php index 853f192..5cf676c 100644 --- a/src/Interfaces/WebhookInterface.php +++ b/src/Interfaces/WebhookInterface.php @@ -8,9 +8,9 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Interfaces; interface WebhookInterface { - } diff --git a/src/Requests/BluemRequest.php b/src/Requests/BluemRequest.php index 356aaf8..9f7f00b 100644 --- a/src/Requests/BluemRequest.php +++ b/src/Requests/BluemRequest.php @@ -1,4 +1,5 @@ * @@ -181,7 +182,7 @@ private function entranceCode(string $expectedReturn = 'none'): string case '': case 'none': default: - break; + break; } } diff --git a/src/Requests/EmandateBluemRequest.php b/src/Requests/EmandateBluemRequest.php index fbff6d6..064f3cc 100644 --- a/src/Requests/EmandateBluemRequest.php +++ b/src/Requests/EmandateBluemRequest.php @@ -1,4 +1,5 @@ * @@ -6,7 +7,6 @@ * with this source code in the file LICENSE. */ - namespace Bluem\BluemPHP\Requests; use Bluem\BluemPHP\Contexts\MandatesContext; diff --git a/src/Requests/EmandateStatusBluemRequest.php b/src/Requests/EmandateStatusBluemRequest.php index 1e95c61..ed397ed 100644 --- a/src/Requests/EmandateStatusBluemRequest.php +++ b/src/Requests/EmandateStatusBluemRequest.php @@ -1,4 +1,5 @@ * diff --git a/src/Requests/IBANBluemRequest.php b/src/Requests/IBANBluemRequest.php index 3f0d0dd..d4532af 100644 --- a/src/Requests/IBANBluemRequest.php +++ b/src/Requests/IBANBluemRequest.php @@ -1,4 +1,5 @@ * diff --git a/src/Requests/IdentityBluemRequest.php b/src/Requests/IdentityBluemRequest.php index 08ab4d2..583a983 100644 --- a/src/Requests/IdentityBluemRequest.php +++ b/src/Requests/IdentityBluemRequest.php @@ -1,4 +1,5 @@ * diff --git a/src/Requests/IdentityStatusBluemRequest.php b/src/Requests/IdentityStatusBluemRequest.php index 307733a..7cf758c 100644 --- a/src/Requests/IdentityStatusBluemRequest.php +++ b/src/Requests/IdentityStatusBluemRequest.php @@ -1,4 +1,6 @@ - * * This source file is subject to the license that is bundled diff --git a/src/Requests/PaymentBluemRequest.php b/src/Requests/PaymentBluemRequest.php index 0c67ca4..728331e 100644 --- a/src/Requests/PaymentBluemRequest.php +++ b/src/Requests/PaymentBluemRequest.php @@ -1,4 +1,5 @@ * @@ -204,7 +205,7 @@ public function setPaymentMethodToIDEAL($BIC = ""): self if (!empty($BIC)) { $this->context->addPaymentMethodDetails( [ - 'BIC'=>$BIC + 'BIC' => $BIC ] ); } @@ -222,7 +223,7 @@ public function setPaymentMethodToPayPal($payPalAccount = ""): self if (!empty($payPalAccount)) { $this->context->addPaymentMethodDetails( [ - 'PayPalAccount'=>$payPalAccount + 'PayPalAccount' => $payPalAccount ] ); } @@ -242,16 +243,17 @@ public function setPaymentMethodToCreditCard( /** * Prepared for future use. */ - if ($cardNumber !== '' && $cardNumber !== '0' || $name !== '' && $name !== '0' || $securityCode !== '' && $securityCode !== '0' + if ( + $cardNumber !== '' && $cardNumber !== '0' || $name !== '' && $name !== '0' || $securityCode !== '' && $securityCode !== '0' || $expirationDateMonth !== '' && $expirationDateMonth !== '0' || $expirationDateYear !== '' && $expirationDateYear !== '0' ) { $this->context->addPaymentMethodDetails( [ - 'CardNumber'=>$cardNumber, - 'Name'=>$name, - 'SecurityCode'=>$securityCode, - 'ExpirationDateMonth'=>$expirationDateMonth, - 'ExpirationDateYear'=>$expirationDateYear, + 'CardNumber' => $cardNumber, + 'Name' => $name, + 'SecurityCode' => $securityCode, + 'ExpirationDateMonth' => $expirationDateMonth, + 'ExpirationDateYear' => $expirationDateYear, ] ); } diff --git a/src/Requests/PaymentStatusBluemRequest.php b/src/Requests/PaymentStatusBluemRequest.php index 197c079..dfa9b05 100644 --- a/src/Requests/PaymentStatusBluemRequest.php +++ b/src/Requests/PaymentStatusBluemRequest.php @@ -1,4 +1,5 @@ * @@ -28,7 +29,8 @@ public function __construct( ) { parent::__construct($config, $entranceCode, $expected_return); - if (isset($config->paymentBrandID) + if ( + isset($config->paymentBrandID) && $config->paymentBrandID !== "" ) { $config->setBrandID($config->paymentBrandID); diff --git a/src/Responses/BluemResponse.php b/src/Responses/BluemResponse.php index 589eeb4..bda5c5f 100644 --- a/src/Responses/BluemResponse.php +++ b/src/Responses/BluemResponse.php @@ -19,7 +19,6 @@ */ class BluemResponse extends SimpleXMLElement implements BluemResponseInterface { - public static string $response_primary_key = null; public static string $transaction_type = null; @@ -78,7 +77,7 @@ protected function getChildXmlElement(): string return self::$response_primary_key; } - protected function getParentStringVariable(string $variable) : string + protected function getParentStringVariable(string $variable): string { return ( isset($this->{$this->getParentXmlElement()}->$variable) ) ? $this->{$this->getParentXmlElement()}->$variable . '' : ''; } diff --git a/src/Responses/ErrorBluemResponse.php b/src/Responses/ErrorBluemResponse.php index d68d780..1c129ea 100644 --- a/src/Responses/ErrorBluemResponse.php +++ b/src/Responses/ErrorBluemResponse.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Responses; use Bluem\BluemPHP\Interfaces\BluemResponseInterface; diff --git a/src/Responses/IBANNameCheckBluemResponse.php b/src/Responses/IBANNameCheckBluemResponse.php index 035a51a..61fa2c1 100644 --- a/src/Responses/IBANNameCheckBluemResponse.php +++ b/src/Responses/IBANNameCheckBluemResponse.php @@ -1,4 +1,5 @@ * diff --git a/src/Responses/IdentityStatusBluemResponse.php b/src/Responses/IdentityStatusBluemResponse.php index ae88b5d..1562a8d 100644 --- a/src/Responses/IdentityStatusBluemResponse.php +++ b/src/Responses/IdentityStatusBluemResponse.php @@ -1,4 +1,5 @@ * diff --git a/src/Responses/IdentityTransactionBluemResponse.php b/src/Responses/IdentityTransactionBluemResponse.php index cca34db..ef89977 100644 --- a/src/Responses/IdentityTransactionBluemResponse.php +++ b/src/Responses/IdentityTransactionBluemResponse.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Responses; class IdentityTransactionBluemResponse extends TransactionBluemResponse diff --git a/src/Responses/MandateStatusBluemResponse.php b/src/Responses/MandateStatusBluemResponse.php index da3816f..170d476 100644 --- a/src/Responses/MandateStatusBluemResponse.php +++ b/src/Responses/MandateStatusBluemResponse.php @@ -1,4 +1,5 @@ * diff --git a/src/Responses/MandateTransactionBluemResponse.php b/src/Responses/MandateTransactionBluemResponse.php index dd9471d..c901cfd 100644 --- a/src/Responses/MandateTransactionBluemResponse.php +++ b/src/Responses/MandateTransactionBluemResponse.php @@ -1,4 +1,5 @@ * diff --git a/src/Responses/PaymentStatusBluemResponse.php b/src/Responses/PaymentStatusBluemResponse.php index c897fee..a091daf 100644 --- a/src/Responses/PaymentStatusBluemResponse.php +++ b/src/Responses/PaymentStatusBluemResponse.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Responses; class PaymentStatusBluemResponse extends StatusBluemResponse @@ -16,6 +17,5 @@ class PaymentStatusBluemResponse extends StatusBluemResponse public static string $response_primary_key = 'PaymentStatus'; - public static ? - string $error_response_type = 'PaymentErrorResponse'; + public static ?string $error_response_type = 'PaymentErrorResponse'; } diff --git a/src/Responses/PaymentTransactionBluemResponse.php b/src/Responses/PaymentTransactionBluemResponse.php index 798607d..c9da787 100644 --- a/src/Responses/PaymentTransactionBluemResponse.php +++ b/src/Responses/PaymentTransactionBluemResponse.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Responses; class PaymentTransactionBluemResponse extends TransactionBluemResponse diff --git a/src/Responses/StatusBluemResponse.php b/src/Responses/StatusBluemResponse.php index fb609a5..0541c37 100644 --- a/src/Responses/StatusBluemResponse.php +++ b/src/Responses/StatusBluemResponse.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Responses; class StatusBluemResponse extends BluemResponse diff --git a/src/Responses/TransactionBluemResponse.php b/src/Responses/TransactionBluemResponse.php index 4392ab2..f4e8eaa 100644 --- a/src/Responses/TransactionBluemResponse.php +++ b/src/Responses/TransactionBluemResponse.php @@ -8,11 +8,11 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Responses; class TransactionBluemResponse extends BluemResponse { - public function GetTransactionURL(): string { return $this->getParentStringVariable("TransactionURL"); diff --git a/src/Validators/BluemConfigurationValidator.php b/src/Validators/BluemConfigurationValidator.php index 364ab29..a058948 100644 --- a/src/Validators/BluemConfigurationValidator.php +++ b/src/Validators/BluemConfigurationValidator.php @@ -1,4 +1,5 @@ * @@ -44,7 +45,6 @@ public function validate($config) $config = $this->_validateEMandateReason($config); $config = $this->_validateLocalInstrumentCode($config); $config = $this->_validateMerchantReturnURLBase($config); - } catch (Throwable $throwable) { $this->errors[] = $throwable->getMessage(); @@ -56,15 +56,16 @@ public function validate($config) private function _validateEnvironment($config) { - if (!isset($config->environment) || !in_array( - $config->environment, - [ - BLUEM_ENVIRONMENT_TESTING, - BLUEM_ENVIRONMENT_ACCEPTANCE, - BLUEM_ENVIRONMENT_PRODUCTION - ], - true - ) + if ( + !isset($config->environment) || !in_array( + $config->environment, + [ + BLUEM_ENVIRONMENT_TESTING, + BLUEM_ENVIRONMENT_ACCEPTANCE, + BLUEM_ENVIRONMENT_PRODUCTION + ], + true + ) ) { throw new Exception( "Invalid environment setting, should be either @@ -103,7 +104,8 @@ private function _validateSenderID($config) private function _validateTest_accessToken($config) { - if ($config->environment === BLUEM_ENVIRONMENT_TESTING + if ( + $config->environment === BLUEM_ENVIRONMENT_TESTING && ( ! isset($config->test_accessToken) || $config->test_accessToken === "" ) ) { @@ -120,7 +122,8 @@ private function _validateProduction_accessToken($config) { // only required if mode is set to PROD // production_accessToken - if ($config->environment === BLUEM_ENVIRONMENT_PRODUCTION + if ( + $config->environment === BLUEM_ENVIRONMENT_PRODUCTION && ( ! isset($config->production_accessToken) || $config->production_accessToken === "" ) ) { @@ -180,7 +183,8 @@ private function _validateThanksPage($config) private function _validateExpectedReturnStatus($config): mixed { if ($config->environment === BLUEM_ENVIRONMENT_TESTING) { - if (! isset($config->expectedReturnStatus) + if ( + ! isset($config->expectedReturnStatus) || ( $config->expectedReturnStatus !== "" && !in_array($config->expectedReturnStatus, $this->getPossibleReturnStatuses(), true)) ) { @@ -219,7 +223,8 @@ private function _validateEMandateReason($config) private function _validateLocalInstrumentCode($config) { - if (! isset($config->localInstrumentCode) + if ( + ! isset($config->localInstrumentCode) || ! in_array( $config->localInstrumentCode, [ 'B2B', 'CORE' ] diff --git a/src/Validators/BluemXMLValidator.php b/src/Validators/BluemXMLValidator.php index 122c460..0f6911e 100644 --- a/src/Validators/BluemXMLValidator.php +++ b/src/Validators/BluemXMLValidator.php @@ -1,4 +1,5 @@ * @@ -75,10 +76,12 @@ private function getKeyFileName(): string if ($this->env === BLUEM_ENVIRONMENT_PRODUCTION && ( ( $current_date === "2025-07-18" && $current_time >= "08:30" ) || $current_date > "2025-07-18")) { $timestamp = '20250717'; // 2025 certificate on testing & acceptance from July 17th, 8:30 CET time - } elseif (($this->env === BLUEM_ENVIRONMENT_TESTING || $this->env === BLUEM_ENVIRONMENT_ACCEPTANCE) - && (($current_date === "2024-07-17" && $current_time >= "06:30") || $current_date > "2024-07-17")) { + } elseif ( + ($this->env === BLUEM_ENVIRONMENT_TESTING || $this->env === BLUEM_ENVIRONMENT_ACCEPTANCE) + && (($current_date === "2024-07-17" && $current_time >= "06:30") || $current_date > "2024-07-17") + ) { $timestamp = '20250717'; - } elseif ( ( $current_date === "2024-07-01" && $current_time >= "12:00" ) || $current_date > "2024-07-01") { + } elseif (( $current_date === "2024-07-01" && $current_time >= "12:00" ) || $current_date > "2024-07-01") { $timestamp = '20240701'; } elseif ($this->env === BLUEM_ENVIRONMENT_TESTING && ( ( $current_date === "2023-06-28" && $current_time >= "08:00" ) || $current_date > "2023-06-28")) { $timestamp = '202306140200-202407050159'; diff --git a/src/Validators/WebhookValidator.php b/src/Validators/WebhookValidator.php index 04b852a..964b011 100644 --- a/src/Validators/WebhookValidator.php +++ b/src/Validators/WebhookValidator.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Validators; abstract class WebhookValidator implements WebhookValidatorInterface diff --git a/src/Validators/WebhookValidatorInterface.php b/src/Validators/WebhookValidatorInterface.php index d5b0796..23b878f 100644 --- a/src/Validators/WebhookValidatorInterface.php +++ b/src/Validators/WebhookValidatorInterface.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Validators; interface WebhookValidatorInterface diff --git a/src/Validators/WebhookXMLValidator.php b/src/Validators/WebhookXMLValidator.php index 5ca6a7d..95e369d 100644 --- a/src/Validators/WebhookXMLValidator.php +++ b/src/Validators/WebhookXMLValidator.php @@ -8,6 +8,7 @@ * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ + namespace Bluem\BluemPHP\Validators; use SimpleXMLElement; diff --git a/src/Validators/WebhookXmlValidation.php b/src/Validators/WebhookXmlValidation.php index 5dea0da..ec18110 100644 --- a/src/Validators/WebhookXmlValidation.php +++ b/src/Validators/WebhookXmlValidation.php @@ -1,4 +1,5 @@ * diff --git a/src/Webhook.php b/src/Webhook.php index 01dd8e6..cd39953 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -1,4 +1,5 @@ * @@ -44,8 +45,7 @@ public function __construct( private function parse($xmlData = ''): void { - if (empty($xmlData)) - { + if (empty($xmlData)) { if (!$this->isHttpsRequest()) { $this->exitWithError(); return; @@ -285,7 +285,7 @@ public function getDebtorAccountName(): ?string return ""; } - return $details->DebtorAccountName."" ?? ""; + return $details->DebtorAccountName . "" ?? ""; } public function getDebtorIBAN(): ?string @@ -295,7 +295,7 @@ public function getDebtorIBAN(): ?string return ""; } - return $details->DebtorIBAN."" ?? ""; + return $details->DebtorIBAN . "" ?? ""; } public function getDebtorBankID(): ?string @@ -305,7 +305,7 @@ public function getDebtorBankID(): ?string return ""; } - return $details->DebtorBankID."" ?? ""; + return $details->DebtorBankID . "" ?? ""; } @@ -360,17 +360,17 @@ public function getAcceptanceReportArray(): array public function getRequestType(): string { - return $this->getPayload()->RequestType .""; + return $this->getPayload()->RequestType . ""; } public function getAuthenticationAuthorityID(): string { - return $this->getPayload()->AuthenticationAuthorityID.''; + return $this->getPayload()->AuthenticationAuthorityID . ''; } public function getAuthenticationAuthorityName(): string { - return $this->getPayload()->AuthenticationAuthorityName.''; + return $this->getPayload()->AuthenticationAuthorityName . ''; } public function getIdentityReportArray(): array @@ -382,29 +382,29 @@ public function getIdentityReportArray(): array } return [ - 'DateTime' => $report->DateTime.'', - 'CustomerIDResponse' => $report->CustomerIDResponse.'', + 'DateTime' => $report->DateTime . '', + 'CustomerIDResponse' => $report->CustomerIDResponse . '', 'NameResponse' => [ - 'Initials'=>$report->NameResponse->Initials. '', - 'LegalLastName'=>$report->NameResponse->LegalLastName. '', - 'LegalLastNamePrefix'=>$report->NameResponse->LegalLastNamePrefix. '', - 'PreferredLastName'=>$report->NameResponse->PreferredLastName. '', - 'PreferredLastNamePrefix'=>$report->NameResponse->PreferredLastNamePrefix. '', - 'PartnerLastName'=>$report->NameResponse->PartnerLastName. '', - 'PartnerLastNamePrefix'=>$report->NameResponse->PartnerLastNamePrefix. '', + 'Initials' => $report->NameResponse->Initials . '', + 'LegalLastName' => $report->NameResponse->LegalLastName . '', + 'LegalLastNamePrefix' => $report->NameResponse->LegalLastNamePrefix . '', + 'PreferredLastName' => $report->NameResponse->PreferredLastName . '', + 'PreferredLastNamePrefix' => $report->NameResponse->PreferredLastNamePrefix . '', + 'PartnerLastName' => $report->NameResponse->PartnerLastName . '', + 'PartnerLastNamePrefix' => $report->NameResponse->PartnerLastNamePrefix . '', ], 'AddressResponse' => [ - 'Street'=>$report->AddressResponse->Street. '', - 'HouseNumber'=>$report->AddressResponse->HouseNumber. '', - 'HouseNumberSuffix'=>$report->AddressResponse->HouseNumberSuffix. '' ?? '', - 'PostalCode'=>$report->AddressResponse->PostalCode. '', - 'City'=>$report->AddressResponse->City. '', - 'CountryCode'=>$report->AddressResponse->CountryCode. '', + 'Street' => $report->AddressResponse->Street . '', + 'HouseNumber' => $report->AddressResponse->HouseNumber . '', + 'HouseNumberSuffix' => $report->AddressResponse->HouseNumberSuffix . '' ?? '', + 'PostalCode' => $report->AddressResponse->PostalCode . '', + 'City' => $report->AddressResponse->City . '', + 'CountryCode' => $report->AddressResponse->CountryCode . '', ], 'BirthDateResponse' => $report->BirthDateResponse . '', 'GenderResponse' => $report->GenderResponse . '', 'TelephoneResponse1' => $report->TelephoneResponse1 . '', - 'EmailResponse' => $report->EmailResponse .'' + 'EmailResponse' => $report->EmailResponse . '' ]; } } diff --git a/tests/Integration/BluemGenericTestCase.php b/tests/Integration/BluemGenericTestCase.php index ec47072..1fb1872 100644 --- a/tests/Integration/BluemGenericTestCase.php +++ b/tests/Integration/BluemGenericTestCase.php @@ -1,4 +1,5 @@ * @@ -31,14 +32,14 @@ abstract class BluemGenericTestCase extends TestCase * * @throws \Exception */ - protected function setUp() : void + protected function setUp(): void { - $env_file =__DIR__. '/../..'; + $env_file = __DIR__ . '/../..'; $dotenv = Dotenv::createImmutable($env_file); $dotenv->load(); // Create a Bluem object and set the Bluem configuration details based on your .env file. - $bluem_config = new stdClass; + $bluem_config = new stdClass(); $bluem_config->environment = $_ENV['BLUEM_ENV']; $bluem_config->senderID = $_ENV['BLUEM_SENDER_ID']; @@ -58,7 +59,7 @@ protected function setUp() : void try { $this->bluem = new Bluem($bluem_config); } catch (\Exception $exception) { - $this->fail("While initializing Bluem, ".$exception->getMessage()." occurred"); + $this->fail("While initializing Bluem, " . $exception->getMessage() . " occurred"); } } @@ -87,7 +88,7 @@ public function testGetConfiguration(): void /** * Perform assertions based on a created BluemPHP Request object */ - protected function _finalizeBluemRequestAssertion(BluemRequestInterface $request) :void + protected function _finalizeBluemRequestAssertion(BluemRequestInterface $request): void { try { // $this->assertEquals($request->getStatus(), "success"); diff --git a/tests/Integration/BluemMainTest.php b/tests/Integration/BluemMainTest.php index 0af55b1..24cd091 100644 --- a/tests/Integration/BluemMainTest.php +++ b/tests/Integration/BluemMainTest.php @@ -1,4 +1,5 @@ Status()) { - $this->assertInstanceOf( MandateStatusBluemResponse::class, $response diff --git a/tests/Integration/IPAPITest.php b/tests/Integration/IPAPITest.php index 82dc781..7602800 100644 --- a/tests/Integration/IPAPITest.php +++ b/tests/Integration/IPAPITest.php @@ -1,4 +1,5 @@ * @@ -38,7 +39,7 @@ public static function NetherlandsIPTestDataProvider(): array { return [ [ - 'ipAddress'=>'31.187.128.0', + 'ipAddress' => '31.187.128.0', '$expectedNetherlands' => true, ] // @todo: add true negative test and check for usage rate limits to prevent false negatives. diff --git a/tests/Integration/IdentityRequestTest.php b/tests/Integration/IdentityRequestTest.php index ca50772..f7421fe 100644 --- a/tests/Integration/IdentityRequestTest.php +++ b/tests/Integration/IdentityRequestTest.php @@ -1,4 +1,5 @@ * @@ -8,7 +9,6 @@ namespace Bluem\BluemPHP\Tests\Integration; - class IdentityRequestTest extends BluemGenericTestCase { public function testCanCreateIdentityRequestWithWeirdCharacters() diff --git a/tests/Integration/MandateRequestTest.php b/tests/Integration/MandateRequestTest.php index 3fe5e91..30a418e 100644 --- a/tests/Integration/MandateRequestTest.php +++ b/tests/Integration/MandateRequestTest.php @@ -12,7 +12,6 @@ use Bluem\BluemPHP\Requests\EmandateBluemRequest; - class MandateRequestTest extends BluemGenericTestCase { public function testCanCreateMandateRequest() @@ -27,7 +26,7 @@ public function testCanCreateMandateRequest() "default" ); } catch (\Exception $exception) { - $this->fail("Exception while creating mandate request: ". $exception->getMessage()); + $this->fail("Exception while creating mandate request: " . $exception->getMessage()); } $this->assertInstanceOf( diff --git a/tests/Integration/PaymentRequestTest.php b/tests/Integration/PaymentRequestTest.php index 834d91d..775e99b 100644 --- a/tests/Integration/PaymentRequestTest.php +++ b/tests/Integration/PaymentRequestTest.php @@ -1,6 +1,6 @@ bluem->CreateEntranceCode(); $this->assertTrue( - (is_string($entranceCode) && $entranceCode!==""), + (is_string($entranceCode) && $entranceCode !== ""), "Valid entranceCode generated" ); } diff --git a/tests/Integration/WebhookTest.php b/tests/Integration/WebhookTest.php index 48f3594..6692c47 100644 --- a/tests/Integration/WebhookTest.php +++ b/tests/Integration/WebhookTest.php @@ -1,4 +1,5 @@ * @@ -8,7 +9,6 @@ namespace Bluem\BluemPHP\Tests\Integration; - class WebhookTest extends BluemGenericTestCase { /** @@ -98,6 +98,6 @@ public function testCanPerformWebhookIdentity() } } - $this->assertEquals('Success', $status, $fileName . ': Status not success: ' . $status); + $this->assertEquals('Success', $status, $fileName . ': Status not success: ' . $status); } } diff --git a/tests/Unit/BluemConfigurationTest.php b/tests/Unit/BluemConfigurationTest.php index a4d7a4f..53d34c6 100644 --- a/tests/Unit/BluemConfigurationTest.php +++ b/tests/Unit/BluemConfigurationTest.php @@ -74,4 +74,3 @@ private function getValidConfig(): stdClass return $bluem_config; } } - diff --git a/tests/Unit/BluemTest.php b/tests/Unit/BluemTest.php index f3dd97c..0725426 100644 --- a/tests/Unit/BluemTest.php +++ b/tests/Unit/BluemTest.php @@ -1,4 +1,5 @@ * @@ -86,7 +87,7 @@ public function testPerformRequestWithInvalidXml(): void // helper classes private function getConfig(): stdClass { - $bluem_config = new stdClass; + $bluem_config = new stdClass(); $bluem_config->environment = 'test'; $bluem_config->senderID = 'S12345'; From c68e9df7249ec799c7ffe4281759043746a6a4d0 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 18:36:48 +0200 Subject: [PATCH 05/24] [UAS-SCOUT] Add linting targets to Makefile for code quality enforcement This commit introduces two new targets in the Makefile: `lint` and `lint_fix`. These targets utilize PHPCS and PHPCBF to enforce PSR-12 coding standards across the codebase, ensuring consistent code quality and formatting. --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index e4bd8d1..4bd3e83 100644 --- a/Makefile +++ b/Makefile @@ -6,3 +6,9 @@ test_integration: @printf 'Running integration tests:\n'; @echo "Note: Ensure you have the necessary environment variables set for integration tests in the .env file." ./vendor/bin/phpunit tests/Integration + +lint: + ./vendor/bin/phpcs --standard=PSR12 --extensions=php --ignore=vendor/ . + +lint_fix: + ./vendor/bin/phpcbf --standard=PSR12 --extensions=php --ignore=vendor/ . From 84c20c77017837c2d2dc4b298a9fca29dad4e289 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 18:40:57 +0200 Subject: [PATCH 06/24] [UAS-SCOUT] Add ownership setup step to CI configuration This change introduces a step in the CI configuration to set up Git ownership for the application directory, ensuring that the CI process can safely manage the repository. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fb1274..977a66c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,9 @@ jobs: - name: Setup .env run: cp .env.example .env + - name: Setup ownership + run: git config --global --add safe.directory /app + - name: Replace placeholders with GitHub Secrets run: | sed -i "s/BLUEM_SENDER_ID=S/BLUEM_SENDER_ID=${{ secrets.BLUEM_SENDER_ID }}/g" .env From aad5b7685e2385ae9cbabc53004c4b0214a09442 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 19:05:21 +0200 Subject: [PATCH 07/24] [UAS-SCOUT] Update CI configuration to use Makefile for PHPCS linting Replaced direct PHPCS command in CI with a Makefile target for better maintainability. Updated PHPCS configuration to use a dedicated ruleset file, allowing for more flexible coding standards enforcement. --- .github/workflows/ci.yml | 2 +- Makefile | 4 ++-- README.md | 2 ++ phpcs.xml.dist | 29 +++++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 phpcs.xml.dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 977a66c..6af5f10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: php_version: ${{ matrix.php-version }} - name: PHPCS Lint - run: vendor/bin/phpcs --standard=PSR12 src tests + run: make lint - name: PHPUnit Tests uses: php-actions/phpunit@v4 diff --git a/Makefile b/Makefile index 4bd3e83..bd8abff 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ test_integration: ./vendor/bin/phpunit tests/Integration lint: - ./vendor/bin/phpcs --standard=PSR12 --extensions=php --ignore=vendor/ . + ./vendor/bin/phpcs --standard=phpcs.xml.dist --extensions=php --ignore=vendor/ . lint_fix: - ./vendor/bin/phpcbf --standard=PSR12 --extensions=php --ignore=vendor/ . + ./vendor/bin/phpcbf --standard=phpcs.xml.dist --extensions=php --ignore=vendor/ . diff --git a/README.md b/README.md index 271d62f..1512ec4 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ No earlier changelog was recorded. Please refer to the [commit log](https://gith ## Testing For improving future features, unit testing is introduced since november 2021. +The repository also has a PHPCS linting setup: `make lint` runs the shared ruleset from `phpcs.xml.dist`, and CI uses the same lint step so local and automated checks stay aligned. That XML file also contains a few narrow exceptions for legacy naming and file-header issues, which lets the existing public API stay intact without failing the build on historical style violations. + Tests are located in the `tests` folder To run tests: ``` diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..6bbcea4 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,29 @@ + + + Project PHPCS rules with narrow exceptions for the legacy public API. + + + + src + tests + examples + rector.php + + vendor/* + + + + + + + + + + + + + + + + + From 51d21909515ca6d8e29860554ceefade25dad551 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 19:08:47 +0200 Subject: [PATCH 08/24] [UAS-SCOUT] Update PHPUnit version and PHP platform configuration - Upgraded PHPUnit to version 11 in CI configuration. - Set PHP platform version to 8.3 in composer.json for compatibility. - Adjusted dependencies in composer.lock to align with new requirements. --- .github/workflows/ci.yml | 2 +- composer.json | 5 +++++ composer.lock | 28 ++++++++++++++++------------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6af5f10..6fda5b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,6 @@ jobs: uses: php-actions/phpunit@v4 with: php_version: ${{ matrix.php-version }} - version: 9.5 + version: 11 bootstrap: ./vendor/autoload.php configuration: ./.github/workflows/phpunit.xml diff --git a/composer.json b/composer.json index ce8dc32..bd42b45 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,11 @@ "support": { "issues": "https://github.com/bluem-development/bluem-php" }, + "config": { + "platform": { + "php": "8.3" + } + }, "require": { "php": ">=8.3", "ext-dom": "*", diff --git a/composer.lock b/composer.lock index f3a1bd5..563a487 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bf2f21ae54543d3cef8f39aa5b643035", + "content-hash": "485d06fa2a6dd9b490a4a98f9f960a2a", "packages": [ { "name": "selective/xmldsig", @@ -110,29 +110,30 @@ }, { "name": "doctrine/instantiator", - "version": "2.1.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", - "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^8.4" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5.58" + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -159,7 +160,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.1.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -175,7 +176,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T06:47:08+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "graham-campbell/result-type", @@ -3124,5 +3125,8 @@ "ext-openssl": "*" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.3" + }, "plugin-api-version": "2.6.0" } From 4195715a5014135046480d68eab043776802d33a Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sat, 4 Apr 2026 19:10:00 +0200 Subject: [PATCH 09/24] [UAS-SCOUT] Update BluemResponse properties to nullable types and adjust CI PHP version Modified the BluemResponse class to use nullable types for response_primary_key and transaction_type properties. Updated the CI configuration to reflect the current PHP version, removing 8.4 from the matrix for now. --- .github/workflows/ci.yml | 3 ++- src/Responses/BluemResponse.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fda5b0..81aa3d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-version: ['8.3', '8.4'] + # add 8.4 soon + php-version: ['8.3'] steps: - name: Checkout code diff --git a/src/Responses/BluemResponse.php b/src/Responses/BluemResponse.php index bda5c5f..bee48f2 100644 --- a/src/Responses/BluemResponse.php +++ b/src/Responses/BluemResponse.php @@ -19,9 +19,9 @@ */ class BluemResponse extends SimpleXMLElement implements BluemResponseInterface { - public static string $response_primary_key = null; + public static ?string $response_primary_key = null; - public static string $transaction_type = null; + public static ?string $transaction_type = null; public static ?string $error_response_type = null; From cfef84cbad13b18dcffa3018c08314f5fa437457 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 08:33:12 +0200 Subject: [PATCH 10/24] [UAS-SCOUT] Update BluemResponse properties to nullable types and adjust CI PHP version Modified the BluemResponse class to use nullable types for response_primary_key and transaction_type properties. Updated the CI configuration to reflect the current PHP version, removing 8.4 from the matrix for now. --- src/Responses/MandateTransactionBluemResponse.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Responses/MandateTransactionBluemResponse.php b/src/Responses/MandateTransactionBluemResponse.php index c901cfd..97c33ed 100644 --- a/src/Responses/MandateTransactionBluemResponse.php +++ b/src/Responses/MandateTransactionBluemResponse.php @@ -11,9 +11,9 @@ class MandateTransactionBluemResponse extends TransactionBluemResponse { - public static string $transaction_type = "EMandate"; + public static ?string $transaction_type = "EMandate"; - public static string $response_primary_key = 'EMandateTransaction'; + public static ?string $response_primary_key = 'EMandateTransaction'; public static ?string $error_response_type = 'EMandateErrorResponse'; From 9900833173b4c2fcf23f3d92d702424a9afed1f9 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 08:33:52 +0200 Subject: [PATCH 11/24] [UAS-SCOUT] Add Git hooks setup and pre-commit script for PHP file checks This commit introduces a new Makefile target for setting up Git hooks and adds a pre-commit script that runs unit tests when PHP files are modified. This ensures that tests are executed automatically, improving code quality and reducing the chances of introducing errors. --- .githooks/pre-commit | 9 +++++++++ Makefile | 6 ++++++ 2 files changed, 15 insertions(+) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..916f01e --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +if git diff --cached --name-only --diff-filter=ACM | grep -qE '\.php$'; then + echo "PHP files changed — running unit tests..." + make test_unit +else + echo "No PHP changes — skipping unit tests." +fi diff --git a/Makefile b/Makefile index bd8abff..7adef21 100644 --- a/Makefile +++ b/Makefile @@ -12,3 +12,9 @@ lint: lint_fix: ./vendor/bin/phpcbf --standard=phpcs.xml.dist --extensions=php --ignore=vendor/ . + + +setup-git-hooks: + @echo "Setting up Git hooks..." + git config core.hooksPath .githooks + chmod +x .githooks/pre-commit From fda3d98c8b8719eb95858e65d4d861e0fa56d676 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 08:45:50 +0200 Subject: [PATCH 12/24] [UAS-SCOUT] Update BluemResponse classes to use nullable types for properties Refactor properties in various BluemResponse classes to nullable types to enhance type safety and prevent potential null reference errors. This change ensures that the application can handle cases where these properties may not be set, improving overall robustness. --- src/Requests/EmandateBluemRequest.php | 36 +++++++++---------- src/Requests/IdentityBluemRequest.php | 34 +++++++++--------- src/Requests/PaymentBluemRequest.php | 8 ++--- src/Responses/IBANNameCheckBluemResponse.php | 4 +-- src/Responses/IdentityStatusBluemResponse.php | 4 +-- .../IdentityTransactionBluemResponse.php | 4 +-- src/Responses/MandateStatusBluemResponse.php | 4 +-- src/Responses/PaymentStatusBluemResponse.php | 4 +-- .../PaymentTransactionBluemResponse.php | 4 +-- 9 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/Requests/EmandateBluemRequest.php b/src/Requests/EmandateBluemRequest.php index 064f3cc..5b5ec8a 100644 --- a/src/Requests/EmandateBluemRequest.php +++ b/src/Requests/EmandateBluemRequest.php @@ -161,30 +161,30 @@ public function TransactionType(): string private function XmlWrapDebtorWalletForPaymentMethod(): string { - $res = ''; - - if ($this->context->isMandate()) { - $bic = ''; + if (!$this->context->isMandate()) { + return ''; + } - if (empty($this->context->getPaymentDetail('BIC'))) { - if (!empty($this->debtorWallet)) { - $bic = $this->debtorWallet; - } + if (empty($this->context->getPaymentDetail('BIC'))) { + if (!empty($this->debtorWallet)) { + $bic = $this->debtorWallet; } else { - $bic = $this->context->getPaymentDetail('BIC'); + $bic = ''; } + } else { + $bic = $this->context->getPaymentDetail('BIC'); + } - if (empty($bic)) { - return ''; - } + if (empty($bic)) { + return ''; + } - $res = PHP_EOL . "" . PHP_EOL; - $res .= sprintf('<%s>', $this->context->debtorWalletElementName); - $res .= "" . $bic . ""; - $res .= sprintf('', $this->context->debtorWalletElementName) . PHP_EOL; + $res = PHP_EOL . "" . PHP_EOL; + $res .= sprintf('<%s>', $this->context->debtorWalletElementName); + $res .= "" . $bic . ""; + $res .= sprintf('', $this->context->debtorWalletElementName) . PHP_EOL; - return $res . ("" . PHP_EOL); - } + return $res . ("" . PHP_EOL); } /** diff --git a/src/Requests/IdentityBluemRequest.php b/src/Requests/IdentityBluemRequest.php index 583a983..e49b9e9 100644 --- a/src/Requests/IdentityBluemRequest.php +++ b/src/Requests/IdentityBluemRequest.php @@ -190,30 +190,30 @@ public function enableStatusGUI() private function XmlWrapDebtorWalletForPaymentMethod(): string { - $res = ''; - if ($this->context->isIDIN()) { - $bic = ''; + return ''; + } - if (empty($this->context->getPaymentDetail('BIC'))) { - if (!empty($this->debtorWallet)) { - $bic = $this->debtorWallet; - } + if (empty($this->context->getPaymentDetail('BIC'))) { + if (!empty($this->debtorWallet)) { + $bic = $this->debtorWallet; } else { - $bic = $this->context->getPaymentDetail('BIC'); + $bic = ''; } + } else { + $bic = $this->context->getPaymentDetail('BIC'); + } - if (empty($bic)) { - return ''; - } + if (empty($bic)) { + return ''; + } - $res = PHP_EOL . "" . PHP_EOL; - $res .= sprintf('<%s>', $this->context->debtorWalletElementName); - $res .= "" . $bic . ""; - $res .= sprintf('', $this->context->debtorWalletElementName) . PHP_EOL; + $res = PHP_EOL . "" . PHP_EOL; + $res .= sprintf('<%s>', $this->context->debtorWalletElementName); + $res .= "" . $bic . ""; + $res .= sprintf('', $this->context->debtorWalletElementName) . PHP_EOL; - return $res . ("" . PHP_EOL); - } + return $res . ("" . PHP_EOL); } /** diff --git a/src/Requests/PaymentBluemRequest.php b/src/Requests/PaymentBluemRequest.php index 728331e..79e10bf 100644 --- a/src/Requests/PaymentBluemRequest.php +++ b/src/Requests/PaymentBluemRequest.php @@ -244,8 +244,8 @@ public function setPaymentMethodToCreditCard( * Prepared for future use. */ if ( - $cardNumber !== '' && $cardNumber !== '0' || $name !== '' && $name !== '0' || $securityCode !== '' && $securityCode !== '0' - || $expirationDateMonth !== '' && $expirationDateMonth !== '0' || $expirationDateYear !== '' && $expirationDateYear !== '0' + !empty($cardNumber) || !empty($name) || !empty($securityCode) + || !empty($expirationDateMonth) || !empty($expirationDateYear) ) { $this->context->addPaymentMethodDetails( [ @@ -303,11 +303,7 @@ public function getDueDateTime(mixed $dueDateTime): string private function XmlWrapDebtorWalletForPaymentMethod(): string { - $res = ''; - if ($this->context->isIDEAL()) { - $bic = ''; - if (empty($this->context->getPaymentDetail('BIC'))) { if (!empty($this->debtorWallet)) { $bic = $this->debtorWallet; diff --git a/src/Responses/IBANNameCheckBluemResponse.php b/src/Responses/IBANNameCheckBluemResponse.php index 61fa2c1..22bfe26 100644 --- a/src/Responses/IBANNameCheckBluemResponse.php +++ b/src/Responses/IBANNameCheckBluemResponse.php @@ -13,9 +13,9 @@ class IBANNameCheckBluemResponse extends TransactionBluemResponse { - public static string $transaction_type = "IBANCheckTransactionResponse"; + public static ?string $transaction_type = "IBANCheckTransactionResponse"; - public static string $response_primary_key = "IBANCheckTransaction"; + public static ?string $response_primary_key = "IBANCheckTransaction"; public static ?string $error_response_type = "IBANCheckResult"; diff --git a/src/Responses/IdentityStatusBluemResponse.php b/src/Responses/IdentityStatusBluemResponse.php index 1562a8d..82f2283 100644 --- a/src/Responses/IdentityStatusBluemResponse.php +++ b/src/Responses/IdentityStatusBluemResponse.php @@ -11,9 +11,9 @@ class IdentityStatusBluemResponse extends StatusBluemResponse { - public static string $transaction_type = "Identity"; + public static ?string $transaction_type = "Identity"; - public static string $response_primary_key = 'IdentityStatus'; + public static ?string $response_primary_key = 'IdentityStatus'; public static ?string $error_response_type = 'IdentityErrorResponse'; diff --git a/src/Responses/IdentityTransactionBluemResponse.php b/src/Responses/IdentityTransactionBluemResponse.php index ef89977..80b9f06 100644 --- a/src/Responses/IdentityTransactionBluemResponse.php +++ b/src/Responses/IdentityTransactionBluemResponse.php @@ -13,9 +13,9 @@ class IdentityTransactionBluemResponse extends TransactionBluemResponse { - public static string $transaction_type = "Identity"; + public static ?string $transaction_type = "Identity"; - public static string $response_primary_key = 'IdentityTransaction'; + public static ?string $response_primary_key = 'IdentityTransaction'; public static ?string $error_response_type = 'IdentityErrorResponse'; } diff --git a/src/Responses/MandateStatusBluemResponse.php b/src/Responses/MandateStatusBluemResponse.php index 170d476..8095c86 100644 --- a/src/Responses/MandateStatusBluemResponse.php +++ b/src/Responses/MandateStatusBluemResponse.php @@ -17,9 +17,9 @@ class MandateStatusBluemResponse extends StatusBluemResponse { - public static string $transaction_type = "EMandate"; + public static ?string $transaction_type = "EMandate"; - public static string $response_primary_key = 'EMandateStatus'; + public static ?string $response_primary_key = 'EMandateStatus'; public static ?string $error_response_type = 'EMandateErrorResponse'; diff --git a/src/Responses/PaymentStatusBluemResponse.php b/src/Responses/PaymentStatusBluemResponse.php index a091daf..ff5d835 100644 --- a/src/Responses/PaymentStatusBluemResponse.php +++ b/src/Responses/PaymentStatusBluemResponse.php @@ -13,9 +13,9 @@ class PaymentStatusBluemResponse extends StatusBluemResponse { - public static string $transaction_type = "Payment"; + public static ?string $transaction_type = "Payment"; - public static string $response_primary_key = 'PaymentStatus'; + public static ?string $response_primary_key = 'PaymentStatus'; public static ?string $error_response_type = 'PaymentErrorResponse'; } diff --git a/src/Responses/PaymentTransactionBluemResponse.php b/src/Responses/PaymentTransactionBluemResponse.php index c9da787..8543e6f 100644 --- a/src/Responses/PaymentTransactionBluemResponse.php +++ b/src/Responses/PaymentTransactionBluemResponse.php @@ -13,9 +13,9 @@ class PaymentTransactionBluemResponse extends TransactionBluemResponse { - public static string $transaction_type = "Payment"; + public static ?string $transaction_type = "Payment"; - public static string $response_primary_key = 'PaymentTransaction'; + public static ?string $response_primary_key = 'PaymentTransaction'; public static ?string $error_response_type = 'PaymentErrorResponse'; } From 563b9fe49a23a700f2499f7e2fbef67b1895259e Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 09:32:56 +0200 Subject: [PATCH 13/24] [UAS-SCOUT] Refactor BluemConfigurationValidator to use Constants for environment and return status values - Introduced a new Constants class to centralize environment and expected return status definitions. - Updated BluemConfigurationValidator to utilize these constants, enhancing code readability and maintainability. --- src/Constants.php | 40 ++++++++++++++++ .../BluemConfigurationValidator.php | 48 +++++++------------ 2 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 src/Constants.php diff --git a/src/Constants.php b/src/Constants.php new file mode 100644 index 0000000..079ac99 --- /dev/null +++ b/src/Constants.php @@ -0,0 +1,40 @@ +environment) || !in_array( $config->environment, - [ - BLUEM_ENVIRONMENT_TESTING, - BLUEM_ENVIRONMENT_ACCEPTANCE, - BLUEM_ENVIRONMENT_PRODUCTION - ], + Constants::ENVIRONMENTS, true ) ) { throw new Exception( - "Invalid environment setting, should be either - 'test', 'acc' or 'prod'" + sprintf(sprintf("Invalid environment setting (%s), should be one of: + %%s", $config->environment), implode(', ', Constants::ENVIRONMENTS)) ); } @@ -105,7 +93,7 @@ private function _validateSenderID($config) private function _validateTest_accessToken($config) { if ( - $config->environment === BLUEM_ENVIRONMENT_TESTING + $config->environment === Constants::TESTING_ENVIRONMENT && ( ! isset($config->test_accessToken) || $config->test_accessToken === "" ) ) { @@ -123,7 +111,7 @@ private function _validateProduction_accessToken($config) // only required if mode is set to PROD // production_accessToken if ( - $config->environment === BLUEM_ENVIRONMENT_PRODUCTION + $config->environment === Constants::PRODUCTION_ENVIRONMENT && ( ! isset($config->production_accessToken) || $config->production_accessToken === "" ) ) { @@ -152,16 +140,16 @@ private function _validateMerchantIDAndSelectAccessToken($config) $config->merchantId = ""; } - if ($config->environment === BLUEM_ENVIRONMENT_PRODUCTION) { + if ($config->environment === Constants::PRODUCTION_ENVIRONMENT) { $config->accessToken = $config->production_accessToken; // @todo consider throwing an exception if these tokens are missing. - } elseif ($config->environment === BLUEM_ENVIRONMENT_TESTING) { + } elseif ($config->environment === Constants::TESTING_ENVIRONMENT) { $config->accessToken = $config->test_accessToken; // @todo consider throwing an exception if these tokens are missing. // hardcoded merchantID in case of test. // It is always the bluem merchant ID then. - $config->merchantID = BLUEM_STATIC_MERCHANT_ID; + $config->merchantID = Constants::BLUEM_STATIC_MERCHANT_ID; } return $config; @@ -182,14 +170,14 @@ private function _validateThanksPage($config) */ private function _validateExpectedReturnStatus($config): mixed { - if ($config->environment === BLUEM_ENVIRONMENT_TESTING) { + if ($config->environment === Constants::TESTING_ENVIRONMENT) { if ( ! isset($config->expectedReturnStatus) || ( $config->expectedReturnStatus !== "" && !in_array($config->expectedReturnStatus, $this->getPossibleReturnStatuses(), true)) ) { // default back to success - $config->expectedReturnStatus = BLUEM_EXPECTED_RETURN_SUCCESS; + $config->expectedReturnStatus = Constants::EXPECTED_RETURN_SUCCESS; } } else { // no need for expectedReturnStatus when in production @@ -205,13 +193,13 @@ private function _validateExpectedReturnStatus($config): mixed private function getPossibleReturnStatuses(): array { return [ - BLUEM_EXPECTED_RETURN_NONE, - BLUEM_EXPECTED_RETURN_SUCCESS, - BLUEM_EXPECTED_RETURN_CANCELLED, - BLUEM_EXPECTED_RETURN_EXPIRED, - BLUEM_EXPECTED_RETURN_FAILURE, - BLUEM_EXPECTED_RETURN_OPEN, - BLUEM_EXPECTED_RETURN_PENDING + Constants::EXPECTED_RETURN_NONE, + Constants::EXPECTED_RETURN_SUCCESS, + Constants::EXPECTED_RETURN_CANCELLED, + Constants::EXPECTED_RETURN_EXPIRED, + Constants::EXPECTED_RETURN_FAILURE, + Constants::EXPECTED_RETURN_OPEN, + Constants::EXPECTED_RETURN_PENDING ]; } From f40f7ec4415fcd4d0e9b026c842acc29162e9d16 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 09:33:12 +0200 Subject: [PATCH 14/24] [UAS-SCOUT] Add acceptance test suite to PHPUnit configuration and Makefile - Introduced a new acceptance test suite in phpunit.xml to organize acceptance tests. - Updated Makefile to include a command for running acceptance tests with detailed output options. --- Makefile | 4 ++++ phpunit.xml | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7adef21..1800eb0 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,10 @@ test_integration: @echo "Note: Ensure you have the necessary environment variables set for integration tests in the .env file." ./vendor/bin/phpunit tests/Integration +test_acceptance: + @printf 'Running acceptance tests:\n'; + ./vendor/bin/phpunit tests/Acceptance --testdox --display-errors --display-warnings --display-deprecations --display-phpunit-deprecations --display-notices + lint: ./vendor/bin/phpcs --standard=phpcs.xml.dist --extensions=php --ignore=vendor/ . diff --git a/phpunit.xml b/phpunit.xml index db121f5..c34213f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,5 @@ - + ./tests/Unit @@ -7,5 +7,8 @@ ./tests/Integration + + ./tests/Acceptance + From d923955c1613e3f6febbdd14d08efc02e7f6de77 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 09:33:21 +0200 Subject: [PATCH 15/24] [UAS-SCOUT] Enhance error handling in BluemMainTest and fix expected key in IPAPITest - Added a check for ErrorBluemResponse in BluemMainTest to ensure proper error handling during mandate creation. - Corrected the key name from '$expectedNetherlands' to 'expectedNetherlands' in IPAPITest for accurate test data. These changes improve the robustness of the tests and ensure they reflect the expected structure of the response. --- tests/Integration/BluemMainTest.php | 6 ++++++ tests/Integration/IPAPITest.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Integration/BluemMainTest.php b/tests/Integration/BluemMainTest.php index 24cd091..777f2de 100644 --- a/tests/Integration/BluemMainTest.php +++ b/tests/Integration/BluemMainTest.php @@ -53,6 +53,12 @@ public function testCanMandate(): void $this->fail("Could not create mandate: " . $exception->getMessage()); } + if($response instanceof ErrorBluemResponse) { + $this->fail( + 'Got ErrorBluem response: '. $response->Error() + ); + } + $this->assertInstanceOf( MandateTransactionBluemResponse::class, $response diff --git a/tests/Integration/IPAPITest.php b/tests/Integration/IPAPITest.php index 7602800..266cd8c 100644 --- a/tests/Integration/IPAPITest.php +++ b/tests/Integration/IPAPITest.php @@ -40,7 +40,7 @@ public static function NetherlandsIPTestDataProvider(): array return [ [ 'ipAddress' => '31.187.128.0', - '$expectedNetherlands' => true, + 'expectedNetherlands' => true, ] // @todo: add true negative test and check for usage rate limits to prevent false negatives. ]; From d8d342f36a4b1d88bc64adbb0d3e85d73a8bd95a Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 09:40:40 +0200 Subject: [PATCH 16/24] [UAS-SCOUT] Refactor BluemRequest and EmandateBluemRequest to utilize Constants for environment checks - Updated environment checks in BluemRequest and EmandateBluemRequest to use Constants for better maintainability. - Introduced new AcceptanceTestCase and related tests for IBAN and Identity requests to ensure XML structure and response handling. - Added PaymentsAcceptanceTest to validate payment request and response XML structures. Testing included verifying XML generation and response parsing for various scenarios. --- src/Requests/BluemRequest.php | 3 +- src/Requests/EmandateBluemRequest.php | 5 +- src/Responses/IBANNameCheckBluemResponse.php | 2 +- tests/Acceptance/AcceptanceTestCase.php | 70 ++++++++++ tests/Acceptance/IbanCheckAcceptanceTest.php | 100 ++++++++++++++ tests/Acceptance/IdentityAcceptanceTest.php | 86 ++++++++++++ tests/Acceptance/MandatesAcceptanceTest.php | 98 ++++++++++++++ tests/Acceptance/PaymentsAcceptanceTest.php | 132 +++++++++++++++++++ tests/Integration/BluemMainTest.php | 4 +- 9 files changed, 494 insertions(+), 6 deletions(-) create mode 100644 tests/Acceptance/AcceptanceTestCase.php create mode 100644 tests/Acceptance/IbanCheckAcceptanceTest.php create mode 100644 tests/Acceptance/IdentityAcceptanceTest.php create mode 100644 tests/Acceptance/MandatesAcceptanceTest.php create mode 100644 tests/Acceptance/PaymentsAcceptanceTest.php diff --git a/src/Requests/BluemRequest.php b/src/Requests/BluemRequest.php index 9f7f00b..e801e7c 100644 --- a/src/Requests/BluemRequest.php +++ b/src/Requests/BluemRequest.php @@ -9,6 +9,7 @@ namespace Bluem\BluemPHP\Requests; +use Bluem\BluemPHP\Constants; use Bluem\BluemPHP\Exceptions\InvalidBluemRequestException; use Bluem\BluemPHP\Helpers\BluemConfiguration; use Bluem\BluemPHP\Helpers\Now; @@ -159,7 +160,7 @@ private function entranceCode(string $expectedReturn = 'none'): string $entranceCode = (new Now())->format("YmdHisv"); $prefix = ""; - if ($this->environment === BLUEM_ENVIRONMENT_TESTING) { + if ($this->environment === Constants::TESTING_ENVIRONMENT) { switch ($expectedReturn) { case 'success': $prefix = "HIO100OIH"; diff --git a/src/Requests/EmandateBluemRequest.php b/src/Requests/EmandateBluemRequest.php index 5b5ec8a..fc6d673 100644 --- a/src/Requests/EmandateBluemRequest.php +++ b/src/Requests/EmandateBluemRequest.php @@ -9,6 +9,7 @@ namespace Bluem\BluemPHP\Requests; +use Bluem\BluemPHP\Constants; use Bluem\BluemPHP\Contexts\MandatesContext; use Bluem\BluemPHP\Helpers\BluemConfiguration; @@ -90,8 +91,8 @@ public function __construct(BluemConfiguration $config, private $debtorReference $this->merchantID = $config->merchantID ?? ""; // override with hardcoded merchantID when in test environment, according to documentation - if ($this->environment === BLUEM_ENVIRONMENT_TESTING) { - $this->merchantID = "0020000387"; + if ($this->environment === Constants::TESTING_ENVIRONMENT) { + $this->merchantID = Constants::BLUEM_STATIC_MERCHANT_ID; } $this->merchantSubID = $config->merchantSubID ?? "0"; diff --git a/src/Responses/IBANNameCheckBluemResponse.php b/src/Responses/IBANNameCheckBluemResponse.php index 22bfe26..65f91af 100644 --- a/src/Responses/IBANNameCheckBluemResponse.php +++ b/src/Responses/IBANNameCheckBluemResponse.php @@ -30,7 +30,7 @@ private function getIBANCheckResultObject($parentObjectKey = "IBANCheckResult"): return null; } - private function getKeyFromIBANCheckResult(string $key, string $parentObjectKey = null): string + private function getKeyFromIBANCheckResult(string $key, ?string $parentObjectKey = null): string { $result = $this->getIBANCheckResultObject($parentObjectKey); diff --git a/tests/Acceptance/AcceptanceTestCase.php b/tests/Acceptance/AcceptanceTestCase.php new file mode 100644 index 0000000..96495ca --- /dev/null +++ b/tests/Acceptance/AcceptanceTestCase.php @@ -0,0 +1,70 @@ + 'test', + 'senderID' => 'S001', + 'test_accessToken' => 'INSERT_TEST_ACCESS_TOKEN_HERE', + 'production_accessToken' => '', + 'brandID' => $brandId, + 'merchantID' => 'INSERT_MERCHANT_ID_HERE', + 'merchantReturnURLBase' => 'https://example.test/return', + 'expectedReturnStatus' => 'success', + 'eMandateReason' => 'INSERT_EMANDATE_REASON_HERE', + 'localInstrumentCode' => 'CORE', + ], + $overrides + ); + + return new BluemConfiguration((object) $config); + } + + /** + * Load a SimpleXML fixture as one of Bluem's response subclasses. + */ + protected function loadXmlResponse(string $xml, string $className): SimpleXMLElement + { + $response = simplexml_load_string($xml, $className); + + if (!$response instanceof SimpleXMLElement) { + throw new RuntimeException('Unable to parse XML fixture for ' . $className); + } + + return $response; + } + + /** + * Assert that a string contains all expected fragments. + */ + protected function assertXmlContains(string $xml, string ...$fragments): void + { + foreach ($fragments as $fragment) { + self::assertStringContainsString($fragment, $xml); + } + } +} diff --git a/tests/Acceptance/IbanCheckAcceptanceTest.php b/tests/Acceptance/IbanCheckAcceptanceTest.php new file mode 100644 index 0000000..20e1f9d --- /dev/null +++ b/tests/Acceptance/IbanCheckAcceptanceTest.php @@ -0,0 +1,100 @@ +createConfiguration('S001Payment'); + + $request = new IBANBluemRequest( + $config, + 'IBAN-ENTRANCE-123', + 'NL66 ABNA 4097 0124 28', + 'D.J.M. Daan Jeroen Maarten Quackernaat ', + '1234' + ); + + $xml = $request->XmlString(); + + $this->assertXmlContains( + $xml, + 'NL66ABNA4097012428', + 'D.J.M. Daan Jeroen Maarten Quackernaat', + '1234' + ); + } + + public function testIbanResponseKnownResultExposesAllFields(): void + { + $xml = <<<'XML' + + + + + KNOWN + MATCH + D.J.M. Daan Jeroen Maarten Quackernaat + OPEN + + + PERSONAL + false + 1 + Netherlands + + + +XML; + + $response = $this->loadXmlResponse($xml, IBANNameCheckBluemResponse::class); + + self::assertSame('KNOWN', $response->GetIBANResult()); + self::assertSame('MATCH', $response->GetNameResult()); + self::assertSame('D.J.M. Daan Jeroen Maarten Quackernaat', $response->GetSuggestedName()); + self::assertSame('OPEN', $response->GetAccountStatus()); + self::assertSame('PERSONAL', $response->GetAccountType()); + self::assertSame('false', $response->GetIsJointAccount()); + self::assertSame('1', $response->GetNumberOfAccountHolders()); + self::assertSame('Netherlands', $response->GetCountryName()); + } + + public function testIbanResponseInvalidAndUnavailableStatesRemainParsable(): void + { + $invalidXml = <<<'XML' + + + + + INVALID + + + +XML; + + $unavailableXml = <<<'XML' + + + + + SERVICE_TEMPORARILY_NOT_AVAILABLE + + + +XML; + + $invalidResponse = $this->loadXmlResponse($invalidXml, IBANNameCheckBluemResponse::class); + $unavailableResponse = $this->loadXmlResponse($unavailableXml, IBANNameCheckBluemResponse::class); + + self::assertSame('INVALID', $invalidResponse->GetIBANResult()); + self::assertSame('SERVICE_TEMPORARILY_NOT_AVAILABLE', $unavailableResponse->GetIBANResult()); + } +} diff --git a/tests/Acceptance/IdentityAcceptanceTest.php b/tests/Acceptance/IdentityAcceptanceTest.php new file mode 100644 index 0000000..7a45523 --- /dev/null +++ b/tests/Acceptance/IdentityAcceptanceTest.php @@ -0,0 +1,86 @@ +createConfiguration('BluemIdentity'); + + $request = new IdentityBluemRequest( + $config, + 'showConsumerGuiMYOWNENTRANCECODE77128', + 'success', + ['AddressRequest', 'BirthDateRequest'], + 'Beschrijving', + '1234', + 'http://localhost/code/etc/' + ); + + $request->selectDebtorWallet('INGBNL2A'); + $request->enableStatusGUI(); + + $xml = $request->XmlString(); + + $this->assertXmlContains( + $xml, + '', + '', + 'Beschrijving', + '1234', + 'http://localhost/code/etc/?debtorReference=1234', + ); + } + + public function testIdentityTransactionResponseReadsTransactionData(): void + { + $xml = <<<'XML' + + + + https://test.viamijnbank.net/identity/transaction/1234abcdef + 1234abcdef + 1234 + + +XML; + + $response = $this->loadXmlResponse($xml, IdentityTransactionBluemResponse::class); + + self::assertSame('https://test.viamijnbank.net/identity/transaction/1234abcdef', $response->GetTransactionURL()); + self::assertSame('1234abcdef', $response->GetTransactionID()); + self::assertSame('1234', $response->GetDebtorReference()); + } + + public function testIdentityStatusResponseReadsIdentityReport(): void + { + $xml = <<<'XML' + + + + AUTH-001 + Success + + Verified + INSERT_VARIABLE_CUSTOMER_NAME_HERE + + + +XML; + + $response = $this->loadXmlResponse($xml, IdentityStatusBluemResponse::class); + + self::assertSame('AUTH-001', $response->GetAuthenticationAuthorityID()); + self::assertNotNull($response->GetIdentityReport()); + self::assertSame('Verified', (string) $response->GetIdentityReport()->ReportStatus); + } +} diff --git a/tests/Acceptance/MandatesAcceptanceTest.php b/tests/Acceptance/MandatesAcceptanceTest.php new file mode 100644 index 0000000..c90e812 --- /dev/null +++ b/tests/Acceptance/MandatesAcceptanceTest.php @@ -0,0 +1,98 @@ +createConfiguration('BluemMandate', [ + 'localInstrumentCode' => 'CORE', + 'merchantReturnURLBase' => 'https://example.test/return', + ]); + + $request = new EmandateBluemRequest( + $config, + '56789', + '1234', + '134426345', + 'success' + ); + + $request->addAdditionalData('CustomerName', 'INSERT_VARIABLE_CUSTOMER_NAME_HERE'); + $request->selectDebtorWallet('INGBNL2A'); + $request->setBrandId('BluemMandate'); + + $xml = $request->XmlString(); + + $this->assertXmlContains( + $xml, + 'https://example.test/return?mandateID=134426345', + 'RCUR', + 'INSERT_EMANDATE_REASON_HERE', + '56789', + '56789-1234', + 'INSERT_VARIABLE_CUSTOMER_NAME_HERE', + '', + '', + 'INGBNL2A' + ); + } + + public function testMandateTransactionResponseReadsTransactionData(): void + { + $xml = <<<'XML' + + + + https://test.viamijnbank.net/mandate/transaction/134426345 + MANDATE-TX-123 + 134426345 + + +XML; + + $response = $this->loadXmlResponse($xml, MandateTransactionBluemResponse::class); + + self::assertSame('https://test.viamijnbank.net/mandate/transaction/134426345', $response->GetTransactionURL()); + self::assertSame('MANDATE-TX-123', $response->GetTransactionID()); + self::assertSame('134426345', $response->GetMandateID()); + } + + public function testMandateStatusResponseReadsAcceptanceReportData(): void + { + $xml = <<<'XML' + + + + + + NL66ABNA4097012428 + ABNANL2A + D.J.M. Daan Jeroen Maarten Quackernaat + 250.00 + + + + +XML; + + $response = $this->loadXmlResponse($xml, MandateStatusBluemResponse::class); + + self::assertSame('NL66ABNA4097012428', $response->GetDebtorIBAN()); + self::assertSame('ABNANL2A', $response->GetDebtorBankID()); + self::assertSame('D.J.M. Daan Jeroen Maarten Quackernaat', $response->GetDebtorAccountName()); + self::assertSame(250.00, $response->GetMaximumAmount()->amount); + self::assertSame('EUR', $response->GetMaximumAmount()->currency->code); + } +} diff --git a/tests/Acceptance/PaymentsAcceptanceTest.php b/tests/Acceptance/PaymentsAcceptanceTest.php new file mode 100644 index 0000000..68b0b26 --- /dev/null +++ b/tests/Acceptance/PaymentsAcceptanceTest.php @@ -0,0 +1,132 @@ +createConfiguration('S001Payment', [ + 'merchantReturnURLBase' => 'http://localhost:8000/?a=callback', + ]); + + $request = new PaymentBluemRequest( + $config, + 'Beschrijving', + '1234', + 12.34, + '2026-04-12', + 'EUR', + 'TRANS123', + 'PAYMENT-ENTRANCE-123' + ); + + $request->selectDebtorWallet('INGBNL2A'); + $request->setPaymentMethodToBancontact(); + + $xml = $request->XmlString(); + + $this->assertXmlContains( + $xml, + 'Beschrijving', + '1234', + 'EUR', + '12.34', + '2026-04-12T00:00:00.000Z', + 'http://localhost:8000/?a=callback?entranceCode=PAYMENT-ENTRANCE-123&transactionID=TRANS123', + '', + '', + ); + } + + public function testPaymentRequestSupportsCreditCardMethodBranch(): void + { + $config = $this->createConfiguration('S001Payment'); + + $request = new PaymentBluemRequest( + $config, + 'Beschrijving', + '1234', + 12.34, + '2026-04-12', + 'EUR', + 'TRANS456', + 'PAYMENT-ENTRANCE-456' + ); + + $request->setPaymentMethodToCreditCard( + '1234000012340000', + 'John Doe', + '123', + '03', + '2025' + ); + + self::assertTrue($request->getContext()->isCreditCard()); + + $xml = $request->XmlString(); + $this->assertXmlContains( + $xml, + '', + '1234000012340000', + 'John Doe', + '123', + '', + '03', + '2025' + ); + } + + public function testPaymentStatusResponseReadsStatusCode(): void + { + $xml = <<<'XML' + + + + 2026-04-05T00:00:00Z + 1234134426345ae + 1234 + 134426345ae + Success + 12.34 + 12.34 + EUR + IDEAL + + +XML; + + $response = $this->loadXmlResponse($xml, PaymentStatusBluemResponse::class); + + self::assertTrue($response->Status()); + self::assertSame('Success', $response->GetStatusCode()); + } + + public function testPaymentTransactionResponseReadsTransactionData(): void + { + $xml = <<<'XML' + + + + https://test.viamijnbank.net/payment/transaction/TRANS123 + TRANS123 + 1234 + + +XML; + + $response = $this->loadXmlResponse($xml, PaymentTransactionBluemResponse::class); + + self::assertSame('https://test.viamijnbank.net/payment/transaction/TRANS123', $response->GetTransactionURL()); + self::assertSame('TRANS123', $response->GetTransactionID()); + self::assertSame('1234', $response->GetDebtorReference()); + } +} diff --git a/tests/Integration/BluemMainTest.php b/tests/Integration/BluemMainTest.php index 777f2de..40f463c 100644 --- a/tests/Integration/BluemMainTest.php +++ b/tests/Integration/BluemMainTest.php @@ -53,9 +53,9 @@ public function testCanMandate(): void $this->fail("Could not create mandate: " . $exception->getMessage()); } - if($response instanceof ErrorBluemResponse) { + if ($response instanceof ErrorBluemResponse) { $this->fail( - 'Got ErrorBluem response: '. $response->Error() + 'Got ErrorBluem response: ' . $response->Error() ); } From d35c33425d690d00368df5277860c5437f08068b Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 09:58:01 +0200 Subject: [PATCH 17/24] [UAS-SCOUT] Update PHPUnit configuration to include separate test suites for Unit, Integration, and Acceptance tests - Adjusted phpunit.xml to define distinct test suites for better organization and clarity. - Enhanced Makefile to facilitate running all tests in one command, improving developer efficiency. --- .github/workflows/phpunit.xml | 8 +++++++- Makefile | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpunit.xml b/.github/workflows/phpunit.xml index bc8631b..957b9a4 100644 --- a/.github/workflows/phpunit.xml +++ b/.github/workflows/phpunit.xml @@ -7,7 +7,13 @@ > - ../../tests + ./tests/Unit + + + ./tests/Integration + + + ./tests/Acceptance diff --git a/Makefile b/Makefile index 1800eb0..835842f 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,18 @@ test_unit: test_integration: @printf 'Running integration tests:\n'; @echo "Note: Ensure you have the necessary environment variables set for integration tests in the .env file." - ./vendor/bin/phpunit tests/Integration + ./vendor/bin/phpunit tests/Integration --testdox --display-errors --display-warnings --display-deprecations --display-phpunit-deprecations --display-notices test_acceptance: @printf 'Running acceptance tests:\n'; ./vendor/bin/phpunit tests/Acceptance --testdox --display-errors --display-warnings --display-deprecations --display-phpunit-deprecations --display-notices +test: + @printf 'Running all tests:\n'; + make test_unit; + make test_acceptance; + make test_integration; + lint: ./vendor/bin/phpcs --standard=phpcs.xml.dist --extensions=php --ignore=vendor/ . From e348112b401823caa47a93ce904c635bacfd69a4 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 09:58:33 +0200 Subject: [PATCH 18/24] [UAS-SCOUT] Implement CurlHttpTransport for HTTP requests Added CurlHttpTransport class to handle HTTP requests using cURL, implementing the HttpTransportInterface. This allows for better encapsulation of HTTP transport logic and error handling. Updated Bluem class to utilize the new transport mechanism, enhancing the overall architecture. Testing was conducted to ensure that the new transport layer correctly handles responses and errors. --- src/Bluem.php | 108 ++++++++++-------- src/Transport/CurlHttpTransport.php | 43 +++++++ src/Transport/HttpTransportInterface.php | 10 ++ src/Transport/HttpTransportResponse.php | 14 +++ .../BluemConfigurationValidator.php | 10 +- tests/Acceptance/AcceptanceTestCase.php | 9 +- tests/Acceptance/IbanCheckAcceptanceTest.php | 66 ----------- tests/Acceptance/IdentityAcceptanceTest.php | 45 -------- tests/Acceptance/MandatesAcceptanceTest.php | 50 -------- tests/Acceptance/PaymentsAcceptanceTest.php | 47 -------- tests/Integration/BluemGenericTestCase.php | 20 +++- tests/Integration/IPAPITest.php | 3 +- tests/Unit/BluemTest.php | 68 ++++++++++- tests/Unit/IbanResponseTest.php | 75 ++++++++++++ tests/Unit/IdentityResponseTest.php | 54 +++++++++ tests/Unit/MandateResponseTest.php | 58 ++++++++++ tests/Unit/PaymentResponseTest.php | 56 +++++++++ tests/Unit/ResponseTestCase.php | 23 ++++ 18 files changed, 493 insertions(+), 266 deletions(-) create mode 100644 src/Transport/CurlHttpTransport.php create mode 100644 src/Transport/HttpTransportInterface.php create mode 100644 src/Transport/HttpTransportResponse.php create mode 100644 tests/Unit/IbanResponseTest.php create mode 100644 tests/Unit/IdentityResponseTest.php create mode 100644 tests/Unit/MandateResponseTest.php create mode 100644 tests/Unit/PaymentResponseTest.php create mode 100644 tests/Unit/ResponseTestCase.php diff --git a/src/Bluem.php b/src/Bluem.php index 7d71cb3..449d245 100644 --- a/src/Bluem.php +++ b/src/Bluem.php @@ -34,10 +34,13 @@ use Bluem\BluemPHP\Responses\MandateTransactionBluemResponse; use Bluem\BluemPHP\Responses\PaymentStatusBluemResponse; use Bluem\BluemPHP\Responses\PaymentTransactionBluemResponse; +use Bluem\BluemPHP\Transport\CurlHttpTransport; +use Bluem\BluemPHP\Transport\HttpTransportInterface; use Bluem\BluemPHP\Validators\BluemXMLValidator; use DOMException; use Exception; use RuntimeException; +use SimpleXMLElement; use Throwable; if (!defined("BLUEM_ENVIRONMENT_PRODUCTION")) { @@ -68,6 +71,8 @@ class Bluem private BluemConfiguration $configuration; + private HttpTransportInterface $transport; + /** * Bluem constructor. @@ -76,7 +81,7 @@ class Bluem * * @throws InvalidBluemConfigurationException */ - public function __construct(mixed $rawConfig) + public function __construct(mixed $rawConfig, ?HttpTransportInterface $transport = null) { if ($rawConfig === null) { throw new InvalidBluemConfigurationException('No configuration given'); @@ -87,6 +92,8 @@ public function __construct(mixed $rawConfig) } catch (Exception $exception) { throw new InvalidBluemConfigurationException($exception->getMessage(), $exception->getCode(), $exception); } + + $this->transport = $transport ?? new CurlHttpTransport(); } @@ -202,7 +209,7 @@ public function CreateMandateID(string $order_id, string $customer_id): string * @throws DOMException * @throws Exception */ - public function PerformRequest(BluemRequestInterface $transaction_request): BluemResponseInterface + public function PerformRequest(BluemRequestInterface $transaction_request): ErrorBluemResponse|IBANNameCheckBluemResponse|IdentityStatusBluemResponse|IdentityTransactionBluemResponse|MandateStatusBluemResponse|MandateTransactionBluemResponse|PaymentStatusBluemResponse|PaymentTransactionBluemResponse { $validator = new BluemXMLValidator(); if ( @@ -230,11 +237,11 @@ public function PerformRequest(BluemRequestInterface $transaction_request): Blue // function to allow for Carbon 1.21 legacy compatibility $xttrs_date = $now->rfc1123(); - $request_url = $transaction_request->HttpRequestUrl(); + $request_url = $transaction_request->HttpRequestURL(); $curl_xml = $transaction_request->XmlString(); - $curl_headers = [ + $headers = [ 'Access-Control-Allow-Origin: *', 'Content-Type: application/xml; type=' . $transaction_request->transaction_code . '; charset=UTF-8', 'x-ttrs-date: ' . $xttrs_date, @@ -242,61 +249,41 @@ public function PerformRequest(BluemRequestInterface $transaction_request): Blue 'x-ttrs-filename: ' . $xttrs_filename, ]; - $curl = curl_init(); - - $curl_options = [ - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => "xmlRequest=" . $curl_xml, - // CURLOPT_POSTFIELDS => http_build_query($params), - CURLOPT_URL => $request_url, - CURLOPT_HTTPHEADER => $curl_headers, - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_SSL_VERIFYPEER => true, // @todo: check if we can set this to true - CURLOPT_FOLLOWLOCATION => 1, - CURLOPT_TIMEOUT => 30 - ]; - - // Set options to cURL request - curl_setopt_array($curl, $curl_options); - try { - // Execute cURL request - $response = curl_exec($curl); - - // Get response HTTP status code - $response_status = curl_getinfo($curl, CURLINFO_HTTP_CODE); - - // Convert the XML result into array - $array_data = json_decode(json_encode(simplexml_load_string($response)), true); + $transport_response = $this->transport->send( + url: $request_url, + headers: $headers, + body: "xmlRequest=" . $curl_xml + ); - curl_close($curl); + $response_status = $transport_response->statusCode; + $responseBody = $transport_response->body; switch ($response_status) { case 200: - if ($response === false || ($response === '' || $response === '0')) { + if ($responseBody === '' || $responseBody === '0') { return new ErrorBluemResponse("Error: Empty response returned"); } + $xml = $this->parseResponseXml($responseBody); + if ($xml === null) { + return new ErrorBluemResponse('Error: Could not parse Bluem response XML'); + } try { - $response = $this->fabricateResponseObject($transaction_request->transaction_code, $response); + $bluemResponse = $this->fabricateResponseObject($transaction_request->transaction_code, $responseBody); } catch (Throwable $th) { return new ErrorBluemResponse("Error: Could not create Bluem Response object. More details: " . $th->getMessage()); } - if ($array_data['@attributes']['type'] === "ErrorResponse") { - $errorMessage = match ((string) $transaction_request->transaction_code) { - 'SRX', 'SUD', 'TRX', 'TRS' => (string) $response->EMandateErrorResponse->Error->ErrorMessage, - 'PSU', 'PSX', 'PTS', 'PTX' => (string) $response->PaymentErrorResponse->Error->ErrorMessage, - 'ITS', 'ITX', 'ISU', 'ISX' => (string) $response->IdentityErrorResponse->Error->ErrorMessage, - 'INS', 'INX' => (string) $response->IBANCheckErrorResponse->Error->ErrorMessage, - default => throw new RuntimeException("Invalid transaction type requested"), - }; + $rootAttributes = $xml->attributes(); + if ($rootAttributes !== null && isset($rootAttributes['type']) && (string) $rootAttributes['type'] === 'ErrorResponse') { + $errorMessage = $this->extractErrorMessage($xml, (string) $transaction_request->transaction_code); // @todo: move into a separate function return new ErrorBluemResponse("Error: " . ( $errorMessage )); } - if (! $response->Status()) { - return new ErrorBluemResponse("Error: " . ( $response->Error->ErrorMessage )); + if (! $bluemResponse->Status()) { + return new ErrorBluemResponse("Error: " . $bluemResponse->Error()); } - return $response; + return $bluemResponse; case 400: return new ErrorBluemResponse('Your request was not formed correctly.'); case 401: @@ -312,6 +299,37 @@ public function PerformRequest(BluemRequestInterface $transaction_request): Blue } } + private function parseResponseXml(string $response): ?SimpleXMLElement + { + $previousUseInternalErrors = libxml_use_internal_errors(true); + $xml = simplexml_load_string($response); + libxml_clear_errors(); + libxml_use_internal_errors($previousUseInternalErrors); + + if ($xml === false) { + return null; + } + + return $xml; + } + + private function extractErrorMessage(SimpleXMLElement $xml, string $transactionCode): string + { + $errorNodeName = match ($transactionCode) { + 'SRX', 'SUD', 'TRX', 'TRS' => 'EMandateErrorResponse', + 'PSU', 'PSX', 'PTS', 'PTX' => 'PaymentErrorResponse', + 'ITS', 'ITX', 'ISU', 'ISX' => 'IdentityErrorResponse', + 'INS', 'INX' => 'IBANCheckErrorResponse', + default => throw new RuntimeException('Invalid transaction type requested'), + }; + + if (!isset($xml->{$errorNodeName}->Error->ErrorMessage)) { + return ''; + } + + return (string) $xml->{$errorNodeName}->Error->ErrorMessage; + } + /** * Create the proper response object class * @@ -320,7 +338,7 @@ public function PerformRequest(BluemRequestInterface $transaction_request): Blue * * @throws Exception */ - private function fabricateResponseObject($type, $response_xml): BluemResponseInterface + private function fabricateResponseObject($type, $response_xml): ErrorBluemResponse|IBANNameCheckBluemResponse|IdentityStatusBluemResponse|IdentityTransactionBluemResponse|MandateStatusBluemResponse|MandateTransactionBluemResponse|PaymentStatusBluemResponse|PaymentTransactionBluemResponse { return match ($type) { 'SRX', 'SUD' => new MandateStatusBluemResponse($response_xml), @@ -377,7 +395,6 @@ public function GetMaximumAmountFromTransactionResponse($response): object * @param $amount * * @throws DOMException - * @throws HTTP_Request2_LogicException * @throws RuntimeException */ public function Payment( @@ -619,7 +636,6 @@ public function GetIdentityRequestTypes(): array * @param string $debtorReference An optional given debtor reference * to append to the check request * @throws DOMException - * @throws HTTP_Request2_LogicException * @throws Exception */ public function IBANNameCheck(string $iban, string $name, string $debtorReference = ""): ErrorBluemResponse|IBANNameCheckBluemResponse|IdentityStatusBluemResponse|IdentityTransactionBluemResponse|MandateStatusBluemResponse|MandateTransactionBluemResponse|PaymentStatusBluemResponse|PaymentTransactionBluemResponse diff --git a/src/Transport/CurlHttpTransport.php b/src/Transport/CurlHttpTransport.php new file mode 100644 index 0000000..5d9d664 --- /dev/null +++ b/src/Transport/CurlHttpTransport.php @@ -0,0 +1,43 @@ + true, + CURLOPT_POSTFIELDS => $body, + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_FOLLOWLOCATION => 1, + CURLOPT_TIMEOUT => 30, + ]); + + $response = curl_exec($curl); + $statusCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE); + $errorMessage = curl_error($curl); + + curl_close($curl); + + if ($response === false) { + $message = $errorMessage !== '' ? $errorMessage : 'Unknown cURL error'; + throw new RuntimeException($message); + } + + return new HttpTransportResponse($statusCode, (string) $response); + } +} diff --git a/src/Transport/HttpTransportInterface.php b/src/Transport/HttpTransportInterface.php new file mode 100644 index 0000000..648ae45 --- /dev/null +++ b/src/Transport/HttpTransportInterface.php @@ -0,0 +1,10 @@ +environment) || !in_array( + !isset($config->environment) + ) { + throw new Exception( + "environment not set; please add this to your configuration when instantiating the Bluem integration" + ); + } + + if ( + !in_array( $config->environment, Constants::ENVIRONMENTS, true diff --git a/tests/Acceptance/AcceptanceTestCase.php b/tests/Acceptance/AcceptanceTestCase.php index 96495ca..953da56 100644 --- a/tests/Acceptance/AcceptanceTestCase.php +++ b/tests/Acceptance/AcceptanceTestCase.php @@ -6,8 +6,6 @@ use Bluem\BluemPHP\Helpers\BluemConfiguration; use PHPUnit\Framework\TestCase; -use RuntimeException; -use SimpleXMLElement; /** * Shared helpers for acceptance-style request/response tests. @@ -22,6 +20,7 @@ abstract class AcceptanceTestCase extends TestCase * Build a minimal but valid Bluem configuration for test environment usage. * * Unknown live values are kept as placeholders so the file stays copy-pastable. + * @throws \Exception */ protected function createConfiguration(string $brandId, array $overrides = []): BluemConfiguration { @@ -47,12 +46,12 @@ protected function createConfiguration(string $brandId, array $overrides = []): /** * Load a SimpleXML fixture as one of Bluem's response subclasses. */ - protected function loadXmlResponse(string $xml, string $className): SimpleXMLElement + protected function loadXmlResponse(string $xml, string $className) { $response = simplexml_load_string($xml, $className); - if (!$response instanceof SimpleXMLElement) { - throw new RuntimeException('Unable to parse XML fixture for ' . $className); + if ($response === false) { + throw new \RuntimeException('Unable to parse XML fixture for ' . $className); } return $response; diff --git a/tests/Acceptance/IbanCheckAcceptanceTest.php b/tests/Acceptance/IbanCheckAcceptanceTest.php index 20e1f9d..c91d653 100644 --- a/tests/Acceptance/IbanCheckAcceptanceTest.php +++ b/tests/Acceptance/IbanCheckAcceptanceTest.php @@ -5,7 +5,6 @@ namespace Bluem\BluemPHP\Tests\Acceptance; use Bluem\BluemPHP\Requests\IBANBluemRequest; -use Bluem\BluemPHP\Responses\IBANNameCheckBluemResponse; final class IbanCheckAcceptanceTest extends AcceptanceTestCase { @@ -32,69 +31,4 @@ public function testIbanRequestXmlSanitizesInput(): void '1234' ); } - - public function testIbanResponseKnownResultExposesAllFields(): void - { - $xml = <<<'XML' - - - - - KNOWN - MATCH - D.J.M. Daan Jeroen Maarten Quackernaat - OPEN - - - PERSONAL - false - 1 - Netherlands - - - -XML; - - $response = $this->loadXmlResponse($xml, IBANNameCheckBluemResponse::class); - - self::assertSame('KNOWN', $response->GetIBANResult()); - self::assertSame('MATCH', $response->GetNameResult()); - self::assertSame('D.J.M. Daan Jeroen Maarten Quackernaat', $response->GetSuggestedName()); - self::assertSame('OPEN', $response->GetAccountStatus()); - self::assertSame('PERSONAL', $response->GetAccountType()); - self::assertSame('false', $response->GetIsJointAccount()); - self::assertSame('1', $response->GetNumberOfAccountHolders()); - self::assertSame('Netherlands', $response->GetCountryName()); - } - - public function testIbanResponseInvalidAndUnavailableStatesRemainParsable(): void - { - $invalidXml = <<<'XML' - - - - - INVALID - - - -XML; - - $unavailableXml = <<<'XML' - - - - - SERVICE_TEMPORARILY_NOT_AVAILABLE - - - -XML; - - $invalidResponse = $this->loadXmlResponse($invalidXml, IBANNameCheckBluemResponse::class); - $unavailableResponse = $this->loadXmlResponse($unavailableXml, IBANNameCheckBluemResponse::class); - - self::assertSame('INVALID', $invalidResponse->GetIBANResult()); - self::assertSame('SERVICE_TEMPORARILY_NOT_AVAILABLE', $unavailableResponse->GetIBANResult()); - } } diff --git a/tests/Acceptance/IdentityAcceptanceTest.php b/tests/Acceptance/IdentityAcceptanceTest.php index 7a45523..172fc47 100644 --- a/tests/Acceptance/IdentityAcceptanceTest.php +++ b/tests/Acceptance/IdentityAcceptanceTest.php @@ -5,8 +5,6 @@ namespace Bluem\BluemPHP\Tests\Acceptance; use Bluem\BluemPHP\Requests\IdentityBluemRequest; -use Bluem\BluemPHP\Responses\IdentityStatusBluemResponse; -use Bluem\BluemPHP\Responses\IdentityTransactionBluemResponse; final class IdentityAcceptanceTest extends AcceptanceTestCase { @@ -40,47 +38,4 @@ public function testIdentityRequestXmlUsesSandboxSampleData(): void 'http://localhost/code/etc/?debtorReference=1234', ); } - - public function testIdentityTransactionResponseReadsTransactionData(): void - { - $xml = <<<'XML' - - - - https://test.viamijnbank.net/identity/transaction/1234abcdef - 1234abcdef - 1234 - - -XML; - - $response = $this->loadXmlResponse($xml, IdentityTransactionBluemResponse::class); - - self::assertSame('https://test.viamijnbank.net/identity/transaction/1234abcdef', $response->GetTransactionURL()); - self::assertSame('1234abcdef', $response->GetTransactionID()); - self::assertSame('1234', $response->GetDebtorReference()); - } - - public function testIdentityStatusResponseReadsIdentityReport(): void - { - $xml = <<<'XML' - - - - AUTH-001 - Success - - Verified - INSERT_VARIABLE_CUSTOMER_NAME_HERE - - - -XML; - - $response = $this->loadXmlResponse($xml, IdentityStatusBluemResponse::class); - - self::assertSame('AUTH-001', $response->GetAuthenticationAuthorityID()); - self::assertNotNull($response->GetIdentityReport()); - self::assertSame('Verified', (string) $response->GetIdentityReport()->ReportStatus); - } } diff --git a/tests/Acceptance/MandatesAcceptanceTest.php b/tests/Acceptance/MandatesAcceptanceTest.php index c90e812..61bdd21 100644 --- a/tests/Acceptance/MandatesAcceptanceTest.php +++ b/tests/Acceptance/MandatesAcceptanceTest.php @@ -4,10 +4,7 @@ namespace Bluem\BluemPHP\Tests\Acceptance; -use Bluem\BluemPHP\Helpers\BluemCurrency; use Bluem\BluemPHP\Requests\EmandateBluemRequest; -use Bluem\BluemPHP\Responses\MandateStatusBluemResponse; -use Bluem\BluemPHP\Responses\MandateTransactionBluemResponse; final class MandatesAcceptanceTest extends AcceptanceTestCase { @@ -48,51 +45,4 @@ public function testMandateRequestXmlUsesSandboxSampleData(): void 'INGBNL2A' ); } - - public function testMandateTransactionResponseReadsTransactionData(): void - { - $xml = <<<'XML' - - - - https://test.viamijnbank.net/mandate/transaction/134426345 - MANDATE-TX-123 - 134426345 - - -XML; - - $response = $this->loadXmlResponse($xml, MandateTransactionBluemResponse::class); - - self::assertSame('https://test.viamijnbank.net/mandate/transaction/134426345', $response->GetTransactionURL()); - self::assertSame('MANDATE-TX-123', $response->GetTransactionID()); - self::assertSame('134426345', $response->GetMandateID()); - } - - public function testMandateStatusResponseReadsAcceptanceReportData(): void - { - $xml = <<<'XML' - - - - - - NL66ABNA4097012428 - ABNANL2A - D.J.M. Daan Jeroen Maarten Quackernaat - 250.00 - - - - -XML; - - $response = $this->loadXmlResponse($xml, MandateStatusBluemResponse::class); - - self::assertSame('NL66ABNA4097012428', $response->GetDebtorIBAN()); - self::assertSame('ABNANL2A', $response->GetDebtorBankID()); - self::assertSame('D.J.M. Daan Jeroen Maarten Quackernaat', $response->GetDebtorAccountName()); - self::assertSame(250.00, $response->GetMaximumAmount()->amount); - self::assertSame('EUR', $response->GetMaximumAmount()->currency->code); - } } diff --git a/tests/Acceptance/PaymentsAcceptanceTest.php b/tests/Acceptance/PaymentsAcceptanceTest.php index 68b0b26..7d1e0d6 100644 --- a/tests/Acceptance/PaymentsAcceptanceTest.php +++ b/tests/Acceptance/PaymentsAcceptanceTest.php @@ -5,8 +5,6 @@ namespace Bluem\BluemPHP\Tests\Acceptance; use Bluem\BluemPHP\Requests\PaymentBluemRequest; -use Bluem\BluemPHP\Responses\PaymentStatusBluemResponse; -use Bluem\BluemPHP\Responses\PaymentTransactionBluemResponse; final class PaymentsAcceptanceTest extends AcceptanceTestCase { @@ -84,49 +82,4 @@ public function testPaymentRequestSupportsCreditCardMethodBranch(): void '2025' ); } - - public function testPaymentStatusResponseReadsStatusCode(): void - { - $xml = <<<'XML' - - - - 2026-04-05T00:00:00Z - 1234134426345ae - 1234 - 134426345ae - Success - 12.34 - 12.34 - EUR - IDEAL - - -XML; - - $response = $this->loadXmlResponse($xml, PaymentStatusBluemResponse::class); - - self::assertTrue($response->Status()); - self::assertSame('Success', $response->GetStatusCode()); - } - - public function testPaymentTransactionResponseReadsTransactionData(): void - { - $xml = <<<'XML' - - - - https://test.viamijnbank.net/payment/transaction/TRANS123 - TRANS123 - 1234 - - -XML; - - $response = $this->loadXmlResponse($xml, PaymentTransactionBluemResponse::class); - - self::assertSame('https://test.viamijnbank.net/payment/transaction/TRANS123', $response->GetTransactionURL()); - self::assertSame('TRANS123', $response->GetTransactionID()); - self::assertSame('1234', $response->GetDebtorReference()); - } } diff --git a/tests/Integration/BluemGenericTestCase.php b/tests/Integration/BluemGenericTestCase.php index 1fb1872..176d616 100644 --- a/tests/Integration/BluemGenericTestCase.php +++ b/tests/Integration/BluemGenericTestCase.php @@ -22,6 +22,18 @@ */ abstract class BluemGenericTestCase extends TestCase { + /** + * @var string[] + */ + private const REQUIRED_ENVIRONMENT_VARIABLES = [ + 'BLUEM_ENV', + 'BLUEM_SENDER_ID', + 'BLUEM_BRANDID', + 'BLUEM_TEST_ACCESS_TOKEN', + 'BLUEM_MERCHANTID', + 'BLUEM_MERCHANTRETURNURLBASE', + ]; + /** * The Bluem integration object */ @@ -36,7 +48,13 @@ protected function setUp(): void { $env_file = __DIR__ . '/../..'; $dotenv = Dotenv::createImmutable($env_file); - $dotenv->load(); + $dotenv->safeLoad(); + + foreach (self::REQUIRED_ENVIRONMENT_VARIABLES as $variable) { + if (!isset($_ENV[$variable]) || $_ENV[$variable] === '') { + $this->markTestSkipped(sprintf('Live Bluem integration tests require %s to be set.', $variable)); + } + } // Create a Bluem object and set the Bluem configuration details based on your .env file. $bluem_config = new stdClass(); diff --git a/tests/Integration/IPAPITest.php b/tests/Integration/IPAPITest.php index 266cd8c..5a03d94 100644 --- a/tests/Integration/IPAPITest.php +++ b/tests/Integration/IPAPITest.php @@ -10,6 +10,7 @@ namespace Bluem\BluemPHP\Tests\Integration; use Bluem\BluemPHP\Extensions\IPAPI; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class IPAPITest extends TestCase @@ -27,7 +28,7 @@ public function testCheckIsNetherlandsReturnsTrueIfNoIPAddressGiven(): void $this->assertTrue($result); } - /** @dataProvider NetherlandsIPTestDataProvider */ + #[DataProvider('NetherlandsIPTestDataProvider')] public function testCheckIPAdressGivenDataProvider($ipAddress, $expectedNetherlands): void { $isNetherlands = $this->IPAPI->checkIsNetherlands($ipAddress); diff --git a/tests/Unit/BluemTest.php b/tests/Unit/BluemTest.php index 0725426..b2fd94e 100644 --- a/tests/Unit/BluemTest.php +++ b/tests/Unit/BluemTest.php @@ -15,6 +15,8 @@ use Bluem\BluemPHP\Interfaces\BluemResponseInterface; use Bluem\BluemPHP\Requests\BluemRequest; use Bluem\BluemPHP\Responses\ErrorBluemResponse; +use Bluem\BluemPHP\Transport\HttpTransportInterface; +use Bluem\BluemPHP\Transport\HttpTransportResponse; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -22,6 +24,7 @@ class BluemTest extends TestCase { private Bluem $bluem; + private FakeHttpTransport $transport; /** * @throws InvalidBluemConfigurationException @@ -30,7 +33,8 @@ protected function setUp(): void { // Mock the configuration as needed $mockedConfig = $this->getConfig(); - $this->bluem = new Bluem($mockedConfig); + $this->transport = new FakeHttpTransport(); + $this->bluem = new Bluem($mockedConfig, $this->transport); } @@ -49,11 +53,35 @@ public function testConstructorWithInvalidConfig(): void public function testMandateWithValidParameters(): void { - // Test the Mandate method with valid parameters - $response = $this->bluem->Mandate('customer_id', 'order_id', 'mandate_id'); + $this->transport->setResponse( + 200, + <<<'XML' + + + + https://test.viamijnbank.net/identity/transaction/1234abcdef + 1234abcdef + 1234 + + +XML + ); + + $request = $this->bluem->CreateIdentityRequest( + requestCategory: ['CustomerIDRequest', 'NameRequest'], + description: 'Identificatie test', + debtorReference: '1234', + entranceCode: '20260405095326915', + returnURL: 'http://localhost/code/etc/' + ); + + $response = $this->bluem->PerformRequest($request); - // Assertions $this->assertInstanceOf(BluemResponseInterface::class, $response); + $this->assertNotInstanceOf(ErrorBluemResponse::class, $response); + $this->assertNotSame('', $this->transport->lastUrl); + $this->assertStringStartsWith('xmlRequest=', $this->transport->lastBody); + $this->assertNotEmpty($this->transport->lastHeaders); } public function testMandateWithException(): void @@ -106,3 +134,35 @@ private function getConfig(): stdClass return $bluem_config; } } + +final class FakeHttpTransport implements HttpTransportInterface +{ + public int $lastStatusCode = 0; + + public string $lastBody = ''; + + /** @var string[] */ + public array $lastHeaders = []; + + public string $lastUrl = ''; + + private int $nextStatusCode = 200; + + private string $nextBody = ''; + + public function setResponse(int $statusCode, string $body): void + { + $this->nextStatusCode = $statusCode; + $this->nextBody = $body; + } + + public function send(string $url, array $headers, string $body): HttpTransportResponse + { + $this->lastUrl = $url; + $this->lastHeaders = $headers; + $this->lastBody = $body; + $this->lastStatusCode = $this->nextStatusCode; + + return new HttpTransportResponse($this->nextStatusCode, $this->nextBody); + } +} diff --git a/tests/Unit/IbanResponseTest.php b/tests/Unit/IbanResponseTest.php new file mode 100644 index 0000000..182926b --- /dev/null +++ b/tests/Unit/IbanResponseTest.php @@ -0,0 +1,75 @@ + + + + + KNOWN + MATCH + D.J.M. Daan Jeroen Maarten Quackernaat + OPEN + + + PERSONAL + false + 1 + Netherlands + + + +XML; + + $response = $this->loadXmlResponse($xml, IBANNameCheckBluemResponse::class); + + self::assertSame('KNOWN', $response->GetIBANResult()); + self::assertSame('MATCH', $response->GetNameResult()); + self::assertSame('D.J.M. Daan Jeroen Maarten Quackernaat', $response->GetSuggestedName()); + self::assertSame('OPEN', $response->GetAccountStatus()); + self::assertSame('PERSONAL', $response->GetAccountType()); + self::assertSame('false', $response->GetIsJointAccount()); + self::assertSame('1', $response->GetNumberOfAccountHolders()); + self::assertSame('Netherlands', $response->GetCountryName()); + } + + public function testIbanResponseInvalidAndUnavailableStatesRemainParsable(): void + { + $invalidXml = <<<'XML' + + + + + INVALID + + + +XML; + + $unavailableXml = <<<'XML' + + + + + SERVICE_TEMPORARILY_NOT_AVAILABLE + + + +XML; + + $invalidResponse = $this->loadXmlResponse($invalidXml, IBANNameCheckBluemResponse::class); + $unavailableResponse = $this->loadXmlResponse($unavailableXml, IBANNameCheckBluemResponse::class); + + self::assertSame('INVALID', $invalidResponse->GetIBANResult()); + self::assertSame('SERVICE_TEMPORARILY_NOT_AVAILABLE', $unavailableResponse->GetIBANResult()); + } +} diff --git a/tests/Unit/IdentityResponseTest.php b/tests/Unit/IdentityResponseTest.php new file mode 100644 index 0000000..1c2ef17 --- /dev/null +++ b/tests/Unit/IdentityResponseTest.php @@ -0,0 +1,54 @@ + + + + https://test.viamijnbank.net/identity/transaction/1234abcdef + 1234abcdef + 1234 + + +XML; + + $response = $this->loadXmlResponse($xml, IdentityTransactionBluemResponse::class); + + self::assertSame('https://test.viamijnbank.net/identity/transaction/1234abcdef', $response->GetTransactionURL()); + self::assertSame('1234abcdef', $response->GetTransactionID()); + self::assertSame('1234', $response->GetDebtorReference()); + } + + public function testIdentityStatusResponseReadsIdentityReport(): void + { + $xml = <<<'XML' + + + + AUTH-001 + Success + + Verified + INSERT_VARIABLE_CUSTOMER_NAME_HERE + + + +XML; + + $response = $this->loadXmlResponse($xml, IdentityStatusBluemResponse::class); + + self::assertSame('AUTH-001', $response->GetAuthenticationAuthorityID()); + self::assertNotNull($response->GetIdentityReport()); + self::assertSame('Verified', (string) $response->GetIdentityReport()->ReportStatus); + } +} diff --git a/tests/Unit/MandateResponseTest.php b/tests/Unit/MandateResponseTest.php new file mode 100644 index 0000000..ffd2e9b --- /dev/null +++ b/tests/Unit/MandateResponseTest.php @@ -0,0 +1,58 @@ + + + + https://test.viamijnbank.net/mandate/transaction/134426345 + MANDATE-TX-123 + 134426345 + + +XML; + + $response = $this->loadXmlResponse($xml, MandateTransactionBluemResponse::class); + + self::assertSame('https://test.viamijnbank.net/mandate/transaction/134426345', $response->GetTransactionURL()); + self::assertSame('MANDATE-TX-123', $response->GetTransactionID()); + self::assertSame('134426345', $response->GetMandateID()); + } + + public function testMandateStatusResponseReadsAcceptanceReportData(): void + { + $xml = <<<'XML' + + + + + + NL66ABNA4097012428 + ABNANL2A + D.J.M. Daan Jeroen Maarten Quackernaat + 250.00 + + + + +XML; + + $response = $this->loadXmlResponse($xml, MandateStatusBluemResponse::class); + + self::assertSame('NL66ABNA4097012428', $response->GetDebtorIBAN()); + self::assertSame('ABNANL2A', $response->GetDebtorBankID()); + self::assertSame('D.J.M. Daan Jeroen Maarten Quackernaat', $response->GetDebtorAccountName()); + self::assertSame(250.00, $response->GetMaximumAmount()->amount); + self::assertSame('EUR', $response->GetMaximumAmount()->currency->code); + } +} diff --git a/tests/Unit/PaymentResponseTest.php b/tests/Unit/PaymentResponseTest.php new file mode 100644 index 0000000..efa16f4 --- /dev/null +++ b/tests/Unit/PaymentResponseTest.php @@ -0,0 +1,56 @@ + + + + 2026-04-05T00:00:00Z + 1234134426345ae + 1234 + 134426345ae + Success + 12.34 + 12.34 + EUR + IDEAL + + +XML; + + $response = $this->loadXmlResponse($xml, PaymentStatusBluemResponse::class); + + self::assertTrue($response->Status()); + self::assertSame('Success', $response->GetStatusCode()); + } + + public function testPaymentTransactionResponseReadsTransactionData(): void + { + $xml = <<<'XML' + + + + https://test.viamijnbank.net/payment/transaction/TRANS123 + TRANS123 + 1234 + + +XML; + + $response = $this->loadXmlResponse($xml, PaymentTransactionBluemResponse::class); + + self::assertSame('https://test.viamijnbank.net/payment/transaction/TRANS123', $response->GetTransactionURL()); + self::assertSame('TRANS123', $response->GetTransactionID()); + self::assertSame('1234', $response->GetDebtorReference()); + } +} diff --git a/tests/Unit/ResponseTestCase.php b/tests/Unit/ResponseTestCase.php new file mode 100644 index 0000000..666b24e --- /dev/null +++ b/tests/Unit/ResponseTestCase.php @@ -0,0 +1,23 @@ + Date: Sun, 5 Apr 2026 10:00:11 +0200 Subject: [PATCH 19/24] [UAS-SCOUT] Add AGENTS.md documentation for project overview and developer workflow This commit introduces AGENTS.md, providing a comprehensive overview of the `bluem-php` library, its code structure, conventions, and developer workflow. This documentation aims to assist new contributors in understanding the project and maintaining consistency in development practices. --- AGENTS.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a2707e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# AGENTS.md + +## Project at a glance +- `bluem-php` is a PHP 8.3 library for Bluem payment flows: Payments, eMandates, iDIN, and IBAN-name check. +- `src/Bluem.php` is the main orchestration layer: it builds requests, validates XML, sends them through `Transport/`, and turns responses into `Responses/*` objects. +- `src/Webhook.php` handles inbound webhook XML and is intentionally strict: HTTPS POST + `text/xml; charset=UTF-8` + XML/signature validation. + +## Code structure to preserve +- `src/Contexts/*` defines service-specific bank/BIC sets and XSD schema paths (`IdentityContext`, `PaymentsContext`, `MandatesContext`). +- `src/Requests/*` owns XML/endpoint generation; `src/Responses/*` wraps `SimpleXMLElement` parsing. +- `src/Transport/` isolates HTTP; `CurlHttpTransport` is the default and is injected into `Bluem` for testability. +- `src/Validators/*` enforces XML and webhook constraints; do not bypass these checks in higher layers. + +## Conventions specific to this repo +- Public API names are legacy-compatible and intentionally mixed-case in places (`CreateMandateRequest`, `PerformRequest`, `getConfig`, `Webhook::getPurchaseID()`); avoid renaming unless you are ready to update downstream consumers. +- `phpcs.xml.dist` uses PSR-12 with narrow exceptions for legacy method/property naming and file-header ordering. +- Existing code mixes older style and newer strict typing; prefer small, behavior-preserving edits over broad refactors. + +## Developer workflow +- Install deps with `composer install`. +- Lint with `make lint`; auto-fix style with `make lint_fix`. +- Run unit tests with `make test_unit` or `./vendor/bin/phpunit tests/Unit`. +- Run live tests with `make test_integration` / `make test_acceptance`; these require `.env`. +- CI (`.github/workflows/ci.yml`) runs on PHP 8.3, then `make lint`, then PHPUnit. + +## Environment and testing +- Copy `.env.example` to `.env` for integration/acceptance tests. +- Required env vars are enforced in `tests/Integration/BluemGenericTestCase.php`: `BLUEM_ENV`, `BLUEM_SENDER_ID`, `BLUEM_BRANDID`, `BLUEM_TEST_ACCESS_TOKEN`, `BLUEM_MERCHANTID`, `BLUEM_MERCHANTRETURNURLBASE`. +- Unit tests typically use a fake transport; integration tests extend the shared base case and hit live Bluem services. + +## When changing code +- Update request/response/context pieces together so XML shape, endpoint URL, and validation stay aligned. +- Check `validation/*.xsd` and `examples/` when touching service-specific payloads. +- Keep webhook validation strict; relaxing HTTPS, content-type, or signature checks is a security regression. + From 839bc9ddeb65f7d4d073babb6a90beb81165f9c2 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 10:00:16 +0200 Subject: [PATCH 20/24] [UAS-SCOUT] Rename test method for clarity Updated the test method from `testMandateWithValidParameters` to `testIdentityRequestWithValidParameters` to better reflect its purpose. This change enhances the readability and maintainability of the test suite. --- tests/Unit/BluemTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/BluemTest.php b/tests/Unit/BluemTest.php index b2fd94e..b6f8adb 100644 --- a/tests/Unit/BluemTest.php +++ b/tests/Unit/BluemTest.php @@ -51,7 +51,7 @@ public function testConstructorWithInvalidConfig(): void } - public function testMandateWithValidParameters(): void + public function testIdentityRequestWithValidParameters(): void { $this->transport->setResponse( 200, From 5bdebf7a288611f3318f9d6173acd732efa61758 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 10:11:01 +0200 Subject: [PATCH 21/24] [UAS-SCOUT] Move FakeHttpTransport implementation to a separate file Extracted the FakeHttpTransport class from BluemTest.php into its own file, enhancing code organization and maintainability. This change allows for easier testing and potential reuse of the FakeHttpTransport in other test cases. --- tests/FakeHttpTransport.php | 45 +++++++++++++++++++++++++++++++++++++ tests/Unit/BluemTest.php | 35 +---------------------------- 2 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 tests/FakeHttpTransport.php diff --git a/tests/FakeHttpTransport.php b/tests/FakeHttpTransport.php new file mode 100644 index 0000000..034fd7a --- /dev/null +++ b/tests/FakeHttpTransport.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bluem\BluemPHP\Tests; + +use Bluem\BluemPHP\Transport\HttpTransportInterface; +use Bluem\BluemPHP\Transport\HttpTransportResponse; + +final class FakeHttpTransport implements HttpTransportInterface +{ + public int $lastStatusCode = 0; + + public string $lastBody = ''; + + /** @var string[] */ + public array $lastHeaders = []; + + public string $lastUrl = ''; + + private int $nextStatusCode = 200; + + private string $nextBody = ''; + + public function setResponse(int $statusCode, string $body): void + { + $this->nextStatusCode = $statusCode; + $this->nextBody = $body; + } + + public function send(string $url, array $headers, string $body): HttpTransportResponse + { + $this->lastUrl = $url; + $this->lastHeaders = $headers; + $this->lastBody = $body; + $this->lastStatusCode = $this->nextStatusCode; + + return new HttpTransportResponse($this->nextStatusCode, $this->nextBody); + } +} diff --git a/tests/Unit/BluemTest.php b/tests/Unit/BluemTest.php index b6f8adb..7a777e7 100644 --- a/tests/Unit/BluemTest.php +++ b/tests/Unit/BluemTest.php @@ -15,8 +15,7 @@ use Bluem\BluemPHP\Interfaces\BluemResponseInterface; use Bluem\BluemPHP\Requests\BluemRequest; use Bluem\BluemPHP\Responses\ErrorBluemResponse; -use Bluem\BluemPHP\Transport\HttpTransportInterface; -use Bluem\BluemPHP\Transport\HttpTransportResponse; +use Bluem\BluemPHP\Tests\FakeHttpTransport; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -134,35 +133,3 @@ private function getConfig(): stdClass return $bluem_config; } } - -final class FakeHttpTransport implements HttpTransportInterface -{ - public int $lastStatusCode = 0; - - public string $lastBody = ''; - - /** @var string[] */ - public array $lastHeaders = []; - - public string $lastUrl = ''; - - private int $nextStatusCode = 200; - - private string $nextBody = ''; - - public function setResponse(int $statusCode, string $body): void - { - $this->nextStatusCode = $statusCode; - $this->nextBody = $body; - } - - public function send(string $url, array $headers, string $body): HttpTransportResponse - { - $this->lastUrl = $url; - $this->lastHeaders = $headers; - $this->lastBody = $body; - $this->lastStatusCode = $this->nextStatusCode; - - return new HttpTransportResponse($this->nextStatusCode, $this->nextBody); - } -} From 671a80077d43e01d7bc733e5422ddd0c69770e07 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 10:12:56 +0200 Subject: [PATCH 22/24] [UAS-SCOUT] Update PHPUnit configuration to adjust test directory paths Modified the PHPUnit configuration to update the paths for Unit, Integration, and Acceptance test directories, ensuring they correctly reference the new structure. This change facilitates better organization and accessibility of test files. --- .github/workflows/phpunit.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phpunit.xml b/.github/workflows/phpunit.xml index 957b9a4..811d8cf 100644 --- a/.github/workflows/phpunit.xml +++ b/.github/workflows/phpunit.xml @@ -7,13 +7,13 @@ > - ./tests/Unit + ../../tests/Unit - ./tests/Integration + ../../tests/Integration - ./tests/Acceptance + ../../tests/Acceptance From ae6be4639c5ec15d2271fe033d8e2b90334ec902 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 10:19:04 +0200 Subject: [PATCH 23/24] [UAS-SCOUT] Add paymentBrandID to BluemConfiguration and update constructor type hints - Introduced a new property `paymentBrandID` in `BluemConfiguration` for better configuration management. - Updated constructor type hints in `BluemRequest` and `PaymentStatusBluemRequest` to use `BluemConfiguration` explicitly. - Enhanced error handling in `BluemResponse` and `BluemConfigurationValidator` for improved clarity. This change aims to streamline the configuration process and enhance type safety across the Bluem integration. --- src/Bluem.php | 5 ++-- src/Helpers/BluemConfiguration.php | 6 ++++- src/Requests/BluemRequest.php | 3 ++- src/Requests/PaymentStatusBluemRequest.php | 3 ++- src/Responses/BluemResponse.php | 4 ++- .../BluemConfigurationValidator.php | 5 +++- src/Validators/WebhookSignatureValidation.php | 26 +++++++++++++++---- tests/Unit/IbanResponseTest.php | 3 ++- 8 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Bluem.php b/src/Bluem.php index 449d245..8158b0a 100644 --- a/src/Bluem.php +++ b/src/Bluem.php @@ -209,8 +209,9 @@ public function CreateMandateID(string $order_id, string $customer_id): string * @throws DOMException * @throws Exception */ - public function PerformRequest(BluemRequestInterface $transaction_request): ErrorBluemResponse|IBANNameCheckBluemResponse|IdentityStatusBluemResponse|IdentityTransactionBluemResponse|MandateStatusBluemResponse|MandateTransactionBluemResponse|PaymentStatusBluemResponse|PaymentTransactionBluemResponse - { + public function PerformRequest( + BluemRequestInterface $transaction_request + ): ErrorBluemResponse|IBANNameCheckBluemResponse|IdentityStatusBluemResponse|IdentityTransactionBluemResponse|MandateStatusBluemResponse|MandateTransactionBluemResponse|PaymentStatusBluemResponse|PaymentTransactionBluemResponse { $validator = new BluemXMLValidator(); if ( ! $validator->validate( diff --git a/src/Helpers/BluemConfiguration.php b/src/Helpers/BluemConfiguration.php index 1664a58..ff1d2d2 100644 --- a/src/Helpers/BluemConfiguration.php +++ b/src/Helpers/BluemConfiguration.php @@ -58,6 +58,8 @@ class BluemConfiguration */ public bool $webhookDebug = false; + public ?string $paymentBrandID = ''; + /** * An object containing the configuration for the Bluem integration. Can be an array or object * @@ -75,7 +77,9 @@ public function __construct(object|array $raw) $validated = $this->validator->validate($raw); if ($validated === false) { - throw new InvalidBluemConfigurationException('Bluem Configuration is not valid: ' . $this->errorsAsString()); + throw new InvalidBluemConfigurationException( + 'Bluem Configuration is not valid: ' . $this->errorsAsString() + ); } $this->environment = $validated->environment ?? self::TESTING_ENVIRONMENT; diff --git a/src/Requests/BluemRequest.php b/src/Requests/BluemRequest.php index e801e7c..234bdce 100644 --- a/src/Requests/BluemRequest.php +++ b/src/Requests/BluemRequest.php @@ -9,6 +9,7 @@ namespace Bluem\BluemPHP\Requests; +use Bluem\BluemPHP\Bluem; use Bluem\BluemPHP\Constants; use Bluem\BluemPHP\Exceptions\InvalidBluemRequestException; use Bluem\BluemPHP\Helpers\BluemConfiguration; @@ -118,7 +119,7 @@ class BluemRequest implements BluemRequestInterface * @throws InvalidBluemRequestException */ public function __construct( - $config, + BluemConfiguration $config, string $entranceCode = "", string $expectedReturn = "" ) { diff --git a/src/Requests/PaymentStatusBluemRequest.php b/src/Requests/PaymentStatusBluemRequest.php index dfa9b05..81de5e0 100644 --- a/src/Requests/PaymentStatusBluemRequest.php +++ b/src/Requests/PaymentStatusBluemRequest.php @@ -10,6 +10,7 @@ namespace Bluem\BluemPHP\Requests; use Bluem\BluemPHP\Contexts\PaymentsContext; +use Bluem\BluemPHP\Helpers\BluemConfiguration; class PaymentStatusBluemRequest extends BluemRequest { @@ -22,7 +23,7 @@ class PaymentStatusBluemRequest extends BluemRequest protected $xmlInterfaceName = "EPaymentInterface"; public function __construct( - $config, + BluemConfiguration $config, $transactionID, $expected_return = "", $entranceCode = "" diff --git a/src/Responses/BluemResponse.php b/src/Responses/BluemResponse.php index bee48f2..5133aa1 100644 --- a/src/Responses/BluemResponse.php +++ b/src/Responses/BluemResponse.php @@ -60,7 +60,9 @@ public function GetEntranceCode(): string $attrs = $this->{$this->getParentXmlElement()}->attributes(); if (! $attrs || ! isset($attrs['entranceCode'])) { - throw new RuntimeException("An error occurred in reading the transaction response: no entrance code found."); + throw new RuntimeException( + "An error occurred in reading the transaction response: no entrance code found." + ); } return $attrs['entranceCode'] . ""; diff --git a/src/Validators/BluemConfigurationValidator.php b/src/Validators/BluemConfigurationValidator.php index feceb4b..45aaaff 100644 --- a/src/Validators/BluemConfigurationValidator.php +++ b/src/Validators/BluemConfigurationValidator.php @@ -11,6 +11,7 @@ use Bluem\BluemPHP\Constants; use Exception; +use RuntimeException; use Throwable; class BluemConfigurationValidator @@ -136,7 +137,9 @@ private function _validateProduction_accessToken($config) private function _validateBrandID($config) { if (! isset($config->brandID)) { - throw new Exception("brandID not set; please add this to your configuration when instantiating the Bluem integration"); + throw new RuntimeException( + "brandID not set; please add this to your configuration when instantiating the Bluem integration" + ); } return $config; diff --git a/src/Validators/WebhookSignatureValidation.php b/src/Validators/WebhookSignatureValidation.php index 39e5100..dd7ec4d 100644 --- a/src/Validators/WebhookSignatureValidation.php +++ b/src/Validators/WebhookSignatureValidation.php @@ -73,19 +73,35 @@ private function getKeyFileName(): string $prefix = 'webhook_bluem_nl_'; // 2025 certificate on production from July 18th, 8:30 CET time - if ($this->env === BLUEM_ENVIRONMENT_PRODUCTION && ( ( $current_date === "2025-07-18" && $current_time >= "08:30" ) || $current_date > "2025-07-18")) { + if ( + $this->env === BLUEM_ENVIRONMENT_PRODUCTION && ( + ( $current_date === "2025-07-18" && $current_time >= "08:30" ) + || $current_date > "2025-07-18") + ) { $timestamp = '20250717'; // 2025 certificate on testing & acceptance from July 17th, 8:30 CET time } elseif ( ($this->env === BLUEM_ENVIRONMENT_TESTING || $this->env === BLUEM_ENVIRONMENT_ACCEPTANCE) - && (($current_date === "2024-07-17" && $current_time >= "06:30") || $current_date > "2024-07-17") + && (($current_date === "2024-07-17" + && $current_time >= "06:30") || $current_date > "2024-07-17") ) { $timestamp = '20250717'; - } elseif (( $current_date === "2024-07-01" && $current_time >= "12:00" ) || $current_date > "2024-07-01") { + } elseif ( + ( $current_date === "2024-07-01" && $current_time >= "12:00" ) + || $current_date > "2024-07-01" + ) { $timestamp = '20240701'; - } elseif ($this->env === BLUEM_ENVIRONMENT_TESTING && ( ( $current_date === "2023-06-28" && $current_time >= "08:00" ) || $current_date > "2023-06-28")) { + } elseif ( + $this->env === BLUEM_ENVIRONMENT_TESTING + && ( ( $current_date === "2023-06-28" && $current_time >= "08:00" ) + || $current_date > "2023-06-28") + ) { $timestamp = '202306140200-202407050159'; - } elseif ($this->env === BLUEM_ENVIRONMENT_PRODUCTION && ( ( $current_date === "2023-07-04" && $current_time >= "08:00" ) || $current_date > "2023-07-04")) { + } elseif ( + $this->env === BLUEM_ENVIRONMENT_PRODUCTION + && ( ( $current_date === "2023-07-04" && $current_time >= "08:00" ) + || $current_date > "2023-07-04") + ) { $timestamp = '202306140200-202407050159'; } else { $timestamp = '202206090200-202307110159'; diff --git a/tests/Unit/IbanResponseTest.php b/tests/Unit/IbanResponseTest.php index 182926b..da47e19 100644 --- a/tests/Unit/IbanResponseTest.php +++ b/tests/Unit/IbanResponseTest.php @@ -12,7 +12,8 @@ public function testIbanResponseKnownResultExposesAllFields(): void { $xml = <<<'XML' - + KNOWN From 3878a654ee60ad31c1bf5b7422d0bccde310b004 Mon Sep 17 00:00:00 2001 From: "daan.rijpkema" Date: Sun, 5 Apr 2026 10:20:01 +0200 Subject: [PATCH 24/24] Add linting step to pre-commit hook Enhance the pre-commit hook to include a linting step after running unit tests for PHP files. This ensures code quality and adherence to coding standards before commits are finalized. --- .githooks/pre-commit | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 916f01e..fdfe632 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -4,6 +4,8 @@ set -eu if git diff --cached --name-only --diff-filter=ACM | grep -qE '\.php$'; then echo "PHP files changed — running unit tests..." make test_unit + + make lint else echo "No PHP changes — skipping unit tests." fi