Моментальные платежи из Сбербанка в LANBilling

Сбербанк разработал собственную систему для работы с ПУ (платежное устройство) используя API на стороне клиента (оператора связи).

Основное это perl-скрипт, с помощью которого осуществляется взаимодействие.

Алгоритм:

  1. Абонент вводит лицевой счет (логин).
  2. Сбербанк запрашивает ФИО и необходимую сумму платежа.
  3. Сервер выдает информацию по логину.
  4. Сбербанк формирует форму с данными.
  5. Абонент подтверждает платеж.
  6. Сбербанк передает данные о платеже.
  7. Скрипт обрабатывает платеж, вносит информацию в БД.

 

Скрипт:

#!/usr/bin/perl
use lib '/usr/local/billing/payments';
use strict;
use CGI;
use LB;
use POSIX;
use Time::localtime;
use Time::Local;
use DBI;
use utf8;
############## ! fix me ! ###############
my $lbcore_host = 'localhost'; # IP сервера LBcore
my $manager_login = 'sberbank'; # логин менеджера АСР
my $manager_pass = 'XXXXXXXXXXXXXXXXXXXXXXXXXXX'; # пароль менеджера АСР
my $type = TYPE_USER_LOGIN; # Тип идентификатора
#########################################
# Список примерных расшифровок ответов для некоторых кодов возврата
my %result_messages = (
0 => 'OK',
1 => 'Временная ошибка. Повторите запрос позже.',
4 => 'Неверный формат идентификатора абонента',
5 => 'Идентификатор абонента не найден',
7 => 'Прием платежа запрещен провайдером',
8 => 'Прием платежа запрещен по техническим причинам',
79 => 'Счет абонента не активен',
241 => 'Сумма слишком мала',
242 => 'Сумма слишком велика',
243 => 'Невозможно проверить состояние счета',
300 => 'Ошибка провайдера'
);
my $balance;
my $login;
# Некоторые входные параметры скрипта
my $command = CGI::param ( 'command' ) || ''; # Тип запроса
my $account = CGI::param ( 'account' ) || ''; # Номер абонента
my $txn_id = CGI::param ( 'txn_id' ) || ''; # Номер платежа (уник. для внешней системы)
my $amount = CGI::param ( 'sum' ) || ''; # Сумма платежа
# Информация, возвращаемая скриптом
my $result_code = 0; # Код ответа
my $prv_txn = ''; # Номер платежа в системе провайдера
#-------------------------------------------------------------------------------
if($command ne 'check_balance')
{
if (($command eq '') || ($account eq '') || ($txn_id eq '') || ($amount eq ''))
{
print_response($amount, 300);
exit;
}
}
#-------------------------------------------------------------------------------
my $pay = new LB(host => $lbcore_host, user => $manager_login, pass => $manager_pass);
my $r;
if ( $command eq 'check' || $command eq 'check_balance')
{
# Тип запроса - проверка номера абонента для платежа
#if ( $account !~ /^\d+$/ )
#{
# Неверное значение № абонента (синтаксис)
# $result_code = 4;
#}
#else
#{
$pay->connect || tmp_error();
$result_code = 300;
#($r, $balance) = $pay->check( 'number' => $account, 'type' => $type, 'fetch_balance' => 'yes' );
($r, $balance, $login) = $pay->check( 'number' => $account, 'type' => $type, 'fetch_balance' => 'yes', 'fetch_param' => TYPE_FIO);
$result_code = 0 if $r == 0;
$result_code = 243 if $r == -2;
$result_code = 5 if $r == 2;
$result_code = 1 if $r == 11;
$pay->disconnect;
#}
}
elsif ( $command eq 'pay' )
{
# Тип запроса - проведение платежа
my $date = CGI::param ( 'txn_date' ); # Дата и время платежа (внешней системы)
#if ( $account !~ /^\d+$/ )
#{
# Неверное значение № абонента (синтаксис)
$result_code = 4;
#}
#elsif ( $amount !~ /^\d+\.\d{0,2}$/ )
if ( $amount !~ /^\d+\.\d{0,2}$/ )
{
# Неверное значение суммы (синтаксис)
$result_code = 7;
}
elsif ( $amount <=0 )
{
$result_code = 241;
}
elsif ( $txn_id !~ /^\d+$/ )
{
# Неверное значение номера платежа (синтаксис)
$result_code = 7;
}
else
{
my $timestamp;
my $amountcurr;
if ($date =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/)
{
$result_code = 300;
$pay->connect || tmp_error();
$timestamp = mktime($6,$5,$4,$3,$2-1,$1-1900,0,0,-1);
( $r, $prv_txn, $amountcurr )
= $pay->payment (
'number' => $account,
'type' => $type,
'amount' => $amount,
'receipt' => $txn_id,
'date' => $timestamp,
);
$amount = $amountcurr if $r == 12;
$result_code = 0 if (($r == 0)||($r == 12));
$result_code = 243 if $r == -2;
$result_code = 5 if $r == 2;
$result_code = 1 if $r == 11;
$result_code = 1 if !defined($prv_txn) || ($prv_txn eq '');
$pay->disconnect;
}
else
{
# Неверное значение даты (синтаксис)
$result_code = 7;
}
}
}
else
{
# Неизвестный тип запроса
$result_code = 300;
}
my $vnesti=&test($account);
print_response($amount,$result_code, $balance,$login,$vnesti);
exit;
################################################################################
sub print_response
{
my ($amount,$result,$balance,$login,$vnesti) = @_;
my $msg = $result_messages{$result};
print "Content-type: text/xml\n\n";
print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
print "<response>\n";
if ( $command ne 'check_balance' )
{
print "<osmp_txn_id>$txn_id</osmp_txn_id>\n";
}
if ( $command eq 'pay')
{
print "<prv_txn>$prv_txn</prv_txn>\n";
print "<sum>$amount</sum>\n";
}
if ( $command eq 'check_balance' )
{
print "<login>$login</login>\n";
print "<curr_balance>$balance</curr_balance>\n";
print "<vnesti>$vnesti</vnesti>\n";
}
print "<result>$result</result>\n";
print "<comment>$msg</comment>\n";
print "</response>\n";
}
################################################################################
sub tmp_error
{
print_response('',1);
die("Can't connect to LBcore\n");
}
sub test () {
my $dToDay = localtime(time);
my $sNextMon = ($dToDay->year()+int(($dToDay->mon+1)/12) + 1900) . ((($dToDay->mon+1)%12)+1) . "01";
my $db_server = 'localhost';
my $db_login = 'login';
my $db_pass = 'Pa55W0rD';
my $db_name = 'billing';
my($dbh,$query,$result);
$dbh = DBI->connect('DBI:mysql:database='.$db_name.';host='.$db_server, $db_login, $db_pass)
|| print('Не могу подключиться к серверу баз данных: '.$DBI::errstr).die();
$query = ("SELECT ifnull(CEIL( t.rent - ag.balance + ifnull( z.`above` , 0 )),0) AS vnesti
FROM `vgroups` AS v
LEFT JOIN (SELECT s.`vg_id` , serc.`above` , serc.`descr` 
FROM `services` AS s, service_categories AS serc
WHERE s.`need_calc`=1 AND s.`tar_id` = serc.`tar_id` 
AND s.`serv_cat_idx` = serc.`serv_cat_idx` )z ON v.`vg_id` = z.`vg_id` 
LEFT JOIN accounts AS a ON a.uid = v.uid
LEFT JOIN accounts_addons_vals AS f ON a.uid = f.uid
LEFT JOIN agreements AS ag ON ag.uid = v.uid AND ag.agrm_id = v.agrm_id
LEFT JOIN tarifs AS t ON t.tar_id = v.tar_id
WHERE a.login='$account' AND balance < rent AND v.archive <>1
AND v.`vg_id` NOT IN (SELECT `vg_id` FROM `tarifs_rasp` WHERE `change_time`=$sNextMon)
UNION
SELECT ifnull(CEIL( q.rent - ag.balance + ifnull( z.`above` , 0 )),0) AS vnesti
FROM `vgroups` AS v
LEFT JOIN (SELECT s.`vg_id` , serc.`above` , serc.`descr` 
FROM `services` AS s, service_categories AS serc
WHERE s.`need_calc`=1 AND s.`tar_id` = serc.`tar_id` AND s.`serv_cat_idx` = serc.`serv_cat_idx` )z ON v.`vg_id` = z.`vg_id` 
LEFT JOIN accounts AS a ON a.uid = v.uid
LEFT JOIN accounts_addons_vals AS f ON a.uid = f.uid
LEFT JOIN agreements AS ag ON ag.uid = v.uid AND ag.agrm_id = v.agrm_id
LEFT JOIN (SELECT tr.`vg_id`,tr.`tar_id_new`, t.descr, t.rent,tr.`change_time`
FROM `tarifs_rasp` as tr, tarifs as t
WHERE tr.`tar_id_new`=t.tar_id and tr.`change_time`=$sNextMon )q ON v.`vg_id` = q.`vg_id`
WHERE a.login='$account' AND balance < rent AND v.archive <>1 
AND q.`change_time`=$sNextMon
ORDER BY 1");
$result = $dbh->prepare($query);
$result->execute()
|| print('Не могу выполнить запрос ('.$query.'): '.$DBI::errstr).die();
$a=($result->fetchrow_array());
if ($a == undef) {$a=0;}
$dbh->disconnect();
return $a;
}

Данный скрипт работает с LANBilling версии 2.0 сборка № 18. К данному скрипту также нужны библиотеки.
Софт установлен в каталог

/usr/local/billing/

Соответственно туда же ссылаются

use lib '/usr/local/billing/payments';

Также используются другие необходимые модули, которые должны быть установлены на сервере, и библиотеки:

use strict;
use CGI;
use LB;
use POSIX;
use Time::localtime;
use Time::Local;
use DBI;
use utf8;

LB.pm
Парсер реестра платежей из формата Сбербанка в формат АСР LANBilling