Продажа товара на сайте за WM, программы приема платежей на Perl. Часть 2
     
 
Бланки
для
предпринимателей
Для Вас
на сайте:

blankinew.narod.ru
 
     
       
   

Продажа товара на сайте за 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 header(-charset => 'windows-1251',
-expires=>'+30s'),
start_html('Форма запроса платежа');

# Прежде всего следует слегка похвалить наш товар!
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";

print header(-charset => 'windows-1251'),
start_html('Просмотр хэша (лог файла продаж)');

my @pay_tmp = keys %pay; print "Продажа № $_<br>$pay{$_}<br>\n" foreach 10..$#pay_tmp;

print end_html;
dbmclose %pay;
exit;

Некоторые замечания по безопасности и отладке программ

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 $host = remote_host().
7. И наступит счастье.
Перейти к принципам работы программ и настройке параметров приема платежей

   
Дмитрий Рассанов
2014