From f355b1a40e210499323067572d1c03812778adbc Mon Sep 17 00:00:00 2001 From: SimonZanta Date: Mon, 18 May 2026 10:48:46 +0200 Subject: [PATCH 1/5] feat: add coverage report generation and artifact upload to tests workflow - Add --coverage --coverage-xml and --coverage-html flags to codecept run - Upload coverage report (XML + HTML) as GitHub Actions artifact on success or failure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/tests.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b8b04a6..54c6695 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -95,7 +95,16 @@ jobs: - name: Run Codeception Tests run: | - vendor/bin/codecept run -v + vendor/bin/codecept run -v --coverage --coverage-xml coverage.xml --coverage-html coverage-html + + - name: Upload coverage report + if: ${{ success() || failure() }} + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + tests/_output/coverage.xml + tests/_output/coverage-html/ - name: IP check on Failure if: ${{ failure() }} From 5e3fa81d7734849f848b8eef4cd749d45d3aebd3 Mon Sep 17 00:00:00 2001 From: SimonZanta Date: Mon, 18 May 2026 11:23:21 +0200 Subject: [PATCH 2/5] fix: set XDEBUG_MODE=coverage env var for codecept run Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54c6695..f5349f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,6 +94,8 @@ jobs: run: "composer dump-autoload --optimize" - name: Run Codeception Tests + env: + XDEBUG_MODE: coverage run: | vendor/bin/codecept run -v --coverage --coverage-xml coverage.xml --coverage-html coverage-html From eaba157b26e350fe046c483d2f09938ba4bba8f8 Mon Sep 17 00:00:00 2001 From: SimonZanta Date: Mon, 18 May 2026 11:27:21 +0200 Subject: [PATCH 3/5] fix: correct coverage HTML output path in artifact upload Codeception outputs to coverage/ not coverage-html/ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5349f1..99757b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: env: XDEBUG_MODE: coverage run: | - vendor/bin/codecept run -v --coverage --coverage-xml coverage.xml --coverage-html coverage-html + vendor/bin/codecept run -v --coverage --coverage-xml coverage.xml --coverage-html - name: Upload coverage report if: ${{ success() || failure() }} @@ -106,7 +106,7 @@ jobs: name: coverage-report path: | tests/_output/coverage.xml - tests/_output/coverage-html/ + tests/_output/coverage/ - name: IP check on Failure if: ${{ failure() }} From 1dce119644e6e165939d5b02e85b262345c19114 Mon Sep 17 00:00:00 2001 From: SimonZanta Date: Mon, 18 May 2026 15:07:08 +0200 Subject: [PATCH 4/5] feat: add unit and integration tests for various SDK entities and responses --- tests/Integration/ClientTerminalCest.php | 184 ++++++++++ tests/Integration/ComgateBuilderCest.php | 138 +++++++ .../Entity/PaymentNotificationCest.php | 111 ++++++ .../Entity/Request/TerminalRequestsCest.php | 96 +++++ tests/Integration/TerminalMethodSyncCest.php | 164 +++++++++ tests/Unit/ConfigCest.php | 71 ++++ tests/Unit/Entity/PaymentCardCest.php | 169 +++++++++ tests/Unit/Entity/PaymentEntityCest.php | 205 +++++++++++ tests/Unit/Entity/PaymentUrlGettersCest.php | 124 +++++++ .../Unit/Entity/Request/MiscRequestsCest.php | 149 ++++++++ .../Unit/Entity/Request/OtherRequestsCest.php | 182 +++++++++ .../Request/PaymentCreateRequestCest.php | 264 +++++++++++++ tests/Unit/Entity/Request/RequestUrnCest.php | 291 +++++++++++++++ .../TerminalCancelStatusRequestsCest.php | 101 +++++ .../Response/FileTransferResponsesCest.php | 137 +++++++ .../Entity/Response/MiscResponsesCest.php | 119 ++++++ .../MotoPaymentCreateResponseCest.php | 117 ++++++ .../Response/PaymentCreateResponseCest.php | 98 +++++ .../Response/PaymentStatusResponseCest.php | 259 +++++++++++++ .../Entity/Response/PreauthResponsesCest.php | 181 +++++++++ .../Entity/Response/SimpleResponsesCest.php | 213 +++++++++++ .../Entity/Response/TerminalResponsesCest.php | 346 ++++++++++++++++++ .../Entity/Response/TransferResponsesCest.php | 152 ++++++++ .../Unit/Entity/TerminalMotoEntitiesCest.php | 165 +++++++++ tests/Unit/Http/PsrAdaptersCest.php | 213 +++++++++++ tests/Unit/Http/QueryParseCest.php | 115 ++++++ 26 files changed, 4364 insertions(+) create mode 100644 tests/Integration/ClientTerminalCest.php create mode 100644 tests/Integration/ComgateBuilderCest.php create mode 100644 tests/Integration/Entity/PaymentNotificationCest.php create mode 100644 tests/Integration/Entity/Request/TerminalRequestsCest.php create mode 100644 tests/Integration/TerminalMethodSyncCest.php create mode 100644 tests/Unit/ConfigCest.php create mode 100644 tests/Unit/Entity/PaymentCardCest.php create mode 100644 tests/Unit/Entity/PaymentEntityCest.php create mode 100644 tests/Unit/Entity/PaymentUrlGettersCest.php create mode 100644 tests/Unit/Entity/Request/MiscRequestsCest.php create mode 100644 tests/Unit/Entity/Request/OtherRequestsCest.php create mode 100644 tests/Unit/Entity/Request/PaymentCreateRequestCest.php create mode 100644 tests/Unit/Entity/Request/RequestUrnCest.php create mode 100644 tests/Unit/Entity/Request/TerminalCancelStatusRequestsCest.php create mode 100644 tests/Unit/Entity/Response/FileTransferResponsesCest.php create mode 100644 tests/Unit/Entity/Response/MiscResponsesCest.php create mode 100644 tests/Unit/Entity/Response/MotoPaymentCreateResponseCest.php create mode 100644 tests/Unit/Entity/Response/PaymentCreateResponseCest.php create mode 100644 tests/Unit/Entity/Response/PaymentStatusResponseCest.php create mode 100644 tests/Unit/Entity/Response/PreauthResponsesCest.php create mode 100644 tests/Unit/Entity/Response/SimpleResponsesCest.php create mode 100644 tests/Unit/Entity/Response/TerminalResponsesCest.php create mode 100644 tests/Unit/Entity/Response/TransferResponsesCest.php create mode 100644 tests/Unit/Entity/TerminalMotoEntitiesCest.php create mode 100644 tests/Unit/Http/PsrAdaptersCest.php create mode 100644 tests/Unit/Http/QueryParseCest.php diff --git a/tests/Integration/ClientTerminalCest.php b/tests/Integration/ClientTerminalCest.php new file mode 100644 index 0000000..0c701bc --- /dev/null +++ b/tests/Integration/ClientTerminalCest.php @@ -0,0 +1,184 @@ +getClient(); + + $response = $client->getTerminalStatus(); + + $I->assertInstanceOf(TerminalStatusResponse::class, $response); + $I->assertNotEmpty($response->getStatus()); + } + + #[Group('terminal')] + #[Group('terminal-payment')] + public function createPaymentTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $payment = $this->createTerminalPayment(); + $response = $client->createPayment($payment); + + $I->assertInstanceOf(TerminalPaymentCreateResponse::class, $response); + $I->assertEquals(RequestCode::OK, $response->getCode()); + $I->assertNotEmpty($response->getTransId()); + } + + #[Group('terminal')] + #[Group('terminal-payment')] + public function getPaymentStatusTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $payment = $this->createTerminalPayment(); + $createResponse = $client->createPayment($payment); + $transId = $createResponse->getTransId(); + + $statusResponse = $client->getPaymentStatus($transId); + + $I->assertInstanceOf(TerminalPaymentStatusResponse::class, $statusResponse); + $I->assertEquals(RequestCode::OK, $statusResponse->getCode()); + $I->assertEquals($transId, $statusResponse->getTransId()); + } + + #[Group('terminal')] + #[Group('terminal-payment')] + public function cancelPaymentTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $payment = $this->createTerminalPayment(); + $createResponse = $client->createPayment($payment); + $transId = $createResponse->getTransId(); + + $cancelResponse = $client->cancelPayment($transId); + + $I->assertInstanceOf(TerminalPaymentCancelResponse::class, $cancelResponse); + $I->assertEquals(RequestCode::OK, $cancelResponse->getCode()); + } + + #[Group('terminal')] + #[Group('terminal-closing')] + public function createClosingTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $response = $client->createClosing(); + + $I->assertInstanceOf(TerminalClosingResponse::class, $response); + $I->assertEquals(RequestCode::OK, $response->getCode()); + $I->assertIsInt($response->getBatchNumber()); + $I->assertIsArray($response->getBatchData()); + } + + #[Group('terminal')] + #[Group('terminal-refund')] + public function createRefundTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $refund = $this->createTerminalRefund(); + $response = $client->createRefund($refund); + + $I->assertInstanceOf(TerminalRefundCreateResponse::class, $response); + $I->assertEquals(RequestCode::OK, $response->getCode()); + $I->assertNotEmpty($response->getTransId()); + } + + #[Group('terminal')] + #[Group('terminal-refund')] + public function getRefundStatusTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $refund = $this->createTerminalRefund(); + $createResponse = $client->createRefund($refund); + $transId = $createResponse->getTransId(); + + $statusResponse = $client->getRefundStatus($transId); + + $I->assertInstanceOf(TerminalRefundStatusResponse::class, $statusResponse); + $I->assertEquals(RequestCode::OK, $statusResponse->getCode()); + $I->assertEquals($transId, $statusResponse->getTransId()); + } + + #[Group('terminal')] + #[Group('terminal-refund')] + public function cancelRefundTest(IntegrationTester $I): void + { + $client = $this->getClient(); + + $refund = $this->createTerminalRefund(); + $createResponse = $client->createRefund($refund); + $transId = $createResponse->getTransId(); + + $cancelResponse = $client->cancelRefund($transId); + + $I->assertInstanceOf(TerminalRefundCancelResponse::class, $cancelResponse); + $I->assertEquals(RequestCode::OK, $cancelResponse->getCode()); + } + + #[Group('terminal')] + #[Group('terminal-payment')] + public function createPaymentFailTest(IntegrationTester $I): void + { + $client = $this->getClient(); + $client->getTransport()->getConfig()->setMerchant('invalid_merchant'); + + $I->expectThrowable(ApiException::class, function () use ($client) { + $client->createPayment($this->createTerminalPayment()); + }); + } + + private function createTerminalPayment(): TerminalPayment + { + return (new TerminalPayment()) + ->setPrice(Money::ofInt(100)) + ->setCurr(CurrencyCode::CZK) + ->setRefId('terminal-test-' . uniqid()); + } + + private function createTerminalRefund(): TerminalRefund + { + return (new TerminalRefund()) + ->setPrice(Money::ofInt(50)) + ->setCurr(CurrencyCode::CZK) + ->setRefId('terminal-refund-' . uniqid()); + } + + private function getClient(): ClientTerminal + { + return Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->setUrl($_ENV['API_URL_REST']) + ->createTerminalClient(); + } +} diff --git a/tests/Integration/ComgateBuilderCest.php b/tests/Integration/ComgateBuilderCest.php new file mode 100644 index 0000000..a8ff673 --- /dev/null +++ b/tests/Integration/ComgateBuilderCest.php @@ -0,0 +1,138 @@ +setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->createClient(); + + $I->assertInstanceOf(Client::class, $client); + } + + #[Group('builder')] + public function createTerminalClientTest(IntegrationTester $I): void + { + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->createTerminalClient(); + + $I->assertInstanceOf(ClientTerminal::class, $client); + } + + #[Group('builder')] + public function customUrlIsAppliedTest(IntegrationTester $I): void + { + $customUrl = 'https://custom.example.com/v1/'; + + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->setUrl($customUrl) + ->createClient(); + + $I->assertInstanceOf(Client::class, $client); + $I->assertEquals($customUrl, $client->getTransport()->getConfig()->getUrl()); + } + + #[Group('builder')] + public function defaultUrlIsProductionTest(IntegrationTester $I): void + { + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->createClient(); + + $config = $client->getTransport()->getConfig(); + $I->assertInstanceOf(ITransport::class, $client->getTransport()); + $I->assertStringStartsWith('https://', $config->getUrl()); + } + + #[Group('builder')] + public function merchantAndSecretAreStoredInConfigTest(IntegrationTester $I): void + { + $merchant = 'test-merchant-123'; + $secret = 'test-secret-abc'; + + $client = Comgate::defaults() + ->setMerchant($merchant) + ->setSecret($secret) + ->createClient(); + + $config = $client->getTransport()->getConfig(); + $I->assertEquals($merchant, $config->getMerchant()); + $I->assertEquals($secret, $config->getSecret()); + } + + #[Group('builder')] + public function builderWithLoggerCreatesClientTest(IntegrationTester $I): void + { + $logger = new StdoutLogger(); + + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->setLogger($logger) + ->createClient(); + + $I->assertInstanceOf(Client::class, $client); + } + + #[Group('builder')] + public function setUrlWithoutTrailingSlashAddsOneTest(IntegrationTester $I): void + { + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->setUrl('https://example.com/v1') + ->createClient(); + + $I->assertStringEndsWith('/', $client->getTransport()->getConfig()->getUrl()); + } + + #[Group('builder')] + public function setUrlWithMultipleTrailingSlashesCollapsesToOneTest(IntegrationTester $I): void + { + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->setUrl('https://example.com/v1///') + ->createClient(); + + $url = $client->getTransport()->getConfig()->getUrl(); + + $I->assertStringEndsWith('/', $url); + $I->assertStringEndsNotWith('//', $url); + } + + #[Group('builder')] + public function setUrlAlreadyWithTrailingSlashIsUnchangedTest(IntegrationTester $I): void + { + $url = 'https://example.com/v1/'; + + $client = Comgate::defaults() + ->setMerchant($_ENV['API_MERCHANT']) + ->setSecret($_ENV['API_SECRET']) + ->setUrl($url) + ->createClient(); + + $I->assertEquals($url, $client->getTransport()->getConfig()->getUrl()); + } +} diff --git a/tests/Integration/Entity/PaymentNotificationCest.php b/tests/Integration/Entity/PaymentNotificationCest.php new file mode 100644 index 0000000..0127f98 --- /dev/null +++ b/tests/Integration/Entity/PaymentNotificationCest.php @@ -0,0 +1,111 @@ + '123456', + 'test' => 'true', + 'price' => '10000', + 'curr' => 'CZK', + 'label' => 'Test product', + 'refId' => 'order-42', + 'email' => 'customer@example.com', + 'transId' => 'AB12-CD34-EF56', + 'status' => 'PAID', + 'fee' => '0.50', + 'vs' => '9876543210', + 'method' => 'CARD_ALL', + 'secret' => 'my-secret', + ]; + + $notification = PaymentNotification::createFrom($data); + + $I->assertInstanceOf(PaymentNotification::class, $notification); + $I->assertEquals('123456', $notification->getMerchant()); + $I->assertTrue($notification->isTest()); + $I->assertInstanceOf(Money::class, $notification->getPrice()); + $I->assertEquals(10000, $notification->getPrice()->get()); + $I->assertEquals('CZK', $notification->getCurrency()); + $I->assertEquals('Test product', $notification->getLabel()); + $I->assertEquals('order-42', $notification->getReferenceId()); + $I->assertEquals('customer@example.com', $notification->getEmail()); + $I->assertEquals('AB12-CD34-EF56', $notification->getTransactionId()); + $I->assertEquals('PAID', $notification->getStatus()); + $I->assertEquals('0.50', $notification->getFee()); + $I->assertEquals('9876543210', $notification->getVs()); + $I->assertEquals('CARD_ALL', $notification->getMethod()); + $I->assertEquals('my-secret', $notification->getSecret()); + } + + #[Group('notification')] + public function createFromMissingFieldsTest(IntegrationTester $I): void + { + $notification = PaymentNotification::createFrom([]); + + $I->assertInstanceOf(PaymentNotification::class, $notification); + $I->assertNull($notification->getMerchant()); + $I->assertNull($notification->getTransactionId()); + $I->assertNull($notification->getStatus()); + $I->assertNull($notification->getPrice()); + $I->assertNull($notification->getCurrency()); + $I->assertNull($notification->getEmail()); + $I->assertNull($notification->getLabel()); + $I->assertNull($notification->getReferenceId()); + $I->assertNull($notification->getFee()); + $I->assertNull($notification->getVs()); + $I->assertNull($notification->getMethod()); + $I->assertNull($notification->getSecret()); + } + + #[Group('notification')] + public function isTestBooleanConversionTest(IntegrationTester $I): void + { + $notificationTrue = PaymentNotification::createFrom(['test' => 'true']); + $I->assertTrue($notificationTrue->isTest()); + + $notificationFalse = PaymentNotification::createFrom(['test' => 'false']); + $I->assertFalse($notificationFalse->isTest()); + + $notificationOne = PaymentNotification::createFrom(['test' => '1']); + $I->assertTrue($notificationOne->isTest()); + + $notificationZero = PaymentNotification::createFrom(['test' => '0']); + $I->assertFalse($notificationZero->isTest()); + } + + #[Group('notification')] + public function createFactoryReturnsEmptyObjectTest(IntegrationTester $I): void + { + $notification = PaymentNotification::create(); + + $I->assertInstanceOf(PaymentNotification::class, $notification); + $I->assertNull($notification->getTransactionId()); + $I->assertNull($notification->getMerchant()); + } + + #[Group('notification')] + public function settersWorkCorrectlyTest(IntegrationTester $I): void + { + $notification = PaymentNotification::createFrom(['vs' => '111', 'method' => 'BANK_ALL']); + + $notification->setVs('999'); + $notification->setMethod('CARD_ALL'); + $notification->setSecret('new-secret'); + + $I->assertEquals('999', $notification->getVs()); + $I->assertEquals('CARD_ALL', $notification->getMethod()); + $I->assertEquals('new-secret', $notification->getSecret()); + } +} diff --git a/tests/Integration/Entity/Request/TerminalRequestsCest.php b/tests/Integration/Entity/Request/TerminalRequestsCest.php new file mode 100644 index 0000000..53e9e4a --- /dev/null +++ b/tests/Integration/Entity/Request/TerminalRequestsCest.php @@ -0,0 +1,96 @@ +assertEquals($example['result'], $request->toArray()); + } + + protected function getTerminalPaymentScenarios(): array + { + return [ + 'minimal payment — only required fields' => [ + 'payment' => (new TerminalPayment()) + ->setPrice(Money::ofInt(100)) + ->setCurr(CurrencyCode::CZK), + 'result' => [ + 'price' => 10000, + 'curr' => CurrencyCode::CZK, + ], + ], + 'payment with refId' => [ + 'payment' => (new TerminalPayment()) + ->setPrice(Money::ofInt(50)) + ->setCurr(CurrencyCode::EUR) + ->setRefId('order-9999'), + 'result' => [ + 'price' => 5000, + 'curr' => CurrencyCode::EUR, + 'refId' => 'order-9999', + ], + ], + 'price in cents' => [ + 'payment' => (new TerminalPayment()) + ->setPrice(Money::ofCents(150)) + ->setCurr(CurrencyCode::CZK), + 'result' => [ + 'price' => 150, + 'curr' => CurrencyCode::CZK, + ], + ], + ]; + } + + #[Group('terminal-request')] + #[DataProvider('getTerminalRefundScenarios')] + public function testTerminalRefundCreateRequestParams(IntegrationTester $I, Example $example): void + { + $request = new TerminalRefundCreateRequest($example['refund']); + $I->assertEquals($example['result'], $request->toArray()); + } + + protected function getTerminalRefundScenarios(): array + { + return [ + 'minimal refund — only required fields' => [ + 'refund' => (new TerminalRefund()) + ->setPrice(Money::ofInt(50)) + ->setCurr(CurrencyCode::CZK), + 'result' => [ + 'price' => 5000, + 'curr' => CurrencyCode::CZK, + ], + ], + 'refund with refId' => [ + 'refund' => (new TerminalRefund()) + ->setPrice(Money::ofInt(25)) + ->setCurr(CurrencyCode::EUR) + ->setRefId('refund-order-42'), + 'result' => [ + 'price' => 2500, + 'curr' => CurrencyCode::EUR, + 'refId' => 'refund-order-42', + ], + ], + ]; + } +} diff --git a/tests/Integration/TerminalMethodSyncCest.php b/tests/Integration/TerminalMethodSyncCest.php new file mode 100644 index 0000000..c644448 --- /dev/null +++ b/tests/Integration/TerminalMethodSyncCest.php @@ -0,0 +1,164 @@ +getMethodsProperties(); + $methodProperties = $properties[$example['name']] ?? null; + $I->assertNotEmpty($methodProperties, "Properties for method {$example['name']} not found. Add them to getMethodsProperties() method."); + + $remoteMethods = $this->getRemoteMethods(); + $I->assertNotEmpty($remoteMethods, "Remote methods not found. Check if http://payments.comgate.cz/openapi.yml is available."); + + $currentRemoteMethodParams = []; + foreach ($remoteMethods as $method) { + if ($method['url'] === $methodProperties['url']) { + $currentRemoteMethodParams = $method['params']; + break; + } + } + + $I->assertNotEmpty($currentRemoteMethodParams, "No remote params found for method {$example['name']}. Is it intentional?"); + + $namespace = $methodProperties['namespace'] ?? "Comgate\\SDK\\Entity\\Request\\" . $methodProperties['class']; + + $localMethodParams = $this->getLocalMethodParams($namespace, $methodProperties['args'] ?? null); + $diffRemote = array_diff($currentRemoteMethodParams, $localMethodParams); + $diffLocal = array_diff($currentRemoteMethodParams, $localMethodParams); + $I->assertEmpty($diffRemote, "Local implementation is missing some parameters."); + $I->assertEmpty($diffLocal, "Local implementation has more parameters than remote."); + } + + private function getLocalMethodNames(): array + { + $mirror = new ReflectionClass(ClientTerminal::class); + + $methodNamesRaw = array_map(function (ReflectionMethod $item) { + return ['name' => $item->getName()]; + }, $mirror->getMethods(ReflectionMethod::IS_PUBLIC)); + + return array_filter($methodNamesRaw, function ($item) { + return $item['name'] !== '__construct' && !in_array($item['name'], $this->skippedMethods, true); + }); + } + + private function getRemoteMethods(): array + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'http://payments.comgate.cz/openapi.yml'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + $result = curl_exec($ch); + if (PHP_VERSION_ID < 80500) { + curl_close($ch); + } + + $parsed = yaml_parse($result); + $availableMethods = array_keys($parsed['paths']); + + return array_map(function ($method) use ($parsed) { + $paramsParsed = $parsed['paths'][$method]['post']['requestBody']['content']['application/x-www-form-urlencoded']['schema']['properties'] ?? []; + $params = array_keys($paramsParsed); + $filteredParams = array_filter($params, function ($item) { + return $item !== 'merchant' && $item !== 'secret'; + }); + return [ + 'url' => $method, + 'params' => $filteredParams, + ]; + }, $availableMethods); + } + + private function getLocalMethodParams(string $namespace, ?array $args): array + { + $mirror = new ReflectionClass($namespace); + + $constructor = $mirror->getConstructor(); + if ($constructor && ($constructor->getNumberOfRequiredParameters() > 0)) { + $instance = $mirror->newInstanceArgs($args); + } else { + $instance = $mirror->newInstance(); + } + + $properties = []; + foreach ($mirror->getProperties() as $prop) { + $prop->setAccessible(true); + if ($prop->getName() === 'params') { + $properties = array_merge($properties, array_keys($prop->getValue($instance))); + } else { + $properties[] = $prop->getName(); + } + } + return $properties; + } + + /** + * Mapování metod ClientTerminal na jejich URL a request třídy. + * Po přidání nové metody je třeba ji zaregistrovat zde. + */ + private function getMethodsProperties(): array + { + return [ + 'createPayment' => [ + 'url' => '/v1.0/terminalPayment', + 'class' => 'TerminalPaymentCreateRequest', + 'namespace' => 'Comgate\\SDK\\Entity\\TerminalPayment', + ], + 'getPaymentStatus' => [ + 'url' => '/v1.0/terminalPayment/transId/{transId}', + 'class' => 'TerminalPaymentStatusRequest', + 'args' => ['AAAA-BBBB-CCCC'], + ], + 'cancelPayment' => [ + 'url' => '/v1.0/terminalPayment/transId/{transId}', + 'class' => 'TerminalPaymentCancelRequest', + 'args' => ['AAAA-BBBB-CCCC'], + ], + 'createClosing' => [ + 'url' => '/v1.0/terminalClosing', + 'class' => 'TerminalClosingRequest', + ], + 'createRefund' => [ + 'url' => '/v1.0/terminalRefund', + 'class' => 'TerminalRefundCreateRequest', + 'namespace' => 'Comgate\\SDK\\Entity\\TerminalRefund', + ], + 'getRefundStatus' => [ + 'url' => '/v1.0/terminalRefund/transId/{transId}', + 'class' => 'TerminalRefundStatusRequest', + 'args' => ['AAAA-BBBB-CCCC'], + ], + 'cancelRefund' => [ + 'url' => '/v1.0/terminalRefund/transId/{transId}', + 'class' => 'TerminalRefundCancelRequest', + 'args' => ['AAAA-BBBB-CCCC'], + ], + 'getTerminalStatus' => [ + 'url' => '/v1.0/terminal', + 'class' => 'TerminalStatusRequest', + ], + ]; + } +} diff --git a/tests/Unit/ConfigCest.php b/tests/Unit/ConfigCest.php new file mode 100644 index 0000000..227c0cd --- /dev/null +++ b/tests/Unit/ConfigCest.php @@ -0,0 +1,71 @@ +assertStringStartsWith('https://', $config->getUrl()); + $I->assertStringEndsWith('/', $config->getUrl()); + } + + public function getMerchantAndSecretTest(UnitTester $I): void + { + $config = new Config('my-merchant', 'my-secret'); + + $I->assertEquals('my-merchant', $config->getMerchant()); + $I->assertEquals('my-secret', $config->getSecret()); + } + + public function setMerchantAndSecretTest(UnitTester $I): void + { + $config = new Config('old', 'old'); + $config->setMerchant('new-merchant'); + $config->setSecret('new-secret'); + + $I->assertEquals('new-merchant', $config->getMerchant()); + $I->assertEquals('new-secret', $config->getSecret()); + } + + public function customUrlWithoutTrailingSlashGetsSlashAddedTest(UnitTester $I): void + { + $config = new Config('m', 's', 'https://example.com/v1'); + + $I->assertEquals('https://example.com/v1/', $config->getUrl()); + } + + public function customUrlWithTrailingSlashIsUnchangedTest(UnitTester $I): void + { + $config = new Config('m', 's', 'https://example.com/v1/'); + + $I->assertEquals('https://example.com/v1/', $config->getUrl()); + } + + public function customUrlWithMultipleTrailingSlashesCollapsedToOneTest(UnitTester $I): void + { + $config = new Config('m', 's', 'https://example.com/v1///'); + + $url = $config->getUrl(); + + $I->assertStringEndsWith('/', $url); + $I->assertStringEndsNotWith('//', $url); + } + + public function setUrlAddsTrailingSlashTest(UnitTester $I): void + { + $config = new Config('m', 's'); + $config->setUrl('https://staging.example.com'); + + $I->assertEquals('https://staging.example.com/', $config->getUrl()); + } +} diff --git a/tests/Unit/Entity/PaymentCardCest.php b/tests/Unit/Entity/PaymentCardCest.php new file mode 100644 index 0000000..c3cd1df --- /dev/null +++ b/tests/Unit/Entity/PaymentCardCest.php @@ -0,0 +1,169 @@ +assertNull($card->getCardNumber()); + $I->assertNull($card->getCardExpiration()); + $I->assertNull($card->getCardCvv()); + } + + public function constructWithValidValuesTest(UnitTester $I): void + { + $card = new PaymentCard('1234567890123456', '202512', '123'); + + $I->assertEquals('1234567890123456', $card->getCardNumber()); + $I->assertEquals('202512', $card->getCardExpiration()); + $I->assertEquals('123', $card->getCardCvv()); + } + + // ----------------------------------------------------------------------- + // Card number validation + // ----------------------------------------------------------------------- + + #[DataProvider('invalidCardNumbers')] + public function invalidCardNumberThrowsExceptionTest(UnitTester $I, Example $example): void + { + $I->expectThrowable(Exception::class, function () use ($example) { + new PaymentCard($example['number']); + }); + } + + protected function invalidCardNumbers(): array + { + return [ + 'too short' => ['number' => '123456789012345'], + 'too long' => ['number' => '12345678901234567'], + 'contains letters' => ['number' => '123456789012345A'], + 'empty string' => ['number' => ''], + 'spaces' => ['number' => '1234 5678 9012 3456'], + ]; + } + + public function validCardNumberNotThrowTest(UnitTester $I): void + { + $card = new PaymentCard('4111111111111111'); + $I->assertEquals('4111111111111111', $card->getCardNumber()); + } + + public function setCardNumberValidDoesNotThrowTest(UnitTester $I): void + { + $card = new PaymentCard(); + $card->setCardNumber('9999999999999999'); + $I->assertEquals('9999999999999999', $card->getCardNumber()); + } + + public function setCardNumberInvalidThrowsExceptionTest(UnitTester $I): void + { + $card = new PaymentCard(); + $I->expectThrowable(Exception::class, function () use ($card) { + $card->setCardNumber('123'); + }); + } + + // ----------------------------------------------------------------------- + // Card expiration validation + // ----------------------------------------------------------------------- + + #[DataProvider('invalidExpirations')] + public function invalidExpirationThrowsExceptionTest(UnitTester $I, Example $example): void + { + $I->expectThrowable(Exception::class, function () use ($example) { + new PaymentCard(null, $example['expiry']); + }); + } + + protected function invalidExpirations(): array + { + return [ + 'too short' => ['expiry' => '20251'], + 'too long' => ['expiry' => '2025121'], + 'contains letters' => ['expiry' => '2025AB'], + 'empty string' => ['expiry' => ''], + 'slashes' => ['expiry' => '2025/12'], + ]; + } + + public function validExpirationNotThrowTest(UnitTester $I): void + { + $card = new PaymentCard(null, '202512'); + $I->assertEquals('202512', $card->getCardExpiration()); + } + + public function setExpirationValidDoesNotThrowTest(UnitTester $I): void + { + $card = new PaymentCard(); + $card->setCardExpiration('203001'); + $I->assertEquals('203001', $card->getCardExpiration()); + } + + public function setExpirationInvalidThrowsExceptionTest(UnitTester $I): void + { + $card = new PaymentCard(); + $I->expectThrowable(Exception::class, function () use ($card) { + $card->setCardExpiration('12/25'); + }); + } + + // ----------------------------------------------------------------------- + // CVV validation + // ----------------------------------------------------------------------- + + #[DataProvider('invalidCvvs')] + public function invalidCvvThrowsExceptionTest(UnitTester $I, Example $example): void + { + $I->expectThrowable(Exception::class, function () use ($example) { + new PaymentCard(null, null, $example['cvv']); + }); + } + + protected function invalidCvvs(): array + { + return [ + 'too short' => ['cvv' => '12'], + 'too long' => ['cvv' => '1234'], + 'contains letters' => ['cvv' => '12A'], + 'empty string' => ['cvv' => ''], + ]; + } + + public function validCvvNotThrowTest(UnitTester $I): void + { + $card = new PaymentCard(null, null, '456'); + $I->assertEquals('456', $card->getCardCvv()); + } + + public function setCvvValidDoesNotThrowTest(UnitTester $I): void + { + $card = new PaymentCard(); + $card->setCardCvv('000'); + $I->assertEquals('000', $card->getCardCvv()); + } + + public function setCvvInvalidThrowsExceptionTest(UnitTester $I): void + { + $card = new PaymentCard(); + $I->expectThrowable(Exception::class, function () use ($card) { + $card->setCardCvv('99'); + }); + } +} diff --git a/tests/Unit/Entity/PaymentEntityCest.php b/tests/Unit/Entity/PaymentEntityCest.php new file mode 100644 index 0000000..00a65e8 --- /dev/null +++ b/tests/Unit/Entity/PaymentEntityCest.php @@ -0,0 +1,205 @@ +setRedirect(); + + $I->assertTrue($payment->isPrepareOnly()); + $I->assertFalse($payment->isEmbedded()); + } + + public function setIframeEnablesPrepareOnlyAndEmbeddedTest(UnitTester $I): void + { + $payment = (new Payment())->setIframe(); + + $I->assertTrue($payment->isPrepareOnly()); + $I->assertTrue($payment->isEmbedded()); + } + + // ----------------------------------------------------------------------- + // billing address getters / setters + // ----------------------------------------------------------------------- + + public function billingAddrCityTest(UnitTester $I): void + { + $payment = (new Payment())->setBillingAddrCity('Prague'); + $I->assertEquals('Prague', $payment->getBillingAddrCity()); + } + + public function billingAddrStreetTest(UnitTester $I): void + { + $payment = (new Payment())->setBillingAddrStreet('Main Street 1'); + $I->assertEquals('Main Street 1', $payment->getBillingAddrStreet()); + } + + public function billingAddrPostalCodeTest(UnitTester $I): void + { + $payment = (new Payment())->setBillingAddrPostalCode('11000'); + $I->assertEquals('11000', $payment->getBillingAddrPostalCode()); + } + + public function billingAddrCountryTest(UnitTester $I): void + { + $payment = (new Payment())->setBillingAddrCountry('CZ'); + $I->assertEquals('CZ', $payment->getBillingAddrCountry()); + } + + // ----------------------------------------------------------------------- + // home delivery getters / setters + // ----------------------------------------------------------------------- + + public function homeDeliveryCityTest(UnitTester $I): void + { + $payment = (new Payment())->setHomeDeliveryCity('Brno'); + $I->assertEquals('Brno', $payment->getHomeDeliveryCity()); + } + + public function homeDeliveryStreetTest(UnitTester $I): void + { + $payment = (new Payment())->setHomeDeliveryStreet('Freedom Square 5'); + $I->assertEquals('Freedom Square 5', $payment->getHomeDeliveryStreet()); + } + + public function homeDeliveryPostalCodeTest(UnitTester $I): void + { + $payment = (new Payment())->setHomeDeliveryPostalCode('60200'); + $I->assertEquals('60200', $payment->getHomeDeliveryPostalCode()); + } + + public function homeDeliveryCountryTest(UnitTester $I): void + { + $payment = (new Payment())->setHomeDeliveryCountry('SK'); + $I->assertEquals('SK', $payment->getHomeDeliveryCountry()); + } + + public function deliveryTest(UnitTester $I): void + { + $payment = (new Payment())->setDelivery(DeliveryCode::HOME_DELIVERY); + $I->assertEquals(DeliveryCode::HOME_DELIVERY, $payment->getDelivery()); + } + + // ----------------------------------------------------------------------- + // optional fields + // ----------------------------------------------------------------------- + + public function phoneTest(UnitTester $I): void + { + $payment = (new Payment())->setPhone('+420123456789'); + $I->assertEquals('+420123456789', $payment->getPhone()); + } + + public function fullNameTest(UnitTester $I): void + { + $payment = (new Payment())->setFullName('Jan Novák'); + $I->assertEquals('Jan Novák', $payment->getFullName()); + } + + public function categoryTest(UnitTester $I): void + { + $payment = (new Payment())->setCategory(CategoryCode::PHYSICAL_GOODS_ONLY); + $I->assertEquals(CategoryCode::PHYSICAL_GOODS_ONLY, $payment->getCategory()); + } + + public function expirationTimeTest(UnitTester $I): void + { + $payment = (new Payment())->setExpirationTime('2099-12-31T23:59:59'); + $I->assertEquals('2099-12-31T23:59:59', $payment->getExpirationTime()); + } + + public function expirationTimeNullableTest(UnitTester $I): void + { + // expirationTime defaults to '' (empty string) + $payment = new Payment(); + $I->assertEquals('', $payment->getExpirationTime()); + } + + public function transactionIdTest(UnitTester $I): void + { + $payment = (new Payment())->setTransactionId('TX-001'); + $I->assertEquals('TX-001', $payment->getTransactionId()); + } + + public function payerIdTest(UnitTester $I): void + { + $payment = (new Payment())->setPayerId('PAYER-42'); + $I->assertEquals('PAYER-42', $payment->getPayerId()); + } + + public function applePayPayloadTest(UnitTester $I): void + { + $payment = (new Payment())->setApplePayPayload('base64payloaddata'); + $I->assertEquals('base64payloaddata', $payment->getApplePayPayload()); + } + + public function initRecurringIdTest(UnitTester $I): void + { + $payment = (new Payment())->setInitRecurringId('INIT-TX-001'); + $I->assertEquals('INIT-TX-001', $payment->getInitRecurringId()); + } + + public function dynamicExpirationTest(UnitTester $I): void + { + $payment = (new Payment())->setDynamicExpiration(true); + $I->assertTrue($payment->isDynamicExpiration()); + } + + // ----------------------------------------------------------------------- + // URL setters — both families + // ----------------------------------------------------------------------- + + public function urlPaidRedirectTest(UnitTester $I): void + { + $payment = (new Payment())->setUrlPaidRedirect('https://example.com/ok'); + $I->assertEquals('https://example.com/ok', $payment->getUrlPaidRedirect()); + } + + public function urlCancelledRedirectTest(UnitTester $I): void + { + $payment = (new Payment())->setUrlCancelledRedirect('https://example.com/cancel'); + $I->assertEquals('https://example.com/cancel', $payment->getUrlCancelledRedirect()); + } + + public function urlPendingRedirectTest(UnitTester $I): void + { + $payment = (new Payment())->setUrlPendingRedirect('https://example.com/pending'); + $I->assertEquals('https://example.com/pending', $payment->getUrlPendingRedirect()); + } + + // ----------------------------------------------------------------------- + // getParams / setParams + // ----------------------------------------------------------------------- + + public function getParamsReturnsArrayTest(UnitTester $I): void + { + $payment = new Payment(); + $I->assertIsArray($payment->getParams()); + } + + public function setParamsOverridesAllParamsTest(UnitTester $I): void + { + $payment = new Payment(); + $payment->setParams(['custom_key' => 'custom_value']); + + $I->assertEquals(['custom_key' => 'custom_value'], $payment->getParams()); + } +} diff --git a/tests/Unit/Entity/PaymentUrlGettersCest.php b/tests/Unit/Entity/PaymentUrlGettersCest.php new file mode 100644 index 0000000..445ccdb --- /dev/null +++ b/tests/Unit/Entity/PaymentUrlGettersCest.php @@ -0,0 +1,124 @@ +expectThrowable(ParamIsNotSetException::class, function () use ($payment) { + $payment->getUrlPaid(); + }); + } + + public function setUrlPaidThenGetReturnsValueTest(UnitTester $I): void + { + $payment = new Payment(); + $payment->setUrlPaid('https://example.com/paid'); + + $I->assertEquals('https://example.com/paid', $payment->getUrlPaid()); + } + + // ----------------------------------------------------------------------- + // getUrlCancelled — throws without prior set, works after set + // ----------------------------------------------------------------------- + + public function getUrlCancelledWithoutSetThrowsTest(UnitTester $I): void + { + $payment = new Payment(); + + $I->expectThrowable(ParamIsNotSetException::class, function () use ($payment) { + $payment->getUrlCancelled(); + }); + } + + public function setUrlCancelledThenGetReturnsValueTest(UnitTester $I): void + { + $payment = new Payment(); + $payment->setUrlCancelled('https://example.com/cancelled'); + + $I->assertEquals('https://example.com/cancelled', $payment->getUrlCancelled()); + } + + // ----------------------------------------------------------------------- + // getUrlPending — throws without prior set, works after set + // ----------------------------------------------------------------------- + + public function getUrlPendingWithoutSetThrowsTest(UnitTester $I): void + { + $payment = new Payment(); + + $I->expectThrowable(ParamIsNotSetException::class, function () use ($payment) { + $payment->getUrlPending(); + }); + } + + public function setUrlPendingThenGetReturnsValueTest(UnitTester $I): void + { + $payment = new Payment(); + $payment->setUrlPending('https://example.com/pending'); + + $I->assertEquals('https://example.com/pending', $payment->getUrlPending()); + } + + // ----------------------------------------------------------------------- + // Legacy redirect getters always return empty string by default + // ----------------------------------------------------------------------- + + public function legacyUrlPaidRedirectDefaultsToEmptyStringTest(UnitTester $I): void + { + $payment = new Payment(); + + $I->assertEquals('', $payment->getUrlPaidRedirect()); + $I->assertEquals('', $payment->getUrlCancelledRedirect()); + $I->assertEquals('', $payment->getUrlPendingRedirect()); + } + + public function legacyAndNewGettersAreIndependentTest(UnitTester $I): void + { + $payment = new Payment(); + $payment->setUrlPaidRedirect('https://legacy.com/paid'); + $payment->setUrlPaid('https://new.com/paid'); + + $I->assertEquals('https://legacy.com/paid', $payment->getUrlPaidRedirect()); + $I->assertEquals('https://new.com/paid', $payment->getUrlPaid()); + } + + // ----------------------------------------------------------------------- + // getParam() throws ParamIsNotSetException for missing key + // ----------------------------------------------------------------------- + + public function getParamWithNonExistentKeyThrowsTest(UnitTester $I): void + { + $payment = new Payment(); + + $I->expectThrowable(ParamIsNotSetException::class, function () use ($payment) { + $payment->getParam('nonExistentKey'); + }); + } +} diff --git a/tests/Unit/Entity/Request/MiscRequestsCest.php b/tests/Unit/Entity/Request/MiscRequestsCest.php new file mode 100644 index 0000000..dc22582 --- /dev/null +++ b/tests/Unit/Entity/Request/MiscRequestsCest.php @@ -0,0 +1,149 @@ +assertEquals('simulation.json', $req->getUrn()); + } + + #[Group('simulation-request')] + public function simulationRequestToArrayReturnsParamsTest(UnitTester $I): void + { + $params = ['transId' => 'AB12-CD34', 'status' => 'PAID']; + $req = new SimulationRequest($params); + + $I->assertEquals($params, $req->toArray()); + } + + #[Group('simulation-request')] + public function simulationRequestEmptyParamsTest(UnitTester $I): void + { + $req = new SimulationRequest([]); + $I->assertEquals([], $req->toArray()); + } + + #[Group('simulation-request')] + public function simulationRequestSetParamsTest(UnitTester $I): void + { + $req = new SimulationRequest([]); + $req->setParams(['transId' => 'ZZ99']); + + $I->assertEquals(['transId' => 'ZZ99'], $req->toArray()); + } + + // ----------------------------------------------------------------------- + // CsvSingleTransferRequest + // ----------------------------------------------------------------------- + + #[Group('csv-single-transfer-request')] + public function csvSingleTransferToArrayTest(UnitTester $I): void + { + $req = new CsvSingleTransferRequest('T-123', false); + + $result = $req->toArray(); + + $I->assertEquals('T-123', $result['transferId']); + $I->assertEquals('false', $result['test']); + } + + #[Group('csv-single-transfer-request')] + public function csvSingleTransferUrnContainsTransferIdAndTestParamTest(UnitTester $I): void + { + $req = new CsvSingleTransferRequest('T-abc', true); + $urn = $req->getUrn(); + + $I->assertStringContainsString('T-abc', $urn); + $I->assertStringContainsString('test=true', $urn); + } + + #[Group('csv-single-transfer-request')] + public function csvSingleTransferUrlencodesTransferIdTest(UnitTester $I): void + { + $req = new CsvSingleTransferRequest('T/special?id', false); + $urn = $req->getUrn(); + + $I->assertStringNotContainsString('T/special?id', $urn); + } + + // ----------------------------------------------------------------------- + // CsvDownloadRequest + // ----------------------------------------------------------------------- + + #[Group('csv-download-request')] + public function csvDownloadToArrayTest(UnitTester $I): void + { + $req = new CsvDownloadRequest('2024-01', false); + + $result = $req->toArray(); + + $I->assertEquals('2024-01', $result['date']); + $I->assertEquals('false', $result['test']); + } + + #[Group('csv-download-request')] + public function csvDownloadUrnContainsDateAndTestParamTest(UnitTester $I): void + { + $req = new CsvDownloadRequest('2024-06', true); + $urn = $req->getUrn(); + + $I->assertStringContainsString('2024-06', $urn); + $I->assertStringContainsString('test=true', $urn); + } + + // ----------------------------------------------------------------------- + // AboDownloadRequest + // ----------------------------------------------------------------------- + + #[Group('abo-download-request')] + public function aboDownloadToArrayTest(UnitTester $I): void + { + $req = new AboDownloadRequest('2024-01', 'VYPIS', false, 'UTF-8'); + + $result = $req->toArray(); + + $I->assertEquals('2024-01', $result['date']); + $I->assertEquals('VYPIS', $result['type']); + $I->assertEquals('false', $result['test']); + $I->assertEquals('UTF-8', $result['encoding']); + } + + #[Group('abo-download-request')] + public function aboDownloadUrnContainsParamsTest(UnitTester $I): void + { + $req = new AboDownloadRequest('2024-03', 'POHYBY', true, 'CP1250'); + $urn = $req->getUrn(); + + $I->assertStringContainsString('2024-03', $urn); + $I->assertStringContainsString('test=true', $urn); + $I->assertStringContainsString('encoding=CP1250', $urn); + $I->assertStringContainsString('type=POHYBY', $urn); + } + + #[Group('abo-download-request')] + public function aboDownloadUrnOmitsTypeWhenEmptyTest(UnitTester $I): void + { + $req = new AboDownloadRequest('2024-03', '', false, 'UTF-8'); + $urn = $req->getUrn(); + + $I->assertStringNotContainsString('type=', $urn); + } +} diff --git a/tests/Unit/Entity/Request/OtherRequestsCest.php b/tests/Unit/Entity/Request/OtherRequestsCest.php new file mode 100644 index 0000000..0a9b6ce --- /dev/null +++ b/tests/Unit/Entity/Request/OtherRequestsCest.php @@ -0,0 +1,182 @@ +setTransId('AB12-CD34-EF56') + ->setAmount(Money::ofFloat(10.0)); + + $result = (new PaymentRefundRequest($refund))->toArray(); + + $I->assertEquals('AB12-CD34-EF56', $result['transId']); + $I->assertEquals(1000, $result['amount']); // 10.0 CZK = 1000 cents + $I->assertEquals('false', $result['test']); // default + $I->assertEquals('', $result['refId']); // default + } + + #[Group('refund-request')] + public function refundRequestWithAllParamsTest(UnitTester $I): void + { + $refund = (new Refund()) + ->setTransId('ZZ99-AA00-BB11') + ->setAmount(Money::ofCents(500)) + ->setTest(true) + ->setRefId('my-refund-ref'); + + $result = (new PaymentRefundRequest($refund))->toArray(); + + $I->assertEquals('ZZ99-AA00-BB11', $result['transId']); + $I->assertEquals(500, $result['amount']); + $I->assertEquals('true', $result['test']); + $I->assertEquals('my-refund-ref', $result['refId']); + } + + #[Group('refund-request')] + public function refundRequestUrnTest(UnitTester $I): void + { + $refund = (new Refund()) + ->setTransId('AB12') + ->setAmount(Money::ofInt(1)); + + $I->assertEquals('refund.json', (new PaymentRefundRequest($refund))->getUrn()); + } + + // ----------------------------------------------------------------------- + // RecurringPaymentRequest::toArray() + // ----------------------------------------------------------------------- + + #[Group('recurring-request')] + public function recurringRequestContainsRequiredFieldsTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(100)) + ->setLabel('Recurring product') + ->setReferenceId('ref-recurring-01'); + $payment->setInitRecurringId('INIT-TRANS-ID'); + + $result = (new RecurringPaymentRequest($payment))->toArray(); + + $I->assertArrayHasKey('price', $result); + $I->assertArrayHasKey('curr', $result); + $I->assertArrayHasKey('label', $result); + $I->assertArrayHasKey('refId', $result); + $I->assertArrayHasKey('account', $result); + $I->assertArrayHasKey('name', $result); + $I->assertArrayHasKey('initRecurringId', $result); + $I->assertArrayHasKey('test', $result); + + $I->assertEquals(10000, $result['price']); // 100 CZK = 10000 cents + $I->assertEquals('Recurring product', $result['label']); + $I->assertEquals('ref-recurring-01', $result['refId']); + $I->assertEquals('INIT-TRANS-ID', $result['initRecurringId']); + } + + #[Group('recurring-request')] + public function recurringRequestUrnTest(UnitTester $I): void + { + $payment = (new Payment())->setPrice(Money::ofInt(1)); + $I->assertEquals('recurring.json', (new RecurringPaymentRequest($payment))->getUrn()); + } + + // ----------------------------------------------------------------------- + // MethodsRequest::toArray() + // ----------------------------------------------------------------------- + + #[Group('methods-request')] + public function methodsRequestDefaultOnlyHasTypeTest(UnitTester $I): void + { + $result = (new MethodsRequest())->toArray(); + + $I->assertArrayHasKey('type', $result); + $I->assertEquals(TypeCode::TYPE_JSON, $result['type']); + $I->assertCount(1, $result); // only 'type' — all optionals are null → absent + } + + #[Group('methods-request')] + public function methodsRequestNullableFieldsAbsentWhenNullTest(UnitTester $I): void + { + $result = (new MethodsRequest())->toArray(); + + $I->assertArrayNotHasKey('lang', $result); + $I->assertArrayNotHasKey('curr', $result); + $I->assertArrayNotHasKey('country', $result); + $I->assertArrayNotHasKey('price', $result); + $I->assertArrayNotHasKey('initRecurring', $result); + $I->assertArrayNotHasKey('verification', $result); + $I->assertArrayNotHasKey('preauth', $result); + $I->assertArrayNotHasKey('embedded', $result); + } + + #[Group('methods-request')] + public function methodsRequestWithAllParamsTest(UnitTester $I): void + { + $request = new MethodsRequest(); + $request->setType(TypeCode::TYPE_JSON); + $request->setLang(LangCode::CS); + $request->setCurr(CurrencyCode::CZK); + $request->setCountry(CountryCode::CZ); + $request->setPrice('10000'); + $request->setInitRecurring(true); + $request->setPreauth(false); + $request->setEmbedded(true); + + $result = $request->toArray(); + + $I->assertEquals(LangCode::CS, $result['lang']); + $I->assertEquals(CurrencyCode::CZK, $result['curr']); + $I->assertEquals(CountryCode::CZ, $result['country']); + $I->assertEquals('10000', $result['price']); + $I->assertTrue($result['initRecurring']); + $I->assertFalse($result['preauth']); + $I->assertTrue($result['embedded']); + } + + #[Group('methods-request')] + public function methodsRequestGetUrnWithoutParamsTest(UnitTester $I): void + { + $urn = (new MethodsRequest())->getUrn(); + + // No optional params → no query string (type is excluded from URL) + $I->assertEquals('method.json', $urn); + } + + #[Group('methods-request')] + public function methodsRequestGetUrnWithParamsContainsQueryStringTest(UnitTester $I): void + { + $request = (new MethodsRequest()) + ->setLang(LangCode::CS) + ->setCurr(CurrencyCode::CZK); + + $urn = $request->getUrn(); + + $I->assertStringStartsWith('method.json?', $urn); + $I->assertStringContainsString('lang=cs', $urn); + $I->assertStringContainsString('curr=CZK', $urn); + } +} diff --git a/tests/Unit/Entity/Request/PaymentCreateRequestCest.php b/tests/Unit/Entity/Request/PaymentCreateRequestCest.php new file mode 100644 index 0000000..4f1618e --- /dev/null +++ b/tests/Unit/Entity/Request/PaymentCreateRequestCest.php @@ -0,0 +1,264 @@ +setPrice(Money::ofInt(1)); + $I->assertEquals('payment.json', (new PaymentCreateRequest($payment))->getUrn()); + } + + // ----------------------------------------------------------------------- + // method encoding + // ----------------------------------------------------------------------- + + public function noMethodsProducesEmptyMethodFieldTest(UnitTester $I): void + { + $payment = (new Payment())->setPrice(Money::ofInt(10)); + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('', $result['method']); + $I->assertArrayNotHasKey('allowedMethods', $result); + $I->assertArrayNotHasKey('excludedMethods', $result); + } + + public function singleAllowedMethodJoinedWithPlusTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(10)) + ->setMethods([PaymentMethodCode::ALL_BANKS]); + + $I->assertEquals('BANK_ALL', (new PaymentCreateRequest($payment))->toArray()['method']); + } + + public function multipleAllowedMethodsJoinedWithPlusTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(10)) + ->setMethods([PaymentMethodCode::ALL_BANKS, PaymentMethodCode::BANK_RB_BUTTON]) + ->addMethod(PaymentMethodCode::CARD_CARD_CZ_CSOB_2); + + $I->assertEquals( + PaymentMethodCode::ALL_BANKS . '+' . PaymentMethodCode::BANK_RB_BUTTON . '+' . PaymentMethodCode::CARD_CARD_CZ_CSOB_2, + (new PaymentCreateRequest($payment))->toArray()['method'] + ); + } + + public function allowedPlusExcludedCombinedTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(10)) + ->setMethods([PaymentMethodCode::ALL_BANKS, PaymentMethodCode::BANK_RB_BUTTON]) + ->setoutMethod(PaymentMethodCode::BANK_FIO_BUTTON); + + $expected = PaymentMethodCode::ALL_BANKS . '+' . PaymentMethodCode::BANK_RB_BUTTON + . '-' . PaymentMethodCode::BANK_FIO_BUTTON; + + $I->assertEquals($expected, (new PaymentCreateRequest($payment))->toArray()['method']); + } + + /** + * Regression test for the ltrim bug: + * When there are NO allowed methods and at least one excluded method, + * ltrim strips the leading '-', making the excluded method appear + * as an allowed method instead. + * + * Current (buggy) behaviour is asserted here so that a future fix + * will produce an intentional test failure rather than a silent regression. + */ + public function onlyExcludedMethodsLtrimRemovesLeadingDashBugTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(10)) + ->setoutMethod(PaymentMethodCode::BANK_FIO_BUTTON); + + $result = (new PaymentCreateRequest($payment))->toArray()['method']; + + // Bug: leading '-' is stripped, method treated as allowed instead of excluded + $I->assertEquals(PaymentMethodCode::BANK_FIO_BUTTON, $result); + $I->assertStringStartsNotWith('-', $result); + } + + // ----------------------------------------------------------------------- + // price conversion + // ----------------------------------------------------------------------- + + public function priceConvertedToCentsTest(UnitTester $I): void + { + $payment = (new Payment())->setPrice(Money::ofInt(25)); + $I->assertEquals(2500, (new PaymentCreateRequest($payment))->toArray()['price']); + } + + // ----------------------------------------------------------------------- + // URL fields + // ----------------------------------------------------------------------- + + public function urlFieldsDefaultToEmptyStringTest(UnitTester $I): void + { + $payment = (new Payment())->setPrice(Money::ofInt(1)); + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('', $result['url_paid']); + $I->assertEquals('', $result['url_cancelled']); + $I->assertEquals('', $result['url_pending']); + } + + public function urlFieldsFromRedirectSettersTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(1)) + ->setUrlPaidRedirect('https://example.com/paid') + ->setUrlCancelledRedirect('https://example.com/cancel') + ->setUrlPendingRedirect('https://example.com/pending'); + + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('https://example.com/paid', $result['url_paid']); + $I->assertEquals('https://example.com/cancel', $result['url_cancelled']); + $I->assertEquals('https://example.com/pending', $result['url_pending']); + } + + // ----------------------------------------------------------------------- + // nullable flags + // ----------------------------------------------------------------------- + + public function chargeUnregulatedCardFeesAbsentWhenNullTest(UnitTester $I): void + { + $payment = (new Payment())->setPrice(Money::ofInt(1)); + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertArrayNotHasKey('chargeUnregulatedCardFees', $result); + $I->assertArrayNotHasKey('enableApplePayGooglePay', $result); + } + + public function chargeUnregulatedCardFeesPresentAsFalseStringTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(1)) + ->setChargeUnregulatedCardFees(false) + ->setEnableApplePayGooglePay(false); + + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('false', $result['chargeUnregulatedCardFees']); + $I->assertEquals('false', $result['enableApplePayGooglePay']); + } + + public function chargeUnregulatedCardFeesPresentAsTrueStringTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(1)) + ->setChargeUnregulatedCardFees(true) + ->setEnableApplePayGooglePay(true); + + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('true', $result['chargeUnregulatedCardFees']); + $I->assertEquals('true', $result['enableApplePayGooglePay']); + } + + // ----------------------------------------------------------------------- + // boolean flags as strings + // ----------------------------------------------------------------------- + + public function boolFlagsConvertedToStringsTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(1)) + ->setTest(true) + ->setPreauth(true) + ->setInitRecurring(true) + ->setEmbedded(true) + ->setVerification(true); + + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('true', $result['test']); + $I->assertEquals('true', $result['preauth']); + $I->assertEquals('true', $result['initRecurring']); + $I->assertEquals('true', $result['embedded']); + $I->assertEquals('true', $result['verification']); + } + + // ----------------------------------------------------------------------- + // billing / delivery fields + // ----------------------------------------------------------------------- + + public function billingAndDeliveryFieldsDefaultToEmptyStringTest(UnitTester $I): void + { + $payment = (new Payment())->setPrice(Money::ofInt(1)); + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('', $result['billingAddrCity']); + $I->assertEquals('', $result['billingAddrStreet']); + $I->assertEquals('', $result['billingAddrPostalCode']); + $I->assertEquals('', $result['billingAddrCountry']); + $I->assertEquals('', $result['delivery']); + $I->assertEquals('', $result['homeDeliveryCity']); + $I->assertEquals('', $result['homeDeliveryStreet']); + $I->assertEquals('', $result['homeDeliveryPostalCode']); + $I->assertEquals('', $result['homeDeliveryCountry']); + } + + public function billingAndDeliveryFieldsSetCorrectlyTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(1)) + ->setBillingAddrCity('Prague') + ->setBillingAddrStreet('Wenceslas Square 1') + ->setBillingAddrPostalCode('11000') + ->setBillingAddrCountry('CZ') + ->setHomeDeliveryCity('Brno') + ->setHomeDeliveryStreet('Freedom Square 5') + ->setHomeDeliveryPostalCode('60200') + ->setHomeDeliveryCountry('CZ'); + + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('Prague', $result['billingAddrCity']); + $I->assertEquals('Wenceslas Square 1', $result['billingAddrStreet']); + $I->assertEquals('11000', $result['billingAddrPostalCode']); + $I->assertEquals('CZ', $result['billingAddrCountry']); + $I->assertEquals('Brno', $result['homeDeliveryCity']); + $I->assertEquals('Freedom Square 5', $result['homeDeliveryStreet']); + $I->assertEquals('60200', $result['homeDeliveryPostalCode']); + $I->assertEquals('CZ', $result['homeDeliveryCountry']); + } + + // ----------------------------------------------------------------------- + // expirationTime optional + // ----------------------------------------------------------------------- + + public function expirationTimeDefaultsToEmptyStringTest(UnitTester $I): void + { + // expirationTime has default '' in $params, getExpirationTime() returns '' + $payment = new Payment(); + $I->assertEquals('', $payment->getExpirationTime()); + } + + public function expirationTimePresentWhenSetTest(UnitTester $I): void + { + $payment = (new Payment()) + ->setPrice(Money::ofInt(1)) + ->setExpirationTime('2099-12-31T23:59:59'); + + $result = (new PaymentCreateRequest($payment))->toArray(); + + $I->assertEquals('2099-12-31T23:59:59', $result['expirationTime']); + } +} diff --git a/tests/Unit/Entity/Request/RequestUrnCest.php b/tests/Unit/Entity/Request/RequestUrnCest.php new file mode 100644 index 0000000..baa0853 --- /dev/null +++ b/tests/Unit/Entity/Request/RequestUrnCest.php @@ -0,0 +1,291 @@ +assertEquals('method.json', $request->getUrn()); + } + + public function testMethodsRequestTypeIsNotIncludedInUrn(UnitTester $I): void + { + $request = new MethodsRequest(); + $request->setType(TypeCode::TYPE_JSON); + // 'type' must be stripped from the URN (it goes in the Accept header, not query) + $I->assertStringNotContainsString('type=', $request->getUrn()); + } + + public function testMethodsRequestSingleOptionalParamAppearsInQuery(UnitTester $I): void + { + $request = new MethodsRequest(); + $request->setLang(LangCode::CS); + + $I->assertStringStartsWith('method.json?', $request->getUrn()); + $I->assertStringContainsString('lang=cs', $request->getUrn()); + } + + /** + * @return array, expectedKeys: string[], absentKeys: string[]}> + */ + protected function methodsRequestOptionalParamScenarios(): array + { + return [ + 'lang + curr' => [ + 'params' => ['lang' => LangCode::CS, 'curr' => CurrencyCode::CZK], + 'expectedKeys' => ['lang=cs', 'curr=CZK'], + 'absentKeys' => ['country', 'price', 'initRecurring', 'type='], + ], + 'preauth + embedded' => [ + 'params' => ['preauth' => true, 'embedded' => false], + 'expectedKeys' => ['preauth=1', 'embedded='], + 'absentKeys' => ['lang', 'curr', 'type='], + ], + 'chargeUnregulatedCardFees=true' => [ + 'params' => ['chargeUnregulatedCardFees' => true], + 'expectedKeys' => ['chargeUnregulatedCardFees=1'], + 'absentKeys' => ['lang', 'type='], + ], + 'null params omitted' => [ + 'params' => ['lang' => null, 'curr' => null], + 'expectedKeys' => [], + 'absentKeys' => ['lang', 'curr'], + ], + ]; + } + + #[DataProvider('methodsRequestOptionalParamScenarios')] + public function testMethodsRequestOptionalParamsInUrn(UnitTester $I, Example $example): void + { + $request = new MethodsRequest(); + + foreach ($example['params'] as $setter => $value) { + $method = 'set' . ucfirst($setter); + $request->$method($value); + } + + $urn = $request->getUrn(); + + foreach ($example['expectedKeys'] as $fragment) { + $I->assertStringContainsString($fragment, $urn, "Expected '{$fragment}' in URN: {$urn}"); + } + foreach ($example['absentKeys'] as $fragment) { + $I->assertStringNotContainsString($fragment, $urn, "Expected '{$fragment}' absent in URN: {$urn}"); + } + } + + // ========================================================================= + // TransferListRequest — date formatting and test flag + // ========================================================================= + + /** + * @return array + */ + protected function transferListUrnScenarios(): array + { + return [ + 'specific date, test=false' => [ + 'date' => '2023-02-01', + 'test' => false, + 'expectedUrn' => 'transferList/date/2023-02-01.json?test=false', + ], + 'specific date, test=true' => [ + 'date' => '2023-12-31', + 'test' => true, + 'expectedUrn' => 'transferList/date/2023-12-31.json?test=true', + ], + ]; + } + + #[DataProvider('transferListUrnScenarios')] + public function testTransferListRequestUrn(UnitTester $I, Example $example): void + { + $request = new TransferListRequest(new DateTime($example['date']), $example['test']); + $I->assertEquals($example['expectedUrn'], $request->getUrn()); + } + + // ========================================================================= + // PaymentStatusRequest + PaymentCancelRequest — urlencode() on transId + // ========================================================================= + + /** + * @return array + */ + protected function transIdUrnScenarios(): array + { + return [ + 'standard dashes' => [ + 'transId' => 'XXXX-YYYY-ZZZZ', + 'expectedFragment' => 'XXXX-YYYY-ZZZZ', + ], + 'transId with space (urlencode → +)' => [ + 'transId' => 'AB CD', + 'expectedFragment' => 'AB+CD', + ], + 'transId with slash (urlencode → %2F)' => [ + 'transId' => 'AB/CD', + 'expectedFragment' => 'AB%2FCD', + ], + ]; + } + + #[DataProvider('transIdUrnScenarios')] + public function testPaymentStatusRequestUrn(UnitTester $I, Example $example): void + { + $request = new PaymentStatusRequest($example['transId']); + $I->assertStringContainsString( + $example['expectedFragment'], + $request->getUrn(), + "URN should contain encoded transId" + ); + $I->assertStringStartsWith('payment/transId/', $request->getUrn()); + $I->assertStringEndsWith('.json', $request->getUrn()); + } + + #[DataProvider('transIdUrnScenarios')] + public function testPaymentCancelRequestUrn(UnitTester $I, Example $example): void + { + $request = new PaymentCancelRequest($example['transId']); + $I->assertStringContainsString($example['expectedFragment'], $request->getUrn()); + $I->assertStringStartsWith('payment/transId/', $request->getUrn()); + } + + #[DataProvider('transIdUrnScenarios')] + public function testPreauthCancelRequestUrn(UnitTester $I, Example $example): void + { + $request = new PreauthCancelRequest($example['transId']); + $I->assertStringContainsString($example['expectedFragment'], $request->getUrn()); + $I->assertStringStartsWith('preauth/transId/', $request->getUrn()); + } + + #[DataProvider('transIdUrnScenarios')] + public function testPreauthCaptureRequestUrn(UnitTester $I, Example $example): void + { + $request = new PreauthCaptureRequest($example['transId'], Money::ofInt(10)); + $I->assertStringContainsString($example['expectedFragment'], $request->getUrn()); + $I->assertStringStartsWith('preauth/transId/', $request->getUrn()); + $I->assertStringEndsWith('.json', $request->getUrn()); + } + + // ========================================================================= + // SingleTransferRequest — integer ID + test flag in query string + // ========================================================================= + + /** + * @return array + */ + protected function singleTransferUrnScenarios(): array + { + return [ + 'id=112233, test=true' => ['id' => 112233, 'test' => true, 'expectedUrn' => 'singleTransfer/transferId/112233.json?test=true'], + 'id=1, test=false' => ['id' => 1, 'test' => false, 'expectedUrn' => 'singleTransfer/transferId/1.json?test=false'], + ]; + } + + #[DataProvider('singleTransferUrnScenarios')] + public function testSingleTransferRequestUrn(UnitTester $I, Example $example): void + { + $request = new SingleTransferRequest($example['id'], $example['test']); + $I->assertEquals($example['expectedUrn'], $request->getUrn()); + } + + // ========================================================================= + // AboSingleTransferRequest — 4 params: type, encoding, test + // ========================================================================= + + /** + * @return array + */ + protected function aboSingleTransferUrnScenarios(): array + { + return [ + 'v1 utf8 test=true' => [ + 'type' => AboSingleTransferRequest::ABO_TYPE_V1, + 'encoding' => AboSingleTransferRequest::ABO_ENCODING_UTF8, + 'test' => true, + 'expectedFragment' => ['type=v1', 'encoding=utf8', 'test=true'], + ], + 'v2 windows test=false' => [ + 'type' => AboSingleTransferRequest::ABO_TYPE_V2, + 'encoding' => AboSingleTransferRequest::ABO_ENCODING_WINDOWS, + 'test' => false, + 'expectedFragment' => ['type=v2', 'encoding=win1250', 'test=false'], + ], + ]; + } + + #[DataProvider('aboSingleTransferUrnScenarios')] + public function testAboSingleTransferRequestUrn(UnitTester $I, Example $example): void + { + $request = new AboSingleTransferRequest( + '112233', + $example['test'], + $example['type'], + $example['encoding'] + ); + + $urn = $request->getUrn(); + $I->assertStringStartsWith('aboSingleTransfer/transferId/112233.json?', $urn); + + foreach ($example['expectedFragment'] as $fragment) { + $I->assertStringContainsString($fragment, $urn, "Expected '{$fragment}' in URN: {$urn}"); + } + } + + // ========================================================================= + // AppleDomainAssociationRequest — currency is conditionally appended + // ========================================================================= + + public function testAppleDomainAssociationUrnWithNoCurrencyHasNoQueryString(UnitTester $I): void + { + $request = new AppleDomainAssociationRequest('', ''); + $I->assertEquals('appleDomainAssociation.json', $request->getUrn()); + } + + public function testAppleDomainAssociationUrnWithCurrencyAppendsCurrencyParam(UnitTester $I): void + { + $request = new AppleDomainAssociationRequest('APPLE_PAY', CurrencyCode::EUR); + $urn = $request->getUrn(); + + $I->assertStringContainsString('currency=EUR', $urn); + // method is NOT a query param in the URN (only currency is conditional) + $I->assertStringNotContainsString('method=', $urn); + } + + public function testAppleDomainAssociationUrnMethodDoesNotAffectUrn(UnitTester $I): void + { + $noMethod = new AppleDomainAssociationRequest('', CurrencyCode::EUR); + $withMethod = new AppleDomainAssociationRequest('APPLE_PAY', CurrencyCode::EUR); + + // Both should produce identical URNs — method is irrelevant to URN construction + $I->assertEquals($noMethod->getUrn(), $withMethod->getUrn()); + } +} diff --git a/tests/Unit/Entity/Request/TerminalCancelStatusRequestsCest.php b/tests/Unit/Entity/Request/TerminalCancelStatusRequestsCest.php new file mode 100644 index 0000000..b63715d --- /dev/null +++ b/tests/Unit/Entity/Request/TerminalCancelStatusRequestsCest.php @@ -0,0 +1,101 @@ +assertEquals(['transId' => 'AB12-CD34'], $req->toArray()); + } + + public function terminalPaymentCancelUrnContainsPlaceholderTest(UnitTester $I): void + { + $req = new TerminalPaymentCancelRequest('AB12-CD34'); + + $I->assertStringContainsString('{transId}', $req->getUrn()); + $I->assertStringContainsString('terminalPayment', $req->getUrn()); + } + + public function terminalPaymentCancelSetTransIdTest(UnitTester $I): void + { + $req = new TerminalPaymentCancelRequest('old-id'); + $req->setTransId('new-id'); + + $I->assertEquals('new-id', $req->getTransId()); + } + + // ----------------------------------------------------------------------- + // TerminalPaymentStatusRequest + // ----------------------------------------------------------------------- + + public function terminalPaymentStatusToArrayTest(UnitTester $I): void + { + $req = new TerminalPaymentStatusRequest('ZZ-99-AB'); + + $I->assertEquals(['transId' => 'ZZ-99-AB'], $req->toArray()); + } + + public function terminalPaymentStatusUrnContainsPlaceholderTest(UnitTester $I): void + { + $req = new TerminalPaymentStatusRequest('ZZ-99-AB'); + + $I->assertStringContainsString('{transId}', $req->getUrn()); + $I->assertStringContainsString('terminalPayment', $req->getUrn()); + } + + // ----------------------------------------------------------------------- + // TerminalRefundCancelRequest + // ----------------------------------------------------------------------- + + public function terminalRefundCancelToArrayTest(UnitTester $I): void + { + $req = new TerminalRefundCancelRequest('REF-001'); + + $I->assertEquals(['transId' => 'REF-001'], $req->toArray()); + } + + public function terminalRefundCancelUrnContainsPlaceholderTest(UnitTester $I): void + { + $req = new TerminalRefundCancelRequest('REF-001'); + + $I->assertStringContainsString('{transId}', $req->getUrn()); + $I->assertStringContainsString('terminalRefund', $req->getUrn()); + } + + // ----------------------------------------------------------------------- + // TerminalRefundStatusRequest + // ----------------------------------------------------------------------- + + public function terminalRefundStatusToArrayTest(UnitTester $I): void + { + $req = new TerminalRefundStatusRequest('REF-002'); + + $I->assertEquals(['transId' => 'REF-002'], $req->toArray()); + } + + public function terminalRefundStatusUrnContainsPlaceholderTest(UnitTester $I): void + { + $req = new TerminalRefundStatusRequest('REF-002'); + + $I->assertStringContainsString('{transId}', $req->getUrn()); + $I->assertStringContainsString('terminalRefund', $req->getUrn()); + } +} diff --git a/tests/Unit/Entity/Response/FileTransferResponsesCest.php b/tests/Unit/Entity/Response/FileTransferResponsesCest.php new file mode 100644 index 0000000..96e9c50 --- /dev/null +++ b/tests/Unit/Entity/Response/FileTransferResponsesCest.php @@ -0,0 +1,137 @@ + json_encode([ + 'abo' => base64_encode($content), + 'nazev' => 'transfer_20230201.abo', + ]), + ]); + + $response = new AboSingleTransferResponse($mock); + + $I->assertEquals('transfer_20230201.abo', $response->getFilename()); + $I->assertEquals($content, $response->getFileContent()); + } + + public function aboSingleTransferApiErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Transfer not found']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new AboSingleTransferResponse($mock); + }); + } + + public function aboSingleTransferApiErrorHasCorrectCodeTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1404, 'message' => 'Not found']), + ]); + + try { + new AboSingleTransferResponse($mock); + $I->fail('Expected ApiException to be thrown'); + } catch (ApiException $e) { + $I->assertEquals(1404, $e->getCode()); + $I->assertEquals('Not found', $e->getMessage()); + } + } + + public function aboSingleTransferInvalidBase64YieldsEmptyContentTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'abo' => '!!!invalid-base64!!!', + 'nazev' => 'file.abo', + ]), + ]); + + $response = new AboSingleTransferResponse($mock); + + $I->assertEquals('', $response->getFileContent()); + $I->assertEquals('file.abo', $response->getFilename()); + } + + // ----------------------------------------------------------------------- + // CsvSingleTransferResponse + // ----------------------------------------------------------------------- + + public function csvSingleTransferSuccessDecodesBase64Test(UnitTester $I): void + { + $content = "col1,col2\nval1,val2"; + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'csv' => base64_encode($content), + 'nazev' => 'transfer_20230201.csv', + ]), + ]); + + $response = new CsvSingleTransferResponse($mock); + + $I->assertEquals('transfer_20230201.csv', $response->getFilename()); + $I->assertEquals($content, $response->getFileContent()); + } + + public function csvSingleTransferApiErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'CSV not available']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new CsvSingleTransferResponse($mock); + }); + } + + public function csvSingleTransferApiErrorHasCorrectCodeTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 9999, 'message' => 'Server error']), + ]); + + try { + new CsvSingleTransferResponse($mock); + $I->fail('Expected ApiException to be thrown'); + } catch (ApiException $e) { + $I->assertEquals(9999, $e->getCode()); + } + } + + public function csvSingleTransferInvalidBase64YieldsEmptyContentTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'csv' => '!!!invalid-base64!!!', + 'nazev' => 'file.csv', + ]), + ]); + + $response = new CsvSingleTransferResponse($mock); + + $I->assertEquals('', $response->getFileContent()); + } +} diff --git a/tests/Unit/Entity/Response/MiscResponsesCest.php b/tests/Unit/Entity/Response/MiscResponsesCest.php new file mode 100644 index 0000000..238153d --- /dev/null +++ b/tests/Unit/Entity/Response/MiscResponsesCest.php @@ -0,0 +1,119 @@ + ['kty' => 'RSA', 'n' => 'abc123']])); + + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'code' => 0, + 'message' => 'OK', + 'key' => $keyData, + ]), + ]); + + $response = new PublicCryptoKeyResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('OK', $response->getMessage()); + $I->assertEquals($keyData, $response->getKey()); + } + + public function publicCryptoKeyErrorThrowsApiExceptionTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Key not available']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new PublicCryptoKeyResponse($mock); + }); + } + + public function publicCryptoKeyToArrayContainsAllFieldsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'OK', 'key' => 'mykey']), + ]); + + $array = (new PublicCryptoKeyResponse($mock))->toArray(); + + $I->assertArrayHasKey('code', $array); + $I->assertArrayHasKey('message', $array); + $I->assertArrayHasKey('key', $array); + $I->assertCount(3, $array); + } + + // ----------------------------------------------------------------------- + // AppleDomainAssociationResponse + // ----------------------------------------------------------------------- + + public function appleDomainAssociationSuccessHydratesFileContentTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['fileContent' => 'apple-developer-merchantid-domain-association']), + ]); + + $response = new AppleDomainAssociationResponse($mock); + + // Response does not expose getFileContent() — just verify it doesn't throw + $I->assertInstanceOf(AppleDomainAssociationResponse::class, $response); + } + + public function appleDomainAssociationCode1400ThrowsMissingParamExceptionTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1400, 'message' => 'Missing parameter']), + ]); + + $I->expectThrowable(MissingParamException::class, function () use ($mock) { + new AppleDomainAssociationResponse($mock); + }); + } + + public function appleDomainAssociationDefaultErrorThrowsApiExceptionTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Apple Pay not enabled']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new AppleDomainAssociationResponse($mock); + }); + } + + public function appleDomainAssociationErrorCodeIsPreservedTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 9876, 'message' => 'Custom error']), + ]); + + try { + new AppleDomainAssociationResponse($mock); + $I->fail('Expected ApiException'); + } catch (ApiException $e) { + $I->assertEquals(9876, $e->getCode()); + $I->assertEquals('Custom error', $e->getMessage()); + } + } +} diff --git a/tests/Unit/Entity/Response/MotoPaymentCreateResponseCest.php b/tests/Unit/Entity/Response/MotoPaymentCreateResponseCest.php new file mode 100644 index 0000000..78da07b --- /dev/null +++ b/tests/Unit/Entity/Response/MotoPaymentCreateResponseCest.php @@ -0,0 +1,117 @@ + json_encode([ + 'code' => 0, + 'message' => 'OK', + 'transId' => 'MT12-CD34-EF56', + 'status' => 'PENDING', + ]), + ]); + + $response = new MotoPaymentCreateResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('OK', $response->getMessage()); + $I->assertEquals('MT12-CD34-EF56', $response->getTransId()); + $I->assertEquals('PENDING', $response->getStatus()); + } + + public function code1400ThrowsMissingParamExceptionTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1400, 'message' => 'Missing required param']), + ]); + + $I->expectThrowable(MissingParamException::class, function () use ($mock) { + new MotoPaymentCreateResponse($mock); + }); + } + + public function defaultErrorThrowsApiExceptionTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Server error']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new MotoPaymentCreateResponse($mock); + }); + } + + /** + * MotoPaymentCreateResponse has unique error message construction: + * on default error, it appends ' - {transId}' and ' - {status}' to message when present. + */ + public function defaultErrorAppendsTransIdAndStatusToMessageTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'code' => 1500, + 'message' => 'Card declined', + 'transId' => 'MT99-FAIL', + 'status' => 'CANCELLED', + ]), + ]); + + try { + new MotoPaymentCreateResponse($mock); + $I->fail('Expected ApiException to be thrown'); + } catch (ApiException $e) { + $I->assertStringContainsString('Card declined', $e->getMessage()); + $I->assertStringContainsString('MT99-FAIL', $e->getMessage()); + $I->assertStringContainsString('CANCELLED', $e->getMessage()); + } + } + + public function defaultErrorWithoutTransIdAndStatusOnlyUsesBaseMessageTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 9999, 'message' => 'Unknown error']), + ]); + + try { + new MotoPaymentCreateResponse($mock); + $I->fail('Expected ApiException to be thrown'); + } catch (ApiException $e) { + $I->assertEquals('Unknown error', $e->getMessage()); + } + } + + public function toArrayContainsAllFieldsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'code' => 0, + 'message' => 'OK', + 'transId' => 'MT00-AA11', + 'status' => 'PAID', + ]), + ]); + + $array = (new MotoPaymentCreateResponse($mock))->toArray(); + + $I->assertArrayHasKey('code', $array); + $I->assertArrayHasKey('message', $array); + $I->assertArrayHasKey('transId', $array); + $I->assertArrayHasKey('status', $array); + $I->assertCount(4, $array); + } +} diff --git a/tests/Unit/Entity/Response/PaymentCreateResponseCest.php b/tests/Unit/Entity/Response/PaymentCreateResponseCest.php new file mode 100644 index 0000000..91c6642 --- /dev/null +++ b/tests/Unit/Entity/Response/PaymentCreateResponseCest.php @@ -0,0 +1,98 @@ + 0, + 'message' => 'OK', + 'transId' => 'ABCD-1234-EFGH', + 'redirect' => 'https://payments.comgate.cz/client/instructions/index?id=ABCD-1234-EFGH', + ]); + + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + $response = new PaymentCreateResponse($responseMock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('OK', $response->getMessage()); + $I->assertEquals('ABCD-1234-EFGH', $response->getTransId()); + $I->assertStringContainsString('ABCD-1234-EFGH', $response->getRedirect()); + } + + // --------------------------------------------------------------------------- + // Error code dispatch — each non-zero code must throw the correct exception + // --------------------------------------------------------------------------- + + /** + * @return array}> + */ + protected function errorCodeScenarios(): array + { + return [ + 'code 1400 → MissingParamException' => [ + 'code' => 1400, + 'message' => 'Missing required parameter', + 'expectedException' => MissingParamException::class, + ], + 'code 1401 → generic ApiException' => [ + 'code' => 1401, + 'message' => 'Unauthorized', + 'expectedException' => ApiException::class, + ], + 'code 1500 → generic ApiException' => [ + 'code' => 1500, + 'message' => 'Internal error', + 'expectedException' => ApiException::class, + ], + 'code 9999 → generic ApiException' => [ + 'code' => 9999, + 'message' => 'Unknown error', + 'expectedException' => ApiException::class, + ], + ]; + } + + #[DataProvider('errorCodeScenarios')] + public function testErrorCodesThrowCorrectExceptions(UnitTester $I, Example $example): void + { + $json = json_encode([ + 'code' => $example['code'], + 'message' => $example['message'], + ]); + + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + $I->expectThrowable($example['expectedException'], function () use ($responseMock) { + new PaymentCreateResponse($responseMock); + }); + } + + /** + * MissingParamException must NOT be thrown for a generic non-1400 error; + * i.e., the exception hierarchy must not accidentally widen the catch. + */ + public function testCode1401DoesNotThrowMissingParamException(UnitTester $I): void + { + $json = json_encode(['code' => 1401, 'message' => 'Unauthorized']); + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + try { + new PaymentCreateResponse($responseMock); + $I->fail('Expected ApiException to be thrown'); + } catch (ApiException $e) { + $I->assertNotInstanceOf(MissingParamException::class, $e); + } + } +} diff --git a/tests/Unit/Entity/Response/PaymentStatusResponseCest.php b/tests/Unit/Entity/Response/PaymentStatusResponseCest.php new file mode 100644 index 0000000..1ce5e52 --- /dev/null +++ b/tests/Unit/Entity/Response/PaymentStatusResponseCest.php @@ -0,0 +1,259 @@ + $overrides + */ + private function buildSuccessJson(array $overrides = []): string + { + $defaults = [ + 'code' => 0, + 'message' => 'OK', + 'merchant' => 'merchant123', + 'secret' => 'secretXYZ', + 'transId' => 'XXXX-YYYY-ZZZZ', + 'test' => 'false', + 'price' => 10000, + 'curr' => 'CZK', + 'label' => 'Test payment', + 'refId' => 'order-42', + 'payerId' => 'payer-99', + 'method' => 'CARD_CZ_COMGATE', + 'account' => '', + 'email' => 'buyer@example.com', + 'name' => 'Widget', + 'phone' => '+420000000000', + 'status' => 'PENDING', + 'payerName' => 'John Doe', + 'payerAcc' => 'CZ00/0000', + 'fee' => '0.00', + 'vs' => '123456', + 'cardValid' => '12/27', + 'cardNumber' => '****1234', + 'appliedFee' => '0', + 'appliedFeeTyp' => '', + 'paymentErrorReason' => '', + ]; + + return (string) json_encode(array_merge($defaults, $overrides)); + } + + // --------------------------------------------------------------------------- + // Full field hydration on success + // --------------------------------------------------------------------------- + + public function testSuccessfulResponseHydratesAllFields(UnitTester $I): void + { + $responseMock = Stub::make(Response::class, [ + 'getContent' => $this->buildSuccessJson(), + ]); + + $status = new PaymentStatusResponse($responseMock); + + $I->assertEquals(0, $status->getCode()); + $I->assertEquals('OK', $status->getMessage()); + $I->assertEquals('merchant123', $status->getMerchant()); + $I->assertEquals('secretXYZ', $status->getSecret()); + $I->assertEquals('XXXX-YYYY-ZZZZ', $status->getTransId()); + $I->assertEquals('CZK', $status->getCurrency()); + $I->assertEquals('Test payment', $status->getLabel()); + $I->assertEquals('order-42', $status->getRefId()); + $I->assertEquals('payer-99', $status->getPayerId()); + $I->assertEquals('CARD_CZ_COMGATE', $status->getMethod()); + $I->assertEquals('buyer@example.com', $status->getEmail()); + $I->assertEquals('Widget', $status->getName()); + $I->assertEquals('+420000000000', $status->getPhone()); + $I->assertEquals('PENDING', $status->getStatus()); + $I->assertEquals('John Doe', $status->getPayerName()); + $I->assertEquals('CZ00/0000', $status->getPayerAcc()); + $I->assertEquals('0.00', $status->getFee()); + $I->assertEquals('123456', $status->getVs()); + $I->assertEquals('12/27', $status->getCardValid()); + $I->assertEquals('****1234', $status->getCardNumber()); + $I->assertEquals('0', $status->getAppliedFee()); + $I->assertEquals('', $status->getAppliedFeeTyp()); + $I->assertEquals('', $status->getPaymentErrorReason()); + } + + // --------------------------------------------------------------------------- + // Boolean coercion: JSON string 'true'/'false' → PHP bool + // --------------------------------------------------------------------------- + + /** + * @return array + */ + protected function testFlagScenarios(): array + { + return [ + 'string "true" → bool true' => ['jsonTest' => 'true', 'expectedBool' => true], + 'string "false" → bool false' => ['jsonTest' => 'false', 'expectedBool' => false], + ]; + } + + #[DataProvider('testFlagScenarios')] + public function testTestFlagIsCoercedToBool(UnitTester $I, Example $example): void + { + $json = $this->buildSuccessJson(['test' => $example['jsonTest']]); + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + $status = new PaymentStatusResponse($responseMock); + $I->assertSame($example['expectedBool'], $status->isTest()); + } + + // --------------------------------------------------------------------------- + // Price in cents → Money value object + // --------------------------------------------------------------------------- + + /** + * @return array + */ + protected function priceConversionScenarios(): array + { + return [ + '100 CZK (10000 cents)' => ['priceCents' => 10000, 'expectedReal' => 100.0], + '1 cent' => ['priceCents' => 1, 'expectedReal' => 0.01], + '0 (free)' => ['priceCents' => 0, 'expectedReal' => 0.0], + 'large amount' => ['priceCents' => 999999,'expectedReal' => 9999.99], + ]; + } + + #[DataProvider('priceConversionScenarios')] + public function testPriceIsHydratedAsMoney(UnitTester $I, Example $example): void + { + $json = $this->buildSuccessJson(['price' => $example['priceCents']]); + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + $status = new PaymentStatusResponse($responseMock); + $I->assertInstanceOf(Money::class, $status->getPrice()); + $I->assertEquals($example['priceCents'], $status->getPrice()->get()); + $I->assertEquals($example['expectedReal'], $status->getPrice()->getReal()); + } + + // --------------------------------------------------------------------------- + // Missing optional fields fall back to empty-string defaults + // --------------------------------------------------------------------------- + + public function testMissingOptionalFieldsFallBackToEmptyString(UnitTester $I): void + { + // Provide only the mandatory fields; every optional field is absent + $json = (string) json_encode([ + 'code' => 0, + 'message' => 'OK', + 'transId' => 'T-001', + ]); + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + $status = new PaymentStatusResponse($responseMock); + + $I->assertEquals('', $status->getMerchant()); + $I->assertEquals('', $status->getSecret()); + $I->assertEquals('', $status->getCurrency()); + $I->assertEquals('', $status->getLabel()); + $I->assertEquals('', $status->getRefId()); + $I->assertEquals('', $status->getPayerId()); + $I->assertEquals('', $status->getMethod()); + $I->assertEquals('', $status->getAccount()); + $I->assertEquals('', $status->getEmail()); + $I->assertEquals('', $status->getName()); + $I->assertEquals('', $status->getPhone()); + $I->assertEquals('', $status->getStatus()); + $I->assertEquals('', $status->getPayerName()); + $I->assertEquals('', $status->getPayerAcc()); + $I->assertEquals('', $status->getFee()); + $I->assertEquals('', $status->getVs()); + $I->assertEquals('', $status->getCardValid()); + $I->assertEquals('', $status->getCardNumber()); + $I->assertEquals('', $status->getAppliedFee()); + $I->assertEquals('', $status->getAppliedFeeTyp()); + $I->assertEquals('', $status->getPaymentErrorReason()); + // 'test' absent → treated as 'false' → false + $I->assertFalse($status->isTest()); + // 'price' absent → 0 cents + $I->assertEquals(0, $status->getPrice()->get()); + } + + // --------------------------------------------------------------------------- + // Error code dispatch + // --------------------------------------------------------------------------- + + /** + * @return array}> + */ + protected function errorCodeScenarios(): array + { + return [ + 'code 1400 → PaymentNotFoundException' => [ + 'code' => 1400, + 'message' => 'Transaction not found', + 'expectedException' => PaymentNotFoundException::class, + ], + 'code 1401 → generic ApiException' => [ + 'code' => 1401, + 'message' => 'Unauthorized', + 'expectedException' => ApiException::class, + ], + 'code 1500 → generic ApiException' => [ + 'code' => 1500, + 'message' => 'Internal server error', + 'expectedException' => ApiException::class, + ], + 'code 9999 → generic ApiException' => [ + 'code' => 9999, + 'message' => 'Unknown', + 'expectedException' => ApiException::class, + ], + ]; + } + + #[DataProvider('errorCodeScenarios')] + public function testErrorCodesThrowCorrectExceptions(UnitTester $I, Example $example): void + { + $json = json_encode([ + 'code' => $example['code'], + 'message' => $example['message'], + ]); + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + $I->expectThrowable($example['expectedException'], function () use ($responseMock) { + new PaymentStatusResponse($responseMock); + }); + } + + /** + * `PaymentNotFoundException` extends `ApiException`. + * For code 1400 the thrown exception must be the specific subtype, not just + * the generic parent — verifies correct exception hierarchy is preserved. + */ + public function testCode1400ThrowsPaymentNotFoundAndNotGenericApiException(UnitTester $I): void + { + $json = json_encode(['code' => 1400, 'message' => 'Not found']); + $responseMock = Stub::make(Response::class, ['getContent' => $json]); + + try { + new PaymentStatusResponse($responseMock); + $I->fail('Expected PaymentNotFoundException to be thrown'); + } catch (PaymentNotFoundException $e) { + $I->assertInstanceOf(ApiException::class, $e); // is-a ApiException + $I->assertEquals(1400, $e->getCode()); + } + } +} diff --git a/tests/Unit/Entity/Response/PreauthResponsesCest.php b/tests/Unit/Entity/Response/PreauthResponsesCest.php new file mode 100644 index 0000000..229d470 --- /dev/null +++ b/tests/Unit/Entity/Response/PreauthResponsesCest.php @@ -0,0 +1,181 @@ + + */ + protected function successScenarios(): array + { + return [ + 'PreauthCaptureResponse success' => [ + 'class' => PreauthCaptureResponse::class, + 'code' => 0, + 'message' => 'OK', + ], + 'PreauthCancelResponse success' => [ + 'class' => PreauthCancelResponse::class, + 'code' => 0, + 'message' => 'Payment preauth cancelled', + ], + ]; + } + + #[DataProvider('successScenarios')] + public function testSuccessPopulatesCodeAndMessage(UnitTester $I, Example $example): void + { + $json = json_encode(['code' => $example['code'], 'message' => $example['message']]); + $mock = Stub::make(Response::class, ['getContent' => $json]); + + $class = $example['class']; + $response = new $class($mock); + + $I->assertEquals($example['code'], $response->getCode()); + $I->assertEquals($example['message'], $response->getMessage()); + } + + // ----------------------------------------------------------------------- + // Error code dispatch — both classes × 3 codes + // ----------------------------------------------------------------------- + + /** + * @return array}> + */ + protected function errorDispatchScenarios(): array + { + $cases = []; + foreach ([PreauthCaptureResponse::class, PreauthCancelResponse::class] as $class) { + $shortName = (new \ReflectionClass($class))->getShortName(); + + $cases["{$shortName} code 1400 → MissingParamException"] = [ + 'class' => $class, + 'code' => 1400, + 'message' => 'Required parameter missing', + 'expectedException' => MissingParamException::class, + ]; + $cases["{$shortName} code 1401 → PreauthException"] = [ + 'class' => $class, + 'code' => 1401, + 'message' => 'Preauth operation failed', + 'expectedException' => PreauthException::class, + ]; + $cases["{$shortName} code 9999 → ApiException"] = [ + 'class' => $class, + 'code' => 9999, + 'message' => 'Unexpected error', + 'expectedException' => ApiException::class, + ]; + } + + return $cases; + } + + #[DataProvider('errorDispatchScenarios')] + public function testErrorCodeDispatchThrowsCorrectException(UnitTester $I, Example $example): void + { + $json = json_encode(['code' => $example['code'], 'message' => $example['message']]); + $mock = Stub::make(Response::class, ['getContent' => $json]); + + $class = $example['class']; + + $I->expectThrowable($example['expectedException'], function () use ($class, $mock) { + new $class($mock); + }); + } + + // ----------------------------------------------------------------------- + // PreauthCaptureResponse::toArray() + // ----------------------------------------------------------------------- + + public function testCaptureToArrayReflectsStoredValues(UnitTester $I): void + { + $json = json_encode(['code' => 0, 'message' => 'Captured']); + $mock = Stub::make(Response::class, ['getContent' => $json]); + + $response = new PreauthCaptureResponse($mock); + $array = $response->toArray(); + + $I->assertSame(['code' => 0, 'message' => 'Captured'], $array); + } + + // ----------------------------------------------------------------------- + // Exception hierarchy — PreauthException specifics + // ----------------------------------------------------------------------- + + /** + * PreauthException must extend ApiException so generic catch blocks work. + */ + public function testPreauthExceptionExtendsApiException(UnitTester $I): void + { + $e = new PreauthException('test', 1401); + + $I->assertInstanceOf(ApiException::class, $e); + } + + /** + * Code 1401 must throw PreauthException, NOT MissingParamException. + * Guards against a future refactor that widens or swaps the exception type. + */ + public function testCode1401ThrowsPreauthExceptionNotMissingParam(UnitTester $I): void + { + $json = json_encode(['code' => 1401, 'message' => 'Preauth failed']); + $mock = Stub::make(Response::class, ['getContent' => $json]); + + try { + new PreauthCaptureResponse($mock); + $I->fail('Expected PreauthException to be thrown'); + } catch (ApiException $e) { + $I->assertInstanceOf(PreauthException::class, $e); + $I->assertNotInstanceOf(MissingParamException::class, $e); + $I->assertEquals(1401, $e->getCode()); + $I->assertEquals('Preauth failed', $e->getMessage()); + } + } + + /** + * Code 1400 must throw MissingParamException, NOT PreauthException. + * Guards against accidentally swapping the two exception types. + */ + public function testCode1400ThrowsMissingParamNotPreauthException(UnitTester $I): void + { + $json = json_encode(['code' => 1400, 'message' => 'Missing transId']); + $mock = Stub::make(Response::class, ['getContent' => $json]); + + try { + new PreauthCancelResponse($mock); + $I->fail('Expected MissingParamException to be thrown'); + } catch (ApiException $e) { + $I->assertInstanceOf(MissingParamException::class, $e); + $I->assertNotInstanceOf(PreauthException::class, $e); + $I->assertEquals(1400, $e->getCode()); + } + } +} diff --git a/tests/Unit/Entity/Response/SimpleResponsesCest.php b/tests/Unit/Entity/Response/SimpleResponsesCest.php new file mode 100644 index 0000000..87da466 --- /dev/null +++ b/tests/Unit/Entity/Response/SimpleResponsesCest.php @@ -0,0 +1,213 @@ +}> + */ + protected function successScenarios(): array + { + return [ + 'PaymentCancelResponse success' => [ + 'class' => PaymentCancelResponse::class, + 'payload' => ['code' => 0, 'message' => 'Payment cancelled'], + ], + 'RecurringPaymentResponse success' => [ + 'class' => RecurringPaymentResponse::class, + 'payload' => ['code' => 0, 'message' => 'OK', 'transId' => 'AB12-CD34-EF56'], + ], + 'RefundResponse success' => [ + 'class' => RefundResponse::class, + 'payload' => ['code' => 0, 'message' => 'Refund accepted'], + ], + 'SimulationResponse success' => [ + 'class' => SimulationResponse::class, + 'payload' => ['code' => 0, 'message' => 'Simulation done'], + ], + ]; + } + + #[DataProvider('successScenarios')] + public function testSuccessPopulatesCodeAndMessage(UnitTester $I, Example $example): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode($example['payload'])]); + + $class = $example['class']; + $response = new $class($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals($example['payload']['message'], $response->getMessage()); + } + + // ----------------------------------------------------------------------- + // RecurringPaymentResponse — transId hydration + // ----------------------------------------------------------------------- + + public function testRecurringPaymentResponseHydratesTransId(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'OK', 'transId' => 'ABCD-1234-EFGH']), + ]); + + $response = new RecurringPaymentResponse($mock); + + $I->assertEquals('ABCD-1234-EFGH', $response->getTransId()); + $I->assertSame( + ['code' => 0, 'message' => 'OK', 'transId' => 'ABCD-1234-EFGH'], + $response->toArray(), + ); + } + + // ----------------------------------------------------------------------- + // Code 1400 → MissingParamException on the three classes that handle it + // ----------------------------------------------------------------------- + + /** + * @return array + */ + protected function missingParamScenarios(): array + { + return [ + 'PaymentCancelResponse 1400' => ['class' => PaymentCancelResponse::class], + 'RecurringPaymentResponse 1400' => ['class' => RecurringPaymentResponse::class], + 'RefundResponse 1400' => ['class' => RefundResponse::class], + ]; + } + + #[DataProvider('missingParamScenarios')] + public function testCode1400ThrowsMissingParamException(UnitTester $I, Example $example): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode(['code' => 1400, 'message' => 'Missing transId'])]); + $class = $example['class']; + + $I->expectThrowable(MissingParamException::class, function () use ($class, $mock) { + new $class($mock); + }); + } + + // ----------------------------------------------------------------------- + // SimulationResponse — code 1400 has NO dedicated branch + // Falls through to the default → ApiException, NOT MissingParamException + // ----------------------------------------------------------------------- + + public function testSimulationResponseCode1400ThrowsApiExceptionNotMissingParam(UnitTester $I): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode(['code' => 1400, 'message' => 'Error'])]); + + try { + new SimulationResponse($mock); + $I->fail('Expected ApiException to be thrown'); + } catch (ApiException $e) { + $I->assertNotInstanceOf( + MissingParamException::class, + $e, + 'SimulationResponse has no explicit 1400 branch; code 1400 must fall through to ApiException', + ); + $I->assertEquals(1400, $e->getCode()); + } + } + + // ----------------------------------------------------------------------- + // Generic error code → ApiException on all four classes + // ----------------------------------------------------------------------- + + /** + * @return array + */ + protected function genericErrorScenarios(): array + { + return [ + 'PaymentCancelResponse 9999' => ['class' => PaymentCancelResponse::class], + 'RecurringPaymentResponse 9999' => ['class' => RecurringPaymentResponse::class], + 'RefundResponse 9999' => ['class' => RefundResponse::class], + 'SimulationResponse 9999' => ['class' => SimulationResponse::class], + ]; + } + + #[DataProvider('genericErrorScenarios')] + public function testGenericErrorCodeThrowsApiException(UnitTester $I, Example $example): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode(['code' => 9999, 'message' => 'Unknown'])]); + $class = $example['class']; + + $I->expectThrowable(ApiException::class, function () use ($class, $mock) { + new $class($mock); + }); + } + + // ----------------------------------------------------------------------- + // toArray() shape — verify all fields are present for each class + // ----------------------------------------------------------------------- + + /** + * @return array, expectedKeys: list}> + */ + protected function toArrayScenarios(): array + { + return [ + 'PaymentCancelResponse toArray' => [ + 'class' => PaymentCancelResponse::class, + 'payload' => ['code' => 0, 'message' => 'Cancelled'], + 'expectedKeys' => ['code', 'message'], + ], + 'RecurringPaymentResponse toArray' => [ + 'class' => RecurringPaymentResponse::class, + 'payload' => ['code' => 0, 'message' => 'OK', 'transId' => 'XY99'], + 'expectedKeys' => ['code', 'message', 'transId'], + ], + 'RefundResponse toArray' => [ + 'class' => RefundResponse::class, + 'payload' => ['code' => 0, 'message' => 'Refunded'], + 'expectedKeys' => ['code', 'message'], + ], + 'SimulationResponse toArray' => [ + 'class' => SimulationResponse::class, + 'payload' => ['code' => 0, 'message' => 'Simulated'], + 'expectedKeys' => ['code', 'message'], + ], + ]; + } + + #[DataProvider('toArrayScenarios')] + public function testToArrayContainsExpectedKeys(UnitTester $I, Example $example): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode($example['payload'])]); + $class = $example['class']; + $response = new $class($mock); + $array = $response->toArray(); + + foreach ($example['expectedKeys'] as $key) { + $I->assertArrayHasKey($key, $array); + } + + $I->assertCount(count($example['expectedKeys']), $array); + } +} diff --git a/tests/Unit/Entity/Response/TerminalResponsesCest.php b/tests/Unit/Entity/Response/TerminalResponsesCest.php new file mode 100644 index 0000000..2550ed2 --- /dev/null +++ b/tests/Unit/Entity/Response/TerminalResponsesCest.php @@ -0,0 +1,346 @@ + json_encode(['code' => 0, 'message' => 'OK', 'transId' => 'AB12-CD34-EF56']), + ]); + + $response = new TerminalPaymentCreateResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('OK', $response->getMessage()); + $I->assertEquals('AB12-CD34-EF56', $response->getTransId()); + } + + public function terminalPaymentCreateErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Terminal error']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalPaymentCreateResponse($mock); + }); + } + + public function terminalPaymentCreateMissingCodeDefaultsToErrorTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode([])]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalPaymentCreateResponse($mock); + }); + } + + // ----------------------------------------------------------------------- + // TerminalPaymentStatusResponse + // ----------------------------------------------------------------------- + + public function terminalPaymentStatusSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'code' => 0, + 'message' => 'OK', + 'price' => 10000, + 'curr' => 'CZK', + 'refId' => 'order-42', + 'transId' => 'AB12-CD34', + 'status' => 'PAID', + 'fee' => '1.50', + 'cardValid' => '202512', + 'cardNumber' => '****1234', + 'paymentErrorReason' => '', + 'reversed' => false, + 'amountRefunded' => '0', + ]), + ]); + + $response = new TerminalPaymentStatusResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals(10000, $response->getPrice()); + $I->assertEquals('CZK', $response->getCurr()); + $I->assertEquals('order-42', $response->getRefId()); + $I->assertEquals('AB12-CD34', $response->getTransId()); + $I->assertEquals('PAID', $response->getStatus()); + $I->assertEquals('1.50', $response->getFee()); + $I->assertEquals('202512', $response->getCardValid()); + $I->assertEquals('****1234', $response->getCardNumber()); + $I->assertFalse($response->isReversed()); + $I->assertEquals('0', $response->getAmountRefunded()); + } + + public function terminalPaymentStatusDefaultsForMissingFieldsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'OK']), + ]); + + $response = new TerminalPaymentStatusResponse($mock); + + $I->assertEquals(0, $response->getPrice()); + $I->assertEquals('', $response->getCurr()); + $I->assertEquals('', $response->getTransId()); + $I->assertEquals('', $response->getStatus()); + $I->assertFalse($response->isReversed()); + } + + public function terminalPaymentStatusErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1404, 'message' => 'Payment not found']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalPaymentStatusResponse($mock); + }); + } + + public function terminalPaymentStatusToArrayTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'OK', 'transId' => 'XY99', 'status' => 'PENDING']), + ]); + + $array = (new TerminalPaymentStatusResponse($mock))->toArray(); + + $I->assertArrayHasKey('code', $array); + $I->assertArrayHasKey('transId', $array); + $I->assertArrayHasKey('status', $array); + $I->assertArrayHasKey('reversed', $array); + $I->assertArrayHasKey('amountRefunded', $array); + } + + // ----------------------------------------------------------------------- + // TerminalPaymentCancelResponse + // ----------------------------------------------------------------------- + + public function terminalPaymentCancelSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'Cancelled']), + ]); + + $response = new TerminalPaymentCancelResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('Cancelled', $response->getMessage()); + } + + public function terminalPaymentCancelErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Cannot cancel']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalPaymentCancelResponse($mock); + }); + } + + // ----------------------------------------------------------------------- + // TerminalClosingResponse + // ----------------------------------------------------------------------- + + public function terminalClosingSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'code' => 0, + 'message' => 'Closing done', + 'batchNumber' => 42, + 'batchData' => [['amount' => 1000, 'count' => 5]], + ]), + ]); + + $response = new TerminalClosingResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals(42, $response->getBatchNumber()); + $I->assertCount(1, $response->getBatchData()); + } + + public function terminalClosingDefaultsForMissingBatchDataTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'OK']), + ]); + + $response = new TerminalClosingResponse($mock); + + $I->assertEquals(0, $response->getBatchNumber()); + $I->assertIsArray($response->getBatchData()); + $I->assertEmpty($response->getBatchData()); + } + + public function terminalClosingErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Terminal busy']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalClosingResponse($mock); + }); + } + + // ----------------------------------------------------------------------- + // TerminalRefundCreateResponse + // ----------------------------------------------------------------------- + + public function terminalRefundCreateSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'OK', 'transId' => 'RF12-CD34']), + ]); + + $response = new TerminalRefundCreateResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('RF12-CD34', $response->getTransId()); + } + + public function terminalRefundCreateErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Refund failed']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalRefundCreateResponse($mock); + }); + } + + // ----------------------------------------------------------------------- + // TerminalRefundStatusResponse + // ----------------------------------------------------------------------- + + public function terminalRefundStatusSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode([ + 'code' => 0, + 'message' => 'OK', + 'price' => 5000, + 'curr' => 'CZK', + 'refId' => 'ref-99', + 'transId' => 'RF99-AA11', + 'status' => 'PAID', + 'cardNumber' => '****5678', + 'reversed' => true, + ]), + ]); + + $response = new TerminalRefundStatusResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals(5000, $response->getPrice()); + $I->assertEquals('CZK', $response->getCurr()); + $I->assertEquals('RF99-AA11', $response->getTransId()); + $I->assertEquals('PAID', $response->getStatus()); + $I->assertEquals('****5678', $response->getCardNumber()); + $I->assertTrue($response->isReversed()); + } + + public function terminalRefundStatusErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1404, 'message' => 'Not found']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalRefundStatusResponse($mock); + }); + } + + // ----------------------------------------------------------------------- + // TerminalRefundCancelResponse + // ----------------------------------------------------------------------- + + public function terminalRefundCancelSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 0, 'message' => 'Refund cancelled']), + ]); + + $response = new TerminalRefundCancelResponse($mock); + + $I->assertEquals(0, $response->getCode()); + $I->assertEquals('Refund cancelled', $response->getMessage()); + } + + public function terminalRefundCancelErrorThrowsTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['code' => 1500, 'message' => 'Cannot cancel refund']), + ]); + + $I->expectThrowable(ApiException::class, function () use ($mock) { + new TerminalRefundCancelResponse($mock); + }); + } + + // ----------------------------------------------------------------------- + // TerminalStatusResponse + // ----------------------------------------------------------------------- + + public function terminalStatusSuccessTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['status' => 'ONLINE']), + ]); + + $response = new TerminalStatusResponse($mock); + + $I->assertEquals('ONLINE', $response->getStatus()); + } + + public function terminalStatusDefaultsToUnknownWhenMissingTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode([])]); + + $response = new TerminalStatusResponse($mock); + + $I->assertEquals('UNKNOWN', $response->getStatus()); + } + + public function terminalStatusToArrayTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, [ + 'getContent' => json_encode(['status' => 'OFFLINE']), + ]); + + $array = (new TerminalStatusResponse($mock))->toArray(); + + $I->assertEquals(['status' => 'OFFLINE'], $array); + } +} diff --git a/tests/Unit/Entity/Response/TransferResponsesCest.php b/tests/Unit/Entity/Response/TransferResponsesCest.php new file mode 100644 index 0000000..5e7c83b --- /dev/null +++ b/tests/Unit/Entity/Response/TransferResponsesCest.php @@ -0,0 +1,152 @@ + 1, + 'transferDate' => '2023-02-01', + 'accountCounterparty' => '0/0000', + 'accountOutgoing' => '1/0000', + 'variableSymbol' => '0123456789', + ], + [ + 'transferId' => 2, + 'transferDate' => '2023-02-02', + 'accountCounterparty' => '2/0200', + 'accountOutgoing' => '3/0300', + 'variableSymbol' => '9876543210', + ], + ]; + + $mock = Stub::make(Response::class, ['getContent' => json_encode($data)]); + + $response = new TransferListResponse($mock); + $list = $response->getTransferList(); + + $I->assertCount(2, $list); + $I->assertInstanceOf(Transfer::class, $list[0]); + $I->assertEquals(1, $list[0]->getTransferId()); + $I->assertEquals('0/0000', $list[0]->getAccountCounterparty()); + $I->assertEquals('1/0000', $list[0]->getAccountOutgoing()); + $I->assertEquals('0123456789', $list[0]->getVariableSymbol()); + } + + public function transferListResponseEmptyArrayReturnsEmptyListTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode([])]); + + $response = new TransferListResponse($mock); + + $I->assertIsArray($response->getTransferList()); + $I->assertEmpty($response->getTransferList()); + } + + // ----------------------------------------------------------------------- + // Transfer::fromArray() — entity hydration + // ----------------------------------------------------------------------- + + public function transferFromArrayHydratesAllFieldsTest(UnitTester $I): void + { + $transfer = (new Transfer())->fromArray([ + 'transferId' => 999, + 'transferDate' => '2024-06-15', + 'accountCounterparty' => '100/0100', + 'accountOutgoing' => '200/0200', + 'variableSymbol' => '5555555555', + ]); + + $I->assertEquals(999, $transfer->getTransferId()); + $I->assertEquals('100/0100', $transfer->getAccountCounterparty()); + $I->assertEquals('200/0200', $transfer->getAccountOutgoing()); + $I->assertEquals('5555555555', $transfer->getVariableSymbol()); + $I->assertInstanceOf(\DateTimeInterface::class, $transfer->getTransferDate()); + $I->assertEquals('2024-06-15', $transfer->getTransferDate()->format('Y-m-d')); + } + + public function transferFromArrayInvalidDateThrowsTest(UnitTester $I): void + { + $I->expectThrowable(Exception::class, function () { + (new Transfer())->fromArray([ + 'transferId' => 1, + 'transferDate' => 'not-a-date', + 'accountCounterparty' => '0/0000', + 'accountOutgoing' => '1/0000', + 'variableSymbol' => '1234567890', + ]); + }); + } + + // ----------------------------------------------------------------------- + // SingleTransferResponse + // ----------------------------------------------------------------------- + + public function singleTransferResponseHydratesPaymentInfoObjectsTest(UnitTester $I): void + { + $data = [ + ['transId' => 'AB12', 'status' => 'PAID', 'amount' => 10000], + ['transId' => 'CD34', 'status' => 'PENDING', 'amount' => 5000], + ]; + + $mock = Stub::make(Response::class, ['getContent' => json_encode($data)]); + + $response = new SingleTransferResponse($mock); + $list = $response->getPaymentsList(); + + $I->assertCount(2, $list); + $I->assertInstanceOf(PaymentInfo::class, $list[0]); + $I->assertEquals(['transId' => 'AB12', 'status' => 'PAID', 'amount' => 10000], $list[0]->getData()); + } + + public function singleTransferResponseEmptyJsonReturnsEmptyListTest(UnitTester $I): void + { + $mock = Stub::make(Response::class, ['getContent' => json_encode([])]); + + $response = new SingleTransferResponse($mock); + + $I->assertIsArray($response->getPaymentsList()); + $I->assertEmpty($response->getPaymentsList()); + } + + // ----------------------------------------------------------------------- + // PaymentInfo — fromArray() and getData() + // ----------------------------------------------------------------------- + + public function paymentInfoFromArrayStoresDataTest(UnitTester $I): void + { + $data = ['transId' => 'XY99', 'amount' => 2500, 'currency' => 'CZK']; + $info = (new PaymentInfo())->fromArray($data); + + $I->assertEquals($data, $info->getData()); + } + + public function paymentInfoSetDataReplacesExistingTest(UnitTester $I): void + { + $info = new PaymentInfo(); + $info->fromArray(['key' => 'old']); + $info->setData(['key' => 'new']); + + $I->assertEquals(['key' => 'new'], $info->getData()); + } +} diff --git a/tests/Unit/Entity/TerminalMotoEntitiesCest.php b/tests/Unit/Entity/TerminalMotoEntitiesCest.php new file mode 100644 index 0000000..1741abf --- /dev/null +++ b/tests/Unit/Entity/TerminalMotoEntitiesCest.php @@ -0,0 +1,165 @@ +setPrice(10); + + $I->assertEquals(1000, $entity->getPrice()->get()); + } + + #[Group('terminal-payment-entity')] + public function terminalPaymentSetPriceFromFloatTest(UnitTester $I): void + { + $entity = (new TerminalPayment())->setPrice(9.99); + + $I->assertEquals(999, $entity->getPrice()->get()); + } + + #[Group('terminal-payment-entity')] + public function terminalPaymentSetPriceFromMoneyTest(UnitTester $I): void + { + $money = Money::ofCents(1234); + $entity = (new TerminalPayment())->setPrice($money); + + $I->assertEquals(1234, $entity->getPrice()->get()); + } + + #[Group('terminal-payment-entity')] + public function terminalPaymentCurrTest(UnitTester $I): void + { + $entity = (new TerminalPayment())->setCurr(CurrencyCode::CZK); + + $I->assertEquals(CurrencyCode::CZK, $entity->getCurr()); + } + + #[Group('terminal-payment-entity')] + public function terminalPaymentRefIdNullByDefaultTest(UnitTester $I): void + { + $entity = new TerminalPayment(); + + $I->assertNull($entity->getRefId()); + } + + #[Group('terminal-payment-entity')] + public function terminalPaymentRefIdSetTest(UnitTester $I): void + { + $entity = (new TerminalPayment())->setRefId('order-999'); + + $I->assertEquals('order-999', $entity->getRefId()); + } + + #[Group('terminal-payment-entity')] + public function terminalPaymentRefIdClearedWithNullTest(UnitTester $I): void + { + $entity = (new TerminalPayment()) + ->setRefId('order-999') + ->setRefId(null); + + $I->assertNull($entity->getRefId()); + } + + // ----------------------------------------------------------------------- + // TerminalRefund + // ----------------------------------------------------------------------- + + #[Group('terminal-refund-entity')] + public function terminalRefundSetPriceFromIntTest(UnitTester $I): void + { + $entity = (new TerminalRefund())->setPrice(5); + + $I->assertEquals(500, $entity->getPrice()->get()); + } + + #[Group('terminal-refund-entity')] + public function terminalRefundCurrTest(UnitTester $I): void + { + $entity = (new TerminalRefund())->setCurr(CurrencyCode::EUR); + + $I->assertEquals(CurrencyCode::EUR, $entity->getCurr()); + } + + #[Group('terminal-refund-entity')] + public function terminalRefundRefIdNullByDefaultTest(UnitTester $I): void + { + $entity = new TerminalRefund(); + + $I->assertNull($entity->getRefId()); + } + + #[Group('terminal-refund-entity')] + public function terminalRefundRefIdSetAndClearedTest(UnitTester $I): void + { + $entity = (new TerminalRefund())->setRefId('ref-42'); + + $I->assertEquals('ref-42', $entity->getRefId()); + + $entity->setRefId(null); + + $I->assertNull($entity->getRefId()); + } + + // ----------------------------------------------------------------------- + // MotoPayment + // ----------------------------------------------------------------------- + + #[Group('moto-payment-entity')] + public function motoPaymentEncryptedCardNumberTest(UnitTester $I): void + { + $entity = (new MotoPayment())->setEncryptedCardNumber('enc-card-123'); + + $I->assertEquals('enc-card-123', $entity->getEncryptedCardNumber()); + } + + #[Group('moto-payment-entity')] + public function motoPaymentEncryptedCardExpirationTest(UnitTester $I): void + { + $entity = (new MotoPayment())->setEncryptedCardExpiration('enc-exp-456'); + + $I->assertEquals('enc-exp-456', $entity->getEncryptedCardExpiration()); + } + + #[Group('moto-payment-entity')] + public function motoPaymentEncryptedCardCvvTest(UnitTester $I): void + { + $entity = (new MotoPayment())->setEncryptedCardCvv('enc-cvv-789'); + + $I->assertEquals('enc-cvv-789', $entity->getEncryptedCardCvv()); + } + + #[Group('moto-payment-entity')] + public function motoPaymentDefaultsAreEmptyStringsTest(UnitTester $I): void + { + $entity = new MotoPayment(); + + $I->assertEquals('', $entity->getEncryptedCardNumber()); + $I->assertEquals('', $entity->getEncryptedCardExpiration()); + $I->assertEquals('', $entity->getEncryptedCardCvv()); + } + + #[Group('moto-payment-entity')] + public function motoPaymentInheritsPaymentPriceTest(UnitTester $I): void + { + $entity = (new MotoPayment())->setPrice(Money::ofInt(50)); + + $I->assertEquals(5000, $entity->getPrice()->get()); + } +} diff --git a/tests/Unit/Http/PsrAdaptersCest.php b/tests/Unit/Http/PsrAdaptersCest.php new file mode 100644 index 0000000..1dc5761 --- /dev/null +++ b/tests/Unit/Http/PsrAdaptersCest.php @@ -0,0 +1,213 @@ +assertEquals('hello world', (string) $stream); + } + + #[Group('psr-stream')] + public function psrStreamGetSizeTest(UnitTester $I): void + { + $stream = new PsrStream('hello'); + + $I->assertEquals(5, $stream->getSize()); + } + + #[Group('psr-stream')] + public function psrStreamGetContentsTest(UnitTester $I): void + { + $stream = new PsrStream('some content'); + $stream->rewind(); + + $I->assertEquals('some content', $stream->getContents()); + } + + #[Group('psr-stream')] + public function psrStreamReadTest(UnitTester $I): void + { + $stream = new PsrStream('abcdefgh'); + $stream->rewind(); + + $I->assertEquals('abc', $stream->read(3)); + } + + #[Group('psr-stream')] + public function psrStreamWriteTest(UnitTester $I): void + { + $stream = new PsrStream('initial'); + $stream->seek(0, SEEK_END); + $stream->write(' appended'); + + $I->assertEquals('initial appended', (string) $stream); + } + + #[Group('psr-stream')] + public function psrStreamTellAfterRewindIsZeroTest(UnitTester $I): void + { + $stream = new PsrStream('data'); + $stream->rewind(); + + $I->assertEquals(0, $stream->tell()); + } + + #[Group('psr-stream')] + public function psrStreamIsSeekableReadableWritableTest(UnitTester $I): void + { + $stream = new PsrStream('x'); + + $I->assertTrue($stream->isSeekable()); + $I->assertTrue($stream->isReadable()); + $I->assertTrue($stream->isWritable()); + } + + #[Group('psr-stream')] + public function psrStreamEofAfterReadingAllTest(UnitTester $I): void + { + $stream = new PsrStream('hi'); + $stream->read(1000); + + $I->assertTrue($stream->eof()); + } + + #[Group('psr-stream')] + public function psrStreamGetMetadataReturnsArrayWithNoKeyTest(UnitTester $I): void + { + $stream = new PsrStream('meta'); + $meta = $stream->getMetadata(); + + $I->assertIsArray($meta); + $I->assertArrayHasKey('stream_type', $meta); + } + + #[Group('psr-stream')] + public function psrStreamGetMetadataWithKeyReturnsValueTest(UnitTester $I): void + { + $stream = new PsrStream('meta'); + $type = $stream->getMetadata('stream_type'); + + $I->assertNotNull($type); + } + + #[Group('psr-stream')] + public function psrStreamEmptyContentTest(UnitTester $I): void + { + $stream = new PsrStream(''); + + $I->assertEquals('', (string) $stream); + $I->assertEquals(0, $stream->getSize()); + } + + #[Group('psr-stream')] + public function psrStreamDetachReturnsResourceTest(UnitTester $I): void + { + $stream = new PsrStream('data'); + $resource = $stream->detach(); + + $I->assertIsResource($resource); + } + + // ----------------------------------------------------------------------- + // PsrResponse + // ----------------------------------------------------------------------- + + #[Group('psr-response')] + public function psrResponseDefaultProtocolVersionTest(UnitTester $I): void + { + $response = new PsrResponse(); + + $I->assertEquals('1.1', $response->getProtocolVersion()); + } + + #[Group('psr-response')] + public function psrResponseWithProtocolVersionTest(UnitTester $I): void + { + $response = new PsrResponse(); + $new = $response->withProtocolVersion('2.0'); + + $I->assertEquals('2.0', $new->getProtocolVersion()); + $I->assertEquals('1.1', $response->getProtocolVersion()); // immutable + } + + #[Group('psr-response')] + public function psrResponseWithHeaderTest(UnitTester $I): void + { + $response = new PsrResponse(); + $new = $response->withHeader('Content-Type', 'application/json'); + + $I->assertTrue($new->hasHeader('Content-Type')); + $I->assertEquals(['application/json'], $new->getHeader('Content-Type')); + } + + #[Group('psr-response')] + public function psrResponseGetHeaderLineTest(UnitTester $I): void + { + $response = new PsrResponse(); + $new = $response->withHeader('Accept', ['text/html', 'application/json']); + + $I->assertEquals('text/html, application/json', $new->getHeaderLine('Accept')); + } + + #[Group('psr-response')] + public function psrResponseWithAddedHeaderTest(UnitTester $I): void + { + $response = (new PsrResponse())->withHeader('X-Custom', 'first'); + $new = $response->withAddedHeader('X-Custom', 'second'); + + $I->assertCount(2, $new->getHeader('X-Custom')); + } + + #[Group('psr-response')] + public function psrResponseWithoutHeaderTest(UnitTester $I): void + { + $response = (new PsrResponse())->withHeader('X-Remove', 'value'); + $new = $response->withoutHeader('X-Remove'); + + $I->assertFalse($new->hasHeader('X-Remove')); + } + + #[Group('psr-response')] + public function psrResponseHasHeaderReturnsFalseForMissingTest(UnitTester $I): void + { + $response = new PsrResponse(); + + $I->assertFalse($response->hasHeader('X-Nonexistent')); + $I->assertEquals([], $response->getHeader('X-Nonexistent')); + } + + #[Group('psr-response')] + public function psrResponseWithBodyTest(UnitTester $I): void + { + $stream = new PsrStream('response body'); + $response = (new PsrResponse())->withBody($stream); + + $I->assertEquals('response body', (string) $response->getBody()); + } + + #[Group('psr-response')] + public function psrResponseImmutableOnWithHeaderTest(UnitTester $I): void + { + $original = new PsrResponse(); + $new = $original->withHeader('X-Test', 'value'); + + $I->assertFalse($original->hasHeader('X-Test')); + $I->assertTrue($new->hasHeader('X-Test')); + } +} diff --git a/tests/Unit/Http/QueryParseCest.php b/tests/Unit/Http/QueryParseCest.php new file mode 100644 index 0000000..0681319 --- /dev/null +++ b/tests/Unit/Http/QueryParseCest.php @@ -0,0 +1,115 @@ +assertArrayHasKey('', $result); + $I->assertEquals('', $result['']); + } + + public function parseSingleKeyValueTest(UnitTester $I): void + { + $result = Query::parse('key=value'); + + $I->assertCount(1, $result); + $I->assertEquals('value', $result['key']); + } + + public function parseMultipleParamsTest(UnitTester $I): void + { + $result = Query::parse('a=1&b=2&c=3'); + + $I->assertCount(3, $result); + $I->assertEquals('1', $result['a']); + $I->assertEquals('2', $result['b']); + $I->assertEquals('3', $result['c']); + } + + public function parseDecodesUrlEncodedValueTest(UnitTester $I): void + { + // %20 = space, %3D = '=' + $result = Query::parse('msg=hello%20world'); + $I->assertEquals('hello world', $result['msg']); + } + + public function parseValueWithEqualsSignUsesSplitLimit2Test(UnitTester $I): void + { + // "a=b=c" → key=a, value=b=c (limit 2 in explode preserves '=' in value) + $result = Query::parse('a=b=c'); + + $I->assertEquals('b=c', $result['a']); + } + + public function parsePercent26InValueDecodesCorrectlyTest(UnitTester $I): void + { + // The split on '&' happens on the RAW string first, so '%26' in a value is NOT + // treated as a separator — it is correctly decoded to '&' within the value. + // This confirms the implementation handles encoded ampersands correctly. + $result = Query::parse('a=hello%26world&b=foo'); + + $I->assertCount(2, $result); + $I->assertEquals('hello&world', $result['a']); + $I->assertEquals('foo', $result['b']); + } + + public function parseMissingValueDefaultsToEmptyStringTest(UnitTester $I): void + { + $result = Query::parse('key='); + $I->assertEquals('', $result['key']); + } + + public function parseMissingEqualsSignResultsInEmptyValueTest(UnitTester $I): void + { + // 'key' without '=' → explode gives ['key'], no index 1 → defaults to '' + $result = Query::parse('key'); + $I->assertEquals('', $result['key']); + } + + protected function typicalPaymentNotificationParams(): array + { + return [ + 'payment notification' => [ + 'params' => [ + 'merchant' => 'ABCD-1234-5678', + 'test' => 'true', + 'price' => '10000', + 'curr' => 'CZK', + 'label' => 'Test product', + 'refId' => 'order-001', + 'payerId' => '', + 'transId' => 'ZZ-99-AB', + 'secret' => 'supersecret', + 'status' => 'PAID', + 'fee' => 'unknown', + 'vs' => '', + ], + ], + ]; + } + + #[DataProvider('typicalPaymentNotificationParams')] + public function parseTypicalPaymentNotificationParamsTest(UnitTester $I, Example $example): void + { + // Use RFC3986 encoding (rawurlencode) so Query::parse (rawurldecode) round-trips correctly + $input = http_build_query($example['params'], '', '&', PHP_QUERY_RFC3986); + $result = Query::parse($input); + + foreach ($example['params'] as $key => $value) { + $I->assertEquals((string) $value, $result[$key]); + } + } +} \ No newline at end of file From 29962562071626203f936986dfa9039ed698c986 Mon Sep 17 00:00:00 2001 From: SimonZanta Date: Mon, 18 May 2026 15:26:00 +0200 Subject: [PATCH 5/5] odebrani terminalovych testu --- tests/Integration/ClientTerminalCest.php | 184 ------------------- tests/Integration/TerminalMethodSyncCest.php | 164 ----------------- 2 files changed, 348 deletions(-) delete mode 100644 tests/Integration/ClientTerminalCest.php delete mode 100644 tests/Integration/TerminalMethodSyncCest.php diff --git a/tests/Integration/ClientTerminalCest.php b/tests/Integration/ClientTerminalCest.php deleted file mode 100644 index 0c701bc..0000000 --- a/tests/Integration/ClientTerminalCest.php +++ /dev/null @@ -1,184 +0,0 @@ -getClient(); - - $response = $client->getTerminalStatus(); - - $I->assertInstanceOf(TerminalStatusResponse::class, $response); - $I->assertNotEmpty($response->getStatus()); - } - - #[Group('terminal')] - #[Group('terminal-payment')] - public function createPaymentTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $payment = $this->createTerminalPayment(); - $response = $client->createPayment($payment); - - $I->assertInstanceOf(TerminalPaymentCreateResponse::class, $response); - $I->assertEquals(RequestCode::OK, $response->getCode()); - $I->assertNotEmpty($response->getTransId()); - } - - #[Group('terminal')] - #[Group('terminal-payment')] - public function getPaymentStatusTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $payment = $this->createTerminalPayment(); - $createResponse = $client->createPayment($payment); - $transId = $createResponse->getTransId(); - - $statusResponse = $client->getPaymentStatus($transId); - - $I->assertInstanceOf(TerminalPaymentStatusResponse::class, $statusResponse); - $I->assertEquals(RequestCode::OK, $statusResponse->getCode()); - $I->assertEquals($transId, $statusResponse->getTransId()); - } - - #[Group('terminal')] - #[Group('terminal-payment')] - public function cancelPaymentTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $payment = $this->createTerminalPayment(); - $createResponse = $client->createPayment($payment); - $transId = $createResponse->getTransId(); - - $cancelResponse = $client->cancelPayment($transId); - - $I->assertInstanceOf(TerminalPaymentCancelResponse::class, $cancelResponse); - $I->assertEquals(RequestCode::OK, $cancelResponse->getCode()); - } - - #[Group('terminal')] - #[Group('terminal-closing')] - public function createClosingTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $response = $client->createClosing(); - - $I->assertInstanceOf(TerminalClosingResponse::class, $response); - $I->assertEquals(RequestCode::OK, $response->getCode()); - $I->assertIsInt($response->getBatchNumber()); - $I->assertIsArray($response->getBatchData()); - } - - #[Group('terminal')] - #[Group('terminal-refund')] - public function createRefundTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $refund = $this->createTerminalRefund(); - $response = $client->createRefund($refund); - - $I->assertInstanceOf(TerminalRefundCreateResponse::class, $response); - $I->assertEquals(RequestCode::OK, $response->getCode()); - $I->assertNotEmpty($response->getTransId()); - } - - #[Group('terminal')] - #[Group('terminal-refund')] - public function getRefundStatusTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $refund = $this->createTerminalRefund(); - $createResponse = $client->createRefund($refund); - $transId = $createResponse->getTransId(); - - $statusResponse = $client->getRefundStatus($transId); - - $I->assertInstanceOf(TerminalRefundStatusResponse::class, $statusResponse); - $I->assertEquals(RequestCode::OK, $statusResponse->getCode()); - $I->assertEquals($transId, $statusResponse->getTransId()); - } - - #[Group('terminal')] - #[Group('terminal-refund')] - public function cancelRefundTest(IntegrationTester $I): void - { - $client = $this->getClient(); - - $refund = $this->createTerminalRefund(); - $createResponse = $client->createRefund($refund); - $transId = $createResponse->getTransId(); - - $cancelResponse = $client->cancelRefund($transId); - - $I->assertInstanceOf(TerminalRefundCancelResponse::class, $cancelResponse); - $I->assertEquals(RequestCode::OK, $cancelResponse->getCode()); - } - - #[Group('terminal')] - #[Group('terminal-payment')] - public function createPaymentFailTest(IntegrationTester $I): void - { - $client = $this->getClient(); - $client->getTransport()->getConfig()->setMerchant('invalid_merchant'); - - $I->expectThrowable(ApiException::class, function () use ($client) { - $client->createPayment($this->createTerminalPayment()); - }); - } - - private function createTerminalPayment(): TerminalPayment - { - return (new TerminalPayment()) - ->setPrice(Money::ofInt(100)) - ->setCurr(CurrencyCode::CZK) - ->setRefId('terminal-test-' . uniqid()); - } - - private function createTerminalRefund(): TerminalRefund - { - return (new TerminalRefund()) - ->setPrice(Money::ofInt(50)) - ->setCurr(CurrencyCode::CZK) - ->setRefId('terminal-refund-' . uniqid()); - } - - private function getClient(): ClientTerminal - { - return Comgate::defaults() - ->setMerchant($_ENV['API_MERCHANT']) - ->setSecret($_ENV['API_SECRET']) - ->setUrl($_ENV['API_URL_REST']) - ->createTerminalClient(); - } -} diff --git a/tests/Integration/TerminalMethodSyncCest.php b/tests/Integration/TerminalMethodSyncCest.php deleted file mode 100644 index c644448..0000000 --- a/tests/Integration/TerminalMethodSyncCest.php +++ /dev/null @@ -1,164 +0,0 @@ -getMethodsProperties(); - $methodProperties = $properties[$example['name']] ?? null; - $I->assertNotEmpty($methodProperties, "Properties for method {$example['name']} not found. Add them to getMethodsProperties() method."); - - $remoteMethods = $this->getRemoteMethods(); - $I->assertNotEmpty($remoteMethods, "Remote methods not found. Check if http://payments.comgate.cz/openapi.yml is available."); - - $currentRemoteMethodParams = []; - foreach ($remoteMethods as $method) { - if ($method['url'] === $methodProperties['url']) { - $currentRemoteMethodParams = $method['params']; - break; - } - } - - $I->assertNotEmpty($currentRemoteMethodParams, "No remote params found for method {$example['name']}. Is it intentional?"); - - $namespace = $methodProperties['namespace'] ?? "Comgate\\SDK\\Entity\\Request\\" . $methodProperties['class']; - - $localMethodParams = $this->getLocalMethodParams($namespace, $methodProperties['args'] ?? null); - $diffRemote = array_diff($currentRemoteMethodParams, $localMethodParams); - $diffLocal = array_diff($currentRemoteMethodParams, $localMethodParams); - $I->assertEmpty($diffRemote, "Local implementation is missing some parameters."); - $I->assertEmpty($diffLocal, "Local implementation has more parameters than remote."); - } - - private function getLocalMethodNames(): array - { - $mirror = new ReflectionClass(ClientTerminal::class); - - $methodNamesRaw = array_map(function (ReflectionMethod $item) { - return ['name' => $item->getName()]; - }, $mirror->getMethods(ReflectionMethod::IS_PUBLIC)); - - return array_filter($methodNamesRaw, function ($item) { - return $item['name'] !== '__construct' && !in_array($item['name'], $this->skippedMethods, true); - }); - } - - private function getRemoteMethods(): array - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'http://payments.comgate.cz/openapi.yml'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - $result = curl_exec($ch); - if (PHP_VERSION_ID < 80500) { - curl_close($ch); - } - - $parsed = yaml_parse($result); - $availableMethods = array_keys($parsed['paths']); - - return array_map(function ($method) use ($parsed) { - $paramsParsed = $parsed['paths'][$method]['post']['requestBody']['content']['application/x-www-form-urlencoded']['schema']['properties'] ?? []; - $params = array_keys($paramsParsed); - $filteredParams = array_filter($params, function ($item) { - return $item !== 'merchant' && $item !== 'secret'; - }); - return [ - 'url' => $method, - 'params' => $filteredParams, - ]; - }, $availableMethods); - } - - private function getLocalMethodParams(string $namespace, ?array $args): array - { - $mirror = new ReflectionClass($namespace); - - $constructor = $mirror->getConstructor(); - if ($constructor && ($constructor->getNumberOfRequiredParameters() > 0)) { - $instance = $mirror->newInstanceArgs($args); - } else { - $instance = $mirror->newInstance(); - } - - $properties = []; - foreach ($mirror->getProperties() as $prop) { - $prop->setAccessible(true); - if ($prop->getName() === 'params') { - $properties = array_merge($properties, array_keys($prop->getValue($instance))); - } else { - $properties[] = $prop->getName(); - } - } - return $properties; - } - - /** - * Mapování metod ClientTerminal na jejich URL a request třídy. - * Po přidání nové metody je třeba ji zaregistrovat zde. - */ - private function getMethodsProperties(): array - { - return [ - 'createPayment' => [ - 'url' => '/v1.0/terminalPayment', - 'class' => 'TerminalPaymentCreateRequest', - 'namespace' => 'Comgate\\SDK\\Entity\\TerminalPayment', - ], - 'getPaymentStatus' => [ - 'url' => '/v1.0/terminalPayment/transId/{transId}', - 'class' => 'TerminalPaymentStatusRequest', - 'args' => ['AAAA-BBBB-CCCC'], - ], - 'cancelPayment' => [ - 'url' => '/v1.0/terminalPayment/transId/{transId}', - 'class' => 'TerminalPaymentCancelRequest', - 'args' => ['AAAA-BBBB-CCCC'], - ], - 'createClosing' => [ - 'url' => '/v1.0/terminalClosing', - 'class' => 'TerminalClosingRequest', - ], - 'createRefund' => [ - 'url' => '/v1.0/terminalRefund', - 'class' => 'TerminalRefundCreateRequest', - 'namespace' => 'Comgate\\SDK\\Entity\\TerminalRefund', - ], - 'getRefundStatus' => [ - 'url' => '/v1.0/terminalRefund/transId/{transId}', - 'class' => 'TerminalRefundStatusRequest', - 'args' => ['AAAA-BBBB-CCCC'], - ], - 'cancelRefund' => [ - 'url' => '/v1.0/terminalRefund/transId/{transId}', - 'class' => 'TerminalRefundCancelRequest', - 'args' => ['AAAA-BBBB-CCCC'], - ], - 'getTerminalStatus' => [ - 'url' => '/v1.0/terminal', - 'class' => 'TerminalStatusRequest', - ], - ]; - } -}