From 689b2068502b661451a4aaea3d5e35dfadf3aa52 Mon Sep 17 00:00:00 2001 From: Nuryagdy Mustapayev Date: Fri, 15 Apr 2022 14:23:28 +0200 Subject: [PATCH] kuveyt pos integration - initial commit --- README.md | 2 + composer.json | 2 +- config/pos.php | 41 +- examples/kuveytpos/3d/_config.php | 20 + examples/kuveytpos/3d/form.php | 36 + examples/kuveytpos/3d/index.php | 10 + examples/kuveytpos/3d/response.php | 4 + examples/kuveytpos/_payment_config.php | 71 ++ examples/kuveytpos/index.php | 6 + examples/template/_header.php | 1 + src/Entity/Account/KuveytPosAccount.php | 56 ++ src/Entity/Card/CreditCardKuveytPos.php | 42 + src/Factory/AccountFactory.php | 19 + src/Gateways/AbstractGateway.php | 48 +- src/Gateways/KuveytPos.php | 771 ++++++++++++++++++ tests/Entity/Card/CreditCardKuveytPosTest.php | 22 + tests/Gateways/KuveytPosTest.php | 523 ++++++++++++ 17 files changed, 1651 insertions(+), 23 deletions(-) create mode 100644 examples/kuveytpos/3d/_config.php create mode 100644 examples/kuveytpos/3d/form.php create mode 100644 examples/kuveytpos/3d/index.php create mode 100644 examples/kuveytpos/3d/response.php create mode 100644 examples/kuveytpos/_payment_config.php create mode 100644 examples/kuveytpos/index.php create mode 100644 src/Entity/Account/KuveytPosAccount.php create mode 100644 src/Entity/Card/CreditCardKuveytPos.php create mode 100644 src/Gateways/KuveytPos.php create mode 100644 tests/Entity/Card/CreditCardKuveytPosTest.php create mode 100644 tests/Gateways/KuveytPosTest.php diff --git a/README.md b/README.md index ae809ced..7bccad4e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Bu paket ile amaçlanan; ortak bir arayüz sınıfı ile, tüm Türk banka sanal - **InterPOS (Deniz bank)** destegi eklenmistir, test edildikce, sorunlari bulundukca hatalar giderilecek. +- **Kuveyt POS** 3d secure ödeme desteği eklenmiştir, test edildikçe, sorunları bulundukça hatalar giderilecek. + ### Özellikler - Standart E-Commerce modeliyle ödeme (model => `regular`) - 3D Secure modeliyle ödeme (model => `3d`) diff --git a/composer.json b/composer.json index 78fa50b1..d88534b8 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "mews/pos", "description": "Türk bankaları için sanal pos kütüphanesi", - "keywords": ["pos", "sanal pos", "est", "est pos", "akbank", "posnet", "payfor", "vakifbankpos", "InterVPos", "DenizBank Sanal Pos"], + "keywords": ["pos", "sanal pos", "est", "est pos", "akbank", "posnet", "payfor", "vakifbankpos", "InterVPos", "DenizBank Sanal Pos", "kuveytpos"], "homepage": "https://github.com/mewebstudio/pos", "license": "MIT", "authors": [ diff --git a/config/pos.php b/config/pos.php index b1cc5d92..f18201f1 100644 --- a/config/pos.php +++ b/config/pos.php @@ -18,7 +18,7 @@ 'production' => 'https://www.sanalakpos.com/fim/est3Dgate', 'test' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', ], - ] + ], ], 'ziraat' => [ 'name' => 'Ziraat Bankası', @@ -30,7 +30,7 @@ 'production' => 'https://sanalpos2.ziraatbank.com.tr/fim/est3dgate', 'test' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', ], - ] + ], ], 'finansbank' => [ 'name' => 'QNB Finansbank', @@ -42,7 +42,7 @@ 'production' => 'https://www.fbwebpos.com/fim/est3dgate', 'test' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', ], - ] + ], ], 'halkbank' => [ 'name' => 'Halkbank', @@ -54,7 +54,7 @@ 'production' => 'https://sanalpos.halkbank.com.tr/fim/est3dgate', 'test' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', ], - ] + ], ], 'teb' => [ 'name' => 'TEB', @@ -66,7 +66,7 @@ 'production' => 'https://sanalpos.teb.com.tr/fim/est3Dgate', 'test' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', ], - ] + ], ], 'isbank' => [ 'name' => 'İşbank', @@ -78,20 +78,7 @@ 'production' => 'https://sanalpos.isbank.com.tr/fim/est3Dgate', 'test' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', ], - ] - ], - 'isbank-payflex' => [ - 'name' => 'İşbank - PayFlex', - //TODO implement PayFlex - 'class' => Mews\Pos\Gateways\PayFlex::class, - 'urls' => [ - 'production' => 'https://trx.payflex.com.tr/VposWeb/v3/Vposreq.aspx', - 'test' => 'https://sanalpos.innova.com.tr/ISBANK_v4/VposWeb/v3/Vposreq.aspx', - 'gateway' => [ - 'production' => 'https://mpi.vpos.isbank.com.tr/MPIEnrollment.aspx', - 'test' => 'https://sanalpos.innova.com.tr/ISBANK/MpiWeb/Enrollment.aspx', - ], - ] + ], ], 'yapikredi' => [ 'name' => 'Yapıkredi', @@ -115,7 +102,7 @@ 'production' => 'https://sanalposprov.garanti.com.tr/servlet/gt3dengine', 'test' => 'https://sanalposprovtest.garanti.com.tr/servlet/gt3dengine', ], - ] + ], ], 'qnbfinansbank-payfor' => [ 'name' => 'QNBFinansbank-PayFor', @@ -131,7 +118,7 @@ 'production' => 'https://vpos.qnbfinansbank.com/Gateway/3DHost.aspx', 'test' => 'https://vpostest.qnbfinansbank.com/Gateway/3DHost.aspx', ], - ] + ], ], 'vakifbank' => [ 'name' => 'VakifBank-VPOS', @@ -161,5 +148,17 @@ ], ], ], + 'kuveytpos' => [ + 'name' => 'kuveyt-pos', + 'class' => Mews\Pos\Gateways\KuveytPos::class, + 'urls' => [ + 'production' => 'https://boa.kuveytturk.com.tr/sanalposservice/Home/ThreeDModelProvisionGate', + 'test' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelProvisionGate', + 'gateway' => [ + 'production' => 'https://boa.kuveytturk.com.tr/sanalposservice/Home/ThreeDModelPayGate', + 'test' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelPayGate', + ], + ], + ], ], ]; diff --git a/examples/kuveytpos/3d/_config.php b/examples/kuveytpos/3d/_config.php new file mode 100644 index 00000000..049a1c21 --- /dev/null +++ b/examples/kuveytpos/3d/_config.php @@ -0,0 +1,20 @@ +getMethod() !== 'POST') { + echo new RedirectResponse($baseUrl); + exit(); +} + +$order = getNewOrder($baseUrl, $ip, $request->get('installment')); + +$session->set('order', $order); + +$card = new \Mews\Pos\Entity\Card\CreditCardKuveytPos( + $request->get('number'), + $request->get('year'), + $request->get('month'), + $request->get('cvv'), + $request->get('name'), + $request->get('type') +); + +$pos->prepare($order, $transaction, $card); + +try { + $formData = $pos->get3DFormData(); +} catch (\Exception $e) { + dd($e); +} + +require '../../template/_redirect_form.php'; +require '../../template/_footer.php'; diff --git a/examples/kuveytpos/3d/index.php b/examples/kuveytpos/3d/index.php new file mode 100644 index 00000000..995b64a4 --- /dev/null +++ b/examples/kuveytpos/3d/index.php @@ -0,0 +1,10 @@ + [ + 'path' => '/3d/index.php', + 'label' => '3D Ödeme', + ], +]; + +$installments = [ + 0 => 'Peşin', + 2 => '2 Taksit', + 6 => '6 Taksit', + 12 => '12 Taksit', +]; + +function getNewOrder( + string $baseUrl, + string $ip, + ?int $installment = 0 +): array { + $successUrl = $baseUrl.'response.php'; + $failUrl = $baseUrl.'response.php'; + + $orderId = date('Ymd').strtoupper(substr(uniqid(sha1(time())), 0, 4)); + + $amount = 10.01; + + $order = [ + 'id' => $orderId, + 'amount' => $amount, + 'installment' => $installment, + 'currency' => 'TRY', + 'success_url' => $successUrl, + 'fail_url' => $failUrl, + 'ip' => $ip, + ]; + + return $order; +} + + +function doPayment(\Mews\Pos\PosInterface $pos, string $transaction, ?\Mews\Pos\Entity\Card\AbstractCreditCard $card) +{ + if ($pos->getAccount()->getModel() === \Mews\Pos\Gateways\AbstractGateway::MODEL_NON_SECURE + && \Mews\Pos\Gateways\AbstractGateway::TX_POST_PAY !== $transaction + ) { + //bu asamada $card regular/non secure odemede lazim. + $pos->payment($card); + } else { + $pos->payment(); + } +} + +$testCards = [ + 'visa1' => new \Mews\Pos\Entity\Card\CreditCardKuveytPos( + '4155650100416111', + 25, + 1, + '123', + 'John Doe', + AbstractCreditCard::CARD_TYPE_VISA + ), +]; diff --git a/examples/kuveytpos/index.php b/examples/kuveytpos/index.php new file mode 100644 index 00000000..92e25b2f --- /dev/null +++ b/examples/kuveytpos/index.php @@ -0,0 +1,6 @@ + PayFor (Finansbank)
  • Garanti POS
  • PosNet (YKB)
  • +
  • KuveytPOS
  • diff --git a/src/Entity/Account/KuveytPosAccount.php b/src/Entity/Account/KuveytPosAccount.php new file mode 100644 index 00000000..558fdeac --- /dev/null +++ b/src/Entity/Account/KuveytPosAccount.php @@ -0,0 +1,56 @@ +subMerchantId = $subMerchantId; + } + + /** + * @return string + */ + public function customerId(): string + { + return $this->password; + } + + /** + * @return string|null + */ + public function getSubMerchantId(): ?string + { + return $this->subMerchantId; + } +} diff --git a/src/Entity/Card/CreditCardKuveytPos.php b/src/Entity/Card/CreditCardKuveytPos.php new file mode 100644 index 00000000..bacedbdc --- /dev/null +++ b/src/Entity/Card/CreditCardKuveytPos.php @@ -0,0 +1,42 @@ + 'Visa', + self::CARD_TYPE_MASTERCARD => 'MasterCard', + ]; + + /** + * @inheritDoc + */ + public function getExpirationDate(): string + { + return $this->getExpireMonth().$this->getExpireYear(); + } + + /** + * @return string + */ + public function getCardCode(): string + { + if (!isset($this->cardTypeToCodeMapping[$this->type])) { + return $this->type; + } + + return $this->cardTypeToCodeMapping[$this->type]; + } + + /** + * @return string[] + */ + public function getCardTypeToCodeMapping(): array + { + return $this->cardTypeToCodeMapping; + } +} diff --git a/src/Factory/AccountFactory.php b/src/Factory/AccountFactory.php index 74427989..1c29f4c6 100644 --- a/src/Factory/AccountFactory.php +++ b/src/Factory/AccountFactory.php @@ -5,6 +5,7 @@ use Mews\Pos\Entity\Account\EstPosAccount; use Mews\Pos\Entity\Account\GarantiPosAccount; use Mews\Pos\Entity\Account\InterPosAccount; +use Mews\Pos\Entity\Account\KuveytPosAccount; use Mews\Pos\Entity\Account\PayForAccount; use Mews\Pos\Entity\Account\PosNetAccount; use Mews\Pos\Entity\Account\VakifBankAccount; @@ -13,6 +14,7 @@ use Mews\Pos\Gateways\EstPos; use Mews\Pos\Gateways\GarantiPos; use Mews\Pos\Gateways\InterPos; +use Mews\Pos\Gateways\KuveytPos; use Mews\Pos\Gateways\PayForPos; use Mews\Pos\Gateways\PosNet; @@ -82,6 +84,23 @@ public static function createGarantiPosAccount(string $bank, string $clientId, s } + /** + * @param string $bank + * @param string $merchantId Mağaza Numarası + * @param string $username POS panelinizden kullanıcı işlemleri sayfasında APİ rolünde kullanıcı oluşturulmalıdır + * @param string $customerId CustomerNumber, Müşteri No + * @param string $storeKey Oluşturulan APİ kullanıcısının şifre bilgisidir. + * @param string $model + * @param string $lang + * @param string|null $subMerchantId + * + * @return KuveytPosAccount + */ + public static function createKuveytPosAccount(string $bank, string $merchantId, string $username, string $customerId, string $storeKey, string $model = AbstractGateway::MODEL_3D_SECURE, string $lang = KuveytPos::LANG_TR, ?string $subMerchantId = null): KuveytPosAccount + { + return new KuveytPosAccount($bank, $merchantId, $username, $customerId, $storeKey, $model, $lang, $subMerchantId); + } + /** * @param string $bank * @param string $merchantId diff --git a/src/Gateways/AbstractGateway.php b/src/Gateways/AbstractGateway.php index 1efd9cc4..8d14960a 100644 --- a/src/Gateways/AbstractGateway.php +++ b/src/Gateways/AbstractGateway.php @@ -31,6 +31,8 @@ abstract class AbstractGateway implements PosInterface const MODEL_3D_HOST = '3d_host'; const MODEL_NON_SECURE = 'regular'; + protected const HASH_ALGORITHM = 'sha1'; + private $config; /** @@ -481,7 +483,7 @@ abstract public function createRefundXML(); * * @param $responseData * - * @return string + * @return string|array */ abstract public function create3DPaymentXML($responseData); @@ -648,6 +650,50 @@ protected function isHTML($str): bool return $str !== strip_tags($str); } + /** + * @param string $str + * + * @return string + */ + protected function hashString(string $str): string + { + return base64_encode(hash(static::HASH_ALGORITHM, $str, true)); + } + + /** + * if 2 arrays has common keys, then non-null value preferred, + * if both arrays has the non-null values for the same key then value of $arr2 is preferred. + * @param array $arr1 + * @param array $arr2 + * + * @return array + */ + protected function mergeArraysPreferNonNullValues(array $arr1, array $arr2): array + { + $resultArray = array_diff_key($arr1, $arr2) + array_diff_key($arr2, $arr1); + $commonArrayKeys = array_keys(array_intersect_key($arr1, $arr2)); + foreach ($commonArrayKeys as $key) { + $resultArray[$key] = $arr2[$key] ?: $arr1[$key]; + } + + return $resultArray; + } + + /** + * Converts XML string to array + * + * @param string $data + * @param array $context + * + * @return array + */ + protected function XMLStringToArray(string $data, array $context = []): array + { + $encoder = new XmlEncoder(); + + return $encoder->decode($data, 'xml', $context); + } + /** * return values are used as a key in config file * @return string diff --git a/src/Gateways/KuveytPos.php b/src/Gateways/KuveytPos.php new file mode 100644 index 00000000..59c3aa48 --- /dev/null +++ b/src/Gateways/KuveytPos.php @@ -0,0 +1,771 @@ + 'approved', + 'ApiUserNotDefined' => 'invalid_transaction', + 'EmptyMDException' => 'invalid_transaction', + 'HashDataError' => 'invalid_transaction', + ]; + + /** + * Transaction Types + * + * @var array + */ + protected $types = [ + self::TX_PAY => 'Sale', + //todo update null values with valid values + self::TX_PRE_PAY => null, + self::TX_POST_PAY => null, + self::TX_CANCEL => null, + self::TX_REFUND => null, + self::TX_STATUS => null, + ]; + + protected $secureTypeMappings = [ + self::MODEL_3D_SECURE => 3, + //todo update null values with valid values + self::MODEL_3D_PAY => null, + self::MODEL_3D_HOST => null, + self::MODEL_NON_SECURE => 0, + ]; + + /** + * Currency mapping + * + * @var array + */ + protected $currencies = [ + 'TRY' => '0949', + 'USD' => '0840', + 'EUR' => '0978', + 'GBP' => '0826', + 'JPY' => '0392', + 'RUB' => '0810', + ]; + + /** + * @var KuveytPosAccount + */ + protected $account; + + /** + * @var CreditCardKuveytPos|null + */ + protected $card; + + /** + * @param array $config + * @param KuveytPosAccount $account + * @param array $currencies + */ + public function __construct($config, $account, array $currencies = []) + { + parent::__construct($config, $account, $currencies); + } + + /** + * @inheritDoc + */ + public function createXML(array $nodes, string $encoding = 'ISO-8859-1', bool $ignorePiNode = false): string + { + return parent::createXML(['KuveytTurkVPosMessage' => $nodes], $encoding, $ignorePiNode); + } + + /** + * @return KuveytPosAccount + */ + public function getAccount(): KuveytPosAccount + { + return $this->account; + } + + /** + * @return CreditCardKuveytPos|null + */ + public function getCard(): ?CreditCardKuveytPos + { + return $this->card; + } + + /** + * @param CreditCardKuveytPos|null $card + */ + public function setCard($card) + { + $this->card = $card; + } + + /** + * Create 3D Hash + * todo Şifrelenen veriler (Hashdata) uyuşmamaktadır. hatasi aliyoruz + * + * @param KuveytPosAccount $account + * @param $order + * @param bool $forProvision + * + * @return string + */ + public function create3DHash(KuveytPosAccount $account, $order, bool $forProvision = false): string + { + $hashedPassword = $this->hashString($account->getStoreKey()); + + if ($forProvision) { + $hashData = $this->createHashDataForAuthorization($account, $order, $hashedPassword); + } else { + $hashData = $this->createHashDataForProvision($account, $order, $hashedPassword); + } + + $hashStr = implode('', $hashData); + + return $this->hashString($hashStr); + } + + /** + * @inheritDoc + */ + public function send($contents, string $url = null) + { + $client = new Client(); + $url = $url ?: $this->getApiURL(); + + $isXML = is_string($contents); + $body = $isXML ? ['body' => $contents] : ['form_params' => $contents]; + $response = $client->request('POST', $url, $body); + $responseBody = $response->getBody()->getContents(); + try { + $this->data = $this->XMLStringToArray($responseBody); + } catch (\Exception $e) { + if (!$this->isHTML($responseBody)) { + throw new \Exception($responseBody); + } + //icinde form olan HTML response dondu + $this->data = $responseBody; + } + + return $this->data; + } + + /** + * todo implement method + * @param AbstractPosAccount $account + * @param array $data + * + * @return bool + */ + public function check3DHash(AbstractPosAccount $account, array $data): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function make3DPayment(Request $request) + { + $gatewayResponse = $request->request->get('AuthenticationResponse'); + $gatewayResponse = urldecode($gatewayResponse); + $gatewayResponse = $this->XMLStringToArray($gatewayResponse); + $bankResponse = null; + $procReturnCode = $this->getProcReturnCode($gatewayResponse); + if ($this->check3DHash($this->account, $gatewayResponse)) { + if ('00' === $procReturnCode) { + $contents = $this->create3DPaymentXML($gatewayResponse); + + $bankResponse = $this->send($contents); + } + } + + $authorizationResponse = $this->emptyStringsToNull($bankResponse); + $this->response = (object) $this->map3DPaymentData($gatewayResponse, $authorizationResponse); + + return $this; + } + + /** + * @inheritDoc + */ + public function make3DPayPayment(Request $request) + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + public function make3DHostPayment(Request $request) + { + return $this->make3DPayPayment($request); + } + + /** + * Deniz bank dokumantasyonunda history sorgusu ile alakali hic bir bilgi yok + * @inheritDoc + */ + public function history(array $meta) + { + throw new NotImplementedException(); + } + + /** + * Amount Formatter + * converts 100 to 10000, or 10.01 to 1001 + * @param float $amount + * + * @return int + */ + public static function amountFormat(float $amount): int + { + return round($amount, 2) * 100; + } + + /** + * @inheritDoc + */ + public function get3DFormData(): array + { + $gatewayUrl = $this->get3DGatewayURL(); + + return $this->getCommon3DFormData($this->account, $this->order, $this->type, $gatewayUrl, $this->card); + } + + /** + * @inheritDoc + */ + public function create3DPaymentXML($responseData) + { + $account = $this->account; + $order = $this->order; + $hash = $this->create3DHash($this->account, $this->order, true); + + $requestData = [ + 'APIVersion' => self::API_VERSION, + 'HashData' => $hash, + 'MerchantId' => $account->getClientId(), + 'CustomerId' => $account->customerId(), + 'UserName' => $account->getUsername(), + 'CustomerIPAddress' => $order->ip, + 'KuveytTurkVPosAdditionalData' => [ + 'AdditionalData' => [ + 'Key' => 'MD', + 'Data' => $responseData['MD'], + ], + ], + 'TransactionType' => $responseData['VPosMessage']['TransactionType'], + 'InstallmentCount' => $responseData['VPosMessage']['InstallmentCount'], + 'Amount' => $responseData['VPosMessage']['Amount'], + 'DisplayAmount' => $responseData['VPosMessage']['DisplayAmount'], + 'CurrencyCode' => $responseData['VPosMessage']['CurrencyCode'], + 'MerchantOrderId' => $responseData['VPosMessage']['MerchantOrderId'], + 'TransactionSecurity' => $responseData['VPosMessage']['TransactionSecurity'], + ]; + + return $requestData; + } + + /** + * @inheritDoc + */ + public function createRegularPaymentXML() + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + public function createRegularPostXML() + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + public function createHistoryXML($customQueryData) + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + public function createStatusXML() + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + public function createCancelXML() + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + public function createRefundXML() + { + throw new NotImplementedException(); + } + + /** + * Get ProcReturnCode + * + * @param array $response + * + * @return string|null + */ + protected function getProcReturnCode(array $response): ?string + { + return $response['ResponseCode'] ?? null; + } + + /** + * Get Status Detail Text + * + * @param string|null $procReturnCode + * + * @return string|null + */ + protected function getStatusDetail(?string $procReturnCode): ?string + { + return $procReturnCode ? ($this->codes[$procReturnCode] ?? null) : null; + } + + /** + * @inheritDoc + */ + protected function map3DPaymentData($raw3DAuthResponseData, $rawPaymentResponseData): array + { + $threeDResponse = $this->tDPayResponseCommon($raw3DAuthResponseData); + + if (empty($rawPaymentResponseData)) { + return array_merge($this->getDefaultPaymentResponse(), $threeDResponse); + } + + $paymentResponseData = $this->mapPaymentResponse($rawPaymentResponseData); + + return $this->mergeArraysPreferNonNullValues($threeDResponse, $paymentResponseData); + } + + /** + * @inheritDoc + */ + protected function mapPaymentResponse($responseData): array + { + + $responseData = (array) $responseData; + if (isset($responseData['VPosMessage'])) { + $responseData['VPosMessage'] = (array) $responseData['VPosMessage']; + } + $responseData = $this->emptyStringsToNull($responseData); + $status = 'declined'; + $procReturnCode = $this->getProcReturnCode($responseData); + + if ('00' === $procReturnCode) { + $status = 'approved'; + } + + $result = $this->getDefaultPaymentResponse(); + + $result['proc_return_code'] = $procReturnCode; + $result['code'] = $procReturnCode; + $result['status'] = $status; + $result['status_detail'] = $this->getStatusDetail($procReturnCode); + $result['all'] = $responseData; + + if ('approved' !== $status) { + $result['error_code'] = $procReturnCode; + $result['error_message'] = $responseData['ResponseMessage']; + + return $result; + } + $result['id'] = $responseData['ProvisionNumber']; + $result['auth_code'] = $responseData['ProvisionNumber']; + $result['order_id'] = $responseData['MerchantOrderId']; + $result['host_ref_num'] = $responseData['RRN']; + $result['amount'] = $responseData['VPosMessage']['Amount']; + $result['currency'] = array_search($responseData['VPosMessage']['CurrencyCode'], $this->currencies); + $result['masked_number'] = $responseData['VPosMessage']['CardNumber']; + + return $result; + } + + /** + * @inheritDoc + */ + protected function map3DPayResponseData($raw3DAuthResponseData) + { + return $this->map3DPaymentData($raw3DAuthResponseData, $raw3DAuthResponseData); + } + + /** + * @inheritDoc + */ + protected function mapRefundResponse($rawResponseData) + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + protected function mapCancelResponse($rawResponseData) + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + protected function mapStatusResponse($rawResponseData) + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + protected function mapHistoryResponse($rawResponseData) + { + return $rawResponseData; + } + + /** + * @inheritDoc + */ + protected function preparePaymentOrder(array $order) + { + // Installment + $installment = 0; + if (isset($order['installment']) && $order['installment'] > 1) { + $installment = (int) $order['installment']; + } + + return (object) array_merge($order, [ + 'installment' => $installment, + 'currency' => $this->mapCurrency($order['currency']), + ]); + } + + /** + * @inheritDoc + */ + protected function preparePostPaymentOrder(array $order) + { + throw new NotImplementedException(); + } + + /** + * @inheritDoc + */ + protected function prepareStatusOrder(array $order) + { + return (object) $order; + } + + /** + * @inheritDoc + */ + protected function prepareHistoryOrder(array $order) + { + return (object) $order; + } + + /** + * @inheritDoc + */ + protected function prepareCancelOrder(array $order) + { + return (object) $order; + } + + /** + * @inheritDoc + */ + protected function prepareRefundOrder(array $order) + { + return (object) $order; + } + + /** + * @param KuveytPosAccount $account + * @param $order + * @param string $txType + * @param string $gatewayURL + * @param CreditCardKuveytPos|null $card + * + * @return array + * + * @throws GuzzleException + */ + private function getCommon3DFormData(KuveytPosAccount $account, $order, string $txType, string $gatewayURL, ?CreditCardKuveytPos $card = null): array + { + $formData = $this->create3DEnrollmentCheckData($account, $order, $txType, $card); + if (!$formData) { + return []; + } + + $xml = $this->createXML($formData); + $bankResponse = $this->send($xml, $gatewayURL); + + return $this->transformReceived3DFormData($bankResponse); + } + + /** + * @param KuveytPosAccount $account + * @param $order + * @param string $txType + * @param CreditCardKuveytPos|null $card + * + * @return array + */ + private function create3DEnrollmentCheckData(KuveytPosAccount $account, $order, string $txType, ?CreditCardKuveytPos $card = null): array + { + if (!$order) { + return []; + } + $hash = $this->create3DHash($this->account, $this->order); + + $inputs = [ + 'APIVersion' => self::API_VERSION, + 'MerchantId' => $account->getClientId(), + 'UserName' => $account->getUsername(), + 'CustomerId' => $account->customerId(), + 'HashData' => $hash, + 'TransactionType' => $txType, + 'TransactionSecurity' => $this->secureTypeMappings[$this->account->getModel()], + 'InstallmentCount' => $order->installment, + 'Amount' => self::amountFormat($order->amount), + //DisplayAmount: Amount değeri ile aynı olacak şekilde gönderilmelidir. + 'DisplayAmount' => self::amountFormat($order->amount), + 'CurrencyCode' => $order->currency, + 'MerchantOrderId' => $order->id, + 'OkUrl' => $order->success_url, + 'FailUrl' => $order->fail_url, + ]; + + if ($card) { + $inputs['CardHolderName'] = $card->getHolderName(); + $inputs['CardType'] = $card->getCardCode(); + $inputs['CardNumber'] = $card->getNumber(); + $inputs['CardExpireDateYear'] = $card->getExpireYear(); + $inputs['CardExpireDateMonth'] = $card->getExpireMonth(); + $inputs['CardCVV2'] = $card->getCvv(); + } + + return $inputs; + } + + /** + * 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 + */ + private function transformReceived3DFormData(string $response): array + { + $dom = new DOMDocument(); + $dom->loadHTML($response); + + $gatewayURL = ''; + $formNode = $dom->getElementsByTagName('form')->item(0); + for ($i = 0; $i < $formNode->attributes->length; ++$i) { + if ('action' === $formNode->attributes->item($i)->name) { + /** + * banka onayladiginda gatewayURL=bankanin gateway url + * onaylanmadiginda (hatali istek oldugunda) ise gatewayURL = istekte yer alan failURL + */ + $gatewayURL = $formNode->attributes->item($i)->value; + break; + } + } + + $els = $dom->getElementsByTagName('input'); + $inputs = $this->builtInputsFromHTMLDoc($els); + + return [ + 'gateway' => $gatewayURL, + 'inputs' => $inputs, + ]; + } + + /** + * html form'da gelen input degeleri array'e donusturur + * @param DOMNodeList $inputNodes + * + * @return array + */ + private function builtInputsFromHTMLDoc(DOMNodeList $inputNodes): array + { + $inputs = []; + foreach ($inputNodes as $el) { + $key = null; + $value = null; + for ($i = 0; $i < $el->attributes->length; ++$i) { + if ('name' === $el->attributes->item($i)->name) { + $key = $el->attributes->item($i)->value; + } + if ('value' === $el->attributes->item($i)->name) { + $value = $el->attributes->item($i)->value; + } + } + if ($key && $value) { + $inputs[$key] = $value; + } + } + unset($inputs['submit']); + + return $inputs; + } + + /** + * bankadan gelen response'da bos string degerler var. + * bu metod ile bos string'leri null deger olarak degistiriyoruz + * + * @param string|object|array $data + * + * @return string|object|array + */ + private function emptyStringsToNull($data) + { + if (is_string($data)) { + $data = '' === $data ? null : $data; + } elseif (is_array($data)) { + foreach ($data as $key => $value) { + $data[$key] = '' === $value ? null : $value; + } + } + + return $data; + } + + /** + * @param KuveytPosAccount $account + * @param $order + * @param string $hashedPassword + * + * @return array + */ + private function createHashDataForAuthorization(KuveytPosAccount $account, $order, string $hashedPassword): array + { + return [ + $account->getClientId(), + $order->id, + self::amountFormat($order->amount), + $account->getUsername(), + $hashedPassword, + ]; + } + + /** + * @param KuveytPosAccount $account + * @param $order + * @param string $hashedPassword + * + * @return array + */ + private function createHashDataForProvision(KuveytPosAccount $account, $order, string $hashedPassword): array + { + return [ + $account->getClientId(), + $order->id, + self::amountFormat($order->amount), + $order->success_url, + $order->fail_url, + $account->getUsername(), + $hashedPassword, + ]; + } + + /** + * @param array $raw3DAuthResponseData + * + * @return array + */ + private function tDPayResponseCommon(array $raw3DAuthResponseData): array + { + $raw3DAuthResponseData = $this->emptyStringsToNull($raw3DAuthResponseData); + $procReturnCode = $this->getProcReturnCode($raw3DAuthResponseData); + $status = 'declined'; + $response = 'Declined'; + if ('00' === $procReturnCode) { + $status = 'approved'; + $response = 'Approved'; + } + + $transactionSecurity = 'MPI fallback'; + + if (isset($raw3DAuthResponseData['VPosMessage'])) { + $orderId = $raw3DAuthResponseData['VPosMessage']['MerchantOrderId']; + } else { + $orderId = $raw3DAuthResponseData['MerchantOrderId']; + } + + $default = [ + 'order_id' => $orderId, + 'response' => $response, + 'transaction_type' => $this->type, + 'transaction' => $this->type, + 'transaction_security' => $transactionSecurity, + 'proc_return_code' => $procReturnCode, + 'code' => $procReturnCode, + 'md_status' => null, + 'status' => $status, + 'status_detail' => $this->getStatusDetail($procReturnCode), + 'hash' => null, + 'rand' => null, + 'hash_params' => null, + 'hash_params_val' => null, + 'amount' => null, + 'currency' => null, + 'tx_status' => null, + 'error_code' => 'approved' !== $status ? $procReturnCode : null, + 'md_error_message' => 'approved' !== $status ? $raw3DAuthResponseData['ResponseMessage'] : null, + '3d_all' => $raw3DAuthResponseData, + ]; + + if ('approved' === $status) { + $default['hash'] = $raw3DAuthResponseData['VPosMessage']['HashData']; + $default['amount'] = $raw3DAuthResponseData['VPosMessage']['Amount']; + $default['currency'] = array_search($raw3DAuthResponseData['VPosMessage']['CurrencyCode'], $this->currencies); + $default['masked_number'] = $raw3DAuthResponseData['VPosMessage']['CardNumber']; + } + + return $default; + } +} diff --git a/tests/Entity/Card/CreditCardKuveytPosTest.php b/tests/Entity/Card/CreditCardKuveytPosTest.php new file mode 100644 index 00000000..4289ef41 --- /dev/null +++ b/tests/Entity/Card/CreditCardKuveytPosTest.php @@ -0,0 +1,22 @@ +assertEquals('0302', $card->getExpirationDate()); + } + + public function testGetCardCode() + { + $card = new CreditCardInterPos('1111222233334444', '02', '03', '111', 'ahmet mehmet', AbstractCreditCard::CARD_TYPE_MASTERCARD); + $this->assertEquals('1', $card->getCardCode()); + } +} diff --git a/tests/Gateways/KuveytPosTest.php b/tests/Gateways/KuveytPosTest.php new file mode 100644 index 00000000..add64731 --- /dev/null +++ b/tests/Gateways/KuveytPosTest.php @@ -0,0 +1,523 @@ +config = require __DIR__.'/../../config/pos.php'; + + $this->threeDAccount = AccountFactory::createKuveytPosAccount( + 'kuveytpos', + '496', + 'apiuser1', + '400235', + 'Api123' + ); + + + $this->card = new CreditCardKuveytPos( + '4155650100416111', + 25, + 1, + '123', + 'John Doe', + AbstractCreditCard::CARD_TYPE_VISA + ); + + $this->order = [ + 'id' => '2020110828BC', + 'amount' => 10.01, + 'installment' => '0', + 'currency' => 'TRY', + 'success_url' => 'http://localhost/finansbank-payfor/3d/response.php', + 'fail_url' => 'http://localhost/finansbank-payfor/3d/response.php', + 'rand' => '0.43625700 1604831630', + 'hash' => 'zmSUxYPhmCj7QOzqpk/28LuE1Oc=', + 'ip' => '127.0.0.1', + 'lang' => KuveytPos::LANG_TR, + ]; + + $this->pos = PosFactory::createPosGateway($this->threeDAccount); + + $this->pos->setTestMode(true); + + $this->xmlDecoder = new XmlEncoder(); + } + + /** + * @return void + */ + public function testInit() + { + $this->assertEquals($this->config['banks'][$this->threeDAccount->getBank()], $this->pos->getConfig()); + $this->assertEquals($this->threeDAccount, $this->pos->getAccount()); + $this->assertNotEmpty($this->pos->getCurrencies()); + $this->assertEquals($this->config['banks'][$this->threeDAccount->getBank()]['urls']['gateway']['test'], $this->pos->get3DGatewayURL()); + $this->assertEquals($this->config['banks'][$this->threeDAccount->getBank()]['urls']['test'], $this->pos->getApiURL()); + } + + /** + * @return void + */ + public function testSetTestMode() + { + $this->pos->setTestMode(false); + $this->assertFalse($this->pos->isTestMode()); + $this->pos->setTestMode(true); + $this->assertTrue($this->pos->isTestMode()); + } + + + /** + * @return void + */ + public function testPrepare() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $this->assertEquals($this->card, $this->pos->getCard()); + } + + /** + * @return void + * + * @throws ReflectionException + * + * @uses \Mews\Pos\Gateways\KuveytPos::create3DEnrollmentCheckData() + * + */ + public function testCompose3DFormData() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $order = $this->pos->getOrder(); + $account = $this->pos->getAccount(); + $card = $this->pos->getCard(); + + $inputs = [ + 'APIVersion' => KuveytPos::API_VERSION, + 'MerchantId' => $account->getClientId(), + 'UserName' => $account->getUsername(), + 'CustomerId' => $account->customerId(), + 'HashData' => $this->pos->create3DHash($account, $order), + 'TransactionType' => 'Sale', + 'TransactionSecurity' => 3, + 'InstallmentCount' => $order->installment, + 'Amount' => KuveytPos::amountFormat($order->amount), + 'DisplayAmount' => KuveytPos::amountFormat($order->amount), + 'CurrencyCode' => $order->currency, + 'MerchantOrderId' => $order->id, + 'OkUrl' => $order->success_url, + 'FailUrl' => $order->fail_url, + ]; + + if ($card) { + $inputs['CardHolderName'] = $card->getHolderName(); + $inputs['CardType'] = $card->getCardCode(); + $inputs['CardNumber'] = $card->getNumber(); + $inputs['CardExpireDateYear'] = $card->getExpireYear(); + $inputs['CardExpireDateMonth'] = $card->getExpireMonth(); + $inputs['CardCVV2'] = $card->getCvv(); + } + $txType = 'Sale'; + $method = $this->getMethod('create3DEnrollmentCheckData'); + $result = $method->invoke($this->pos, $account, $order, $txType, $card); + $this->assertEquals($inputs, $result); + } + + /** + * @return void + */ + public function teGetCommon3DFormData() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $order = $this->pos->getOrder(); + + $failResponse = [ + 'gateway' => $order->fail_url, + 'inputs' => [ + 'IsEnrolled' => 'true', + 'IsVirtual' => 'false', + 'ResponseCode' => 'HashDataError', + 'ResponseMessage' => 'Şifrelenen veriler (Hashdata) uyuşmamaktadır.', + 'OrderId' => '0', + 'TransactionTime' => '0001-01-01T00:00:00', + 'MerchantOrderId' => '2020110828BC', + 'ReferenceId' => '6c66175eadfd4f31b00ac26f0d83761a', + 'BusinessKey' => '0', + ], + ]; + $result = $this->pos->get3DFormData(); + $this->assertEquals($failResponse, $result); + } + + /** + * @return void + */ + public function testGetCommon3DFormDataFailedResponse() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $this->pos->setTestMode(false); + + $result = $this->pos->get3DFormData(); + $this->assertArrayHasKey('AuthenticationResponse', $result['inputs']); + //form data olusturulmasi icin gonderilen istek banka tarafindan reddedillirse, banka failURL'a yonlendirilecek bir response doner. + //istek basarili olursa, gateway = bankanin gateway URL'ne esit olur. + $this->assertSame($result['gateway'], $this->order['fail_url']); + } + + /** + * @return void + */ + public function testGetCommon3DFormDataSuccessResponse() + { + + $testGateway = 'https://boa.kuveytturk.com.tr/sanalposservice/Home/ThreeDModelPayGate'; + $posMock = $this->getMockBuilder(KuveytPos::class) + ->setConstructorArgs([['urls' => [ + 'gateway' => [ + 'test' => $testGateway, + ], + ], ], $this->threeDAccount, [], ]) + ->onlyMethods(['send']) + ->getMock(); + $posMock->setTestMode(true); + $posMock->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $posMock->method('send')->willReturn('
    '); + + $result = $posMock->get3DFormData(); + $this->assertArrayHasKey('AuthenticationResponse', $result['inputs']); + //form data olusturulmasi icin gonderilen istek banka tarafindan reddedillirse, banka failURL'a yonlendirilecek bir response doner. + //istek basarili olursa, gateway = bankanin gateway URL'ne esit olur. + $this->assertSame($testGateway, $result['gateway']); + } + + /** + * @return void + * + * @throws GuzzleException + */ + public function testMake3DPaymentAuthFail() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $request = Request::create('', 'POST', [ + 'AuthenticationResponse' => '%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3cVPosTransactionResponseContract+xmlns%3axsd%3d%22http%3a%2f%2fwww.w3.org%2f2001%2fXMLSchema%22+xmlns%3axsi%3d%22http%3a%2f%2fwww.w3.org%2f2001%2fXMLSchema-instance%22%3e%3cIsEnrolled%3etrue%3c%2fIsEnrolled%3e%3cIsVirtual%3efalse%3c%2fIsVirtual%3e%3cResponseCode%3eHashDataError%3c%2fResponseCode%3e%3cResponseMessage%3e%c5%9eifrelenen+veriler+(Hashdata)+uyu%c5%9fmamaktad%c4%b1r.%3c%2fResponseMessage%3e%3cOrderId%3e0%3c%2fOrderId%3e%3cTransactionTime%3e0001-01-01T00%3a00%3a00%3c%2fTransactionTime%3e%3cMerchantOrderId%3e2020110828BC%3c%2fMerchantOrderId%3e%3cReferenceId%3e9b8e2326a9df44c2b2aac0b98b11f0a4%3c%2fReferenceId%3e%3cBusinessKey%3e0%3c%2fBusinessKey%3e%3c%2fVPosTransactionResponseContract%3e', + ]); + + $this->pos->make3DPayment($request); + $result = $this->pos->getResponse(); + $this->assertIsObject($result); + $result = (array) $result; + $this->assertSame('declined', $result['status']); + $this->assertSame('Şifrelenen veriler (Hashdata) uyuşmamaktadır.', $result['md_error_message']); + } + + /** + * @return void + * + * @throws GuzzleException + */ + public function testMake3DPaymentAuthSuccessProvisionFail() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $xml = '1.0.0http://localhost:44785/Home/Successhttp://localhost:44785/Home/FaillYJYMi/gVO9MWr32Pshaa/zAbSHY=800400235apiuser4025502306586032afafaMasterCard0Sale0100100Order 1230094900003AutoVPOS_ThreeDModelPayGatetruefalse00001-01-01T00:00:0000HATATA67YtBfBRTZ0XBKnAHi8c/A==WYGDgSIrSHDtYwF/WEN+nfwX63sppA=https://acs.bkm.com.tr/mdpayacs/pareq'; + $request = Request::create('', 'POST', [ + 'AuthenticationResponse' => urlencode($xml), + ]); + + $posMock = $this->getMockBuilder(KuveytPos::class) + ->setConstructorArgs([[], $this->threeDAccount, []]) + ->onlyMethods(['send', 'check3DHash']) + ->getMock(); + + $posMock->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + + $posMock->expects($this->once())->method('send')->willReturn([ + 'IsEnrolled' => 'false', + 'IsVirtual' => 'false', + 'ResponseCode' => 'EmptyMDException', + 'ResponseMessage' => 'Geçerli bir MD değeri giriniz.', + 'OrderId' => '0', + 'TransactionTime' => '0001-01-01T00:00:00', + 'BusinessKey' => '0', + ]); + $posMock->expects($this->once())->method('check3DHash')->willReturn(true); + + $posMock->make3DPayment($request); + $result = $posMock->getResponse(); + $result = (array) $result; + + $this->assertSame('declined', $result['status']); + $this->assertSame('EmptyMDException', $result['proc_return_code']); + $this->assertSame('EmptyMDException', $result['error_code']); + $this->assertSame('Geçerli bir MD değeri giriniz.', $result['error_message']); + $this->assertSame('Order 123', $result['order_id']); + $this->assertSame('Sale', $result['transaction']); + $this->assertSame('4025502306586032', $result['masked_number']); + $this->assertSame('100', $result['amount']); + $this->assertSame('TRY', $result['currency']); + $this->assertSame('lYJYMi/gVO9MWr32Pshaa/zAbSHY=', $result['hash']); + $this->assertNotEmpty($result['all']); + $this->assertNotEmpty($result['3d_all']); + } + + /** + * @return void + * + * @throws GuzzleException + */ + public function testMake3DPaymentAuthSuccessProvisionSuccess() + { + $this->pos->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $xml = '1.0.0http://localhost:44785/Home/Successhttp://localhost:44785/Home/FaillYJYMi/gVO9MWr32Pshaa/zAbSHY=800400235apiuser4025502306586032afafaMasterCard0Sale0100100Order 1230094900003AutoVPOS_ThreeDModelPayGatetruefalse00001-01-01T00:00:0000HATATA67YtBfBRTZ0XBKnAHi8c/A==WYGDgSIrSHDtYwF/WEN+nfwX63sppA=https://acs.bkm.com.tr/mdpayacs/pareq'; + $request = Request::create('', 'POST', [ + 'AuthenticationResponse' => urlencode($xml), + ]); + + $posMock = $this->getMockBuilder(KuveytPos::class) + ->setConstructorArgs([[], $this->threeDAccount, []]) + ->onlyMethods(['send', 'check3DHash']) + ->getMock(); + + $posMock->prepare($this->order, AbstractGateway::TX_PAY, $this->card); + $posMock->expects($this->once())->method('send')->willReturn([ + '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' => '4025502306586032', + '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=', + ]); + + $posMock->expects($this->once())->method('check3DHash')->willReturn(true); + + $posMock->make3DPayment($request); + $result = $posMock->getResponse(); + $result = (array) $result; + + $this->assertSame('approved', $result['status']); + $this->assertSame('00', $result['proc_return_code']); + $this->assertNull($result['error_code']); + $this->assertSame('660723214', $result['order_id']); + $this->assertSame('Sale', $result['transaction']); + $this->assertSame('4025502306586032', $result['masked_number']); + $this->assertSame('100', $result['amount']); + $this->assertSame('TRY', $result['currency']); + $this->assertSame('lYJYMi/gVO9MWr32Pshaa/zAbSHY=', $result['hash']); + $this->assertNotEmpty($result['all']); + $this->assertNotEmpty($result['3d_all']); + } + + /** + * @return void + * + * @throws BankClassNullException + * @throws BankNotFoundException + */ + public function testCreate3DHashForAuthorization() + { + $account = AccountFactory::createKuveytPosAccount( + 'kuveytpos', + '80', + 'apiuser', + '400235', + 'Api123' + ); + $pos = PosFactory::createPosGateway($account); + $order = [ + 'id' => 'ORDER-123', + 'amount' => 72.56, + 'currency' => 'TRY', + 'installment' => '0', + 'success_url' => 'http://localhost:44785/Home/Success', + 'fail_url' => 'http://localhost:44785/Home/Fail', + ]; + $hash = 'P3a0zjAklu2g8XDJfTx2qvwHH8g='; + $pos->prepare($order, AbstractGateway::TX_PAY); + $actual = $pos->create3DHash($pos->getAccount(), $pos->getOrder()); + $this->assertEquals($hash, $actual); + } + + /** + * @return void + * + * @throws BankClassNullException + * @throws BankNotFoundException + */ + public function testCreate3DHashForProvision() + { + $account = AccountFactory::createKuveytPosAccount( + 'kuveytpos', + '80', + 'apiuser', + '400235', + 'Api123' + ); + $pos = PosFactory::createPosGateway($account); + $order = [ + 'id' => 'ORDER-123', + 'amount' => 72.56, + 'currency' => 'TRY', + 'installment' => '0', + 'success_url' => 'http://localhost:44785/Home/Success', + 'fail_url' => 'http://localhost:44785/Home/Fail', + ]; + $hash = 'Bf+hZf2c1gf1pTXnEaSGxDpGRr0='; + $pos->prepare($order, AbstractGateway::TX_PAY); + $actual = $pos->create3DHash($pos->getAccount(), $pos->getOrder(), true); + $this->assertEquals($hash, $actual); + } + + /** + * @return void + * + * @throws BankClassNullException + * @throws BankNotFoundException + */ + public function testCreate3DPaymentXML() + { + $responseData = [ + 'MD' => '67YtBfBRTZ0XBKnAHi8c/A==', + 'VPosMessage' => [ + 'TransactionType' => 'Sale', + 'InstallmentCount' => '0', + 'Amount' => '100', + 'DisplayAmount' => '100', + 'CurrencyCode' => '0949', + 'MerchantOrderId' => 'Order 123', + 'TransactionSecurity' => '3', + ], + ]; + /** @var KuveytPos $pos */ + $pos = PosFactory::createPosGateway($this->threeDAccount); + $pos->prepare($this->order, AbstractGateway::TX_PAY); + + $actual = $pos->create3DPaymentXML($responseData); + + $expectedData = $this->getSample3DPaymentXMLData($pos, $responseData); + $this->assertEquals($expectedData, $actual); + } + + /** + * @param KuveytPos $pos + * @param $responseData + * + * @return array + */ + private function getSample3DPaymentXMLData(KuveytPos $pos, $responseData): array + { + $account = $pos->getAccount(); + $order = $pos->getOrder(); + $hash = $pos->create3DHash($pos->getAccount(), $pos->getOrder(), true); + + $requestData = [ + 'APIVersion' => KuveytPos::API_VERSION, + 'HashData' => $hash, + 'MerchantId' => $account->getClientId(), + 'CustomerId' => $account->customerId(), + 'UserName' => $account->getUsername(), + 'CustomerIPAddress' => $order->ip, + 'KuveytTurkVPosAdditionalData' => [ + 'AdditionalData' => [ + 'Key' => 'MD', + 'Data' => $responseData['MD'], + ], + ], + 'TransactionType' => $responseData['VPosMessage']['TransactionType'], + 'InstallmentCount' => $responseData['VPosMessage']['InstallmentCount'], + 'Amount' => $responseData['VPosMessage']['Amount'], + 'DisplayAmount' => $responseData['VPosMessage']['DisplayAmount'], + 'CurrencyCode' => $responseData['VPosMessage']['CurrencyCode'], + 'MerchantOrderId' => $responseData['VPosMessage']['MerchantOrderId'], + 'TransactionSecurity' => $responseData['VPosMessage']['TransactionSecurity'], + ]; + + return $requestData; + } + + /** + * @param string $methodName + * + * @return ReflectionMethod + * + * @throws ReflectionException + */ + private function getMethod(string $methodName): ReflectionMethod + { + $class = new \ReflectionClass(KuveytPos::class); + $method = $class->getMethod($methodName); + $method->setAccessible(true); + + return $method; + } +}