From 16f44f52940982b95373054136faef3aa4ecdc8c Mon Sep 17 00:00:00 2001 From: Nuryagdy Mustapayev Date: Mon, 1 Apr 2024 15:04:00 +0200 Subject: [PATCH 1/4] issue #181 prepare apiUrl mapping for vakifKatilimPos Changes --- src/Gateways/AbstractGateway.php | 88 ++++++++++++++++++++++++-------- src/Gateways/EstPos.php | 11 ++-- src/Gateways/GarantiPos.php | 10 ++-- src/Gateways/InterPos.php | 10 ++-- src/Gateways/KuveytPos.php | 21 +++++--- src/Gateways/PayFlexCPV4Pos.php | 17 ++++-- src/Gateways/PayFlexV4Pos.php | 10 ++-- src/Gateways/PayForPos.php | 10 ++-- src/Gateways/PosNet.php | 24 +++++++-- src/Gateways/PosNetV1Pos.php | 12 +++-- src/Gateways/ToslaPos.php | 11 ++-- 11 files changed, 162 insertions(+), 62 deletions(-) diff --git a/src/Gateways/AbstractGateway.php b/src/Gateways/AbstractGateway.php index 4ce31007..4b912708 100644 --- a/src/Gateways/AbstractGateway.php +++ b/src/Gateways/AbstractGateway.php @@ -149,11 +149,15 @@ public function get3DHostGatewayURL(): string } /** + * @phpstan-param self::TX_TYPE_* $txType + * + * @param string|null $txType + * * @return non-empty-string */ - public function getQueryAPIUrl(): string + public function getQueryAPIUrl(string $txType = null): string { - return $this->config['gateway_endpoints']['query_api'] ?? $this->getApiURL(); + return $this->config['gateway_endpoints']['query_api'] ?? $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE); } /** @@ -230,7 +234,12 @@ public function makeRegularPayment(array $order, CreditCardInterface $creditCard } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_NON_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + ); $this->response = $this->responseDataMapper->mapPaymentResponse($bankResponse, $txType, $order); return $this; @@ -249,7 +258,7 @@ public function makeRegularPostPayment(array $order): PosInterface $requestData = $this->requestDataMapper->createNonSecurePostAuthPaymentRequestData($this->account, $order); - $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), PosInterface::TX_TYPE_PAY_POST_AUTH); + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); $this->eventDispatcher->dispatch($event); if ($requestData !== $event->getRequestData()) { $this->logger->debug('Request data is changed via listeners', [ @@ -262,7 +271,12 @@ public function makeRegularPostPayment(array $order): PosInterface } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_NON_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + ); $this->response = $this->responseDataMapper->mapPaymentResponse($bankResponse, $txType, $order); return $this; @@ -273,9 +287,10 @@ public function makeRegularPostPayment(array $order): PosInterface */ public function refund(array $order): PosInterface { + $txType = PosInterface::TX_TYPE_REFUND; $requestData = $this->requestDataMapper->createRefundRequestData($this->account, $order); - $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), PosInterface::TX_TYPE_REFUND); + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); $this->eventDispatcher->dispatch($event); if ($requestData !== $event->getRequestData()) { $this->logger->debug('Request data is changed via listeners', [ @@ -287,8 +302,13 @@ public function refund(array $order): PosInterface $requestData = $event->getRequestData(); } - $data = $this->serializer->encode($requestData, PosInterface::TX_TYPE_REFUND); - $bankResponse = $this->send($data, PosInterface::TX_TYPE_REFUND, PosInterface::MODEL_NON_SECURE); + $data = $this->serializer->encode($requestData, $txType); + $bankResponse = $this->send( + $data, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + ); $this->response = $this->responseDataMapper->mapRefundResponse($bankResponse); return $this; @@ -299,9 +319,10 @@ public function refund(array $order): PosInterface */ public function cancel(array $order): PosInterface { + $txType = PosInterface::TX_TYPE_CANCEL; $requestData = $this->requestDataMapper->createCancelRequestData($this->account, $order); - $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), PosInterface::TX_TYPE_CANCEL); + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); $this->eventDispatcher->dispatch($event); if ($requestData !== $event->getRequestData()) { $this->logger->debug('Request data is changed via listeners', [ @@ -313,8 +334,13 @@ public function cancel(array $order): PosInterface $requestData = $event->getRequestData(); } - $data = $this->serializer->encode($requestData, PosInterface::TX_TYPE_CANCEL); - $bankResponse = $this->send($data, PosInterface::TX_TYPE_CANCEL, PosInterface::MODEL_NON_SECURE); + $data = $this->serializer->encode($requestData, $txType); + $bankResponse = $this->send( + $data, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + ); $this->response = $this->responseDataMapper->mapCancelResponse($bankResponse); return $this; @@ -325,9 +351,10 @@ public function cancel(array $order): PosInterface */ public function status(array $order): PosInterface { + $txType = PosInterface::TX_TYPE_STATUS; $requestData = $this->requestDataMapper->createStatusRequestData($this->account, $order); - $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), PosInterface::TX_TYPE_STATUS); + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); $this->eventDispatcher->dispatch($event); if ($requestData !== $event->getRequestData()) { $this->logger->debug('Request data is changed via listeners', [ @@ -339,8 +366,13 @@ public function status(array $order): PosInterface $requestData = $event->getRequestData(); } - $data = $this->serializer->encode($requestData, PosInterface::TX_TYPE_STATUS); - $bankResponse = $this->send($data, PosInterface::TX_TYPE_STATUS, PosInterface::MODEL_NON_SECURE, $this->getQueryAPIUrl()); + $data = $this->serializer->encode($requestData, $txType); + $bankResponse = $this->send( + $data, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getQueryAPIUrl($txType) + ); $this->response = $this->responseDataMapper->mapStatusResponse($bankResponse); return $this; @@ -351,9 +383,10 @@ public function status(array $order): PosInterface */ public function history(array $data): PosInterface { + $txType = PosInterface::TX_TYPE_HISTORY; $requestData = $this->requestDataMapper->createHistoryRequestData($this->account, $data); - $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), PosInterface::TX_TYPE_HISTORY); + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); $this->eventDispatcher->dispatch($event); if ($requestData !== $event->getRequestData()) { $this->logger->debug('Request data is changed via listeners', [ @@ -365,8 +398,13 @@ public function history(array $data): PosInterface $requestData = $event->getRequestData(); } - $encodedRequestData = $this->serializer->encode($requestData, PosInterface::TX_TYPE_HISTORY); - $bankResponse = $this->send($encodedRequestData, PosInterface::TX_TYPE_HISTORY, PosInterface::MODEL_NON_SECURE); + $encodedRequestData = $this->serializer->encode($requestData, $txType); + $bankResponse = $this->send( + $encodedRequestData, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + ); $this->response = $this->responseDataMapper->mapHistoryResponse($bankResponse); return $this; @@ -377,9 +415,10 @@ public function history(array $data): PosInterface */ public function orderHistory(array $order): PosInterface { + $txType = PosInterface::TX_TYPE_ORDER_HISTORY; $requestData = $this->requestDataMapper->createOrderHistoryRequestData($this->account, $order); - $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), PosInterface::TX_TYPE_ORDER_HISTORY); + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); $this->eventDispatcher->dispatch($event); if ($requestData !== $event->getRequestData()) { $this->logger->debug('Request data is changed via listeners', [ @@ -391,8 +430,13 @@ public function orderHistory(array $order): PosInterface $requestData = $event->getRequestData(); } - $data = $this->serializer->encode($requestData, PosInterface::TX_TYPE_ORDER_HISTORY); - $bankResponse = $this->send($data, PosInterface::TX_TYPE_ORDER_HISTORY, PosInterface::MODEL_NON_SECURE); + $data = $this->serializer->encode($requestData, $txType); + $bankResponse = $this->send( + $data, + $txType, + PosInterface::MODEL_NON_SECURE, + $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + ); $this->response = $this->responseDataMapper->mapOrderHistoryResponse($bankResponse); return $this; @@ -437,11 +481,11 @@ public function getLanguages(): array * @param array|string $contents data to send * @param string $txType * @param string $paymentModel - * @param non-empty-string|null $url URL address of the API + * @param non-empty-string $url URL address of the API * * @return array */ - abstract protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array; + abstract protected function send($contents, string $txType, string $paymentModel, string $url): array; /** * @inheritDoc diff --git a/src/Gateways/EstPos.php b/src/Gateways/EstPos.php index 6edc00b9..458b5788 100644 --- a/src/Gateways/EstPos.php +++ b/src/Gateways/EstPos.php @@ -98,7 +98,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $provisionResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $provisionResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData( $request->all(), @@ -162,10 +167,8 @@ public function history(array $data): PosInterface * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url = $this->getApiURL(); - $this->logger->debug('sending request', ['url' => $url]); $response = $this->client->post($url, ['body' => $contents]); diff --git a/src/Gateways/GarantiPos.php b/src/Gateways/GarantiPos.php index 88375031..ce45401f 100644 --- a/src/Gateways/GarantiPos.php +++ b/src/Gateways/GarantiPos.php @@ -90,7 +90,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData($request->all(), $bankResponse, $txType, $order); $this->logger->debug('finished 3D payment', ['mapped_response' => $this->response]); @@ -139,9 +144,8 @@ public function history(array $data): PosInterface * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url = $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); $response = $this->client->post($url, ['body' => $contents]); diff --git a/src/Gateways/InterPos.php b/src/Gateways/InterPos.php index b9208762..3371868e 100644 --- a/src/Gateways/InterPos.php +++ b/src/Gateways/InterPos.php @@ -96,7 +96,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData($gatewayResponse, $bankResponse, $txType, $order); $this->logger->debug('finished 3D payment', ['mapped_response' => $this->response]); @@ -162,9 +167,8 @@ public function get3DFormData(array $order, string $paymentModel, string $txType * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url ??= $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); if (!\is_array($contents)) { throw new InvalidArgumentException(\sprintf('Argument type must be array, %s provided.', \gettype($contents))); diff --git a/src/Gateways/KuveytPos.php b/src/Gateways/KuveytPos.php index 7d3a3e6f..946adcbd 100644 --- a/src/Gateways/KuveytPos.php +++ b/src/Gateways/KuveytPos.php @@ -164,7 +164,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData($gatewayResponse, $bankResponse, $txType, $order); $this->logger->debug('finished 3D payment', ['mapped_response' => $this->response]); @@ -178,17 +183,16 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr * * @return array */ - protected function send($contents, string $txType, string $paymentModel, string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { if (\in_array($txType, [PosInterface::TX_TYPE_REFUND, PosInterface::TX_TYPE_STATUS, PosInterface::TX_TYPE_CANCEL], true)) { if (!\is_array($contents)) { - throw new InvalidArgumentException(sprintf('Invalid data type provided for %s transaction!', $txType)); + throw new InvalidArgumentException(\sprintf('Invalid data type provided for %s transaction!', $txType)); } return $this->data = $this->sendSoapRequest($contents, $txType); } - $url ??= $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); $body = [ 'body' => $contents, @@ -207,16 +211,19 @@ protected function send($contents, string $txType, string $paymentModel, string * * @param array $contents * @param string $txType - * @param non-empty-string|null $url * * @return array * * @throws SoapFault * @throws Throwable */ - protected function sendSoapRequest(array $contents, string $txType, string $url = null): array + protected function sendSoapRequest(array $contents, string $txType): array { - $url ??= $this->getQueryAPIUrl(); + $url = $this->getQueryAPIUrl(); + $this->logger->debug('sending soap request', [ + 'txType' => $txType, + 'url' => $url, + ]); $sslConfig = [ 'allow_self_signed' => true, diff --git a/src/Gateways/PayFlexCPV4Pos.php b/src/Gateways/PayFlexCPV4Pos.php index cca01dd1..3809d38c 100644 --- a/src/Gateways/PayFlexCPV4Pos.php +++ b/src/Gateways/PayFlexCPV4Pos.php @@ -111,7 +111,12 @@ public function make3DPayPayment(Request $request, array $order, string $txType) * TransactionId: string, * PaymentToken: string} $bankResponse */ - $bankResponse = $this->send($requestData, PosInterface::TX_TYPE_PAY_AUTH, PosInterface::MODEL_3D_SECURE, $this->getQueryAPIUrl()); + $bankResponse = $this->send( + $requestData, + PosInterface::TX_TYPE_PAY_AUTH, + PosInterface::MODEL_3D_SECURE, + $this->getQueryAPIUrl() + ); $this->response = $this->responseDataMapper->map3DPayResponseData($bankResponse, $txType, $order); @@ -183,9 +188,8 @@ public function get3DFormData(array $order, string $paymentModel, string $txType * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url ??= $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); $isXML = \is_string($contents); @@ -242,7 +246,12 @@ private function registerPayment(array $order, string $txType, string $paymentMo } /** @var array{CommonPaymentUrl: string|null, PaymentToken: string|null, ErrorCode: string|null, ResponseMessage: string|null} $response */ - $response = $this->send($requestData, $txType, $paymentModel); + $response = $this->send( + $requestData, + $txType, + $paymentModel, + $this->getApiURL() + ); return $response; } diff --git a/src/Gateways/PayFlexV4Pos.php b/src/Gateways/PayFlexV4Pos.php index 8c68306b..75811d98 100644 --- a/src/Gateways/PayFlexV4Pos.php +++ b/src/Gateways/PayFlexV4Pos.php @@ -89,7 +89,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData($request->all(), $bankResponse, $txType, $order); $this->logger->debug('finished 3D payment', ['mapped_response' => $this->response]); @@ -174,9 +179,8 @@ public function get3DFormData(array $order, string $paymentModel, string $txType * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url ??= $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); $isXML = \is_string($contents); diff --git a/src/Gateways/PayForPos.php b/src/Gateways/PayForPos.php index 129d66d3..e77461e2 100644 --- a/src/Gateways/PayForPos.php +++ b/src/Gateways/PayForPos.php @@ -90,7 +90,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData($request->all(), $bankResponse, $txType, $order); @@ -174,9 +179,8 @@ public function get3DFormData(array $order, string $paymentModel, string $txType * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url = $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); $response = $this->client->post($url, [ 'headers' => [ diff --git a/src/Gateways/PosNet.php b/src/Gateways/PosNet.php index b104f49c..b2998504 100644 --- a/src/Gateways/PosNet.php +++ b/src/Gateways/PosNet.php @@ -82,7 +82,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $userVerifyResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $userVerifyResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); if (!$this->is3DAuthSuccess($userVerifyResponse)) { $this->response = $this->responseDataMapper->map3DPaymentData($userVerifyResponse, null, $txType, $order); @@ -110,7 +115,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $bankResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); $this->response = $this->responseDataMapper->map3DPaymentData($userVerifyResponse, $bankResponse, $txType, $order); $this->logger->debug('finished 3D payment', ['mapped_response' => $this->response]); @@ -182,9 +192,8 @@ public function orderHistory(array $order): PosInterface * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url = $this->getApiURL(); $this->logger->debug('sending request', ['url' => $url]); if (!\is_string($contents)) { @@ -238,6 +247,11 @@ private function getOosTransactionData(array $order, string $txType, CreditCardI $xml = $this->serializer->encode($requestData, $txType); - return $this->send($xml, $txType, PosInterface::MODEL_3D_SECURE); + return $this->send( + $xml, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL() + ); } } diff --git a/src/Gateways/PosNetV1Pos.php b/src/Gateways/PosNetV1Pos.php index b046efec..6729dceb 100644 --- a/src/Gateways/PosNetV1Pos.php +++ b/src/Gateways/PosNetV1Pos.php @@ -100,7 +100,12 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr } $contents = $this->serializer->encode($requestData, $txType); - $provisionResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + $provisionResponse = $this->send( + $contents, + $txType, + PosInterface::MODEL_3D_SECURE, + $this->getApiURL($txType) + ); $this->logger->debug('send $provisionResponse', ['$provisionResponse' => $provisionResponse]); $this->response = $this->responseDataMapper->map3DPaymentData($request->all(), $provisionResponse, $txType, $order); @@ -156,12 +161,11 @@ public function orderHistory(array $order): PosInterface * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url = $this->getApiURL($txType); $this->logger->debug('sending request', ['url' => $url]); - if (!is_string($contents)) { + if (!\is_string($contents)) { throw new InvalidArgumentException('Invalid data provided'); } diff --git a/src/Gateways/ToslaPos.php b/src/Gateways/ToslaPos.php index 281c3db3..60befcca 100644 --- a/src/Gateways/ToslaPos.php +++ b/src/Gateways/ToslaPos.php @@ -168,10 +168,8 @@ public function history(array $data): PosInterface * * @return array */ - protected function send($contents, string $txType, string $paymentModel, ?string $url = null): array + protected function send($contents, string $txType, string $paymentModel, string $url): array { - $url = $this->getApiURL($txType, $paymentModel); - $this->logger->debug('sending request', ['url' => $url]); $response = $this->client->post($url, [ 'headers' => [ @@ -232,7 +230,12 @@ private function registerPayment(array $order, string $paymentModel, string $txT $requestData = $this->serializer->encode($requestData, $txType); - return $this->send($requestData, $txType, $paymentModel); + return $this->send( + $requestData, + $txType, + $paymentModel, + $this->getApiURL($txType, $paymentModel) + ); } /** From 63f378266584aa35e0e05e8bca8355cf71d7d30c Mon Sep 17 00:00:00 2001 From: Nuryagdy Mustapayev Date: Mon, 1 Apr 2024 16:44:22 +0200 Subject: [PATCH 2/4] readme.md auto format --- README.md | 250 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 2c15d082..c312350b 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![License](http://poser.pugx.org/mews/pos/license)](https://packagist.org/packages/mews/pos) [![PHP Version Require](http://poser.pugx.org/mews/pos/require/php)](https://packagist.org/packages/mews/pos) - -Bu paket ile amaçlanan; ortak bir arayüz sınıfı ile, tüm Türk banka sanal pos sistemlerinin kullanılabilmesidir. +Bu paket ile amaçlanan; ortak bir arayüz sınıfı ile, tüm Türk banka sanal pos +sistemlerinin kullanılabilmesidir. ### Deskteklenen Payment Gateway'ler / Bankalar: @@ -21,24 +21,24 @@ Bu paket ile amaçlanan; ortak bir arayüz sınıfı ile, tüm Türk banka sanal | PosNet | YapıKredi | NonSecure
3DSecure
| İptal
İade
Durum sorgulama | | 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 | +| InterPOS | Deniz bank | NonSecure
3DSecure
3DPay
3DHost | İptal
İade
Durum sorgulama | | Kuveyt POS | Kuveyt Türk | 3DSecure | İptal
İade
Durum sorgulama
(SOAP API) | - ### Ana başlıklar + - [Özellikler](#ozellikler) - [Latest updates](#latest-updates) - [Minimum Gereksinimler](#minimum-gereksinimler) - [Kurulum](#kurulum) - [Farklı Banka Sanal Poslarını Eklemek](#farkli-banka-sanal-poslarini-eklemek) - [Ornek Kodlar](#ornek-kodlar) - - [3DSecure, 3DPay ve 3DHost Ödeme Örneği](./docs/THREED-PAYMENT-EXAMPLE.md) - - [3DSecure, 3DPay ve 3DHost Modal Box ile Ödeme Örneği](./docs/THREED-SECURE-AND-PAY-PAYMENT-IN-MODALBOX-EXAMPLE.md) - - [Non Secure Ödeme Örneği](./docs/NON-SECURE-PAYMENT-EXAMPLE.md) - - [Ön otorizasyon ve Ön otorizasyon kapama](./docs/PRE-AUTH-POST-EXAMPLE.md) - - [Ödeme İptal](./docs/CANCEL-EXAMPLE.md) - - [Ödeme İade](./docs/REFUND-EXAMPLE.md) - - [Ödeme Durum Sorgulama](./docs/STATUS-EXAMPLE.md) + - [3DSecure, 3DPay ve 3DHost Ödeme Örneği](./docs/THREED-PAYMENT-EXAMPLE.md) + - [3DSecure, 3DPay ve 3DHost Modal Box ile Ödeme Örneği](./docs/THREED-SECURE-AND-PAY-PAYMENT-IN-MODALBOX-EXAMPLE.md) + - [Non Secure Ödeme Örneği](./docs/NON-SECURE-PAYMENT-EXAMPLE.md) + - [Ön otorizasyon ve Ön otorizasyon kapama](./docs/PRE-AUTH-POST-EXAMPLE.md) + - [Ödeme İptal](./docs/CANCEL-EXAMPLE.md) + - [Ödeme İade](./docs/REFUND-EXAMPLE.md) + - [Ödeme Durum Sorgulama](./docs/STATUS-EXAMPLE.md) - [Popup Windowda veya Iframe icinde odeme yapma](#popup-windowda-veya-iframe-icinde-odeme-yapma) - [Troubleshoots](#troubleshoots) @@ -46,65 +46,82 @@ Bu paket ile amaçlanan; ortak bir arayüz sınıfı ile, tüm Türk banka sanal - [Docker ile test ortamı](#docker-ile-test-ortami) ### Ozellikler - - Non Secure E-Commerce modeliyle ödeme (`PosInterface::MODEL_NON_SECURE`) - - 3D Secure modeliyle ödeme (`PosInterface::MODEL_3D_SECURE`) - - 3D Pay modeliyle ödeme (`PosInterface::MODEL_3D_PAY`) - - 3D Host modeliyle ödeme (`PosInterface::MODEL_3D_HOST`) - - Sipariş/Ödeme durum sorgulama (`PosInterface::TX_TYPE_STATUS`) - - Sipariş Tarihçesini sorgulama sorgulama (`PosInterface::TX_TYPE_ORDER_HISTORY`) - - Geçmiş işlemleri sorgulama (`PosInterface::TX_TYPE_HISTORY`) - - Sipariş/Para iadesi yapma (`PosInterface::TX_TYPE_REFUND`) - - Sipariş iptal etme (`PosInterface::TX_TYPE_CANCEL`) - - API istek verilerinin gateway API'na gönderilmeden önce değiştirebilme - - Farklı Para birimler ile ödeme desteği - - Tekrarlanan (Recurring) ödeme talimatları - - [PSR-3](https://www.php-fig.org/psr/psr-3/) logger desteği - - [PSR-18](https://www.php-fig.org/psr/psr-18/) HTTP Client desteği + +- Non Secure E-Commerce modeliyle ödeme (`PosInterface::MODEL_NON_SECURE`) +- 3D Secure modeliyle ödeme (`PosInterface::MODEL_3D_SECURE`) +- 3D Pay modeliyle ödeme (`PosInterface::MODEL_3D_PAY`) +- 3D Host modeliyle ödeme (`PosInterface::MODEL_3D_HOST`) +- Sipariş/Ödeme durum sorgulama (`PosInterface::TX_TYPE_STATUS`) +- Sipariş Tarihçesini sorgulama + sorgulama (`PosInterface::TX_TYPE_ORDER_HISTORY`) +- Geçmiş işlemleri sorgulama (`PosInterface::TX_TYPE_HISTORY`) +- Sipariş/Para iadesi yapma (`PosInterface::TX_TYPE_REFUND`) +- Sipariş iptal etme (`PosInterface::TX_TYPE_CANCEL`) +- API istek verilerinin gateway API'na gönderilmeden önce değiştirebilme +- Farklı Para birimler ile ödeme desteği +- Tekrarlanan (Recurring) ödeme talimatları +- [PSR-3](https://www.php-fig.org/psr/psr-3/) logger desteği +- [PSR-18](https://www.php-fig.org/psr/psr-18/) HTTP Client desteği #### Farkli Gateway'ler Tek islem akisi -* Bir (**3DSecure**, **3DPay**, **3DHost**, **NonSecure**) ödeme modelden diğerine geçiş çok az değişiklik gerektirir. -* Aynı tip işlem için farklı POS Gateway'lerden dönen değerler aynı formata normalize edilmiş durumda. -Yani kod güncellemenize gerek yok. + +* Bir (**3DSecure**, **3DPay**, **3DHost**, **NonSecure**) ödeme modelden + diğerine geçiş çok az değişiklik gerektirir. +* Aynı tip işlem için farklı POS Gateway'lerden dönen değerler aynı formata + normalize edilmiş durumda. + Yani kod güncellemenize gerek yok. * Aynı tip işlem için farklı Gateway'lere gönderilecek değerler de genel olarak -aynı formatta olacak şekilde normalize edilmiştir. + aynı formatta olacak şekilde normalize edilmiştir. ### Latest updates Son yapılan değişiklikler için [`CHANGELOG`](./docs/CHANGELOG.md). ### Minimum Gereksinimler - - PHP >= 7.4 - - ext-dom - - ext-json - - ext-openssl - - ext-SimpleXML - - ext-soap (sadece KuveytPos için) - - [PSR-18](https://packagist.org/providers/psr/http-client-implementation): HTTP Client - - [PSR-14](https://packagist.org/providers/psr/event-dispatcher-implementation): Event Dispatcher + +- PHP >= 7.4 +- ext-dom +- ext-json +- ext-openssl +- ext-SimpleXML +- ext-soap (sadece KuveytPos için) +- [PSR-18](https://packagist.org/providers/psr/http-client-implementation): HTTP + Client +- [PSR-14](https://packagist.org/providers/psr/event-dispatcher-implementation): + Event Dispatcher ### Kurulum + ```sh $ composer require symfony/event-dispatcher mews/pos ``` + Kütüphane belli bir HTTP Client'ile zorunlu bağımlılığı yoktur. PSR-18 HTTP Client standarta uyan herhangi bir kütüphane kullanılabilinir. Projenizde zaten kurulu PSR-18 uygulaması varsa otomatik onu kullanır. Veya hızlı başlangıç için: + ```sh $ composer require php-http/curl-client nyholm/psr7 symfony/event-dispatcher mews/pos ``` -Diğer PSR-18 uygulamasını sağlayan kütüphaneler: https://packagist.org/providers/psr/http-client-implementation + +Diğer PSR-18 uygulamasını sağlayan +kütüphaneler: https://packagist.org/providers/psr/http-client-implementation ### Farkli Banka Sanal Poslarini Eklemek + Kendi projenizin dizinindeyken + ```sh $ cp ./vendor/mews/pos/config/pos_production.php ./pos_ayarlar.php ``` + ya da; Projenizde bir ayar dosyası oluşturup (`pos_ayarlar.php` gibi), -paket içerisinde `./config/pos_production.php` dosyasının içeriğini buraya kopyalayın. +paket içerisinde `./config/pos_production.php` dosyasının içeriğini buraya +kopyalayın. ```php `form.php`), +3D ödeme örnek kodlar genel olarak kart bilgilerini website sunucusuna POST +eder (`index.php` => `form.php`), ondan sonra da işlenip gateway'e yönlendiriliyor. -Bu şekilde farklı bankalar arası implementation degişmemesi sağlanmakta (ortak kredi kart formu ve aynı işlem akışı). +Bu şekilde farklı bankalar arası implementation degişmemesi sağlanmakta (ortak +kredi kart formu ve aynı işlem akışı). Genel olarak kart bilgilerini, website sunucusuna POST yapmadan, -direk gateway'e yönlendirecek şekilde kullanılabilinir (genelde, banka örnek kodları bu şekilde implement edilmiş). +direk gateway'e yönlendirecek şekilde kullanılabilinir (genelde, banka örnek +kodları bu şekilde implement edilmiş). Fakat, -- birden fazla bank seçenegi olunca veya müşteri banka degiştirmek istediginde kart bilgi formunu ona göre güncellemeniz gerekecek. -- üstelik YKB POSNet ve VakıfBank POS kart bilgilerini website sunucusu tarafından POST edilmesini gerektiriyor. + +- birden fazla bank seçenegi olunca veya müşteri banka degiştirmek istediginde + kart bilgi formunu ona göre güncellemeniz gerekecek. +- üstelik YKB POSNet ve VakıfBank POS kart bilgilerini website sunucusu + tarafından POST edilmesini gerektiriyor. ### Popup Windowda veya Iframe icinde odeme yapma -Müşteriyi banka sayfasına redirect etmeden **iframe** üzerinden veya **popup window** + +Müşteriyi banka sayfasına redirect etmeden **iframe** üzerinden veya **popup +window** üzerinden ödeme akışı `/examples/` içinde 3D ödeme ile örnek PHP ve JS kodlar yer almaktadır. -Ayrıca Modal Box ile iframe kullarak ödeme örneği [/docs](./docs/THREED-SECURE-AND-PAY-PAYMENT-IN-MODALBOX-EXAMPLE.md)'ta bulabilirsiniz. +Ayrıca Modal Box ile iframe kullarak ödeme +örneği [/docs](./docs/THREED-SECURE-AND-PAY-PAYMENT-IN-MODALBOX-EXAMPLE.md)'ta +bulabilirsiniz. #### Dikkat edilmesi gerekenler + - Popup window taraycı tarafından engellenebilir bu yüzden onun yerine modal box içinde iframe kullanılması tavsiye edilir. ## Troubleshoots + ### Session sıfırlanması -Cookie session kullanığınızda, kullanıcı gatewayden geri websitenize yönlendirilidiğinde session sıfırlanabilir. -Response'da `samesite` değeri set etmeniz gerekiyor. [çözüm](https://stackoverflow.com/a/51128675/4896948). + +Cookie session kullanığınızda, kullanıcı gatewayden geri websitenize +yönlendirilidiğinde session sıfırlanabilir. +Response'da `samesite` değeri set etmeniz +gerekiyor. [çözüm](https://stackoverflow.com/a/51128675/4896948). + ### Shared hosting'lerde IP tanımsız hatası -- Shared hosting'lerde Cpanel'de gördüğünüz IP'den farklı olarak fiziksel sunucun bir tane daha IP'si olur. -O IP adres Cpanel'de gözükmez, hosting firmanızdan sorup öğrenmeniz gerekmekte. -Bu hatayı alırsanız hosting firmanın verdiği IP adrese'de banka gateway'i tarafından izin verilmesini sağlayın. -- kütüphane ortam degerini de kontrol etmeyi unutmayınız, ortama göre bankanın URL'leri degişir. - - test ortamı için `$pos->setTestMode(true);` - - canlı ortamı için `$pos->setTestMode(false);` (default olarak `false`) - _ortam değeri hem bankaya istek gönderirken hem de gelen isteği işlerken doğru deger olması gerekiyor._ +- Shared hosting'lerde Cpanel'de gördüğünüz IP'den farklı olarak fiziksel + sunucun bir tane daha IP'si olur. + O IP adres Cpanel'de gözükmez, hosting firmanızdan sorup öğrenmeniz + gerekmekte. + Bu hatayı alırsanız hosting firmanın verdiği IP adrese'de banka gateway'i + tarafından izin verilmesini sağlayın. +- kütüphane ortam degerini de kontrol etmeyi unutmayınız, ortama göre bankanın + URL'leri degişir. + - test ortamı için `$pos->setTestMode(true);` + - canlı ortamı için `$pos->setTestMode(false);` (default olarak `false`) + + _ortam değeri hem bankaya istek gönderirken hem de gelen isteği işlerken doğru + deger olması gerekiyor._ ### Debugging -Kütüphane [PSR-3](https://www.php-fig.org/psr/psr-3/) standarta uygun logger uygulamayı destekler. + +Kütüphane [PSR-3](https://www.php-fig.org/psr/psr-3/) standarta uygun logger +uygulamayı destekler. Örnekler: https://packagist.org/providers/psr/log-implementation . Monolog logger kullanım örnegi: + ```shell composer require monolog/monolog ``` + ```php $handler = new \Monolog\Handler\StreamHandler(__DIR__.'/../var/log/pos.log', \Psr\Log\LogLevel::DEBUG); $logger = new \Monolog\Logger('pos', [$handler]); @@ -201,70 +247,92 @@ $pos = \Mews\Pos\Factory\PosFactory::createPosGateway( ``` ## Genel Kultur + ### NonSecure, 3D Secure, 3DPay ve 3DHost ödeme modeller arasındaki farklar + - **3DSecure** - Bankaya göre farklı isimler verilebilir, örn. 3D Full. -Gateway'den (3D şifre girdiginiz sayfadan) döndükten sonra ödemeyi tamamlamak için -banka gateway'ne 1 istek daha (_provizyon_ isteği) gönderir. -Bu isteği göndermeden ödeme **tamamlanmaz**. + Gateway'den (3D şifre girdiginiz sayfadan) döndükten sonra ödemeyi tamamlamak + için + banka gateway'ne 1 istek daha (_provizyon_ isteği) gönderir. + Bu isteği göndermeden ödeme **tamamlanmaz**. - **3DPay** - Bankaya göre farklı isimler verilebilir, örn. 3D Half. -Gateway'den (3D şifre girdiginiz sayfadan) döndükten sonra ödeme bitmiş sayılır. -3DSecure ödemede yapıldığı gibi ekstra provizyon istek gönderilmez. + Gateway'den (3D şifre girdiginiz sayfadan) döndükten sonra ödeme bitmiş + sayılır. + 3DSecure ödemede yapıldığı gibi ekstra provizyon istek gönderilmez. - **3DHost** - Kredi kart girişi için kullanıcı bankanın sayfasına yönledirilir, -kredi kart bilgileri girdikten sonra bankanın 3D gateway sayfasına yönlendirilir, -ordan da websitenize geri yönlendirilir. Yönlendirme sonucunda ödeme tamanlanmış olur. + kredi kart bilgileri girdikten sonra bankanın 3D gateway sayfasına + yönlendirilir, + ordan da websitenize geri yönlendirilir. Yönlendirme sonucunda ödeme + tamanlanmış olur. - **NonSecure** - Ödeme işlemi kullanıcı 3D onay işlemi yapmadan gerçekleşir. -- **NonSecure, 3DSecure ve 3DPay** - Ödemede kredi kart bilgisi websiteniz tarafından alınır. -**3DHost** ödemede ise banka websayfasından alınır. +- **NonSecure, 3DSecure ve 3DPay** - Ödemede kredi kart bilgisi websiteniz + tarafından alınır. + **3DHost** ödemede ise banka websayfasından alınır. ### Otorizasyon, Ön Otorizasyon, Ön Provizyon Kapama İşlemler arasındaki farklar -- **Otorizasyon** - bildiğimiz ve genel olarak kullandığımız işlem. Tek seferde ödeme işlemi biter. -Bu işlem için kullanıcıdan hep kredi kart bilgisini _alınır_. -İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_PAY_AUTH` -- **Ön Otorizasyon** - müşteriden parayı direk çekmek yerine, işlem sonucunda para bloke edilir. -Bu işlem için kullanıcıdan hep kredi kart bilgisini _alınır_. -İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_PAY_PRE_AUTH` -- **Ön Provizyon Kapama** - ön provizyon sonucunda bloke edilen miktarın çekimini gerçekleştirir. -Ön otorizasyon yapıldıktan sonra, örneğin 1 hafta sonra, Post Otorizasyon isteği gönderilebilinir. -Bu işlem için kullanıcıdan kredi kart bilgisi _alınmaz_. -Onun yerine bazı gateway'ler `orderId` degeri istenir, -bazıları ise ön provizyon sonucu dönen banka tarafındaki `orderId`'yi ister. -Satıcı _ön otorizasyon_ isteği iptal etmek isterse de `cancel` isteği gönderir. -Post Otorizasyon İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_PAY_POST_AUTH`. -Bu işlem **sadece NonSecure** ödeme modeliyle gerçekleşir. -- `TX_TYPE_PAY_AUTH` vs `TX_TYPE_PAY_PRE_AUTH` işlemler genelde bütün ödeme modelleri -(NonSecure, 3DSecure, 3DPay ve 3DHost) tarafından desteklenir. +- **Otorizasyon** - bildiğimiz ve genel olarak kullandığımız işlem. Tek seferde + ödeme işlemi biter. + Bu işlem için kullanıcıdan hep kredi kart bilgisini _alınır_. + İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_PAY_AUTH` +- **Ön Otorizasyon** - müşteriden parayı direk çekmek yerine, işlem sonucunda + para bloke edilir. + Bu işlem için kullanıcıdan hep kredi kart bilgisini _alınır_. + İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_PAY_PRE_AUTH` +- **Ön Provizyon Kapama** - ön provizyon sonucunda bloke edilen miktarın + çekimini gerçekleştirir. + Ön otorizasyon yapıldıktan sonra, örneğin 1 hafta sonra, Post Otorizasyon + isteği gönderilebilinir. + Bu işlem için kullanıcıdan kredi kart bilgisi _alınmaz_. + Onun yerine bazı gateway'ler `orderId` degeri istenir, + bazıları ise ön provizyon sonucu dönen banka tarafındaki `orderId`'yi ister. + Satıcı _ön otorizasyon_ isteği iptal etmek isterse de `cancel` isteği + gönderir. + Post Otorizasyon İşlemin kütüphanedeki + karşılığı `PosInterface::TX_TYPE_PAY_POST_AUTH`. + Bu işlem **sadece NonSecure** ödeme modeliyle gerçekleşir. +- `TX_TYPE_PAY_AUTH` vs `TX_TYPE_PAY_PRE_AUTH` işlemler genelde bütün ödeme + modelleri + (NonSecure, 3DSecure, 3DPay ve 3DHost) tarafından desteklenir. ### Refund ve Cancel işlemler arasındaki farklar + - **Refund** - Tamamlanan ödemeyi iade etmek için kullanılır. -Bu işlem bazı gatewaylerde sadece gün kapandıktan _sonra_ yapılabilir. -İade işlemi için _miktar zorunlu_, çünkü ödenen ve iade edilen miktarı aynı olmayabilir. -İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_REFUND` + Bu işlem bazı gatewaylerde sadece gün kapandıktan _sonra_ yapılabilir. + İade işlemi için _miktar zorunlu_, çünkü ödenen ve iade edilen miktarı aynı + olmayabilir. + İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_REFUND` - **Cancel** - Tamamlanan ödemeyi iptal etmek için kullanılır. -Ödeme yapıldıktan sonra gün kapanmadan yapılabilir. Gün kapandıktan sonra `refund` işlemi kullanmak zorundasınız. -Genel olarak _miktar_ bilgisi _istenmez_, ancak bazı Gateway'ler ister. -İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_CANCEL` + Ödeme yapıldıktan sonra gün kapanmadan yapılabilir. Gün kapandıktan + sonra `refund` işlemi kullanmak zorundasınız. + Genel olarak _miktar_ bilgisi _istenmez_, ancak bazı Gateway'ler ister. + İşlemin kütüphanedeki karşılığı `PosInterface::TX_TYPE_CANCEL` ## Docker ile test ortami + 1. Makinenizde Docker kurulu olması gerekir. 2. Projenin root klasöründe `docker-compose up -d` komutu çalıştırınız. 3. ```sh $ composer require mews/pos ``` + **Note**: localhost port 80 boş olması gerekiyor. -Sorunsuz çalışması durumda kod örneklerine http://localhost/estpos/3d/index.php şekilde erişebilirsiniz. +Sorunsuz çalışması durumda kod örneklerine http://localhost/estpos/3d/index.php +şekilde erişebilirsiniz. http://localhost/ URL projenin `examples` klasörünün içine bakar. ### Unit testler çalıştırma + Projenin root klasoründe bu satırı çalıştırmanız gerekiyor + ```sh $ ./vendor/bin/phpunit ``` - > Değerli yorum, öneri ve katkılarınızı > -> Sorun bulursanız veya eklenmesi gereken POS sistemi varsa lütfen issue oluşturun. +> Sorun bulursanız veya eklenmesi gereken POS sistemi varsa lütfen issue +> oluşturun. License ---- From 16466230e88dee750c8491dc48bc5ec2adca6da0 Mon Sep 17 00:00:00 2001 From: Nuryagdy Mustapayev Date: Mon, 1 Apr 2024 16:51:47 +0200 Subject: [PATCH 3/4] issue #181 integrate vakif katilim pos --- README.md | 1 + config/pos_production.php | 9 + config/pos_test.php | 9 + docs/CANCEL-EXAMPLE.md | 5 + docs/HISTORY-EXAMPLE.md | 8 + docs/ORDER-HISTORY-EXAMPLE.md | 8 + docs/REFUND-EXAMPLE.md | 5 + examples/_common-codes/regular/cancel.php | 5 + examples/_common-codes/regular/history.php | 13 +- .../_common-codes/regular/order_history.php | 11 + examples/_common-codes/regular/refund.php | 5 + examples/_templates/_header.php | 1 + examples/vakif-katilim/3d-host/_config.php | 22 + examples/vakif-katilim/3d-host/index.php | 3 + examples/vakif-katilim/3d-host/response.php | 3 + examples/vakif-katilim/3d/_config.php | 22 + examples/vakif-katilim/3d/form.php | 3 + examples/vakif-katilim/3d/index.php | 3 + examples/vakif-katilim/3d/response.php | 3 + examples/vakif-katilim/_payment_config.php | 19 + examples/vakif-katilim/index.php | 6 + examples/vakif-katilim/regular/_config.php | 20 + examples/vakif-katilim/regular/cancel.php | 3 + examples/vakif-katilim/regular/form.php | 3 + examples/vakif-katilim/regular/history.php | 3 + examples/vakif-katilim/regular/index.php | 3 + .../vakif-katilim/regular/order_history.php | 3 + examples/vakif-katilim/regular/refund.php | 3 + examples/vakif-katilim/regular/status.php | 3 + src/Crypt/AbstractCrypt.php | 2 +- src/Crypt/CryptInterface.php | 7 + src/Crypt/GarantiPosCrypt.php | 2 +- src/Crypt/KuveytPosCrypt.php | 5 +- .../VakifKatilimPosRequestDataMapper.php | 458 +++++++ .../VakifKatilimPosResponseDataMapper.php | 576 +++++++++ src/Factory/AccountFactory.php | 5 +- src/Factory/CryptFactory.php | 22 +- src/Factory/RequestDataMapperFactory.php | 23 +- src/Factory/ResponseDataMapperFactory.php | 25 +- src/Factory/SerializerFactory.php | 4 +- src/Gateways/AbstractGateway.php | 34 +- src/Gateways/PosNetV1Pos.php | 2 +- src/Gateways/ToslaPos.php | 2 +- src/Gateways/VakifKatilimPos.php | 276 ++++ src/Serializer/VakifKatilimPosSerializer.php | 172 +++ tests/Functional/PaymentTestTrait.php | 38 +- tests/Unit/Crypt/KuveytPosCryptTest.php | 12 + .../VakifKatilimPosRequestDataMapperTest.php | 649 ++++++++++ .../VakifKatilimPosResponseDataMapperTest.php | 1133 +++++++++++++++++ tests/Unit/Factory/AccountFactoryTest.php | 36 + tests/Unit/Gateways/VakifKatilimTest.php | 888 +++++++++++++ .../VakifKatilimPosSerializerTest.php | 442 +++++++ 52 files changed, 4965 insertions(+), 53 deletions(-) create mode 100644 examples/vakif-katilim/3d-host/_config.php create mode 100644 examples/vakif-katilim/3d-host/index.php create mode 100644 examples/vakif-katilim/3d-host/response.php create mode 100644 examples/vakif-katilim/3d/_config.php create mode 100644 examples/vakif-katilim/3d/form.php create mode 100644 examples/vakif-katilim/3d/index.php create mode 100644 examples/vakif-katilim/3d/response.php create mode 100644 examples/vakif-katilim/_payment_config.php create mode 100644 examples/vakif-katilim/index.php create mode 100644 examples/vakif-katilim/regular/_config.php create mode 100644 examples/vakif-katilim/regular/cancel.php create mode 100644 examples/vakif-katilim/regular/form.php create mode 100644 examples/vakif-katilim/regular/history.php create mode 100644 examples/vakif-katilim/regular/index.php create mode 100644 examples/vakif-katilim/regular/order_history.php create mode 100644 examples/vakif-katilim/regular/refund.php create mode 100644 examples/vakif-katilim/regular/status.php create mode 100644 src/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapper.php create mode 100644 src/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapper.php create mode 100644 src/Gateways/VakifKatilimPos.php create mode 100644 src/Serializer/VakifKatilimPosSerializer.php create mode 100644 tests/Unit/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapperTest.php create mode 100644 tests/Unit/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapperTest.php create mode 100644 tests/Unit/Factory/AccountFactoryTest.php create mode 100644 tests/Unit/Gateways/VakifKatilimTest.php create mode 100644 tests/Unit/Serializer/VakifKatilimPosSerializerTest.php diff --git a/README.md b/README.md index c312350b..c852a60d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ sistemlerinin kullanılabilmesidir. | 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 | Kuveyt Türk | 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 be3e6d7b..0f67ce60 100644 --- a/config/pos_production.php +++ b/config/pos_production.php @@ -157,5 +157,14 @@ 'query_api' => 'https://boa.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', ], ], + 'vakif-katilim' => [ + 'name' => 'Vakıf Katılım', + 'class' => Mews\Pos\Gateways\VakifKatilimPos::class, + 'gateway_endpoints' => [ + 'payment_api' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home', + 'gateway_3d' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + 'gateway_3d_host' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/CommonPaymentPage/CommonPaymentPage', + ], + ], ], ]; diff --git a/config/pos_test.php b/config/pos_test.php index 490460b9..eb706c0a 100644 --- a/config/pos_test.php +++ b/config/pos_test.php @@ -105,5 +105,14 @@ 'query_api' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', ], ], + 'vakif-katilim' => [ + 'name' => 'Vakıf Katılım', + 'class' => Mews\Pos\Gateways\VakifKatilimPos::class, + 'gateway_endpoints' => [ + 'payment_api' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home', + 'gateway_3d' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + 'gateway_3d_host' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/CommonPaymentPage/CommonPaymentPage', + ], + ], ], ]; diff --git a/docs/CANCEL-EXAMPLE.md b/docs/CANCEL-EXAMPLE.md index 4d99e04a..6d0e68f1 100644 --- a/docs/CANCEL-EXAMPLE.md +++ b/docs/CANCEL-EXAMPLE.md @@ -59,6 +59,11 @@ function createCancelOrder(string $gatewayClass, array $lastResponse, string $ip $cancelOrder['auth_code'] = $lastResponse['auth_code']; $cancelOrder['transaction_id'] = $lastResponse['transaction_id']; $cancelOrder['amount'] = $lastResponse['amount']; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $cancelOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id + $cancelOrder['amount'] = $lastResponse['amount']; + // on otorizasyon islemin iptali icin PosInterface::TX_TYPE_PAY_PRE_AUTH saglanmasi gerekiyor + $cancelOrder['transaction_type'] = $lastResponse['transaction_type'] ?? PosInterface::TX_TYPE_PAY_AUTH; } elseif (\Mews\Pos\Gateways\PayFlexV4Pos::class === $gatewayClass || \Mews\Pos\Gateways\PayFlexCPV4Pos::class === $gatewayClass) { // çalışmazsa $lastResponse['all']['ReferenceTransactionId']; ile denenmesi gerekiyor. $cancelOrder['transaction_id'] = $lastResponse['transaction_id']; diff --git a/docs/HISTORY-EXAMPLE.md b/docs/HISTORY-EXAMPLE.md index 105b8681..21bae3d0 100644 --- a/docs/HISTORY-EXAMPLE.md +++ b/docs/HISTORY-EXAMPLE.md @@ -52,6 +52,14 @@ function createHistoryOrder(string $gatewayClass, array $extraData): array // odeme tarihi 'transaction_date' => $extraData['transaction_date'] ?? new \DateTimeImmutable(), ]; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $txTime = new \DateTimeImmutable(); + $order = [ + 'page' => 1, + 'page_size' => 20, + 'start_date' => $txTime->modify('-1 day'), + 'end_date' => $txTime->modify('+1 day'), + ]; } return $order; diff --git a/docs/ORDER-HISTORY-EXAMPLE.md b/docs/ORDER-HISTORY-EXAMPLE.md index bc7f7029..a94f5f28 100644 --- a/docs/ORDER-HISTORY-EXAMPLE.md +++ b/docs/ORDER-HISTORY-EXAMPLE.md @@ -67,6 +67,14 @@ function createOrderHistoryOrder(string $gatewayClass, array $lastResponse): arr 'currency' => $lastResponse['currency'], 'ip' => '127.0.0.1', ]; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + /** @var DateTimeImmutable $txTime */ + $txTime = $lastResponse['transaction_time']; + $order = [ + 'auth_code' => $lastResponse['auth_code'], + 'start_date' => $txTime->modify('-1 day'), + 'end_date' => $txTime->modify('+1 day'), + ]; } return $order; diff --git a/docs/REFUND-EXAMPLE.md b/docs/REFUND-EXAMPLE.md index afa3e8da..6ef7bbf7 100644 --- a/docs/REFUND-EXAMPLE.md +++ b/docs/REFUND-EXAMPLE.md @@ -57,6 +57,11 @@ function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip $refundOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id $refundOrder['auth_code'] = $lastResponse['auth_code']; $refundOrder['transaction_id'] = $lastResponse['transaction_id']; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $refundOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id + $refundOrder['amount'] = $lastResponse['amount']; + // on otorizasyon islemin iadesi icin PosInterface::TX_TYPE_PAY_PRE_AUTH saglanmasi gerekiyor + $refundOrder['transaction_type'] = $lastResponse['transaction_type'] ?? PosInterface::TX_TYPE_PAY_AUTH; } elseif (\Mews\Pos\Gateways\PayFlexV4Pos::class === $gatewayClass || \Mews\Pos\Gateways\PayFlexCPV4Pos::class === $gatewayClass) { // çalışmazsa $lastResponse['all']['ReferenceTransactionId']; ile denenmesi gerekiyor. $refundOrder['transaction_id'] = $lastResponse['transaction_id']; diff --git a/examples/_common-codes/regular/cancel.php b/examples/_common-codes/regular/cancel.php index 4f2646f1..bcf01023 100644 --- a/examples/_common-codes/regular/cancel.php +++ b/examples/_common-codes/regular/cancel.php @@ -25,6 +25,11 @@ function createCancelOrder(string $gatewayClass, array $lastResponse, string $ip $cancelOrder['auth_code'] = $lastResponse['auth_code']; $cancelOrder['transaction_id'] = $lastResponse['transaction_id']; $cancelOrder['amount'] = $lastResponse['amount']; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $cancelOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id + $cancelOrder['amount'] = $lastResponse['amount']; + // on otorizasyon islemin iptali icin PosInterface::TX_TYPE_PAY_PRE_AUTH saglanmasi gerekiyor + $cancelOrder['transaction_type'] = $lastResponse['transaction_type'] ?? PosInterface::TX_TYPE_PAY_AUTH; } elseif (\Mews\Pos\Gateways\PayFlexV4Pos::class === $gatewayClass || \Mews\Pos\Gateways\PayFlexCPV4Pos::class === $gatewayClass) { // çalışmazsa $lastResponse['all']['ReferenceTransactionId']; ile denenmesi gerekiyor. $cancelOrder['transaction_id'] = $lastResponse['transaction_id']; diff --git a/examples/_common-codes/regular/history.php b/examples/_common-codes/regular/history.php index 47df207e..f586becd 100644 --- a/examples/_common-codes/regular/history.php +++ b/examples/_common-codes/regular/history.php @@ -17,7 +17,18 @@ function createHistoryOrder(string $gatewayClass, array $extraData): array if (PayForPos::class === $gatewayClass) { $order = [ // odeme tarihi - 'transaction_date' => $extraData['transaction_date'] ?? new \DateTimeImmutable(), + 'transaction_date' => $extraData['transaction_date'] ?? new \DateTimeImmutable(), + ]; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $txTime = new \DateTimeImmutable(); + $order = [ + 'page' => 1, + 'page_size' => 20, + /** + * Tarih aralığı maksimum 90 gün olabilir. + */ + 'start_date' => $txTime->modify('-1 day'), + 'end_date' => $txTime->modify('+1 day'), ]; } diff --git a/examples/_common-codes/regular/order_history.php b/examples/_common-codes/regular/order_history.php index b53a2481..538e77b0 100644 --- a/examples/_common-codes/regular/order_history.php +++ b/examples/_common-codes/regular/order_history.php @@ -39,6 +39,17 @@ function createOrderHistoryOrder(string $gatewayClass, array $lastResponse): arr 'currency' => $lastResponse['currency'], 'ip' => '127.0.0.1', ]; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + /** @var DateTimeImmutable $txTime */ + $txTime = $lastResponse['transaction_time']; + $order = [ + 'auth_code' => $lastResponse['auth_code'], + /** + * Tarih aralığı maksimum 90 gün olabilir. + */ + 'start_date' => $txTime->modify('-1 day'), + 'end_date' => $txTime->modify('+1 day'), + ]; } return $order; diff --git a/examples/_common-codes/regular/refund.php b/examples/_common-codes/regular/refund.php index f935f5fd..c1e70b00 100644 --- a/examples/_common-codes/regular/refund.php +++ b/examples/_common-codes/regular/refund.php @@ -24,6 +24,11 @@ function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip $refundOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id $refundOrder['auth_code'] = $lastResponse['auth_code']; $refundOrder['transaction_id'] = $lastResponse['transaction_id']; + } elseif (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $refundOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id + $refundOrder['amount'] = $lastResponse['amount']; + // on otorizasyon islemin iadesi icin PosInterface::TX_TYPE_PAY_PRE_AUTH saglanmasi gerekiyor + $refundOrder['transaction_type'] = $lastResponse['transaction_type'] ?? PosInterface::TX_TYPE_PAY_AUTH; } elseif (\Mews\Pos\Gateways\PayFlexV4Pos::class === $gatewayClass || \Mews\Pos\Gateways\PayFlexCPV4Pos::class === $gatewayClass) { // çalışmazsa $lastResponse['all']['ReferenceTransactionId']; ile denenmesi gerekiyor. $refundOrder['transaction_id'] = $lastResponse['transaction_id']; diff --git a/examples/_templates/_header.php b/examples/_templates/_header.php index 40e07150..a4989a6a 100644 --- a/examples/_templates/_header.php +++ b/examples/_templates/_header.php @@ -38,6 +38,7 @@
  • PosNet (YKB)
  • PosNetV1 (Albaraka)
  • KuveytPOS
  • +
  • VakifKatilimPos
  • diff --git a/examples/vakif-katilim/3d-host/_config.php b/examples/vakif-katilim/3d-host/_config.php new file mode 100644 index 00000000..453ddf25 --- /dev/null +++ b/examples/vakif-katilim/3d-host/_config.php @@ -0,0 +1,22 @@ +get('tx', PosInterface::TX_TYPE_PAY_AUTH); + +$templateTitle = '3D Model Payment'; +$paymentModel = \Mews\Pos\PosInterface::MODEL_3D_SECURE; diff --git a/examples/vakif-katilim/3d/form.php b/examples/vakif-katilim/3d/form.php new file mode 100644 index 00000000..361d8083 --- /dev/null +++ b/examples/vakif-katilim/3d/form.php @@ -0,0 +1,3 @@ + [ + 'number' => '4155650100416111', + 'year' => '25', + 'month' => '1', + 'cvv' => '123', + 'name' => 'John Doe', + 'type' => CreditCardInterface::CARD_TYPE_VISA, + ], +]; diff --git a/examples/vakif-katilim/index.php b/examples/vakif-katilim/index.php new file mode 100644 index 00000000..a17e61bb --- /dev/null +++ b/examples/vakif-katilim/index.php @@ -0,0 +1,6 @@ +hashStringUpperCase($str, self::HASH_ALGORITHM); } diff --git a/src/Crypt/KuveytPosCrypt.php b/src/Crypt/KuveytPosCrypt.php index 3ef0204d..9e05e74f 100644 --- a/src/Crypt/KuveytPosCrypt.php +++ b/src/Crypt/KuveytPosCrypt.php @@ -56,8 +56,9 @@ public function createHash(AbstractPosAccount $posAccount, array $requestData): $hashData = [ $posAccount->getClientId(), - $requestData['MerchantOrderId'], - $requestData['Amount'], + // non-payment request may not have MerchantOrderId and Amount fields + $requestData['MerchantOrderId'] ?? '', + $requestData['Amount'] ?? '', $posAccount->getUsername(), $hashedPassword, ]; diff --git a/src/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapper.php b/src/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapper.php new file mode 100644 index 00000000..606025f5 --- /dev/null +++ b/src/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapper.php @@ -0,0 +1,458 @@ + '3', + PosInterface::MODEL_NON_SECURE => '5', + ]; + + /** + * {@inheritDoc} + */ + protected array $txTypeMappings = [ + PosInterface::TX_TYPE_PAY_AUTH => '1', + ]; + + /** + * Currency mapping + * + * {@inheritdoc} + */ + protected array $currencyMappings = [ + PosInterface::CURRENCY_TRY => '0949', + PosInterface::CURRENCY_USD => '0840', + PosInterface::CURRENCY_EUR => '0978', + PosInterface::CURRENCY_GBP => '0826', + PosInterface::CURRENCY_JPY => '0392', + PosInterface::CURRENCY_RUB => '0810', + ]; + + /** @var KuveytPosCrypt */ + protected CryptInterface $crypt; + + /** + * @param KuveytPosAccount $posAccount + * + * {@inheritDoc} + * @return array + */ + public function create3DPaymentRequestData(AbstractPosAccount $posAccount, array $order, string $txType, array $responseData): array + { + $order = $this->preparePaymentOrder($order); + + $result = $this->getRequestAccountData($posAccount) + [ + 'OkUrl' => $order['success_url'], + 'FailUrl' => $order['fail_url'], + 'HashData' => '', + 'APIVersion' => self::API_VERSION, + 'AdditionalData' => [ + 'AdditionalDataList' => [ + 'VPosAdditionalData' => [ + 'Key' => 'MD', + 'Data' => $responseData['MD'], + ], + ], + ], + 'InstallmentCount' => $this->mapInstallment($order['installment']), + 'Amount' => $this->formatAmount($order['amount']), + 'MerchantOrderId' => $responseData['MerchantOrderId'], + 'TransactionSecurity' => $this->secureTypeMappings[PosInterface::MODEL_3D_SECURE], + ]; + + $result['HashData'] = $this->crypt->create3DHash($posAccount, $result); + + return $result; + } + + /** + * @phpstan-param PosInterface::MODEL_3D_* $paymentModel + * @phpstan-param PosInterface::TX_TYPE_PAY_AUTH|PosInterface::TX_TYPE_PAY_PRE_AUTH $txType + * + * @param KuveytPosAccount $kuveytPosAccount + * @param array $order + * @param string $paymentModel + * @param string $txType + * @param CreditCardInterface|null $creditCard + * + * @return array + */ + public function create3DEnrollmentCheckRequestData(KuveytPosAccount $kuveytPosAccount, array $order, string $paymentModel, string $txType, ?CreditCardInterface $creditCard = null): array + { + $order = $this->preparePaymentOrder($order); + + $inputs = $this->getRequestAccountData($kuveytPosAccount) + [ + 'APIVersion' => self::API_VERSION, + 'HashPassword' => $this->crypt->hashString($kuveytPosAccount->getStoreKey() ?? ''), + 'TransactionSecurity' => $this->secureTypeMappings[$paymentModel], + 'InstallmentCount' => $this->mapInstallment($order['installment']), + 'Amount' => $this->formatAmount($order['amount']), + 'FECCurrencyCode' => $this->mapCurrency($order['currency']), + 'MerchantOrderId' => $order['id'], + 'OkUrl' => $order['success_url'], + 'FailUrl' => $order['fail_url'], + ]; + + if ($creditCard instanceof CreditCardInterface) { + $inputs['CardHolderName'] = $creditCard->getHolderName(); + $inputs['CardNumber'] = $creditCard->getNumber(); + $inputs['CardExpireDateYear'] = $creditCard->getExpireYear(self::CREDIT_CARD_EXP_YEAR_FORMAT); + $inputs['CardExpireDateMonth'] = $creditCard->getExpireMonth(self::CREDIT_CARD_EXP_MONTH_FORMAT); + $inputs['CardCVV2'] = $creditCard->getCvv(); + } + + $inputs['HashData'] = $this->crypt->create3DHash($kuveytPosAccount, $inputs); + + return $inputs; + } + + /** + * @phpstan-param KuveytPosAccount $posAccount + * + * {@inheritDoc} + */ + public function createNonSecurePostAuthPaymentRequestData(AbstractPosAccount $posAccount, array $order): array + { + $order = $this->preparePostPaymentOrder($order); + + $inputs = $this->getRequestAccountData($posAccount) + [ + 'HashPassword' => $this->crypt->hashString($posAccount->getStoreKey() ?? ''), + 'MerchantOrderId' => $order['id'], + 'OrderId' => $order['remote_order_id'], + 'CustomerIPAddress' => $order['ip'], + ]; + + $inputs['HashData'] = $this->crypt->createHash($posAccount, $inputs); + + return $inputs; + } + + /** + * @phpstan-param KuveytPosAccount $posAccount + * + * {@inheritDoc} + */ + public function createNonSecurePaymentRequestData(AbstractPosAccount $posAccount, array $order, string $txType, CreditCardInterface $creditCard): array + { + $order = $this->preparePaymentOrder($order); + + $inputs = $this->getRequestAccountData($posAccount) + [ + 'APIVersion' => self::API_VERSION, + 'HashPassword' => $this->crypt->hashString($posAccount->getStoreKey() ?? ''), + 'MerchantOrderId' => $order['id'], + 'InstallmentCount' => $this->mapInstallment($order['installment']), + 'Amount' => $this->formatAmount($order['amount']), + 'FECCurrencyCode' => $this->mapCurrency($order['currency']), + 'CurrencyCode' => $this->mapCurrency($order['currency']), + 'TransactionSecurity' => $this->secureTypeMappings[PosInterface::MODEL_NON_SECURE], + 'CardNumber' => $creditCard->getNumber(), + 'CardExpireDateYear' => $creditCard->getExpireYear(self::CREDIT_CARD_EXP_YEAR_FORMAT), + 'CardExpireDateMonth' => $creditCard->getExpireMonth(self::CREDIT_CARD_EXP_MONTH_FORMAT), + 'CardCVV2' => $creditCard->getCvv(), + 'CardHolderName' => $creditCard->getHolderName(), + ]; + + $inputs['HashData'] = $this->crypt->createHash($posAccount, $inputs); + + return $inputs; + } + + /** + * @param KuveytPosAccount $posAccount + * {@inheritDoc} + */ + public function createStatusRequestData(AbstractPosAccount $posAccount, array $order): array + { + $order = $this->prepareStatusOrder($order); + + $result = $this->getRequestAccountData($posAccount) + [ + 'MerchantOrderId' => $order['id'], + ]; + + $result['HashData'] = $this->crypt->createHash($posAccount, $result); + + return $result; + } + + /** + * @param KuveytPosAccount $posAccount + * {@inheritDoc} + */ + public function createCancelRequestData(AbstractPosAccount $posAccount, array $order): array + { + $order = $this->prepareCancelOrder($order); + + $result = $this->getRequestAccountData($posAccount) + [ + 'HashPassword' => $this->crypt->hashString($posAccount->getStoreKey() ?? ''), + 'MerchantOrderId' => $order['id'], + 'OrderId' => $order['remote_order_id'], + 'PaymentType' => '1', + ]; + + if (!isset($order['transaction_type']) || PosInterface::TX_TYPE_PAY_PRE_AUTH !== $order['transaction_type']) { + $result['Amount'] = $this->formatAmount($order['amount']); + } + + $result['HashData'] = $this->crypt->createHash($posAccount, $result); + + return $result; + } + + /** + * @param KuveytPosAccount $posAccount + * {@inheritDoc} + */ + public function createRefundRequestData(AbstractPosAccount $posAccount, array $order): array + { + $order = $this->prepareRefundOrder($order); + + $result = $this->getRequestAccountData($posAccount) + [ + 'HashPassword' => $this->crypt->hashString($posAccount->getStoreKey() ?? ''), + 'MerchantOrderId' => $order['id'], + 'OrderId' => $order['remote_order_id'], + ]; + + $result['HashData'] = $this->crypt->createHash($posAccount, $result); + + return $result; + } + + /** + * {@inheritDoc} + * + * @param array $order Vakif Katilim bank'tan donen HTML cevaptan parse edilen form inputlar yada + * 3D Host odemede siparis bilgileri + * + * @return array{gateway: string, method: 'POST', inputs: array} + */ + public function create3DFormData(AbstractPosAccount $posAccount, array $order, string $paymentModel, string $txType, string $gatewayURL, ?CreditCardInterface $creditCard = null): array + { + $inputs = $order; + if (PosInterface::MODEL_3D_HOST === $paymentModel) { + $order = $this->preparePaymentOrder($order); + + $inputs = [ + 'UserName' => $posAccount->getUsername(), + 'HashPassword' => $this->crypt->hashString($posAccount->getStoreKey() ?? ''), + 'MerchantId' => $posAccount->getClientId(), + 'MerchantOrderId' => (string) $order['id'], + 'Amount' => $this->formatAmount($order['amount']), + 'FECCurrencyCode' => $this->mapCurrency($order['currency']), + 'OkUrl' => $order['success_url'], + 'FailUrl' => $order['fail_url'], + 'PaymentType' => '1', + ]; + } + + return [ + 'gateway' => $gatewayURL, + 'method' => 'POST', + 'inputs' => $inputs, + ]; + } + + /** + * @phpstan-param KuveytPosAccount $posAccount + * + * {@inheritDoc} + */ + public function createHistoryRequestData(AbstractPosAccount $posAccount, array $data = []): array + { + $data = $this->prepareHistoryOrder($data); + + $result = $this->getRequestAccountData($posAccount) + [ + /** + * Tarih aralığı maksimum 90 gün olabilir. + */ + 'StartDate' => $data['start_date']->format('Y-m-d'), + 'EndDate' => $data['end_date']->format('Y-m-d'), + 'LowerLimit' => ($data['page'] - 1) * $data['page_size'], + 'UpperLimit' => $data['page_size'], + 'ProvNumber' => null, + 'OrderStatus' => null, + 'TranResult' => null, + 'OrderNo' => null, + ]; + + $result['HashData'] = $this->crypt->createHash($posAccount, $result); + + return $result; + } + + /** + * @phpstan-param KuveytPosAccount $posAccount + * + * {@inheritDoc} + */ + public function createOrderHistoryRequestData(AbstractPosAccount $posAccount, array $order): array + { + $order = $this->prepareOrderHistoryOrder($order); + + $result = $this->getRequestAccountData($posAccount) + [ + 'StartDate' => $order['start_date']->format('Y-m-d'), + 'EndDate' => $order['end_date']->format('Y-m-d'), + 'LowerLimit' => 0, + 'UpperLimit' => 100, + 'ProvNumber' => $order['auth_code'], + 'OrderStatus' => null, + 'TranResult' => null, + 'OrderNo' => null, + ]; + + $result['HashData'] = $this->crypt->createHash($posAccount, $result); + + return $result; + } + + /** + * Amount Formatter + * converts 100 to 10000, or 10.01 to 1001 + * + * @param float $amount + * + * @return int + */ + protected function formatAmount(float $amount): int + { + return (int) (\round($amount, 2) * 100); + } + + /** + * 0 => '0' + * 1 => '0' + * 2 => '2' + * @inheritDoc + */ + protected function mapInstallment(int $installment): string + { + return $installment > 1 ? (string) $installment : '0'; + } + + /** + * @inheritDoc + */ + protected function preparePaymentOrder(array $order): array + { + return \array_merge($order, [ + 'installment' => $order['installment'] ?? 0, + 'currency' => $order['currency'] ?? PosInterface::CURRENCY_TRY, + ]); + } + + /** + * @inheritDoc + */ + protected function preparePostPaymentOrder(array $order): array + { + return \array_merge($order, [ + 'id' => $order['id'], + 'remote_order_id' => $order['remote_order_id'], + 'ip' => $order['ip'], + ]); + } + + /** + * @inheritDoc + */ + protected function prepareStatusOrder(array $order): array + { + return \array_merge($order, [ + 'id' => $order['id'], + ]); + } + + /** + * @inheritDoc + */ + protected function prepareCancelOrder(array $order): array + { + return \array_merge($order, [ + 'id' => $order['id'], + 'remote_order_id' => $order['remote_order_id'], + 'amount' => $order['amount'], + ]); + } + + /** + * @inheritDoc + */ + protected function prepareRefundOrder(array $order): array + { + return \array_merge($order, [ + 'id' => $order['id'], + 'remote_order_id' => $order['remote_order_id'], + 'amount' => $order['amount'], + ]); + } + + /** + * @return array{start_date: \DateTimeInterface, end_date: \DateTimeInterface, page: int, page_size: int} + * + * @inheritDoc + */ + protected function prepareHistoryOrder(array $order): array + { + return [ + 'start_date' => $order['start_date'], + 'end_date' => $order['end_date'], + 'page' => $order['page'] ?? 1, + 'page_size' => $order['page_size'] ?? 10, + ]; + } + + /** + * @return array{start_date: \DateTimeInterface, end_date: \DateTimeInterface, auth_code: string} + * + * @inheritDoc + */ + protected function prepareOrderHistoryOrder(array $order): array + { + return [ + 'start_date' => $order['start_date'], + 'end_date' => $order['end_date'], + 'auth_code' => $order['auth_code'], + ]; + } + + /** + * @param KuveytPosAccount $posAccount + * + * @return array{MerchantId: string, CustomerId: string, UserName: string, SubMerchantId: string} + */ + private function getRequestAccountData(AbstractPosAccount $posAccount): array + { + return [ + 'MerchantId' => $posAccount->getClientId(), + 'CustomerId' => $posAccount->getCustomerId(), + 'UserName' => $posAccount->getUsername(), + 'SubMerchantId' => $posAccount->getSubMerchantId() ?? '0', + ]; + } +} diff --git a/src/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapper.php b/src/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapper.php new file mode 100644 index 00000000..243de31f --- /dev/null +++ b/src/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapper.php @@ -0,0 +1,576 @@ + + */ + protected array $codes = [ + self::PROCEDURE_SUCCESS_CODE => self::TX_APPROVED, + 'CardNotEnrolled' => 'reject', + '51' => 'reject', + ]; + + /** + * {@inheritDoc} + */ + public function mapPaymentResponse(array $rawPaymentResponseData, string $txType, array $order): array + { + $this->logger->debug('mapping payment response', [$rawPaymentResponseData]); + + $rawPaymentResponseData = $this->emptyStringsToNull($rawPaymentResponseData); + $result = $this->getDefaultPaymentResponse($txType, PosInterface::MODEL_NON_SECURE); + if ([] === $rawPaymentResponseData) { + return $result; + } + + $status = self::TX_DECLINED; + $procReturnCode = $this->getProcReturnCode($rawPaymentResponseData); + + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $result['proc_return_code'] = $procReturnCode; + $result['status'] = $status; + $result['status_detail'] = $this->getStatusDetail($procReturnCode); + $result['order_id'] = $rawPaymentResponseData['MerchantOrderId']; + $result['remote_order_id'] = $rawPaymentResponseData['OrderId']; + $result['all'] = $rawPaymentResponseData; + + if (self::TX_APPROVED !== $status) { + $result['error_code'] = $procReturnCode; + $result['error_message'] = $rawPaymentResponseData['ResponseMessage']; + $this->logger->debug('mapped payment response', $result); + + return $result; + } + + /** @var array $vPosMessage */ + $vPosMessage = $rawPaymentResponseData['VPosMessage']; + + // ProvisionNumber: Başarılı işlemlerde kart bankasının vermiş olduğu otorizasyon numarasıdır. + $result['auth_code'] = $rawPaymentResponseData['ProvisionNumber']; + // RRN: Pos bankası tarafında verilen referans işlem referans numarasıdır. + $result['ref_ret_num'] = $rawPaymentResponseData['RRN']; + // Stan: Pos bankası tarafında verilen referans işlem referans numarasıdır. + $result['transaction_id'] = $rawPaymentResponseData['Stan']; + $result['masked_number'] = $vPosMessage['CardNumber']; + $result['amount'] = $this->formatAmount($vPosMessage['Amount']); + $result['currency'] = $this->mapCurrency($vPosMessage['CurrencyCode']); + $result['installment_count'] = $this->mapInstallment($vPosMessage['InstallmentCount']); + if ('0001-01-01T00:00:00' !== $rawPaymentResponseData['TransactionTime'] && '00010101T00:00:00' !== $rawPaymentResponseData['TransactionTime']) { + $result['transaction_time'] = new \DateTimeImmutable($rawPaymentResponseData['TransactionTime']); + } else { + $result['transaction_time'] = new \DateTimeImmutable(); + } + + + $this->logger->debug('mapped payment response', $result); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function map3DPaymentData(array $raw3DAuthResponseData, ?array $rawPaymentResponseData, string $txType, array $order): array + { + $this->logger->debug('mapping 3D payment data', [ + '3d_auth_response' => $raw3DAuthResponseData, + 'provision_response' => $rawPaymentResponseData, + ]); + $threeDResponse = $this->map3DCommonResponseData($raw3DAuthResponseData); + /** @var PosInterface::TX_TYPE_PAY_AUTH|PosInterface::TX_TYPE_PAY_PRE_AUTH $txType */ + $txType = $threeDResponse['transaction_type'] ?? $txType; + if (null === $rawPaymentResponseData || [] === $rawPaymentResponseData) { + /** @var PosInterface::MODEL_3D_* $paymentModel */ + $paymentModel = $threeDResponse['payment_model']; + + return $this->mergeArraysPreferNonNullValues( + $this->getDefaultPaymentResponse($txType, $paymentModel), + $threeDResponse + ); + } + + $paymentResponseData = $this->map3DPaymentPaymentResponse($rawPaymentResponseData, $txType, $order); + + $paymentResponseData['payment_model'] = $threeDResponse['payment_model']; + + return $this->mergeArraysPreferNonNullValues($threeDResponse, $paymentResponseData); + } + + /** + * {@inheritdoc} + */ + public function map3DPayResponseData(array $raw3DAuthResponseData, string $txType, array $order): array + { + throw new NotImplementedException(); + } + + /** + * {@inheritdoc} + */ + public function map3DHostResponseData(array $raw3DAuthResponseData, string $txType, array $order): array + { + $this->logger->debug('mapping 3D payment data', [ + '3d_auth_response' => $raw3DAuthResponseData, + ]); + + $raw3DAuthResponseData = $this->emptyStringsToNull($raw3DAuthResponseData); + + $defaultResponse = $this->getDefaultPaymentResponse($txType, PosInterface::MODEL_3D_HOST); + + $status = self::TX_DECLINED; + $procReturnCode = $this->getProcReturnCode($raw3DAuthResponseData); + + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $defaultResponse['md_status'] = null; + $defaultResponse['md_error_message'] = null; + $defaultResponse['transaction_security'] = null; + $defaultResponse['proc_return_code'] = $procReturnCode; + $defaultResponse['status'] = $status; + $defaultResponse['status_detail'] = $this->getStatusDetail($procReturnCode); + $defaultResponse['all'] = $raw3DAuthResponseData; + + if (self::TX_APPROVED !== $status) { + $defaultResponse['error_code'] = $procReturnCode; + $defaultResponse['error_message'] = $raw3DAuthResponseData['ResponseMessage']; + $this->logger->debug('mapped payment response', $defaultResponse); + + return $defaultResponse; + } + + // ProvisionNumber: Başarılı işlemlerde kart bankasının vermiş olduğu otorizasyon numarasıdır. + $defaultResponse['order_id'] = $raw3DAuthResponseData['MerchantOrderId']; + $defaultResponse['remote_order_id'] = $raw3DAuthResponseData['OrderId']; + // RRN: Pos bankası tarafında verilen referans işlem referans numarasıdır. + $defaultResponse['ref_ret_num'] = $raw3DAuthResponseData['RRN']; + // Stan: Pos bankası tarafında verilen referans işlem referans numarasıdır. + $defaultResponse['transaction_id'] = $raw3DAuthResponseData['Stan']; + $defaultResponse['auth_code'] = $raw3DAuthResponseData['ProvisionNumber'] ?? null; + $defaultResponse['transaction_time'] = new \DateTimeImmutable(); + + return $defaultResponse; + } + + /** + * {@inheritdoc} + */ + public function mapStatusResponse(array $rawResponseData): array + { + $rawResponseData = $this->emptyStringsToNull($rawResponseData); + $defaultResponse = $this->getDefaultStatusResponse($rawResponseData); + $procReturnCode = $this->getProcReturnCode($rawResponseData); + + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $mappedTransactions = $this->mapSingleHistoryTransaction($rawResponseData['VPosOrderData']['OrderContract']); + + return \array_merge($defaultResponse, $mappedTransactions); + } + + $defaultResponse['proc_return_code'] = $procReturnCode; + $defaultResponse['error_code'] = $procReturnCode; + $defaultResponse['error_message'] = $rawResponseData['ResponseMessage']; + $defaultResponse['order_id'] = $rawResponseData['MerchantOrderId']; + + return $defaultResponse; + } + + /** + * @inheritDoc + */ + public function mapRefundResponse(array $rawResponseData): array + { + return $this->mapCancelResponse($rawResponseData); + } + + /** + * @inheritDoc + */ + public function mapCancelResponse(array $rawResponseData): array + { + $rawResponseData = $this->emptyStringsToNull($rawResponseData); + $status = self::TX_DECLINED; + + $result = [ + 'order_id' => null, + 'auth_code' => null, + 'proc_return_code' => null, + 'transaction_id' => null, + 'currency' => null, + 'error_message' => null, + 'ref_ret_num' => null, + 'status' => $status, + 'error_code' => null, + 'status_detail' => null, + 'all' => $rawResponseData, + ]; + + $vposMessage = $rawResponseData['VPosMessage']; + $procReturnCode = $this->getProcReturnCode($rawResponseData); + + if (null === $procReturnCode) { + return $result; + } + + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $result['proc_return_code'] = $procReturnCode; + $result['order_id'] = $vposMessage['MerchantOrderId']; + $result['remote_order_id'] = (string) $rawResponseData['OrderId']; + $result['status'] = $status; + $result['currency'] = $this->mapCurrency($vposMessage['FECCurrencyCode']); + + if (self::TX_APPROVED !== $status) { + $result['error_code'] = $procReturnCode; + $result['error_message'] = $rawResponseData['ResponseMessage']; + } else { + $result['transaction_id'] = $rawResponseData['Stan']; + $result['ref_ret_num'] = $rawResponseData['RRN']; + } + + return $result; + } + + + /** + * {@inheritDoc} + */ + public function mapHistoryResponse(array $rawResponseData): array + { + $rawResponseData = $this->emptyStringsToNull($rawResponseData); + + $mappedTransactions = []; + $procReturnCode = $this->getProcReturnCode($rawResponseData); + $status = self::TX_DECLINED; + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + if (isset($rawResponseData['VPosOrderData']['OrderContract'])) { + foreach ($rawResponseData['VPosOrderData']['OrderContract'] as $tx) { + $mappedTransactions[] = $this->mapSingleHistoryTransaction($tx); + } + } + + $mappedTransactions = \array_reverse($mappedTransactions); + + $result = [ + 'proc_return_code' => $procReturnCode, + 'error_code' => null, + 'error_message' => null, + 'status' => $status, + 'status_detail' => null !== $procReturnCode ? $this->getStatusDetail($procReturnCode) : null, + 'trans_count' => \count($mappedTransactions), + 'transactions' => $mappedTransactions, + 'all' => $rawResponseData, + ]; + + if (null !== $procReturnCode && self::PROCEDURE_SUCCESS_CODE !== $procReturnCode) { + $result['error_code'] = $procReturnCode; + $result['error_message'] = $rawResponseData['ResponseMessage']; + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function mapOrderHistoryResponse(array $rawResponseData): array + { + $rawResponseData = $this->emptyStringsToNull($rawResponseData); + + $mappedTransactions = []; + $procReturnCode = $this->getProcReturnCode($rawResponseData); + $status = self::TX_DECLINED; + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $orderId = null; + $remoteOrderId = null; + if (isset($rawResponseData['VPosOrderData']['OrderContract'])) { + foreach ($rawResponseData['VPosOrderData']['OrderContract'] as $tx) { + $mappedTransactions[] = $this->mapSingleOrderHistoryTransaction($tx); + $orderId = $tx['MerchantOrderId']; + $remoteOrderId = $tx['OrderId']; + } + } + + $mappedTransactions = \array_reverse($mappedTransactions); + + $result = [ + 'proc_return_code' => $procReturnCode, + 'order_id' => $orderId, + 'remote_order_id' => $remoteOrderId, + 'error_code' => null, + 'error_message' => null, + 'status' => $status, + 'status_detail' => null !== $procReturnCode ? $this->getStatusDetail($procReturnCode) : null, + 'trans_count' => \count($mappedTransactions), + 'transactions' => $mappedTransactions, + 'all' => $rawResponseData, + ]; + + if (null !== $procReturnCode && self::PROCEDURE_SUCCESS_CODE !== $procReturnCode) { + $result['error_code'] = $procReturnCode; + $result['error_message'] = $rawResponseData['ResponseMessage']; + } + + return $result; + } + + /** + * @inheritDoc + */ + public function is3dAuthSuccess(?string $mdStatus): bool + { + return self::PROCEDURE_SUCCESS_CODE === $mdStatus; + } + + /** + * @inheritDoc + */ + public function extractMdStatus(array $raw3DAuthResponseData): ?string + { + return $this->getProcReturnCode($raw3DAuthResponseData); + } + + /** + * "101" => 1.01 + * @param string $amount + * + * @return float + */ + protected function formatAmount(string $amount): float + { + return (float) $amount / 100; + } + + /** + * Get ProcReturnCode + * + * @param array $response + * + * @return string|null + */ + protected function getProcReturnCode(array $response): ?string + { + return $response['ResponseCode'] ?? null; + } + + /** + * @param string $currency currency code that is accepted by bank + * + * @return PosInterface::CURRENCY_*|string + */ + protected function mapCurrency(string $currency): string + { + // 949 => 0949; for the request gateway wants 0949 code, but in response they send 949 code. + $currencyNormalized = \str_pad($currency, 4, '0', STR_PAD_LEFT); + + return parent::mapCurrency($currencyNormalized); + } + + /** + * Get Status Detail Text + * + * @param string|null $procReturnCode + * + * @return string|null + */ + protected function getStatusDetail(?string $procReturnCode): ?string + { + return $this->codes[$procReturnCode] ?? $procReturnCode; + } + + /** + * @param string $mdStatus + * + * @return string + */ + protected function mapResponseTransactionSecurity(string $mdStatus): string + { + return 'MPI fallback'; + } + + /** + * returns mapped data of the common response data among all 3d models. + * + * @param array $raw3DAuthResponseData + * + * @return array + */ + protected function map3DCommonResponseData(array $raw3DAuthResponseData): array + { + $raw3DAuthResponseData = $this->emptyStringsToNull($raw3DAuthResponseData); + $procReturnCode = $this->getProcReturnCode($raw3DAuthResponseData); + $status = self::TX_DECLINED; + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $orderId = $raw3DAuthResponseData['MerchantOrderId']; + + return [ + 'order_id' => $orderId, + 'transaction_security' => null, + 'transaction_type' => null, + 'proc_return_code' => $procReturnCode, + 'md_status' => null, + 'payment_model' => PosInterface::MODEL_3D_SECURE, + 'status' => $status, + 'status_detail' => $this->getStatusDetail($procReturnCode), + 'amount' => null, + 'currency' => null, + 'tx_status' => null, + 'error_code' => self::TX_APPROVED !== $status ? $procReturnCode : null, + 'md_error_message' => self::TX_APPROVED !== $status ? $raw3DAuthResponseData['ResponseMessage'] : null, + '3d_all' => $raw3DAuthResponseData, + ]; + } + + /** + * @param array $rawTx + * + * @return array + * + * @throws \Exception + */ + private function mapSingleHistoryTransaction(array $rawTx): array + { + $response = $this->mapSingleOrderHistoryTransaction($rawTx); + $response['order_id'] = $rawTx['MerchantOrderId']; + $response['remote_order_id'] = $rawTx['OrderId']; + + return $response; + } + + + /** + * @param array $rawTx + * + * @return array + * + * @throws \Exception + */ + private function mapSingleOrderHistoryTransaction(array $rawTx): array + { + $procReturnCode = $this->getProcReturnCode($rawTx); + $status = self::TX_DECLINED; + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $defaultResponse = $this->getDefaultOrderHistoryTxResponse(); + + $defaultResponse['proc_return_code'] = $procReturnCode; + $defaultResponse['status'] = $status; + $defaultResponse['status_detail'] = $this->getStatusDetail($procReturnCode); + $defaultResponse['error_code'] = self::TX_APPROVED === $status ? null : $procReturnCode; + $defaultResponse['error_message'] = self::TX_APPROVED === $status ? null : $rawTx['ResponseExplain']; + $defaultResponse['currency'] = null !== $rawTx['FEC'] ? $this->mapCurrency($rawTx['FEC']) : null; + $defaultResponse['payment_model'] = null !== $rawTx['TransactionSecurity'] ? $this->mapSecurityType($rawTx['TransactionSecurity']) : null; + $defaultResponse['ref_ret_num'] = $rawTx['RRN']; + $defaultResponse['transaction_id'] = $rawTx['Stan']; + $defaultResponse['transaction_time'] = null !== $rawTx['OrderDate'] ? new \DateTimeImmutable($rawTx['OrderDate']) : null; + + if (self::TX_APPROVED === $status) { + $defaultResponse['auth_code'] = $rawTx['ProvNumber'] ?? null; + $defaultResponse['installment_count'] = $this->mapInstallment($rawTx['InstallmentCount']); + $defaultResponse['masked_number'] = $rawTx['CardNumber']; + $defaultResponse['first_amount'] = (float) $rawTx['FirstAmount']; + $defaultResponse['capture_amount'] = isset($rawTx['TranAmount']) ? (float) $rawTx['TranAmount'] : 0; + $defaultResponse['capture'] = $defaultResponse['first_amount'] === $defaultResponse['capture_amount']; + $defaultResponse['order_status'] = $rawTx['LastOrderStatusDescription']; + + if ($defaultResponse['capture']) { + $defaultResponse['capture_time'] = $defaultResponse['transaction_time']; + } + } + + return $defaultResponse; + } + + /** + * @phpstan-param PosInterface::TX_TYPE_PAY_* $txType + * + * @param array $rawPaymentResponseData + * @param string $txType + * @param array $order + * + * @return array + */ + private function map3DPaymentPaymentResponse(array $rawPaymentResponseData, string $txType, array $order): array + { + $this->logger->debug('mapping payment response', [$rawPaymentResponseData]); + + $rawPaymentResponseData = $this->emptyStringsToNull($rawPaymentResponseData); + $result = $this->getDefaultPaymentResponse($txType, PosInterface::MODEL_NON_SECURE); + if ([] === $rawPaymentResponseData) { + return $result; + } + + $status = self::TX_DECLINED; + $procReturnCode = $this->getProcReturnCode($rawPaymentResponseData); + + if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) { + $status = self::TX_APPROVED; + } + + $result['proc_return_code'] = $procReturnCode; + $result['status'] = $status; + $result['status_detail'] = $this->getStatusDetail($procReturnCode); + $result['all'] = $rawPaymentResponseData; + + if (self::TX_APPROVED !== $status) { + $result['error_code'] = $procReturnCode; + $result['error_message'] = $rawPaymentResponseData['ResponseMessage']; + $this->logger->debug('mapped payment response', $result); + + return $result; + } + + /** @var array $vPosMessage */ + $vPosMessage = $rawPaymentResponseData['VPosMessageContract']; + + // ProvisionNumber: Başarılı işlemlerde kart bankasının vermiş olduğu otorizasyon numarasıdır. + $result['order_id'] = $rawPaymentResponseData['MerchantOrderId']; + $result['remote_order_id'] = $rawPaymentResponseData['OrderId']; + // RRN: Pos bankası tarafında verilen referans işlem referans numarasıdır. + $result['ref_ret_num'] = $rawPaymentResponseData['RRN']; + // Stan: Pos bankası tarafında verilen referans işlem referans numarasıdır. + $result['transaction_id'] = $rawPaymentResponseData['Stan']; + $result['auth_code'] = $rawPaymentResponseData['ProvisionNumber'] ?? null; + $result['masked_number'] = $vPosMessage['CardNumber'] ?? null; + $result['currency'] = isset($vPosMessage['CurrencyCode']) ? $this->mapCurrency($vPosMessage['CurrencyCode']) : $order['currency']; + $result['amount'] = $this->formatAmount($vPosMessage['Amount']); + $result['installment_count'] = $this->mapInstallment($vPosMessage['InstallmentCount']); + if ('0001-01-01T00:00:00' !== $rawPaymentResponseData['TransactionTime'] && '00010101T00:00:00' !== $rawPaymentResponseData['TransactionTime']) { + $result['transaction_time'] = new \DateTimeImmutable($rawPaymentResponseData['TransactionTime']); + } else { + $result['transaction_time'] = new \DateTimeImmutable(); + } + + $this->logger->debug('mapped payment response', $result); + + return $result; + } +} diff --git a/src/Factory/AccountFactory.php b/src/Factory/AccountFactory.php index 7ab7639c..ac67bd91 100644 --- a/src/Factory/AccountFactory.php +++ b/src/Factory/AccountFactory.php @@ -112,9 +112,8 @@ public static function createGarantiPosAccount(string $bank, string $merchantId, * @phpstan-param PosInterface::MODEL_* $model * * @param non-empty-string $bank - * @param non-empty-string $merchantId Mağaza Numarası - * @param non-empty-string $username POS panelinizden kullanıcı işlemleri sayfasında APİ rolünde kullanıcı - * oluşturulmalıdır + * @param non-empty-string $merchantId Mağaza Numarası / Üye iş yeri tekil numarası + * @param non-empty-string $username Yönetim panelinden oluşturulan api rollü kullanıcı adı * @param non-empty-string $customerId CustomerNumber, Müşteri No * @param non-empty-string $storeKey Oluşturulan APİ kullanıcısının şifre bilgisidir. * @param non-empty-string $model diff --git a/src/Factory/CryptFactory.php b/src/Factory/CryptFactory.php index a4936266..30d5c3fd 100644 --- a/src/Factory/CryptFactory.php +++ b/src/Factory/CryptFactory.php @@ -27,6 +27,7 @@ use Mews\Pos\Gateways\PosNet; use Mews\Pos\Gateways\PosNetV1Pos; use Mews\Pos\Gateways\ToslaPos; +use Mews\Pos\Gateways\VakifKatilimPos; use Psr\Log\LoggerInterface; /** @@ -43,16 +44,17 @@ class CryptFactory public static function createGatewayCrypt(string $gatewayClass, LoggerInterface $logger): CryptInterface { $classMappings = [ - ToslaPos::class => ToslaPosCrypt::class, - EstV3Pos::class => EstV3PosCrypt::class, - EstPos::class => EstPosCrypt::class, - GarantiPos::class => GarantiPosCrypt::class, - InterPos::class => InterPosCrypt::class, - KuveytPos::class => KuveytPosCrypt::class, - PayForPos::class => PayForPosCrypt::class, - PosNet::class => PosNetCrypt::class, - PosNetV1Pos::class => PosNetV1PosCrypt::class, - PayFlexCPV4Pos::class => PayFlexCPV4Crypt::class, + ToslaPos::class => ToslaPosCrypt::class, + EstV3Pos::class => EstV3PosCrypt::class, + EstPos::class => EstPosCrypt::class, + GarantiPos::class => GarantiPosCrypt::class, + InterPos::class => InterPosCrypt::class, + KuveytPos::class => KuveytPosCrypt::class, + VakifKatilimPos::class => KuveytPosCrypt::class, + PayForPos::class => PayForPosCrypt::class, + PosNet::class => PosNetCrypt::class, + PosNetV1Pos::class => PosNetV1PosCrypt::class, + PayFlexCPV4Pos::class => PayFlexCPV4Crypt::class, ]; if (isset($classMappings[$gatewayClass])) { diff --git a/src/Factory/RequestDataMapperFactory.php b/src/Factory/RequestDataMapperFactory.php index 7c8c18ea..9465dfb8 100644 --- a/src/Factory/RequestDataMapperFactory.php +++ b/src/Factory/RequestDataMapperFactory.php @@ -19,6 +19,7 @@ use Mews\Pos\DataMapper\RequestDataMapper\PosNetV1PosRequestDataMapper; use Mews\Pos\DataMapper\RequestDataMapper\RequestDataMapperInterface; use Mews\Pos\DataMapper\RequestDataMapper\ToslaPosRequestDataMapper; +use Mews\Pos\DataMapper\RequestDataMapper\VakifKatilimPosRequestDataMapper; use Mews\Pos\Gateways\EstPos; use Mews\Pos\Gateways\EstV3Pos; use Mews\Pos\Gateways\GarantiPos; @@ -30,6 +31,7 @@ use Mews\Pos\Gateways\PosNet; use Mews\Pos\Gateways\PosNetV1Pos; use Mews\Pos\Gateways\ToslaPos; +use Mews\Pos\Gateways\VakifKatilimPos; use Mews\Pos\PosInterface; use Psr\EventDispatcher\EventDispatcherInterface; @@ -49,16 +51,17 @@ class RequestDataMapperFactory public static function createGatewayRequestMapper(string $gatewayClass, EventDispatcherInterface $eventDispatcher, CryptInterface $crypt, array $currencies = []): RequestDataMapperInterface { $classMappings = [ - ToslaPos::class => ToslaPosRequestDataMapper::class, - EstPos::class => EstPosRequestDataMapper::class, - EstV3Pos::class => EstV3PosRequestDataMapper::class, - GarantiPos::class => GarantiPosRequestDataMapper::class, - InterPos::class => InterPosRequestDataMapper::class, - KuveytPos::class => KuveytPosRequestDataMapper::class, - PayForPos::class => PayForPosRequestDataMapper::class, - PosNet::class => PosNetRequestDataMapper::class, - PosNetV1Pos::class => PosNetV1PosRequestDataMapper::class, - PayFlexCPV4Pos::class => PayFlexCPV4PosRequestDataMapper::class, + ToslaPos::class => ToslaPosRequestDataMapper::class, + EstPos::class => EstPosRequestDataMapper::class, + EstV3Pos::class => EstV3PosRequestDataMapper::class, + GarantiPos::class => GarantiPosRequestDataMapper::class, + InterPos::class => InterPosRequestDataMapper::class, + KuveytPos::class => KuveytPosRequestDataMapper::class, + VakifKatilimPos::class => VakifKatilimPosRequestDataMapper::class, + PayForPos::class => PayForPosRequestDataMapper::class, + PosNet::class => PosNetRequestDataMapper::class, + PosNetV1Pos::class => PosNetV1PosRequestDataMapper::class, + PayFlexCPV4Pos::class => PayFlexCPV4PosRequestDataMapper::class, ]; if (isset($classMappings[$gatewayClass])) { return new $classMappings[$gatewayClass]($eventDispatcher, $crypt, $currencies); diff --git a/src/Factory/ResponseDataMapperFactory.php b/src/Factory/ResponseDataMapperFactory.php index e3556eba..f145dc12 100644 --- a/src/Factory/ResponseDataMapperFactory.php +++ b/src/Factory/ResponseDataMapperFactory.php @@ -18,6 +18,7 @@ use Mews\Pos\DataMapper\ResponseDataMapper\PosNetV1PosResponseDataMapper; use Mews\Pos\DataMapper\ResponseDataMapper\ResponseDataMapperInterface; use Mews\Pos\DataMapper\ResponseDataMapper\ToslaPosResponseDataMapper; +use Mews\Pos\DataMapper\ResponseDataMapper\VakifKatilimPosResponseDataMapper; use Mews\Pos\Gateways\EstPos; use Mews\Pos\Gateways\EstV3Pos; use Mews\Pos\Gateways\GarantiPos; @@ -29,6 +30,7 @@ use Mews\Pos\Gateways\PosNet; use Mews\Pos\Gateways\PosNetV1Pos; use Mews\Pos\Gateways\ToslaPos; +use Mews\Pos\Gateways\VakifKatilimPos; use Psr\Log\LoggerInterface; /** @@ -46,17 +48,18 @@ class ResponseDataMapperFactory public static function createGatewayResponseMapper(string $gatewayClass, RequestDataMapperInterface $requestDataMapper, LoggerInterface $logger): ResponseDataMapperInterface { $classMappings = [ - ToslaPos::class => ToslaPosResponseDataMapper::class, - EstV3Pos::class => EstPosResponseDataMapper::class, - EstPos::class => EstPosResponseDataMapper::class, - GarantiPos::class => GarantiPosResponseDataMapper::class, - InterPos::class => InterPosResponseDataMapper::class, - KuveytPos::class => KuveytPosResponseDataMapper::class, - PayForPos::class => PayForPosResponseDataMapper::class, - PosNet::class => PosNetResponseDataMapper::class, - PosNetV1Pos::class => PosNetV1PosResponseDataMapper::class, - PayFlexV4Pos::class => PayFlexV4PosResponseDataMapper::class, - PayFlexCPV4Pos::class => PayFlexCPV4PosResponseDataMapper::class, + ToslaPos::class => ToslaPosResponseDataMapper::class, + EstV3Pos::class => EstPosResponseDataMapper::class, + EstPos::class => EstPosResponseDataMapper::class, + GarantiPos::class => GarantiPosResponseDataMapper::class, + InterPos::class => InterPosResponseDataMapper::class, + KuveytPos::class => KuveytPosResponseDataMapper::class, + VakifKatilimPos::class => VakifKatilimPosResponseDataMapper::class, + PayForPos::class => PayForPosResponseDataMapper::class, + PosNet::class => PosNetResponseDataMapper::class, + PosNetV1Pos::class => PosNetV1PosResponseDataMapper::class, + PayFlexV4Pos::class => PayFlexV4PosResponseDataMapper::class, + PayFlexCPV4Pos::class => PayFlexCPV4PosResponseDataMapper::class, ]; if (isset($classMappings[$gatewayClass])) { diff --git a/src/Factory/SerializerFactory.php b/src/Factory/SerializerFactory.php index 715cd5db..a3e2a518 100644 --- a/src/Factory/SerializerFactory.php +++ b/src/Factory/SerializerFactory.php @@ -17,6 +17,7 @@ use Mews\Pos\Serializer\PosNetV1PosSerializer; use Mews\Pos\Serializer\SerializerInterface; use Mews\Pos\Serializer\ToslaPosSerializer; +use Mews\Pos\Serializer\VakifKatilimPosSerializer; /** * SerializerFactory @@ -37,6 +38,7 @@ public static function createGatewaySerializer(string $gatewayClass): Serializer GarantiPosSerializer::class, InterPosSerializer::class, KuveytPosSerializer::class, + VakifKatilimPosSerializer::class, PayFlexV4PosSerializer::class, PayFlexCPV4PosSerializer::class, PayForPosSerializer::class, @@ -50,6 +52,6 @@ public static function createGatewaySerializer(string $gatewayClass): Serializer } } - throw new DomainException(sprintf('Serializer not found for the gateway %s', $gatewayClass)); + throw new DomainException(\sprintf('Serializer not found for the gateway %s', $gatewayClass)); } } diff --git a/src/Gateways/AbstractGateway.php b/src/Gateways/AbstractGateway.php index 4b912708..f20fd4d6 100644 --- a/src/Gateways/AbstractGateway.php +++ b/src/Gateways/AbstractGateway.php @@ -119,15 +119,17 @@ public function isSuccess(): bool } /** - * @phpstan-param self::TX_TYPE_* $txType - * @phpstan-param self::MODEL_* $paymentModel + * @phpstan-param self::TX_TYPE_* $txType + * @phpstan-param self::MODEL_* $paymentModel + * @phpstan-param self::TX_TYPE_PAY_* $orderTxType * * @param string|null $txType * @param string|null $paymentModel + * @param string|null $orderTxType * * @return non-empty-string */ - public function getApiURL(string $txType = null, string $paymentModel = null): string + public function getApiURL(string $txType = null, string $paymentModel = null, ?string $orderTxType = null): string { return $this->config['gateway_endpoints']['payment_api']; } @@ -150,14 +152,20 @@ public function get3DHostGatewayURL(): string /** * @phpstan-param self::TX_TYPE_* $txType + * @phpstan-param self::TX_TYPE_PAY_* $orderTxType * * @param string|null $txType + * @param string|null $orderTxType transaction type of order when it was made * * @return non-empty-string */ - public function getQueryAPIUrl(string $txType = null): string + public function getQueryAPIUrl(string $txType = null, ?string $orderTxType = null): string { - return $this->config['gateway_endpoints']['query_api'] ?? $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE); + return $this->config['gateway_endpoints']['query_api'] ?? $this->getApiURL( + $txType, + PosInterface::MODEL_NON_SECURE, + $orderTxType + ); } /** @@ -215,10 +223,10 @@ public function makeRegularPayment(array $order, CreditCardInterface $creditCard 'model' => PosInterface::MODEL_NON_SECURE, 'tx_type' => $txType, ]); - if (in_array($txType, [PosInterface::TX_TYPE_PAY_AUTH, PosInterface::TX_TYPE_PAY_PRE_AUTH], true)) { + if (\in_array($txType, [PosInterface::TX_TYPE_PAY_AUTH, PosInterface::TX_TYPE_PAY_PRE_AUTH], true)) { $requestData = $this->requestDataMapper->createNonSecurePaymentRequestData($this->account, $order, $txType, $creditCard); } else { - throw new LogicException(sprintf('Invalid transaction type "%s" provided', $txType)); + throw new LogicException(\sprintf('Invalid transaction type "%s" provided', $txType)); } $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); @@ -307,7 +315,11 @@ public function refund(array $order): PosInterface $data, $txType, PosInterface::MODEL_NON_SECURE, - $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + $this->getApiURL( + $txType, + PosInterface::MODEL_NON_SECURE, + $order['transaction_type'] ?? null + ) ); $this->response = $this->responseDataMapper->mapRefundResponse($bankResponse); @@ -339,7 +351,11 @@ public function cancel(array $order): PosInterface $data, $txType, PosInterface::MODEL_NON_SECURE, - $this->getApiURL($txType, PosInterface::MODEL_NON_SECURE) + $this->getApiURL( + $txType, + PosInterface::MODEL_NON_SECURE, + $order['transaction_type'] ?? null + ) ); $this->response = $this->responseDataMapper->mapCancelResponse($bankResponse); diff --git a/src/Gateways/PosNetV1Pos.php b/src/Gateways/PosNetV1Pos.php index 6729dceb..bade0cd4 100644 --- a/src/Gateways/PosNetV1Pos.php +++ b/src/Gateways/PosNetV1Pos.php @@ -58,7 +58,7 @@ public function getAccount(): AbstractPosAccount /** * @inheritDoc */ - public function getApiURL(string $txType = null, string $paymentModel = null): string + public function getApiURL(string $txType = null, string $paymentModel = null, ?string $orderTxType = null): string { if (null !== $txType) { return parent::getApiURL().'/'.$this->requestDataMapper->mapTxType($txType); diff --git a/src/Gateways/ToslaPos.php b/src/Gateways/ToslaPos.php index 60befcca..c27861f7 100644 --- a/src/Gateways/ToslaPos.php +++ b/src/Gateways/ToslaPos.php @@ -66,7 +66,7 @@ public function getAccount(): AbstractPosAccount /** * @inheritDoc */ - public function getApiURL(string $txType = null, string $paymentModel = null): string + public function getApiURL(string $txType = null, string $paymentModel = null, ?string $orderTxType = null): string { if (null !== $txType && null !== $paymentModel) { return parent::getApiURL().'/'.$this->getRequestURIByTransactionType($txType, $paymentModel); diff --git a/src/Gateways/VakifKatilimPos.php b/src/Gateways/VakifKatilimPos.php new file mode 100644 index 00000000..f9a8db3e --- /dev/null +++ b/src/Gateways/VakifKatilimPos.php @@ -0,0 +1,276 @@ + [ + PosInterface::MODEL_NON_SECURE, + PosInterface::MODEL_3D_SECURE, + PosInterface::MODEL_3D_HOST, + ], + PosInterface::TX_TYPE_PAY_PRE_AUTH => [ + PosInterface::MODEL_NON_SECURE, + ], + PosInterface::TX_TYPE_PAY_POST_AUTH => true, + PosInterface::TX_TYPE_STATUS => true, + PosInterface::TX_TYPE_CANCEL => true, + PosInterface::TX_TYPE_REFUND => true, + PosInterface::TX_TYPE_HISTORY => true, + PosInterface::TX_TYPE_ORDER_HISTORY => true, + ]; + + /** @return KuveytPosAccount */ + public function getAccount(): AbstractPosAccount + { + return $this->account; + } + + /** + * @inheritDoc + */ + public function getApiURL(string $txType = null, string $paymentModel = null, ?string $orderTxType = null): string + { + if (null !== $txType && null !== $paymentModel) { + return parent::getApiURL().'/'.$this->getRequestURIByTransactionType($txType, $paymentModel, $orderTxType); + } + + return parent::getApiURL(); + } + + /** + * @inheritDoc + */ + public function make3DPayPayment(Request $request, array $order, string $txType): PosInterface + { + throw new UnsupportedPaymentModelException(); + } + + /** + * @inheritDoc + */ + public function make3DHostPayment(Request $request, array $order, string $txType): PosInterface + { + $this->response = $this->responseDataMapper->map3DHostResponseData($request->request->all(), $txType, $order); + + return $this; + } + + /** + * @inheritDoc + */ + public function get3DFormData(array $order, string $paymentModel, string $txType, CreditCardInterface $creditCard = null): array + { + $this->logger->debug('preparing 3D form data'); + + if (PosInterface::MODEL_3D_HOST === $paymentModel) { + return $this->requestDataMapper->create3DFormData($this->account, $order, $paymentModel, $txType, $this->get3DHostGatewayURL()); + } + + $gatewayUrl = $this->get3DGatewayURL(); + $response = $this->sendEnrollmentRequest($this->account, $order, $paymentModel, $txType, $gatewayUrl, $creditCard); + + return $this->requestDataMapper->create3DFormData($this->account, $response['form_inputs'], $paymentModel, $txType, $response['gateway'], $creditCard); + } + + /** + * @inheritDoc + */ + public function make3DPayment(Request $request, array $order, string $txType, CreditCardInterface $creditCard = null): PosInterface + { + $gatewayResponse = $request->request->all(); + + if (!$this->is3DAuthSuccess($gatewayResponse)) { + $this->response = $this->responseDataMapper->map3DPaymentData($gatewayResponse, null, $txType, $order); + + return $this; + } + + if (!$this->requestDataMapper->getCrypt()->check3DHash($this->account, $gatewayResponse)) { + throw new HashMismatchException(); + } + + $this->logger->debug('finishing payment'); + + $requestData = $this->requestDataMapper->create3DPaymentRequestData($this->account, $order, $txType, $gatewayResponse); + + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); + $this->eventDispatcher->dispatch($event); + if ($requestData !== $event->getRequestData()) { + $this->logger->debug('Request data is changed via listeners', [ + 'txType' => $event->getTxType(), + 'bank' => $event->getBank(), + 'initialData' => $requestData, + 'updatedData' => $event->getRequestData(), + ]); + $requestData = $event->getRequestData(); + } + + $contents = $this->serializer->encode($requestData, $txType); + $bankResponse = $this->send($contents, $txType, PosInterface::MODEL_3D_SECURE); + + $this->response = $this->responseDataMapper->map3DPaymentData($gatewayResponse, $bankResponse, $txType, $order); + $this->logger->debug('finished 3D payment', ['mapped_response' => $this->response]); + + return $this; + } + + + /** + * @inheritDoc + * + * @return array + */ + protected function send($contents, string $txType, string $paymentModel, string $url = null): array + { + $url ??= $this->getApiURL($txType, $paymentModel); + + $this->logger->debug('sending request', ['url' => $url]); + $body = [ + 'body' => $contents, + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ]; + $response = $this->client->post($url, $body); + $this->logger->debug('request completed', ['status_code' => $response->getStatusCode()]); + + return $this->data = $this->serializer->decode($response->getBody()->getContents(), $txType); + } + + /** + * @phpstan-param PosInterface::MODEL_3D_* $paymentModel + * @phpstan-param PosInterface::TX_TYPE_PAY_AUTH|PosInterface::TX_TYPE_PAY_PRE_AUTH $txType + * + * @param KuveytPosAccount $kuveytPosAccount + * @param array $order + * @param string $paymentModel + * @param string $txType + * @param non-empty-string $gatewayURL + * @param CreditCardInterface|null $creditCard + * + * @return array{gateway: string, form_inputs: array} + * + * @throws Exception + */ + private function sendEnrollmentRequest(KuveytPosAccount $kuveytPosAccount, array $order, string $paymentModel, string $txType, string $gatewayURL, ?CreditCardInterface $creditCard = null): array + { + $requestData = $this->requestDataMapper->create3DEnrollmentCheckRequestData($kuveytPosAccount, $order, $paymentModel, $txType, $creditCard); + + $event = new RequestDataPreparedEvent($requestData, $this->account->getBank(), $txType); + $this->eventDispatcher->dispatch($event); + if ($requestData !== $event->getRequestData()) { + $this->logger->debug('Request data is changed via listeners', [ + 'txType' => $event->getTxType(), + 'bank' => $event->getBank(), + 'initialData' => $requestData, + 'updatedData' => $event->getRequestData(), + ]); + $requestData = $event->getRequestData(); + } + + $data = $this->serializer->encode($requestData, $txType); + + /** + * @var array{form_inputs: array, gateway: string} $decodedResponse + */ + $decodedResponse = $this->send($data, $txType, $paymentModel, $gatewayURL); + + return $decodedResponse; + } + + + /** + * @phpstan-param PosInterface::TX_TYPE_* $txType + * @phpstan-param PosInterface::MODEL_* $paymentModel + * @phpstan-param PosInterface::TX_TYPE_PAY_* $orderTxType + * + * @return string + * + * @throws UnsupportedTransactionTypeException + */ + private function getRequestURIByTransactionType(string $txType, string $paymentModel, ?string $orderTxType = null): string + { + $orderTxType ??= PosInterface::TX_TYPE_PAY_AUTH; + + $arr = [ + PosInterface::TX_TYPE_PAY_AUTH => [ + PosInterface::MODEL_NON_SECURE => 'Non3DPayGate', + PosInterface::MODEL_3D_SECURE => 'ThreeDModelProvisionGate', + ], + PosInterface::TX_TYPE_PAY_PRE_AUTH => [ + PosInterface::MODEL_NON_SECURE => 'PreAuthorizaten', + ], + PosInterface::TX_TYPE_PAY_POST_AUTH => 'PreAuthorizatenClose', + PosInterface::TX_TYPE_CANCEL => [ + PosInterface::MODEL_NON_SECURE => [ + PosInterface::TX_TYPE_PAY_AUTH => 'SaleReversal', + PosInterface::TX_TYPE_PAY_PRE_AUTH => 'PreAuthorizationReversal', + ], + ], + PosInterface::TX_TYPE_REFUND => [ + PosInterface::MODEL_NON_SECURE => [ + PosInterface::TX_TYPE_PAY_AUTH => 'DrawBack', + PosInterface::TX_TYPE_PAY_PRE_AUTH => 'PreAuthorizationDrawBack', + ], + ], + PosInterface::TX_TYPE_STATUS => 'SelectOrderByMerchantOrderId', + PosInterface::TX_TYPE_ORDER_HISTORY => 'SelectOrder', + PosInterface::TX_TYPE_HISTORY => 'SelectOrder', + ]; + + if (!isset($arr[$txType])) { + throw new UnsupportedTransactionTypeException(); + } + + if (\is_string($arr[$txType])) { + return $arr[$txType]; + } + + if (!isset($arr[$txType][$paymentModel])) { + throw new UnsupportedTransactionTypeException(); + } + + if (\is_array($arr[$txType][$paymentModel])) { + return $arr[$txType][$paymentModel][$orderTxType]; + } + + return $arr[$txType][$paymentModel]; + } +} diff --git a/src/Serializer/VakifKatilimPosSerializer.php b/src/Serializer/VakifKatilimPosSerializer.php new file mode 100644 index 00000000..2397a200 --- /dev/null +++ b/src/Serializer/VakifKatilimPosSerializer.php @@ -0,0 +1,172 @@ + 'VPosMessageContract', + XmlEncoder::ENCODING => 'ISO-8859-1', + ]); + + $this->serializer = new Serializer([], [$encoder, new JsonEncoder()]); + } + + /** + * @inheritDoc + */ + public static function supports(string $gatewayClass): bool + { + return VakifKatilimPos::class === $gatewayClass; + } + + /** + * @inheritDoc + */ + public function encode(array $data, string $txType): string + { + return $this->serializer->encode($data, XmlEncoder::FORMAT); + } + + /** + * @inheritDoc + */ + public function decode(string $data, string $txType): array + { + try { + $data = \str_replace("�", '', $data); + $data = \str_replace(' encoding="utf-16"', '', $data); + + return $this->serializer->decode($data, XmlEncoder::FORMAT); + } catch (NotEncodableValueException $notEncodableValueException) { + if ($this->isHTML($data)) { + // 3D form data icin enrollment istegi gonderiyoruz, o istegin cevabi icinde form olan HTML donuyor. + return $this->transformReceived3DFormData($data); + } + + throw new Exception($data, $notEncodableValueException->getCode(), $notEncodableValueException); + } + } + + /** + * @param string $str + * + * @return bool returns true if string is a HTML document. + */ + private function isHTML(string $str): bool + { + return $str !== \strip_tags($str); + } + + /** + * Diger Gateway'lerden farkli olarak bu gateway HTML form olan bir response doner. + * Kutupahenin islem akisina uymasi icin bu HTML form verilerini array'e donusturup, kendimiz post ediyoruz. + * + * @param string $response + * + * @return array{gateway: string, form_inputs: array} + */ + private function transformReceived3DFormData(string $response): array + { + $dom = new DOMDocument(); + /** + * Kuveyt Pos started sending HTML with custom HTML tags such as . + * Without LIBXML_NOERROR flag loadHTML throws "Tag apm_do_not_touch invalid in Entity" exception + */ + $dom->loadHTML($response, LIBXML_NOERROR); + + $gatewayURL = ''; + /** @var DOMElement|null $formNode */ + $formNode = $dom->getElementsByTagName('form')->item(0); + if (null === $formNode) { + throw new \Exception($response, 974); + } + + /** @var DOMNamedNodeMap $attributes */ + $attributes = $formNode->attributes; + for ($i = 0; $i < $attributes->length; ++$i) { + /** @var DOMAttr $attribute */ + $attribute = $attributes->item($i); + if ('action' === $attribute->name) { + /** + * banka onayladiginda gatewayURL=bankanin gateway url + * onaylanmadiginda (hatali istek oldugunda) ise gatewayURL = istekte yer alan failURL + */ + $gatewayURL = $attribute->value; + break; + } + } + + $els = $dom->getElementsByTagName('input'); + $inputs = $this->builtInputsFromHTMLDoc($els); + + return [ + 'gateway' => $gatewayURL, + 'form_inputs' => $inputs, + ]; + } + + /** + * html form'da gelen input degeleri array'e donusturur + * + * @param DOMNodeList $domNodeList + * + * @return array + */ + private function builtInputsFromHTMLDoc(DOMNodeList $domNodeList): array + { + $inputs = []; + foreach ($domNodeList as $el) { + $key = null; + $value = ''; + + /** @var DOMNamedNodeMap $attributes */ + $attributes = $el->attributes; + // for each input element select name and value attribute values + for ($i = 0; $i < $attributes->length; ++$i) { + /** @var DOMAttr $attribute */ + $attribute = $attributes->item($i); + if ('name' === $attribute->name) { + /** @var string|null $key */ + $key = $attribute->value; + } + + if ('value' === $attribute->name) { + /** @var string|null $value */ + $value = $attribute->value; + } + } + + if (!$key) { + continue; + } + + if (\in_array($key, ['submit', 'submitBtn'])) { + continue; + } + + $inputs[$key] = $value; + } + + return $inputs; + } +} diff --git a/tests/Functional/PaymentTestTrait.php b/tests/Functional/PaymentTestTrait.php index 5a05ef5d..89fd63bf 100644 --- a/tests/Functional/PaymentTestTrait.php +++ b/tests/Functional/PaymentTestTrait.php @@ -10,6 +10,7 @@ use Mews\Pos\Gateways\GarantiPos; use Mews\Pos\Gateways\PayForPos; use Mews\Pos\Gateways\ToslaPos; +use Mews\Pos\Gateways\VakifKatilimPos; use Mews\Pos\PosInterface; trait PaymentTestTrait @@ -19,7 +20,8 @@ private function createPaymentOrder( float $amount = 1.01, int $installment = 0, bool $tekrarlanan = false - ): array { + ): array + { $orderId = date('Ymd').strtoupper(substr(uniqid(sha1(time())), 0, 4)); $order = [ @@ -141,6 +143,10 @@ public function createCancelOrder(string $gatewayClass, array $lastResponse): ar $cancelOrder['auth_code'] = $lastResponse['auth_code']; $cancelOrder['transaction_id'] = $lastResponse['transaction_id']; $cancelOrder['amount'] = $lastResponse['amount']; + } elseif (VakifKatilimPos::class === $gatewayClass) { + $cancelOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id + $cancelOrder['amount'] = $lastResponse['amount']; + $cancelOrder['transaction_type'] = $lastResponse['transaction_type'] ?? PosInterface::TX_TYPE_PAY_AUTH; } elseif (\Mews\Pos\Gateways\PayFlexV4Pos::class === $gatewayClass || \Mews\Pos\Gateways\PayFlexCPV4Pos::class === $gatewayClass) { // çalışmazsa $lastResponse['all']['ReferenceTransactionId']; ile denenmesi gerekiyor. $cancelOrder['transaction_id'] = $lastResponse['transaction_id']; @@ -200,12 +206,22 @@ private function createOrderHistoryOrder(string $gatewayClass, array $lastRespon 'currency' => $lastResponse['currency'], 'ip' => '127.0.0.1', ]; + } elseif (VakifKatilimPos::class === $gatewayClass) { + /** @var \DateTimeImmutable $txTime */ + $txTime = $lastResponse['transaction_time']; + $order = [ + 'auth_code' => $lastResponse['auth_code'], + /** + * Tarih aralığı maksimum 90 gün olabilir. + */ + 'start_date' => $txTime->modify('-1 day'), + 'end_date' => $txTime->modify('+1 day'), + ]; } return $order; } - private function createHistoryOrder(string $gatewayClass, array $extraData): array { if (PayForPos::class === $gatewayClass) { @@ -215,6 +231,20 @@ private function createHistoryOrder(string $gatewayClass, array $extraData): arr ]; } + if (\Mews\Pos\Gateways\VakifKatilimPos::class === $gatewayClass) { + $txTime = new \DateTimeImmutable(); + + return [ + 'page' => 1, + 'page_size' => 20, + /** + * Tarih aralığı maksimum 90 gün olabilir. + */ + 'start_date' => $txTime->modify('-1 day'), + 'end_date' => $txTime->modify('+1 day'), + ]; + } + return []; } @@ -232,6 +262,10 @@ private function createRefundOrder(string $gatewayClass, array $lastResponse): a $refundOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id $refundOrder['auth_code'] = $lastResponse['auth_code']; $refundOrder['transaction_id'] = $lastResponse['transaction_id']; + } elseif (VakifKatilimPos::class === $gatewayClass) { + $refundOrder['remote_order_id'] = $lastResponse['remote_order_id']; // banka tarafındaki order id + $refundOrder['amount'] = $lastResponse['amount']; + $refundOrder['transaction_type'] = $lastResponse['transaction_type'] ?? PosInterface::TX_TYPE_PAY_AUTH; } elseif (\Mews\Pos\Gateways\PayFlexV4Pos::class === $gatewayClass || \Mews\Pos\Gateways\PayFlexCPV4Pos::class === $gatewayClass) { // çalışmazsa $lastResponse['all']['ReferenceTransactionId']; ile denenmesi gerekiyor. $refundOrder['transaction_id'] = $lastResponse['transaction_id']; diff --git a/tests/Unit/Crypt/KuveytPosCryptTest.php b/tests/Unit/Crypt/KuveytPosCryptTest.php index 75348fe4..3aaafff9 100644 --- a/tests/Unit/Crypt/KuveytPosCryptTest.php +++ b/tests/Unit/Crypt/KuveytPosCryptTest.php @@ -35,6 +35,13 @@ protected function setUp(): void $this->crypt = new KuveytPosCrypt(new NullLogger()); } + public function testHashString(): void + { + $actual = $this->crypt->hashString('123'); + + $this->assertSame('QL0AFWMIX8NRZTKeof9cXsvbvu8=', $actual); + } + /** * @dataProvider threeDHashCreateDataProvider */ @@ -45,6 +52,11 @@ public function testCreate3DHash(array $requestData, string $expected): void $this->assertSame($expected, $actual); } + public function testCheck3DHash(): void + { + $this->assertTrue($this->crypt->check3DHash($this->account, [])); + } + /** * @dataProvider hashCreateDataProvider */ diff --git a/tests/Unit/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapperTest.php b/tests/Unit/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapperTest.php new file mode 100644 index 00000000..e42cc322 --- /dev/null +++ b/tests/Unit/DataMapper/RequestDataMapper/VakifKatilimPosRequestDataMapperTest.php @@ -0,0 +1,649 @@ +account = AccountFactory::createKuveytPosAccount( + 'vakif-katilim', + '1', + 'APIUSER', + '11111', + 'kdsnsksl', + ); + + $dispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->card = CreditCardFactory::create( + '4155650100416111', + 25, + 1, + '123', + 'John Doe', + ); + + $crypt = CryptFactory::createGatewayCrypt(VakifKatilimPos::class, new NullLogger()); + $this->requestDataMapper = new VakifKatilimPosRequestDataMapper($dispatcher, $crypt); + } + + /** + * @testWith ["pay", "1"] + */ + public function testMapTxType(string $txType, string $expected): void + { + $actual = $this->requestDataMapper->mapTxType($txType); + $this->assertSame($expected, $actual); + } + + /** + * @testWith ["Sale"] + */ + public function testMapTxTypeException(string $txType): void + { + $this->expectException(UnsupportedTransactionTypeException::class); + $this->requestDataMapper->mapTxType($txType); + } + + /** + * @return void + */ + public function testFormatAmount(): void + { + $class = new \ReflectionObject($this->requestDataMapper); + $method = $class->getMethod('formatAmount'); + $method->setAccessible(true); + $this->assertSame(0, $method->invokeArgs($this->requestDataMapper, [0])); + $this->assertSame(0, $method->invokeArgs($this->requestDataMapper, [0.0])); + $this->assertSame(1025, $method->invokeArgs($this->requestDataMapper, [10.25])); + $this->assertSame(1000, $method->invokeArgs($this->requestDataMapper, [10.00])); + } + + /** + * @return void + */ + public function testMapCurrency(): void + { + $class = new \ReflectionObject($this->requestDataMapper); + $method = $class->getMethod('mapCurrency'); + $method->setAccessible(true); + $this->assertSame('0949', $method->invokeArgs($this->requestDataMapper, [PosInterface::CURRENCY_TRY])); + $this->assertSame('0978', $method->invokeArgs($this->requestDataMapper, [PosInterface::CURRENCY_EUR])); + } + + /** + * @param string|int|null $installment + * @param string|int $expected + * + * @testWith ["0", "0"] + * ["1", "0"] + * ["2", "2"] + * [2, "2"] + * + * @return void + */ + public function testMapInstallment($installment, $expected): void + { + $class = new \ReflectionObject($this->requestDataMapper); + $method = $class->getMethod('mapInstallment'); + $method->setAccessible(true); + $this->assertSame($expected, $method->invokeArgs($this->requestDataMapper, [$installment])); + } + + /** + * @dataProvider threeDFormDataProvider + */ + public function testGet3DFormData( + array $order, + string $gatewayURL, + string $txType, + string $paymentModel, + array $expected + ): void + { + $actual = $this->requestDataMapper->create3DFormData( + $this->account, + $order, + $paymentModel, + $txType, + $gatewayURL, + ); + + $this->assertEquals($expected, $actual); + } + + /** + * @dataProvider nonSecurePaymentRequestDataDataProvider + */ + public function testCreateNonSecurePaymentRequestData(array $order, array $expectedData): void + { + $actual = $this->requestDataMapper->createNonSecurePaymentRequestData( + $this->account, + $order, + PosInterface::TX_TYPE_PAY_AUTH, + $this->card + ); + + $this->assertEquals($expectedData, $actual); + } + + /** + * @dataProvider createNonSecurePostAuthPaymentRequestDataDataProvider + */ + public function testCreateNonSecurePostAuthPaymentRequestData(array $order, array $expectedData): void + { + $actual = $this->requestDataMapper->createNonSecurePostAuthPaymentRequestData( + $this->account, + $order, + ); + + $this->assertEquals($expectedData, $actual); + } + + + /** + * @dataProvider create3DEnrollmentCheckRequestDataDataProvider + */ + public function testCreate3DEnrollmentCheckRequestData(array $order, string $txType, array $expectedData): void + { + $account = $this->account; + $card = $this->card; + + $actualData = $this->requestDataMapper->create3DEnrollmentCheckRequestData( + $account, + $order, + PosInterface::MODEL_3D_SECURE, + $txType, + $card + ); + $this->assertEquals($expectedData, $actualData); + } + + /** + * @dataProvider createCancelRequestDataProvider + */ + public function testCreateCancelRequestData(array $order, array $expected): void + { + $actual = $this->requestDataMapper->createCancelRequestData($this->account, $order); + $this->assertEquals($expected, $actual); + } + + /** + * @dataProvider createRefundRequestDataProvider + */ + public function testCreateRefundRequestData(array $order, array $expected): void + { + $actual = $this->requestDataMapper->createRefundRequestData($this->account, $order); + $this->assertEquals($expected, $actual); + } + + /** + * @dataProvider createHistoryRequestDataProvider + */ + public function testCreateHistoryRequestData(array $order, array $expected): void + { + $actual = $this->requestDataMapper->createHistoryRequestData($this->account, $order); + $this->assertEquals($expected, $actual); + } + + + /** + * @dataProvider createOrderHistoryRequestDataProvider + */ + public function testCreateOrderHistoryRequestData(array $order, array $expected): void + { + $actual = $this->requestDataMapper->createOrderHistoryRequestData($this->account, $order); + $this->assertEquals($expected, $actual); + } + + /** + * @dataProvider createStatusRequestDataProvider + */ + public function testCreateStatusRequestData(array $order, array $expected): void + { + $actual = $this->requestDataMapper->createStatusRequestData($this->account, $order); + $this->assertEquals($expected, $actual); + } + + /** + * @dataProvider create3DPaymentRequestDataDataProvider + */ + public function testCreate3DPaymentRequestData(KuveytPosAccount $kuveytPosAccount, array $order, string $txType, array $responseData, array $expectedData): void + { + $actual = $this->requestDataMapper->create3DPaymentRequestData($kuveytPosAccount, $order, $txType, $responseData); + + $this->assertEquals($expectedData, $actual); + } + + + public static function createCancelRequestDataProvider(): iterable + { + yield [ + 'order' => [ + 'id' => '2023070849CD', + 'remote_order_id' => '114293600', + 'amount' => 1.01, + ], + 'expected' => [ + 'CustomerId' => '11111', + 'Amount' => 101, + 'MerchantId' => '1', + 'OrderId' => '114293600', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '2023070849CD', + 'PaymentType' => '1', + 'HashData' => '/fcrfEy2juD7L/aTujiLgtJg23M=', + ], + ]; + + yield [ + 'order' => [ + 'id' => '2023070849CD', + 'remote_order_id' => '114293600', + 'amount' => 1.01, + 'transaction_type' => PosInterface::TX_TYPE_PAY_AUTH, + ], + 'expected' => [ + 'CustomerId' => '11111', + 'Amount' => 101, + 'MerchantId' => '1', + 'OrderId' => '114293600', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '2023070849CD', + 'PaymentType' => '1', + 'HashData' => '/fcrfEy2juD7L/aTujiLgtJg23M=', + ], + ]; + + yield [ + 'order' => [ + 'id' => '2023070849CD', + 'remote_order_id' => '114293600', + 'amount' => 1.01, + 'transaction_type' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + ], + 'expected' => [ + 'CustomerId' => '11111', + 'MerchantId' => '1', + 'OrderId' => '114293600', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '2023070849CD', + 'PaymentType' => '1', + 'HashData' => '2JEWD55cC2JtLJIxM8KDRz2f6hU=', + ], + ]; + } + + public static function createRefundRequestDataProvider(): Generator + { + yield [ + 'order' => [ + 'id' => '2023070849CD', + 'remote_order_id' => '114293600', + 'amount' => 1.01, + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '2023070849CD', + 'OrderId' => '114293600', + 'HashData' => '2JEWD55cC2JtLJIxM8KDRz2f6hU=', + ], + ]; + } + + public static function createStatusRequestDataProvider(): iterable + { + yield [ + 'order' => [ + 'id' => 'order-123', + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashData' => '4oNmzFPMeC/tOK8i/XCPNy4W+FU=', + 'MerchantOrderId' => 'order-123', + ], + ]; + } + + public static function create3DPaymentRequestDataDataProvider(): array + { + $account = AccountFactory::createKuveytPosAccount( + 'vakif-katilim', + '1', + 'APIUSER', + '11111', + 'kdsnsksl', + ); + + $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', + ]; + + return [ + [ + 'account' => $account, + 'order' => $order, + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'responseData' => [ + 'MD' => '67YtBfBRTZ0XBKnAHi8c/A==', + 'OrderId' => '86297530', + 'MerchantOrderId' => '2020110828BC', + 'ResponseCode' => '00', + 'ResponseMessage' => 'Kart doğrulandı.', + 'HashData' => 'ucejRvHjCbuPXagyoweFLnJfSJg=', + ], + 'expected' => [ + 'APIVersion' => VakifKatilimPosRequestDataMapper::API_VERSION, + 'HashData' => 'sFxxO809/N3Yif4p/js1UKFMRro=', + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'InstallmentCount' => '0', + 'Amount' => 100, + 'MerchantOrderId' => '2020110828BC', + 'TransactionSecurity' => '3', + 'SubMerchantId' => '0', + 'OkUrl' => 'http://localhost/finansbank-payfor/3d/response.php', + 'FailUrl' => 'http://localhost/finansbank-payfor/3d/response.php', + 'AdditionalData' => [ + 'AdditionalDataList' => [ + 'VPosAdditionalData' => [ + 'Key' => 'MD', + 'Data' => '67YtBfBRTZ0XBKnAHi8c/A==', + ], + ], + ], + ], + ], + ]; + } + + public static function create3DEnrollmentCheckRequestDataDataProvider(): array + { + return [ + [ + 'order' => [ + 'id' => '2020110828BC', + 'amount' => 10.01, + 'installment' => '0', + 'currency' => PosInterface::CURRENCY_TRY, + 'success_url' => 'http://localhost/finansbank-payfor/3d/success.php', + 'fail_url' => 'http://localhost/finansbank-payfor/3d/fail.php', + ], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'expectedData' => [ + 'APIVersion' => VakifKatilimPosRequestDataMapper::API_VERSION, + 'MerchantId' => '1', + 'UserName' => 'APIUSER', + 'CustomerId' => '11111', + 'HashData' => 'xAV9RXTP58Hy+8KYe1VgYDaZFqs=', + 'TransactionSecurity' => '3', + 'InstallmentCount' => '0', + 'Amount' => 1001, + 'FECCurrencyCode' => '0949', + 'MerchantOrderId' => '2020110828BC', + 'OkUrl' => 'http://localhost/finansbank-payfor/3d/success.php', + 'FailUrl' => 'http://localhost/finansbank-payfor/3d/fail.php', + 'CardHolderName' => 'John Doe', + 'CardNumber' => '4155650100416111', + 'CardExpireDateYear' => '25', + 'CardExpireDateMonth' => '01', + 'CardCVV2' => '123', + 'SubMerchantId' => '0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + ], + ], + ]; + } + + + public static function nonSecurePaymentRequestDataDataProvider(): iterable + { + yield [ + 'order' => [ + 'id' => '123', + 'amount' => 10.0, + 'installment' => 0, + 'currency' => PosInterface::CURRENCY_TRY, + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'APIVersion' => '1.0.0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '123', + 'InstallmentCount' => '0', + 'Amount' => 1000, + 'FECCurrencyCode' => '0949', + 'CurrencyCode' => '0949', + 'TransactionSecurity' => '5', + 'CardHolderName' => 'John Doe', + 'CardNumber' => '4155650100416111', + 'CardExpireDateYear' => '25', + 'CardExpireDateMonth' => '01', + 'CardCVV2' => '123', + 'HashData' => 'AYOjSzXn6dgwiV3U0vXzNTWlO8g=', + ], + ]; + + yield 'withInstallment' => [ + 'order' => [ + 'id' => '123', + 'amount' => 10.0, + 'currency' => PosInterface::CURRENCY_TRY, + 'installment' => 3, + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'APIVersion' => '1.0.0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '123', + 'InstallmentCount' => '3', + 'Amount' => 1000, + 'FECCurrencyCode' => '0949', + 'CurrencyCode' => '0949', + //'PaymentType' => '1', + 'TransactionSecurity' => '5', + 'CardHolderName' => 'John Doe', + 'CardNumber' => '4155650100416111', + 'CardExpireDateYear' => '25', + 'CardExpireDateMonth' => '01', + 'CardCVV2' => '123', + 'HashData' => 'AYOjSzXn6dgwiV3U0vXzNTWlO8g=', + ], + ]; + } + + public static function createNonSecurePostAuthPaymentRequestDataDataProvider(): iterable + { + yield [ + 'order' => [ + 'id' => '123', + 'remote_order_id' => 'remote-123', + 'ip' => '127.0.0.1', + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantOrderId' => '123', + 'HashData' => 'K0LvOf07C/ayD53wWiykcUCZPc8=', + 'OrderId' => 'remote-123', + 'CustomerIPAddress' => '127.0.0.1', + ], + ]; + } + + public static function createHistoryRequestDataProvider(): Generator + { + yield [ + 'order' => [ + 'start_date' => (new \DateTime('2024-03-30')), + 'end_date' => (new \DateTime('2024-03-31')), + 'page' => 1, + 'page_size' => 10, + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashData' => '58xhdJGlgIZtsid8cvSDlr8EItk=', + 'StartDate' => '2024-03-30', + 'EndDate' => '2024-03-31', + 'LowerLimit' => 0, + 'UpperLimit' => 10, + 'ProvNumber' => null, + 'OrderStatus' => null, + 'TranResult' => null, + 'OrderNo' => null, + ], + ]; + } + + public static function createOrderHistoryRequestDataProvider(): Generator + { + yield [ + 'order' => [ + 'start_date' => (new \DateTime('2024-03-30')), + 'end_date' => (new \DateTime('2024-03-31')), + 'auth_code' => '896626', + ], + 'expected' => [ + 'MerchantId' => '1', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'SubMerchantId' => '0', + 'HashData' => '58xhdJGlgIZtsid8cvSDlr8EItk=', + 'StartDate' => '2024-03-30', + 'EndDate' => '2024-03-31', + 'LowerLimit' => 0, + 'UpperLimit' => 100, + 'ProvNumber' => '896626', + 'OrderStatus' => null, + 'TranResult' => null, + 'OrderNo' => null, + ], + ]; + } + + public static function threeDFormDataProvider(): array + { + $order = [ + 'id' => 'order222', + 'amount' => '100.25', + 'currency' => PosInterface::CURRENCY_TRY, + 'success_url' => 'https://domain.com/success', + 'fail_url' => 'https://domain.com/fail_url', + ]; + + return [ + '3d_host' => [ + 'order' => $order, + 'gatewayUrl' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/CommonPaymentPage/CommonPaymentPage', + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_3D_HOST, + 'expected' => [ + 'gateway' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/CommonPaymentPage/CommonPaymentPage', + 'method' => 'POST', + 'inputs' => [ + 'UserName' => 'APIUSER', + 'HashPassword' => 'h58bUB83xQz2/21SUeOemUgkF5U=', + 'MerchantId' => '1', + 'MerchantOrderId' => 'order222', + 'Amount' => 10025, + 'FECCurrencyCode' => '0949', + 'OkUrl' => 'https://domain.com/success', + 'FailUrl' => 'https://domain.com/fail_url', + 'PaymentType' => '1', + ], + ], + ], + '3d' => [ + 'order' => [ + 'ResponseCode' => '00', + 'ResponseMessage' => '', + 'ProvisionNumber' => 'prov-123', + 'MerchantOrderId' => 'order-123', + 'OrderId' => 'bank-123', + 'RRN' => 'rrn-123', + 'Stan' => 'stan-123', + 'HashData' => 'hash-123', + 'MD' => 'ktSVkYJHcHSYM1ibA/nM6nObr8WpWdcw34ziyRQRLv06g7UR2r5LrpLeNvwfBwPz', + ], + 'gatewayUrl' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_3D_SECURE, + 'expected' => [ + 'gateway' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + 'method' => 'POST', + 'inputs' => [ + 'ResponseCode' => '00', + 'ResponseMessage' => '', + 'ProvisionNumber' => 'prov-123', + 'MerchantOrderId' => 'order-123', + 'OrderId' => 'bank-123', + 'RRN' => 'rrn-123', + 'Stan' => 'stan-123', + 'HashData' => 'hash-123', + 'MD' => 'ktSVkYJHcHSYM1ibA/nM6nObr8WpWdcw34ziyRQRLv06g7UR2r5LrpLeNvwfBwPz', + ], + ], + ], + ]; + } +} diff --git a/tests/Unit/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapperTest.php b/tests/Unit/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapperTest.php new file mode 100644 index 00000000..f0efa656 --- /dev/null +++ b/tests/Unit/DataMapper/ResponseDataMapper/VakifKatilimPosResponseDataMapperTest.php @@ -0,0 +1,1133 @@ +createMock(EventDispatcherInterface::class), $crypt); + $this->responseDataMapper = new VakifKatilimPosResponseDataMapper( + $requestDataMapper->getCurrencyMappings(), + $requestDataMapper->getTxTypeMappings(), + $requestDataMapper->getSecureTypeMappings(), + new NullLogger() + ); + } + + /** + * @testWith [null, false] + * ["", false] + * ["HashDataError", false] + * ["00", true] + * + */ + public function testIs3dAuthSuccess(?string $mdStatus, bool $expected): void + { + $actual = $this->responseDataMapper->is3dAuthSuccess($mdStatus); + $this->assertSame($expected, $actual); + } + + + /** + * @testWith [[], null] + * [{"ResponseCode": "00"}, "00"] + * + */ + public function testExtractMdStatus(array $responseData, ?string $expected): void + { + $actual = $this->responseDataMapper->extractMdStatus($responseData); + $this->assertSame($expected, $actual); + } + + /** + * @return void + */ + public function testFormatAmount(): void + { + $class = new \ReflectionObject($this->responseDataMapper); + $method = $class->getMethod('formatAmount'); + $method->setAccessible(true); + $this->assertSame(0.1, $method->invokeArgs($this->responseDataMapper, [10])); + $this->assertSame(1.01, $method->invokeArgs($this->responseDataMapper, [101])); + } + + /** + * @dataProvider paymentTestDataProvider + */ + public function testMapPaymentResponse(string $txType, array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->mapPaymentResponse($responseData, $txType, []); + if ($expectedData['transaction_time'] instanceof \DateTimeImmutable && $actualData['transaction_time'] instanceof \DateTimeImmutable) { + $this->assertSame($expectedData['transaction_time']->format('Ymd'), $actualData['transaction_time']->format('Ymd')); + } else { + $this->assertEquals($expectedData['transaction_time'], $actualData['transaction_time']); + } + + unset($actualData['transaction_time'], $expectedData['transaction_time']); + unset($actualData['all']); + \ksort($expectedData); + \ksort($actualData); + $this->assertSame($expectedData, $actualData); + } + + /** + * @dataProvider threeDPaymentDataProvider + */ + public function testMap3DPaymentData(array $order, string $txType, array $threeDResponseData, array $paymentResponse, array $expectedData): void + { + $actualData = $this->responseDataMapper->map3DPaymentData( + $threeDResponseData, + $paymentResponse, + $txType, + $order + ); + if ($expectedData['transaction_time'] instanceof \DateTimeImmutable && $actualData['transaction_time'] instanceof \DateTimeImmutable) { + $this->assertSame($expectedData['transaction_time']->format('Ymd'), $actualData['transaction_time']->format('Ymd')); + } else { + $this->assertEquals($expectedData['transaction_time'], $actualData['transaction_time']); + } + + unset($actualData['transaction_time'], $expectedData['transaction_time']); + unset($actualData['all'], $actualData['3d_all']); + $this->assertEquals($expectedData, $actualData); + } + + /** + * @dataProvider threeDHostPaymentDataProvider + */ + public function testMap3DHostResponseData(array $order, string $txType, array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->map3DHostResponseData($responseData, $txType, $order); + if ($expectedData['transaction_time'] instanceof \DateTimeImmutable && $actualData['transaction_time'] instanceof \DateTimeImmutable) { + $this->assertSame($expectedData['transaction_time']->format('Ymd'), $actualData['transaction_time']->format('Ymd')); + } else { + $this->assertEquals($expectedData['transaction_time'], $actualData['transaction_time']); + } + + unset($actualData['transaction_time'], $expectedData['transaction_time']); + + unset($actualData['all']); + \ksort($expectedData); + \ksort($actualData); + $this->assertSame($expectedData, $actualData); + } + + public function testMap3DPayResponseData(): void + { + $this->expectException(NotImplementedException::class); + $this->responseDataMapper->map3DPayResponseData([], PosInterface::TX_TYPE_PAY_AUTH, []); + } + + /** + * @dataProvider refundTestDataProvider + */ + public function testMapRefundResponse(array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->mapRefundResponse($responseData); + unset($actualData['all']); + $this->assertSame($expectedData, $actualData); + } + + /** + * @dataProvider cancelTestDataProvider + */ + public function testMapCancelResponse(array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->mapCancelResponse($responseData); + unset($actualData['all']); + $this->assertSame($expectedData, $actualData); + } + + /** + * @dataProvider statusTestDataProvider + */ + public function testMapStatusResponse(array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->mapStatusResponse($responseData); + $this->assertEquals($expectedData['transaction_time'], $actualData['transaction_time']); + $this->assertEquals($expectedData['capture_time'], $actualData['capture_time']); + $this->assertEquals($expectedData['refund_time'], $actualData['refund_time']); + $this->assertEquals($expectedData['cancel_time'], $actualData['cancel_time']); + unset($actualData['transaction_time'], $expectedData['transaction_time']); + unset($actualData['capture_time'], $expectedData['capture_time']); + unset($actualData['refund_time'], $expectedData['refund_time']); + unset($actualData['cancel_time'], $expectedData['cancel_time']); + + unset($actualData['all']); + \ksort($expectedData); + \ksort($actualData); + $this->assertSame($expectedData, $actualData); + } + + /** + * @dataProvider historyTestDataProvider + */ + public function testMapHistoryResponse(array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->mapHistoryResponse($responseData); + + if (count($actualData['transactions']) > 1 + && null !== $actualData['transactions'][0]['transaction_time'] + && null !== $actualData['transactions'][1]['transaction_time'] + ) { + $this->assertGreaterThan( + $actualData['transactions'][0]['transaction_time'], + $actualData['transactions'][1]['transaction_time'], + ); + } + + $this->assertCount($actualData['trans_count'], $actualData['transactions']); + + foreach (array_keys($actualData['transactions']) as $key) { + $this->assertEquals($expectedData['transactions'][$key]['transaction_time'], $actualData['transactions'][$key]['transaction_time'], 'tx: '.$key); + $this->assertEquals($expectedData['transactions'][$key]['capture_time'], $actualData['transactions'][$key]['capture_time'], 'tx: '.$key); + unset($actualData['transactions'][$key]['transaction_time'], $expectedData['transactions'][$key]['transaction_time']); + unset($actualData['transactions'][$key]['capture_time'], $expectedData['transactions'][$key]['capture_time']); + \ksort($actualData['transactions'][$key]); + \ksort($expectedData['transactions'][$key]); + } + + unset($actualData['all']); + $this->assertSame($expectedData, $actualData); + } + + /** + * @dataProvider orderHistoryTestDataProvider + */ + public function testMapOrderHistoryResponse(array $responseData, array $expectedData): void + { + $actualData = $this->responseDataMapper->mapOrderHistoryResponse($responseData); + + if (count($actualData['transactions']) > 1 + && null !== $actualData['transactions'][0]['transaction_time'] + && null !== $actualData['transactions'][1]['transaction_time'] + ) { + $this->assertGreaterThan( + $actualData['transactions'][0]['transaction_time'], + $actualData['transactions'][1]['transaction_time'], + ); + } + + $this->assertCount($actualData['trans_count'], $actualData['transactions']); + + foreach (array_keys($actualData['transactions']) as $key) { + $this->assertEquals($expectedData['transactions'][$key]['transaction_time'], $actualData['transactions'][$key]['transaction_time'], 'tx: '.$key); + $this->assertEquals($expectedData['transactions'][$key]['capture_time'], $actualData['transactions'][$key]['capture_time'], 'tx: '.$key); + unset($actualData['transactions'][$key]['transaction_time'], $expectedData['transactions'][$key]['transaction_time']); + unset($actualData['transactions'][$key]['capture_time'], $expectedData['transactions'][$key]['capture_time']); + \ksort($actualData['transactions'][$key]); + \ksort($expectedData['transactions'][$key]); + } + + unset($actualData['all']); + $this->assertSame($expectedData, $actualData); + } + + public static function paymentTestDataProvider(): iterable + { + yield 'fail_pre_auth' => [ + 'txType' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + 'responseData' => [ + 'VPosMessage' => [ + 'HashData' => 'fY5CK7nmMa9WkVdhV+u2Bp557n4=', + 'MerchantId' => '1', + 'SubMerchantId' => '0', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'HashPassword' => 'kfkdsnskslkclswr9430ır', + 'MerchantOrderId' => '58957265', + 'InstallmentCount' => '0', + 'Amount' => '840', + 'DisplayAmount' => '840', + 'FECAmount' => '0', + 'FECCurrencyCode' => '0949', + 'Products' => '', + 'Addresses' => [ + 'VPosAddressContract' => [ + 'Type' => '1', + 'Name' => 'Mahmut Sami YAZAR', + 'PhoneNumber' => '324234234234', + 'OrderId' => '0', + 'AddressId' => '12', + 'Email' => 'mahmutsamiyazar@hotmail.com', + ], + ], + 'APIVersion' => '1.0.0', + 'CardNumber' => '5353550000958906', + 'CardHolderName' => 'Hasan Karacan', + 'PaymentType' => 'None', + 'DebtId' => '0', + 'SurchargeAmount' => '0', + 'SGKDebtAmount' => '0', + 'InstallmentMaturityCommisionFlag' => '0', + 'TransactionSecurity' => '5', + ], + 'IsEnrolled' => 'true', + 'IsVirtual' => 'false', + 'RRN' => '922810016639', + 'Stan' => '016639', + 'ResponseCode' => '51', + 'ResponseMessage' => 'Limit Yetersiz.', + 'OrderId' => '15188', + 'TransactionTime' => '2019-08-16T10:54:23.81069', + 'MerchantOrderId' => '58957265', + 'BusinessKey' => '0', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expectedData' => [ + 'order_id' => '58957265', + 'remote_order_id' => '15188', + 'transaction_id' => null, + 'transaction_type' => 'pre', + 'transaction_time' => null, + 'currency' => null, + 'amount' => null, + 'payment_model' => 'regular', + 'auth_code' => null, + 'ref_ret_num' => null, + 'proc_return_code' => '51', + 'status' => 'declined', + 'status_detail' => 'reject', + 'error_code' => '51', + 'error_message' => 'Limit Yetersiz.', + 'installment_count' => null, + ], + ]; + + yield 'success1' => [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'responseData' => [ + 'VPosMessage' => [ + 'OrderId' => '4480', + 'OkUrl' => 'http://localhost:10398//ThreeDModel/SuccessXml', + 'FailUrl' => 'http://localhost:10398//ThreeDModel/FailXml', + 'MerchantId' => '80', + 'SubMerchantId' => '0', + 'CustomerId' => '400235', + 'HashPassword' => 'c77dFssAnYSy6O2MJo+5tMYtGVc=', + 'CardNumber' => '5124********1609', + 'BatchID' => '1906', + 'InstallmentCount' => '0', + 'Amount' => '100', + 'MerchantOrderId' => '660723214', + 'FECAmount' => '0', + 'CurrencyCode' => '949', + 'QeryId' => '0', + 'DebtId' => '0', + 'SurchargeAmount' => '0', + 'SGKDebtAmount' => '0', + 'TransactionSecurity' => '0', + ], + 'IsEnrolled' => 'true', + 'ProvisionNumber' => '896626', + 'RRN' => '904115005554', + 'Stan' => '005554', + 'ResponseCode' => '00', + 'ResponseMessage' => 'OTORİZASYON VERİLDİ', + 'OrderId' => '4480', + 'TransactionTime' => '0001-01-01T00:00:00', + 'MerchantOrderId' => '660723214', + 'HashData' => 'I7H/6nwfydM6VcwXsl82mqeC83o=', + ], + 'expectedData' => [ + 'order_id' => '660723214', + 'remote_order_id' => '4480', + 'transaction_id' => '005554', + 'transaction_type' => 'pay', + 'transaction_time' => new \DateTimeImmutable(), + 'currency' => 'TRY', + 'amount' => 1.0, + 'payment_model' => 'regular', + 'auth_code' => '896626', + 'ref_ret_num' => '904115005554', + 'proc_return_code' => '00', + 'status' => 'approved', + 'status_detail' => 'approved', + 'error_code' => null, + 'error_message' => null, + 'masked_number' => '5124********1609', + 'installment_count' => 0, + ], + ]; + } + + + public static function threeDPaymentDataProvider(): array + { + return [ + 'success1' => [ + 'order' => [ + 'currency' => PosInterface::CURRENCY_TRY, + ], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'threeDResponseData' => [ + 'ResponseCode' => '00', + 'ResponseMessage' => '', + 'ProvisionNumber' => '', + 'MerchantOrderId' => '15161', + 'OrderId' => '0', + 'RRN' => '904115005554', + 'Stan' => '005554', + 'HashData' => 'mOw0JGvy1JVWqDDmFyaDTvKz9Fk=', + 'MD' => 'ktSVkYJHcHSYM1ibA/nM6nObr8WpWdcw34ziyRQRLv06g7UR2r5LrpLeNvwfBwPz', + ], + 'paymentData' => [ + 'VPosMessageContract' => [ + 'OkUrl' => 'http://localhost/ThreeDModel/Approval', + 'FailUrl' => 'http://localhost/ThreeDModel/Fail', + 'HashData' => 'DvAUXMvYV4ex5m16mMezEl+kxrI=', + 'MerchantId' => '1', + 'SubMerchantId' => '0', + 'CustomerId' => '936', + 'UserName' => 'APIUSER', + 'HashPassword' => 'kfkdsnskslkclswr9430ır', + 'MerchantOrderId' => '1554891870', + 'InstallmentCount' => '0', + 'Amount' => '111', + 'FECAmount' => '0', + 'AdditionalData' => [ + 'AdditionalDataList' => [ + 'VPosAdditionalData' => [ + 'Key' => 'MD', + 'Data' => 'vygnTBD4smBxAOlDsgbaOQ==', + ], + ], + ], + 'Products' => '', + 'Addresses' => '', + 'PaymentType' => '1', + 'DebtId' => '0', + 'SurchargeAmount' => '0', + 'SGKDebtAmount' => '0', + 'InstallmentMaturityCommisionFlag' => '0', + 'TransactionSecurity' => '3', + ], + 'IsEnrolled' => 'true', + 'IsVirtual' => 'false', + 'RRN' => '922709016599', + 'Stan' => '016599', + 'ResponseCode' => '00', + 'ResponseMessage' => 'Provizyon Alindi.', + 'OrderId' => '15161', + 'TransactionTime' => '00010101T00:00:00', + 'MerchantOrderId' => '1554891870', + 'HashData' => 'bcCqBe4hbElPOVYtfvsw7M44usQ=', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expectedData' => [ + 'transaction_security' => null, + 'md_status' => null, + 'tx_status' => null, + 'md_error_message' => null, + 'transaction_id' => '016599', + 'transaction_type' => 'pay', + 'transaction_time' => new \DateTimeImmutable(), + 'auth_code' => null, + 'ref_ret_num' => '922709016599', + 'error_code' => null, + 'error_message' => null, + 'remote_order_id' => '15161', + 'order_id' => '1554891870', + 'proc_return_code' => '00', + 'status' => 'approved', + 'status_detail' => 'approved', + 'amount' => 1.11, + 'currency' => PosInterface::CURRENCY_TRY, + 'masked_number' => null, + 'payment_model' => '3d', + 'installment_count' => 0, + ], + ], + '3d_auth_fail1' => [ + 'order' => [ + 'currency' => PosInterface::CURRENCY_TRY, + ], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'threeDResponseData' => [ + 'ResponseCode' => '05', + 'ResponseMessage' => '', + 'ProvisionNumber' => '', + 'MerchantOrderId' => '15161', + 'OrderId' => '0', + 'RRN' => '', + 'Stan' => '', + 'HashData' => 'mOw0JGvy1JVWqDDmFyaDTvKz9Fk=', + 'MD' => 'ktSVkYJHcHSYM1ibA/nM6nObr8WpWdcw34ziyRQRLv06g7UR2r5LrpLeNvwfBwPz', + ], + 'paymentData' => [], + 'expectedData' => [ + 'transaction_security' => null, + 'md_status' => null, + 'tx_status' => null, + 'md_error_message' => null, + 'transaction_id' => null, + 'transaction_type' => 'pay', + 'transaction_time' => null, + 'auth_code' => null, + 'ref_ret_num' => null, + 'error_code' => '05', + 'error_message' => null, + 'order_id' => '15161', + 'proc_return_code' => '05', + 'status' => 'declined', + 'status_detail' => '05', + 'amount' => null, + 'currency' => null, + 'payment_model' => '3d', + 'installment_count' => null, + ], + ], + ]; + } + + public static function statusTestDataProvider(): iterable + { + yield 'fail1' => [ + 'responseData' => [ + 'VPosOrderData' => null, + 'ResponseCode' => 'MerchantNotDefined', + 'ResponseMessage' => 'Uye isyeri kullanici tanimi bulunamadi.', + 'MerchantOrderId' => '202403290D3D', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expectedData' => [ + 'auth_code' => null, + 'capture' => null, + 'capture_amount' => null, + 'currency' => null, + 'error_code' => 'MerchantNotDefined', + 'error_message' => 'Uye isyeri kullanici tanimi bulunamadi.', + 'first_amount' => null, + 'installment_count' => null, + 'masked_number' => null, + 'order_id' => '202403290D3D', + 'order_status' => null, + 'proc_return_code' => 'MerchantNotDefined', + 'ref_ret_num' => null, + 'refund_amount' => null, + 'status' => 'declined', + 'status_detail' => null, + 'transaction_id' => null, + 'transaction_type' => null, + 'transaction_time' => null, + 'capture_time' => null, + 'refund_time' => null, + 'cancel_time' => null, + ], + ]; + + yield 'success1' => [ + 'responseData' => [ + 'VPosOrderData' => [ + 'OrderContract' => [ + 'OrderId' => '12743', + 'MerchantOrderId' => '1995434716', + 'MerchantId' => '1', + 'PosTerminalId' => '111111', + 'OrderStatus' => '1', + 'OrderStatusDescription' => '', + 'OrderType' => '1', + 'OrderTypeDescription' => '', + 'TransactionStatus' => '1', + 'TransactionStatusDescription' => 'Basarili', + 'LastOrderStatus' => '1', + 'LastOrderStatusDescription' => '', + 'EndOfDayStatus' => '1', + 'EndOfDayStatusDescription' => 'Acik', + 'FEC' => '0949', + 'FecDescription' => 'TRY', + 'TransactionSecurity' => '1', + 'TransactionSecurityDescription' => "3d'siz islem", + 'CardHolderName' => 'Hasan Karacan', + 'CardType' => 'MasterCard', + 'CardNumber' => '5353********7017', + 'OrderDate' => '2020-12-24T09:21:41.55', + 'FirstAmount' => '9.30', + 'FECAmount' => '0.00', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'InstallmentCount' => '0', + 'ResponseCode' => '00', + 'ResponseExplain' => 'Provizyon alındı.', + 'ProvNumber' => '043290', + 'RRN' => '035909014127', + 'Stan' => '014127', + 'MerchantUserName' => 'USERNAME', + 'BatchId' => '69', + ], + ], + 'ResponseCode' => '00', + 'ResponseMessage' => '', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expectedData' => [ + 'auth_code' => '043290', + 'capture' => false, + 'capture_amount' => 0, + 'currency' => 'TRY', + 'error_code' => null, + 'error_message' => null, + 'first_amount' => 9.3, + 'installment_count' => 0, + 'masked_number' => '5353********7017', + 'order_id' => '1995434716', + 'order_status' => null, + 'payment_model' => null, + 'proc_return_code' => '00', + 'ref_ret_num' => '035909014127', + 'refund_amount' => null, + 'remote_order_id' => '12743', + 'status' => 'approved', + 'status_detail' => 'approved', + 'transaction_id' => '014127', + 'transaction_type' => null, + 'transaction_time' => new \DateTimeImmutable('2020-12-24T09:21:41.55'), + 'capture_time' => null, + 'refund_time' => null, + 'cancel_time' => null, + ], + ]; + } + + public static function cancelTestDataProvider(): iterable + { + yield 'success1' => [ + 'responseData' => [ + 'VPosMessage' => [ + 'HashData' => 'I7H/6nwfydM6VcwXsl82mqeC83o=', + 'MerchantId' => '1', + 'SubMerchantId' => '0', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'CustomerIPAddress' => '', + 'BatchId' => '', + 'MerchantOrderId' => '2023070849CD', + 'InstallmentCount' => '0', + 'Amount' => '100', + 'DisplayAmount' => '100', + 'FECAmount' => '', + 'FECCurrencyCode' => '0949', + 'Addresses' => [ + 'VPosAddressContract' => [ + 'Type' => '', + 'Name' => '', + 'PhoneNumber' => '', + 'OrderId' => '', + 'AddressId' => '', + 'Email' => '', + ], + ], + 'APIVersion' => '1.0.0', + 'PaymentType' => '', + 'SurchargeAmount' => '', + 'SGKDebtAmount' => '', + 'InstallmentMaturityCommisionFlag' => '', + 'TransactionSecurity' => '', + ], + 'RRN' => '904115005554', + 'Stan' => '005554', + 'IsEnrolled' => 'false', + 'IsVirtual' => 'false', + 'ResponseCode' => '00', + 'ResponseMessage' => 'OTORİZASYON VERİLDİ', + 'OrderId' => '114293600', + 'TransactionTime' => '2023-07-08T23:45:15.797', + 'BusinessKey' => '202208456498416947', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expectedData' => [ + 'order_id' => '2023070849CD', + 'auth_code' => null, + 'proc_return_code' => '00', + 'transaction_id' => '005554', + 'currency' => PosInterface::CURRENCY_TRY, + 'error_message' => null, + 'ref_ret_num' => '904115005554', + 'status' => 'approved', + 'error_code' => null, + 'status_detail' => null, + 'remote_order_id' => '114293600', + ], + ]; + } + + public static function refundTestDataProvider(): iterable + { + yield 'success1' => [ + 'responseData' => [ + 'VPosMessage' => [ + 'HashData' => 'I7H/6nwfydM6VcwXsl82mqeC83o=', + 'MerchantId' => '1', + 'SubMerchantId' => '0', + 'CustomerId' => '11111', + 'UserName' => 'APIUSER', + 'CustomerIPAddress' => '', + 'OrderId' => '114293600', + 'BatchId' => '', + 'MerchantOrderId' => '2023070849CD', + 'InstallmentCount' => '0', + 'Amount' => '100', + 'DisplayAmount' => '100', + 'FECAmount' => '', + 'FECCurrencyCode' => '0949', + 'Products' => '', + 'Addresses' => [ + 'VPosAddressContract' => [ + 'Type' => '', + 'Name' => ' ', + 'PhoneNumber' => '', + 'OrderId' => '', + 'AddressId' => '', + 'Email' => ' ', + ], + ], + 'APIVersion' => '1.0.0', + 'PaymentType' => '1', + 'SurchargeAmount' => '', + 'SGKDebtAmount' => '', + 'InstallmentMaturityCommisionFlag' => '', + 'TransactionSecurity' => '', + ], + 'IsEnrolled' => 'false', + 'IsVirtual' => 'false', + 'RRN' => '904115005554', + 'Stan' => '005554', + 'ResponseCode' => '00', + 'ResponseMessage' => '', + 'OrderId' => '114293600', + 'TransactionTime' => '2023-07-08T23:45:15.797', + 'MerchantOrderId' => '2023070849CD', + 'BusinessKey' => '202208456498416947', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expectedData' => [ + 'order_id' => '2023070849CD', + 'auth_code' => null, + 'proc_return_code' => '00', + 'transaction_id' => '005554', + 'currency' => PosInterface::CURRENCY_TRY, + 'error_message' => null, + 'ref_ret_num' => '904115005554', + 'status' => 'approved', + 'error_code' => null, + 'status_detail' => null, + 'remote_order_id' => '114293600', + ], + ]; + } + + public static function historyTestDataProvider(): array + { + return [ + [ + 'input' => [ + 'VPosOrderData' => [ + 'OrderContract' => [ + [ + 'OrderId' => '12754', + 'MerchantOrderId' => '709834990', + 'MerchantId' => '1', + 'PosTerminalId' => '111111', + 'OrderStatus' => '1', + 'OrderStatusDescription' => 'Satis', + 'OrderType' => '1', + 'OrderTypeDescription' => 'Pesin', + 'TransactionStatus' => '2', + 'TransactionStatusDescription' => 'Basarisiz', + 'LastOrderStatus' => '1', + 'LastOrderStatusDescription' => 'Satis', + 'EndOfDayStatus' => '1', + 'EndOfDayStatusDescription' => 'Acik', + 'FEC' => '0949', + 'FecDescription' => 'TRY', + 'TransactionSecurity' => '5', + 'TransactionSecurityDescription' => '', + 'CardHolderName' => 'Hasan Karacan', + 'CardType' => 'MasterCard', + 'CardNumber' => '5353********3233', + 'OrderDate' => '2020-12-25T12:13:35.74', + 'TranAmount' => '3.90', + 'FirstAmount' => '3.90', + 'FECAmount' => '0.00', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'InstallmentCount' => '0', + 'ResponseCode' => '05', + 'ResponseExplain' => 'Hata Kodu5', + 'ProvNumber' => '', + 'RRN' => '03611114146', + 'Stan' => '012246', + 'MerchantUserName' => 'USERNAME', + 'BatchId' => '73', + ], + [ + 'OrderId' => '12753', + 'MerchantOrderId' => '424636131', + 'MerchantId' => '1', + 'PosTerminalId' => '111111', + 'OrderStatus' => '1', + 'OrderStatusDescription' => 'Satis', + 'OrderType' => '1', + 'OrderTypeDescription' => 'Pesin', + 'TransactionStatus' => '1', + 'TransactionStatusDescription' => 'Basarili', + 'LastOrderStatus' => '1', + 'LastOrderStatusDescription' => 'Satis', + 'EndOfDayStatus' => '2', + 'EndOfDayStatusDescription' => 'Kapali', + 'FEC' => '0949', + 'FecDescription' => 'TRY', + 'TransactionSecurity' => '5', + 'TransactionSecurityDescription' => '', + 'CardHolderName' => 'Hasan Karacan', + 'CardType' => 'MasterCard', + 'CardNumber' => '5353********8906', + 'OrderDate' => '2020-12-25T08:41:40.947', + 'FirstAmount' => '2.70', + 'FECAmount' => '0.00', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'InstallmentCount' => '0', + 'ResponseCode' => '00', + 'ResponseExplain' => 'Provizyon alındı.', + 'ProvNumber' => '831168', + 'RRN' => '036008014143', + 'Stan' => '014143', + 'MerchantUserName' => 'USERNAME', + 'BatchId' => '72', + ], + ], + ], + 'ResponseCode' => '00', + 'ResponseMessage' => '', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expected' => [ + 'proc_return_code' => '00', + 'error_code' => null, + 'error_message' => null, + 'status' => 'approved', + 'status_detail' => 'approved', + 'trans_count' => 2, + 'transactions' => [ + [ + 'auth_code' => '831168', + 'proc_return_code' => '00', + 'transaction_id' => '014143', + 'transaction_time' => new \DateTimeImmutable('2020-12-25T08:41:40.947'), + 'capture_time' => null, + 'error_message' => null, + 'ref_ret_num' => '036008014143', + 'order_status' => 'Satis', + 'transaction_type' => null, + 'first_amount' => 2.7, + 'capture_amount' => 0, + 'status' => 'approved', + 'error_code' => null, + 'status_detail' => 'approved', + 'capture' => false, + 'currency' => 'TRY', + 'masked_number' => '5353********8906', + 'order_id' => '424636131', + 'remote_order_id' => '12753', + 'payment_model' => 'regular', + 'installment_count' => 0, + ], + [ + 'auth_code' => null, + 'proc_return_code' => '05', + 'transaction_id' => '012246', + 'transaction_time' => new \DateTimeImmutable('2020-12-25T12:13:35.74'), + 'capture_time' => null, + 'error_message' => 'Hata Kodu5', + 'ref_ret_num' => '03611114146', + 'order_status' => null, + 'transaction_type' => null, + 'first_amount' => null, + 'capture_amount' => null, + 'status' => 'declined', + 'error_code' => '05', + 'status_detail' => '05', + 'capture' => null, + 'currency' => 'TRY', + 'masked_number' => null, + 'order_id' => '709834990', + 'remote_order_id' => '12754', + 'payment_model' => 'regular', + ], + ], + ], + ], + ]; + } + + public static function orderHistoryTestDataProvider(): array + { + return [ + 'fail1' => [ + 'input' => [ + 'VPosOrderData' => '', + 'ResponseCode' => 'MerchantNotDefined', + 'ResponseMessage' => 'Uye isyeri kullanici tanimi bulunamadi.', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expected' => [ + 'proc_return_code' => 'MerchantNotDefined', + 'order_id' => null, + 'remote_order_id' => null, + 'error_code' => 'MerchantNotDefined', + 'error_message' => 'Uye isyeri kullanici tanimi bulunamadi.', + 'status' => 'declined', + 'status_detail' => 'MerchantNotDefined', + 'trans_count' => 0, + 'transactions' => [], + ], + ], + 'success1' => [ + 'input' => [ + 'VPosOrderData' => [ + 'OrderContract' => [ + [ + 'OrderId' => '12754', + 'MerchantOrderId' => '709834990', + 'MerchantId' => '1', + 'PosTerminalId' => '111111', + 'OrderStatus' => '1', + 'OrderStatusDescription' => 'Satis', + 'OrderType' => '1', + 'OrderTypeDescription' => 'Pesin', + 'TransactionStatus' => '2', + 'TransactionStatusDescription' => 'Basarisiz', + 'LastOrderStatus' => '1', + 'LastOrderStatusDescription' => 'Satis', + 'EndOfDayStatus' => '1', + 'EndOfDayStatusDescription' => 'Acik', + 'FEC' => '0949', + 'FecDescription' => 'TRY', + 'TransactionSecurity' => '5', + 'TransactionSecurityDescription' => '', + 'CardHolderName' => 'Hasan Karacan', + 'CardType' => 'MasterCard', + 'CardNumber' => '5353********3233', + 'OrderDate' => '2020-12-25T12:13:35.74', + 'TranAmount' => '3.90', + 'FirstAmount' => '3.90', + 'FECAmount' => '0.00', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'InstallmentCount' => '0', + 'ResponseCode' => '05', + 'ResponseExplain' => 'Hata Kodu5', + 'ProvNumber' => '', + 'RRN' => '03611114146', + 'Stan' => '012246', + 'MerchantUserName' => 'USERNAME', + 'BatchId' => '73', + ], + [ + 'OrderId' => '12754', + 'MerchantOrderId' => '709834990', + 'MerchantId' => '1', + 'PosTerminalId' => '111111', + 'OrderStatus' => '1', + 'OrderStatusDescription' => 'Satis', + 'OrderType' => '1', + 'OrderTypeDescription' => 'Pesin', + 'TransactionStatus' => '1', + 'TransactionStatusDescription' => 'Basarili', + 'LastOrderStatus' => '1', + 'LastOrderStatusDescription' => 'Satis', + 'EndOfDayStatus' => '2', + 'EndOfDayStatusDescription' => 'Kapali', + 'FEC' => '0949', + 'FecDescription' => 'TRY', + 'TransactionSecurity' => '5', + 'TransactionSecurityDescription' => '', + 'CardHolderName' => 'Hasan Karacan', + 'CardType' => 'MasterCard', + 'CardNumber' => '5353********8906', + 'OrderDate' => '2020-12-25T08:41:40.947', + 'FirstAmount' => '2.70', + 'FECAmount' => '0.00', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'InstallmentCount' => '0', + 'ResponseCode' => '00', + 'ResponseExplain' => 'Provizyon alındı.', + 'ProvNumber' => '831168', + 'RRN' => '036008014143', + 'Stan' => '014143', + 'MerchantUserName' => 'USERNAME', + 'BatchId' => '72', + ], + ], + ], + 'ResponseCode' => '00', + 'ResponseMessage' => '', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + 'expected' => [ + 'proc_return_code' => '00', + 'order_id' => '709834990', + 'remote_order_id' => '12754', + 'error_code' => null, + 'error_message' => null, + 'status' => 'approved', + 'status_detail' => 'approved', + 'trans_count' => 2, + 'transactions' => [ + [ + 'auth_code' => '831168', + 'proc_return_code' => '00', + 'transaction_id' => '014143', + 'transaction_time' => new \DateTimeImmutable('2020-12-25T08:41:40.947'), + 'capture_time' => null, + 'error_message' => null, + 'ref_ret_num' => '036008014143', + 'order_status' => 'Satis', + 'transaction_type' => null, + 'first_amount' => 2.7, + 'capture_amount' => 0, + 'status' => 'approved', + 'error_code' => null, + 'status_detail' => 'approved', + 'capture' => false, + 'currency' => 'TRY', + 'masked_number' => '5353********8906', + 'payment_model' => 'regular', + 'installment_count' => 0, + ], + [ + 'auth_code' => null, + 'proc_return_code' => '05', + 'transaction_id' => '012246', + 'transaction_time' => new \DateTimeImmutable('2020-12-25T12:13:35.74'), + 'capture_time' => null, + 'error_message' => 'Hata Kodu5', + 'ref_ret_num' => '03611114146', + 'order_status' => null, + 'transaction_type' => null, + 'first_amount' => null, + 'capture_amount' => null, + 'status' => 'declined', + 'error_code' => '05', + 'status_detail' => '05', + 'capture' => null, + 'currency' => 'TRY', + 'masked_number' => null, + 'payment_model' => 'regular', + ], + ], + ], + ], + ]; + } + + public static function threeDHostPaymentDataProvider(): array + { + return [ + 'success' => [ + 'order' => [], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentData' => [ + 'ResponseCode' => '00', + 'ResponseMessage' => '', + 'ProvisionNumber' => 'prov-123', + 'MerchantOrderId' => '15161', + 'OrderId' => 'o-123', + 'RRN' => '904115005554', + 'Stan' => '005554', + 'HashData' => 'mOw0JGvy1JVWqDDmFyaDTvKz9Fk=', + 'MD' => 'ktSVkYJHcHSYM1ibA/nM6nObr8WpWdcw34ziyRQRLv06g7UR2r5LrpLeNvwfBwPz', + ], + 'expectedData' => [ + 'amount' => null, + 'auth_code' => 'prov-123', + 'currency' => null, + 'error_code' => null, + 'error_message' => null, + 'installment_count' => null, + 'md_error_message' => null, + 'md_status' => null, + 'order_id' => '15161', + 'payment_model' => '3d_host', + 'proc_return_code' => '00', + 'ref_ret_num' => '904115005554', + 'remote_order_id' => 'o-123', + 'status' => 'approved', + 'status_detail' => 'approved', + 'transaction_id' => '005554', + 'transaction_time' => new \DateTimeImmutable(), + 'transaction_type' => 'pay', + 'transaction_security' => null, + ], + ], + 'fail' => [ + 'order' => [], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentData' => [ + 'ResponseCode' => '05', + 'ResponseMessage' => 'error abcd', + 'MerchantOrderId' => '15161', + 'OrderId' => 'o-123', + ], + 'expectedData' => [ + 'amount' => null, + 'auth_code' => null, + 'currency' => null, + 'error_code' => '05', + 'error_message' => 'error abcd', + 'installment_count' => null, + 'md_error_message' => null, + 'md_status' => null, + 'order_id' => null, + 'payment_model' => '3d_host', + 'proc_return_code' => '05', + 'ref_ret_num' => null, + 'status' => 'declined', + 'status_detail' => '05', + 'transaction_id' => null, + 'transaction_type' => 'pay', + 'transaction_time' => null, + 'transaction_security' => null, + ], + ], + ]; + } +} diff --git a/tests/Unit/Factory/AccountFactoryTest.php b/tests/Unit/Factory/AccountFactoryTest.php new file mode 100644 index 00000000..25d01f94 --- /dev/null +++ b/tests/Unit/Factory/AccountFactoryTest.php @@ -0,0 +1,36 @@ +assertSame('1', $account->getClientId()); + $this->assertSame('APIUSER', $account->getUsername()); + $this->assertSame('11111', $account->getCustomerId()); + $this->assertSame('kdsnsksl', $account->getStoreKey()); + $this->assertSame('SUB1', $account->getSubMerchantId()); + } +} diff --git a/tests/Unit/Gateways/VakifKatilimTest.php b/tests/Unit/Gateways/VakifKatilimTest.php new file mode 100644 index 00000000..c76c7253 --- /dev/null +++ b/tests/Unit/Gateways/VakifKatilimTest.php @@ -0,0 +1,888 @@ +config = [ + 'name' => 'Vakıf Katılım', + 'class' => VakifKatilimPos::class, + 'gateway_endpoints' => [ + 'payment_api' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home', + 'gateway_3d' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + ], + ]; + + $this->account = AccountFactory::createKuveytPosAccount( + 'vakif-katilim', + '1', + 'APIUSER', + '11111', + 'kdsnsksl', + ); + + $this->order = [ + 'id' => '2020110828BC', + 'amount' => 10.01, + 'installment' => '0', + 'currency' => PosInterface::CURRENCY_TRY, + 'success_url' => 'http://localhost/finansbank-payfor/3d/response.php', + 'fail_url' => 'http://localhost/finansbank-payfor/3d/response.php', + ]; + + $this->requestMapperMock = $this->createMock(VakifKatilimPosRequestDataMapper::class); + $this->responseMapperMock = $this->createMock(ResponseDataMapperInterface::class); + $this->serializerMock = $this->createMock(SerializerInterface::class); + $this->cryptMock = $this->createMock(CryptInterface::class); + $this->httpClientMock = $this->createMock(HttpClient::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); + $this->eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); + + $this->requestMapperMock->expects(self::any()) + ->method('getCrypt') + ->willReturn($this->cryptMock); + + $this->pos = new VakifKatilimPos( + $this->config, + $this->account, + $this->requestMapperMock, + $this->responseMapperMock, + $this->serializerMock, + $this->eventDispatcherMock, + $this->httpClientMock, + $this->loggerMock, + ); + + $this->pos->setTestMode(true); + + $this->card = CreditCardFactory::createForGateway( + $this->pos, + '4155650100416111', + 25, + 1, + '123', + 'John Doe', + CreditCardInterface::CARD_TYPE_VISA + ); + } + + /** + * @return void + */ + public function testInit(): void + { + $this->requestMapperMock->expects(self::once()) + ->method('getCurrencyMappings') + ->willReturn([PosInterface::CURRENCY_TRY => '0949']); + $this->assertSame([PosInterface::CURRENCY_TRY], $this->pos->getCurrencies()); + $this->assertSame($this->config, $this->pos->getConfig()); + $this->assertSame($this->account, $this->pos->getAccount()); + } + + /** + * @dataProvider getApiUrlDataProvider + */ + public function testGetApiURL(string $txType, ?string $orderTxType, string $paymentModel, string $expected): void + { + $actual = $this->pos->getApiURL($txType, $paymentModel, $orderTxType); + + $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 + */ + public function testGetCommon3DFormDataSuccessResponse(): void + { + $response = 'bank-api-html-response'; + $txType = PosInterface::TX_TYPE_PAY_AUTH; + $paymentModel = PosInterface::MODEL_3D_SECURE; + $card = $this->card; + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['form-data'], $txType) + ->willReturn('encoded-request-data'); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with($response, $txType) + ->willReturn(['form_inputs' => ['form-inputs'], 'gateway' => 'form-action-url']); + $this->prepareClient( + $this->httpClientMock, + $response, + 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + [ + 'body' => 'encoded-request-data', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ], + ); + + $this->eventDispatcherMock->expects(self::once()) + ->method('dispatch'); + + $this->requestMapperMock->expects(self::once()) + ->method('create3DEnrollmentCheckRequestData') + ->with( + $this->pos->getAccount(), + $this->order, + $paymentModel, + $txType, + $card + ) + ->willReturn(['form-data']); + + $this->requestMapperMock->expects(self::once()) + ->method('create3DFormData') + ->with( + $this->pos->getAccount(), + ['form-inputs'], + $paymentModel, + $txType, + 'form-action-url', + $card + ) + ->willReturn(['3d-form-data']); + $result = $this->pos->get3DFormData($this->order, $paymentModel, $txType, $card); + + $this->assertSame(['3d-form-data'], $result); + } + + /** + * @return void + */ + public function testGet3DHostFormData(): void + { + $order = ['id' => '124']; + $paymentModel = PosInterface::MODEL_3D_HOST; + $txType = PosInterface::TX_TYPE_PAY_AUTH; + + $this->requestMapperMock->expects(self::once()) + ->method('create3DFormData') + ->with( + $this->pos->getAccount(), + $order, + $paymentModel, + $txType, + 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelPayGate', + ) + ->willReturn(['formData']); + + $actual = $this->pos->get3DFormData($order, $paymentModel, $txType); + + $this->assertSame(['formData'], $actual); + } + + /** + * @dataProvider make3DPaymentDataProvider + */ + public function testMake3DPayment( + array $order, + string $txType, + Request $request, + array $paymentResponse, + array $expectedResponse, + bool $is3DSuccess, + bool $isSuccess + ): void + { + if ($is3DSuccess) { + $this->cryptMock->expects(self::once()) + ->method('check3DHash') + ->with($this->account, $request->request->all()) + ->willReturn(true); + } + + $this->responseMapperMock->expects(self::once()) + ->method('extractMdStatus') + ->with($request->request->all()) + ->willReturn('3d-status'); + + $this->responseMapperMock->expects(self::once()) + ->method('is3dAuthSuccess') + ->with('3d-status') + ->willReturn($is3DSuccess); + + $create3DPaymentRequestData = [ + 'create3DPaymentRequestData', + ]; + + + if ($is3DSuccess) { + $this->requestMapperMock->expects(self::once()) + ->method('create3DPaymentRequestData') + ->with($this->account, $order, $txType, $request->request->all()) + ->willReturn($create3DPaymentRequestData); + $this->prepareClient( + $this->httpClientMock, + 'response-body', + 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelProvisionGate', + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with($create3DPaymentRequestData, $txType) + ->willReturn('request-body'); + + $this->serializerMock->expects(self::exactly(1)) + ->method('decode') + ->willReturnMap([ + [ + 'response-body', + $txType, + $paymentResponse, + ], + ]); + + $this->responseMapperMock->expects(self::once()) + ->method('map3DPaymentData') + ->with($request->request->all(), $paymentResponse, $txType, $order) + ->willReturn($expectedResponse); + } else { + $this->responseMapperMock->expects(self::once()) + ->method('map3DPaymentData') + ->with($request->request->all(), null, $txType, $order) + ->willReturn($expectedResponse); + $this->requestMapperMock->expects(self::never()) + ->method('create3DPaymentRequestData'); + $this->serializerMock->expects(self::never()) + ->method('encode'); + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with(urldecode($request->request->get('AuthenticationResponse')), $txType) + ->willReturn($request->request->all()); + } + + $this->pos->make3DPayment($request, $order, $txType); + + $result = $this->pos->getResponse(); + $this->assertSame($expectedResponse, $result); + $this->assertSame($isSuccess, $this->pos->isSuccess()); + } + + public function testMake3DHostPayment(): void + { + $this->cryptMock->expects(self::never()) + ->method('check3DHash'); + + $responseData = ['$responseData']; + $request = Request::create('', 'POST', $responseData); + $order = ['id' => '123']; + $txType = PosInterface::TX_TYPE_PAY_AUTH; + + $this->responseMapperMock->expects(self::once()) + ->method('map3DHostResponseData') + ->with($request->request->all(), $txType, $order) + ->willReturn(['status' => 'approved']); + + $pos = $this->pos; + + $pos->make3DHostPayment($request, $order, $txType); + + $result = $pos->getResponse(); + $this->assertSame(['status' => 'approved'], $result); + $this->assertTrue($pos->isSuccess()); + } + + public function testMake3DPayPayment(): void + { + $request = Request::create('', 'POST'); + + $this->expectException(UnsupportedPaymentModelException::class); + $this->pos->make3DPayPayment($request, [], PosInterface::TX_TYPE_PAY_AUTH); + } + + /** + * @dataProvider makeRegularPaymentDataProvider + */ + public function testMakeRegularPayment(array $order, string $txType, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $card = $this->card; + $this->requestMapperMock->expects(self::once()) + ->method('createNonSecurePaymentRequestData') + ->with($account, $order, $txType, $card) + ->willReturn(['createNonSecurePaymentRequestData']); + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createNonSecurePaymentRequestData'], $txType) + ->willReturn('request-body'); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['paymentResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapPaymentResponse') + ->with(['paymentResponse'], $txType, $order) + ->willReturn(['result']); + + $this->pos->makeRegularPayment($order, $card, $txType); + } + + /** + * @dataProvider makeRegularPostAuthPaymentDataProvider + */ + public function testMakeRegularPostAuthPayment(array $order, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $txType = PosInterface::TX_TYPE_PAY_POST_AUTH; + + $this->requestMapperMock->expects(self::once()) + ->method('createNonSecurePostAuthPaymentRequestData') + ->with($account, $order) + ->willReturn(['createNonSecurePostAuthPaymentRequestData']); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createNonSecurePostAuthPaymentRequestData'], $txType) + ->willReturn('request-body'); + + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['paymentResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapPaymentResponse') + ->with(['paymentResponse'], $txType, $order) + ->willReturn(['result']); + + $this->pos->makeRegularPostPayment($order); + } + + + /** + * @dataProvider statusRequestDataProvider + */ + public function testStatusRequest(array $order, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $txType = PosInterface::TX_TYPE_STATUS; + + $this->requestMapperMock->expects(self::once()) + ->method('createStatusRequestData') + ->with($account, $order) + ->willReturn(['createStatusRequestData']); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createStatusRequestData'], $txType) + ->willReturn('request-body'); + + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['decodedResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapStatusResponse') + ->with(['decodedResponse']) + ->willReturn(['result']); + + $this->pos->status($order); + } + + /** + * @dataProvider cancelRequestDataProvider + */ + public function testCancelRequest(array $order, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $txType = PosInterface::TX_TYPE_CANCEL; + + $this->requestMapperMock->expects(self::once()) + ->method('createCancelRequestData') + ->with($account, $order) + ->willReturn(['createCancelRequestData']); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createCancelRequestData'], $txType) + ->willReturn('request-body'); + + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['decodedResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapCancelResponse') + ->with(['decodedResponse']) + ->willReturn(['result']); + + $this->pos->cancel($order); + } + + /** + * @dataProvider refundRequestDataProvider + */ + public function testRefundRequest(array $order, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $txType = PosInterface::TX_TYPE_REFUND; + + $this->requestMapperMock->expects(self::once()) + ->method('createRefundRequestData') + ->with($account, $order) + ->willReturn(['createRefundRequestData']); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createRefundRequestData'], $txType) + ->willReturn('request-body'); + + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['decodedResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapRefundResponse') + ->with(['decodedResponse']) + ->willReturn(['result']); + + $this->pos->refund($order); + } + + + /** + * @dataProvider historyRequestDataProvider + */ + public function testHistoryRequest(array $order, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $txType = PosInterface::TX_TYPE_HISTORY; + + $this->requestMapperMock->expects(self::once()) + ->method('createHistoryRequestData') + ->with($account, $order) + ->willReturn(['createHistoryRequestData']); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createHistoryRequestData'], $txType) + ->willReturn('request-body'); + + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['decodedResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapHistoryResponse') + ->with(['decodedResponse']) + ->willReturn(['result']); + + $this->pos->history($order); + } + + /** + * @dataProvider orderHistoryRequestDataProvider + */ + public function testOrderHistoryRequest(array $order, string $apiUrl): void + { + $account = $this->pos->getAccount(); + $txType = PosInterface::TX_TYPE_ORDER_HISTORY; + + $this->requestMapperMock->expects(self::once()) + ->method('createOrderHistoryRequestData') + ->with($account, $order) + ->willReturn(['createOrderHistoryRequestData']); + + $this->serializerMock->expects(self::once()) + ->method('encode') + ->with(['createOrderHistoryRequestData'], $txType) + ->willReturn('request-body'); + + $this->prepareClient( + $this->httpClientMock, + 'response-body', + $apiUrl, + [ + 'body' => 'request-body', + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF-8', + ], + ] + ); + + $this->serializerMock->expects(self::once()) + ->method('decode') + ->with('response-body', $txType) + ->willReturn(['decodedResponse']); + + $this->responseMapperMock->expects(self::once()) + ->method('mapOrderHistoryResponse') + ->with(['decodedResponse']) + ->willReturn(['result']); + + $this->pos->orderHistory($order); + } + + public static function make3DPaymentDataProvider(): array + { + return [ + 'auth_fail' => [ + 'order' => VakifKatilimPosResponseDataMapperTest::threeDPaymentDataProvider()['success1']['order'], + 'txType' => VakifKatilimPosResponseDataMapperTest::threeDPaymentDataProvider()['success1']['txType'], + 'request' => Request::create( + '', + 'POST', + VakifKatilimPosResponseDataMapperTest::threeDPaymentDataProvider()['success1']['threeDResponseData'] + ), + 'paymentResponse' => VakifKatilimPosResponseDataMapperTest::threeDPaymentDataProvider()['success1']['paymentData'], + 'expected' => VakifKatilimPosResponseDataMapperTest::threeDPaymentDataProvider()['success1']['expectedData'], + 'is3DSuccess' => true, + 'isSuccess' => true, + ], + ]; + } + + public static function getApiUrlDataProvider(): array + { + return [ + [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'orderTxType' => null, + 'paymentModel' => PosInterface::MODEL_3D_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/ThreeDModelProvisionGate', + ], + [ + 'txType' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + 'orderTxType' => null, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizaten', + ], + [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'orderTxType' => null, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/Non3DPayGate', + ], + [ + 'txType' => PosInterface::TX_TYPE_PAY_POST_AUTH, + 'orderTxType' => null, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizatenClose', + ], + [ + 'txType' => PosInterface::TX_TYPE_STATUS, + 'orderTxType' => null, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SelectOrderByMerchantOrderId', + ], + [ + 'txType' => PosInterface::TX_TYPE_ORDER_HISTORY, + 'orderTxType' => null, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SelectOrder', + ], + [ + 'txType' => PosInterface::TX_TYPE_CANCEL, + 'orderTxType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SaleReversal', + ], + [ + 'txType' => PosInterface::TX_TYPE_REFUND, + 'orderTxType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/DrawBack', + ], + [ + 'txType' => PosInterface::TX_TYPE_CANCEL, + 'orderTxType' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizationReversal', + ], + [ + 'txType' => PosInterface::TX_TYPE_REFUND, + 'orderTxType' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + 'paymentModel' => PosInterface::MODEL_NON_SECURE, + 'expected' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizationDrawBack', + ], + ]; + } + + public static function getApiUrlExceptionDataProvider(): array + { + return [ + [ + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'paymentModel' => PosInterface::MODEL_3D_PAY, + 'exception_class' => UnsupportedTransactionTypeException::class, + ], + ]; + } + + public static function makeRegularPaymentDataProvider(): array + { + return [ + [ + 'order' => [ + 'id' => '2020110828BC', + ], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/Non3DPayGate', + ], + [ + 'order' => [ + 'id' => '2020110828BC', + ], + 'txType' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizaten', + ], + ]; + } + + public static function makeRegularPostAuthPaymentDataProvider(): array + { + return [ + [ + 'order' => [ + 'id' => '2020110828BC', + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizatenClose', + ], + ]; + } + + public static function statusRequestDataProvider(): array + { + return [ + [ + 'order' => [ + 'id' => '2020110828BC', + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SelectOrderByMerchantOrderId', + ], + ]; + } + + public static function cancelRequestDataProvider(): array + { + return [ + 'pay_order' => [ + 'order' => [ + 'id' => '2020110828BC', + 'transaction_type' => PosInterface::TX_TYPE_PAY_AUTH, + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SaleReversal', + ], + 'pay_auth_order' => [ + 'order' => [ + 'id' => '2020110828BC', + 'transaction_type' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizationReversal', + ], + ]; + } + + public static function refundRequestDataProvider(): array + { + return [ + 'pay_order' => [ + 'order' => [ + 'id' => '2020110828BC', + 'transaction_type' => PosInterface::TX_TYPE_PAY_AUTH, + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/DrawBack', + ], + 'pay_auth_order' => [ + 'order' => [ + 'id' => '2020110828BC', + 'transaction_type' => PosInterface::TX_TYPE_PAY_PRE_AUTH, + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/PreAuthorizationDrawBack', + ], + ]; + } + + public static function historyRequestDataProvider(): array + { + return [ + [ + 'order' => [ + 'id' => '2020110828BC', + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SelectOrder', + ], + ]; + } + + public static function orderHistoryRequestDataProvider(): array + { + return [ + [ + 'order' => [ + 'id' => '2020110828BC', + ], + 'api_url' => 'https://boa.vakifkatilim.com.tr/VirtualPOS.Gateway/Home/SelectOrder', + ], + ]; + } +} diff --git a/tests/Unit/Serializer/VakifKatilimPosSerializerTest.php b/tests/Unit/Serializer/VakifKatilimPosSerializerTest.php new file mode 100644 index 00000000..490724d7 --- /dev/null +++ b/tests/Unit/Serializer/VakifKatilimPosSerializerTest.php @@ -0,0 +1,442 @@ +serializer = new VakifKatilimPosSerializer(); + } + + public function testSupports(): void + { + $supports = $this->serializer::supports(VakifKatilimPos::class); + + $this->assertTrue($supports); + } + + /** + * @dataProvider encodeDataProvider + */ + public function testEncode(array $input, string $expected): void + { + $actual = $this->serializer->encode($input, PosInterface::TX_TYPE_PAY_AUTH); + $expected = str_replace(["\r"], '', $expected); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider decodeHtmlDataProvider + */ + public function testDecodeHtml(string $input, array $expected): void + { + $actual = $this->serializer->decode($input, PosInterface::TX_TYPE_PAY_AUTH); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider decodeExceptionDataProvider + */ + public function testDecodeException(string $input, string $txType, string $exceptionClass): void + { + $this->expectException($exceptionClass); + + $this->serializer->decode($input, $txType); + } + + /** + * @dataProvider decodeXmlDataProvider + */ + public function testDecodeXML(string $input, string $txType, array $expected): void + { + $actual = $this->serializer->decode($input, $txType); + + $this->assertSame($expected, $actual); + } + + public static function decodeHtmlDataProvider(): array + { + $vakifKatilimHTML = << + + + + + +
    + + + + + + + + + + + +
    + + + +HTML; + + return [ + '3d_auth_fail' => [ + 'html' => $vakifKatilimHTML, + 'expected' => [ + 'gateway' => 'https://localhost/VirtualPos/ThreeDModel/Fail', + 'form_inputs' => [ + 'ResponseCode' => 'CardNotEnrolled', + 'ResponseMessage' => 'Card 3D Secure kayitli degil.', + 'ProvisionNumber' => '', + 'MerchantOrderId' => '', + 'OrderId' => '0', + 'RRN' => '', + 'Stan' => '', + 'HashData' => '', + 'MD' => '', + ], + ], + ], + ]; + } + + public static function decodeXmlDataProvider(): iterable + { + yield [ + 'input' => ' + + +http://localhost/ThreeDModel/Approval +http://localhost/ThreeDModel/Fail +DvAUXMvYV4ex5m16mMezEl+kxrI= +1 +0 +936 +APIUSER +kfkdsnskslkclswr9430ır +1554891870 +0 +111 +0 + + + + MD + vygnTBD4smBxAOlDsgbaOQ== + + + + + +1 +0 +0 +0 +0 +3 + + true + false + 922709016599 + 016599 + 00 + Provizyon Alindi. + 15161 + 00010101T00:00:00 + 1554891870 + bcCqBe4hbElPOVYtfvsw7M44usQ= +', + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'expected' => [ + 'VPosMessageContract' => [ + 'OkUrl' => 'http://localhost/ThreeDModel/Approval', + 'FailUrl' => 'http://localhost/ThreeDModel/Fail', + 'HashData' => 'DvAUXMvYV4ex5m16mMezEl+kxrI=', + 'MerchantId' => '1', + 'SubMerchantId' => '0', + 'CustomerId' => '936', + 'UserName' => 'APIUSER', + 'HashPassword' => 'kfkdsnskslkclswr9430ır', + 'MerchantOrderId' => '1554891870', + 'InstallmentCount' => '0', + 'Amount' => '111', + 'FECAmount' => '0', + 'AdditionalData' => [ + 'AdditionalDataList' => [ + 'VPosAdditionalData' => [ + 'Key' => 'MD', + 'Data' => 'vygnTBD4smBxAOlDsgbaOQ==', + ], + ], + ], + 'Products' => '', + 'Addresses' => '', + 'PaymentType' => '1', + 'DebtId' => '0', + 'SurchargeAmount' => '0', + 'SGKDebtAmount' => '0', + 'InstallmentMaturityCommisionFlag' => '0', + 'TransactionSecurity' => '3', + ], + 'IsEnrolled' => 'true', + 'IsVirtual' => 'false', + 'RRN' => '922709016599', + 'Stan' => '016599', + 'ResponseCode' => '00', + 'ResponseMessage' => 'Provizyon Alindi.', + 'OrderId' => '15161', + 'TransactionTime' => '00010101T00:00:00', + 'MerchantOrderId' => '1554891870', + 'HashData' => 'bcCqBe4hbElPOVYtfvsw7M44usQ=', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + ]; + + $testUtf16 = << + + + + 12743 + 1995434716 + 1 + 111111 + 1 + + 1 + + 1 + Basarili + 1 + + 1 + Acik + 0949 + TRY + 1 + 3d'siz islem + Hasan Karacan + MasterCard + 5353********7017 + 2020-12-24T09:21:41.55 + 9.30 + 0.00 + 0.00 + 0.00 + 0.00 + 0 + 00 + Provizyon alındı. + 043290 + 035909014127 + 014127 + USERNAME + 69 + + + 00 + + +A_WRAP +; + yield 'test_utf_16' => [ + 'input' => $testUtf16, + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'expected' => [ + 'VPosOrderData' => [ + 'OrderContract' => [ + 'OrderId' => '12743', + 'MerchantOrderId' => '1995434716', + 'MerchantId' => '1', + 'PosTerminalId' => '111111', + 'OrderStatus' => '1', + 'OrderStatusDescription' => '', + 'OrderType' => '1', + 'OrderTypeDescription' => '', + 'TransactionStatus' => '1', + 'TransactionStatusDescription' => 'Basarili', + 'LastOrderStatus' => '1', + 'LastOrderStatusDescription' => '', + 'EndOfDayStatus' => '1', + 'EndOfDayStatusDescription' => 'Acik', + 'FEC' => '0949', + 'FecDescription' => 'TRY', + 'TransactionSecurity' => '1', + 'TransactionSecurityDescription' => "3d'siz islem", + 'CardHolderName' => 'Hasan Karacan', + 'CardType' => 'MasterCard', + 'CardNumber' => '5353********7017', + 'OrderDate' => '2020-12-24T09:21:41.55', + 'FirstAmount' => '9.30', + 'FECAmount' => '0.00', + 'CancelAmount' => '0.00', + 'DrawbackAmount' => '0.00', + 'ClosedAmount' => '0.00', + 'InstallmentCount' => '0', + 'ResponseCode' => '00', + 'ResponseExplain' => 'Provizyon alındı.', + 'ProvNumber' => '043290', + 'RRN' => '035909014127', + 'Stan' => '014127', + 'MerchantUserName' => 'USERNAME', + 'BatchId' => '69', + ], + + ], + + 'ResponseCode' => '00', + 'ResponseMessage' => '', + '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + '@xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + ], + ]; + } + + public static function decodeExceptionDataProvider(): Generator + { + yield 'test1' => [ + 'input' => << + + + Runtime Error + + + + + + +

    Server Error in '/VirtualPOS.Gateway' Application.

    + +

    Runtime Error

    + + + + Description: An application error occurred on the server. The current custom error settings for this application prevent the details of the application error from being viewed remotely (for security reasons). It could, however, be viewed by browsers running on the local server machine. +

    + + Details: To enable the details of this specific error message to be viewable on remote machines, please create a <customErrors> tag within a "web.config" configuration file located in the root directory of the current web application. This <customErrors> tag should then have its "mode" attribute set to "Off".

    + + + + + +
    +
    +
    +<!-- Web.Config Configuration File -->
    +
    +<configuration>
    +    <system.web>
    +        <customErrors mode="Off"/>
    +    </system.web>
    +</configuration>
    + +
    + +
    + + Notes: The current error page you are seeing can be replaced by a custom error page by modifying the "defaultRedirect" attribute of the application's <customErrors> configuration tag to point to a custom error page URL.

    + + + + + +
    +
    +
    +<!-- Web.Config Configuration File -->
    +
    +<configuration>
    +    <system.web>
    +        <customErrors mode="RemoteOnly" defaultRedirect="mycustompage.htm"/>
    +    </system.web>
    +</configuration>
    + +
    + +
    + + + +A_WRAP + + , + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'expected_exception_class' => \Exception::class, + ]; + + yield 'test2' => [ + 'input' => '', + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'expected_exception_class' => \Exception::class, + ]; + } + + public static function encodeDataProvider(): array + { + return [ + [ + 'input' => VakifKatilimPosRequestDataMapperTest::create3DPaymentRequestDataDataProvider()[0]['expected'], + 'expected' => ' +1.0.0sFxxO809/N3Yif4p/js1UKFMRro=111111APIUSER01002020110828BC30http://localhost/finansbank-payfor/3d/response.phphttp://localhost/finansbank-payfor/3d/response.phpMD67YtBfBRTZ0XBKnAHi8c/A== +', + ], + ]; + } +} From 7e470e53f493a14e21f3772e1dac4f224a778c99 Mon Sep 17 00:00:00 2001 From: Nuryagdy Mustapayev Date: Tue, 23 Apr 2024 14:57:48 +0200 Subject: [PATCH 4/4] updated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c852a60d..5deeb4b2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,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 | Kuveyt Türk | 3DSecure | İptal
    İade
    Durum sorgulama
    (SOAP API) | +| Kuveyt POS TDV2.0.0 | Kuveyt Türk | 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