diff --git a/api/Config.php b/api/Config.php index 09cde6e..370d60f 100755 --- a/api/Config.php +++ b/api/Config.php @@ -3,7 +3,7 @@ /** * Simpla CMS * - * @copyright 2016 Denis Pikusov + * @copyright 2017 Denis Pikusov * @link http://simplacms.ru * @author Denis Pikusov * @@ -52,7 +52,7 @@ */ class Config { - public $version = '2.3.7'; + public $version = '2.3.8'; // Файл для хранения настроек public $config_file = 'config/config.php'; diff --git a/payment/Alfabank/Alfabank.php b/payment/Alfabank/Alfabank.php new file mode 100644 index 0000000..5145c15 --- /dev/null +++ b/payment/Alfabank/Alfabank.php @@ -0,0 +1,91 @@ +request->method('post') && $this->request->post('go')) + { + $this->redirect($order_id); + } + else + { + $button = "
". + "". + "
"; + return $button; + } + } + + public function redirect($order_id) + { + $order = $this->orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_settings = $this->payment->get_payment_settings($payment_method->id); + if(!empty($payment_settings['alfabank_server'])) + $this->getaway_url = $payment_settings['alfabank_server']; + $price = $this->money->convert($order->total_price, $payment_method->currency_id, false); + $return_url = $this->config->root_url.'/payment/Alfabank/callback.php?o='.$order->id; + + $data = array( + 'userName' => $payment_settings['alfabank_login'], + 'password' => $payment_settings['alfabank_password'], + 'orderNumber' => $order->id, + 'amount' => $price*100, + 'returnUrl' => $return_url + ); + + $response = $this->gateway('register.do', $data); + if ($response['errorCode'] != 0) + { + print($response['errorMessage']); + } + else + { + print "REDIRECT"; + header('Location: '.$response['formUrl']); + exit; + } + + return $button; + } + + + public function gateway($method, $data) + { + $curl = curl_init(); // Инициализируем запрос + curl_setopt_array($curl, array( + CURLOPT_URL => $this->getaway_url.$method, + CURLOPT_RETURNTRANSFER => true, // Возвращать ответ + CURLOPT_POST => true, // Метод POST + CURLOPT_POSTFIELDS => http_build_query($data) // Данные в запросе + )); + + $response = curl_exec($curl); // Выполненяем запрос + $response = json_decode($response, true); // Декодируем из JSON в массив + $err = curl_error($curl); + if($err) + { + print $err; + } + curl_close($curl); // Закрываем соединение + return $response; // Возвращаем ответ + } + +} diff --git a/payment/Alfabank/callback.php b/payment/Alfabank/callback.php new file mode 100644 index 0000000..c493da6 --- /dev/null +++ b/payment/Alfabank/callback.php @@ -0,0 +1,73 @@ +orders->get_order(intval($order_id)); +if(empty($order)) + errorlink('Оплачиваемый заказ не найден'); + +// Нельзя оплатить уже оплаченный заказ +if($order->paid) + errorlink('Этот заказ уже оплачен'); + +$method = $alfa->payment->get_payment_method(intval($order->payment_method_id)); +if(empty($method)) + errorlink("Неизвестный метод оплаты"); + +$settings = unserialize($method->settings); +if(!empty($settings['alfabank_server'])) + $alfa->getaway_url = $settings['alfabank_server']; + + +$data = array( + 'userName' => $settings['alfabank_login'], + 'password' => $settings['alfabank_password'], + 'orderId' => $external_order_id +); + +$response = $alfa->gateway('getOrderStatus.do', $data); + +if ($response['ErrorCode'] !== 0) +{ + errorlink($response['ErrorMessage']); +} + +if($response['Amount'] != 100*$alfa->money->convert($order->total_price, $method->currency_id, false) || $response['Amount']<=0) + errorlink("incorrect price\n"); + +if($response['OrderNumber'] != $order->id) + errorlink("incorrect order number\n"); + +// Установим статус оплачен +$alfa->orders->update_order(intval($order->id), array('paid'=>1)); + +// Спишем товары +$alfa->orders->close(intval($order->id)); +$alfa->notify->email_order_user(intval($order->id)); +$alfa->notify->email_order_admin(intval($order->id)); + +header("Location: ".$alfa->config->root_url.'/order/'.$order->url); + +function errorlink($message) +{ + print "$message
"; + print "Вернуться на страницу заказа"; + die(); +} diff --git a/payment/Alfabank/settings.xml b/payment/Alfabank/settings.xml new file mode 100644 index 0000000..08dc25c --- /dev/null +++ b/payment/Alfabank/settings.xml @@ -0,0 +1,19 @@ + + + + Альфа-Банк + + + alfabank_login + Логин от API + + + alfabank_password + Пароль от API + + + alfabank_server + Адрес сервера + https://test.paymentgate.ru/testpayment/rest/ + + diff --git a/payment/Assist/Assist.php b/payment/Assist/Assist.php new file mode 100644 index 0000000..2ae0eb5 --- /dev/null +++ b/payment/Assist/Assist.php @@ -0,0 +1,58 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_currency = $this->money->get_currency(intval($payment_method->currency_id)); + $settings = $this->payment->get_payment_settings($payment_method->id); + + $price = round($this->money->convert($order->total_price, $payment_method->currency_id, false), 2); + + // описание заказа + // order description + + $return_url = $this->config->root_url.'/order/'.$order->url; + + $hashcode = strtoupper(md5(strtoupper(md5( $settings['assist_key'] ).md5( $settings['assist_merchant_id'] . $order->id . $order->total_price . str_replace("RUR", "RUB", $payment_currency->code))))); + + + $fio_arr = explode(" ", $order->name); + $firstname = $fio_arr[0]; + $lastname = $fio_arr[1]; + + if (trim($firstname) == "") { + $firstname = "---"; + } + if (trim($lastname) == "") { + $lastname = "---"; + } + + + $button = '
'. + ''. + ''. + ''. + ''. + ''. + 'code).'" />'. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
'; + return $button; + } +} diff --git a/payment/Assist/callback.php b/payment/Assist/callback.php new file mode 100644 index 0000000..7bd92b3 --- /dev/null +++ b/payment/Assist/callback.php @@ -0,0 +1,71 @@ +orders->get_order(intval($order_id)); +if(empty($order)) + die('Оплачиваемый заказ не найден'); + +// Нельзя оплатить уже оплаченный заказ +if($order->paid) + die('Этот заказ уже оплачен'); + + +//////////////////////////////////////////////// +// Выбираем из базы соответствующий метод оплаты +//////////////////////////////////////////////// +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); +if(empty($method)) + die("Неизвестный метод оплаты"); + +$settings = unserialize($method->settings); + +// Проверяем контрольную подпись +$my_sign = strtoupper(md5(strtoupper(md5($settings['assist_key']).md5($data_return['merchant_id'].$data_return['ordernumber'].$data_return['orderamount'].$data_return['ordercurrency'].$data_return['orderstate'])))); +if($data_return['checkvalue'] !== $my_sign) + die("bad sign\n"); + +if($amount != $simpla->money->convert($order->total_price, $method->currency_id, false) || $amount<=0) + die("incorrect price\n"); + +//////////////////////////////////// +// Проверка наличия товара +//////////////////////////////////// +$purchases = $simpla->orders->get_purchases(array('order_id'=>intval($order->id))); +foreach($purchases as $purchase) +{ + $variant = $simpla->variants->get_variant(intval($purchase->variant_id)); + if(empty($variant) || (!$variant->infinity && $variant->stock < $purchase->amount)) + { + die("Нехватка товара $purchase->product_name $purchase->variant_name"); + } +} + +// Установим статус оплачен +$simpla->orders->update_order(intval($order->id), array('paid'=>1)); + +// Спишем товары +$simpla->orders->close(intval($order->id)); +$simpla->notify->email_order_user(intval($order->id)); +$simpla->notify->email_order_admin(intval($order->id)); + +die("OK".$order_id."\n"); diff --git a/payment/Assist/example.gif b/payment/Assist/example.gif new file mode 100644 index 0000000..baff87f Binary files /dev/null and b/payment/Assist/example.gif differ diff --git a/payment/Assist/settings.xml b/payment/Assist/settings.xml new file mode 100644 index 0000000..e3488c3 --- /dev/null +++ b/payment/Assist/settings.xml @@ -0,0 +1,18 @@ + + + + Assist + + + assist_key + Секретное слово + + + assist_merchant_id + ID магазина + + + assist_url + URL + + diff --git a/payment/ChronoPay/ChronoPay.php b/payment/ChronoPay/ChronoPay.php new file mode 100644 index 0000000..7f14dd7 --- /dev/null +++ b/payment/ChronoPay/ChronoPay.php @@ -0,0 +1,44 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $settings = $this->payment->get_payment_settings($payment_method->id); + + $price = number_format($this->money->convert($order->total_price, $payment_method->currency_id, false), 2, '.', ''); + + $success_url = $this->config->root_url.'/order/'.$order->url; + $fail_url = $this->config->root_url.'/order/'.$order->url; + $cb_url = $this->config->root_url.'/payment/ChronoPay/callback.php'; + $product_id = $settings['chronopay_product_id']; + + $sign = md5( + $product_id.'-'.$price.'-'.$order->id.'-'.$settings['chronopay_sharedSec'] + ); + + $payment_url = "https://payments.chronopay.com"; + + $button = '
+ + + + + + + + + + + +
'; + return $button; + } +} diff --git a/payment/ChronoPay/callback.php b/payment/ChronoPay/callback.php new file mode 100644 index 0000000..f3ccd8b --- /dev/null +++ b/payment/ChronoPay/callback.php @@ -0,0 +1,32 @@ +orders->get_order(intval($_POST['order_id'])); +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); + +$settings = unserialize($method->settings); + +$sign = md5( + trim($settings['chronopay_sharedSec']). + $_POST['customer_id']. + $_POST['transaction_id']. + $_POST['transaction_type']. + $_POST['total'] +); + +if($sign == $_POST['sign']) { + $simpla->orders->update_order(intval($order->id), array('paid'=>1)); + + $simpla->orders->close(intval($order->id)); + $simpla->notify->email_order_user(intval($order->id)); + $simpla->notify->email_order_admin(intval($order->id)); +} diff --git a/payment/ChronoPay/settings.xml b/payment/ChronoPay/settings.xml new file mode 100644 index 0000000..4f9941c --- /dev/null +++ b/payment/ChronoPay/settings.xml @@ -0,0 +1,12 @@ + + + ChronoPay + + chronopay_product_id + Ваш идентификатор продукта + + + chronopay_sharedSec + Cекретный ключ + + diff --git a/payment/MandarinBank/MandarinBank.php b/payment/MandarinBank/MandarinBank.php new file mode 100644 index 0000000..3d7cfc4 --- /dev/null +++ b/payment/MandarinBank/MandarinBank.php @@ -0,0 +1,59 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_settings = $this->payment->get_payment_settings($payment_method->id); + + $amount = $this->money->convert($order->total_price, $payment_method->currency_id, false); + + $success_url = $this->config->root_url.'/order/'.$order->url; + + $fail_url = $this->config->root_url.'/order/'.$order->url; + $fields = array(); + + $button = "
".$this->generate_form($payment_settings['secret'], $values = array( + "email" => $order->email, + "merchantId" => $payment_settings['merchantId'], + "orderId" => $order->id, + "price" => $amount, + ))."
"; + return $button; + } + + public function calc_sign($secret, $fields) + { + if(isset($fields['sign'])){ + unset($fields['sign']); + } + ksort($fields); + $secret_t = ''; + foreach($fields as $key => $val) + { + $secret_t = $secret_t . '-' . $val; + } + $secret_t = substr($secret_t, 1) . '-' . $secret; + return hash("sha256", $secret_t); + } + + public function generate_form($secret, $fields) + { + $sign = $this->calc_sign($secret, $fields); + $form = ""; + foreach($fields as $key => $val) + { + $form .= ''."\n"; + } + $form .= ''; + return $form; + } + + +} diff --git a/payment/MandarinBank/callback.php b/payment/MandarinBank/callback.php new file mode 100644 index 0000000..ec91310 --- /dev/null +++ b/payment/MandarinBank/callback.php @@ -0,0 +1,105 @@ +orders->get_order(intval($order_id)); +if(empty($order)) + die('Оплачиваемый заказ не найден'); + +// Нельзя оплатить уже оплаченный заказ +if($order->paid) + die('Этот заказ уже оплачен'); + +//////////////////////////////////////////////// +// Выбираем из базы соответствующий метод оплаты +//////////////////////////////////////////////// +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); +if(empty($method)) + die("Неизвестный метод оплаты"); + +$settings = unserialize($method->settings); + +if($status == 'failed') +{ + $simpla->orders->update_order($order_id, array('note'=>$order->note .' '. ' Оплата прошла с ошибкой (status faild)')); + die("Оплата прошла с ошибкой (status faild)"); +} + +// Сумма заказа у нас в магазине +$order_amount = $simpla->money->convert($order->total_price, $method->currency_id, false); + +// Должна быть равна переданной сумме +if($order_amount != $amount || $amount<=0) +{ + $simpla->orders->update_order($order_id, array('note'=>$order->note .' '. "Неверная сумма оплаты MandarinBank")); + die("Неверная сумма оплаты"); +} + +if($marchantId != $settings['merchantId']) +{ + $simpla->orders->update_order($order_id, array('note'=>$order->note .' '. "Неверный id мерчанта (возможно попытка подмены)")); + die("Неверный id мерчанта"); +} + +if(!check_sign($settings['secret'],$_POST)){ + $simpla->orders->update_order($order_id, array('note'=>$order->note .' '. "Неверный sign")); + die("Неверный sign"); +} + + +$note = $order->note . ' Статус'. $status .' Email: '. $customerEmail.' Телефон: '.$customerPhone .' Действие:'. $action; +// Установим статус оплачен +$simpla->orders->update_order(intval($order->id), array('paid'=>1,'note'=>$note)); + +// Спишем товары +$simpla->orders->close(intval($order->id)); + + +$simpla->notify->email_order_user(intval($order->id)); +$simpla->notify->email_order_admin(intval($order->id)); + +function check_sign($secret,$fields) +{ + $signAnswer = $fields['sign']; + $sign = calc_sign($secret,$fields); + return $sign == $signAnswer; +} + +function calc_sign($secret, $fields) +{ + if(isset($fields['sign'])){ + unset($fields['sign']); + } + ksort($fields); + $secret_t = ''; + foreach($fields as $key => $val) + { + $secret_t = $secret_t . '-' . $val; + } + $secret_t = substr($secret_t, 1) . '-' . $secret; + return hash("sha256", $secret_t); +} + +die("Yes"); diff --git a/payment/MandarinBank/readme-mandarin.txt b/payment/MandarinBank/readme-mandarin.txt new file mode 100644 index 0000000..cc071a9 --- /dev/null +++ b/payment/MandarinBank/readme-mandarin.txt @@ -0,0 +1,10 @@ +Simpla 2.3 + +1. ����� �� ������ �������� � payment/MandarinBank (����� ������� �� �������) +2. � ������� ��������� - ������ - �������� ������ ������, ������ �������� ������� ������, id � secret. +3. ��������� ��� �������� �������: +callbackURL - http://{�����}/payment/MandarinBank/callback.php +returnURL - http://{} + + + diff --git a/payment/MandarinBank/settings.xml b/payment/MandarinBank/settings.xml new file mode 100644 index 0000000..932fc80 --- /dev/null +++ b/payment/MandarinBank/settings.xml @@ -0,0 +1,14 @@ + + + + MandarinBank + + + merchantId + MerchantId + + + secret + Секретный ключ + + diff --git a/payment/NetPay/NetPay.php b/payment/NetPay/NetPay.php new file mode 100644 index 0000000..3d476c4 --- /dev/null +++ b/payment/NetPay/NetPay.php @@ -0,0 +1,94 @@ +orders->get_order(intval($order_id)); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_settings = $this->payment->get_payment_settings($payment_method->id); + $price = to_float($this->money->convert($order->total_price, $payment_method->currency_id, false)); + $currency = $this->money->get_currency($payment_method->currency_id); + $success_url = $this->config->root_url . '/order/' . $order->url; + $fail_url = $this->config->root_url . '/order/' . $order->url; + + if ($payment_settings['api_key'] == ''){ + $url_net2pay_pay = 'https://demo.net2pay.ru/billingService/paypage/'; + $Api_key='js4cucpn4kkc6jl1p95np054g2'; + $AuthSign='1'; + $submitval='Оплатить онлайн'; + } + else{ + $url_net2pay_pay = 'https://my.net2pay.ru/billingService/paypage/'; + $Api_key=$payment_settings['api_key']; + $AuthSign=$payment_settings['auth_sign']; + $submitval='Оплатить онлайн'; + } + + $md5_Api_key = base64_encode(md5($Api_key, true)); + $dateClass = new DateTime(); + $dateClass->modify('+999 day'); + $order_date = $dateClass->format('Y-m-dVH:i:s'); + $cryptoKey = substr(base64_encode(md5($md5_Api_key.$order_date, true)),0,16); + + $params = array(); + $params['description'] = 'ORDER '.$order->id; + $params['amount'] = $price; + $params['currency'] = 'RUB'; + $params['orderID'] = $order->id; + $params['cardHolderCity'] = ""; + $params['cardHolderCountry'] = ""; + $params['cardHolderPostal'] = ""; + $params['cardHolderRegion'] = ""; + $params['successUrl'] = $success_url; + $params['failUrl'] = $fail_url; + + $params_crypted = array(); + foreach ($params as $key=>$param){ + $cripter = Security::encrypt($key.'='.$param, $cryptoKey); + $params_crypted[] = $cripter; + } + + $params_crypted_str = implode('&', $params_crypted); + + $button = '
+ + + + + + +
'; + if ($payment_settings['mailforsend']!='') { + $netpay_url = 'http://'.$_SERVER['HTTP_HOST'].'/netpay/index.html'; + $link = $netpay_url."?data=".urlencode($params_crypted_str)."&auth=".$AuthSign."&expire=".urlencode($order_date); + $link = str_replace("%", "%25", $link); + if (mail ($payment_settings['mailforsend'], 'Ссылка для оплаты заказа #'.$order->id, $link, "From: ".$payment_settings['mailforsend']." \nContent-Type: text/html; charset=\"windows-1251\"\n")) $button=$payment_settings['textinsteadbutton']; + else $button=$payment_settings['sendmailerror']; + + } + return $button; + } + +} diff --git a/payment/NetPay/callback.php b/payment/NetPay/callback.php new file mode 100644 index 0000000..94bacf7 --- /dev/null +++ b/payment/NetPay/callback.php @@ -0,0 +1,95 @@ +db->placehold("SELECT * FROM __payment_methods WHERE module=? LIMIT 1", 'NetPay'); +$simpla->db->query($query); +$payment_method = $simpla->db->result(); +$pyment_settins = unserialize($payment_method->settings); +$api_key = $pyment_settins['api_key']; + + +$data = $_GET['data']; +$auth = $_GET['auth']; +$md5_Api_key = base64_encode(md5($api_key, true)); +$order_date = $_GET['expire']; + +function decrypt($sStr, $sKey) +{ + $decrypted = mcrypt_decrypt( + MCRYPT_RIJNDAEL_128, $sKey, base64_decode($sStr), MCRYPT_MODE_ECB + ); + $dec_s = strlen($decrypted); + $padding = ord($decrypted[$dec_s - 1]); + $decrypted = substr($decrypted, 0, -$padding); + return $decrypted; +} + +$md5_Api_key = base64_encode(md5($api_key, true)); + +$cryptoKey = substr(base64_encode(md5($md5_Api_key.$order_date, true)),0,16); + +$arr = explode('&', $data); +$i = '0'; + +foreach ($arr as $par) { + $newarr[$i] = explode('=', decrypt($par, $cryptoKey)); + $i++; +} + +if ($newarr[0][0] == 'orderID') { + $status = '1'; + $error = ''; +} else { + $data = urldecode($data); + $order_date = urldecode($order_date); + $cryptoKey = substr(base64_encode(md5($md5_Api_key . $order_date, true)), 0, 16); + $arr = explode('&', $data); + $i = '0'; + foreach ($arr as $par) { + $newarr[$i] = explode('=', decrypt($par, $cryptoKey)); + $i++; + } + if ($newarr[0][0] == 'orderID') { + $status = '1'; + $error = ''; + } else { + $status = '0'; + $error = 'error parsing data'; + } +} + +//Меняем статус заказа на оплачен +if (($newarr[2][1] == 'APPROVED') & + (($newarr[3][1] == 'Sale') || ($newarr[3][1] == 'Sale_Qiwi') || ($newarr[3][1] == 'Sale_YaMoney') || ($newarr[3][1] == 'Sale_WebMoney'))){ + + // Выберем заказ из базы + $order = $simpla->orders->get_order(intval($newarr[0][1])); + if(empty($order)) + $error = 'Оплачиваемый заказ не найден'; + + // Установим статус оплачен + $simpla->orders->update_order(intval($order->id), array('paid'=>1)); + + // Отправим уведомление на email + $simpla->notify->email_order_user(intval($order->id)); + $simpla->notify->email_order_admin(intval($order->id)); + + // Спишем товары + $simpla->orders->close(intval($order->id)); + + $status = '1'; + +} + +echo ' +' . $order->id . ' +' . $newarr[3][1] . ' +' . $status . ' +' . $error . ' +'; diff --git a/payment/NetPay/read_me.txt b/payment/NetPay/read_me.txt new file mode 100644 index 0000000..e2af909 --- /dev/null +++ b/payment/NetPay/read_me.txt @@ -0,0 +1,12 @@ +Для установки модуля платёжной системы Net Pay нужно: +Копировать папку NetPay в директорию /payment/ сайта (через FTP). +Зайти в админпанель по адресу http://вашсайт.ру/simpla/index.php +Выбрать пункт меню «настройки» +Выбрать вкладку «Оплата» +Нажать кнопку «Добавить способ оплаты» +Назовите метод оплаты. Например: Онлайн оплата банковскими картами (VISA/Visa Electron/MasterCard/Maestro) +Выберете способ оплаты "Net Pay" +Поставьте галочку АКТИВЕН +Укажите Api_key и Auth_signature (отправлены на email при регистрации), для работы в тестовом режиме данные поля нужно оставить пустыми +Обязательно включите все доступные способоы доставки. +Дайте описание данному методу оплаты, например: (Для проведения платежа вы будете переведены на страницу оплаты платёжной системы Net Pay) diff --git a/payment/NetPay/security.class.php b/payment/NetPay/security.class.php new file mode 100644 index 0000000..c2f266a --- /dev/null +++ b/payment/NetPay/security.class.php @@ -0,0 +1,34 @@ + diff --git a/payment/NetPay/settings.xml b/payment/NetPay/settings.xml new file mode 100644 index 0000000..f101ea8 --- /dev/null +++ b/payment/NetPay/settings.xml @@ -0,0 +1,26 @@ + + + + NetPay + + + api_key + Api_key + + + auth_sign + Auth_signature + + + mailforsend + Mail for send paylink + + + textinsteadbutton + Text instead button + + + sendmailerror + Text instead button (error send link) + + diff --git a/payment/PSBank/PSBank.php b/payment/PSBank/PSBank.php new file mode 100644 index 0000000..4b8bd3e --- /dev/null +++ b/payment/PSBank/PSBank.php @@ -0,0 +1,81 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $settings = $this->payment->get_payment_settings($payment_method->id); + $amount = $this->money->convert($order->total_price, $payment_method->currency_id, false); + $currency = $this->money->get_currency(intval($payment_method->currency_id)); + $return_url = $this->config->root_url.'/order/'.$order->url; + $desc = 'Оплата заказа №'.$order->id; + + // Московское время - 3 часа. + // Зачем это банку? Неизвестно, но без этого не работает + $date = new DateTime('now', new DateTimeZone('Europe/Moscow')); + $date->modify('-3Hours'); + $timestamp = $date->format('YmdHis'); + + $data = array( + 'AMOUNT' => $amount, + 'CURRENCY' => $currency->code, + // Номер заказа почему-то должен состоять минимум из 6 цифр + 'ORDER' => 1000000 + $order->id, + 'MERCH_NAME' => $this->settings->site_name, + 'MERCHANT' => $settings['psbank_merchant'], + 'TERMINAL' => $settings['psbank_terminal'], + 'EMAIL' => $order->email, + 'TRTYPE' => '1', + 'TIMESTAMP' => $timestamp, + // Случайное число неизвестного назначения + 'NONCE' => rand(10000000000000000, 99999999999999999999), + 'BACKREF' => $return_url + ); + + // Формируем строку, которую далее будем шифровать + $mac = ''; + foreach($data as $k=>$v) + { + $mac .= strlen($v).$v; + } + $sign = hash_hmac('sha1', $mac ,pack('H*', $settings['psbank_key'])); + + // Форма для отправки банку + if($settings['psbank_test_mode'] == 1) + $gate = $this->test_gate; + else + $gate = $this->real_gate; + $button = ""; + $button .= "
"; + foreach($data as $k=>$v) + { + $button .= ""; + } + $button .= " + + +
"; + return $button; + } + +} diff --git a/payment/PSBank/callback.php b/payment/PSBank/callback.php new file mode 100644 index 0000000..d1bbcb4 --- /dev/null +++ b/payment/PSBank/callback.php @@ -0,0 +1,91 @@ +orders->get_order(intval($order_id)); +if(empty($order)) + stop('Оплачиваемый заказ не найден'); + +// Нельзя оплатить уже оплаченный заказ +if($order->paid) + stop('Этот заказ уже оплачен'); + +//////////////////////////////////////////////// +// Выбираем из базы соответствующий метод оплаты +//////////////////////////////////////////////// +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); +if(empty($method)) + stop("Неизвестный метод оплаты"); + +$settings = unserialize($method->settings); + + +//////////////////////////////////////////////// +// Проверка контрольной подписи +//////////////////////////////////////////////// +$fields = array('AMOUNT', 'CURRENCY', 'ORDER', 'MERCH_NAME', 'MERCHANT', 'TERMINAL', 'EMAIL', 'TRTYPE', 'TIMESTAMP', 'NONCE', 'BACKREF', 'RESULT', 'RC', 'RCTEXT', 'AUTHCODE', 'RRN', 'INT_REF'); +$mac = ''; +foreach($fields as $f) +{ + if($_POST[$f] !== '') + $mac .= strlen($_POST[$f]).$_POST[$f]; + else + $mac .= '-'; +} +$sign = hash_hmac('sha1', $mac ,pack('H*', $settings['psbank_key'])); +if(strtoupper($sign) != $_POST['P_SIGN']) + stop('Контрольная подпись неверна'); + +//////////////////////////////////// +// Проверка суммы платежа +//////////////////////////////////// + +// Сумма заказа у нас в магазине +$order_amount = $simpla->money->convert($order->total_price, $method->currency_id, false); + +// Должна быть равна переданной сумме +if($order_amount != $_POST['AMOUNT'] || $_POST['AMOUNT']<=0) + stop("Неверная сумма оплаты"); + +// Проверка успешности операции +if($_POST['RESULT'] != 0) + stop("RESULT != 0, ".$_POST['RCTEXT']); + +// Установим статус оплачен +$simpla->orders->update_order(intval($order->id), array('paid'=>1)); + +// Спишем товары +$simpla->orders->close(intval($order->id)); + + +$simpla->notify->email_order_user(intval($order->id)); +$simpla->notify->email_order_admin(intval($order->id)); + + +stop("OK"); + +function stop($message) +{ + print($message); + die(); +} + diff --git a/payment/PSBank/settings.xml b/payment/PSBank/settings.xml new file mode 100644 index 0000000..e741d4f --- /dev/null +++ b/payment/PSBank/settings.xml @@ -0,0 +1,30 @@ + + + + Промсвязьбанк + + + psbank_terminal + Терминал + + + psbank_merchant + Номер торговой точки + + + psbank_key + Секретный ключ + + + psbank_test_mode + Режим + + Тестовые платежи + 1 + + + Реальные платежи + 0 + + + diff --git a/payment/Paysera/Paysera.php b/payment/Paysera/Paysera.php new file mode 100644 index 0000000..bea3ca3 --- /dev/null +++ b/payment/Paysera/Paysera.php @@ -0,0 +1,58 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_settings = $this->payment->get_payment_settings($payment_method->id); + + $amount = $this->money->convert($order->total_price, $payment_method->currency_id, false); + + $success_url = $this->config->root_url.'/order/'.$order->url; + $fail_url = $this->config->root_url.'/order/'.$order->url; + $callback_url = $this->config->root_url.'/payment/Paysera/callback.php?order_id='.$order->id; + + $currency = $this->money->get_currency(intval($payment_method->currency_id)); + + $request = WebToPay::buildRequest(array( + 'projectid' => $payment_settings['paysera_project_id'], + 'sign_password' => $payment_settings['paysera_password'], + 'test' => $payment_settings['paysera_test_mode'], + 'orderid' => $order->id, + 'p_email' => $order->email, + 'amount' => round($amount*100), + 'currency' => $currency->code, + 'paytext' => 'Payment for order #[order_nr] on [site_name]', + 'accepturl' => $success_url, + 'cancelurl' => $fail_url, + 'callbackurl' => $callback_url + )); + + $button = "
+ + + +
"; + return $button; + } + +} diff --git a/payment/Paysera/WebToPay.php b/payment/Paysera/WebToPay.php new file mode 100644 index 0000000..74df248 --- /dev/null +++ b/payment/Paysera/WebToPay.php @@ -0,0 +1,2357 @@ +. + * + * @package WebToPay + * @author EVP International + * @license http://www.gnu.org/licenses/lgpl.html + * @version 1.6 + * @link http://www.webtopay.com/ + */ + + +/** + * Contains static methods for most used scenarios. + */ +class WebToPay { + + /** + * WebToPay Library version. + */ + const VERSION = '1.6'; + + /** + * Server URL where all requests should go. + */ + const PAY_URL = 'https://www.paysera.com/pay/'; + + /** + * Server URL where all non-lithuanian language requests should go. + */ + const PAYSERA_PAY_URL = 'https://www.paysera.com/pay/'; + + /** + * Server URL where we can get XML with payment method data. + */ + const XML_URL = 'https://www.paysera.com/new/api/paymentMethods/'; + + /** + * SMS answer url. + */ + const SMS_ANSWER_URL = 'https://www.paysera.com/psms/respond/'; + + /** + * Builds request data array. + * + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * Possible keys: + * https://developers.paysera.com/en/payments/current#integration-via-specification + * + * @param array $data Information about current payment request + * + * @return array + * + * @throws WebToPayException on data validation error + */ + public static function buildRequest($data) { + if (!isset($data['sign_password']) || !isset($data['projectid'])) { + throw new WebToPayException('sign_password or projectid is not provided'); + } + $password = $data['sign_password']; + $projectId = $data['projectid']; + unset($data['sign_password']); + unset($data['projectid']); + + $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRequest($data); + } + + + /** + * Builds request and redirects user to payment window with generated request data + * + * Possible array keys are described here: + * https://developers.paysera.com/en/payments/current#integration-via-specification + * + * @param array $data Information about current payment request. + * @param boolean $exit if true, exits after sending Location header; default false + * + * @throws WebToPayException on data validation error + */ + public static function redirectToPayment($data, $exit = false) { + if (!isset($data['sign_password']) || !isset($data['projectid'])) { + throw new WebToPayException('sign_password or projectid is not provided'); + } + $password = $data['sign_password']; + $projectId = $data['projectid']; + unset($data['sign_password']); + unset($data['projectid']); + + $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $url = $factory->getRequestBuilder() + ->buildRequestUrlFromData($data); + + if (headers_sent()) { + echo ''; + } else { + header("Location: $url", true); + } + printf( + 'Redirecting to %s. Please wait.', + htmlentities($url, ENT_QUOTES, 'UTF-8'), + htmlentities($url, ENT_QUOTES, 'UTF-8') + ); + if ($exit) { + exit(); + } + } + + /** + * Builds repeat request data array. + * + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * Method accepts single parameter $data of array type. All possible array + * keys are described here: + * https://developers.paysera.com/en/payments/current#integration-via-specification + * + * @param array $data Information about current payment request + * + * @return array + * + * @throws WebToPayException on data validation error + */ + public static function buildRepeatRequest($data) { + if (!isset($data['sign_password']) || !isset($data['projectid']) || !isset($data['orderid'])) { + throw new WebToPayException('sign_password, projectid or orderid is not provided'); + } + $password = $data['sign_password']; + $projectId = $data['projectid']; + $orderId = $data['orderid']; + + $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRepeatRequest($orderId); + } + + /** + * Returns payment url. Argument is same as lang parameter in request data + * + * @param string $language + * @return string $url + */ + public static function getPaymentUrl($language = 'LIT') { + return (in_array($language, array('lt', 'lit', 'LIT'))) + ? self::PAY_URL + : self::PAYSERA_PAY_URL; + } + + /** + * Parses response from WebToPay server and validates signs. + * + * This function accepts both micro and macro responses. + * + * First parameter usualy should be $_GET array. + * + * Description about response can be found here: + * makro: https://developers.paysera.com/en/payments/current#integration-via-specification + * mikro: https://developers.paysera.com/en/sms-keywords/current#detailed-specification + * + * If response is not correct, WebToPayException will be raised. + * + * @param array $query Response array + * @param array $userData + * + * @return array + * + * @throws WebToPayException + * @deprecated use validateAndParseData() and check status code yourself + */ + public static function checkResponse($query, $userData = array()) { + $projectId = isset($userData['projectid']) ? $userData['projectid'] : null; + $password = isset($userData['sign_password']) ? $userData['sign_password'] : null; + $logFile = isset($userData['log']) ? $userData['log'] : null; + + try { + $data = self::validateAndParseData($query, $projectId, $password); + if ($data['type'] == 'macro' && $data['status'] != 1) { + throw new WebToPayException('Expected status code 1', WebToPayException::E_DEPRECATED_USAGE); + } + + if ($logFile) { + self::log('OK', http_build_query($data, null, '&'), $logFile); + } + return $data; + + } catch (WebToPayException $exception) { + if ($logFile && $exception->getCode() != WebToPayException::E_DEPRECATED_USAGE) { + self::log('ERR', $exception . "\nQuery: " . http_build_query($query, null, '&'), $logFile); + } + throw $exception; + } + } + + /** + * Parses request (query) data and validates its signature. + * + * @param array $query usually $_GET + * @param integer $projectId + * @param string $password + * + * @return array + * + * @throws WebToPayException + */ + public static function validateAndParseData(array $query, $projectId, $password) { + $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $validator = $factory->getCallbackValidator(); + $data = $validator->validateAndParseData($query); + return $data; + } + + /** + * Sends SMS answer + * + * @param array $userData + * + * @throws WebToPayException + * @throws WebToPay_Exception_Validation + */ + public static function smsAnswer($userData) { + if (!isset($userData['id']) || !isset($userData['msg']) || !isset($userData['sign_password'])) { + throw new WebToPay_Exception_Validation('id, msg and sign_password are required'); + } + + $smsId = $userData['id']; + $text = $userData['msg']; + $password = $userData['sign_password']; + $logFile = isset($userData['log']) ? $userData['log'] : null; + + try { + + $factory = new WebToPay_Factory(array('password' => $password)); + $factory->getSmsAnswerSender()->sendAnswer($smsId, $text); + + if ($logFile) { + self::log('OK', 'SMS ANSWER ' . $smsId . ' ' . $text, $logFile); + } + + } catch (WebToPayException $e) { + if ($logFile) { + self::log('ERR', 'SMS ANSWER ' . $e, $logFile); + } + throw $e; + } + + } + + + /** + * Gets available payment methods for project. Gets methods min and max amounts in specified currency. + * + * @param integer $projectId + * @param string $currency + * + * @return WebToPay_PaymentMethodList + * + * @throws WebToPayException + */ + public static function getPaymentMethodList($projectId, $currency = 'EUR') { + $factory = new WebToPay_Factory(array('projectId' => $projectId)); + return $factory->getPaymentMethodListProvider()->getPaymentMethodList($currency); + } + + /** + * Logs to file. Just skips logging if file is not writeable + * + * @param string $type + * @param string $msg + * @param string $logfile + */ + protected static function log($type, $msg, $logfile) { + $fp = @fopen($logfile, 'a'); + if (!$fp) { + return; + } + + $logline = array( + $type, + isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-', + date('[Y-m-d H:i:s O]'), + 'v' . self::VERSION . ':', + $msg + ); + + $logline = implode(' ', $logline)."\n"; + fwrite($fp, $logline); + fclose($fp); + + // clear big log file + if (filesize($logfile) > 1024 * 1024 * pi()) { + copy($logfile, $logfile.'.old'); + unlink($logfile); + } + } +} + + + + +/** + * Base exception class for all exceptions in this library + */ +class WebToPayException extends Exception { + + /** + * Missing field. + */ + const E_MISSING = 1; + + /** + * Invalid field value. + */ + const E_INVALID = 2; + + /** + * Max length exceeded. + */ + const E_MAXLEN = 3; + + /** + * Regexp for field value doesn't match. + */ + const E_REGEXP = 4; + + /** + * Missing or invalid user given parameters. + */ + const E_USER_PARAMS = 5; + + /** + * Logging errors + */ + const E_LOG = 6; + + /** + * SMS answer errors + */ + const E_SMS_ANSWER = 7; + + /** + * Macro answer errors + */ + const E_STATUS = 8; + + /** + * Library errors - if this happens, bug-report should be sent; also you can check for newer version + */ + const E_LIBRARY = 9; + + /** + * Errors in remote service - it returns some invalid data + */ + const E_SERVICE = 10; + + /** + * Deprecated usage errors + */ + const E_DEPRECATED_USAGE = 11; + + /** + * @var string|boolean + */ + protected $fieldName = false; + + /** + * Sets field which failed + * + * @param string $fieldName + */ + public function setField($fieldName) { + $this->fieldName = $fieldName; + } + + /** + * Gets field which failed + * + * @return string|boolean false + */ + public function getField() { + return $this->fieldName; + } +} + +/** + * Parses and validates callbacks + */ +class WebToPay_CallbackValidator { + + /** + * @var WebToPay_Sign_SignCheckerInterface + */ + protected $signer; + + /** + * @var WebToPay_Util + */ + protected $util; + + /** + * @var integer + */ + protected $projectId; + + /** + * Constructs object + * + * @param integer $projectId + * @param WebToPay_Sign_SignCheckerInterface $signer + * @param WebToPay_Util $util + */ + public function __construct($projectId, WebToPay_Sign_SignCheckerInterface $signer, WebToPay_Util $util) { + $this->signer = $signer; + $this->util = $util; + $this->projectId = $projectId; + } + + /** + * Parses callback parameters from query parameters and checks if sign is correct. + * Request has parameter "data", which is signed and holds all callback parameters + * + * @param array $requestData + * + * @return array Parsed callback parameters + * + * @throws WebToPayException + * @throws WebToPay_Exception_Callback + */ + public function validateAndParseData(array $requestData) { + if (!$this->signer->checkSign($requestData)) { + throw new WebToPay_Exception_Callback('Invalid sign parameters, check $_GET length limit'); + } + + if (!isset($requestData['data'])) { + throw new WebToPay_Exception_Callback('"data" parameter not found'); + } + $data = $requestData['data']; + + $queryString = $this->util->decodeSafeUrlBase64($data); + $request = $this->util->parseHttpQuery($queryString); + + if (!isset($request['projectid'])) { + throw new WebToPay_Exception_Callback( + 'Project ID not provided in callback', + WebToPayException::E_INVALID + ); + } + + if ((string) $request['projectid'] !== (string) $this->projectId) { + throw new WebToPay_Exception_Callback( + sprintf('Bad projectid: %s, should be: %s', $request['projectid'], $this->projectId), + WebToPayException::E_INVALID + ); + } + + if (!isset($request['type']) || !in_array($request['type'], array('micro', 'macro'))) { + $micro = ( + isset($request['to']) + && isset($request['from']) + && isset($request['sms']) + ); + $request['type'] = $micro ? 'micro' : 'macro'; + } + + return $request; + } + + /** + * Checks data to have all the same parameters provided in expected array + * + * @param array $data + * @param array $expected + * + * @throws WebToPayException + */ + public function checkExpectedFields(array $data, array $expected) { + foreach ($expected as $key => $value) { + $passedValue = isset($data[$key]) ? $data[$key] : null; + if ($passedValue != $value) { + throw new WebToPayException( + sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) + ); + } + } + } +} + +/** + * Wrapper class to group payment methods. Each country can have several payment method groups, each of them + * have one or more payment methods. + */ +class WebToPay_PaymentMethodGroup { + /** + * Some unique (in the scope of country) key for this group + * + * @var string + */ + protected $groupKey; + + /** + * Translations array for this group. Holds associative array of group title by country codes. + * + * @var array + */ + protected $translations; + + /** + * Holds actual payment methods + * + * @var WebToPay_PaymentMethod[] + */ + protected $paymentMethods; + + /** + * Default language for titles + * + * @var string + */ + protected $defaultLanguage; + + /** + * Constructs object + * + * @param string $groupKey + * @param array $translations + * @param string $defaultLanguage + */ + public function __construct($groupKey, array $translations = array(), $defaultLanguage = 'lt') { + $this->groupKey = $groupKey; + $this->translations = $translations; + $this->defaultLanguage = $defaultLanguage; + $this->paymentMethods = array(); + } + + /** + * Sets default language for titles. + * Returns itself for fluent interface + * + * @param string $language + * + * @return WebToPay_PaymentMethodGroup + */ + public function setDefaultLanguage($language) { + $this->defaultLanguage = $language; + foreach ($this->paymentMethods as $paymentMethod) { + $paymentMethod->setDefaultLanguage($language); + } + return $this; + } + + /** + * Gets default language for titles + * + * @return string + */ + public function getDefaultLanguage() { + return $this->defaultLanguage; + } + + /** + * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not + * specified, uses default language, given to constructor. + * + * @param string [Optional] $languageCode + * + * @return string + */ + public function getTitle($languageCode = null) { + if ($languageCode !== null && isset($this->translations[$languageCode])) { + return $this->translations[$languageCode]; + } elseif (isset($this->translations[$this->defaultLanguage])) { + return $this->translations[$this->defaultLanguage]; + } else { + return $this->groupKey; + } + } + + /** + * Returns group key + * + * @return string + */ + public function getKey() { + return $this->groupKey; + } + + /** + * Returns available payment methods for this group + * + * @return WebToPay_PaymentMethod[] + */ + public function getPaymentMethods() { + return $this->paymentMethods; + } + + + /** + * Adds new payment method for this group. + * If some other payment method with specified key was registered earlier, overwrites it. + * Returns given payment method + * + * @param WebToPay_PaymentMethod $paymentMethod + * + * @return WebToPay_PaymentMethod + */ + public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod) { + return $this->paymentMethods[$paymentMethod->getKey()] = $paymentMethod; + } + + /** + * Gets payment method object with key. If no payment method with such key is found, returns null. + * + * @param string $key + * + * @return null|WebToPay_PaymentMethod + */ + public function getPaymentMethod($key) { + return isset($this->paymentMethods[$key]) ? $this->paymentMethods[$key] : null; + } + + /** + * Returns new group instance with only those payment methods, which are available for provided amount. + * + * @param integer $amount + * @param string $currency + * + * @return WebToPay_PaymentMethodGroup + */ + public function filterForAmount($amount, $currency) { + $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); + foreach ($this->getPaymentMethods() as $paymentMethod) { + if ($paymentMethod->isAvailableForAmount($amount, $currency)) { + $group->addPaymentMethod($paymentMethod); + } + } + return $group; + } + + /** + * Returns new country instance with only those payment methods, which are returns or not iban number after payment + * + * @param boolean $isIban + * + * @return WebToPay_PaymentMethodGroup + */ + public function filterForIban($isIban = true) { + $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); + foreach ($this->getPaymentMethods() as $paymentMethod) { + if ($paymentMethod->isIban() == $isIban) { + $group->addPaymentMethod($paymentMethod); + } + } + return $group; + } + + /** + * Returns whether this group has no payment methods + * + * @return boolean + */ + public function isEmpty() { + return count($this->paymentMethods) === 0; + } + + /** + * Loads payment methods from given XML node + * + * @param SimpleXMLElement $groupNode + */ + public function fromXmlNode($groupNode) { + foreach ($groupNode->payment_type as $paymentTypeNode) { + $key = (string) $paymentTypeNode->attributes()->key; + $titleTranslations = array(); + foreach ($paymentTypeNode->title as $titleNode) { + $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + } + $logoTranslations = array(); + foreach ($paymentTypeNode->logo_url as $logoNode) { + if ((string) $logoNode !== '') { + $logoTranslations[(string) $logoNode->attributes()->language] = (string) $logoNode; + } + } + $minAmount = null; + $maxAmount = null; + $currency = null; + $isIban = false; + $baseCurrency = null; + if (isset($paymentTypeNode->min)) { + $minAmount = (int) $paymentTypeNode->min->attributes()->amount; + $currency = (string) $paymentTypeNode->min->attributes()->currency; + } + if (isset($paymentTypeNode->max)) { + $maxAmount = (int) $paymentTypeNode->max->attributes()->amount; + $currency = (string) $paymentTypeNode->max->attributes()->currency; + } + + if (isset($paymentTypeNode->is_iban)) { + $isIban = (int) $paymentTypeNode->is_iban; + } + if (isset($paymentTypeNode->base_currency)) { + $baseCurrency = (string) $paymentTypeNode->base_currency; + } + $this->addPaymentMethod($this->createPaymentMethod( + $key, $minAmount, $maxAmount, $currency, $logoTranslations, $titleTranslations, $isIban, $baseCurrency + )); + } + } + + /** + * Method to create new payment method instances. Overwrite if you have to use some other subclass. + * + * @param string $key + * @param integer $minAmount + * @param integer $maxAmount + * @param string $currency + * @param array $logoList + * @param array $titleTranslations + * @param bool $isIban + * @param null $baseCurrency + * + * @return WebToPay_PaymentMethod + */ + protected function createPaymentMethod( + $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), + $isIban = false, $baseCurrency = null + ) { + return new WebToPay_PaymentMethod( + $key, $minAmount, $maxAmount, $currency, $logoList, $titleTranslations, $this->defaultLanguage, + $isIban, $baseCurrency + ); + } +} + +/** + * Loads data about payment methods and constructs payment method list object from that data + * You need SimpleXML support to use this feature + */ +class WebToPay_PaymentMethodListProvider { + + /** + * @var integer + */ + protected $projectId; + + /** + * @var WebToPay_WebClient + */ + protected $webClient; + + /** + * Holds constructed method lists by currency + * + * @var WebToPay_PaymentMethodList[] + */ + protected $methodListCache = array(); + + /** + * Builds various request URLs + * + * @var WebToPay_UrlBuilder $urlBuilder + */ + protected $urlBuilder; + + /** + * Constructs object + * + * @param integer $projectId + * @param WebToPay_WebClient $webClient + * @param WebToPay_UrlBuilder $urlBuilder + * + * @throws WebToPayException if SimpleXML is not available + */ + public function __construct( + $projectId, + WebToPay_WebClient $webClient, + WebToPay_UrlBuilder $urlBuilder + ) + { + $this->projectId = $projectId; + $this->webClient = $webClient; + $this->urlBuilder = $urlBuilder; + + if (!function_exists('simplexml_load_string')) { + throw new WebToPayException('You have to install libxml to use payment methods API'); + } + } + + /** + * Gets payment method list for specified currency + * + * @param string $currency + * + * @return WebToPay_PaymentMethodList + * + * @throws WebToPayException + */ + public function getPaymentMethodList($currency) { + if (!isset($this->methodListCache[$currency])) { + $xmlAsString = $this->webClient->get($this->urlBuilder->buildForPaymentsMethodList($this->projectId, $currency)); + $useInternalErrors = libxml_use_internal_errors(false); + $rootNode = simplexml_load_string($xmlAsString); + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + if (!$rootNode) { + throw new WebToPayException('Unable to load XML from remote server'); + } + $methodList = new WebToPay_PaymentMethodList($this->projectId, $currency); + $methodList->fromXmlNode($rootNode); + $this->methodListCache[$currency] = $methodList; + } + return $this->methodListCache[$currency]; + } +} + +/** + * Raised on error in callback + */ +class WebToPay_Exception_Callback extends WebToPayException { + +} + +/** + * Raised if configuration is incorrect + */ +class WebToPay_Exception_Configuration extends WebToPayException { + +} + + +/** + * Raised on validation error in passed data when building the request + */ +class WebToPay_Exception_Validation extends WebToPayException { + + public function __construct($message, $code = 0, $field = null, Exception $previousException = null) { + parent::__construct($message, $code, $previousException); + if ($field) { + $this->setField($field); + } + } +} + +/** + * Class to hold information about payment method + */ +class WebToPay_PaymentMethod { + /** + * Assigned key for this payment method + * + * @var string + */ + protected $key; + + /** + * Logo url list by language. Usually logo is same for all languages, but exceptions exist + * + * @var array + */ + protected $logoList; + + /** + * Title list by language + * + * @var array + */ + protected $titleTranslations; + + /** + * Default language to use for titles + * + * @var string + */ + protected $defaultLanguage; + + /** + * @var boolean + */ + protected $isIban; + + /** + * @var string + */ + protected $baseCurrency; + + /** + * Constructs object + * + * @param string $key + * @param integer $minAmount + * @param integer $maxAmount + * @param string $currency + * @param array $logoList + * @param array $titleTranslations + * @param string $defaultLanguage + * @param bool $isIban + * @param string $baseCurrency + */ + public function __construct( + $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), + $defaultLanguage = 'lt', $isIban = false, $baseCurrency = null + ) { + $this->key = $key; + $this->minAmount = $minAmount; + $this->maxAmount = $maxAmount; + $this->currency = $currency; + $this->logoList = $logoList; + $this->titleTranslations = $titleTranslations; + $this->defaultLanguage = $defaultLanguage; + $this->isIban = $isIban; + $this->baseCurrency = $baseCurrency; + } + + /** + * Sets default language for titles. + * Returns itself for fluent interface + * + * @param string $language + * + * @return WebToPay_PaymentMethod + */ + public function setDefaultLanguage($language) { + $this->defaultLanguage = $language; + return $this; + } + + /** + * Gets default language for titles + * + * @return string + */ + public function getDefaultLanguage() { + return $this->defaultLanguage; + } + + /** + * Get assigned payment method key + * + * @return string + */ + public function getKey() { + return $this->key; + } + + /** + * Gets logo url for this payment method. Uses specified language or default one. + * If logotype is not found for specified language, null is returned. + * + * @param string [Optional] $languageCode + * + * @return string|null + */ + public function getLogoUrl($languageCode = null) { + if ($languageCode !== null && isset($this->logoList[$languageCode])) { + return $this->logoList[$languageCode]; + } elseif (isset($this->logoList[$this->defaultLanguage])) { + return $this->logoList[$this->defaultLanguage]; + } else { + return null; + } + } + + /** + * Gets title for this payment method. Uses specified language or default one. + * + * @param string [Optional] $languageCode + * + * @return string + */ + public function getTitle($languageCode = null) { + if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { + return $this->titleTranslations[$languageCode]; + } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { + return $this->titleTranslations[$this->defaultLanguage]; + } else { + return $this->key; + } + } + + /** + * Checks if this payment method can be used for specified amount. + * Throws exception if currency checked is not the one, for which payment method list was downloaded. + * + * @param integer $amount + * @param string $currency + * + * @return boolean + * + * @throws WebToPayException + */ + public function isAvailableForAmount($amount, $currency) { + if ($this->currency !== $currency) { + throw new WebToPayException( + 'Currencies does not match. You have to get payment types for the currency you are checking. Given currency: ' + . $currency . ', available currency: ' . $this->currency + ); + } + return ( + ($this->minAmount === null || $amount >= $this->minAmount) + && ($this->maxAmount === null || $amount <= $this->maxAmount) + ); + } + + /** + * Returns min amount for this payment method. If no min amount is specified, returns empty string. + * + * @return string + */ + public function getMinAmountAsString() { + return $this->minAmount === null ? '' : ($this->minAmount . ' ' . $this->currency); + } + + /** + * Returns max amount for this payment method. If no max amount is specified, returns empty string. + * + * @return string + */ + public function getMaxAmountAsString() { + return $this->maxAmount === null ? '' : ($this->maxAmount . ' ' . $this->currency); + } + + /** + * Set if this method returns IBAN number after payment + * + * @param boolean $isIban + */ + public function setIsIban($isIban) { + $this->isIban = $isIban == 1; + } + + /** + * Get if this method returns IBAN number after payment + * + * @return bool + */ + public function isIban() { + return $this->isIban; + } + + /** + * Setter of BaseCurrency + * + * @param string $baseCurrency + */ + public function setBaseCurrency($baseCurrency) + { + $this->baseCurrency = $baseCurrency; + } + + /** + * Getter of BaseCurrency + * + * @return string + */ + public function getBaseCurrency() + { + return $this->baseCurrency; + } +} + + +/** + * Utility class + */ +class WebToPay_Util { + + /** + * Decodes url-safe-base64 encoded string + * Url-safe-base64 is same as base64, but + is replaced to - and / to _ + * + * @param string $encodedText + * + * @return string + */ + public function decodeSafeUrlBase64($encodedText) { + return base64_decode(strtr($encodedText, array('-' => '+', '_' => '/'))); + } + + /** + * Encodes string to url-safe-base64 + * Url-safe-base64 is same as base64, but + is replaced to - and / to _ + * + * @param string $text + * + * @return string + */ + public function encodeSafeUrlBase64($text) { + return strtr(base64_encode($text), array('+' => '-', '/' => '_')); + } + + /** + * Parses HTTP query to array + * + * @param string $query + * + * @return array + */ + public function parseHttpQuery($query) { + $params = array(); + parse_str($query, $params); + if (get_magic_quotes_gpc()) { + $params = $this->stripSlashesRecursively($params); + } + return $params; + } + + /** + * Strips slashes recursively, so this method can be used on arrays with more than one level + * + * @param mixed $data + * + * @return mixed + */ + protected function stripSlashesRecursively($data) { + if (is_array($data)) { + $result = array(); + foreach ($data as $key => $value) { + $result[stripslashes($key)] = $this->stripSlashesRecursively($value); + } + return $result; + } else { + return stripslashes($data); + } + } +} + +/** + * Class with all information about available payment methods for some project, optionally filtered by some amount. + */ +class WebToPay_PaymentMethodList { + /** + * Holds available payment countries + * + * @var WebToPay_PaymentMethodCountry[] + */ + protected $countries; + + /** + * Default language for titles + * + * @var string + */ + protected $defaultLanguage; + + /** + * Project ID, to which this method list is valid + * + * @var integer + */ + protected $projectId; + + /** + * Currency for min and max amounts in this list + * + * @var string + */ + protected $currency; + + /** + * If this list is filtered for some amount, this field defines it + * + * @var integer + */ + protected $amount; + + /** + * Constructs object + * + * @param integer $projectId + * @param string $currency currency for min and max amounts in this list + * @param string $defaultLanguage + * @param integer $amount null if this list is not filtered by amount + */ + public function __construct($projectId, $currency, $defaultLanguage = 'lt', $amount = null) { + $this->projectId = $projectId; + $this->countries = array(); + $this->defaultLanguage = $defaultLanguage; + $this->currency = $currency; + $this->amount = $amount; + } + + /** + * Sets default language for titles. + * Returns itself for fluent interface + * + * @param string $language + * + * @return WebToPay_PaymentMethodList + */ + public function setDefaultLanguage($language) { + $this->defaultLanguage = $language; + foreach ($this->countries as $country) { + $country->setDefaultLanguage($language); + } + return $this; + } + + /** + * Gets default language for titles + * + * @return string + */ + public function getDefaultLanguage() { + return $this->defaultLanguage; + } + + /** + * Gets project ID for this payment method list + * + * @return integer + */ + public function getProjectId() { + return $this->projectId; + } + + /** + * Gets currency for min and max amounts in this list + * + * @return string + */ + public function getCurrency() { + return $this->currency; + } + + /** + * Gets whether this list is already filtered for some amount + * + * @return boolean + */ + public function isFiltered() { + return $this->amount !== null; + } + + /** + * Returns available countries + * + * @return WebToPay_PaymentMethodCountry[] + */ + public function getCountries() { + return $this->countries; + } + + /** + * Adds new country to payment methods. If some other country with same code was registered earlier, overwrites it. + * Returns added country instance + * + * @param WebToPay_PaymentMethodCountry $country + * + * @return WebToPay_PaymentMethodCountry + */ + public function addCountry(WebToPay_PaymentMethodCountry $country) { + return $this->countries[$country->getCode()] = $country; + } + + /** + * Gets country object with specified country code. If no country with such country code is found, returns null. + * + * @param string $countryCode + * + * @return null|WebToPay_PaymentMethodCountry + */ + public function getCountry($countryCode) { + return isset($this->countries[$countryCode]) ? $this->countries[$countryCode] : null; + } + + /** + * Returns new payment method list instance with only those payment methods, which are available for provided + * amount. + * Returns itself, if list is already filtered and filter amount matches the given one. + * + * @param integer $amount + * @param string $currency + * + * @return WebToPay_PaymentMethodList + * + * @throws WebToPayException if this list is already filtered and not for provided amount + */ + public function filterForAmount($amount, $currency) { + if ($currency !== $this->currency) { + throw new WebToPayException( + 'Currencies do not match. Given currency: ' . $currency . ', currency in list: ' . $this->currency + ); + } + if ($this->isFiltered()) { + if ($this->amount === $amount) { + return $this; + } else { + throw new WebToPayException('This list is already filtered, use unfiltered list instead'); + } + } else { + $list = new WebToPay_PaymentMethodList($this->projectId, $currency, $this->defaultLanguage, $amount); + foreach ($this->getCountries() as $country) { + $country = $country->filterForAmount($amount, $currency); + if (!$country->isEmpty()) { + $list->addCountry($country); + } + } + return $list; + } + } + + /** + * Loads countries from given XML node + * + * @param SimpleXMLElement $xmlNode + */ + public function fromXmlNode($xmlNode) { + foreach ($xmlNode->country as $countryNode) { + $titleTranslations = array(); + foreach ($countryNode->title as $titleNode) { + $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + } + $this->addCountry($this->createCountry((string) $countryNode->attributes()->code, $titleTranslations)) + ->fromXmlNode($countryNode); + } + } + + /** + * Method to create new country instances. Overwrite if you have to use some other country subtype. + * + * @param string $countryCode + * @param array $titleTranslations + * + * @return WebToPay_PaymentMethodCountry + */ + protected function createCountry($countryCode, array $titleTranslations = array()) { + return new WebToPay_PaymentMethodCountry($countryCode, $titleTranslations, $this->defaultLanguage); + } +} + +/** + * Sends answer to SMS payment if it was not provided with response to callback + */ +class WebToPay_SmsAnswerSender { + + /** + * @var string + */ + protected $password; + + /** + * @var WebToPay_WebClient + */ + protected $webClient; + + /** + * @var WebToPay_UrlBuilder $urlBuilder + */ + protected $urlBuilder; + + /** + * Constructs object + * + * @param string $password + * @param WebToPay_WebClient $webClient + * @param WebToPay_UrlBuilder $urlBuilder + */ + public function __construct( + $password, + WebToPay_WebClient $webClient, + WebToPay_UrlBuilder $urlBuilder + ) { + $this->password = $password; + $this->webClient = $webClient; + $this->urlBuilder = $urlBuilder; + } + + /** + * Sends answer by sms ID get from callback. Answer can be send only if it was not provided + * when responding to callback + * + * @param integer $smsId + * @param string $text + * + * @throws WebToPayException + */ + public function sendAnswer($smsId, $text) { + $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), array( + 'id' => $smsId, + 'msg' => $text, + 'transaction' => md5($this->password . '|' . $smsId), + )); + if (strpos($content, 'OK') !== 0) { + throw new WebToPayException( + sprintf('Error: %s', $content), + WebToPayException::E_SMS_ANSWER + ); + } + } +} + + +/** + * Creates objects. Also caches to avoid creating several instances of same objects + */ +class WebToPay_Factory { + + const ENV_PRODUCTION = 'production'; + const ENV_SANDBOX = 'sandbox'; + + /** + * @var array + */ + protected static $defaultConfiguration = array( + 'routes' => array( + self::ENV_PRODUCTION => array( + 'publicKey' => 'http://www.paysera.com/download/public.key', + 'payment' => 'https://www.paysera.com/pay/', + 'paymentMethodList' => 'https://www.paysera.com/new/api/paymentMethods/', + 'smsAnswer' => 'https://www.paysera.com/psms/respond/', + ), + self::ENV_SANDBOX => array( + 'publicKey' => 'http://sandbox.paysera.com/download/public.key', + 'payment' => 'https://sandbox.paysera.com/pay/', + 'paymentMethodList' => 'https://sandbox.paysera.com/new/api/paymentMethods/', + 'smsAnswer' => 'https://sandbox.paysera.com/psms/respond/', + ), + ) + ); + + /** + * @var string + */ + protected $environment; + + /** + * @var array + */ + protected $configuration; + + /** + * @var WebToPay_WebClient + */ + protected $webClient = null; + + /** + * @var WebToPay_CallbackValidator + */ + protected $callbackValidator = null; + + /** + * @var WebToPay_RequestBuilder + */ + protected $requestBuilder = null; + + /** + * @var WebToPay_Sign_SignCheckerInterface + */ + protected $signer = null; + + /** + * @var WebToPay_SmsAnswerSender + */ + protected $smsAnswerSender = null; + + /** + * @var WebToPay_PaymentMethodListProvider + */ + protected $paymentMethodListProvider = null; + + /** + * @var WebToPay_Util + */ + protected $util = null; + + /** + * @var WebToPay_UrlBuilder + */ + protected $urlBuilder = null; + + + /** + * Constructs object. + * Configuration keys: projectId, password + * They are required only when some object being created needs them, + * if they are not found at that moment - exception is thrown + * + * @param array $configuration + */ + public function __construct(array $configuration = array()) { + + $this->configuration = array_merge(self::$defaultConfiguration, $configuration); + $this->environment = self::ENV_PRODUCTION; + } + + /** + * If passed true the factory will use sandbox when constructing URLs + * + * @param $enableSandbox + * @return self + */ + public function useSandbox($enableSandbox) + { + if ($enableSandbox) { + $this->environment = self::ENV_SANDBOX; + } else { + $this->environment = self::ENV_PRODUCTION; + } + return $this; + } + + /** + * Creates or gets callback validator instance + * + * @return WebToPay_CallbackValidator + * + * @throws WebToPay_Exception_Configuration + */ + public function getCallbackValidator() { + if ($this->callbackValidator === null) { + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->callbackValidator = new WebToPay_CallbackValidator( + $this->configuration['projectId'], + $this->getSigner(), + $this->getUtil() + ); + } + return $this->callbackValidator; + } + + /** + * Creates or gets request builder instance + * + * @throws WebToPay_Exception_Configuration + * + * @return WebToPay_RequestBuilder + */ + public function getRequestBuilder() { + if ($this->requestBuilder === null) { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration('You have to provide project password to sign request'); + } + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->requestBuilder = new WebToPay_RequestBuilder( + $this->configuration['projectId'], + $this->configuration['password'], + $this->getUtil(), + $this->getUrlBuilder() + ); + } + return $this->requestBuilder; + } + + /** + * @return WebToPay_UrlBuilder + */ + public function getUrlBuilder() { + if ($this->urlBuilder === null) { + $this->urlBuilder = new WebToPay_UrlBuilder( + $this->configuration, + $this->environment + ); + } + return $this->urlBuilder; + } + + /** + * Creates or gets SMS answer sender instance + * + * @throws WebToPay_Exception_Configuration + * + * @return WebToPay_SmsAnswerSender + */ + public function getSmsAnswerSender() { + if ($this->smsAnswerSender === null) { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration('You have to provide project password'); + } + $this->smsAnswerSender = new WebToPay_SmsAnswerSender( + $this->configuration['password'], + $this->getWebClient(), + $this->getUrlBuilder() + ); + } + return $this->smsAnswerSender; + } + + /** + * Creates or gets payment list provider instance + * + * @throws WebToPay_Exception_Configuration + * + * @return WebToPay_PaymentMethodListProvider + */ + public function getPaymentMethodListProvider() { + if ($this->paymentMethodListProvider === null) { + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->paymentMethodListProvider = new WebToPay_PaymentMethodListProvider( + $this->configuration['projectId'], + $this->getWebClient(), + $this->getUrlBuilder() + + ); + } + return $this->paymentMethodListProvider; + } + + /** + * Creates or gets signer instance. Chooses SS2 signer if openssl functions are available, SS1 in other case + * + * @throws WebToPay_Exception_Configuration + * + * @return WebToPay_Sign_SignCheckerInterface + * + * @throws WebToPayException + */ + protected function getSigner() { + if ($this->signer === null) { + if (function_exists('openssl_pkey_get_public')) { + $webClient = $this->getWebClient(); + $publicKey = $webClient->get($this->getUrlBuilder()->buildForPublicKey()); + if (!$publicKey) { + throw new WebToPayException('Cannot download public key from WebToPay website'); + } + $this->signer = new WebToPay_Sign_SS2SignChecker($publicKey, $this->getUtil()); + } else { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration( + 'You have to provide project password if OpenSSL is unavailable' + ); + } + $this->signer = new WebToPay_Sign_SS1SignChecker($this->configuration['password']); + } + } + return $this->signer; + } + + /** + * Creates or gets web client instance + * + * @throws WebToPay_Exception_Configuration + * + * @return WebToPay_WebClient + */ + protected function getWebClient() { + if ($this->webClient === null) { + $this->webClient = new WebToPay_WebClient(); + } + return $this->webClient; + } + + /** + * Creates or gets util instance + * + * @throws WebToPay_Exception_Configuration + * + * @return WebToPay_Util + */ + protected function getUtil() { + if ($this->util === null) { + $this->util = new WebToPay_Util(); + } + return $this->util; + } +} + + +/** + * Sign checker which checks SS1 signature. SS1 does not depend on SSL functions + */ +class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface { + + /** + * @var string + */ + protected $projectPassword; + + /** + * Constructs object + * + * @param string $projectPassword + */ + public function __construct($projectPassword) { + $this->projectPassword = $projectPassword; + } + + /** + * Check for SS1, which is not depend on openssl functions. + * + * @param array $request + * + * @return boolean + * + * @throws WebToPay_Exception_Callback + */ + public function checkSign(array $request) { + if (!isset($request['data']) || !isset($request['ss1'])) { + throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + } + + return md5($request['data'] . $this->projectPassword) === $request['ss1']; + } +} + +/** + * Checks SS2 signature. Depends on SSL functions + */ +class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface { + + /** + * @var string + */ + protected $publicKey; + + /** + * @var WebToPay_Util + */ + protected $util; + + /** + * Constructs object + * + * @param string $publicKey + * @param WebToPay_Util $util + */ + public function __construct($publicKey, WebToPay_Util $util) { + $this->publicKey = $publicKey; + $this->util = $util; + } + + /** + * Checks signature + * + * @param array $request + * + * @return boolean + * + * @throws WebToPay_Exception_Callback + */ + public function checkSign(array $request) { + if (!isset($request['data']) || !isset($request['ss2'])) { + throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + } + + $ss2 = $this->util->decodeSafeUrlBase64($request['ss2']); + $ok = openssl_verify($request['data'], $ss2, $this->publicKey); + return $ok === 1; + } +} + +/** + * Interface for sign checker + */ +interface WebToPay_Sign_SignCheckerInterface { + + /** + * Checks whether request is signed properly + * + * @param array $request + * + * @return boolean + */ + public function checkSign(array $request); +} + +/** + * Simple web client + */ +class WebToPay_WebClient { + + /** + * Gets page contents by specified URI. Adds query data if provided to the URI + * Ignores status code of the response and header fields + * + * @param string $uri + * @param array $queryData + * + * @return string + * + * @throws WebToPayException + */ + public function get($uri, array $queryData = array()) { + if (count($queryData) > 0) { + $uri .= strpos($uri, '?') === false ? '?' : '&'; + $uri .= http_build_query($queryData, null, '&'); + } + $url = parse_url($uri); + if ('https' == $url['scheme']) { + $host = 'ssl://'.$url['host']; + $port = 443; + } else { + $host = $url['host']; + $port = 80; + } + + $fp = fsockopen($host, $port, $errno, $errstr, 30); + if (!$fp) { + throw new WebToPayException(sprintf('Cannot connect to %s', $uri), WebToPayException::E_INVALID); + } + + if(isset($url['query'])) { + $data = $url['path'].'?'.$url['query']; + } else { + $data = $url['path']; + } + + $out = "GET " . $data . " HTTP/1.0\r\n"; + $out .= "Host: ".$url['host']."\r\n"; + $out .= "Connection: Close\r\n\r\n"; + + $content = ''; + + fwrite($fp, $out); + while (!feof($fp)) $content .= fgets($fp, 8192); + fclose($fp); + + list($header, $content) = explode("\r\n\r\n", $content, 2); + + return trim($content); + } +} + +/** + * Payment method configuration for some country + */ +class WebToPay_PaymentMethodCountry { + /** + * @var string + */ + protected $countryCode; + + /** + * Holds available payment types for this country + * + * @var WebToPay_PaymentMethodGroup[] + */ + protected $groups; + + /** + * Default language for titles + * + * @var string + */ + protected $defaultLanguage; + + /** + * Translations array for this country. Holds associative array of country title by language codes. + * + * @var array + */ + protected $titleTranslations; + + /** + * Constructs object + * + * @param string $countryCode + * @param array $titleTranslations + * @param string $defaultLanguage + */ + public function __construct($countryCode, $titleTranslations, $defaultLanguage = 'lt') { + $this->countryCode = $countryCode; + $this->defaultLanguage = $defaultLanguage; + $this->titleTranslations = $titleTranslations; + $this->groups = array(); + } + + /** + * Sets default language for titles. + * Returns itself for fluent interface + * + * @param string $language + * + * @return WebToPay_PaymentMethodCountry + */ + public function setDefaultLanguage($language) { + $this->defaultLanguage = $language; + foreach ($this->groups as $group) { + $group->setDefaultLanguage($language); + } + return $this; + } + + /** + * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not + * specified, uses default language, given to constructor. + * + * @param string [Optional] $languageCode + * + * @return string + */ + public function getTitle($languageCode = null) { + if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { + return $this->titleTranslations[$languageCode]; + } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { + return $this->titleTranslations[$this->defaultLanguage]; + } else { + return $this->countryCode; + } + } + + /** + * Gets default language for titles + * + * @return string + */ + public function getDefaultLanguage() { + return $this->defaultLanguage; + } + + /** + * Gets country code + * + * @return string + */ + public function getCode() { + return $this->countryCode; + } + + /** + * Adds new group to payment methods for this country. + * If some other group was registered earlier with same key, overwrites it. + * Returns given group + * + * @param WebToPay_PaymentMethodGroup $group + * + * @return WebToPay_PaymentMethodGroup + */ + public function addGroup(WebToPay_PaymentMethodGroup $group) { + return $this->groups[$group->getKey()] = $group; + } + + /** + * Gets group object with specified group key. If no group with such key is found, returns null. + * + * @param string $groupKey + * + * @return null|WebToPay_PaymentMethodGroup + */ + public function getGroup($groupKey) { + return isset($this->groups[$groupKey]) ? $this->groups[$groupKey] : null; + } + + /** + * Returns payment method groups registered for this country. + * + * @return WebToPay_PaymentMethodGroup[] + */ + public function getGroups() { + return $this->groups; + } + + /** + * Gets payment methods in all groups + * + * @return WebToPay_PaymentMethod[] + */ + public function getPaymentMethods() { + $paymentMethods = array(); + foreach ($this->groups as $group) { + $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); + } + return $paymentMethods; + } + + /** + * Returns new country instance with only those payment methods, which are available for provided amount. + * + * @param integer $amount + * @param string $currency + * + * @return WebToPay_PaymentMethodCountry + */ + public function filterForAmount($amount, $currency) { + $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); + foreach ($this->getGroups() as $group) { + $group = $group->filterForAmount($amount, $currency); + if (!$group->isEmpty()) { + $country->addGroup($group); + } + } + return $country; + } + + /** + * Returns new country instance with only those payment methods, which are returns or not iban number after payment + * + * @param boolean $isIban + * + * @return WebToPay_PaymentMethodCountry + */ + public function filterForIban($isIban = true) { + $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); + foreach ($this->getGroups() as $group) { + $group = $group->filterForIban($isIban); + if (!$group->isEmpty()) { + $country->addGroup($group); + } + } + return $country; + } + + /** + * Returns whether this country has no groups + * + * @return boolean + */ + public function isEmpty() { + return count($this->groups) === 0; + } + + /** + * Loads groups from given XML node + * + * @param SimpleXMLElement $countryNode + */ + public function fromXmlNode($countryNode) { + foreach ($countryNode->payment_group as $groupNode) { + $key = (string) $groupNode->attributes()->key; + $titleTranslations = array(); + foreach ($groupNode->title as $titleNode) { + $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + } + $this->addGroup($this->createGroup($key, $titleTranslations))->fromXmlNode($groupNode); + } + } + + /** + * Method to create new group instances. Overwrite if you have to use some other group subtype. + * + * @param string $groupKey + * @param array $translations + * + * @return WebToPay_PaymentMethodGroup + */ + protected function createGroup($groupKey, array $translations = array()) { + return new WebToPay_PaymentMethodGroup($groupKey, $translations, $this->defaultLanguage); + } +} + + +/** + * Used to build a complete request URL. + * + * Class WebToPay_UrlBuilder + */ +class WebToPay_UrlBuilder { + + const PLACEHOLDER_KEY = '[domain]'; + + /** + * @var array + */ + protected $configuration = array(); + + /** + * @var string + */ + protected $environment; + + /** + * @var array + */ + protected $environmentSettings; + + /** + * @param array $configuration + * @param string $environment + */ + function __construct($configuration, $environment) + { + $this->configuration = $configuration; + $this->environment = $environment; + $this->environmentSettings = $this->configuration['routes'][$this->environment]; + } + + /** + * Builds a complete request URL based on the provided parameters + * + * @param $request + * @param null $language + * @return string + */ + public function buildForRequest($request, $language = null) { + return $this->createUrlFromRequestAndLanguage($request); + } + + /** + * Builds a complete URL for payment list API + * + * @param int $projectId + * @param string $currency + * @return string + */ + public function buildForPaymentsMethodList($projectId, $currency) { + $route = $this->environmentSettings['paymentMethodList']; + return $route . $projectId . '/currency:' . $currency; + } + + /** + * Builds a complete URL for Sms Answer + * + * @return string + */ + public function buildForSmsAnswer() { + $route = $this->environmentSettings['smsAnswer']; + return $route; + } + + /** + * Build the url to the public key + * + * @return string + */ + public function buildForPublicKey() { + $route = $this->environmentSettings['publicKey']; + return $route; + } + + /** + * Creates an URL from the request and data provided. + * + * @param array $request + * @return string + */ + protected function createUrlFromRequestAndLanguage($request) { + $url = $this->getPaymentUrl() . '?' . http_build_query($request, null, '&'); + return preg_replace('/[\r\n]+/is', '', $url); + } + + /** + * Returns payment url. Argument is same as lang parameter in request data + * + * @return string $url + */ + protected function getPaymentUrl() { + $route = $this->environmentSettings['payment']; + return $route; + } +} + + +/** + * Builds and signs requests + */ +class WebToPay_RequestBuilder { + + /** + * @var string + */ + protected $projectPassword; + + /** + * @var WebToPay_Util + */ + protected $util; + + /** + * @var integer + */ + protected $projectId; + + + /** + * @var WebToPay_UrlBuilder $urlBuilder + */ + protected $urlBuilder; + + /** + * Constructs object + * + * @param integer $projectId + * @param string $projectPassword + * @param WebToPay_Util $util + * @param WebToPay_UrlBuilder $urlBuilder + */ + public function __construct( + $projectId, + $projectPassword, + WebToPay_Util $util, + WebToPay_UrlBuilder $urlBuilder + ) + { + $this->projectId = $projectId; + $this->projectPassword = $projectPassword; + $this->util = $util; + $this->urlBuilder = $urlBuilder; + } + + /** + * Builds request data array. + * + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * @param array $data information about current payment request + * + * @return array + * + * @throws WebToPayException + */ + public function buildRequest($data) { + $this->validateRequest($data, self::getRequestSpec()); + $data['version'] = WebToPay::VERSION; + $data['projectid'] = $this->projectId; + unset($data['repeat_request']); + return $this->createRequest($data); + } + + /** + * Builds the full request url (including the protocol and the domain) + * + * @param array $data + * @return string + */ + public function buildRequestUrlFromData($data) { + $language = isset($data['lang']) ? $data['lang'] : null; + $request = $this->buildRequest($data); + return $this->urlBuilder->buildForRequest($request, $language); + } + + /** + * Builds repeat request data array. + * + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * @param string $orderId order id of repeated request + * + * @return array + * + * @throws WebToPayException + */ + public function buildRepeatRequest($orderId) { + $data['orderid'] = $orderId; + $data['version'] = WebToPay::VERSION; + $data['projectid'] = $this->projectId; + $data['repeat_request'] = '1'; + return $this->createRequest($data); + } + + /** + * Builds the full request url for a repeated request (including the protocol and the domain) + * + * @param string $orderId order id of repeated request + * @return string + */ + public function buildRepeatRequestUrlFromOrderId($orderId) { + $request = $this->buildRepeatRequest($orderId); + return $this->urlBuilder->buildForRequest($request); + } + + /** + * Checks data to be valid by passed specification + * + * @param array $data + * @param array $specs + * + * @throws WebToPay_Exception_Validation + */ + protected function validateRequest($data, $specs) { + foreach ($specs as $spec) { + list($name, $maxlen, $required, $regexp) = $spec; + if ($required && !isset($data[$name])) { + throw new WebToPay_Exception_Validation( + sprintf("'%s' is required but missing.", $name), + WebToPayException::E_MISSING, + $name + ); + } + + if (!empty($data[$name])) { + if ($maxlen && strlen($data[$name]) > $maxlen) { + throw new WebToPay_Exception_Validation(sprintf( + "'%s' value is too long (%d), %d characters allowed.", + $name, + strlen($data[$name]), + $maxlen + ), WebToPayException::E_MAXLEN, $name); + } + + if ($regexp !== '' && !preg_match($regexp, $data[$name])) { + throw new WebToPay_Exception_Validation( + sprintf("'%s' value '%s' is invalid.", $name, $data[$name]), + WebToPayException::E_REGEXP, + $name + ); + } + } + } + } + + /** + * Makes request data array from parameters, also generates signature + * + * @param array $request + * + * @return array + */ + protected function createRequest(array $request) { + $data = $this->util->encodeSafeUrlBase64(http_build_query($request, null, '&')); + return array( + 'data' => $data, + 'sign' => md5($data . $this->projectPassword), + ); + } + + /** + * Returns specification of fields for request. + * + * Array structure: + * name – request item name + * maxlen – max allowed value for item + * required – is this item is required + * regexp – regexp to test item value + * + * @return array + */ + protected static function getRequestSpec() { + return array( + array('orderid', 40, true, ''), + array('accepturl', 255, true, ''), + array('cancelurl', 255, true, ''), + array('callbackurl', 255, true, ''), + array('lang', 3, false, '/^[a-z]{3}$/i'), + array('amount', 11, false, '/^\d+$/'), + array('currency', 3, false, '/^[a-z]{3}$/i'), + array('payment', 20, false, ''), + array('country', 2, false, '/^[a-z_]{2}$/i'), + array('paytext', 255, false, ''), + array('p_firstname', 255, false, ''), + array('p_lastname', 255, false, ''), + array('p_email', 255, false, ''), + array('p_street', 255, false, ''), + array('p_city', 255, false, ''), + array('p_state', 20, false, ''), + array('p_zip', 20, false, ''), + array('p_countrycode', 2, false, '/^[a-z]{2}$/i'), + array('test', 1, false, '/^[01]$/'), + array('time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'), + ); + } +} diff --git a/payment/Paysera/callback.php b/payment/Paysera/callback.php new file mode 100644 index 0000000..701a9fd --- /dev/null +++ b/payment/Paysera/callback.php @@ -0,0 +1,83 @@ +orders->get_order(intval($order_id)); +if(empty($order)) + die('Оплачиваемый заказ не найден'); + +// Нельзя оплатить уже оплаченный заказ +if($order->paid) + die('Этот заказ уже оплачен'); + +//////////////////////////////////////////////// +// Выбираем из базы соответствующий метод оплаты +//////////////////////////////////////////////// +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); +if(empty($method)) + die("Неизвестный метод оплаты"); + +$settings = unserialize($method->settings); + + +$response = WebToPay::checkResponse($_GET, array( + 'projectid' => $settings['paysera_project_id'], + 'sign_password' => $settings['paysera_password'])); + +if ($response['type'] !== 'macro') { + die('Only macro payment callbacks are accepted'); +} + +$test_mode = $response['test']; +$order_id = $response['orderid']; +$amount = $response['amount']; +$currency = $response['currency']; + + +//////////////////////////////////// +// Проверка суммы платежа +//////////////////////////////////// + +// Сумма заказа у нас в магазине +$order_amount = $simpla->money->convert($order->total_price, $method->currency_id, false); + +// Должна быть равна переданной сумме +if(round($order_amount*100) != $amount || $amount<=0) + die("Неверная сумма оплаты"); + +// Запишем +if(!$pre_request) +{ + // Установим статус оплачен + $simpla->orders->update_order(intval($order->id), array('paid'=>1)); + + // Спишем товары + $simpla->orders->close(intval($order->id)); +} + +if(!$pre_request) +{ + $simpla->notify->email_order_user(intval($order->id)); + $simpla->notify->email_order_admin(intval($order->id)); +} + +die("OK"); diff --git a/payment/Paysera/settings.xml b/payment/Paysera/settings.xml new file mode 100644 index 0000000..a6774bc --- /dev/null +++ b/payment/Paysera/settings.xml @@ -0,0 +1,26 @@ + + + + Paysera + + + paysera_project_id + Project ID + + + paysera_password + Project password + + + paysera_test_mode + Test mode + + Test payments + 1 + + + Real payments + 0 + + + diff --git a/payment/Rficb/README.TXT b/payment/Rficb/README.TXT new file mode 100644 index 0000000..0624508 --- /dev/null +++ b/payment/Rficb/README.TXT @@ -0,0 +1,12 @@ +��������� ���������� ������ rficb. +����������������� � ������� rficb (rficb.ru). +� ������ �������� rficb � ������� "����������� / �������" �������� ������ � ��������� ���������. +URL ������� ����������� �� ����� �����: http://www.example.com/payment/Rficb/callback.php +URL �������� �������� �������: http://www.example.com/order +URL �������� ������: http://www.example.com/order +��� ������ www.example.com ����������� �������� ��� ������ ��������. +���������� ����� � �������� ����� �����. + +��������� ���� ����� ���������� � ������ �������� rficb "����������� / �������" �� �������� �������������� ������� ��� �� �������� "����������� / API �����". + +� ���������� �������� "���������->������" �������� ������ ������, � ���������� ������ ������� "��� ����", ������ ���� � ��������� ���� �� ������� ��������. diff --git a/payment/Rficb/Rficb.php b/payment/Rficb/Rficb.php new file mode 100644 index 0000000..c3c0ffd --- /dev/null +++ b/payment/Rficb/Rficb.php @@ -0,0 +1,35 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_currency = $this->money->get_currency(intval($payment_method->currency_id)); + $settings = $this->payment->get_payment_settings($payment_method->id); + + $price = round($this->money->convert($order->total_price, $payment_method->currency_id, false), 2); + + + // описание заказа + $desc = 'Оплата заказа №'.$order->id; + + $key = $settings['rficb_payment_key']; + + $button = "
+ + + + + +
"; + return $button; + } +} diff --git a/payment/Rficb/callback.php b/payment/Rficb/callback.php new file mode 100644 index 0000000..acae137 --- /dev/null +++ b/payment/Rficb/callback.php @@ -0,0 +1,76 @@ +orders->get_order(intval($_POST['order_id'])); +if(empty($order)) + die('Оплачиваемый заказ не найден'); + +//////////////////////////////////////////////// +// Выбираем из базы соответствующий метод оплаты +//////////////////////////////////////////////// +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); +if(empty($method)) + die("Неизвестный метод оплаты"); + +$settings = unserialize($method->settings); +$payment_currency = $simpla->money->get_currency(intval($method->currency_id)); + +// Проверяем контрольную подпись +$in_data = array( 'tid' => $_POST['tid'], + 'name' => $_POST['name'], + 'comment' => $_POST['comment'], + 'partner_id' => $_POST['partner_id'], + 'service_id' => $_POST['service_id'], + 'order_id' => $_POST['order_id'], + 'type' => $_POST['type'], + 'partner_income' => $_POST['partner_income'], + 'system_income' => $_POST['system_income'], + 'test' => $_POST['test'] + ); + $transaction_sign = md5(implode('', array_values($in_data)) . $settings['rficb_secret_key']); + +if ($transaction_sign !== $_POST['check'] || empty($settings['rficb_secret_key'])) + die('bad sign'); + +// Нельзя оплатить уже оплаченный заказ +if($order->paid) + die('Этот заказ уже оплачен'); + +if($_POST['system_income'] != round($simpla->money->convert($order->total_price, $method->currency_id, false), 2) || $_POST['system_income']<=0) + die("incorrect price"); + +// Установим статус оплачен +$simpla->orders->update_order(intval($order->id), array('paid'=>1)); +echo 'OK'; +// Отправим уведомление на email +$simpla->notify->email_order_user(intval($order->id)); +$simpla->notify->email_order_admin(intval($order->id)); + +// Спишем товары +$simpla->orders->close(intval($order->id)); + +exit(); diff --git a/payment/Rficb/settings.xml b/payment/Rficb/settings.xml new file mode 100644 index 0000000..59bd039 --- /dev/null +++ b/payment/Rficb/settings.xml @@ -0,0 +1,14 @@ + + + + РФИ банк + + + rficb_payment_key + Ключ магазина + + + rficb_secret_key + Секретный ключ + + diff --git a/payment/Uniteller/Uniteller.php b/payment/Uniteller/Uniteller.php new file mode 100644 index 0000000..1c5b18c --- /dev/null +++ b/payment/Uniteller/Uniteller.php @@ -0,0 +1,78 @@ +orders->get_order((int)$order_id); + $payment_method = $this->payment->get_payment_method($order->payment_method_id); + $payment_currency = $this->money->get_currency(intval($payment_method->currency_id)); + $settings = $this->payment->get_payment_settings($payment_method->id); + + + + $uniteller_shop_id = $settings['uniteller_shop_id']; + $uniteller_password = $settings['uniteller_password']; + $Customer_IDP = ''; + $IData = ''; + $PT_Code = ''; + $EMoneyType = ''; + $MeanType = ''; + $Lifetime = 300; + $Subtotal_P = round($this->money->convert($order->total_price, $payment_method->currency_id, false), 2); + $return_url = $this->config->root_url.'/order/'.$order->url; + $Signature = strtoupper( + md5( + md5($uniteller_shop_id) . "&" . + md5($order->id) . "&" . + md5($Subtotal_P) . "&" . + md5($MeanType) . "&" . + md5($EMoneyType) . "&" . + md5($Lifetime) . "&" . + md5($Customer_IDP) . "&" . + md5($Card_IDP) . "&" . + md5($IData) . "&" . + md5($PT_Code) . "&" . + md5($uniteller_password) + ) + ); + + + + + + if($settings['uniteller_test_action']) + $action = 'https://test.wpay.uniteller.ru/pay/'; + else + $action = 'https://wpay.uniteller.ru/pay/'; + + $button = '
'. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
'; + + return $button; + } +} diff --git a/payment/Uniteller/callback.php b/payment/Uniteller/callback.php new file mode 100644 index 0000000..1323ef8 --- /dev/null +++ b/payment/Uniteller/callback.php @@ -0,0 +1,45 @@ +request->post('Order_ID', 'integer'); + +$order = $simpla->orders->get_order(intval($order_id)); +if(empty($order) && !empty($order_id)) + die('Оплачиваемый заказ не найден'); + + +$method = $simpla->payment->get_payment_method(intval($order->payment_method_id)); +$settings = unserialize($method->settings); +if($_POST["Signature"] = strtoupper(md5($order_id . $_POST["Status"] . $settings['uniteller_password']))) +{ + if( strtolower($_POST["Status"]) == 'authorized') + { + // Установим статус оплачен + $simpla->orders->update_order(intval($order->id), array('paid'=>1)); + + // Отправим уведомление на email + $simpla->notify->email_order_user(intval($order->id)); + $simpla->notify->email_order_admin(intval($order->id)); + + // Спишем товары + $simpla->orders->close(intval($order->id)); + + } +} diff --git a/payment/Uniteller/example.gif b/payment/Uniteller/example.gif new file mode 100644 index 0000000..370054d Binary files /dev/null and b/payment/Uniteller/example.gif differ diff --git a/payment/Uniteller/settings.xml b/payment/Uniteller/settings.xml new file mode 100644 index 0000000..cacb4a7 --- /dev/null +++ b/payment/Uniteller/settings.xml @@ -0,0 +1,26 @@ + + + + Uniteller + + + uniteller_shop_id + ID точки продажи + + + uniteller_password + Пароль Uniteller + + + uniteller_test_action + Тестовый шлюз + + Нет + 0 + + + Да + 1 + + +