From 8a14553493795152a6e8df6fd5ab24a577508e9e Mon Sep 17 00:00:00 2001 From: Nuryagdy Mustapayev Date: Wed, 24 Apr 2024 16:42:50 +0200 Subject: [PATCH] Kuveyt Pos added support for non secure payment --- README.md | 2 +- config/pos_production.php | 2 +- config/pos_test.php | 2 +- examples/kuveytpos/regular/_config.php | 6 +- .../KuveytPosRequestDataMapper.php | 27 ++- .../KuveytPosResponseDataMapper.php | 11 +- src/Gateways/KuveytPos.php | 72 +++++-- tests/Functional/KuveytPosTest.php | 116 +++++++++++ .../KuveytPosRequestDataMapperTest.php | 181 ++++++++++++++--- .../KuveytPosResponseDataMapperTest.php | 188 +++++++++++++++++- tests/Unit/Gateways/KuveytPosTest.php | 104 +++++++++- 11 files changed, 652 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index aadf6c39..9416edfc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ sistemlerinin kullanılabilmesidir. | PosNetV1
(JSON API) | Albaraka Türk | NonSecure
3DSecure | İptal
İade
Durum sorgulama | | PayFor | Finansbank
Enpara | NonSecure
3DSecure
3DPay
3DHost | İptal
İade
Durum sorgulama
Sipariş Tarihçesini sorgulama
Geçmiş İşlemleri sorgulama | | InterPOS | Deniz bank | NonSecure
3DSecure
3DPay
3DHost | İptal
İade
Durum sorgulama | -| Kuveyt POS TDV2.0.0 | Kuveyt Türk | 3DSecure | İptal
İade
Durum sorgulama
(SOAP API) | +| Kuveyt POS TDV2.0.0 | Kuveyt Türk | NonSecure
3DSecure | İptal
İade
Durum sorgulama
(SOAP API) | | VakifKatilimPos
(test edilmesi gerekiyor) | Vakıf Katılım | NonSecure
3DSecure
3DHost | İptal
İade
Durum sorgulama
Sipariş Tarihçesini sorgulama
Geçmiş İşlemleri sorgulama | ### Ana başlıklar diff --git a/config/pos_production.php b/config/pos_production.php index a46ebf52..76ff7c0d 100644 --- a/config/pos_production.php +++ b/config/pos_production.php @@ -161,7 +161,7 @@ 'name' => 'kuveyt-pos', 'class' => Mews\Pos\Gateways\KuveytPos::class, 'gateway_endpoints' => [ - 'payment_api' => 'https://sanalpos.kuveytturk.com.tr/ServiceGateWay/Home/ThreeDModelProvisionGate', + 'payment_api' => 'https://sanalpos.kuveytturk.com.tr/ServiceGateWay/Home', 'gateway_3d' => 'https://sanalpos.kuveytturk.com.tr/ServiceGateWay/Home/ThreeDModelPayGate', 'query_api' => 'https://boa.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', ], diff --git a/config/pos_test.php b/config/pos_test.php index f0e7b0fa..285837fc 100644 --- a/config/pos_test.php +++ b/config/pos_test.php @@ -109,7 +109,7 @@ 'name' => 'kuveyt-pos', 'class' => Mews\Pos\Gateways\KuveytPos::class, 'gateway_endpoints' => [ - 'payment_api' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelProvisionGate', + 'payment_api' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home', 'gateway_3d' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelPayGate', 'query_api' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', ], diff --git a/examples/kuveytpos/regular/_config.php b/examples/kuveytpos/regular/_config.php index e5a17540..62ed2287 100644 --- a/examples/kuveytpos/regular/_config.php +++ b/examples/kuveytpos/regular/_config.php @@ -9,13 +9,13 @@ $account = \Mews\Pos\Factory\AccountFactory::createKuveytPosAccount( 'kuveytpos', '496', - 'apiuser1', + 'apitest', '400235', - 'Api1232', + 'api123', PosInterface::MODEL_3D_SECURE ); $pos = getGateway($account, $eventDispatcher); $templateTitle = 'Regular Payment'; -$paymentModel = PosInterface::MODEL_3D_SECURE; +$paymentModel = PosInterface::MODEL_NON_SECURE; diff --git a/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php b/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php index a0709a1c..a03f23a0 100644 --- a/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php +++ b/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php @@ -94,7 +94,7 @@ public function create3DPaymentRequestData(AbstractPosAccount $posAccount, array 'TransactionType' => $this->mapTxType($txType), 'InstallmentCount' => $responseData['VPosMessage']['InstallmentCount'], 'Amount' => $responseData['VPosMessage']['Amount'], - 'DisplayAmount' => $this->formatAmount($responseData['VPosMessage']['Amount']), + 'DisplayAmount' => $responseData['VPosMessage']['Amount'], 'CurrencyCode' => $responseData['VPosMessage']['CurrencyCode'], 'MerchantOrderId' => $responseData['VPosMessage']['MerchantOrderId'], 'TransactionSecurity' => $responseData['VPosMessage']['TransactionSecurity'], @@ -161,11 +161,34 @@ public function createNonSecurePostAuthPaymentRequestData(AbstractPosAccount $po } /** + * @param KuveytPosAccount $posAccount + * * {@inheritDoc} */ public function createNonSecurePaymentRequestData(AbstractPosAccount $posAccount, array $order, string $txType, CreditCardInterface $creditCard): array { - throw new NotImplementedException(); + $order = $this->preparePaymentOrder($order); + + $requestData = $this->getRequestAccountData($posAccount) + [ + 'APIVersion' => self::API_VERSION, + 'HashData' => '', + 'TransactionType' => $this->mapTxType($txType), + 'TransactionSecurity' => '1', + 'MerchantOrderId' => (string) $order['id'], + 'Amount' => $this->formatAmount($order['amount']), + 'DisplayAmount' => $this->formatAmount($order['amount']), + 'CurrencyCode' => $this->mapCurrency($order['currency']), + 'InstallmentCount' => $this->mapInstallment($order['installment']), + 'CardHolderName' => $creditCard->getHolderName(), + 'CardNumber' => $creditCard->getNumber(), + 'CardExpireDateYear' => $creditCard->getExpireYear(self::CREDIT_CARD_EXP_YEAR_FORMAT), + 'CardExpireDateMonth' => $creditCard->getExpireMonth(self::CREDIT_CARD_EXP_MONTH_FORMAT), + 'CardCVV2' => $creditCard->getCvv(), + ]; + + $requestData['HashData'] = $this->crypt->createHash($posAccount, $requestData); + + return $requestData; } /** diff --git a/src/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapper.php b/src/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapper.php index d71877d5..82f983af 100644 --- a/src/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapper.php +++ b/src/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapper.php @@ -176,11 +176,18 @@ public function mapStatusResponse(array $rawResponseData): array $defaultResponse['transaction_id'] = $orderContract['Stan']; $defaultResponse['currency'] = $this->mapCurrency($orderContract['FEC']); $defaultResponse['first_amount'] = (float) $orderContract['FirstAmount']; - $defaultResponse['capture_amount'] = null !== $orderContract['FirstAmount'] ? (float) $orderContract['FirstAmount'] : null; - $defaultResponse['capture'] = $defaultResponse['first_amount'] > 0 && $defaultResponse['first_amount'] === $defaultResponse['capture_amount']; $defaultResponse['masked_number'] = $orderContract['CardNumber']; $defaultResponse['transaction_time'] = new \DateTimeImmutable($orderContract['OrderDate']); $defaultResponse['installment_count'] = $this->mapInstallment($orderContract['InstallmentCount']); + if (PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED === $defaultResponse['order_status']) { + $defaultResponse['capture_amount'] = null !== $orderContract['FirstAmount'] ? (float) $orderContract['FirstAmount'] : null; + $defaultResponse['capture'] = $defaultResponse['first_amount'] > 0 && $defaultResponse['first_amount'] === $defaultResponse['capture_amount']; + if ($defaultResponse['capture']) { + $defaultResponse['capture_time'] = new \DateTimeImmutable($orderContract['UpdateSystemDate']); + } + } elseif (PosInterface::PAYMENT_STATUS_CANCELED === $defaultResponse['order_status']) { + $defaultResponse['cancel_time'] = new \DateTimeImmutable($orderContract['UpdateSystemDate']); + } } return $defaultResponse; diff --git a/src/Gateways/KuveytPos.php b/src/Gateways/KuveytPos.php index 95a71162..08a93f59 100644 --- a/src/Gateways/KuveytPos.php +++ b/src/Gateways/KuveytPos.php @@ -45,6 +45,7 @@ class KuveytPos extends AbstractGateway /** @inheritdoc */ protected static array $supportedTransactions = [ PosInterface::TX_TYPE_PAY_AUTH => [ + PosInterface::MODEL_NON_SECURE, PosInterface::MODEL_3D_SECURE, ], PosInterface::TX_TYPE_PAY_PRE_AUTH => false, @@ -62,6 +63,29 @@ public function getAccount(): AbstractPosAccount return $this->account; } + /** + * @inheritDoc + */ + public function getApiURL(string $txType = null, string $paymentModel = null, ?string $orderTxType = null): string + { + if (\in_array( + $txType, + [ + PosInterface::TX_TYPE_REFUND, + PosInterface::TX_TYPE_STATUS, + PosInterface::TX_TYPE_CANCEL, + ], + true + )) { + return $this->getQueryAPIUrl(); + } + if (null !== $txType && null !== $paymentModel) { + return parent::getApiURL().'/'.$this->getRequestURIByTransactionType($txType, $paymentModel); + } + + return parent::getApiURL(); + } + /** * @inheritDoc */ @@ -107,14 +131,6 @@ public function get3DFormData(array $order, string $paymentModel, string $txType return $this->getCommon3DFormData($this->account, $order, $paymentModel, $txType, $gatewayUrl, $creditCard); } - /** - * @inheritDoc - */ - public function makeRegularPayment(array $order, CreditCardInterface $creditCard, string $txType): PosInterface - { - throw new UnsupportedPaymentModelException(); - } - /** * @inheritDoc */ @@ -163,7 +179,7 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr $contents, $txType, PosInterface::MODEL_3D_SECURE, - $this->getApiURL() + $this->getApiURL($txType, PosInterface::MODEL_3D_SECURE) ); $this->response = $this->responseDataMapper->map3DPaymentData($gatewayResponse, $bankResponse, $txType, $order); @@ -185,7 +201,7 @@ protected function send($contents, string $txType, string $paymentModel, string throw new InvalidArgumentException(\sprintf('Invalid data type provided for %s transaction!', $txType)); } - return $this->data = $this->sendSoapRequest($contents, $txType); + return $this->data = $this->sendSoapRequest($contents, $txType, $url); } $this->logger->debug('sending request', ['url' => $url]); @@ -204,17 +220,17 @@ protected function send($contents, string $txType, string $paymentModel, string /** * @phpstan-param PosInterface::TX_TYPE_STATUS|PosInterface::TX_TYPE_REFUND|PosInterface::TX_TYPE_CANCEL $txType * - * @param array $contents - * @param string $txType + * @param array $contents + * @param string $txType + * @param string $url * * @return array * * @throws SoapFault * @throws Throwable */ - protected function sendSoapRequest(array $contents, string $txType): array + private function sendSoapRequest(array $contents, string $txType, string $url): array { - $url = $this->getQueryAPIUrl(); $this->logger->debug('sending soap request', [ 'txType' => $txType, 'url' => $url, @@ -308,4 +324,32 @@ private function getCommon3DFormData(KuveytPosAccount $kuveytPosAccount, array $ return $this->requestDataMapper->create3DFormData($this->account, $decodedResponse['form_inputs'], $paymentModel, $txType, $decodedResponse['gateway'], $creditCard); } + + /** + * @phpstan-param PosInterface::TX_TYPE_* $txType + * @phpstan-param PosInterface::MODEL_* $paymentModel + * + * @return string + * + * @throws UnsupportedTransactionTypeException + */ + private function getRequestURIByTransactionType(string $txType, string $paymentModel): string + { + $arr = [ + PosInterface::TX_TYPE_PAY_AUTH => [ + PosInterface::MODEL_NON_SECURE => 'Non3DPayGate', + PosInterface::MODEL_3D_SECURE => 'ThreeDModelProvisionGate', + ], + ]; + + if (!isset($arr[$txType])) { + throw new UnsupportedTransactionTypeException(); + } + + if (!isset($arr[$txType][$paymentModel])) { + throw new UnsupportedTransactionTypeException(); + } + + return $arr[$txType][$paymentModel]; + } } diff --git a/tests/Functional/KuveytPosTest.php b/tests/Functional/KuveytPosTest.php index 5d66b21d..215b70cf 100644 --- a/tests/Functional/KuveytPosTest.php +++ b/tests/Functional/KuveytPosTest.php @@ -138,4 +138,120 @@ function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThro $this->assertNotEmpty($formData); $this->assertTrue($eventIsThrown); } + + public function testNonSecurePaymentSuccess(): array + { + $order = $this->createPaymentOrder(); + + $this->eventDispatcher->addListener( + RequestDataPreparedEvent::class, + function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void { + $eventIsThrown = true; + $this->assertSame(PosInterface::TX_TYPE_PAY_AUTH, $requestDataPreparedEvent->getTxType()); + $this->assertCount(17, $requestDataPreparedEvent->getRequestData()); + }); + + $this->pos->payment( + PosInterface::MODEL_NON_SECURE, + $order, + PosInterface::TX_TYPE_PAY_AUTH, + $this->card + ); + + $response = $this->pos->getResponse(); + + $this->assertTrue($this->pos->isSuccess()); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertTrue($eventIsThrown); + + return $this->pos->getResponse(); + } + + /** + * @depends testRefundFail + */ + public function testCancelSuccess(array $lastResponse): array + { + $statusOrder = $this->createCancelOrder(\get_class($this->pos), $lastResponse); + + $eventIsThrown = false; + $this->eventDispatcher->addListener( + RequestDataPreparedEvent::class, + function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void { + $eventIsThrown = true; + $this->assertSame(PosInterface::TX_TYPE_CANCEL, $requestDataPreparedEvent->getTxType()); + $this->assertCount(15, $requestDataPreparedEvent->getRequestData()); + }); + + $this->pos->cancel($statusOrder); + + $this->assertTrue($this->pos->isSuccess()); + $response = $this->pos->getResponse(); + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertTrue($eventIsThrown); + + return $lastResponse; + } + + /** + * @depends testNonSecurePaymentSuccess + */ + public function testStatusSuccess(array $lastResponse): array + { + $statusOrder = $this->createStatusOrder(\get_class($this->pos), $lastResponse); + + $eventIsThrown = false; + $this->eventDispatcher->addListener( + RequestDataPreparedEvent::class, + function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void { + $eventIsThrown = true; + $this->assertSame(PosInterface::TX_TYPE_STATUS, $requestDataPreparedEvent->getTxType()); + $this->assertCount(15, $requestDataPreparedEvent->getRequestData()); + }); + + $this->pos->status($statusOrder); + + $this->assertTrue($this->pos->isSuccess()); + $response = $this->pos->getResponse(); + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertTrue($eventIsThrown); + + return $lastResponse; + } + + /** + * @depends testNonSecurePaymentSuccess + */ + public function testRefundFail(array $lastResponse): array + { + $refundOrder = $this->createRefundOrder(\get_class($this->pos), $lastResponse); + $refundOrder['amount'] = 1.0; + + $eventIsThrown = false; + $this->eventDispatcher->addListener( + RequestDataPreparedEvent::class, + function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void { + $eventIsThrown = true; + $this->assertSame(PosInterface::TX_TYPE_REFUND, $requestDataPreparedEvent->getTxType()); + $this->assertCount(15, $requestDataPreparedEvent->getRequestData()); + }); + + $this->pos->refund($refundOrder); + + $this->assertFalse($this->pos->isSuccess()); + $response = $this->pos->getResponse(); + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertTrue($eventIsThrown); + $this->assertSame( + 'İade işlemi, satışla aynı gün içerisinde yapılamaz. İptal işlemi yapabilirsiniz.', + $response['error_message'] + ); + + return $lastResponse; + } } diff --git a/tests/Unit/DataMapper/RequestDataMapper/KuveytPosRequestDataMapperTest.php b/tests/Unit/DataMapper/RequestDataMapper/KuveytPosRequestDataMapperTest.php index 92e6a402..62f23375 100644 --- a/tests/Unit/DataMapper/RequestDataMapper/KuveytPosRequestDataMapperTest.php +++ b/tests/Unit/DataMapper/RequestDataMapper/KuveytPosRequestDataMapperTest.php @@ -6,18 +6,17 @@ namespace Mews\Pos\Tests\Unit\DataMapper\RequestDataMapper; use Generator; +use Mews\Pos\Crypt\CryptInterface; use Mews\Pos\DataMapper\RequestDataMapper\KuveytPosRequestDataMapper; use Mews\Pos\Entity\Account\KuveytPosAccount; use Mews\Pos\Entity\Card\CreditCardInterface; use Mews\Pos\Exceptions\UnsupportedTransactionTypeException; use Mews\Pos\Factory\AccountFactory; use Mews\Pos\Factory\CreditCardFactory; -use Mews\Pos\Factory\CryptFactory; -use Mews\Pos\Gateways\KuveytPos; use Mews\Pos\PosInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; -use Psr\Log\NullLogger; /** * @covers \Mews\Pos\DataMapper\RequestDataMapper\KuveytPosRequestDataMapper @@ -31,6 +30,9 @@ class KuveytPosRequestDataMapperTest extends TestCase private KuveytPosRequestDataMapper $requestDataMapper; + /** @var CryptInterface|MockObject */ + private CryptInterface $crypt; + protected function setUp(): void { parent::setUp(); @@ -54,8 +56,8 @@ protected function setUp(): void CreditCardInterface::CARD_TYPE_VISA ); - $crypt = CryptFactory::createGatewayCrypt(KuveytPos::class, new NullLogger()); - $this->requestDataMapper = new KuveytPosRequestDataMapper($dispatcher, $crypt); + $this->crypt = $this->createMock(CryptInterface::class); + $this->requestDataMapper = new KuveytPosRequestDataMapper($dispatcher, $this->crypt); } /** @@ -129,6 +131,10 @@ public function testCreate3DEnrollmentCheckRequestData(array $order, string $txT $account = $this->account; $card = $this->card; + $this->crypt->expects(self::once()) + ->method('create3DHash') + ->willReturn('request-3d-hash'); + $actualData = $this->requestDataMapper->create3DEnrollmentCheckRequestData( $account, $order, @@ -136,7 +142,11 @@ public function testCreate3DEnrollmentCheckRequestData(array $order, string $txT $txType, $card ); - $this->assertEquals($expectedData, $actualData); + + ksort($expectedData); + ksort($actualData); + + $this->assertSame($expectedData, $actualData); } /** @@ -144,7 +154,26 @@ public function testCreate3DEnrollmentCheckRequestData(array $order, string $txT */ public function testCreateCancelRequestData(array $order, array $expected): void { + $this->crypt->expects(self::once()) + ->method('createHash') + ->willReturn('request-hash'); + $actual = $this->requestDataMapper->createCancelRequestData($this->account, $order); + + foreach ($actual as &$item) { + if (is_array($item)) { + ksort($item); + } + } + foreach ($expected as &$item) { + if (is_array($item)) { + ksort($item); + } + } + + \ksort($actual); + \ksort($expected); + $this->assertEquals($expected, $actual); } @@ -153,8 +182,27 @@ public function testCreateCancelRequestData(array $order, array $expected): void */ public function testCreateRefundRequestData(array $order, array $expected): void { + $this->crypt->expects(self::once()) + ->method('createHash') + ->willReturn('request-hash'); + $actual = $this->requestDataMapper->createRefundRequestData($this->account, $order); - $this->assertEquals($expected, $actual); + + foreach ($actual as &$item) { + if (is_array($item)) { + ksort($item); + } + } + foreach ($expected as &$item) { + if (is_array($item)) { + ksort($item); + } + } + + \ksort($actual); + \ksort($expected); + + $this->assertSame($expected, $actual); } /** @@ -162,18 +210,71 @@ public function testCreateRefundRequestData(array $order, array $expected): void */ public function testCreateStatusRequestData(array $order, array $expected): void { + $this->crypt->expects(self::once()) + ->method('createHash') + ->willReturn('request-hash'); + $actual = $this->requestDataMapper->createStatusRequestData($this->account, $order); - $this->assertEquals($expected, $actual); + + foreach ($actual as &$item) { + if (is_array($item)) { + ksort($item); + } + } + foreach ($expected as &$item) { + if (is_array($item)) { + ksort($item); + } + } + + \ksort($actual); + \ksort($expected); + + $this->assertSame($expected, $actual); } /** * @dataProvider create3DPaymentRequestDataDataProvider */ - public function testCreate3DPaymentRequestData(KuveytPosAccount $kuveytPosAccount, array $order, string $txType, array $responseData, array $expectedData): void + public function testCreate3DPaymentRequestData(array $order, string $txType, array $responseData, array $expectedData): void { - $actual = $this->requestDataMapper->create3DPaymentRequestData($kuveytPosAccount, $order, $txType, $responseData); + $this->crypt->expects(self::once()) + ->method('createHash') + ->willReturn('request-hash'); + + $actual = $this->requestDataMapper->create3DPaymentRequestData( + $this->account, + $order, + $txType, + $responseData + ); + + \ksort($actual); + \ksort($expectedData); + + $this->assertSame($expectedData, $actual); + } + + /** + * @dataProvider nonSecurePaymentRequestDataProvider + */ + public function testCreateNonSecurePaymentRequestData(array $order, string $txType, array $expectedData): void + { + $this->crypt->expects(self::once()) + ->method('createHash') + ->willReturn('request-hash'); + + $actualData = $this->requestDataMapper->createNonSecurePaymentRequestData( + $this->account, + $order, + $txType, + $this->card + ); - $this->assertEquals($expectedData, $actual); + ksort($expectedData); + ksort($actualData); + + $this->assertSame($expectedData, $actualData); } public function testGet3DFormData(): void @@ -245,7 +346,7 @@ public static function createCancelRequestDataProvider(): iterable 'VPosMessage' => [ 'APIVersion' => KuveytPosRequestDataMapper::API_VERSION, 'InstallmentMaturityCommisionFlag' => 0, - 'HashData' => 'Om26dd7XpVGq0KyTJBM3TUH4fSU=', + 'HashData' => 'request-hash', 'MerchantId' => '80', 'SubMerchantId' => 0, 'CustomerId' => '400235', @@ -300,7 +401,7 @@ public static function createRefundRequestDataProvider(): Generator 'VPosMessage' => [ 'APIVersion' => KuveytPosRequestDataMapper::API_VERSION, 'InstallmentMaturityCommisionFlag' => 0, - 'HashData' => 'Om26dd7XpVGq0KyTJBM3TUH4fSU=', + 'HashData' => 'request-hash', 'MerchantId' => '80', 'SubMerchantId' => 0, 'CustomerId' => '400235', @@ -351,7 +452,7 @@ public static function createStatusRequestDataProvider(): iterable 'VPosMessage' => [ 'APIVersion' => KuveytPosRequestDataMapper::API_VERSION, 'InstallmentMaturityCommisionFlag' => 0, - 'HashData' => 'RwQ5Sfc6D4Ovy7jvQgf5jGA/rOk=', + 'HashData' => 'request-hash', 'MerchantId' => '80', 'SubMerchantId' => 0, 'CustomerId' => '400235', @@ -381,28 +482,17 @@ public static function createStatusRequestDataProvider(): iterable public static function create3DPaymentRequestDataDataProvider(): array { - $account = AccountFactory::createKuveytPosAccount( - 'kuveytpos', - '80', - 'apiuser', - '400235', - 'Api123' - ); - $order = [ 'id' => '2020110828BC', 'amount' => 1, 'installment' => '0', 'currency' => PosInterface::CURRENCY_TRY, - 'success_url' => 'http://localhost/finansbank-payfor/3d/response.php', - 'fail_url' => 'http://localhost/finansbank-payfor/3d/response.php', 'ip' => '127.0.0.1', 'lang' => PosInterface::LANG_TR, ]; return [ [ - 'account' => $account, 'order' => $order, 'txType' => PosInterface::TX_TYPE_PAY_AUTH, 'responseData' => [ @@ -434,7 +524,7 @@ public static function create3DPaymentRequestDataDataProvider(): array ], 'expected' => [ 'APIVersion' => KuveytPosRequestDataMapper::API_VERSION, - 'HashData' => '9nMtjMKzb7y/hOC4RiDZXkR8uqE=', + 'HashData' => 'request-hash', 'MerchantId' => '80', 'CustomerId' => '400235', 'UserName' => 'apiuser', @@ -448,7 +538,7 @@ public static function create3DPaymentRequestDataDataProvider(): array 'TransactionType' => 'Sale', 'InstallmentCount' => '0', 'Amount' => '100', - 'DisplayAmount' => 10000, + 'DisplayAmount' => '100', 'CurrencyCode' => '0949', 'MerchantOrderId' => '2020110828BC', 'TransactionSecurity' => '3', @@ -476,7 +566,7 @@ public static function create3DEnrollmentCheckRequestDataDataProvider(): array 'MerchantId' => '80', 'UserName' => 'apiuser', 'CustomerId' => '400235', - 'HashData' => 'aEW1KcKuzz2e+oeU36kyEnC65/4=', + 'HashData' => 'request-3d-hash', 'TransactionType' => 'Sale', 'TransactionSecurity' => '3', 'InstallmentCount' => '0', @@ -499,4 +589,39 @@ public static function create3DEnrollmentCheckRequestDataDataProvider(): array ], ]; } + + public static function nonSecurePaymentRequestDataProvider(): array + { + return [ + 'pay_no_installment' => [ + 'order' => [ + 'id' => '2020110828BC', + 'amount' => 1.10, + 'ip' => '127.0.0.1', + 'installment' => 0, + 'currency' => PosInterface::CURRENCY_TRY, + ], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'expected' => [ + 'APIVersion' => 'TDV2.0.0', + 'Amount' => 110, + 'CardCVV2' => '123', + 'CardExpireDateMonth' => '01', + 'CardExpireDateYear' => '25', + 'CardHolderName' => 'John Doe', + 'CardNumber' => '4155650100416111', + 'CurrencyCode' => '0949', + 'CustomerId' => '400235', + 'DisplayAmount' => 110, + 'HashData' => 'request-hash', + 'InstallmentCount' => '0', + 'MerchantId' => '80', + 'MerchantOrderId' => '2020110828BC', + 'TransactionSecurity' => '1', + 'TransactionType' => 'Sale', + 'UserName' => 'apiuser', + ], + ], + ]; + } } diff --git a/tests/Unit/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapperTest.php b/tests/Unit/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapperTest.php index c76115a6..426eec8b 100644 --- a/tests/Unit/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapperTest.php +++ b/tests/Unit/DataMapper/ResponseDataMapper/KuveytPosResponseDataMapperTest.php @@ -143,7 +143,11 @@ public function testMapStatusResponse(array $responseData, array $expectedData): unset($actualData['refund_time'], $expectedData['refund_time']); unset($actualData['cancel_time'], $expectedData['cancel_time']); + $this->assertArrayHasKey('all', $actualData); + $this->assertIsArray($actualData['all']); + $this->assertNotEmpty($actualData['all']); unset($actualData['all']); + \ksort($expectedData); \ksort($actualData); $this->assertSame($expectedData, $actualData); @@ -940,7 +944,7 @@ public static function statusTestDataProvider(): iterable 'capture' => true, 'remote_order_id' => '114293600', 'currency' => PosInterface::CURRENCY_TRY, - 'capture_time' => null, + 'capture_time' => new \DateTimeImmutable('2023-07-08T23:45:35.283'), 'transaction_time' => new \DateTimeImmutable('2023-07-08T23:45:15.797'), 'cancel_time' => null, 'refund_amount' => null, @@ -989,6 +993,188 @@ public static function statusTestDataProvider(): iterable 'cancel_time' => null, ], ]; + yield 'tdv2_success_tx_pay' => [ + 'responseData' => [ + 'GetMerchantOrderDetailResult' => [ + 'Results' => [], + 'Success' => true, + 'Value' => [ + 'OrderContract' => [ + 'IsSelected' => false, + 'IsSelectable' => true, + 'OrderId' => 155768281, + 'MerchantOrderId' => '20240424C7A5', + 'MerchantId' => 496, + 'CardHolderName' => 'John Doe', + 'CardType' => 'MasterCard', + 'CardNumber' => '518896******2544', + 'OrderDate' => '2024-04-24T16:03:42.07', + 'OrderStatus' => 1, + 'LastOrderStatus' => 1, + 'OrderType' => 1, + 'TransactionStatus' => 1, + 'FirstAmount' => '10.01', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'FEC' => '0949', + 'VPSEntryMode' => 'ECOM', + 'InstallmentCount' => 0, + 'TransactionSecurity' => 3, + 'ResponseCode' => '00', + 'ResponseExplain' => 'İşlem gerçekleştirildi.', + 'EndOfDayStatus' => 1, + 'TransactionSide' => 'Auto', + 'CardHolderIPAddress' => '', + 'MerchantIPAddress' => '45.130.202.59', + 'MerchantUserName' => 'apitest', + 'ProvNumber' => '050990', + 'BatchId' => 545, + 'CardExpireDate' => '2506', + 'PosTerminalId' => 'VP008759', + 'Explain' => '', + 'Explain2' => '', + 'Explain3' => '', + 'RRN' => '411516539768', + 'Stan' => '539768', + 'UserName' => 'vposuser', + 'HostName' => 'STD8BOATEST1', + 'SystemDate' => '2024-04-24T16:03:42.077', + 'UpdateUserName' => 'vposuser', + 'UpdateHostName' => 'STD8BOATEST2', + 'UpdateSystemDate' => '2024-04-24T16:04:12.373', + 'EndOfDayDate' => null, + 'HostIP' => '172.20.8.84', + 'FECAmount' => '0', + 'IdentityTaxNumber' => '', + 'QueryId' => '0', + 'DebtId' => '0', + 'DebtorName' => '', + 'Period' => '', + 'SurchargeAmount' => '0', + 'SGKDebtAmount' => '0', + 'DeferringCount' => null, + ], + ], + ], + ], + 'expectedData' => [ + 'auth_code' => '050990', + 'capture' => true, + 'capture_amount' => 10.01, + 'currency' => 'TRY', + 'error_code' => null, + 'error_message' => null, + 'first_amount' => 10.01, + 'installment_count' => 0, + 'masked_number' => '518896******2544', + 'order_id' => '20240424C7A5', + 'order_status' => 'PAYMENT_COMPLETED', + 'proc_return_code' => '00', + 'ref_ret_num' => '411516539768', + 'refund_amount' => null, + 'remote_order_id' => '155768281', + 'status' => 'approved', + 'status_detail' => null, + 'transaction_id' => '539768', + 'transaction_type' => null, + 'transaction_time' => new \DateTimeImmutable('2024-04-24T16:03:42.07'), + 'capture_time' => new \DateTimeImmutable('2024-04-24T16:04:12.373'), + 'refund_time' => null, + 'cancel_time' => null, + ], + ]; + yield 'tdv2_success_tx_pay_then_cancel' => [ + 'responseData' => [ + 'GetMerchantOrderDetailResult' => [ + 'Results' => [], + 'Success' => true, + 'Value' => [ + 'OrderContract' => [ + 'IsSelected' => false, + 'IsSelectable' => true, + 'OrderId' => 155768281, + 'MerchantOrderId' => '20240424C7A5', + 'MerchantId' => 496, + 'CardHolderName' => 'John Doe', + 'CardType' => 'MasterCard', + 'CardNumber' => '518896******2544', + 'OrderDate' => '2024-04-24T16:03:42.07', + 'OrderStatus' => 1, + 'LastOrderStatus' => 6, + 'OrderType' => 1, + 'TransactionStatus' => 1, + 'FirstAmount' => '10.01', + 'CancelAmount' => '10.01', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'FEC' => '0949', + 'VPSEntryMode' => 'ECOM', + 'InstallmentCount' => 0, + 'TransactionSecurity' => 3, + 'ResponseCode' => '00', + 'ResponseExplain' => 'İşlem gerçekleştirildi.', + 'EndOfDayStatus' => 1, + 'TransactionSide' => 'Auto', + 'CardHolderIPAddress' => '', + 'MerchantIPAddress' => '45.130.202.59', + 'MerchantUserName' => 'apitest', + 'ProvNumber' => '050990', + 'BatchId' => 545, + 'CardExpireDate' => '2506', + 'PosTerminalId' => 'VP008759', + 'Explain' => '', + 'Explain2' => '', + 'Explain3' => '', + 'RRN' => '411516539768', + 'Stan' => '539768', + 'UserName' => 'vposuser', + 'HostName' => 'STD8BOATEST1', + 'SystemDate' => '2024-04-24T16:03:42.077', + 'UpdateUserName' => 'webgate', + 'UpdateHostName' => 'STD8BOATEST1', + 'UpdateSystemDate' => '2024-04-24T16:09:27.067', + 'EndOfDayDate' => null, + 'HostIP' => '172.20.8.84', + 'FECAmount' => '0', + 'IdentityTaxNumber' => '', + 'QueryId' => '0', + 'DebtId' => '0', + 'DebtorName' => '', + 'Period' => '', + 'SurchargeAmount' => '0', + 'SGKDebtAmount' => '0', + 'DeferringCount' => null, + ], + ], + ], + ], + 'expectedData' => [ + 'auth_code' => '050990', + 'capture' => null, + 'capture_amount' => null, + 'currency' => 'TRY', + 'error_code' => null, + 'error_message' => null, + 'first_amount' => 10.01, + 'installment_count' => 0, + 'masked_number' => '518896******2544', + 'order_id' => '20240424C7A5', + 'order_status' => 'CANCELED', + 'proc_return_code' => '00', + 'ref_ret_num' => '411516539768', + 'refund_amount' => null, + 'remote_order_id' => '155768281', + 'status' => 'approved', + 'status_detail' => null, + 'transaction_id' => '539768', + 'transaction_type' => null, + 'transaction_time' => new \DateTimeImmutable('2024-04-24T16:03:42.07'), + 'capture_time' => null, + 'refund_time' => null, + 'cancel_time' => new \DateTimeImmutable('2024-04-24T16:09:27.067'), + ], + ]; } public static function cancelTestDataProvider(): iterable diff --git a/tests/Unit/Gateways/KuveytPosTest.php b/tests/Unit/Gateways/KuveytPosTest.php index e092c6d1..d8a0c837 100644 --- a/tests/Unit/Gateways/KuveytPosTest.php +++ b/tests/Unit/Gateways/KuveytPosTest.php @@ -79,7 +79,7 @@ protected function setUp(): void 'name' => 'kuveyt-pos', 'class' => KuveytPos::class, 'gateway_endpoints' => [ - 'payment_api' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelProvisionGate', + 'payment_api' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home', 'gateway_3d' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelPayGate', 'query_api' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', ], @@ -164,6 +164,26 @@ public function testSetTestMode(): void $this->assertTrue($this->pos->isTestMode()); } + /** + * @dataProvider getApiUrlDataProvider + */ + public function testGetApiURL(?string $txType, ?string $paymentModel, string $expected): void + { + $actual = $this->pos->getApiURL($txType, $paymentModel); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider getApiUrlExceptionDataProvider + */ + public function testGetApiURLException(string $txType, string $paymentModel, string $exceptionClass): void + { + $this->expectException($exceptionClass); + + $this->pos->getApiURL($txType, $paymentModel); + } + /** * @return void */ @@ -253,7 +273,7 @@ public function testMake3DPayment( $this->prepareClient( $this->httpClientMock, 'response-body', - $this->config['gateway_endpoints']['payment_api'], + 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelProvisionGate', [ 'body' => 'request-body', 'headers' => [ @@ -308,10 +328,35 @@ public function testMake3DPayment( $this->assertSame($isSuccess, $this->pos->isSuccess()); } - public function testMakeRegularPayment(): void + /** + * @dataProvider makeRegularPaymentDataProvider + */ + public function testMakeRegularPayment(array $order, string $txType, string $apiUrl): void { - $this->expectException(UnsupportedPaymentModelException::class); - $this->pos->makeRegularPayment([], $this->card, PosInterface::TX_TYPE_PAY_AUTH); + $account = $this->pos->getAccount(); + $card = $this->card; + $this->requestMapperMock->expects(self::once()) + ->method('createNonSecurePaymentRequestData') + ->with($account, $order, $txType, $card) + ->willReturn(['createNonSecurePaymentRequestData']); + + $paymentResponse = ['paymentResponse']; + + $this->configureClientResponse( + $txType, + $apiUrl, + ['createNonSecurePaymentRequestData'], + 'request-body', + 'response-body', + $paymentResponse + ); + + $this->responseMapperMock->expects(self::once()) + ->method('mapPaymentResponse') + ->with($paymentResponse, $txType, $order) + ->willReturn(['result']); + + $this->pos->makeRegularPayment($order, $card, $txType); } public function testMakeRegularPostAuthPayment(): void @@ -405,7 +450,7 @@ public static function makeRegularPaymentDataProvider(): array 'id' => '2020110828BC', ], 'txType' => PosInterface::TX_TYPE_PAY_AUTH, - 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/Non3DPayGate', + 'api_url' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/Non3DPayGate', ], ]; } @@ -443,4 +488,51 @@ private function configureClientResponse( $statusCode ); } + + public static function getApiUrlDataProvider(): array + { + return [ + [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_3D_SECURE, + 'expected' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelProvisionGate', + ], + [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/Non3DPayGate', + ], + [ + 'txType' => PosInterface::TX_TYPE_REFUND, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', + ], + [ + 'txType' => PosInterface::TX_TYPE_CANCEL, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', + ], + [ + 'txType' => PosInterface::TX_TYPE_STATUS, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', + ], + [ + 'txType' => null, + 'paymentModel' => null, + 'expected' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home', + ], + ]; + } + + public static function getApiUrlExceptionDataProvider(): array + { + return [ + [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_3D_PAY, + 'exception_class' => UnsupportedTransactionTypeException::class, + ], + ]; + } }