1 |
525 |
ahitrov |
package payments::Provider::PayTure; |
2 |
|
|
|
3 |
|
|
use strict; |
4 |
|
|
use warnings 'all'; |
5 |
|
|
|
6 |
|
|
use base 'payments::Provider::Base'; |
7 |
|
|
use Contenido::Globals; |
8 |
|
|
use payments::Keeper; |
9 |
|
|
use Digest::MD5; |
10 |
|
|
use Data::Dumper; |
11 |
|
|
use URI; |
12 |
|
|
use URI::QueryParam; |
13 |
554 |
ahitrov |
use XML::Fast; |
14 |
525 |
ahitrov |
|
15 |
|
|
our $init_url = ''; |
16 |
|
|
|
17 |
|
|
sub new { |
18 |
|
|
my ($proto, %params) = @_; |
19 |
|
|
my $class = ref($proto) || $proto; |
20 |
|
|
my $self = {}; |
21 |
|
|
my $prefix = $class =~ /\:\:(\w+)$/ ? lc($1) : undef; |
22 |
|
|
return unless $prefix; |
23 |
|
|
|
24 |
|
|
$self->{payment_system} = $prefix; |
25 |
|
|
$self->{app_id} = $state->{payments}->{$prefix."_app_id"}; |
26 |
|
|
$self->{secret} = $state->{payments}->{$prefix."_app_secret"}; |
27 |
|
|
$self->{currency} = $state->{payments}->{$prefix."_currency_code"}; |
28 |
|
|
$self->{test_mode} = $state->{payments}->{$prefix."_test_mode"}; |
29 |
|
|
|
30 |
556 |
ahitrov |
my $host = $self->{test_mode} ? 'sandbox' : 'secure'; |
31 |
|
|
$self->{api} = { |
32 |
|
|
init => "https://$host.payture.com/apim/Init", |
33 |
|
|
pay => "https://$host.payture.com/apim/Pay", |
34 |
|
|
status => "https://$host.payture.com/apim/PayStatus", |
35 |
554 |
ahitrov |
}; |
36 |
|
|
$self->{result} = {}; |
37 |
525 |
ahitrov |
|
38 |
|
|
bless $self, $class; |
39 |
|
|
|
40 |
|
|
return $self; |
41 |
|
|
} |
42 |
|
|
|
43 |
|
|
sub init { |
44 |
|
|
my $self = shift; |
45 |
|
|
my (%opts) = @_; |
46 |
|
|
|
47 |
529 |
ahitrov |
warn "PayTure Init: ".Dumper(\%opts) if $DEBUG; |
48 |
525 |
ahitrov |
|
49 |
529 |
ahitrov |
### Session Type: Pay or Block |
50 |
|
|
my $session_type = $opts{session_type} || 'Pay'; |
51 |
|
|
|
52 |
|
|
my $order_id = $opts{order_id}; |
53 |
554 |
ahitrov |
unless ( $order_id ) { |
54 |
|
|
$self->{result}{error} = 'Не указан order_id'; |
55 |
|
|
return $self; |
56 |
|
|
} |
57 |
529 |
ahitrov |
|
58 |
554 |
ahitrov |
my $uid = $opts{uid}; |
59 |
|
|
unless ( $uid ) { |
60 |
|
|
$self->{result}{error} = 'Не указан user id'; |
61 |
|
|
return $self; |
62 |
|
|
} |
63 |
|
|
|
64 |
529 |
ahitrov |
### Сумма в копейках. Если дробное - преобразуем |
65 |
554 |
ahitrov |
my $total; |
66 |
529 |
ahitrov |
my $sum = $opts{sum}; |
67 |
554 |
ahitrov |
if ( !$sum || $sum !~ /^[\d\,\.]+$/ ) { |
68 |
|
|
$self->{result}{error} = 'Не указана или неправильно указана сумма транзакции'; |
69 |
|
|
return $self; |
70 |
525 |
ahitrov |
} |
71 |
554 |
ahitrov |
$sum =~ s/\,/\./; |
72 |
|
|
$total = sprintf("%.02f", $sum); |
73 |
|
|
$sum = int($sum * 100); |
74 |
525 |
ahitrov |
|
75 |
554 |
ahitrov |
my $operation = $keeper->get_documents( |
76 |
|
|
class => 'payments::Operation', |
77 |
|
|
status => $state->{payments}->{payture_test_mode}, |
78 |
|
|
order_id => $order_id, |
79 |
|
|
order_by => 'ctime', |
80 |
|
|
return_mode => 'array_ref', |
81 |
|
|
); |
82 |
|
|
if ( ref $operation eq 'ARRAY' && @$operation ) { |
83 |
|
|
my $last = $operation->[-1]; |
84 |
564 |
ahitrov |
if ( $last->name eq 'suspend' || $last->name eq 'cancel' || $last->name eq 'close' ) { |
85 |
|
|
$self->{result}{error} = 'Заказ закрыт, отменен или заморожен. Оплата по нему невозможна'; |
86 |
554 |
ahitrov |
return $self; |
87 |
|
|
} else { |
88 |
|
|
$operation = $last; |
89 |
|
|
} |
90 |
|
|
} else { |
91 |
|
|
$operation = payments::Operation->new( $keeper ); |
92 |
|
|
$operation->status( $state->{payments}->{payture_test_mode} ); |
93 |
|
|
$operation->name( 'create' ); |
94 |
|
|
$operation->order_id( $order_id ); |
95 |
|
|
$operation->uid( $uid ); |
96 |
|
|
$operation->sum( $sum ); |
97 |
|
|
$operation->store; |
98 |
|
|
} |
99 |
|
|
|
100 |
529 |
ahitrov |
my $ip = $opts{ip}; |
101 |
554 |
ahitrov |
unless ( $ip && $ip =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ ) { |
102 |
|
|
$self->{result}{error} = 'Неверный IP-адрес'; |
103 |
|
|
return $self; |
104 |
|
|
} |
105 |
|
|
warn "IP: $ip\n" if $DEBUG; |
106 |
525 |
ahitrov |
|
107 |
529 |
ahitrov |
### Пример: http://yoursite.com/result?orderid={orderid}&result={success} |
108 |
|
|
my $url = $opts{url}; |
109 |
525 |
ahitrov |
|
110 |
529 |
ahitrov |
my $template = $opts{teplate}; |
111 |
|
|
my $lang = $opts{lang}; |
112 |
|
|
my $params = $opts{params}; |
113 |
|
|
|
114 |
554 |
ahitrov |
my ($transaction) = $keeper->get_documents( |
115 |
|
|
class => 'payments::Transaction', |
116 |
|
|
status => $state->{payments}->{payture_test_mode}, |
117 |
|
|
limit => 1, |
118 |
|
|
order_id => $order_id, |
119 |
|
|
provider => $self->{payment_system}, |
120 |
|
|
); |
121 |
|
|
if ( ref $transaction ) { |
122 |
|
|
### Init already exists |
123 |
|
|
$self->{result}{success} = 1; |
124 |
|
|
$self->{result}{session_id} = $transaction->session_id; |
125 |
|
|
$self->{result}{transaction} = $transaction; |
126 |
|
|
} else { |
127 |
|
|
my $ua = LWP::UserAgent->new; |
128 |
556 |
ahitrov |
if ( $DEBUG ) { |
129 |
|
|
my $is_https = $ua->is_protocol_supported( 'https' ); |
130 |
|
|
warn "PayTure Init: protocol 'https' is $is_https\n"; |
131 |
|
|
} |
132 |
554 |
ahitrov |
$ua->agent('Mozilla/5.0'); |
133 |
|
|
my $req = URI->new( $self->{api}{init} ); |
134 |
|
|
my @data = ( "SessionType=$session_type", "OrderId=$order_id", "Amount=$sum", "IP=$ip"); |
135 |
|
|
push @data, "Url=$url" if $url; |
136 |
|
|
push @data, "TemplateTag=$template" if $template; |
137 |
|
|
push @data, "Url=$url" if $url; |
138 |
|
|
push @data, "Language=$lang" if $lang; |
139 |
|
|
push @data, "Total=$total" unless exists $params->{Total}; |
140 |
|
|
if ( ref $params eq 'HASH' ) { |
141 |
|
|
while ( my ($param, $val) = each %$params ) { |
142 |
|
|
push @data, "$param=$val"; |
143 |
|
|
} |
144 |
525 |
ahitrov |
} |
145 |
554 |
ahitrov |
my $data_unescaped = join ';', @data; |
146 |
|
|
warn Dumper "PayTure Init data: $data_unescaped\n" if $DEBUG; |
147 |
|
|
my $data_str = URI::Escape::uri_escape( $data_unescaped ); |
148 |
|
|
warn "PayTure Init query: ".Dumper($req) if $DEBUG; |
149 |
|
|
|
150 |
|
|
my $result = $ua->post( $req, Content => { Key => $self->id, Data => $data_str } ); |
151 |
|
|
my $return_data = {}; |
152 |
|
|
if ( $result->code == 200 ) { |
153 |
|
|
warn "PayTure Init result: [".$result->content."]\n" if $DEBUG; |
154 |
|
|
my $content = xml2hash $result->content; |
155 |
|
|
warn Dumper $content if $DEBUG; |
156 |
|
|
if ( ref $content && exists $content->{'Init'} && exists $content->{'Init'}{'-Success'} ) { |
157 |
|
|
if ( $content->{'Init'}{'-Success'} eq 'True' ) { |
158 |
|
|
my $now = Contenido::DateTime->new; |
159 |
|
|
my $transaction = payments::Transaction->new( $keeper ); |
160 |
|
|
$transaction->dtime( $now->ymd('-').' '.$now->hms ); |
161 |
|
|
$transaction->provider( $self->{payment_system} ); |
162 |
|
|
$transaction->session_id( $content->{'Init'}{'-SessionId'} ); |
163 |
|
|
$transaction->status( $state->{payments}->{payture_test_mode} ); |
164 |
|
|
$transaction->order_id( $order_id ); |
165 |
|
|
$transaction->operation_id( $operation->id ); |
166 |
|
|
$transaction->currency_code( 'RUR' ); |
167 |
|
|
$transaction->sum( $sum ); |
168 |
|
|
$transaction->name( 'Init' ); |
169 |
|
|
$transaction->store; |
170 |
|
|
|
171 |
|
|
$self->{result}{success} = 1; |
172 |
|
|
$self->{result}{session_id} = $content->{'Init'}{'-SessionId'}; |
173 |
|
|
$self->{result}{transaction} = $transaction; |
174 |
|
|
} else { |
175 |
|
|
$self->{result}{error} = $content->{'Init'}{'-ErrCode'}; |
176 |
|
|
} |
177 |
|
|
} else { |
178 |
|
|
$self->{result}{error} = 'PayTure Init failed'; |
179 |
|
|
$self->{result}{responce} = $content; |
180 |
|
|
warn $self->{result}{error}."\n"; |
181 |
|
|
warn "[$content]\n"; |
182 |
|
|
} |
183 |
|
|
} else { |
184 |
|
|
$self->{result}{error} = 'PayTure Init failed'; |
185 |
|
|
$self->{result}{responce} = $result->status_line; |
186 |
|
|
warn $self->{result}{error}.": ".$result->status_line."\n"; |
187 |
|
|
warn Dumper $result; |
188 |
|
|
} |
189 |
525 |
ahitrov |
} |
190 |
554 |
ahitrov |
return $self; |
191 |
|
|
} |
192 |
529 |
ahitrov |
|
193 |
554 |
ahitrov |
sub pay { |
194 |
|
|
my $self = shift; |
195 |
|
|
my (%opts) = @_; |
196 |
|
|
|
197 |
|
|
unless ( exists $self->{result} ) { |
198 |
|
|
$self->init( %opts ); |
199 |
|
|
} |
200 |
|
|
if ( $self->{result}{success} ) { |
201 |
|
|
return $self->{api}{pay}.'?SessionId='.$self->{result}{session_id}; |
202 |
|
|
} |
203 |
|
|
return undef; |
204 |
|
|
} |
205 |
|
|
|
206 |
|
|
sub status { |
207 |
|
|
my $self = shift; |
208 |
|
|
my (%opts) = @_; |
209 |
|
|
|
210 |
|
|
warn "PayTure Status: ".Dumper(\%opts) if $DEBUG; |
211 |
|
|
|
212 |
|
|
my $order_id = $opts{order_id}; |
213 |
|
|
unless ( $order_id ) { |
214 |
|
|
$self->{result} = { error => 'Не указан order_id' }; |
215 |
|
|
return $self; |
216 |
|
|
} |
217 |
|
|
|
218 |
|
|
my ($transaction) = exists $self->{result}{transaction} ? ($self->{result}{transaction}) : $keeper->get_documents( |
219 |
|
|
class => 'payments::Transaction', |
220 |
|
|
status => $state->{payments}->{payture_test_mode}, |
221 |
|
|
limit => 1, |
222 |
|
|
order_id => $order_id, |
223 |
|
|
provider => $self->{payment_system}, |
224 |
|
|
); |
225 |
|
|
if ( ref $transaction ) { |
226 |
|
|
my $ua = LWP::UserAgent->new; |
227 |
|
|
$ua->agent('Mozilla/5.0'); |
228 |
|
|
my $req = URI->new( $self->{api}{status} ); |
229 |
|
|
my $result = $ua->post( $req, Content => { Key => $self->id, OrderId => $order_id } ); |
230 |
|
|
my $return_data = {}; |
231 |
|
|
if ( $result->code == 200 ) { |
232 |
|
|
warn "PayTure Status result: [".$result->content."]\n" if $DEBUG; |
233 |
|
|
my $content = xml2hash $result->content; |
234 |
|
|
warn Dumper $content if $DEBUG; |
235 |
|
|
if ( ref $content && exists $content->{'PayStatus'} && exists $content->{'PayStatus'}{'-Success'} ) { |
236 |
|
|
if ( $content->{'PayStatus'}{'-Success'} eq 'True' ) { |
237 |
|
|
$self->{result} = { |
238 |
|
|
success => 1, |
239 |
|
|
status => $content->{'PayStatus'}{'-State'}, |
240 |
|
|
amount => $content->{'PayStatus'}{'-Amount'}, |
241 |
|
|
transaction => $transaction, |
242 |
|
|
}; |
243 |
|
|
} else { |
244 |
|
|
$self->{result} = { |
245 |
|
|
error => $content->{'PayStatus'}{'-ErrCode'}, |
246 |
|
|
transaction => $transaction, |
247 |
|
|
}; |
248 |
|
|
} |
249 |
|
|
} else { |
250 |
|
|
$self->{result}{error} = 'PayTure Status failed'; |
251 |
|
|
$self->{result}{responce} = $content; |
252 |
|
|
warn $self->{result}{error}."\n"; |
253 |
|
|
warn "[$content]\n"; |
254 |
|
|
} |
255 |
|
|
} else { |
256 |
|
|
$self->{result}{error} = 'PayTure Status failed'; |
257 |
|
|
$self->{result}{responce} = $result->status_line; |
258 |
|
|
warn $self->{result}{error}.": ".$result->status_line."\n"; |
259 |
|
|
warn Dumper $result; |
260 |
|
|
} |
261 |
529 |
ahitrov |
} else { |
262 |
554 |
ahitrov |
$self->{result}{error} = "Не найдена транзакция для order_id=$order_id"; |
263 |
|
|
return $self; |
264 |
529 |
ahitrov |
} |
265 |
554 |
ahitrov |
|
266 |
|
|
return $self; |
267 |
525 |
ahitrov |
} |
268 |
|
|
|
269 |
|
|
1; |