Последние темы


Подключение оплаты через Tinkoff Bank

  • Всем привет. Только закончил прикручивать оплату через Tinkoff Bank и пока еще не все забыл хочу поделить наработками.
    Предполагается, что у вас уже настроен Shopkeeper и остается только прикрутить оплату. В Тинькове вы тоже зарегались и получили Идентификатор терминала и Пароль.
    В репозитарии ModX есть модуль Тинькова, но он там очень сырой и без документации, в общем то его я и переписал почти полностью.

    Первым делом необходимо перейти на страницу Оформления заказа или Корзину и установить в вызове снипета FormIt параметр &redirectTo=100 равным ID страницы где вызывается снипет Тинькова. У меня эта страница называется Оплата и содержит вызов снипета [[!Tinkoff]]

    Добавим в настройки Shopkeeper`a способ оплаты Картой (card).

    payment.zip
    Качаем архив и закидываем в папку /assets/components/ там же и распакуем. Появится папка payment в которой найдите файл config.php и настройте под себя

    Содержимое файла config.php

    <?php
    define('PAY_URL', 'https://securepay.tinkoff.ru/rest/Init');// URL запроса
    define('TERMINAL_KEY', '1479721120904DEMO');// Идентификатор терминала
    define('PAY_PASSWORD', '73mq7zfg30hhhwde');	// пароль Тинькова
    define('PAY_NETMASK', '91.194.226.0/23');	// подсеть с которой приходит нотификация
    

    Содержимое файла result.php

    <?php
    	require_once '/home/u67838/foodseasons.ru/www/config.core.php'; // указать ваш абсолютный путь до файла 
    	require_once MODX_CORE_PATH.'model/modx/modx.class.php';
    	require_once MODX_CORE_PATH.'../assets/components/payment/config.php';
    
    	$ip = ip2long($_SERVER['REMOTE_ADDR']); 
    	list($net,$mask) = explode('/',PAY_NETMASK);
    	$net = ip2long($net);
    	$mask = pow(2, 32 - $mask) - 1;
    	$net = $net&~$mask;
    	if (!(($ip^$net)&~$mask)) { // Проверяем принадлежит ли IP к подсети Тинькова
    		if ($_SERVER["REQUEST_METHOD"] == "POST") {
    			if($_POST["TerminalKey"] == TERMINAL_KEY) { // Сверяем Идентификатор терминала
    				$modx = new modX();
    				$modx->initialize('web');
    				$modx->addPackage('shopkeeper3', $modx->getOption('core_path').'components/shopkeeper3/model/');
    				$order_id = $_POST['OrderId'];
    				$order = $modx->getObject('shk_order', $order_id);
    				if ( (isset($order)) && ($order > 0) ) {
    					$order_status = $order->get('status');
    					$amount = $order->get('price');
    					$amount = $amount*100; // Сумма в копейках
    					$email = $order->get('email');
    
    					$args['OrderId'] = $order_id;
    					$args['Amount'] = $amount;
    					$args['Description'] = "Оплата счета №".$order_id;
    					$args['DATA'] = "Email=".$email;
    					$args['TerminalKey'] = TERMINAL_KEY;
    					$args['Password'] = PAY_PASSWORD;
    					// генерируем наш токен, чтобы сверить с тем что пришел в нотификации
    					$token = '';
    					ksort($args);
    					foreach ($args as $arg){$token .= $arg;}
    					//echo '<p>'.$token.'</p>';
    					$token = hash('sha256', $token);
    					unset($args);
    
    					if( ($_POST["Token"] == $token) && ($_POST["ErrorCode"] == 0) && ($_POST["Success"]) ){ // Сверяем токены наш и нотификации из банка
    						$change_status = $order->set('status', 6);
    						$order->save();
    						$modx->invokeEvent('OnSHKChangeStatus',array('order_id'=>$order_id,'status'=>6));
    						echo "OK";
    					}
    				} else {
    					echo "Order not found";
    				}
    			}
    		}
    	}
    

    Далее создаем сам снипет Tinkoff

    <?php
    if ( ($_SESSION['shk_lastOrder']['payment'] != 'card') && (!$_GET['ord_id']) ){ // Проверяем что у нас способ оплаты указан card
    	$modx->sendRedirect('/checkout/success.html', 0, 'REDIRECT_HEADER'); // иначе редиректим на страницу какую хотите
    }
    
    if ( ($_GET['ord_id']) && ($_GET['payment'] == 'card') ){
    	$order_id = $_GET['ord_id'];
    }else{
    	$order_id = $_SESSION['shk_lastOrder']['id']; // получаем ID заказа
    }
    require_once MODX_BASE_PATH."assets/components/payment/config.php"; подтягиваем конфиг Тинькова
    
    $order = $modx->getObject('shk_order', $order_id);
    if ( isset($order) ){
    	$order_status = $order->get('status');
    	$amount = $order->get('price');
    	$amount = $amount*100; // Сумма в копейках
    	$email = $order->get('email');
    
    	$args['OrderId'] = $order_id?:'';
    	$args['Amount'] = $amount?:'';
    	$args['Description'] = "Оплата счета №".$order_id;
    	$args['DATA'] = "Email=".$email;
    	$args['TerminalKey'] = TERMINAL_KEY;
    	$args['Password'] = PAY_PASSWORD;
    	//token generation
    	$token = '';
    	ksort($args);
    	foreach ($args as $arg){$token .= $arg;}
    	//echo '<p>'.$token.'</p>';
    	$token = hash('sha256', $token);
    	$args['Token'] = $token;
    	unset($args['Password']);
    
    	$args = http_build_query($args);
    	//return print_r($args,true);
    	if ($curl = curl_init()){
    		curl_setopt($curl, CURLOPT_URL, PAY_URL);
    		curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
    		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    		curl_setopt($curl, CURLOPT_POST, true);
    		curl_setopt($curl, CURLOPT_POSTFIELDS, $args);
    		$out = curl_exec($curl);
    
    		$json = json_decode($out);
    		//echo '<p>'.print_r($json,true).'</p>';
    
    		curl_close($curl);
    
    		if ($json->PaymentURL){
    		  $change_status = $order->set('status', 2);
    		  $order->save();
    		  $modx->invokeEvent('OnSHKChangeStatus',array('order_id'=>$order_id,'status'=>2));
    			$modx->sendRedirect($json->PaymentURL);
    		}
    		return $json->PaymentURL?:$out;
    	}else{
    		$modx->sendRedirect('/checkout/error.html', 0, 'REDIRECT_HEADER');
    	}
    }
    

    В личный кабинет Тинькова добавим URL для нотификации http://site.ru/assets/components/payment/result.php

    Вот собственно и вся настройка. Теперь идем в магазин, добавляем товар в корзину, выбираем способ оплаты Картой (card) и попадаем на страницу где Тиньков просит ввести данные карты, после успешной оплаты статус заказа изменится на Оплачен

  • Спасибо, что поделились!

  • Действительно, спасибо.
    @Gulik, Tinkoff рекомендует логировать все входящие параметры. Считаю, что нужно обязательно добавить логирование.
    Вот пример того, как я это делаю. Код очень кривой, но работает.

    // логирование
    class Logger {
     
    	protected $fh;
     
    	public function __construct() {
    		$this->fh = fopen('core/logs/log.log', 'a+');
    	}
     
    	public function log($msg) {
    		if(!$this->fh) {
    			throw new Exception('Unable to open log file for writing');
    		}
    		if(fwrite($this->fh, $msg . "\n") === false) {
    			throw new Exception('Unable to write to log file.');
    		}
    	}
     
    	public function __destruct() {
    		fclose($this->fh);
    	}
    }
     
    $logger = new Logger();
    $logger->log(date('m-d-Y H:i:s') . ' ' . $_SERVER['REMOTE_ADDR']);
    $logger->log('$_POST: ' . print_r($_POST, true));
    $logger->log('$_GET: ' . print_r($_GET, true));
    

    Я его вставил перед проверкой токена. Предлагаю вам включить логирование в ваш снипет. Если получится, прошу вас поделиться кодом того, что получилось.

  • @Gulik, Так что на счет логирования?

  • @alexanderr Я использую просто запись нужных мне данных в файл.
    Собираю в процессе выполнения скрипта данные в переменную, а потом (в конце) пишу в файл. Но это нужно только для отладки.

 

Последние комментарии

  • M

    @Andchir , спасибо. Проблема и правда была в одном из плагинов, который на другом сайте нормально работает

    Читать далее
  • Что нового в Shopkeeper 4.0.3:

    Исправлено некорректное определение языка по умолчанию В настройках в админке скрываются пароли. Добавлена возможность загружать картинки для категорий. shopkeeper.js - добавлена функция updateProductsPrice() для поддержки текстовых полей для цены. Twig-функции contentList() и includeContent() вынесены в отдельный класс. Добавлено событие "order.before_create". Сортировка всех полей типа контента перетаскиванием. Автоматическое сохранение сортировки полей при сохранении типа контента (не нужно нажимать на отдельную кнопку). В интерфейсе админа добавлено поле поиска для списка Composer-пакетов.

    Скачать можно на главной странице https://modx-shopkeeper.ru/

    Читать далее
  • Вот этот плагин:
    0_1550334109280_screenshot_022.png

    Вроде по умолчанию он выключен. Надо включить. Но плагин работает только на редактирование товаров, при удалении он делалать ничего не будет. Только что проверил кнопку, всё работает корректно, фильтры удаляются и добавляются, когда нужно. Но нужно очищать корзину после удаления товаров (возможно баг).

    Читать далее
  • J

    @Andchir Если нажимаю кнопочку "Обновить значения", то в фильтрах появляются как раз те самые удаленные значения фильтра.. Потом приходится Ручками выбирать эти удаленные значения.
    вот так выглядит Управление фильтрами когда удаляешь ручками: https://yadi.sk/i/_zw64CGkZ_sAYg
    А вот так выглядит когда просто нажимаешь "Обновить значения": https://yadi.sk/i/7WFbXC6xV5sQAw (красным выделено, то что приходится постоянно удалять

    Читать далее