readParams(); $order = $this->getOrderId($this->session_id); } if (empty($order)) { $payu_response = $this->makeStatusRequest(); $trans = $this->readResponse($payu_response); $result = $this->readStatus($trans); logError(__FILE__, __LINE__, 'Not Found: result:'.print_r($result, true)); if ($result['code'] == 2 || $result['code'] == 1) { // If payment canceled or not paid yet, confirm echo 'OK'; exit; } if ($result['code'] == 99) { logError(__FILE__, __LINE__, 'Missed payment!! Creating ...'); $order = intval($trans->order_id); } logError(__FILE__, __LINE__, 'Not Found: payu_response:'.$payu_response); } return parent::setOrder($order); } public function storePaymentInfo() { $data = parent::storePaymentInfo(); $data['method'] = explode('-', getVal('payment_id'))[1]; return $data; } public function checkAuth() { $this->readParams(); if ($this->session_id && !getVal('cf')) { $parts = explode('_', $this->session_id); $_GET['cf'] = getVal(0, $parts); } } public function readParams() { $this->session_id = getVal('session_id'); $this->ts = time(); } public function readResponse($payu_response) { $xml = simplexml_load_string($payu_response); if (!$xml) { logError(__FILE__, __LINE__, "Chyba parsovani xml: {$payu_response}"); } if (strval($xml->status) != 'OK') { logError(__FILE__, __LINE__, 'Transakce vratila chybu'); } return $xml->trans; } public function readStatus($trans) { // incorrect POS ID number specified in response if ($trans->pos_id != $this->config['pos']) { return ['code' => false, 'message' => 'incorrect POS number']; } // calculating signature for comparison with sig sent by PayU $sig = md5($trans->pos_id.$trans->session_id.$trans->order_id.$trans->status.$trans->amount.$trans->desc.$trans->ts.$this->config['key2']); // incorrect signature in response in comparison to locally calculated one if ($trans->sig != $sig) { return ['code' => false, 'message' => 'incorrect signature']; } // different messages depending on transaction status. For status description, see documentation switch ($trans->status) { case 1: return ['code' => $trans->status, 'message' => 'new']; case 2: return ['code' => $trans->status, 'message' => 'cancelled']; case 3: return ['code' => $trans->status, 'message' => 'rejected']; case 4: return ['code' => $trans->status, 'message' => 'started']; case 5: return ['code' => $trans->status, 'message' => 'awaiting receipt']; case 6: return ['code' => $trans->status, 'message' => 'no authorization']; case 7: return ['code' => $trans->status, 'message' => 'payment rejected']; case 99: return ['code' => $trans->status, 'message' => 'payment received - ended']; case 888: return ['code' => $trans->status, 'message' => 'incorrect status']; default: return ['code' => false, 'message' => 'no status']; } } public function checkReceivedSignature() { // some parameters are missing if (!isset($_POST['pos_id']) || !isset($_POST['session_id']) || !isset($_POST['ts']) || !isset($_POST['sig'])) { logError(__FILE__, __LINE__, 'ERROR: EMPTY PARAMETERS'); } // received POS ID is different than expected if ($_POST['pos_id'] != $this->config['pos']) { logError(__FILE__, __LINE__, 'ERROR: INCORRECT POS ID'); } // verification of received signature $sig = md5($_POST['pos_id'].$_POST['session_id'].$_POST['ts'].$this->config['key2']); // incorrect signature if ($_POST['sig'] != $sig) { logError(__FILE__, __LINE__, 'ERROR: INCORRECT SIGNATURE'); } return true; } public function makeStatusRequest() { // signature that will be sent to PayU with request $sig = md5($this->config['pos'].$this->session_id.$this->ts.$this->config['key1']); // preparing parameters string to be sent to PayU $parameters = 'pos_id='.$this->config['pos'].'&session_id='.$this->session_id.'&ts='.$this->ts.'&sig='.$sig; // sending request via CURL $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->url.'UTF/Payment/get'); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 20); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $payu_response = curl_exec($ch); curl_close($ch); return $payu_response; } public function step($index, $message, $data = []) { if ($index == 1) { if (!empty($this->method)) { $data['method'] = $this->method; } else { $data['method'] = getVal('payu_method'); } } parent::step($index, $message, $data); } public function processStep_1() { $this->ts = strval(time()); $this->session_id = "{$this->order->getSecurityCode()}_{$this->ts}"; $this->method = getVal('method'); return true; } public function processStep_5() { $this->error('Platba byla zrušena.'); } public function processStep_6() { $this->updatePaymentStatus(); if ($this->status == Payment::STATUS_FINISHED) { $this->success('Platba proběhla v pořádku.'); } else { $this->step(2, 'Platba byla zaznamenána, čeká se na zaplacení. O přijetí platby Vám zašleme email.'); } } public function processStep_7() { $this->checkReceivedSignature(); if ($this->updatePaymentStatus()) { logError(__FILE__, __LINE__, 'PayU step 7 - OK'); echo 'OK'; } else { logError(__FILE__, __LINE__, 'PayU step 7 - KO'); echo 'KO'; } exit; } public function ensurePaymentExists($trans) { if (!$this->getStatus($this->session_id)) { $this->createPayment($this->session_id, intval($trans->amount) / 100, ['id_payu' => $trans->id]); } } public function updatePaymentStatus() { $payu_response = $this->makeStatusRequest(); logError(__FILE__, __LINE__, 'updatePaymentStatus: payu_response:'.$payu_response); $trans = $this->readResponse($payu_response); $result = $this->readStatus($trans); logError(__FILE__, __LINE__, 'updatePaymentStatus: response:'.print_r($result, true)); if ($result['code']) { if ($result['code'] == '2') { // transaction canceled return true; } $this->ensurePaymentExists($trans); // change of transaction status in system of the shop if ($result['code'] == '99') { // payment sucessful so we send back OK if (!$this->setStatus(Payment::STATUS_FINISHED, $this->session_id)) { logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!'); } return true; } elseif ($result['code'] == '4') { // transaction pending if (!$this->setStatus(Payment::STATUS_PENDING, $this->session_id)) { logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!'); } return true; } elseif ($result['code'] == '1') { // transaction started if (!$this->setStatus(Payment::STATUS_CREATED, $this->session_id)) { logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!'); } return true; } logError(__FILE__, __LINE__, 'updatePaymentStatus: not handled'); return false; } else { logError(__FILE__, __LINE__, 'updatePaymentStatus: error: code='.$result['code'].' message='.$result['message']."\n{$payu_response}"); } return false; } public function getName() { $methods = $this->getAvailableMethods(); return parent::getName().' - '.getVal($this->method, $methods, ['name' => 'Neznámý typ PayU platby'])['name']; } public function getAvailableMethods() { if (!($methods = getCache('payu-methods'))) { $methods = $this->fetchAvailableMethods(); setCache('payu-methods', $methods); } return $methods; } public function fetchAvailableMethods() { $key = substr($this->config['key1'], 0, 2); $xml = new SimpleXMLElement($this->url."UTF/xml/{$this->config['pos']}/{$key}/paytype.xml", 0, true); $payTypes = $xml->xpath('//paytype'); $methods = []; foreach ($payTypes as $payment) { $payment = (array) $payment; $payment['enable'] = $payment['enable'] == 'true'; $methods[$payment['type']] = $payment; } return $methods; } public function requiresEET() { return true; } public function hasOnlinePayment() { return true; } public static function isEnabled($className) { $cfg = Config::get(); if (empty($cfg['Modules']['payments'][$className])) { return false; } return true; } }