Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions tests/Integration/ClientFunctionalCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php declare(strict_types = 1);

namespace Tests\Integration;

use Codeception\Attribute\DataProvider;
use Codeception\Attribute\Group;
use Codeception\Example;
use Comgate\SDK\Client;
use Comgate\SDK\Entity\Codes\CurrencyCode;
use Comgate\SDK\Entity\Money;
use Comgate\SDK\Entity\Refund;
use Comgate\SDK\Exception\Api\MissingParamException;
use Comgate\SDK\Exception\Api\PaymentNotFoundException;
use Comgate\SDK\Exception\Api\PreauthException;
use Comgate\SDK\Exception\ApiException;
use Tests\Support\FakeTransport;
use Tests\Support\IntegrationTester;

/**
*
* Drives {@see Client} against a {@see FakeTransport}: each test queues the
* API responses it expects and then asserts both the hydrated result and the
* requests the SDK sent.
*/
final class ClientFunctionalCest
{
#[Group('payment')]
public function paymentLifecycleUsesExpectedProtocol(IntegrationTester $I): void
{
$transport = (new FakeTransport())
->willReturnJson(['code' => 0, 'message' => 'OK', 'transId' => 'PAY-123', 'redirect' => 'https://pay.test/PAY-123'])
->willReturnJson([
'code' => 0, 'message' => 'OK', 'merchant' => 'fixture-merchant', 'secret' => 'fixture-secret',
'transId' => 'PAY-123', 'test' => 'true', 'price' => 12345, 'curr' => 'CZK', 'label' => 'Fixture order',
'refId' => 'ORDER-42', 'payerId' => 'PAYER-7', 'method' => 'CARD_CZ_CSOB_2', 'account' => '123/0100',
'email' => 'buyer@example.test', 'name' => 'Ada Buyer', 'phone' => '+420123456789', 'status' => 'PAID',
'payerName' => 'Ada Buyer', 'payerAcc' => '987/0300', 'fee' => '12', 'vs' => '42', 'cardValid' => '12/30',
'cardNumber' => '************1111', 'appliedFee' => '3', 'appliedFeeTyp' => 'fixed', 'paymentErrorReason' => '',
])
->willReturnJson(['code' => 0, 'message' => 'OK']);
$client = new Client($transport);

$created = $client->createPayment($I->createPayment());
$I->assertSame('PAY-123', $created->getTransId());
$I->assertSame('https://pay.test/PAY-123', $created->getRedirect());
$I->assertSame('PAY-123', $created->toArray()['transId']);

$status = $client->getStatus($created->getTransId());
$I->assertTrue($status->isTest());
$I->assertSame(12345, $status->getPrice()->get());
$I->assertSame('CARD_CZ_CSOB_2', $status->getMethod());
$I->assertSame('************1111', $status->getCardNumber());
$I->assertSame('fixed', $status->toArray()['appliedFeeTyp']);

$cancelled = $client->cancelPayment($created->getTransId());
$I->assertSame(['code' => 0, 'message' => 'OK'], $cancelled->toArray());

$requests = $transport->requests();
$I->assertSame('POST', $requests[0]['method']);
$I->assertSame('payment.json', $requests[0]['urn']);
$I->assertSame('SDK test payment', $requests[0]['data']['label']);
$I->assertSame('GET', $requests[1]['method']);
$I->assertSame('payment/transId/PAY-123.json', $requests[1]['urn']);
$I->assertSame('DELETE', $requests[2]['method']);
}

#[Group('payment')]
public function paymentOperationsSerializeAndHydrate(IntegrationTester $I): void
{
$transport = (new FakeTransport())
->willReturnJson(['code' => 0, 'message' => 'OK'])
->willReturnJson(['code' => 0, 'message' => 'OK'])
->willReturnJson(['code' => 0, 'message' => 'OK'])
->willReturnJson(['code' => 0, 'message' => 'OK', 'transId' => 'REC-123'])
->willReturnJson(['code' => 0, 'message' => 'OK']);
$client = new Client($transport);

$I->assertSame(0, $client->capturePreauth('PAY/A B', Money::ofCents(500))->getCode());
$I->assertSame(0, $client->cancelPreauth('PAY/A B')->getCode());

$refund = (new Refund())
->setTransId('PAY-123')
->setAmount(Money::ofCents(250))
->setCurrency(CurrencyCode::CZK)
->setTest(true)
->setRefId('REF-1');
$I->assertSame(0, $client->refundPayment($refund)->getCode());

$recurring = $I->createPayment();
$recurring->setInitRecurringId('PAY-123');
$I->assertSame('REC-123', $client->initRecurringPayment($recurring)->getTransId());
$I->assertSame(0, $client->simulation(['subject' => 'payment', 'transId' => 'PAY-123', 'task' => 'PAID'])->getCode());

$requests = $transport->requests();
$I->assertSame('PUT', $requests[0]['method']);
$I->assertSame('preauth/transId/PAY%2FA+B.json', $requests[0]['urn']);
$I->assertSame(500, $requests[0]['data']['amount']);
$I->assertSame('DELETE', $requests[1]['method']);
$I->assertSame('refund.json', $requests[2]['urn']);
$I->assertSame(250, $requests[2]['data']['amount']);
$I->assertSame('recurring.json', $requests[3]['urn']);
$I->assertSame('simulation.json', $requests[4]['urn']);
}

#[Group('payment')]
#[DataProvider('apiErrors')]
public function mapsApiErrors(IntegrationTester $I, Example $example): void
{
$client = new Client((new FakeTransport())->willReturnJson($example['response']));

$I->expectThrowable($example['exception'], function () use ($I, $client, $example): void {
if ($example['operation'] === 'status') {
$client->getStatus('missing');
return;
}
if ($example['operation'] === 'capture') {
$client->capturePreauth('PAY-1', Money::ofCents(100));
return;
}
$client->createPayment($I->createPayment());
});
}

protected function apiErrors(): array
{
return [
'general API error' => ['response' => ['code' => 1500, 'message' => 'Fixture API error'], 'operation' => 'status', 'exception' => ApiException::class],
'payment not found' => ['response' => ['code' => 1400, 'message' => 'Fixture missing parameter'], 'operation' => 'status', 'exception' => PaymentNotFoundException::class],
'preauth rejected' => ['response' => ['code' => 1401, 'message' => 'Fixture preauth error'], 'operation' => 'capture', 'exception' => PreauthException::class],
'missing create parameter' => ['response' => ['code' => 1400, 'message' => 'Fixture missing parameter'], 'operation' => 'create', 'exception' => MissingParamException::class],
];
}
}
113 changes: 113 additions & 0 deletions tests/Integration/MotoPaymentFunctionalCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php declare(strict_types = 1);

namespace Tests\Integration;

use Codeception\Attribute\DataProvider;
use Codeception\Attribute\Group;
use Codeception\Example;
use Comgate\SDK\Client;
use Comgate\SDK\Entity\PaymentCard;
use Exception;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\RSA\PrivateKey;
use Tests\Support\FakeTransport;
use Tests\Support\IntegrationTester;

/**
* Covers MOTO card encryption without a live crypto endpoint: the test owns the
* RSA key pair, hands the SDK the matching public JWK via {@see FakeTransport},
* and decrypts what the SDK sent to prove the card data was encrypted correctly.
*/
final class MotoPaymentFunctionalCest
{
/** @var PrivateKey */
private $privateKey;

#[Group('moto')]
public function encryptsCardDataBeforeSending(IntegrationTester $I): void
{
$transport = (new FakeTransport())
->willReturnJson($this->publicKeyResponse())
->willReturnJson(['code' => 0, 'message' => 'OK', 'transId' => 'MOTO-1', 'status' => 'PAID']);

$card = new PaymentCard('4111111111111111', '203012', '123');
$response = (new Client($transport))->createMotoPayment($I->createPayment(), $card);
$I->assertSame('MOTO-1', $response->getTransId());
$I->assertSame('PAID', $response->toArray()['status']);

$requests = $transport->requests();
$body = $requests[1]['data'];
$I->assertSame('4111111111111111', $this->decrypt($body['encryptedCardNumber']));
$I->assertSame('203012', $this->decrypt($body['encryptedCardExpiration']));
$I->assertSame('123', $this->decrypt($body['encryptedCardCvv']));
$I->assertSame('pubCryptoKey.json', $requests[0]['urn']);
$I->assertSame('moto.json', $requests[1]['urn']);
}

#[Group('moto')]
public function rejectsMissingPublicKey(IntegrationTester $I): void
{
$transport = (new FakeTransport())->willReturnJson([
'code' => 0, 'message' => 'OK', 'key' => base64_encode((string) json_encode(['notJwk' => []])),
]);

$I->expectThrowable(new Exception('No public encryption key for encrypting card data'), function () use ($I, $transport): void {
(new Client($transport))->createMotoPayment($I->createPayment(), new PaymentCard('4111111111111111', '203012', '123'));
});
}

#[Group('moto')]
#[DataProvider('missingRequiredCardFields')]
public function rejectsMissingRequiredCardFields(IntegrationTester $I, Example $example): void
{
$transport = (new FakeTransport())->willReturnJson($this->publicKeyResponse());

$I->expectThrowable(new Exception($example['message']), function () use ($I, $example, $transport): void {
$card = new PaymentCard($example['number'], $example['expiration'], '123');
(new Client($transport))->createMotoPayment($I->createPayment(), $card);
});
}

protected function missingRequiredCardFields(): array
{
return [
'card number' => ['number' => null, 'expiration' => '203012', 'message' => 'No card number for encrypting card data'],
'expiration' => ['number' => '4111111111111111', 'expiration' => null, 'message' => 'No card expiration for encrypting card data'],
];
}

#[Group('moto')]
public function permitsMissingCvv(IntegrationTester $I): void
{
$transport = (new FakeTransport())
->willReturnJson($this->publicKeyResponse())
->willReturnJson(['code' => 0, 'message' => 'OK', 'transId' => 'MOTO-1', 'status' => 'PAID']);

$response = (new Client($transport))->createMotoPayment($I->createPayment(), new PaymentCard('4111111111111111', '203012'));
$I->assertSame('MOTO-1', $response->getTransId());
$I->assertArrayNotHasKey('encryptedCardCvv', $transport->requests()[1]['data']);
}

/**
* Builds a fresh RSA key pair, keeps the private half for decryption and
* returns the pubCryptoKey.json body carrying the public key as a JWK.
*
* @return array<string, int|string>
*/
private function publicKeyResponse(): array
{
$this->privateKey = RSA::createKey(1024);
$publicJwk = $this->privateKey->getPublicKey()->toString('JWK');

return [
'code' => 0,
'message' => 'OK',
'key' => base64_encode((string) json_encode(['jwk' => json_decode($publicJwk, true)])),
];
}

private function decrypt(string $ciphertext): string
{
return $this->privateKey->decrypt(base64_decode($ciphertext, true));
}
}
72 changes: 72 additions & 0 deletions tests/Integration/TerminalClientFunctionalCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php declare(strict_types = 1);

namespace Tests\Integration;

use Codeception\Attribute\Group;
use Comgate\SDK\ClientTerminal;
use Comgate\SDK\Entity\TerminalPayment;
use Comgate\SDK\Entity\TerminalRefund;
use Tests\Support\FakeTransport;
use Tests\Support\IntegrationTester;

/**
* Drives {@see ClientTerminal} against a {@see FakeTransport} - one queued
* response per terminal call.
*/
final class TerminalClientFunctionalCest
{
#[Group('terminal')]
public function paymentLifecycle(IntegrationTester $I): void
{
$transport = (new FakeTransport())
->willReturnJson(['code' => 0, 'message' => 'OK', 'transId' => 'TERM-PAY-1'])
->willReturnJson([
'code' => 0, 'message' => 'OK', 'price' => 1250, 'curr' => 'CZK', 'refId' => 'TP-1', 'transId' => 'TERM-PAY-1',
'status' => 'PAID', 'fee' => '10', 'cardValid' => '12/30', 'cardNumber' => '****1111',
'paymentErrorReason' => '', 'reversed' => false, 'amountRefunded' => '0',
])
->willReturnJson(['code' => 0, 'message' => 'OK']);
$client = new ClientTerminal($transport);

$created = $client->createPayment((new TerminalPayment())->setPrice(12.50)->setCurr('CZK')->setRefId('TP-1'));
$I->assertSame('TERM-PAY-1', $created->toArray()['transId']);

$status = $client->getPaymentStatus('TERM-PAY-1');
$I->assertSame('PAID', $status->getStatus());
$I->assertSame(1250, $status->toArray()['price']);
$I->assertFalse($status->isReversed());
$I->assertSame(0, $client->cancelPayment('TERM-PAY-1')->getCode());

$requests = $transport->requests();
$I->assertSame(['price' => 1250, 'curr' => 'CZK', 'refId' => 'TP-1'], $requests[0]['data']);
$I->assertSame('terminalPayment/transId/TERM-PAY-1.json', $requests[1]['urn']);
$I->assertSame('DELETE', $requests[2]['method']);
}

#[Group('terminal')]
public function refundClosingAndStatusLifecycle(IntegrationTester $I): void
{
$transport = (new FakeTransport())
->willReturnJson(['code' => 0, 'message' => 'OK', 'transId' => 'TERM-REF-1'])
->willReturnJson([
'code' => 0, 'message' => 'OK', 'price' => 500, 'curr' => 'CZK', 'refId' => 'TR-1', 'transId' => 'TERM-REF-1',
'status' => 'REFUNDED', 'cardNumber' => '****1111', 'reversed' => true,
])
->willReturnJson(['code' => 0, 'message' => 'OK'])
->willReturnJson(['code' => 0, 'message' => 'OK', 'batchNumber' => 12, 'batchData' => [['count' => 2, 'amount' => 1750]]])
->willReturnJson(['status' => 'ONLINE']);
$client = new ClientTerminal($transport);

$I->assertSame('TERM-REF-1', $client->createRefund((new TerminalRefund())->setPrice(5)->setCurr('CZK')->setRefId('TR-1'))->getTransId());

$status = $client->getRefundStatus('TERM-REF-1');
$I->assertSame('REFUNDED', $status->getStatus());
$I->assertTrue($status->isReversed());
$I->assertSame(0, $client->cancelRefund('TERM-REF-1')->getCode());

$closing = $client->createClosing();
$I->assertSame(12, $closing->getBatchNumber());
$I->assertSame(2, $closing->toArray()['batchData'][0]['count']);
$I->assertSame('ONLINE', $client->getTerminalStatus()->toArray()['status']);
}
}
Loading
Loading