From a125d68e254be8456668fb7f378dd553eb5e0b7e Mon Sep 17 00:00:00 2001 From: Erko Evgen Date: Tue, 16 May 2017 13:37:08 +0300 Subject: [PATCH] 2.3.8 --- api/Config.php | 4 +- payment/Alfabank/Alfabank.php | 91 + payment/Alfabank/callback.php | 73 + payment/Alfabank/settings.xml | 19 + payment/Assist/Assist.php | 58 + payment/Assist/callback.php | 71 + payment/Assist/example.gif | Bin 0 -> 56378 bytes payment/Assist/settings.xml | 18 + payment/ChronoPay/ChronoPay.php | 44 + payment/ChronoPay/callback.php | 32 + payment/ChronoPay/settings.xml | 12 + payment/MandarinBank/MandarinBank.php | 59 + payment/MandarinBank/callback.php | 105 + payment/MandarinBank/readme-mandarin.txt | 10 + payment/MandarinBank/settings.xml | 14 + payment/NetPay/NetPay.php | 94 + payment/NetPay/callback.php | 95 + payment/NetPay/read_me.txt | 12 + payment/NetPay/security.class.php | 34 + payment/NetPay/settings.xml | 26 + payment/PSBank/PSBank.php | 81 + payment/PSBank/callback.php | 91 + payment/PSBank/settings.xml | 30 + payment/Paysera/Paysera.php | 58 + payment/Paysera/WebToPay.php | 2357 ++++++++++++++++++++++ payment/Paysera/callback.php | 83 + payment/Paysera/settings.xml | 26 + payment/Rficb/README.TXT | 12 + payment/Rficb/Rficb.php | 35 + payment/Rficb/callback.php | 76 + payment/Rficb/settings.xml | 14 + payment/Uniteller/Uniteller.php | 78 + payment/Uniteller/callback.php | 45 + payment/Uniteller/example.gif | Bin 0 -> 24321 bytes payment/Uniteller/settings.xml | 26 + 35 files changed, 3881 insertions(+), 2 deletions(-) create mode 100644 payment/Alfabank/Alfabank.php create mode 100644 payment/Alfabank/callback.php create mode 100644 payment/Alfabank/settings.xml create mode 100644 payment/Assist/Assist.php create mode 100644 payment/Assist/callback.php create mode 100644 payment/Assist/example.gif create mode 100644 payment/Assist/settings.xml create mode 100644 payment/ChronoPay/ChronoPay.php create mode 100644 payment/ChronoPay/callback.php create mode 100644 payment/ChronoPay/settings.xml create mode 100644 payment/MandarinBank/MandarinBank.php create mode 100644 payment/MandarinBank/callback.php create mode 100644 payment/MandarinBank/readme-mandarin.txt create mode 100644 payment/MandarinBank/settings.xml create mode 100644 payment/NetPay/NetPay.php create mode 100644 payment/NetPay/callback.php create mode 100644 payment/NetPay/read_me.txt create mode 100644 payment/NetPay/security.class.php create mode 100644 payment/NetPay/settings.xml create mode 100644 payment/PSBank/PSBank.php create mode 100644 payment/PSBank/callback.php create mode 100644 payment/PSBank/settings.xml create mode 100644 payment/Paysera/Paysera.php create mode 100644 payment/Paysera/WebToPay.php create mode 100644 payment/Paysera/callback.php create mode 100644 payment/Paysera/settings.xml create mode 100644 payment/Rficb/README.TXT create mode 100644 payment/Rficb/Rficb.php create mode 100644 payment/Rficb/callback.php create mode 100644 payment/Rficb/settings.xml create mode 100644 payment/Uniteller/Uniteller.php create mode 100644 payment/Uniteller/callback.php create mode 100644 payment/Uniteller/example.gif create mode 100644 payment/Uniteller/settings.xml 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 0000000000000000000000000000000000000000..baff87f7bb7e24edb74ed61ace8ed16ef2918738 GIT binary patch literal 56378 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 G0028Qt8bS8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..370054d3f0e14ad2554f786c600799c2091833c1 GIT binary patch literal 24321 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 APXGV_ literal 0 HcmV?d00001 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 + + +