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;