diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
index 6e8578c..1327a09 100644
--- a/.github/workflows/phpunit.yml
+++ b/.github/workflows/phpunit.yml
@@ -14,6 +14,7 @@ jobs:
fail-fast: false
matrix:
php-version: ['8.1', '8.2', '8.3', '8.4', '8.5']
+ guzzle: ['^7', '^8']
steps:
- name: Checkout code
@@ -26,7 +27,9 @@ jobs:
coverage: xdebug
- name: Install dependencies
- run: composer install --no-interaction --no-progress --prefer-dist
+ run: |
+ composer require "guzzlehttp/guzzle:${{ matrix.guzzle }}" --no-update --no-interaction
+ composer update --no-interaction --no-progress --prefer-dist
- name: Run PHPUnit with coverage
run: vendor/bin/phpunit --coverage-clover coverage.xml
diff --git a/Tests/Unit/Model/PayOrderTest.php b/Tests/Unit/Model/PayOrderTest.php
index 46735d1..5349430 100644
--- a/Tests/Unit/Model/PayOrderTest.php
+++ b/Tests/Unit/Model/PayOrderTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Unit;
+namespace Tests\Unit\Model;
use PayNL\Sdk\Model\Amount;
use PayNL\Sdk\Model\Pay\PayOrder;
diff --git a/Tests/Unit/Model/PayStatusTest.php b/Tests/Unit/Model/PayStatusTest.php
index 634a7c0..1fba6ff 100644
--- a/Tests/Unit/Model/PayStatusTest.php
+++ b/Tests/Unit/Model/PayStatusTest.php
@@ -2,6 +2,8 @@
declare(strict_types=1);
+namespace Tests\Unit\Model;
+
use PayNL\Sdk\Model\Pay\PayStatus;
use PHPUnit\Framework\TestCase;
diff --git a/Tests/Unit/Model/PayloadTest.php b/Tests/Unit/Model/PayloadTest.php
index d158655..00e5eb0 100644
--- a/Tests/Unit/Model/PayloadTest.php
+++ b/Tests/Unit/Model/PayloadTest.php
@@ -2,12 +2,12 @@
declare(strict_types=1);
-namespace Tests\Unit;
+namespace Tests\Unit\Model;
use PayNL\Sdk\Model\Pay\PayLoad;
use PHPUnit\Framework\TestCase;
-class PayLoadTest extends TestCase
+class PayloadTest extends TestCase
{
public function testLegacyPayloadMapping(): void
{
diff --git a/Tests/Unit/OrderRequests/OrderApproveRequestTest.php b/Tests/Unit/OrderRequests/OrderApproveRequestTest.php
index 367bd9c..aa21369 100644
--- a/Tests/Unit/OrderRequests/OrderApproveRequestTest.php
+++ b/Tests/Unit/OrderRequests/OrderApproveRequestTest.php
@@ -1,6 +1,6 @@
getProperty('optimize');
- $optimizeProperty->setAccessible(true);
$this->assertEquals([
'flow' => 'fastCheckout',
@@ -43,7 +42,6 @@ public function testSetReturnUrl(): void
$reflection = new \ReflectionClass($request);
$returnUrlProperty = $reflection->getProperty('returnUrl');
- $returnUrlProperty->setAccessible(true);
$this->assertEquals('https://example.com/return', $returnUrlProperty->getValue($request));
}
@@ -58,7 +56,6 @@ public function testSetAmount(): void
$reflection = new \ReflectionClass($request);
$amountProperty = $reflection->getProperty('amount');
- $amountProperty->setAccessible(true);
$this->assertEquals(12345, $amountProperty->getValue($request));
}
@@ -73,10 +70,8 @@ public function testSetAmountUsingObject(): void
$reflection = new \ReflectionClass($request);
$amountProperty = $reflection->getProperty('amount');
- $amountProperty->setAccessible(true);
$currencyProperty = $reflection->getProperty('currency');
- $currencyProperty->setAccessible(true);
$this->assertEquals(12345, $amountProperty->getValue($request));
$this->assertEquals('EUR', $currencyProperty->getValue($request));
@@ -94,7 +89,6 @@ public function testSetReference(): void
$reflection = new \ReflectionClass($request);
$referenceProperty = $reflection->getProperty('reference');
- $referenceProperty->setAccessible(true);
$this->assertEquals('Order123', $referenceProperty->getValue($request));
}
@@ -124,9 +118,7 @@ public function testSetNotification(): void
$reflection = new \ReflectionClass($request);
$notificationTypeProperty = $reflection->getProperty('notificationType');
- $notificationTypeProperty->setAccessible(true);
$notificationRecipientProperty = $reflection->getProperty('notificationRecipient');
- $notificationRecipientProperty->setAccessible(true);
$this->assertEquals('email', $notificationTypeProperty->getValue($request));
$this->assertEquals('test@example.com', $notificationRecipientProperty->getValue($request));
@@ -170,7 +162,6 @@ public function testSetCustomer(): void
$reflection = new \ReflectionClass($request);
$customerProperty = $reflection->getProperty('customer');
- $customerProperty->setAccessible(true);
$this->assertSame($mockCustomer, $customerProperty->getValue($request));
}
@@ -187,7 +178,6 @@ public function testSetOrder(): void
$reflection = new \ReflectionClass($request);
$orderProperty = $reflection->getProperty('order');
- $orderProperty->setAccessible(true);
$this->assertSame($mockOrder, $orderProperty->getValue($request));
}
@@ -204,7 +194,6 @@ public function testSetStats(): void
$reflection = new \ReflectionClass($request);
$statsProperty = $reflection->getProperty('stats');
- $statsProperty->setAccessible(true);
$this->assertSame($mockStats, $statsProperty->getValue($request));
}
diff --git a/Tests/Unit/OrderRequests/OrderStatusRequestTest.php b/Tests/Unit/OrderRequests/OrderStatusRequestTest.php
index a46acaf..7536c9c 100644
--- a/Tests/Unit/OrderRequests/OrderStatusRequestTest.php
+++ b/Tests/Unit/OrderRequests/OrderStatusRequestTest.php
@@ -1,6 +1,6 @@
setServiceId('SL-1234-5678');
$reflection = new \ReflectionClass($request);
$serviceIdProperty = $reflection->getProperty('serviceId');
- $serviceIdProperty->setAccessible(true);
$this->assertEquals('SL-1234-5678', $serviceIdProperty->getValue($request));
}
@@ -77,7 +76,6 @@ public function testSetCardNumber(): void
$request->setCardNumber('1234-5678-9012-3456');
$reflection = new \ReflectionClass($request);
$cardNumberProperty = $reflection->getProperty('cardNumber');
- $cardNumberProperty->setAccessible(true);
$this->assertEquals('1234-5678-9012-3456', $cardNumberProperty->getValue($request));
}
@@ -93,7 +91,6 @@ public function testSetPinCode(): void
$request->setPinCode('123456');
$reflection = new \ReflectionClass($request);
$pinCodeProperty = $reflection->getProperty('pinCode');
- $pinCodeProperty->setAccessible(true);
$this->assertEquals('123456', $pinCodeProperty->getValue($request));
}
@@ -109,7 +106,6 @@ public function testSetPointOfInteraction(): void
$request->setPointOfInteraction('ON_THE_MOVE');
$reflection = new \ReflectionClass($request);
$poiProperty = $reflection->getProperty('pointOfInteraction');
- $poiProperty->setAccessible(true);
$this->assertEquals('ON_THE_MOVE', $poiProperty->getValue($request));
}
diff --git a/Tests/Unit/OrderRequests/VoucherPaymentRequestTest.php b/Tests/Unit/OrderRequests/VoucherPaymentRequestTest.php
index 8ccdcab..b828c45 100644
--- a/Tests/Unit/OrderRequests/VoucherPaymentRequestTest.php
+++ b/Tests/Unit/OrderRequests/VoucherPaymentRequestTest.php
@@ -1,6 +1,6 @@
setCardNumber('1234-5678-9012-3456');
$reflection = new \ReflectionClass($request);
$cardNumberProperty = $reflection->getProperty('cardNumber');
- $cardNumberProperty->setAccessible(true);
$this->assertEquals('1234-5678-9012-3456', $cardNumberProperty->getValue($request));
}
@@ -78,7 +77,6 @@ public function testSetPinCode(): void
$request->setPinCode('123456');
$reflection = new \ReflectionClass($request);
$pinCodeProperty = $reflection->getProperty('pinCode');
- $pinCodeProperty->setAccessible(true);
$this->assertEquals('123456', $pinCodeProperty->getValue($request));
}
@@ -94,7 +92,6 @@ public function testSetPointOfInteraction(): void
$request->setPointOfInteraction('ON_THE_MOVE');
$reflection = new \ReflectionClass($request);
$poiProperty = $reflection->getProperty('pointOfInteraction');
- $poiProperty->setAccessible(true);
$this->assertEquals('ON_THE_MOVE', $poiProperty->getValue($request));
}
diff --git a/Tests/Unit/Request/AbstractRequestExecuteTest.php b/Tests/Unit/Request/AbstractRequestExecuteTest.php
new file mode 100644
index 0000000..fa31974
--- /dev/null
+++ b/Tests/Unit/Request/AbstractRequestExecuteTest.php
@@ -0,0 +1,65 @@
+ 'application/json'], '{"id":"1"}'),
+ ]);
+ $client = new Client([
+ 'handler' => HandlerStack::create($mock),
+ 'base_uri' => 'https://rest.pay.nl/v2/',
+ ]);
+
+ $request = new Request('orders', Request::METHOD_GET);
+ $request->applyClient($client, 'https://rest.pay.nl/v2/');
+
+ $response = new Response();
+ $request->execute($response);
+
+ $this->assertSame(200, $response->getStatusCode());
+ $this->assertSame('{"id":"1"}', $response->getRawBody());
+
+ $lastRequest = $mock->getLastRequest();
+ $this->assertNotNull($lastRequest);
+ $this->assertSame('GET', $lastRequest->getMethod());
+ $this->assertSame('https://rest.pay.nl/v2/orders', (string)$lastRequest->getUri());
+ }
+
+ public function testExecuteUsesAbsoluteUriWhenUrlOptionIsSet(): void
+ {
+ $mock = new MockHandler([
+ new GuzzleResponse(200, ['Content-Type' => 'application/json'], '{"ok":true}'),
+ ]);
+ $client = new Client([
+ 'handler' => HandlerStack::create($mock),
+ 'base_uri' => 'https://rest.pay.nl/v2/',
+ ]);
+
+ $request = new Request('orders', Request::METHOD_POST, [], ['url' => 'https://failover.pay.nl']);
+ $request->applyClient($client, 'https://rest.pay.nl/v2/');
+
+ $response = new Response();
+ $request->execute($response);
+
+ $this->assertSame(200, $response->getStatusCode());
+
+ $lastRequest = $mock->getLastRequest();
+ $this->assertNotNull($lastRequest);
+ $this->assertSame('POST', $lastRequest->getMethod());
+ $this->assertSame('https://failover.pay.nl/orders', (string)$lastRequest->getUri());
+ }
+}
diff --git a/Tests/Unit/Response/VoucherInfoResponseTest.php b/Tests/Unit/Response/VoucherInfoResponseTest.php
index 9b2d4b8..3038153 100644
--- a/Tests/Unit/Response/VoucherInfoResponseTest.php
+++ b/Tests/Unit/Response/VoucherInfoResponseTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Unit;
+namespace Tests\Unit\Response;
use PayNL\Sdk\Model\Amount;
use PayNL\Sdk\Model\Response\VoucherInfoResponse;
diff --git a/Tests/Unit/Util/ExchangeResponseTest.php b/Tests/Unit/Util/ExchangeResponseTest.php
index a71126f..ef6537a 100644
--- a/Tests/Unit/Util/ExchangeResponseTest.php
+++ b/Tests/Unit/Util/ExchangeResponseTest.php
@@ -2,6 +2,8 @@
declare(strict_types=1);
+namespace Tests\Unit\Util;
+
use PayNL\Sdk\Util\ExchangeResponse;
use PHPUnit\Framework\TestCase;
@@ -11,7 +13,6 @@ private function getProperty(object $object, string $name): mixed
{
$ref = new \ReflectionClass($object);
$prop = $ref->getProperty($name);
- $prop->setAccessible(true);
return $prop->getValue($object);
}
diff --git a/Tests/Unit/Util/ExchangeTest.php b/Tests/Unit/Util/ExchangeTest.php
index e8b7b8a..534548d 100644
--- a/Tests/Unit/Util/ExchangeTest.php
+++ b/Tests/Unit/Util/ExchangeTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Unit;
+namespace Tests\Unit\Util;
use PayNL\Sdk\Util\Exchange;
use PayNL\Sdk\Util\ExchangeResponse;
@@ -25,7 +25,6 @@ private function setHeaders(Exchange $exchange, array $headers): void
{
$refClass = new ReflectionClass(Exchange::class);
$prop = $refClass->getProperty('headers');
- $prop->setAccessible(true);
$prop->setValue($exchange, $headers);
}
@@ -107,6 +106,22 @@ public function testIsSignExchangeFalseWhenSignatureMethodMissingOrNotHmac(): vo
$this->assertFalse($exchange->isSignExchange());
}
+ public function testIsSignExchangeReadsSignatureMethodFromServer(): void
+ {
+ $previous = $_SERVER['HTTP_SIGNATURE_METHOD'] ?? null;
+ $_SERVER['HTTP_SIGNATURE_METHOD'] = 'HMAC';
+
+ try {
+ $this->assertTrue($this->createExchange()->isSignExchange());
+ } finally {
+ if ($previous === null) {
+ unset($_SERVER['HTTP_SIGNATURE_METHOD']);
+ } else {
+ $_SERVER['HTTP_SIGNATURE_METHOD'] = $previous;
+ }
+ }
+ }
+
public function testCheckSignExchangeReturnsFalseWhenNotSigningExchange(): void
{
$exchange = $this->createExchange();
diff --git a/Tests/Unit/Util/PayCacheTest.php b/Tests/Unit/Util/PayCacheTest.php
index 70188a2..8bf6937 100644
--- a/Tests/Unit/Util/PayCacheTest.php
+++ b/Tests/Unit/Util/PayCacheTest.php
@@ -2,6 +2,8 @@
declare(strict_types=1);
+namespace Tests\Unit\Util;
+
use PayNL\Sdk\Util\PayCache;
use PHPUnit\Framework\TestCase;
@@ -11,7 +13,6 @@ private function invokeMethod(object $object, string $method, array $args = []):
{
$refClass = new \ReflectionClass($object);
$refMethod = $refClass->getMethod($method);
- $refMethod->setAccessible(true);
return $refMethod->invokeArgs($object, $args);
}
diff --git a/Tests/Unit/Util/VatFunctionsTest.php b/Tests/Unit/Util/VatFunctionsTest.php
index a8abbc0..60c60e4 100644
--- a/Tests/Unit/Util/VatFunctionsTest.php
+++ b/Tests/Unit/Util/VatFunctionsTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Unit;
+namespace Tests\Unit\Util;
use PHPUnit\Framework\TestCase;
diff --git a/composer.json b/composer.json
index de8ee31..764a3bc 100644
--- a/composer.json
+++ b/composer.json
@@ -7,7 +7,7 @@
"php": "^8.1",
"ext-curl": "*",
"ext-json": "*",
- "guzzlehttp/guzzle": "^7",
+ "guzzlehttp/guzzle": "^7 || ^8",
"psr/container": "^2 || ^1",
"psr/http-message": "^2 || ^1"
},
@@ -46,7 +46,7 @@
},
"autoload-dev": {
"psr-4": {
- "Tests\\": "tests/"
+ "Tests\\": "Tests/"
}
},
"minimum-stability": "dev",
@@ -62,8 +62,10 @@
},
"scripts": {
"test": [
+ "@phpunit",
"@style-check"
],
+ "phpunit": "vendor/bin/phpunit",
"style-check": [
"@phpstan",
"@phpcs"
diff --git a/phpcs.xml b/phpcs.xml
index 2c11b4e..b6106db 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -17,7 +17,7 @@
- -->
+
diff --git a/src/Api/Api.php b/src/Api/Api.php
index e36e56c..d03f6bc 100644
--- a/src/Api/Api.php
+++ b/src/Api/Api.php
@@ -101,7 +101,11 @@ protected function setAuthAdapter(AuthAdapterInterface $adapter): self
public function doHandle(RequestInterface $request, Response $response): Response
{
$format = $request->getFormat();
- $request->applyClient($this->getClient());
+ if ($request instanceof AbstractRequest) {
+ $request->applyClient($this->getClient(), $this->getConfiguredBaseUri());
+ } else {
+ $request->applyClient($this->getClient());
+ }
# Apply the correct headers based on the formats set on request and response object
# and also add the authentication header which is based on the authentication adapter
@@ -126,4 +130,21 @@ public function doHandle(RequestInterface $request, Response $response): Respons
return $response;
}
+
+ /**
+ * @return string
+ */
+ private function getConfiguredBaseUri(): string
+ {
+ $api = $this->getOption('api');
+ if (!is_array($api)) {
+ return '';
+ }
+
+ $url = rtrim((string)($api['url'] ?? ''), '/');
+ $version = $api['version'] ?? '';
+ $pathVersion = $version === '' ? '' : '/v' . $version;
+
+ return $url . $pathVersion . '/';
+ }
}
diff --git a/src/Request/AbstractRequest.php b/src/Request/AbstractRequest.php
index c28cb6c..993d67a 100644
--- a/src/Request/AbstractRequest.php
+++ b/src/Request/AbstractRequest.php
@@ -27,7 +27,7 @@
};
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
-use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Psr7\Request as Psr7Request;
/**
* Class AbstractRequest
@@ -82,6 +82,11 @@ abstract class AbstractRequest implements
*/
protected $client;
+ /**
+ * @var string
+ */
+ protected $clientBaseUri = '';
+
/**
* @var array
*/
@@ -333,11 +338,13 @@ public function setBody(mixed $body): self
/**
* @param Client $client
+ * @param string $baseUri
* @return $this
*/
- public function applyClient(Client $client): self
+ public function applyClient(Client $client, string $baseUri = ''): self
{
$this->client = $client;
+ $this->clientBaseUri = $baseUri;
return $this;
}
@@ -435,28 +442,35 @@ public function execute(Response $response): void
$filters = $this->getFilters();
$uri = trim($uri . '?' . implode('&', $filters), '?');
- $url = $this->getOption('url');
+ $overrideUrl = $this->getOption('url');
+ $baseUri = is_string($overrideUrl) && $overrideUrl !== ''
+ ? $overrideUrl
+ : (string)$this->clientBaseUri;
$this->dumpPreStringAdvanced($this->getBody(), 'Request body', 400);
+ $requestBody = '';
+ $curlRequest = '';
+ $rawBody = '';
+ $body = '';
+ $statusCode = 500;
+
try {
$guzzleClient = $this->getClient();
if (false === ($guzzleClient instanceof Client)) {
throw new RuntimeException('No HTTP client found', 500);
}
- if (!empty($url)) {
- $guzzleConfig = $guzzleClient->getConfig();
- # This also will override the version
- $guzzleConfig['base_uri'] = $url;
- $guzzleClient = new Client($guzzleConfig);
- }
$requestBody = $this->getBody();
+ $method = strtoupper($this->getMethod());
+ $requestUri = $uri;
+ if (is_string($overrideUrl) && $overrideUrl !== '') {
+ $requestUri = rtrim($overrideUrl, '/') . '/' . $uri;
+ }
- # Create a Guzzle PSR 7 Request
- $guzzleRequest = new \GuzzleHttp\Psr7\Request($this->getMethod(), $uri, $this->getHeaders(), $requestBody);
+ $guzzleRequest = new Psr7Request($method, $requestUri, $this->getHeaders(), $requestBody);
- $curlRequest = 'curl -X ' . $this->getMethod() . ' ' . $guzzleClient->getConfig('base_uri') . $uri;
+ $curlRequest = 'curl -X ' . $method . ' ' . rtrim((string)$baseUri, '/') . '/' . $uri;
foreach ($this->getHeaders() as $headerfield => $headervalue) {
$curlRequest .= ' -H "' . $headerfield . ': ' . $headervalue . '"';
}
@@ -464,39 +478,18 @@ public function execute(Response $response): void
$curlRequest .= empty($requestBody) ? '' : ' -d \'' . $requestBody . '\'';
$this->dumpPreString($curlRequest, 'Curl request');
- $this->dumpPreString(rtrim((string)$guzzleClient->getConfig('base_uri'), '/') . '/' . $guzzleRequest->getUri(), 'Requested URL');
+ $this->dumpPreString(rtrim((string)$baseUri, '/') . '/' . $guzzleRequest->getUri(), 'Requested URL');
$this->dumpPreString(implode(PHP_EOL, array_map(static function ($item, $key) {
return "{$key}: {$item}";
}, $this->getHeaders(), array_keys($this->getHeaders()))), 'Headers');
- $guzzleResponse = $guzzleClient->send($guzzleRequest);
-
+ $guzzleResponse = $guzzleClient->send($guzzleRequest, ['http_errors' => false]);
$rawBody = $guzzleResponse->getBody()->getContents();
-
$statusCode = $guzzleResponse->getStatusCode();
$body = $rawBody;
- } catch (RequestException $re) {
- $errorMessages = '';
- $rawBody = $re->getMessage();
-
- if (null !== $re->getResponse()) {
- $guzzleExceptionBody = $re->getResponse()->getBody();
- $size = $guzzleExceptionBody->isSeekable() === true ? (int)$guzzleExceptionBody->getSize() : 0;
-
- if (0 < $size) {
- $content = $guzzleExceptionBody->read($size);
- $guzzleExceptionBody->rewind();
-
- $errorMessages = $content;
- }
-
- $rawBody = $errorMessages;
- }
- $statusCode = $re->getCode();
- $body = '';
- if ('' !== $errorMessages) {
- $body = $this->getErrorsString($response->getFormat(), (int)$statusCode, $errorMessages);
+ if ($statusCode >= 400 && $rawBody !== '') {
+ $body = $this->getErrorsString($response->getFormat(), $statusCode, $rawBody);
}
} catch (GuzzleException | ExceptionInterface $e) {
$statusCode = $e->getCode() ?: 500;
@@ -505,7 +498,7 @@ public function execute(Response $response): void
}
if (function_exists('displayPayRequest')) {
- displayPayRequest($guzzleClient->getConfig('base_uri') . $uri, $requestBody ?? '', $rawBody, $curlRequest ?? '');
+ displayPayRequest(rtrim((string)$baseUri, '/') . '/' . $uri, $requestBody, $rawBody, $curlRequest);
}
$response->setStatusCode($statusCode)->setRawBody($rawBody)->setBody($body);
diff --git a/src/Util/Exchange.php b/src/Util/Exchange.php
index b74073a..f66924e 100644
--- a/src/Util/Exchange.php
+++ b/src/Util/Exchange.php
@@ -451,7 +451,24 @@ public function isSignExchange(): bool
private function getRequestHeaders(): bool|array|string
{
if (empty($this->headers)) {
- $this->headers = array_change_key_case(getallheaders());
+ $headers = [];
+ foreach ($_SERVER as $name => $value) {
+ if (!is_string($name)) {
+ continue;
+ }
+ if (str_starts_with($name, 'HTTP_')) {
+ $headers[str_replace('_', '-', substr($name, 5))] = $value;
+ continue;
+ }
+ if ($name === 'CONTENT_TYPE') {
+ $headers['Content-Type'] = $value;
+ continue;
+ }
+ if ($name === 'CONTENT_LENGTH') {
+ $headers['Content-Length'] = $value;
+ }
+ }
+ $this->headers = array_change_key_case($headers);
}
return $this->headers;
}