diff --git a/README.md b/README.md index 1334879..351c20d 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ $TPV = new Redsys\Tpv\Tpv($config); # Realizamos la comprobación de la transacción try { - $datos = $TPV->checkTransaction($_POST); + $datos = $TPV->checkTransactionXml($_POST); } catch (Exception $e) { die(file_put_contents(__DIR__.'/logs/errores-tpv.log', $e->getMessage(), FILE_APPEND)); } diff --git a/src/Redsys/Tpv/Curl.php b/src/Redsys/Tpv/Curl.php index f72aa11..827718b 100644 --- a/src/Redsys/Tpv/Curl.php +++ b/src/Redsys/Tpv/Curl.php @@ -1,7 +1,9 @@ ['.$last['line'].'] '.$row.''; - echo '
'; var_dump($info); echo '
'; + echo '
';
+        var_dump($info);
+        echo '
'; } public static function dd($info, $title = '') diff --git a/src/Redsys/Tpv/Signature.php b/src/Redsys/Tpv/Signature.php index 4814e97..0d3cbeb 100644 --- a/src/Redsys/Tpv/Signature.php +++ b/src/Redsys/Tpv/Signature.php @@ -5,21 +5,39 @@ class Signature { - public static function fromValues($prefix, $values, $key) + public static function fromValues($prefix, array $values, $key) { $fields = array('Amount', 'Order', 'MerchantCode', 'Currency', 'TransactionType', 'MerchantURL'); return self::calculate($prefix, $fields, $values, $key); } - public static function fromTransaction($prefix, $values, $key) + public static function fromTransaction($prefix, array $values, $key) { $fields = array('Amount', 'Order', 'MerchantCode', 'Currency', 'Response'); return self::calculate($prefix, $fields, $values, $key); } - private static function calculate($prefix, $fields, $values, $key) + public static function fromTransactionXML($prefix, array $values, $key) + { + $fields = array('Amount', 'Order', 'MerchantCode', 'Currency', 'Response', 'TransactionType', 'SecurePayment'); + + return self::calculate($prefix, $fields, $values, $key); + } + + public static function fromXML($xml, $key) + { + preg_match('#([^<]+)#i', $xml, $order); + + if (empty($order[1])) { + throw new Exception('Can not be extracted Order from XML string'); + } + + return self::MAC256($xml, self::encryptKey($order[1], $key)); + } + + private static function calculate($prefix, array $fields, array $values, $key) { foreach ($fields as $field) { if (!isset($values[$prefix.$field])) { @@ -27,9 +45,9 @@ private static function calculate($prefix, $fields, $values, $key) } } - $key = self::encrypt3DES($values[$prefix.'Order'], base64_decode($key)); + $key = self::encryptKey($values[$prefix.'Order'], $key); - return base64_encode(hash_hmac('sha256', base64_encode(json_encode($values)), $key, true)); + return self::MAC256(base64_encode(json_encode($values)), $key); } private static function encrypt3DES($message, $key) @@ -38,4 +56,14 @@ private static function encrypt3DES($message, $key) return mcrypt_encrypt(MCRYPT_3DES, $key, $message, MCRYPT_MODE_CBC, $iv); } -} \ No newline at end of file + + private static function encryptKey($order, $key) + { + return self::encrypt3DES($order, base64_decode($key)); + } + + private static function MAC256($string, $key) + { + return base64_encode(hash_hmac('sha256', $string, $key, true)); + } +} diff --git a/src/Redsys/Tpv/Tpv.php b/src/Redsys/Tpv/Tpv.php index 1cdfbc1..d0bd3ca 100644 --- a/src/Redsys/Tpv/Tpv.php +++ b/src/Redsys/Tpv/Tpv.php @@ -2,7 +2,8 @@ namespace Redsys\Tpv; use Exception; -use DOMDocument, DOMElement; +use DOMDocument; +use DOMElement; use Redsys\Messages\Messages; class Tpv @@ -152,22 +153,17 @@ public function sendXml(array $options) { $this->values = array(); - if (isset($options['Order'])) { - $options['Order'] = $this->getOrder($options['Order']); - } - - if (isset($options['Amount'])) { - $options['Amount'] = $this->getAmount($options['Amount']); - } + $options['Order'] = $this->getOrder($options['Order']); + $options['Amount'] = $this->getAmount($options['Amount']); $this->setValueDefault($options, 'MerchantCode'); - $this->setValueDefault($options, 'MerchantName'); - $this->setValueDefault($options, 'MerchantData'); + $this->setValueDefault($options, 'MerchantURL'); $this->setValueDefault($options, 'Currency'); $this->setValueDefault($options, 'Terminal'); - $this->setValueDefault($options, 'TransactionType'); - $this->setValueDefault($options, 'Order'); - $this->setValueDefault($options, 'Amount'); + + $this->setValue($options, 'TransactionType'); + $this->setValue($options, 'Order'); + $this->setValue($options, 'Amount'); $Curl = new Curl(array( 'base' => $this->getPath('') @@ -175,7 +171,9 @@ public function sendXml(array $options) $Curl->setHeader(CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); - return $Curl->post('/operaciones', array(), 'entrada='.$this->xmlArray2string($this->setXmlValues())); + return $Curl->post('/operaciones', array( + 'entrada' => $this->xmlArray2string($this->setXmlValues()) + )); } private function setXmlValues() @@ -192,19 +190,18 @@ private function setXmlValues() private function xmlArray2string($xml) { $doc = new DOMDocument(); - $doc->formatOutput = true; $data = $doc->createElement('DATOSENTRADA'); foreach ($xml as $key => $value) { - $data->appendChild((new DOMElement($key, $value))); + $data->appendChild((new DOMElement(strtoupper($key), $value))); } $root = $doc->createElement('REQUEST'); - $root->appendChild(new DOMElement('DS_SIGNATUREVERSION', $this->options['SignatureVersion'])); - $root->appendChild(new DOMElement('DS_SIGNATURE', $this->getValuesSignature())); $root->appendChild($data); + $root->appendChild(new DOMElement('DS_SIGNATUREVERSION', $this->options['SignatureVersion'])); + $root->appendChild(new DOMElement('DS_SIGNATURE', Signature::fromXML((string)$doc->saveXML($data), $this->options['Key']))); $doc->appendChild($root); @@ -216,6 +213,31 @@ public function xmlString2array($xml) return json_decode(json_encode(simplexml_load_string($xml)), true); } + public function checkTransactionXml(array $post) + { + $prefix = 'Ds_'; + + if (empty($post) || empty($post[$prefix.'Signature']) || empty($post[$prefix.'MerchantParameters'])) { + throw new Exception('_POST data is empty'); + } + + $data = $this->getTransactionParameters($post); + $data = $this->xmlString2array($data['datos']); + + if (empty($data)) { + throw new Exception('_POST data can not be decoded'); + } + + $this->checkTransactionError($data, $prefix); + $this->checkTransactionResponse($data, $prefix); + + $signature = Signature::fromTransactionXml($prefix, $data, $this->options['Key']); + + $this->checkTransactionSignature($signature, $post[$prefix.'Signature']); + + return array_merge($post, $data); + } + private function setValueDefault(array $options, $option) { $code = $this->option_prefix.$option; @@ -291,16 +313,33 @@ public function checkTransaction(array $post) throw new Exception('_POST data can not be decoded'); } + $this->checkTransactionError($data, $prefix); + $this->checkTransactionResponse($data, $prefix); + + $signature = Signature::fromTransaction($prefix, $data, $this->options['Key']); + + $this->checkTransactionSignature($signature, $post[$prefix.'Signature']); + + return array_merge($post, array_map('urldecode', $data)); + } + + private function checkTransactionError(array $data, $prefix) + { $error = isset($data[$prefix.'ErrorCode']) ? $data[$prefix.'ErrorCode'] : null; - if ($error) { - if ($message = Messages::getByCode($error)) { - throw new Exception(sprintf('TPV returned error code %s: %s', $error, $message['message'])); - } else { - throw new Exception(sprintf('TPV returned unknown error code %s', $error)); - } + if (empty($error)) { + return null; } + if ($message = Messages::getByCode($error)) { + throw new Exception(sprintf('TPV returned error code %s: %s', $error, $message['message'])); + } else { + throw new Exception(sprintf('TPV returned unknown error code %s', $error)); + } + } + + private function checkTransactionResponse(array $data, $prefix) + { $response = isset($data[$prefix.'Response']) ? $data[$prefix.'Response'] : null; if (is_null($response) || (strlen($response) === 0)) { @@ -314,19 +353,18 @@ public function checkTransaction(array $post) throw new Exception(sprintf('Response code is unknown %s', $response)); } } + } - $signature = Signature::fromTransaction($prefix, $data, $this->options['Key']); - - $postSignature = strtr($post[$prefix.'Signature'], '-_', '+/'); + private function checkTransactionSignature($signature, $postSignature) + { + $postSignature = strtr($postSignature, '-_', '+/'); if ($signature !== $postSignature) { throw new Exception(sprintf('Signature not valid (%s != %s)', $signature, $postSignature)); } - - return array_merge($post, array_map('urldecode', $data)); } - private function getTransactionParameters(array $post) + public function getTransactionParameters(array $post) { return json_decode(base64_decode(strtr($post['Ds_MerchantParameters'], '-_', '+/')), true); } diff --git a/src/autoload.php b/src/autoload.php index ad5b819..afc95ef 100644 --- a/src/autoload.php +++ b/src/autoload.php @@ -1,6 +1,5 @@