package payments::Provider::PayTure;
use strict;
use warnings 'all';
use base 'payments::Provider::Base';
use Contenido::Globals;
use payments::Keeper;
use Digest::MD5;
use Data::Dumper;
use URI;
use URI::QueryParam;
use XML::Fast;
our $init_url = '';
sub new {
my ($proto, %params) = @_;
my $class = ref($proto) || $proto;
my $self = {};
my $prefix = $class =~ /\:\:(\w+)$/ ? lc($1) : undef;
return unless $prefix;
$self->{payment_system} = $prefix;
$self->{app_id} = $state->{payments}->{$prefix."_app_id"};
$self->{secret} = $state->{payments}->{$prefix."_app_secret"};
$self->{currency} = $state->{payments}->{$prefix."_currency_code"};
$self->{test_mode} = $state->{payments}->{$prefix."_test_mode"};
my $host = $self->{test_mode} ? 'sandbox' : 'secure';
$self->{api} = {
init => "https://$host.payture.com/apim/Init",
pay => "https://$host.payture.com/apim/Pay",
status => "https://$host.payture.com/apim/PayStatus",
};
$self->{result} = {};
bless $self, $class;
return $self;
}
sub init {
my $self = shift;
my (%opts) = @_;
warn "PayTure Init: ".Dumper(\%opts) if $DEBUG;
### Session Type: Pay or Block
my $session_type = $opts{session_type} || 'Pay';
my $order_id = $opts{order_id};
unless ( $order_id ) {
$self->{result}{error} = 'Не указан order_id';
return $self;
}
my $uid = $opts{uid};
unless ( $uid ) {
$self->{result}{error} = 'Не указан user id';
return $self;
}
### Сумма в копейках. Если дробное - преобразуем
my $total;
my $sum = $opts{sum};
if ( !$sum || $sum !~ /^[\d\,\.]+$/ ) {
$self->{result}{error} = 'Не указана или неправильно указана сумма транзакции';
return $self;
}
$sum =~ s/\,/\./;
$total = sprintf("%.02f", $sum);
$sum = int($sum * 100);
my $operation = $keeper->get_documents(
class => 'payments::Operation',
status => $state->{payments}->{payture_test_mode},
order_id => $order_id,
order_by => 'ctime',
return_mode => 'array_ref',
);
if ( ref $operation eq 'ARRAY' && @$operation ) {
my $last = $operation->[-1];
if ( $last->name eq 'suspend' || $last->name eq 'cancel' || $last->name eq 'close' ) {
$self->{result}{error} = 'Заказ закрыт, отменен или заморожен. Оплата по нему невозможна';
return $self;
} else {
$operation = $last;
}
} else {
$operation = payments::Operation->new( $keeper );
$operation->status( $state->{payments}->{payture_test_mode} );
$operation->name( 'create' );
$operation->order_id( $order_id );
$operation->uid( $uid );
$operation->sum( $sum );
$operation->store;
}
my $ip = $opts{ip};
unless ( $ip && $ip =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ ) {
$self->{result}{error} = 'Неверный IP-адрес';
return $self;
}
warn "IP: $ip\n" if $DEBUG;
### Пример: http://yoursite.com/result?orderid={orderid}&result={success}
my $url = $opts{url};
my $template = $opts{teplate};
my $lang = $opts{lang};
my $params = $opts{params};
my ($transaction) = $keeper->get_documents(
class => 'payments::Transaction',
status => $state->{payments}->{payture_test_mode},
limit => 1,
order_id => $order_id,
provider => $self->{payment_system},
);
if ( ref $transaction ) {
### Init already exists
$self->{result}{success} = 1;
$self->{result}{session_id} = $transaction->session_id;
$self->{result}{transaction} = $transaction;
} else {
my $ua = LWP::UserAgent->new;
if ( $DEBUG ) {
my $is_https = $ua->is_protocol_supported( 'https' );
warn "PayTure Init: protocol 'https' is $is_https\n";
}
$ua->agent('Mozilla/5.0');
my $req = URI->new( $self->{api}{init} );
my @data = ( "SessionType=$session_type", "OrderId=$order_id", "Amount=$sum", "IP=$ip");
push @data, "Url=$url" if $url;
push @data, "TemplateTag=$template" if $template;
push @data, "Url=$url" if $url;
push @data, "Language=$lang" if $lang;
push @data, "Total=$total" unless exists $params->{Total};
if ( ref $params eq 'HASH' ) {
while ( my ($param, $val) = each %$params ) {
push @data, "$param=$val";
}
}
my $data_unescaped = join ';', @data;
warn Dumper "PayTure Init data: $data_unescaped\n" if $DEBUG;
my $data_str = URI::Escape::uri_escape( $data_unescaped );
warn "PayTure Init query: ".Dumper($req) if $DEBUG;
my $result = $ua->post( $req, Content => { Key => $self->id, Data => $data_str } );
my $return_data = {};
if ( $result->code == 200 ) {
warn "PayTure Init result: [".$result->content."]\n" if $DEBUG;
my $content = xml2hash $result->content;
warn Dumper $content if $DEBUG;
if ( ref $content && exists $content->{'Init'} && exists $content->{'Init'}{'-Success'} ) {
if ( $content->{'Init'}{'-Success'} eq 'True' ) {
my $now = Contenido::DateTime->new;
my $transaction = payments::Transaction->new( $keeper );
$transaction->dtime( $now->ymd('-').' '.$now->hms );
$transaction->provider( $self->{payment_system} );
$transaction->session_id( $content->{'Init'}{'-SessionId'} );
$transaction->status( $state->{payments}->{payture_test_mode} );
$transaction->order_id( $order_id );
$transaction->operation_id( $operation->id );
$transaction->currency_code( 'RUR' );
$transaction->sum( $sum );
$transaction->name( 'Init' );
$transaction->store;
$self->{result}{success} = 1;
$self->{result}{session_id} = $content->{'Init'}{'-SessionId'};
$self->{result}{transaction} = $transaction;
} else {
$self->{result}{error} = $content->{'Init'}{'-ErrCode'};
}
} else {
$self->{result}{error} = 'PayTure Init failed';
$self->{result}{responce} = $content;
warn $self->{result}{error}."\n";
warn "[$content]\n";
}
} else {
$self->{result}{error} = 'PayTure Init failed';
$self->{result}{responce} = $result->status_line;
warn $self->{result}{error}.": ".$result->status_line."\n";
warn Dumper $result;
}
}
return $self;
}
sub pay {
my $self = shift;
my (%opts) = @_;
unless ( exists $self->{result} ) {
$self->init( %opts );
}
if ( $self->{result}{success} ) {
return $self->{api}{pay}.'?SessionId='.$self->{result}{session_id};
}
return undef;
}
sub status {
my $self = shift;
my (%opts) = @_;
warn "PayTure Status: ".Dumper(\%opts) if $DEBUG;
my $order_id = $opts{order_id};
unless ( $order_id ) {
$self->{result} = { error => 'Не указан order_id' };
return $self;
}
my ($transaction) = exists $self->{result}{transaction} ? ($self->{result}{transaction}) : $keeper->get_documents(
class => 'payments::Transaction',
status => $state->{payments}->{payture_test_mode},
limit => 1,
order_id => $order_id,
provider => $self->{payment_system},
);
if ( ref $transaction ) {
my $ua = LWP::UserAgent->new;
$ua->agent('Mozilla/5.0');
my $req = URI->new( $self->{api}{status} );
my $result = $ua->post( $req, Content => { Key => $self->id, OrderId => $order_id } );
my $return_data = {};
if ( $result->code == 200 ) {
warn "PayTure Status result: [".$result->content."]\n" if $DEBUG;
my $content = xml2hash $result->content;
warn Dumper $content if $DEBUG;
if ( ref $content && exists $content->{'PayStatus'} && exists $content->{'PayStatus'}{'-Success'} ) {
if ( $content->{'PayStatus'}{'-Success'} eq 'True' ) {
$self->{result} = {
success => 1,
status => $content->{'PayStatus'}{'-State'},
amount => $content->{'PayStatus'}{'-Amount'},
transaction => $transaction,
};
} else {
$self->{result} = {
error => $content->{'PayStatus'}{'-ErrCode'},
transaction => $transaction,
};
}
} else {
$self->{result}{error} = 'PayTure Status failed';
$self->{result}{responce} = $content;
warn $self->{result}{error}."\n";
warn "[$content]\n";
}
} else {
$self->{result}{error} = 'PayTure Status failed';
$self->{result}{responce} = $result->status_line;
warn $self->{result}{error}.": ".$result->status_line."\n";
warn Dumper $result;
}
} else {
$self->{result}{error} = "Не найдена транзакция для order_id=$order_id";
return $self;
}
return $self;
}
1;