diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..fdfe632 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,11 @@ +#!/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 + + make lint +else + echo "No PHP changes — skipping unit tests." +fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d5f141..81aa3d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,47 @@ name: CI - on: [push] - jobs: build-test: runs-on: ubuntu-latest + strategy: + matrix: + # add 8.4 soon + php-version: ['8.3'] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - 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 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: PHPCS Lint + run: make lint - name: PHPUnit Tests - uses: php-actions/phpunit@v3 + uses: php-actions/phpunit@v4 with: - version: 9.5 - php_version: 8.1 + php_version: ${{ matrix.php-version }} + version: 11 bootstrap: ./vendor/autoload.php configuration: ./.github/workflows/phpunit.xml diff --git a/.github/workflows/phpunit.xml b/.github/workflows/phpunit.xml index bc8631b..811d8cf 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/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. + diff --git a/Makefile b/Makefile index e4bd8d1..835842f 100644 --- a/Makefile +++ b/Makefile @@ -5,4 +5,26 @@ 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/ . + +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 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/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" } 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/* + + + + + + + + + + + + + + + + + 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 + diff --git a/src/Bluem.php b/src/Bluem.php index c1e61dc..8158b0a 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,9 +81,9 @@ class Bluem * * @throws InvalidBluemConfigurationException */ - public function __construct(mixed $rawConfig) + public function __construct(mixed $rawConfig, ?HttpTransportInterface $transport = null) { - if ($rawConfig ===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,13 +209,15 @@ 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 (! $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: " . @@ -229,11 +238,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, @@ -241,61 +250,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: @@ -306,11 +295,42 @@ 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 } } + 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 * @@ -319,7 +339,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), @@ -376,7 +396,6 @@ public function GetMaximumAmountFromTransactionResponse($response): object * @param $amount * * @throws DOMException - * @throws HTTP_Request2_LogicException * @throws RuntimeException */ public function Payment( @@ -618,7 +637,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/Constants.php b/src/Constants.php new file mode 100644 index 0000000..079ac99 --- /dev/null +++ b/src/Constants.php @@ -0,0 +1,40 @@ +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..ff1d2d2 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; @@ -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; @@ -127,7 +131,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..234bdce 100644 --- a/src/Requests/BluemRequest.php +++ b/src/Requests/BluemRequest.php @@ -1,4 +1,5 @@ * @@ -8,6 +9,8 @@ namespace Bluem\BluemPHP\Requests; +use Bluem\BluemPHP\Bluem; +use Bluem\BluemPHP\Constants; use Bluem\BluemPHP\Exceptions\InvalidBluemRequestException; use Bluem\BluemPHP\Helpers\BluemConfiguration; use Bluem\BluemPHP\Helpers\Now; @@ -116,7 +119,7 @@ class BluemRequest implements BluemRequestInterface * @throws InvalidBluemRequestException */ public function __construct( - $config, + BluemConfiguration $config, string $entranceCode = "", string $expectedReturn = "" ) { @@ -158,7 +161,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"; @@ -181,7 +184,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..fc6d673 100644 --- a/src/Requests/EmandateBluemRequest.php +++ b/src/Requests/EmandateBluemRequest.php @@ -1,4 +1,5 @@ * @@ -6,9 +7,9 @@ * with this source code in the file LICENSE. */ - 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"; @@ -161,30 +162,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/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..e49b9e9 100644 --- a/src/Requests/IdentityBluemRequest.php +++ b/src/Requests/IdentityBluemRequest.php @@ -1,4 +1,5 @@ * @@ -189,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/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..79e10bf 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' - || $expirationDateMonth !== '' && $expirationDateMonth !== '0' || $expirationDateYear !== '' && $expirationDateYear !== '0' + if ( + !empty($cardNumber) || !empty($name) || !empty($securityCode) + || !empty($expirationDateMonth) || !empty($expirationDateYear) ) { $this->context->addPaymentMethodDetails( [ - 'CardNumber'=>$cardNumber, - 'Name'=>$name, - 'SecurityCode'=>$securityCode, - 'ExpirationDateMonth'=>$expirationDateMonth, - 'ExpirationDateYear'=>$expirationDateYear, + 'CardNumber' => $cardNumber, + 'Name' => $name, + 'SecurityCode' => $securityCode, + 'ExpirationDateMonth' => $expirationDateMonth, + 'ExpirationDateYear' => $expirationDateYear, ] ); } @@ -301,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/Requests/PaymentStatusBluemRequest.php b/src/Requests/PaymentStatusBluemRequest.php index 197c079..81de5e0 100644 --- a/src/Requests/PaymentStatusBluemRequest.php +++ b/src/Requests/PaymentStatusBluemRequest.php @@ -1,4 +1,5 @@ * @@ -9,6 +10,7 @@ namespace Bluem\BluemPHP\Requests; use Bluem\BluemPHP\Contexts\PaymentsContext; +use Bluem\BluemPHP\Helpers\BluemConfiguration; class PaymentStatusBluemRequest extends BluemRequest { @@ -21,14 +23,15 @@ class PaymentStatusBluemRequest extends BluemRequest protected $xmlInterfaceName = "EPaymentInterface"; public function __construct( - $config, + BluemConfiguration $config, $transactionID, $expected_return = "", $entranceCode = "" ) { 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..5133aa1 100644 --- a/src/Responses/BluemResponse.php +++ b/src/Responses/BluemResponse.php @@ -19,10 +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; @@ -61,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'] . ""; @@ -78,7 +79,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..65f91af 100644 --- a/src/Responses/IBANNameCheckBluemResponse.php +++ b/src/Responses/IBANNameCheckBluemResponse.php @@ -1,4 +1,5 @@ * @@ -12,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"; @@ -29,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/src/Responses/IdentityStatusBluemResponse.php b/src/Responses/IdentityStatusBluemResponse.php index ae88b5d..82f2283 100644 --- a/src/Responses/IdentityStatusBluemResponse.php +++ b/src/Responses/IdentityStatusBluemResponse.php @@ -1,4 +1,5 @@ * @@ -10,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 cca34db..80b9f06 100644 --- a/src/Responses/IdentityTransactionBluemResponse.php +++ b/src/Responses/IdentityTransactionBluemResponse.php @@ -8,13 +8,14 @@ * 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 { - 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 da3816f..8095c86 100644 --- a/src/Responses/MandateStatusBluemResponse.php +++ b/src/Responses/MandateStatusBluemResponse.php @@ -1,4 +1,5 @@ * @@ -16,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/MandateTransactionBluemResponse.php b/src/Responses/MandateTransactionBluemResponse.php index dd9471d..97c33ed 100644 --- a/src/Responses/MandateTransactionBluemResponse.php +++ b/src/Responses/MandateTransactionBluemResponse.php @@ -1,4 +1,5 @@ * @@ -10,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'; diff --git a/src/Responses/PaymentStatusBluemResponse.php b/src/Responses/PaymentStatusBluemResponse.php index c897fee..ff5d835 100644 --- a/src/Responses/PaymentStatusBluemResponse.php +++ b/src/Responses/PaymentStatusBluemResponse.php @@ -8,14 +8,14 @@ * 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 { - 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'; + public static ?string $error_response_type = 'PaymentErrorResponse'; } diff --git a/src/Responses/PaymentTransactionBluemResponse.php b/src/Responses/PaymentTransactionBluemResponse.php index 798607d..8543e6f 100644 --- a/src/Responses/PaymentTransactionBluemResponse.php +++ b/src/Responses/PaymentTransactionBluemResponse.php @@ -8,13 +8,14 @@ * 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 { - 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'; } 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/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 @@ + * @@ -8,18 +9,11 @@ namespace Bluem\BluemPHP\Validators; +use Bluem\BluemPHP\Constants; use Exception; +use RuntimeException; use Throwable; -define("BLUEM_EXPECTED_RETURN_NONE", "none"); -define("BLUEM_EXPECTED_RETURN_SUCCESS", "success"); -define("BLUEM_EXPECTED_RETURN_CANCELLED", "cancelled"); -define("BLUEM_EXPECTED_RETURN_EXPIRED", "expired"); -define("BLUEM_EXPECTED_RETURN_FAILURE", "failure"); -define("BLUEM_EXPECTED_RETURN_OPEN", "open"); -define("BLUEM_EXPECTED_RETURN_PENDING", "pending"); - - class BluemConfigurationValidator { private ?array $errors = null; @@ -44,7 +38,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,19 +49,24 @@ 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) ) { throw new Exception( - "Invalid environment setting, should be either - 'test', 'acc' or 'prod'" + "environment not set; please add this to your configuration when instantiating the Bluem integration" + ); + } + + if ( + !in_array( + $config->environment, + Constants::ENVIRONMENTS, + true + ) + ) { + throw new Exception( + sprintf(sprintf("Invalid environment setting (%s), should be one of: + %%s", $config->environment), implode(', ', Constants::ENVIRONMENTS)) ); } @@ -103,7 +101,8 @@ private function _validateSenderID($config) private function _validateTest_accessToken($config) { - if ($config->environment === BLUEM_ENVIRONMENT_TESTING + if ( + $config->environment === Constants::TESTING_ENVIRONMENT && ( ! isset($config->test_accessToken) || $config->test_accessToken === "" ) ) { @@ -120,7 +119,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 === Constants::PRODUCTION_ENVIRONMENT && ( ! isset($config->production_accessToken) || $config->production_accessToken === "" ) ) { @@ -137,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; @@ -149,16 +151,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; @@ -179,13 +181,14 @@ private function _validateThanksPage($config) */ private function _validateExpectedReturnStatus($config): mixed { - if ($config->environment === BLUEM_ENVIRONMENT_TESTING) { - if (! isset($config->expectedReturnStatus) + 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 @@ -201,13 +204,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 ]; } @@ -219,7 +222,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 @@ * @@ -72,17 +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")) { + } 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")) { + } 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/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/Acceptance/AcceptanceTestCase.php b/tests/Acceptance/AcceptanceTestCase.php new file mode 100644 index 0000000..953da56 --- /dev/null +++ b/tests/Acceptance/AcceptanceTestCase.php @@ -0,0 +1,69 @@ + '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) + { + $response = simplexml_load_string($xml, $className); + + if ($response === false) { + 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..c91d653 --- /dev/null +++ b/tests/Acceptance/IbanCheckAcceptanceTest.php @@ -0,0 +1,34 @@ +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' + ); + } +} diff --git a/tests/Acceptance/IdentityAcceptanceTest.php b/tests/Acceptance/IdentityAcceptanceTest.php new file mode 100644 index 0000000..172fc47 --- /dev/null +++ b/tests/Acceptance/IdentityAcceptanceTest.php @@ -0,0 +1,41 @@ +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', + ); + } +} diff --git a/tests/Acceptance/MandatesAcceptanceTest.php b/tests/Acceptance/MandatesAcceptanceTest.php new file mode 100644 index 0000000..61bdd21 --- /dev/null +++ b/tests/Acceptance/MandatesAcceptanceTest.php @@ -0,0 +1,48 @@ +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' + ); + } +} diff --git a/tests/Acceptance/PaymentsAcceptanceTest.php b/tests/Acceptance/PaymentsAcceptanceTest.php new file mode 100644 index 0000000..7d1e0d6 --- /dev/null +++ b/tests/Acceptance/PaymentsAcceptanceTest.php @@ -0,0 +1,85 @@ +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' + ); + } +} 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/Integration/BluemGenericTestCase.php b/tests/Integration/BluemGenericTestCase.php index ec47072..176d616 100644 --- a/tests/Integration/BluemGenericTestCase.php +++ b/tests/Integration/BluemGenericTestCase.php @@ -1,4 +1,5 @@ * @@ -21,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 */ @@ -31,14 +44,20 @@ 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(); + $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; + $bluem_config = new stdClass(); $bluem_config->environment = $_ENV['BLUEM_ENV']; $bluem_config->senderID = $_ENV['BLUEM_SENDER_ID']; @@ -58,7 +77,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 +106,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..40f463c 100644 --- a/tests/Integration/BluemMainTest.php +++ b/tests/Integration/BluemMainTest.php @@ -1,4 +1,5 @@ fail("Could not create mandate: " . $exception->getMessage()); } + if ($response instanceof ErrorBluemResponse) { + $this->fail( + 'Got ErrorBluem response: ' . $response->Error() + ); + } + $this->assertInstanceOf( MandateTransactionBluemResponse::class, $response @@ -74,7 +80,6 @@ public function testCanMandateStatus(): void // @todo: deal with the corresponding status if proper or improper status request if ($response->Status()) { - $this->assertInstanceOf( MandateStatusBluemResponse::class, $response diff --git a/tests/Integration/IPAPITest.php b/tests/Integration/IPAPITest.php index 82dc781..5a03d94 100644 --- a/tests/Integration/IPAPITest.php +++ b/tests/Integration/IPAPITest.php @@ -1,4 +1,5 @@ * @@ -9,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 @@ -26,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); @@ -38,8 +40,8 @@ public static function NetherlandsIPTestDataProvider(): array { return [ [ - 'ipAddress'=>'31.187.128.0', - '$expectedNetherlands' => true, + '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..7a777e7 100644 --- a/tests/Unit/BluemTest.php +++ b/tests/Unit/BluemTest.php @@ -1,4 +1,5 @@ * @@ -14,6 +15,7 @@ use Bluem\BluemPHP\Interfaces\BluemResponseInterface; use Bluem\BluemPHP\Requests\BluemRequest; use Bluem\BluemPHP\Responses\ErrorBluemResponse; +use Bluem\BluemPHP\Tests\FakeHttpTransport; use PHPUnit\Framework\TestCase; use RuntimeException; use stdClass; @@ -21,6 +23,7 @@ class BluemTest extends TestCase { private Bluem $bluem; + private FakeHttpTransport $transport; /** * @throws InvalidBluemConfigurationException @@ -29,7 +32,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); } @@ -46,13 +50,37 @@ public function testConstructorWithInvalidConfig(): void } - public function testMandateWithValidParameters(): void + public function testIdentityRequestWithValidParameters(): 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 @@ -86,7 +114,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'; diff --git a/tests/Unit/IbanResponseTest.php b/tests/Unit/IbanResponseTest.php new file mode 100644 index 0000000..da47e19 --- /dev/null +++ b/tests/Unit/IbanResponseTest.php @@ -0,0 +1,76 @@ + + + + + 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 @@ +