Группа: Super Admins
Сообщений: 4178
Регистрация: 28.12.2005
Из: Москва
Пользователь №: 1
Продажа товара на сайте за WM, принцип работы программ и настройка параметров приема платежей. Часть 1
Большинству вебмастеров есть что продавать на своих сайтах. И для этого вовсе не обязательно устанавливать программы (скрипты) полноценного интернет-магазина, об уязвимости которых так много и увлекательно написано. Для продажи одного или нескольких товаров вполне достаточно использовать простые программы приема платежей на сайте, в просторечии называемые биллинг. Вполне разумная рекомендация – написать такие программы самостоятельно. Однако и чужие программы приема платежей тоже могут оказаться полезными, хотя бы частично. Рассмотрим простой пример таких программ приема платежей и выдачи товара на сайте в системе Webmoney Merchant Interface одного электронного товара – в виде файла за WM, без использования баз данных (таких как MySQL). Такие программы продажи товара в дальнейшем могут быть основой и для полноценного интернет-магазина.
Принцип работы программ приема платежей в WM Для начала есть смысл распечатать полное руководство интерфейса Web Merchant Interface по приему платежей со страницы https://merchant.webmoney.ru/conf/guide.asp Дополнительно будем руководствоваться принципами: «Береженого Бог бережет» и «Подальше положишь, поближе возьмешь». Итак, мы (продавец) должны сформировать страницу с формой запроса платежа (с условным именем zapros.pl), включающую наши данные (номер продажи, сумму платежа в WM, номер нашего кошелька, признак тестового/рабочего режима и некоторые вспомогательные) и отослать сервису Webmoney Merchant, который возвращает нам эти данные (и данные покупателя) для сравнения и подтверждения на нашу страницу (с программой), с условным именем result.pl. Мы должны вернуть сервису Webmoney Merchant строку ‘YES’, если данные верны, и что-либо другое в противном (действительно противном) случае. Здесь и в дальнейшем каждый раз запоминаем результат сравнения и используем его при последующих проверках. Вернув строку ‘YES’, мы даем возможность покупателю продолжить платеж и сервис Webmoney Merchant передает нам в таком случае форму оповещения о платеже на ту же страницу result.pl с этими же и дополнительными данными, позволяющими определить целостность переданных данных и источник данных (Webmoney Merchant ли это). Также передаются данные о внутреннем для Webmoney Merchant номере счета и номере платежа. В руководстве рекомендуется проверить (и мы проверяем) дополнительно сумму платежа, кошелек продавца и режим работы (тестовый/рабочий). В случае возврата нами чего-либо еще, вместо строки ‘YES’, сервис Webmoney Merchant направляет браузер покупателя на заранее нами подготовленную страницу формы невыполненного платежа (с условным именем fail.pl), на которой мы обязаны сообщить покупателю об ошибке во время прохождения платежа. Проверив целостность и источник данных и другие рекомендуемые параметры, также сохраним результат сравнения для выдачи/невыдачи товара. Сохраним также и все остальные данные, связанные с номером этой продажи. В случае успешного выполнения платежа сервис Webmoney Merchant направляет браузер покупателя на заранее подготовленную нами страницу выдачи товара с условным именем success.pl и передает свой внутренний номер счета, платежа и дату совершения продажи, которые мы также сравниваем с полученными ранее и разумеется проверяем успешность предыдущей проверки. Таким образом до выдачи покупателю товара поступающие данные проходят ряд (цепочку) проверок, и только в случае полного совпадения всех данных покупателю выдается товар. Данные о продаже запоминаются. Если где-либо проверка оказалась неудачной, товар не выдается, покупателю сообщается об ошибке, даже если сервис Webmoney Merchant направил браузер покупателя на страницу выдачи товара. И в таком случае считаю совершенно необходимым сообщить покупателю свои контактные данные для решения спорной ситуации. Данные о неудачной продаже также запоминаются, кроме случая, когда ошибка произошла в самом начале выполнения платежа и сервис Webmoney Merchant прервал выполнение операции и направил браузер покупателя на страницу ошибки.
Настройка параметров приема платежа на сервисе Webmoney Merchant Interface В параметрах указываем полные пути к файлам result.pl, success.pl и fail.pl, например для этого сайта (если бы можно было выполнять скрипты) http : // forum . ribca . net / cgi-bin / result . pl, метод вызова для success.pl и fail.pl - POST. Метод формирования контрольной подписи оповещения о платеже – MD5. Режим обработки платежей – тестовый. Указываем Secret Key для идентификации сервиса Webmoney Merchant, например 9d7brvG3uw8k. Обязательно устанавливаем флаг – Передавать параметры в предварительном запросе. Это позволит сервису Webmoney Merchant убедиться перед выполнением платежа, что сайт продавца работоспособен, а продавцу – что переданные данные в запросе платежа – корректны. Нужно также установить флаг - Активность. И поскольку мы здесь не используем протокол SSL, то не устанавливаем флаг - Высылать Secret Key, а также не устанавливаем флаг - Позволять использовать URL, передаваемые в форме. И еще нужна такая малость, как персональный аттестат в сервисе Webmoney Merchant, для чего вначале нужно получить начальный аттестат, заполнив форму, затем отослать регистратору копию паспорта, заверенную нотариусом. Стоит это, по-моему, десять американских рублей. Нужен также второй кошелек, с которого будет производится оплата (достаточно начального аттестата). Общие замечания по программам приема платежей (продажи товара). Для хранения наших начальных данных и установок (номера продажи и т.д.), промежуточных и конечных данных по каждой продаже будем использовать хэш, хранящийся в файле с условным именем pay (своего рода лог файл). Составим также маленький скрипт, позволяющий просматривать в браузере содержимое этого файла. Реализуем возможность заплатить за товар несколькими видами валют, самостоятельно определив их курсы (сам себе Центробанк).
Счастье есть - его не может не быть. Не бойся смерти - бойся непрожитой жизни. Как бы не было плохо, нужно расслабиться и получать удовольствие, а если хорошо, то тем более. Мозги надо менять а не конституцию.
Это мы то нубы?! Да я вас всех продам, куплю, а потом еще раз продам но уже дороже!!!
<> ужос...сегодня впервые за пару месяцев на ночь выключил комп....... <> еле в этой долбаной тишине уснул.......
Группа: Super Admins
Сообщений: 4178
Регистрация: 28.12.2005
Из: Москва
Пользователь №: 1
Продажа товара на сайте за WM, программы приема платежей на Perl. Часть 2
Опустим пока некоторые детали программ, относящиеся к безопасности (приведены ниже).
Программа, генерирующая форму запроса платежа.
Цитата
#!/usr/bin/perl -s use CGI qw/-no_xhtml :standard/;
# вывод сообщений при ошибочных ситуациях в программах, используется при отладке use CGI::Carp qw(fatalsToBrowser);
my %pay; # размещаем нашу "базу данных" вне дерева документов my $file_c = "../../log/pay";
dbmopen(%pay, $file_c, 0640) or die "Can't open $file_c: $!\n"; # инициализация начальных данных при первом запуске unless ( exists $pay{0} ) { # в $pay{0} номер продажи $pay{0} = 9; $pay{1} = 'R584486433773'; $pay{2} = 'Z541878171685'; $pay{3} = 'E738832585013'; $pay{4} = 'за рубли'; $pay{5} = 'за доллары'; $pay{6} = 'за евро'; $pay{7} = 13.06; $pay{8} = 0.5; $pay{9} = 0.44; } # при каждом запуске (обращении к этой программе) переходим к новой продаже $pay{0}++; # в $pay{'номер продажи'} - будут все данные о данной продаже $pay{$pay{0}} = 1;
# Прежде всего следует слегка похвалить наш товар! print "<h2 align='center'>Описание товара:</h2><div>Самый лучший товар во Вселенной!</div>";
# для каждого кошелька формируем форму запроса платежа for ( my $i =0; $i < 3; $i++ ) { print start_form(-method=>'POST', -action=>'https://merchant.webmoney.ru/lmi/payment.asp', -enctype=>'application/x-www-form-urlencoded'), # устанавливаем номер кошелька, сумму, номер продажи, ее описание, тестовый режим и нежелание # кредитовать hidden(-name=>'LMI_PAYEE_PURSE', -value=> $pay{1 + $i}), "<div>$pay{4 + $i} $pay{7 + $i}</div>", hidden(-name=>'LMI_PAYMENT_AMOUNT', -value=> $pay{7 + $i}), hidden(-name=>'LMI_PAYMENT_NO', -value=> $pay{0}, -override=>1), hidden('LMI_PAYMENT_DESC','платеж за товар'), hidden('LMI_SIM_MODE',0), hidden('LMI_PAYMENT_CREDITDAYS',0),
# элемент формы, устанавливаемый продавцом. Очень полезная штука, например для дополнительного # определения целостности данных. hidden('MY_SHOP','is_good'), submit('shop','приобрести'), endform; } print end_html; dbmclose %pay; exit;
Эта форма запроса платежа отсылает данные непосредственно сервису Webmoney Merchant. Однако мы знаем, что не все, что посылают нам посетители, одинаково полезно. Поэтому имеет смысл сначала отсылать данные к себе на сайт и после проверки – на сервис Webmoney Merchant.
Программа проверки формы предварительного запроса и формы оповещения о платеже
Цитата
#!/usr/bin/perl use CGI qw(-no_xhtml :standard :cgi-lib); use Digest::MD5 qw(md5_hex);
my %pay; my $file_c = "../../log/pay"; dbmopen(%pay, $file_c, 0640) or die "Can't open $file_c: $!\n";
# подпрограмма записывает в хэш все значения присланной формы в случае отрицательного результата # проверки sub last_data { my $pay_tmp = Vars; my $add; foreach ( keys %{$pay_tmp} ) { $add .= $_ . '=' . ${$pay_tmp}{$_} . '<br>'; } $add .= 'END_FORM<br>'; }
# вначале проверяем существование этого номера продажи и если номера не существует, тогда с # двухсекундной задержкой выдаем строку 'NO' if ( param('LMI_PAYMENT_NO') =~ /^(\d{1,4})$/ && exists $pay{$1} ) { my $nom_pay = $1; # если имеется признак формы предварительного запроса, то if ( param('LMI_PREREQUEST') == 1 ) {
# проверяем номера наших кошельков, признак тестового режима (здесь тестовый), наше нежелание # кредитовать, суммы платежа. В случае успешной проверки посылаем в ответ строку 'EYS' и # запоминаем для этого номера продажи результат и место проверки (предварительный запрос). Если # результат проверки отрицательный, то посылаем в ответ строку 'NO' и также запоминаем результат # и место проверки и к тому же сохраняем данные запроса (для анализа и истории) if ( ( grep {param('LMI_PAYEE_PURSE') eq $_} @pay{1..3} ) && param('LMI_MODE') == 1 && param('LMI_PAYMENT_CREDITDAYS') == 0 && ( grep {param('LMI_PAYMENT_AMOUNT') == $_} @pay{7..9} ) ) { print header, start_html, 'YES', end_html; $pay{$nom_pay} = 'SUCCESS_FROM_PREREQUEST'; } else { print header, start_html, 'NO', end_html; $pay{$nom_pay} = 'MISTAKE_FROM_PREREQUEST<br>' . &last_data; } } # если не имеется признака предварительного запроса, то приступаем к проверке данных нового # запроса только при успешности предыдущей проверки. Для чего "склеиваем" в строку данные запроса # со строкой SecretKey, как описано в руководстве, и формируем последовательность по алгоритму # MD5. # Соответственно и сервис Webmoney Merchant присылает последовательность, сформированную таким же # образом. Мы сравниваем эти строки и дополнительно проверяем сумму платежа, режим (здесь # тестовый), номера кошельков. При положительном результате проверки запоминаем (устанавливаем # значение хэша с ключом, соответствующим номеру продажи) результат, место проверки, данные о # продаже. # Если же результат предыдущей проверки был неудачен, то не проверяя данные вновь поступившего # запроса, запоминаем эту новую проверку как ошибочную (не удаляя данных предыдущей), запоминаем # место проверки, добавляем данные вновь поступившего запроса. # В случае успешности предыдущей проверки, но неудачи текущей, данные предыдущей удаляются и # запоминаются результат (ошибка), место проверки и данные запроса этой проверки. else { if ( $pay{$nom_pay} eq 'SUCCESS_FROM_PREREQUEST' ) { my $data_for_hash = param('LMI_PAYEE_PURSE') . param('LMI_PAYMENT_AMOUNT') . param('LMI_PAYMENT_NO') . param('LMI_MODE') . param('LMI_SYS_INVS_NO') . param('LMI_SYS_TRANS_NO') . param('LMI_SYS_TRANS_DATE') . "SecretKey" . param('LMI_PAYER_PURSE') . param('LMI_PAYER_WM'); my $my_hash = uc(md5_hex($data_for_hash)); if ( $my_hash eq param('LMI_HASH') && param('LMI_MODE') == 1 && ( grep {param('LMI_PAYMENT_AMOUNT') == $_} @pay{7..9} ) && ( grep {param('LMI_PAYEE_PURSE') eq $_} @pay{1..3} ) ) { $pay{$nom_pay} = 'SUCCESS_FROM_PAYMENT<br>' . param('LMI_SYS_INVS_NO') . '<br>' . param('LMI_SYS_TRANS_NO') . '<br>LMI_PAYER_WM=' . param('LMI_PAYER_WM') . '<br>LMI_PAYER_PURSE=' . param('LMI_PAYER_PURSE') . '<br>LMI_PAYMENT_AMOUNT=' . param('LMI_PAYMENT_AMOUNT'); } else { $pay{$nom_pay} = 'MISTAKE_FROM_PAYMENT<br>' . $pay{$nom_pay}. '<br>' . &last_data; } } else { $pay{$nom_pay} = 'MISTAKE_FROM_PAYMENT<br>' . $pay{$nom_pay}. '<br>' . &last_data; } } } else { sleep(2); print header, start_html, 'NO', end_html; } dbmclose %pay; exit;
Таким образом для успешного выполнения платежа достаточно положительного результата проверки формы предварительного запроса посылаемого сервисом Webmoney Merchant. Но ошибка может возникнуть и при проверке формы оповещения о платеже. Кроме того, при направлении браузера покупателя на страницу формы выполненного платежа, в форме также передаются данные о внутреннем номере счета и платежа системы Webmoney Merchant, которые тоже могут не совпадать для данного номера покупки. Поэтому на странице формы выполненного платежа (выдачи товара) необходимо убедиться в успешности предыдущей проверки и вновь полученных данных. При несовпадении (а платеж был выполнен) считаю необходимым указать контактные данные продавца (e-mail, номер ICQ) для решения спорной ситуации и товар не выдавать. Характер ошибки, думаю, тоже нет смысла конкретизировать (для «покупателя»). Как и в предыдущей программе, при возникновении ошибки проверки данных, запоминаем для данного номера продажи результат, место проверки и полученные данные. Если все проверки были успешно выполнены, также запоминаем данные о продаже и размещаем ссылку на купленный товар (файл). Можно для безопасности создать для этого временную директорию (папку) и поместить туда файл, через некоторое время удалив и папку и файл и т.д.
Программа проверки формы выполненного платежа
Цитата
#!/usr/bin/perl use CGI qw/-no_xhtml :standard/; use CGI::Carp qw(fatalsToBrowser);
my %pay; my $file_c = "../../log/pay"; dbmopen(%pay, $file_c, 0640) or die "Can't open $file_c: $!\n";
# вначале проверяем существование этого номера продажи и если номера не существует, тогда с # двухсекундной задержкой выдаем пустую страницу if ( param('LMI_PAYMENT_NO') =~ /^(\d{1,4})/ && exists $pay{$1} ) { my $nom_pay = $1; my @pay_tmp = split "<br>", $pay{$nom_pay};
# проверка для данного номера продажи внутреннего номера счета и платежа сервиса # merchant.webmoney if ( $pay_tmp[0] eq 'SUCCESS_FROM_PAYMENT' && $pay_tmp[1] == param('LMI_SYS_INVS_NO') && $pay_tmp[2] == param('LMI_SYS_TRANS_NO') ) { # успешная проверка; выдаем файл и запоминаем данные продажи $prom = '<div>Скачайте файл товара <a href="http://blankinew.narod.ru/moi_lyubimye_aforizmy.html">Файл товара</a></div>'; $pay{$nom_pay} = 'SUCCESS<br>LMI_SYS_INVS_NO=' . $pay_tmp[1] . '<br>' . $pay_tmp[5] . '<br>LMI_SYS_TRANS_NO=' . $pay_tmp[2] . '<br>' . $pay_tmp[3] . '<br>' . $pay_tmp[4] . '<br>LMI_SYS_TRANS_DATE=' . param('LMI_SYS_TRANS_DATE'); } else { $prom = 'К сожалению произошла ошибка, файл не может быть выдан, обратитесь по e-mail'; my $pay_tmp; @names = param(); $pay_tmp .= $_ . '=' . param($_) . '<br>' foreach @names; $pay{$nom_pay} = "MISTAKE_FROM_SUCCESS<br>$pay{$nom_pay}<br>$pay_tmp"; } print header(-charset => 'windows-1251'), start_html('Succsess Выдача товара'), $prom, end_html; } else { sleep(2); print header, start_html, end_html; }
dbmclose %pay; exit;
Если покупатель прервал выполнение платежа или произошла ошибка во время проверки данных формы предварительного запроса, сервис Webmoney Merchant направляет браузер покупателя на страницу формы невыполненного платежа. Здесь мы просто отмечаем факт попадания для данного номера продажи на эту страницу и сообщаем покупателю об ошибке выполнения платежа. Предложим ему вернуться к началу платежа, на главную страницу сайта, к другим разделам сайта.
Программа получения формы невыполненного платежа (страница ошибки платежа)
Цитата
#!/usr/bin/perl use CGI qw/-no_xhtml :standard/; use CGI::Carp qw(fatalsToBrowser);
my %pay; my $file_c = "../../log/pay"; dbmopen(%pay, $file_c, 0640) or die "Can't open $file_c: $!\n";
# вначале проверяем существование этого номера продажи и если номера не существует, тогда с # двухсекундной задержкой выдаем пустую страницу if ( param('LMI_PAYMENT_NO') =~ /^(\d{1,4})$/ && exists $pay{$1} ) { my $nom_pay = $1; $pay{$nom_pay} = 'MISTAKE_FROM_FAIL<br>' . $pay{$nom_pay}; print header(-charset => 'windows-1251'), start_html('Fail Страница ошибки'), "<div>Ошибка совершения платежа<br>Вернуться к началу платежа<br>На главную<br>К разделам сайта</div>", end_html; } else { sleep(2); print header, start_html, end_html; } dbmclose %pay; exit; И наконец маленькая программа, позволяющая просматривать содержимое нашего хэша (лог файла продаж) через браузер. Необходимо поместить файл программы в папку (директорию) и установить пароль для папки. #!/usr/bin/perl -s use CGI qw/-no_xhtml :standard/;
my %pay; my $file_c = "../../log/pay"; dbmopen(%pay, $file_c, 0640) or die "Can't open $file_c: $!\n";
Некоторые замечания по безопасности и отладке программ 1. Необходимо запретить загрузку файлов в модуле CGI и ограничить размер данных, передаваемых программе, т.е.:
Цитата
$CGI::POST_MAX=1024 * 2; # max 2K posts $CGI::DISABLE_UPLOADS = 1; # no uploads
2. Для корректного выполнения программ с прагмой strict (use strict;) необходимо присваивать значение данных получаемой формы переменной, а лишь затем выполнять проверку уже переменной, например
Цитата
my $a = param('LMI_SYS_TRANS_DATE'); if ( $a eq и т.д.
3. Необходимо проверять каждое получаемое значение на соответствие формату этого типа значения, а затем на равенство значений (строк, чисел). Например,
Цитата
my $b = param('LMI_PAYEE_PURSE'); if ( $b =~ /^([ZRE]\d{12})$/ && $1 eq ‘R111111111111’ ) { # теперь то что нужно
4. Необходимо добиться работы программ в режиме «меченых данных», то есть использовать ключ –T (#!/usr/bin/perl –T). Дополняя предыдущий пример
Цитата
my $b = param('LMI_PAYEE_PURSE'); if ( $b =~ /^([ZRE]\d{12})$/ && $1 eq ‘R111111111111’ ) { $b = $1; # теперь совсем правильно, в $b - номер кошелька
5. Выводить сообщения об ошибках на экран удобно конечно, но и записывать в лог файл тоже нужно, например так:
Цитата
use CGI::Carp qw(fatalsToBrowser carpout); open(EERR, ">>../../log/eerr-cgi") or die "Can't append to eerr-cgi: $!\n"; carpout(*EERR);
6. При большом количестве запросов с ошибочными данными имеет смысл увеличивать по нарастающей время отклика нашей программы для данного хоста, IP определяем так my
Счастье есть - его не может не быть. Не бойся смерти - бойся непрожитой жизни. Как бы не было плохо, нужно расслабиться и получать удовольствие, а если хорошо, то тем более. Мозги надо менять а не конституцию.
Это мы то нубы?! Да я вас всех продам, куплю, а потом еще раз продам но уже дороже!!!
<> ужос...сегодня впервые за пару месяцев на ночь выключил комп....... <> еле в этой долбаной тишине уснул.......