From bd14f8f3e6728c16d4c5919187d4e74c69520df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20=C3=87akar?= Date: Tue, 12 Jan 2016 15:59:46 +0200 Subject: [PATCH 1/2] event-dispatcher version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7577649..56ffe90 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "require": { "php": "> 5.3.0", "guzzle/guzzle": "3.9.2", - "symfony/event-dispatcher": "2.6.3" + "symfony/event-dispatcher": "2.8.1" }, "require-dev": { "squizlabs/php_codesniffer": "~1.5.2", From a3fd8f117e58e4bb5ddaeb2d0f6d4aacbf1ab047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20=C3=87akar?= Date: Thu, 18 Feb 2016 17:59:34 +0200 Subject: [PATCH 2/2] 3d integration --- .../Configuration/AbstractConfiguration.php | 72 +++++ src/Paranoia/Configuration/Gvp.php | 25 ++ src/Paranoia/Configuration/NestPay.php | 24 ++ src/Paranoia/Configuration/Posnet.php | 144 ++++++++++ .../Payment/Adapter/AdapterAbstract.php | 67 ++++- src/Paranoia/Payment/Adapter/Gvp.php | 171 +++++++++++- src/Paranoia/Payment/Adapter/NestPay.php | 140 +++++++++- src/Paranoia/Payment/Adapter/Posnet.php | 247 +++++++++++++++++- src/Paranoia/Payment/ConfirmRequest.php | 37 +++ .../Exception/ResponseVerificationError.php | 7 + src/Paranoia/Payment/Request.php | 112 ++++++++ .../Payment/Response/Payment3DResponse.php | 30 +++ .../Payment/Response/PaymentResponse.php | 23 ++ .../Payment/Response/ResponseAbstract.php | 16 ++ 14 files changed, 1084 insertions(+), 31 deletions(-) create mode 100644 src/Paranoia/Payment/ConfirmRequest.php create mode 100644 src/Paranoia/Payment/Exception/ResponseVerificationError.php create mode 100644 src/Paranoia/Payment/Response/Payment3DResponse.php diff --git a/src/Paranoia/Configuration/AbstractConfiguration.php b/src/Paranoia/Configuration/AbstractConfiguration.php index 59dd71b..b8ca07c 100644 --- a/src/Paranoia/Configuration/AbstractConfiguration.php +++ b/src/Paranoia/Configuration/AbstractConfiguration.php @@ -9,6 +9,21 @@ class AbstractConfiguration */ private $apiUrl; + /** + * @var string + */ + private $api3DUrl; + + /** + * @var string + */ + private $successUrl; + + /** + * @var string + */ + private $errorUrl; + /** * @param string $apiUrl * @@ -27,4 +42,61 @@ public function getApiUrl() { return $this->apiUrl; } + + /** + * @param string $api3DUrl + * + * @return $this + */ + public function setApi3DUrl($api3DUrl) + { + $this->api3DUrl = $api3DUrl; + return $this; + } + + /** + * @return string + */ + public function getApi3DUrl() + { + return $this->api3DUrl; + } + + /** + * @param string $key + * + * @return $this + */ + public function setSuccessUrl($url) + { + $this->successUrl = $url; + return $this; + } + + /** + * @return string + */ + public function getSuccessUrl() + { + return $this->successUrl; + } + + /** + * @param string $key + * + * @return $this + */ + public function setErrorUrl($url) + { + $this->errorUrl = $url; + return $this; + } + + /** + * @return string + */ + public function getErrorUrl() + { + return $this->errorUrl; + } } diff --git a/src/Paranoia/Configuration/Gvp.php b/src/Paranoia/Configuration/Gvp.php index 5af8632..e2e17d6 100644 --- a/src/Paranoia/Configuration/Gvp.php +++ b/src/Paranoia/Configuration/Gvp.php @@ -40,6 +40,12 @@ class Gvp extends AbstractConfiguration */ private $mode; + /** + * @var string + */ + private $securekey; + + /** * @param string $authorizationPassword * @@ -172,4 +178,23 @@ public function getTerminalId() { return $this->terminalId; } + + /** + * @param int $securekey + * + * @return $this + */ + public function setSecureKey($securekey) + { + $this->securekey = $securekey; + return $this; + } + + /** + * @return int + */ + public function getSecureKey() + { + return $this->securekey; + } } diff --git a/src/Paranoia/Configuration/NestPay.php b/src/Paranoia/Configuration/NestPay.php index 329ead8..a813acb 100644 --- a/src/Paranoia/Configuration/NestPay.php +++ b/src/Paranoia/Configuration/NestPay.php @@ -25,6 +25,11 @@ class NestPay extends AbstractConfiguration */ private $mode; + /** + * @var string + */ + private $storeKey; + /** * @param string $clientId * @@ -100,4 +105,23 @@ public function getUsername() { return $this->username; } + + /** + * @param string $key + * + * @return $this + */ + public function setStoreKey($storeKey) + { + $this->storeKey = $storeKey; + return $this; + } + + /** + * @return string + */ + public function getStoreKey() + { + return $this->storeKey; + } } diff --git a/src/Paranoia/Configuration/Posnet.php b/src/Paranoia/Configuration/Posnet.php index b0d3b92..d7c6cf4 100644 --- a/src/Paranoia/Configuration/Posnet.php +++ b/src/Paranoia/Configuration/Posnet.php @@ -13,6 +13,36 @@ class Posnet extends AbstractConfiguration */ private $terminalId; + /** + * @var int + */ + private $terminal3DId; + + /** + * @var int + */ + private $posnetId; + + /** + * @var string + */ + private $username; + + /** + * @var string + */ + private $password; + + /** + * @var string + */ + private $secureKey; + + /** + * @var string + */ + private $jokerVadaa; + /** * @param int $merchantId * @@ -50,4 +80,118 @@ public function getTerminalId() { return $this->terminalId; } + + /** + * @param int $terminal3DId + * + * @return $this + */ + public function setTerminal3DId($terminal3DId) + { + $this->terminal3DId = $terminal3DId; + return $this; + } + + /** + * @return int + */ + public function getTerminal3DId() + { + return $this->terminal3DId; + } + + /** + * @param int $posnetId + * + * @return $this + */ + public function setPosnetId($posnetId) + { + $this->posnetId = $posnetId; + return $this; + } + + /** + * @return int + */ + public function getPosnetId() + { + return $this->posnetId; + } + + /** + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param int $secureKey + * + * @return $this + */ + public function setSecureKey($secureKey) + { + $this->secureKey = $secureKey; + return $this; + } + + /** + * @return int + */ + public function getSecureKey() + { + return $this->secureKey; + } + + /** + * @param int $jokerVadaa + * + * @return $this + */ + public function setJokerVadaa($jokerVadaa) + { + $this->jokerVadaa = $jokerVadaa; + return $this; + } + + /** + * @return int + */ + public function getJokerVadaa() + { + return $this->jokerVadaa; + } } diff --git a/src/Paranoia/Payment/Adapter/AdapterAbstract.php b/src/Paranoia/Payment/Adapter/AdapterAbstract.php index e2e4975..d9d5e6b 100644 --- a/src/Paranoia/Payment/Adapter/AdapterAbstract.php +++ b/src/Paranoia/Payment/Adapter/AdapterAbstract.php @@ -1,11 +1,13 @@ configuration = $configuration; } @@ -133,6 +136,10 @@ abstract protected function buildCancelRequest(Request $request); */ abstract protected function buildRequest(Request $request, $requestBuilder); + abstract protected function build3DRequest(Request $request, $requestBuilder); + + abstract protected function check3DHashIntegrity($payload); + /** * parses response from returned provider. * @@ -156,19 +163,13 @@ abstract protected function parseResponse($rawResponse, $transactionType); protected function sendRequest($url, $data, $options = null) { $client = new HttpClient(); - $client->setConfig(array( - 'curl.options' => array( - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_SSL_VERIFYHOST => false, - ) - )); - $request = $client->post($url, null, $data); + try { - return $request->send()->getBody(); + $request = $client->post($url, ['form_params' => $data]); + return $request->getBody(); } catch (RequestException $e) { throw new CommunicationError('Communication failed: ' . $url); } - } /** @@ -218,6 +219,16 @@ protected function formatExpireDate($month, $year) return sprintf('%02s/%04s', $month, $year); } + protected function formatExpireMonth($month) + { + return sprintf('%02s', $month); + } + + protected function formatExpireYear($year) + { + return sprintf('%s', substr($year, -2)); + } + /** * returns formatted installment amount * @@ -230,6 +241,11 @@ protected function formatInstallment($installment) return (!is_numeric($installment) || intval($installment) <= 1) ? '' : $installment; } + protected function formatCardNumber($cardNumber) + { + return str_replace(' ', '', $cardNumber); + } + /** * returns formatted order number. * @@ -242,6 +258,11 @@ protected function formatOrderId($orderId) return $orderId; } + protected function hashBase64($data) + { + return base64_encode(pack("H*",sha1($data))); + } + /** * returns transaction code by expected provider. * @@ -297,6 +318,30 @@ public function sale(Request $request) return $response; } + public function sale3D(Request $request) + { + $rawRequest = $this->build3DRequest($request, 'buildSale3DRequest'); + $rawResponse = $this->sendRequest($this->configuration->getApi3DUrl(), $rawRequest); + return $rawResponse->__toString(); + } + + public function getBank3DResponse($payload) + { + return $this->parseBank3DResponse($payload); + } + + public function confirm3D(ConfirmRequest $confirmRequest) + { + $verify = $this->check3DHashIntegrity($confirmRequest->getPayload()); + if (! $verify) { + throw new ResponseVerificationError('Response cannot be verified'); + } + $rawRequest = $this->buildConfirmRequest($confirmRequest, 'buildSale3DConfirmRequest'); + $rawResponse = $this->sendRequest($this->configuration->getApiUrl(), $rawRequest); + $response = $this->parseResponse($rawResponse, self::TRANSACTION_TYPE_SALE); + return $response; + } + /** * @param \Paranoia\Payment\Request $request * diff --git a/src/Paranoia/Payment/Adapter/Gvp.php b/src/Paranoia/Payment/Adapter/Gvp.php index cb26af0..49568ce 100644 --- a/src/Paranoia/Payment/Adapter/Gvp.php +++ b/src/Paranoia/Payment/Adapter/Gvp.php @@ -4,9 +4,11 @@ use Paranoia\Common\Serializer\Serializer; use Paranoia\Payment\PaymentEventArg; use Paranoia\Payment\Request; +use Paranoia\Payment\ConfirmRequest; use Paranoia\Payment\Response\PaymentResponse; use Paranoia\Payment\Exception\UnexpectedResponse; use Paranoia\Payment\Exception\UnimplementedMethod; +use Paranoia\Payment\Exception\ResponseVerificationError; class Gvp extends AdapterAbstract { @@ -17,6 +19,7 @@ class Gvp extends AdapterAbstract self::TRANSACTION_TYPE_PREAUTHORIZATION => 'preauth', self::TRANSACTION_TYPE_POSTAUTHORIZATION => 'postauth', self::TRANSACTION_TYPE_SALE => 'sales', + self::TRANSACTION_TYPE_SALE_3D => 'sales', self::TRANSACTION_TYPE_CANCEL => 'void', self::TRANSACTION_TYPE_REFUND => 'refund', self::TRANSACTION_TYPE_POINT_QUERY => 'pointinquiry', @@ -73,15 +76,15 @@ private function buildTerminal(Request $request, $transactionType) * * @return array */ - private function buildCustomer() + private function buildCustomer($ipAddress = null, $email = null) { /** * we don't want to share customer information * to bank. */ return array( - 'IPAddress' => '127.0.0.1', - 'EmailAddress' => 'dummy@dummy.net' + 'IPAddress' => !is_null($ipAddress) ? $ipAddress : '127.0.0.1', + 'EmailAddress' => !is_null($email) ? $email : 'dummy@dummy.net' ); } @@ -99,7 +102,7 @@ private function buildCard(Request $request) $request->getExpireYear() ); return array( - 'Number' => $request->getCardNumber(), + 'Number' => $this->formatCardNumber($request->getCardNumber()), 'ExpireDate' => $expireMonth, 'CVV2' => $request->getSecurityCode() ); @@ -116,7 +119,7 @@ private function buildOrder(Request $request) { return array( 'OrderID' => $this->formatOrderId($request->getOrderId()), - 'GroupID' => null, + 'GroupID' => $request->getGroupId(), 'Description' => null ); } @@ -152,6 +155,22 @@ private function buildTransaction( ); } + private function build3DTransaction(ConfirmRequest $confirmRequest, $transactionType) + { + $transactionData = $this->buildTransaction($confirmRequest->getRequest(), $transactionType); + + $payload = $confirmRequest->getPayload(); + $secure3D = array( + 'AuthenticationCode' => $payload['cavv'], + 'SecurityLevel' => $payload['eci'], + 'TxnID' => $payload['xid'], + 'Md' => $payload['md'], + ); + $transactionData['Secure3D'] = $secure3D; + + return $transactionData; + } + /** * returns boolean true, when amount field is required * for request transaction type. @@ -166,6 +185,7 @@ private function isAmountRequired($transactionType) $transactionType, array( self::TRANSACTION_TYPE_SALE, + self::TRANSACTION_TYPE_SALE_3D, self::TRANSACTION_TYPE_PREAUTHORIZATION, self::TRANSACTION_TYPE_POSTAUTHORIZATION, ) @@ -186,6 +206,7 @@ private function isCardNumberRequired($transactionType) $transactionType, array( self::TRANSACTION_TYPE_SALE, + self::TRANSACTION_TYPE_SALE_3D, self::TRANSACTION_TYPE_PREAUTHORIZATION, ) ); @@ -204,6 +225,7 @@ private function getApiCredentialsByRequest($transactionType) $transactionType, array( self::TRANSACTION_TYPE_SALE, + self::TRANSACTION_TYPE_SALE_3D, self::TRANSACTION_TYPE_PREAUTHORIZATION, self::TRANSACTION_TYPE_POSTAUTHORIZATION, ) @@ -231,6 +253,7 @@ private function getApiCredentialsByRequest($transactionType) private function getSecurityHash($password) { $tidPrefix = str_repeat('0', 9 - strlen($this->configuration->getTerminalId())); + $terminalId = sprintf('%s%s', $tidPrefix, $this->configuration->getTerminalId()); return strtoupper(SHA1(sprintf('%s%s', $password, $terminalId))); } @@ -248,7 +271,7 @@ private function getTransactionHash(Request $request, $password, $transactionTyp { $orderId = $this->formatOrderId($request->getOrderId()); $terminalId = $this->configuration->getTerminalId(); - $cardNumber = $this->isCardNumberRequired($transactionType) ? $request->getCardNumber() : ''; + $cardNumber = $this->isCardNumberRequired($transactionType) ? $this->formatCardNumber($request->getCardNumber()) : ''; $amount = $this->isAmountRequired($transactionType) ? $this->formatAmount($request->getAmount()) : '1'; $securityData = $this->getSecurityHash($password); return strtoupper( @@ -265,6 +288,33 @@ private function getTransactionHash(Request $request, $password, $transactionTyp ); } + private function getTransaction3DHash(Request $request, $password, $transactionType) + { + $orderId = $this->formatOrderId($request->getOrderId()); + $terminalId = $this->configuration->getTerminalId(); + $cardNumber = $this->isCardNumberRequired($transactionType) ? $this->formatCardNumber($request->getCardNumber()) : ''; + $amount = $this->isAmountRequired($transactionType) ? $this->formatAmount($request->getAmount()) : '1'; + $installment = $this->formatInstallment($request->getInstallment()); + $securityData = $this->getSecurityHash($password); + + return strtoupper( + sha1( + sprintf( + '%s%s%s%s%s%s%s%s%s', + $terminalId, + $orderId, + $amount, + $this->configuration->getSuccessUrl(), + $this->configuration->getErrorUrl(), + $this->getProviderTransactionType(self::TRANSACTION_TYPE_SALE_3D), + $installment, + $this->configuration->getSecureKey(), + $securityData + ) + ) + ); + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildRequest() @@ -280,6 +330,22 @@ protected function buildRequest(Request $request, $requestBuilder) return array( 'data' => $xml ); } + protected function buildConfirmRequest(ConfirmRequest $confirmRequest, $requestBuilder) + { + $rawRequest = call_user_func(array( $this, $requestBuilder ), $confirmRequest); + $serializer = new Serializer(Serializer::XML); + $xml = $serializer->serialize( + $rawRequest, + array( 'root_name' => 'GVPSRequest' ) + ); + return array( 'data' => $xml ); + } + + public function build3DRequest(Request $request, $requestBuilder) + { + return call_user_func(array( $this, $requestBuilder ), $request); + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildPreauthorizationRequest() @@ -310,6 +376,65 @@ protected function buildSaleRequest(Request $request) return array_merge($requestData, $this->buildBaseRequest($request, self::TRANSACTION_TYPE_SALE)); } + protected function buildSale3DRequest(Request $request) + { + $cardNumber = $this->formatCardNumber($request->getCardNumber()); + $installment = $this->formatInstallment($request->getInstallment()); + $amount = $this->formatAmount($request->getAmount()); + $cardYear = $this->formatExpireYear($request->getExpireYear()); + $cardMonth = $this->formatExpireMonth($request->getExpireMonth()); + $currency = $this->formatCurrency($request->getCurrency()); + + $hashData = $this->getTransaction3DHash($request, $this->configuration->getAuthorizationPassword(), self::TRANSACTION_TYPE_SALE_3D); + + $requestData = array( + 'cardnumber' => $cardNumber, + 'cardexpiredatemonth' => $cardMonth, + 'cardexpiredateyear' => $cardYear, + 'mode' => $this->configuration->getMode(), + 'orderid' => $request->getOrderId(), + 'ordergroupid' => $request->getOrderId(), + 'cardcvv2' => $request->getSecurityCode(), + 'apiversion' => 'v0.01', + 'terminalprovuserid' => $this->configuration->getAuthorizationUsername(), + 'terminaluserid' => $this->configuration->getAuthorizationUsername(), + 'terminalid' => $this->configuration->getTerminalId(), + 'terminalmerchantid' => $this->configuration->getMerchantId(), + 'customeripaddress' => $request->getIPAddress(), + 'customeremailaddress' => $request->getEmail(), + 'txntype' => $this->getProviderTransactionType(self::TRANSACTION_TYPE_SALE_3D), + 'secure3dsecuritylevel' => '3D', + 'txnamount' => $amount, + 'txncurrencycode' => $currency, + 'companyname' => $this->configuration->getAuthorizationUsername(), + 'txninstallmentcount' => $installment, + 'successurl' => $this->configuration->getSuccessUrl(), + 'errorurl' => $this->configuration->getErrorUrl(), + 'secure3dhash' => $hashData, + 'refreshtime' => '60', + 'lang' => 'tr', + 'txnmotoind' => 'N', + 'orderdescription' => '' + ); + + return $requestData; + } + + protected function buildSale3DConfirmRequest(ConfirmRequest $confirmRequest) + { + $request = $confirmRequest->getRequest(); + $payload = $confirmRequest->getPayload(); + $amount = $this->formatAmount($request->getAmount()); + $installment = $this->formatInstallment($request->getInstallment()); + $currency = $this->formatCurrency($request->getCurrency()); + $type = $this->getProviderTransactionType(self::TRANSACTION_TYPE_SALE_3D); + + $requestData = $this->buildBaseRequest($request, self::TRANSACTION_TYPE_SALE_3D); + $requestData['Transaction'] = $this->build3DTransaction($confirmRequest, self::TRANSACTION_TYPE_SALE_3D); + + return $requestData; + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildRefundRequest() @@ -370,6 +495,7 @@ protected function parseResponse($rawResponse, $transactionType) } $response->setIsSuccess('00' == (string)$xml->Transaction->Response->Code); $response->setResponseCode((string)$xml->Transaction->ReasonCode); + $response->setRawResponse($xml); if (!$response->isSuccess()) { $errorMessages = array(); if (property_exists($xml->Transaction->Response, 'ErrorMsg')) { @@ -390,23 +516,46 @@ protected function parseResponse($rawResponse, $transactionType) $response->setResponseMessage('Success'); $response->setOrderId((string)$xml->Order->OrderID); $response->setTransactionId((string)$xml->Transaction->RetrefNum); + $response->setAuthCode((string)$xml->Transaction->AuthCode); } $event = $response->isSuccess() ? self::EVENT_ON_TRANSACTION_SUCCESSFUL : self::EVENT_ON_TRANSACTION_FAILED; $this->getDispatcher()->dispatch($event, new PaymentEventArg(null, $response, $transactionType)); return $response; } + protected function check3DHashIntegrity($payload) { + $params = explode(':', $payload['hashparams']); + $secureKey = $this->configuration->getSecureKey(); + $hash = ''; + + foreach($params as $param) { + if (!empty($param)) + $hash .= is_null($payload[$param]) ? '' : $payload[$param]; + } + + if($this->hashBase64($hash . $secureKey) == $payload['hash']) + return true; + + return false; + } + + protected function parseBank3DResponse($rawResponse) + { + $response = new PaymentResponse(); + $response->setOrderId($rawResponse['orderid']); + $response->setTransactionId($rawResponse['orderid']); + $response->setMdStatus($rawResponse['mdstatus']); + $response->setResponseMessage($rawResponse['mderrormessage']); + return $response; + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::formatAmount() */ protected function formatAmount($amount, $reverse = false) { - if (!$reverse) { - return number_format($amount, 2, '', ''); - } else { - return (float)sprintf('%s.%s', substr($amount, 0, -2), substr($amount, -2)); - } + return str_replace(".", "", number_format($amount, 2, '.', '')); } /** diff --git a/src/Paranoia/Payment/Adapter/NestPay.php b/src/Paranoia/Payment/Adapter/NestPay.php index 05f19f8..480ab32 100644 --- a/src/Paranoia/Payment/Adapter/NestPay.php +++ b/src/Paranoia/Payment/Adapter/NestPay.php @@ -4,9 +4,11 @@ use Paranoia\Common\Serializer\Serializer; use Paranoia\Payment\PaymentEventArg; use Paranoia\Payment\Request; +use Paranoia\Payment\ConfirmRequest; use Paranoia\Payment\Response\PaymentResponse; use Paranoia\Payment\Exception\UnexpectedResponse; use Paranoia\Payment\Exception\UnimplementedMethod; +use Paranoia\Payment\Exception\ResponseVerificationError; class NestPay extends AdapterAbstract { @@ -17,6 +19,7 @@ class NestPay extends AdapterAbstract self::TRANSACTION_TYPE_PREAUTHORIZATION => 'PreAuth', self::TRANSACTION_TYPE_POSTAUTHORIZATION => 'PostAuth', self::TRANSACTION_TYPE_SALE => 'Auth', + self::TRANSACTION_TYPE_SALE_3D => 'Auth', self::TRANSACTION_TYPE_CANCEL => 'Void', self::TRANSACTION_TYPE_REFUND => 'Credit', self::TRANSACTION_TYPE_POINT_QUERY => '', @@ -38,6 +41,21 @@ private function buildBaseRequest() ); } + protected function get3DTransactionHash($orderId, $amount, $randomKey) + { + $hashData = sprintf('%s%s%s%s%s%s%s', + $this->configuration->getClientId(), + $orderId, + $amount, + $this->configuration->getSuccessUrl(), + $this->configuration->getErrorUrl(), + $randomKey, + $this->configuration->getStoreKey() + ); + + return base64_encode(pack("H*",sha1($hashData))); + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildRequest() @@ -53,6 +71,26 @@ protected function buildRequest(Request $request, $requestBuilder) return array( 'DATA' => $xml ); } + /** + * {@inheritdoc} + * @see Paranoia\Payment\Adapter\AdapterAbstract::buildRequest() + */ + protected function buildConfirmRequest(ConfirmRequest $confirmRequest, $requestBuilder) + { + $rawRequest = call_user_func(array( $this, $requestBuilder ), $confirmRequest); + $serializer = new Serializer(Serializer::XML); + $xml = $serializer->serialize( + array_merge($rawRequest, $this->buildBaseRequest()), + array( 'root_name' => 'CC5Request' ) + ); + return array( 'DATA' => $xml ); + } + + public function build3DRequest(Request $request, $requestBuilder) + { + return call_user_func(array( $this, $requestBuilder ), $request); + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildPreauthorizationRequest() @@ -69,7 +107,7 @@ protected function buildPreAuthorizationRequest(Request $request) 'Total' => $amount, 'Currency' => $currency, 'Taksit' => $installment, - 'Number' => $request->getCardNumber(), + 'Number' => $this->formatCardNumber($request->getCardNumber()), 'Cvv2Val' => $request->getSecurityCode(), 'Expires' => $expireMonth, 'OrderId' => $this->formatOrderId($request->getOrderId()), @@ -107,7 +145,7 @@ protected function buildSaleRequest(Request $request) 'Total' => $amount, 'Currency' => $currency, 'Taksit' => $installment, - 'Number' => $request->getCardNumber(), + 'Number' => $this->formatCardNumber($request->getCardNumber()), 'Cvv2Val' => $request->getSecurityCode(), 'Expires' => $expireMonth, 'OrderId' => $this->formatOrderId($request->getOrderId()), @@ -115,6 +153,76 @@ protected function buildSaleRequest(Request $request) return $requestData; } + protected function buildSale3DRequest(Request $request) + { + $clientId = $this->configuration->getClientId(); + $orderId = $this->formatOrderId($request->getOrderId()); + $amount = $this->formatAmount($request->getAmount()); + $successURL = $this->configuration->getSuccessUrl(); + $errorURL = $this->configuration->getErrorUrl(); + $secureCode = $this->configuration->getStoreKey(); + $cardMonth = $request->getExpireMonth(); + $cardYear = $request->getExpireYear(); + $currency = $this->formatCurrency($request->getCurrency()); + $randomKey = md5(microtime()); + + $hash = $this->get3DTransactionHash($orderId, $amount, $randomKey); + + $requestData = array( + 'clientid' => $clientId, + 'storetype' => '3d', + 'hash' => $hash, + 'pan' => $this->formatCardNumber($request->getCardNumber()), + 'amount' => $amount, + 'currency' => $currency, + 'oid' => $orderId, + 'okUrl' => $successURL, + 'failUrl' => $errorURL, + 'rnd' => $randomKey, + 'lang' => 'tr', + 'kart_sahibi' => $request->getCardHolderName(), + 'Ecom_Payment_Card_ExpDate_Month' => $cardMonth, + 'Ecom_Payment_Card_ExpDate_Year' => $cardYear, + 'cv2' => $request->getSecurityCode() + ); + + return $requestData; + } + + protected function buildSale3DConfirmRequest(ConfirmRequest $confirmRequest) + { + $request = $confirmRequest->getRequest(); + $payload = $confirmRequest->getPayload(); + $amount = $this->formatAmount($request->getAmount()); + $installment = $this->formatInstallment($request->getInstallment()); + $currency = $this->formatCurrency($request->getCurrency()); + $orderId = $this->formatOrderId($request->getOrderId()); + $type = $this->getProviderTransactionType(self::TRANSACTION_TYPE_SALE_3D); + + $verify = $this->check3DHashIntegrity($payload, $this->configuration->getStoreKey()); + + if (! $verify) { + throw new ResponseVerificationError('Response cannot be verified'); + } + + $requestData = array( + 'Type' => $type, + 'IPAddress' => $request->getIPAddress(), + 'OrderId' => $orderId, + 'GroupId' => $orderId, + 'Total' => $amount, + 'Currency' => $currency, + 'Number' => $payload['md'], + 'Taksit' => $installment, + 'PayerSecurityLevel' => $payload['eci'], + 'PayerTxnId' => $payload['xid'], + 'PayerAuthenticationCode' => $payload['cavv'], + 'CardholderPresentCode' => 13 + ); + + return $requestData; + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildRefundRequest() @@ -186,6 +294,7 @@ protected function parseResponse($rawResponse, $transactionType) $this->getDispatcher()->dispatch(self::EVENT_ON_EXCEPTION, $eventArg); throw $exception; } + $response->setRawResponse($xml); $response->setIsSuccess((string)$xml->Response == 'Approved'); $response->setResponseCode((string)$xml->ProcReturnCode); if (!$response->isSuccess()) { @@ -210,10 +319,37 @@ protected function parseResponse($rawResponse, $transactionType) } else { $response->setResponseMessage('Success'); $response->setOrderId((string)$xml->OrderId); + $response->setAuthCode((string)$xml->AuthCode); $response->setTransactionId((string)$xml->TransId); } $event = $response->isSuccess() ? self::EVENT_ON_TRANSACTION_SUCCESSFUL : self::EVENT_ON_TRANSACTION_FAILED; $this->getDispatcher()->dispatch($event, new PaymentEventArg(null, $response, $transactionType)); return $response; } + + protected function parseBank3DResponse($rawResponse) + { + $response = new PaymentResponse(); + $response->setOrderId($rawResponse['oid']); + $response->setTransactionId($rawResponse['oid']); + $response->setMdStatus($rawResponse['mdStatus']); + $response->setResponseMessage($rawResponse['mdErrorMsg']); + return $response; + } + + public function check3DHashIntegrity($payload) { + $params = explode(':', $payload['HASHPARAMS']); + $storeKey = $this->configuration->getStoreKey(); + $hash = ''; + + foreach($params as $param) { + if (!empty($param)) + $hash .= !isset($payload[$param]) ? '' : $payload[$param]; + } + + if($this->hashBase64($hash . $storeKey) == $payload['HASH']) + return true; + + return false; + } } diff --git a/src/Paranoia/Payment/Adapter/Posnet.php b/src/Paranoia/Payment/Adapter/Posnet.php index 1f6da20..0b95221 100644 --- a/src/Paranoia/Payment/Adapter/Posnet.php +++ b/src/Paranoia/Payment/Adapter/Posnet.php @@ -4,6 +4,7 @@ use Paranoia\Common\Serializer\Serializer; use Paranoia\Payment\PaymentEventArg; use Paranoia\Payment\Request; +use Paranoia\Payment\ConfirmRequest; use Paranoia\Payment\Response\PaymentResponse; use Paranoia\Payment\Exception\UnexpectedResponse; use Paranoia\Payment\Exception\UnimplementedMethod; @@ -26,6 +27,7 @@ class Posnet extends AdapterAbstract self::TRANSACTION_TYPE_PREAUTHORIZATION => 'auth', self::TRANSACTION_TYPE_POSTAUTHORIZATION => 'capt', self::TRANSACTION_TYPE_SALE => 'sale', + self::TRANSACTION_TYPE_SALE_3D => 'Sale', self::TRANSACTION_TYPE_CANCEL => 'reverse', self::TRANSACTION_TYPE_REFUND => 'return', self::TRANSACTION_TYPE_POINT_QUERY => 'pointinquiry', @@ -37,11 +39,12 @@ class Posnet extends AdapterAbstract * * @return array */ - private function buildBaseRequest() + private function buildBaseRequest($is3D = null) { return array( - 'mid' => $this->configuration->getMerchantId(), - 'tid' => $this->configuration->getTerminalId() + 'mid' => $this->configuration->getMerchantId(), + 'tid' => !$is3D ? $this->configuration->getTerminalId() : + $this->configuration->getTerminal3DId() ); } @@ -60,12 +63,97 @@ protected function buildRequest(Request $request, $requestBuilder) return array( 'xmldata' => $xml ); } + protected function buildConfirmRequest(ConfirmRequest $confirmRequest, $requestBuilder) + { + $rawRequest = call_user_func(array( $this, $requestBuilder ), $confirmRequest); + $serializer = new Serializer(Serializer::XML); + $xml = $serializer->serialize( + array_merge($this->buildBaseRequest(true), $rawRequest), + array( 'root_name' => 'posnetRequest' ) + ); + return array( 'xmldata' => $xml ); + } + + protected function buildRawRequest($request, $requestBuilder) + { + $rawRequest = call_user_func(array( $this, $requestBuilder ), $request); + $serializer = new Serializer(Serializer::XML); + $xml = $serializer->serialize( + array_merge($this->buildBaseRequest(true), $rawRequest), + array( 'root_name' => 'posnetRequest' ) + ); + return array( 'xmldata' => $xml ); + } + + public function sale3D(Request $request) + { + $rawRequest = $this->buildRequest($request, 'buildSale3DRequest'); + $rawResponse = $this->sendRequest($this->configuration->getApiUrl(), $rawRequest); + $response = $this->parse3DRequestResponse($rawResponse); + + if(! $response->isSuccess()) { + return $response; + } + + $rawRequest = $this->buildRedirect3DRequest($response); + $rawResponse = $this->sendRequest($this->configuration->getApi3DUrl(), $rawRequest); + return $rawResponse->__toString(); + } + + public function confirm3D(ConfirmRequest $confirmRequest) + { + $request = $confirmRequest->getRequest(); + $payload = $confirmRequest->getPayload(); + + $rawRequest = $this->buildRawRequest($payload, 'buildSale3DResolveRequest'); + $rawResponse = $this->sendRequest($this->configuration->getApiUrl(), $rawRequest); + $response = $this->parse3DResolveResponse($rawResponse); + + if(! $response->isSuccess()) { + return $response; + } + + $mdStatus = $response->getMdStatus(); + + $rawRequest = $this->buildRawRequest($payload, 'buildSale3DTranRequest'); + $rawResponse = $this->sendRequest($this->configuration->getApiUrl(), $rawRequest); + $response = $this->parseResponse($rawResponse, self::TRANSACTION_TYPE_SALE); + $response->setMdStatus($mdStatus); + + return $response; + } + + public function build3DRequest(Request $request, $requestBuilder) + { + // pass + } + + protected function buildRedirect3DRequest($response) + { + $data = $response->getData(); + + $requestData = array( + 'mid' => $this->configuration->getMerchantId(), + 'posnetID' => $this->configuration->getPosnetId(), + 'posnetData' => $data['data1'], + 'posnetData2' => $data['data2'], + 'digest' => $data['sign'], + 'merchantReturnURL' => $this->configuration->getSuccessUrl(), + 'lang' => 'tr', + 'url' => $this->configuration->getErrorUrl(), + 'openANewWindow' => '0', + ); + + return $requestData; + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildPreauthorizationRequest() */ protected function buildPreauthorizationRequest(Request $request) { + $cardNumber = $this->formatCardNumber($request->getCardNumber()); $amount = $this->formatAmount($request->getAmount()); $installment = $this->formatInstallment($request->getInstallment()); $currency = $this->formatCurrency($request->getCurrency()); @@ -73,7 +161,7 @@ protected function buildPreauthorizationRequest(Request $request) $type = $this->getProviderTransactionType(self::TRANSACTION_TYPE_PREAUTHORIZATION); $requestData = array( $type => array( - 'ccno' => $request->getCardNumber(), + 'ccno' => $cardNumber, 'expDate' => $expireMonth, 'cvc' => $request->getSecurityCode(), 'amount' => $amount, @@ -119,6 +207,7 @@ protected function buildPostAuthorizationRequest(Request $request) */ protected function buildSaleRequest(Request $request) { + $cardNumber = $this->formatCardNumber($request->getCardNumber()); $amount = $this->formatAmount($request->getAmount()); $installment = $this->formatInstallment($request->getInstallment()); $currency = $this->formatCurrency($request->getCurrency()); @@ -126,7 +215,7 @@ protected function buildSaleRequest(Request $request) $type = $this->getProviderTransactionType(self::TRANSACTION_TYPE_SALE); $requestData = array( $type => array( - 'ccno' => $request->getCardNumber(), + 'ccno' => $cardNumber, 'expDate' => $expireMonth, 'cvc' => $request->getSecurityCode(), 'amount' => $amount, @@ -138,9 +227,57 @@ protected function buildSaleRequest(Request $request) // 'multiplePoint' => "000000" ) ); + + return $requestData; + } + + protected function buildSale3DRequest(Request $request) + { + $cardNumber = $this->formatCardNumber($request->getCardNumber()); + $amount = $this->formatAmount($request->getAmount()); + $installment = $this->formatInstallment($request->getInstallment()); + $currency = $this->formatCurrency($request->getCurrency()); + $expireDate = $this->formatExpireDate($request->getExpireMonth(), $request->getExpireYear()); + $type = $this->getProviderTransactionType(self::TRANSACTION_TYPE_SALE_3D); + + $requestData = array( + 'oosRequestData' => array( + 'posnetid' => $this->configuration->getPosnetId(), + 'ccno' => $cardNumber, + 'expDate' => $expireDate, + 'cvc' => $request->getSecurityCode(), + 'amount' => $amount, + 'currencyCode' => $currency, + 'installment' => $installment, + 'XID' => $request->getOrderId(), + 'cardHolderName' => $request->getCardHolderName(), + 'tranType' => $type + ) + ); + return $requestData; } + protected function buildSale3DResolveRequest($payload) + { + return array( + 'oosResolveMerchantData' => array( + 'bankData' => $payload['BankPacket'], + 'merchantData' => $payload['MerchantPacket'], + 'sign' => $payload['Sign'] + ) + ); + } + + protected function buildSale3DTranRequest($payload) + { + return array( + 'oosTranData' => array( + 'bankData' => $payload['BankPacket'] + ) + ); + } + /** * {@inheritdoc} * @see Paranoia\Payment\Adapter\AdapterAbstract::buildRefundRequest() @@ -213,7 +350,8 @@ protected function parseResponse($rawResponse, $transactionType) $this->getDispatcher()->dispatch(self::EVENT_ON_EXCEPTION, $eventArg); throw $exception; } - $response->setIsSuccess((int)$xml->approved > 0); + $response->setIsSuccess((int)$xml->approved == 1); + $response->setRawResponse($xml); if (!$response->isSuccess()) { $response->setResponseCode((string)$xml->respCode); $errorMessages = array(); @@ -233,7 +371,7 @@ protected function parseResponse($rawResponse, $transactionType) } $response->setTransactionId((string)$xml->hostlogkey); if (property_exists($xml, 'authCode')) { - $response->setOrderId((string)$xml->authCode); + $response->setAuthCode((string)$xml->authCode); } } $event = $response->isSuccess() ? self::EVENT_ON_TRANSACTION_SUCCESSFUL : self::EVENT_ON_TRANSACTION_FAILED; @@ -241,6 +379,101 @@ protected function parseResponse($rawResponse, $transactionType) return $response; } + public function parse3DResolveResponse($rawResponse) { + + $response = new PaymentResponse(); + + try { + $xml = simplexml_load_string($rawResponse); + + $approved = (int) $xml->approved; + $orderId = (string) $xml->oosResolveMerchantDataResponse->xid; + $errorMsg = (string) $xml->respText; + $mdStatus = (int) $xml->oosResolveMerchantDataResponse->mdStatus; + $mdErrorMsg = (string) $xml->oosResolveMerchantDataResponse->mdErrorMessage; + + // NOTE: $mdStatus should be 9 for success in test env. + $response->setIsSuccess($approved == 1 && $mdStatus == 1); + $response->setOrderId($orderId); + $response->setMdStatus($mdStatus); + $response->setResponseCode($xml->respCode); + $response->setResponseMessage(sprintf('%s - %s', utf8_encode($errorMsg), $mdErrorMsg)); + } catch(Exception $e) { + $response->setIsSuccess(false); + $response->setResponseMessage($e->getMessage()); + } + + $response->setRawResponse(utf8_encode($rawResponse)); + + return $response; + } + + protected function parse3DRequestResponse($rawResponse) + { + $response = new PaymentResponse(); + + try { + $resultObject = simplexml_load_string($rawResponse); + + $approved = (string) $resultObject->approved; + $respCode = (string) $resultObject->respCode; + $respMessage = (string) $resultObject->respText; + + $response->setIsSuccess($approved == 1); + + if($approved == 1) { + $data1 = (string) $resultObject->oosRequestDataResponse->data1; + $data2 = (string) $resultObject->oosRequestDataResponse->data2; + $sign = (string) $resultObject->oosRequestDataResponse->sign; + + $data = [ + 'data1' => $data1, + 'data2' => $data2, + 'sign' => $sign + ]; + + $response->setData($data); + } + + $response->setResponseCode($respCode); + $response->setResponseMessage(utf8_encode($respMessage)); + } catch(Exception $e){ + $response->setIsSuccess(false); + $response->setResponseMessage($e->getMessage()); + } + + $response->setRawResponse(utf8_encode($rawResponse)); + + return $response; + } + + protected function parseBank3DResponse($rawResponse) + { + $response = new PaymentResponse(); + + $response->setOrderId($rawResponse['Xid']); + $response->setTransactionId($rawResponse['Xid']); + + // IMPORTANT! in this step, Posnet hasn't sent mdStatus to us yet. + // so assume that mdStatus is 1 to continue. + $response->setMdStatus(1); + + // extraData is not currently using. + $extraData = [ + 'bankPacket' => $rawResponse['BankPacket'], + 'merchantPacket' => $rawResponse['MerchantPacket'], + 'sign' => $rawResponse['Sign'], + ]; + $response->setData($extraData); + + return $response; + } + + public function check3DHashIntegrity($payload) + { + return true; + } + /** * {@inheritdoc} * Posnet tutar değerinde nokta istemiyor. Örnek:15.00TL için 1500 gönderilmesi gerekiyor. diff --git a/src/Paranoia/Payment/ConfirmRequest.php b/src/Paranoia/Payment/ConfirmRequest.php new file mode 100644 index 0000000..138827c --- /dev/null +++ b/src/Paranoia/Payment/ConfirmRequest.php @@ -0,0 +1,37 @@ +request = $request; + $this->payload = $payload; + } + + public function setRequest(Request $request) + { + $this->request = $request; + return $this; + } + + public function getRequest() + { + return $this->request; + } + + public function setPayload($payload) + { + $this->payload = $payload; + return $this; + } + + public function getPayload() + { + return $this->payload; + } +} diff --git a/src/Paranoia/Payment/Exception/ResponseVerificationError.php b/src/Paranoia/Payment/Exception/ResponseVerificationError.php new file mode 100644 index 0000000..bbe97d3 --- /dev/null +++ b/src/Paranoia/Payment/Exception/ResponseVerificationError.php @@ -0,0 +1,7 @@ +groupId; + } + + /** + * sets order identity to request object. + * + * @param $groupId + * + * @return self + */ + public function setGroupId($groupId) + { + $this->groupId = $groupId; + return $this; + } + /** * returns order amount. * @@ -169,6 +212,29 @@ public function setCardNumber($cardNumber) return $this; } + /** + * returns card holder name. + * + * @return string + */ + public function getCardHolderName() + { + return $this->cardHolderName; + } + + /** + * sets card holder name to request object. + * + * @param string $cardHolderName Numeric value + * + * @return self + */ + public function setCardHolderName($cardHolderName) + { + $this->cardHolderName = $cardHolderName; + return $this; + } + /** * returns card security code. * @@ -283,4 +349,50 @@ public function setAuthCode($authCode) $this->authCode = $authCode; return $this; } + + /** + * returns ip address. + * + * @return string + */ + public function getIPAddress() + { + return $this->ipAddress; + } + + /** + * sets ip address to request object. + * + * @param string $ipAddress + * + * @return self + */ + public function setIPAddress($ipAddress) + { + $this->ipAddress = $ipAddress; + return $this; + } + + /** + * returns email. + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * sets email to request object. + * + * @param string $email + * + * @return self + */ + public function setEmail($email) + { + $this->email = $email; + return $this; + } } diff --git a/src/Paranoia/Payment/Response/Payment3DResponse.php b/src/Paranoia/Payment/Response/Payment3DResponse.php new file mode 100644 index 0000000..53b0a2d --- /dev/null +++ b/src/Paranoia/Payment/Response/Payment3DResponse.php @@ -0,0 +1,30 @@ +mdStatus = $mdStatus; + return $this; + } + + public function getMdStatus() + { + return $this->mdStatus; + } + + public function setData($data) + { + $this->data = $data; + return $this; + } + + public function getData() + { + return $this->data; + } +} diff --git a/src/Paranoia/Payment/Response/PaymentResponse.php b/src/Paranoia/Payment/Response/PaymentResponse.php index e569647..d7e45fe 100644 --- a/src/Paranoia/Payment/Response/PaymentResponse.php +++ b/src/Paranoia/Payment/Response/PaymentResponse.php @@ -3,5 +3,28 @@ class PaymentResponse extends ResponseAbstract implements ResponseInterface { + private $mdStatus; + private $data; + public function setMdStatus($mdStatus) + { + $this->mdStatus = $mdStatus; + return $this; + } + + public function getMdStatus() + { + return $this->mdStatus; + } + + public function setData($data) + { + $this->data = $data; + return $this; + } + + public function getData() + { + return $this->data; + } } diff --git a/src/Paranoia/Payment/Response/ResponseAbstract.php b/src/Paranoia/Payment/Response/ResponseAbstract.php index 93dda76..79f608d 100644 --- a/src/Paranoia/Payment/Response/ResponseAbstract.php +++ b/src/Paranoia/Payment/Response/ResponseAbstract.php @@ -39,6 +39,11 @@ abstract class ResponseAbstract */ protected $responseMessage; + /** + * @var string + */ + protected $rawResponse; + /** * {@inheritdoc} * @see \Payment\Response\ResponseInterface::isSuccess() @@ -174,4 +179,15 @@ public function setResponseMessage($responseMessage) $this->responseMessage = $responseMessage; return $this; } + + public function setRawResponse($rawResponse) + { + $this->rawResponse = $rawResponse; + return $this; + } + + public function getRawResponse() + { + return $this->rawResponse; + } }