+380(66)433-69-36 | |
+380(66)433-69-36 | |
+380(66)433-69-36 |
- BitLocker с GUI под linux
- Ищем вирус elTest
- Работаем с бесплатным SSL сертификатом Letsencrypt с помощью certbot
- Синхронизация ресурсов с удаленного сервера локально
- Применение нестандартного SEO и статус 404
- MySQL синхронизируем права с разных серверов
- IPSec VPN соединение между офисами.
- "Зеркало" сайта на стороне. Донастраиваем nginx
- Дефрагментация таблиц всех баз MySQL
- Месяц в родительном падеже strftime PHP
- INIT скрипт для Dropbox
- osCommerce VAM Edition 226. Ошибки
- PositiveSSL порядок сертификатов
- osCommerce. Создаем модуль доставки
- Восстановление mySQL баз данных
- osCommerce.Перенос магазина в другой домен
- osCommerce.Прячем адмику
- osCommerce. Продление жизни сессий
- osCommerce. Создаем платежный модуль
- 10 причин выбрать нас
- GRUB2 восстановление
- osCommerce не пересчитывает общую сумму заказа
- Список потенциально опасных скриптов
- Отправка файлов из Dropbox по e-mail
- "Черный список" почтовых доменов
- Боремся с назойливыми иностранцами
- Яндекс-Диск, и стоит ли им пользоваться.
- Обновление модуля Интеркассы для osCommerce
- Веб-почта на сайте хостинга
- Подключение Outlook Express к хостингу
osCommerce. Создаем платежный модуль
Рано или поздно приходится сталкиваться с тем, что модулей оплаты, встроенных в магазин не хватает, или необходимо в существующих что-либо поменять. Этот материал предназначен в основном для того, чтобы показать как и на каких этапах магазин обращается в платежные модули, и как создать новый платежный модуль.
Платежный модуль может быть во-первых с возможностью сразу оплатить покупку, и во-вторых - просто отображать какую-либо информацию в заказе - например атрибуты расчетного счета в банке. Рассмотрим оба этих вариант и их отличие.
Для начала опишем структуру класса платежного модуля. Вот например код простейшего модуля для Яндекс денег, отображающий номер кошелька для оплаты после выполнения заказа:
class yandex {
во-первых обязательным условием является наименование класса и значение переменной code ниже. Они должны быть одинаковыми!.
var $code, $title, $description, $enabled;
В конструкторе класса обычно определяются переменные класса, а так же устанавливается, доступен ли модуль для платежей. За это отвечает свойство класса enabled.
function yandex() {
$this->code = 'yandex';
$this->title = MODULE_PAYMENT_YANDEX_TEXT_TITLE;
$this->description = MODULE_PAYMENT_YANDEX_TEXT_DESCRIPTION;
$this->sort_order = MODULE_PAYMENT_YANDEX_SORT_ORDER;
$this->email_footer = MODULE_PAYMENT_YANDEX_TEXT_EMAIL_FOOTER;
$this->enabled = MODULE_PAYMENT_YANDEX_STATUS;
}
// class methods
function javascript_validation() {return false;}
Функция selection отображает наименование метода оплаты при выборе способа оплаты при обработке checkout_payment.php. В этом примере просто выводится текстовое наименование модуля оплаты
function selection() {
return array('id' => $this->code,
'module' => $this->title);
}
Возможна модификация кода с тем, чтобы можно было отображать иконку перед наименованием. Естественно имя иконки должно быть отображено в конструкторе класса например так: $this->icon=DIR_WS_ICONS.'yandex.png'
function selection() {
if (tep_not_null($this->icon)) $icon = tep_image($this->icon, $this->title);
$selection = array('id' => $this->code,
'icon' => $icon,
'module' => $this->title,
'description'=>$this->info);
return $selection;
}
Следующие четыре функции объединены вместе не случайно. Они поочередно и именно в этой последовательности обрабатываются при checkout_confirmation.php - это форма перед окончательным подтверждением заказа. Функция update_status() обычно содержит код, который проверяет доступность платежного метода в платежной зоне - т.е. можем ли мы принять платеж от клиента, если мы находимся например в городе А, а клиент в городе Б. Например это используется при обработке платежей за наличный расчет при доставке за пределы города магазина. Функция pre_confirmation_check() осуществляет в данном случае ряд действий перед тем, как предложить подтвердить заказ. Обычно устанавливает переменные, и данные, которые обрабатываются в функции
confirmation(). В простейшем модуле как правило они почти пустые. Функция
confirmation() в данном случае возвращает текст, который отображается в информации об оплате перед подтверждением заказа. Это могут быть например расчетный счет, коды банка и тому подобное.
function update_status(){return false;}
function pre_confirmation_check() {return false;}
function confirmation() {return array('title' => MODULE_PAYMENT_YANDEX_TEXT_DESCRIPTION);}
function process_button() {return false;}
Следующие две функции обрабатываются в процессе окончательного подтверждения заказа Как правило эти функции пустые или содержат код для установки или очисти параметров платежного модуля после обработки.
function before_process() {return false;}
function after_process() {return false;}
function get_error() {return false;}
Функция check() запускается каждый раз при инициализации модуля и устанавливает переменную доступности модуля оплаты в зависимости от установленного в админке флажка. Здесь могут использоваться и дополнительные проверки. Если эта функция вернет false то модуль не будет ни отображаться ни обрабатываться.
function check() {
if (!isset($this->check)) {
$check_query = tep_db_query("select configuration_value from " . TABLE_CONFIGURATION . " where configuration_key = 'MODULE_PAYMENT_YANDEX_STATUS'");
$this->check = tep_db_num_rows($check_query);
}
return $this->check;
}
Функция добавляет в таблицу конфигурации параметры платежного модуля, которые можно менять через админку. Количество ключей должно быть равно их количеству из функции keys(). Они, конечно могут различаться - но это можно считать некорректностью. При этом стоит обратить особое внимание на то, что параметры обычно начинаются с MODULE_PAYMENT_ потом идет название модуля и параметр. Это предотвращает возможный конфликт с другими модулями.
function install() {
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Оплата через систему Яндекс-Деньги', 'MODULE_PAYMENT_YANDEX_STATUS', '1', 'Вы хотите использовать модуль Оплата через систему Яндекс-Деньги? 1 - да, 0 - нет', '6', '1', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Номер Вашего счёта в системе Яндекс-Деньги', 'MODULE_PAYMENT_YANDEX_1', '11111111111', 'Введите номер Вашего счёта в системе Яндекс-Деньги.', '6', '1', now());");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Порядок сортировки.', 'MODULE_PAYMENT_YANDEX_SORT_ORDER', '0', 'Порядок сортировки модуля.', '6', '0', now())");
}
function remove() {
tep_db_query("delete from " . TABLE_CONFIGURATION . " where configuration_key in ('" . implode("', '", $this->keys()) . "')");
}
function keys() {
$keys = array('MODULE_PAYMENT_YANDEX_STATUS', 'MODULE_PAYMENT_YANDEX_1', 'MODULE_PAYMENT_YANDEX_SORT_ORDER');
return $keys;
}
}
На этом описание простейшего модуля оплаты можно считать законченным. Теперь обратим внимание на особенности работы интерактивного модуля оплаты, например через WebMoney или PayPal. При этом на заключительной странице окончательного подтверждения заказа checkout_confirmation.php происходит переход на страницу оплаты. Следует обратить внимание, что при этом не выполняется код checkout_process.php, и потому сам заказ в магазине не создается!. Это связано с тем, что в переменных класса есть параметр $this->form_action_url = 'https://merchant.webmoney.ru/lmi/payment.asp'; который иницилизируется как правило в конструкторе класса. Если этот параметр задан - то механизм работы checkout_confirmation.php несколько меняется. Во-первых вместо адреса окончательного подтверждения заказа checkout_process.php меняется на установленный в параметре адрес. Во вторых данные для передачи формы на указанный адрес строятся на основе того, что возвращает функция process_button(), а не на основе того, что передано в форму. Вот как, например выглядит эта функция в модуле платежей через Webmoney:
global $customer_id, $order, $sendto, $currency, $currencies, $cart_webmoney_id, $shipping;
$process_button_string = '';
$purse = MODULE_PAYMENT_WEBMONEY_MERCHANT_WMR;
$order_sum = $order->info['total'];
$process_button_string = tep_draw_hidden_field('LMI_PAYMENT_NO', substr($cart_webmoney_id, strpos($cart_webmoney_id, '-')+1)) .
tep_draw_hidden_field('LMI_PAYEE_PURSE', $purse) .
tep_draw_hidden_field('LMI_PAYMENT_DESC', substr($cart_webmoney_id, strpos($cart_webmoney_id, '-')+1)) .
tep_draw_hidden_field('LMI_PAYMENT_AMOUNT', $order_sum) .
tep_draw_hidden_field('LMI_SIM_MODE', '0');
return $process_button_string;
}
Поскольку как таковой дальнейшей обработки заказа не происходит, то интерактивный модуль должен сделать всю работу по созданию заказа сам. При этом используются функции pre_confirmation_check() и confirmation(). В первой из них создаются вспомагательные параметры, во второй - создается сам по себе заказ. Вот пример из того же модуля WebMoney.
function pre_confirmation_check() {
global $cartID, $cart;
if (empty($cart->cartID)) {
$cartID = $cart->cartID = $cart->generate_cart_id();
}
if (!tep_session_is_registered('cartID')) {
tep_session_register('cartID');
}
}
function confirmation() {
global $cartID, $cart_webmoney_id, $customer_id, $languages_id, $order, $order_total_modules;
if (tep_session_is_registered('cartID')) {
$insert_order = false;
if (tep_session_is_registered('cart_webmoney_id')) {
$order_id = substr($cart_webmoney_id, strpos($cart_webmoney_id, '-')+1);
$curr_check = tep_db_query("select currency from " . TABLE_ORDERS . " where orders_id = '" . (int)$order_id . "'");
$curr = tep_db_fetch_array($curr_check);
if ( ($curr['currency'] != $order->info['currency']) || ($cartID != substr($cart_webmoney_id, 0, strlen($cartID))) ) {
$check_query = tep_db_query('select orders_id from ' . TABLE_ORDERS_STATUS_HISTORY . ' where orders_id = "' . (int)$order_id . '" limit 1');
if (tep_db_num_rows($check_query) < 1) {
tep_db_query('delete from ' . TABLE_ORDERS . ' where orders_id = "' . (int)$order_id . '"');
tep_db_query('delete from ' . TABLE_ORDERS_TOTAL . ' where orders_id = "' . (int)$order_id . '"');
tep_db_query('delete from ' . TABLE_ORDERS_STATUS_HISTORY . ' where orders_id = "' . (int)$order_id . '"');
tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS . ' where orders_id = "' . (int)$order_id . '"');
tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . ' where orders_id = "' . (int)$order_id . '"');
tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS_DOWNLOAD . ' where orders_id = "' . (int)$order_id . '"');
}
$insert_order = true;
}
} else {
$insert_order = true;
}
if ($insert_order == true) {
$order_totals = array();
if (is_array($order_total_modules->modules)) {
reset($order_total_modules->modules);
while (list(, $value) = each($order_total_modules->modules)) {
$class = substr($value, 0, strrpos($value, '.'));
if ($GLOBALS[$class]->enabled) {
for ($i=0, $n=sizeof($GLOBALS[$class]->output); $i<$n; $i++) {
if (tep_not_null($GLOBALS[$class]->output[$i]['title']) && tep_not_null($GLOBALS[$class]->output[$i]['text'])) {
$order_totals[] = array('code' => $GLOBALS[$class]->code,
'title' => $GLOBALS[$class]->output[$i]['title'],
'text' => $GLOBALS[$class]->output[$i]['text'],
'value' => $GLOBALS[$class]->output[$i]['value'],
'sort_order' => $GLOBALS[$class]->sort_order);
}
}
}
}
}
$sql_data_array = array('customers_id' => $customer_id,
'customers_name' => $order->customer['firstname'] . ' ' . $order->customer['lastname'],
'customers_company' => $order->customer['company'],
'customers_street_address' => $order->customer['street_address'],
'customers_suburb' => $order->customer['suburb'],
'customers_city' => $order->customer['city'],
'customers_postcode' => $order->customer['postcode'],
'customers_state' => $order->customer['state'],
'customers_country' => $order->customer['country']['title'],
'customers_telephone' => $order->customer['telephone'],
'customers_email_address' => $order->customer['email_address'],
'customers_address_format_id' => $order->customer['format_id'],
'delivery_name' => $order->delivery['firstname'] . ' ' . $order->delivery['lastname'],
'delivery_company' => $order->delivery['company'],
'delivery_street_address' => $order->delivery['street_address'],
'delivery_suburb' => $order->delivery['suburb'],
'delivery_city' => $order->delivery['city'],
'delivery_postcode' => $order->delivery['postcode'],
'delivery_state' => $order->delivery['state'],
'delivery_country' => $order->delivery['country']['title'],
'delivery_address_format_id' => $order->delivery['format_id'],
'billing_name' => $order->billing['firstname'] . ' ' . $order->billing['lastname'],
'billing_company' => $order->billing['company'],
'billing_street_address' => $order->billing['street_address'],
'billing_suburb' => $order->billing['suburb'],
'billing_city' => $order->billing['city'],
'billing_postcode' => $order->billing['postcode'],
'billing_state' => $order->billing['state'],
'billing_country' => $order->billing['country']['title'],
'billing_address_format_id' => $order->billing['format_id'],
'payment_method' => $order->info['payment_method'],
'cc_type' => $order->info['cc_type'],
'cc_owner' => $order->info['cc_owner'],
'cc_number' => $order->info['cc_number'],
'cc_expires' => $order->info['cc_expires'],
'date_purchased' => 'now()',
'orders_status' => $order->info['order_status'],
'currency' => $order->info['currency'],
'currency_value' => $order->info['currency_value']);
tep_db_perform(TABLE_ORDERS, $sql_data_array);
$insert_id = tep_db_insert_id();
$customer_notification = (SEND_EMAILS == 'true') ? '1' : '0';
$sql_data_array = array ('orders_id' => $insert_id, 'orders_status_id' => $order->info['order_status'], 'date_added' => 'now()', 'customer_notified' => $customer_notification, 'comments' => $order->info['comments']);
tep_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
for ($i=0, $n=sizeof($order_totals); $i<$n; $i++) {
$sql_data_array = array('orders_id' => $insert_id,
'title' => $order_totals[$i]['title'],
'text' => $order_totals[$i]['text'],
'value' => $order_totals[$i]['value'],
'class' => $order_totals[$i]['code'],
'sort_order' => $order_totals[$i]['sort_order']);
tep_db_perform(TABLE_ORDERS_TOTAL, $sql_data_array);
}
for ($i=0, $n=sizeof($order->products); $i<$n; $i++) {
$sql_data_array = array('orders_id' => $insert_id,
'products_id' => tep_get_prid($order->products[$i]['id']),
'products_model' => $order->products[$i]['model'],
'products_name' => $order->products[$i]['name'],
'products_price' => $order->products[$i]['price'],
'final_price' => $order->products[$i]['final_price'],
'products_tax' => $order->products[$i]['tax'],
'products_quantity' => $order->products[$i]['qty']);
tep_db_perform(TABLE_ORDERS_PRODUCTS, $sql_data_array);
$order_products_id = tep_db_insert_id();
$attributes_exist = '0';
if (isset($order->products[$i]['attributes'])) {
$attributes_exist = '1';
for ($j=0, $n2=sizeof($order->products[$i]['attributes']); $j<$n2; $j++) {
if (DOWNLOAD_ENABLED == 'true') {
$attributes_query = "select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix, pad.products_attributes_maxdays, pad.products_attributes_maxcount , pad.products_attributes_filename
from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa
left join " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad
on pa.products_attributes_id=pad.products_attributes_id
where pa.products_id = '" . $order->products[$i]['id'] . "'
and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "'
and pa.options_id = popt.products_options_id
and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "'
and pa.options_values_id = poval.products_options_values_id
and popt.language_id = '" . $languages_id . "'
and poval.language_id = '" . $languages_id . "'";
$attributes = tep_db_query($attributes_query);
} else {
$attributes = tep_db_query("select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa where pa.products_id = '" . $order->products[$i]['id'] . "' and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "' and pa.options_id = popt.products_options_id and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "' and pa.options_values_id = poval.products_options_values_id and popt.language_id = '" . $languages_id . "' and poval.language_id = '" . $languages_id . "'");
}
$attributes_values = tep_db_fetch_array($attributes);
$sql_data_array = array('orders_id' => $insert_id,
'orders_products_id' => $order_products_id,
'products_options' => $attributes_values['products_options_name'],
'products_options_values' => $attributes_values['products_options_values_name'],
'options_values_price' => $attributes_values['options_values_price'],
'price_prefix' => $attributes_values['price_prefix']);
tep_db_perform(TABLE_ORDERS_PRODUCTS_ATTRIBUTES, $sql_data_array);
if ((DOWNLOAD_ENABLED == 'true') && isset($attributes_values['products_attributes_filename']) && tep_not_null($attributes_values['products_attributes_filename'])) {
$sql_data_array = array('orders_id' => $insert_id,
'orders_products_id' => $order_products_id,
'orders_products_filename' => $attributes_values['products_attributes_filename'],
'download_maxdays' => $attributes_values['products_attributes_maxdays'],
'download_count' => $attributes_values['products_attributes_maxcount']);
tep_db_perform(TABLE_ORDERS_PRODUCTS_DOWNLOAD, $sql_data_array);
}
}
}
}
$cart_webmoney_id = $cartID . '-' . $insert_id;
tep_session_register('cart_webmoney_id');
}
}
return array('title' => MODULE_PAYMENT_WEBMONEY_MERCHANT_TEXT_DESCRIPTION);
}
ну вот примерно так и работает платежный модуль. Еще одно отступление. Иногда есть необходимость сделать так, чтобы клиент мог распечатать банковский атрибуты из истории заказов, или например выполнить реальную оплату тоже только из истории заказов. В таком случае обычно на странице истории account_history.php отрисовывается кнопка, при нажатии которой либо отображается всплывающее окно с атрибутами либо происходит переход на платежную страницу. В этом случае используется переменная класса $title - поскольку именно она сохраняется в заказе. Например для модуля оплаты через российский банк код выглядит примерно так:
if ($history['payment_method'] == MODULE_PAYMENT_RUS_BANK_TEXT_TITLE) {}
т.е. при совпадении с заголовком платежного модуля отображается кнопка или делается что-либо еще. Обратите внимание, что переменная заголовка модуля обычно выносится в отдельный языковой файл.