Revision 554
Date:
2016/03/16 22:57:06
Author:
ahitrov
Revision Log:
Payture InPay gate API (init + pay) & Notification backend
Files:
Legend:
Added
Removed
Modified
utf8/plugins/payments/comps/www/payment.backend/payture_pay.html
1
<%once>
2
3
my %NAMES = (
4
'MerchantBlock' => 'Authorized',
5
'MerchantPay' => 'Charged',
6
'MerchantRefund' => 'Refunded',
7
);
8
9
</%once>
10
<%args>
11
12
$SessionId => undef
13
$OrderId => undef
14
$Notification => undef
15
$Success => undef
16
$Amount => undef
17
$CardNumber => undef
18
$MerchantContract => undef
19
20
</%args>
21
<%init>
22
23
warn Dumper \%ARGS;
24
25
my $provider = payments::Provider::PayTure->new;
26
my (@operations, $transaction);
27
if ( $OrderId ) {
28
@operations = $keeper->get_documents(
29
class => 'payments::Operation',
30
order_id => $OrderId,
31
order_by => 'ctime',
32
);
33
($transaction) = $keeper->get_documents (
34
class => 'payments::Transaction',
35
provider => $provider->payment_system,
36
order_id => $OrderId,
37
order_by => 'ctime desc',
38
limit => 1,
39
);
40
}
41
if ( ref $transaction ) {
42
$transaction->name( $NAMES{$Notification} );
43
$transaction->success( $Success eq 'True' ? 1 : 0 );
44
$transaction->store;
45
46
if ( $keeper->can('_payture_handler') ) {
47
$keeper->_payture_handler( $transaction );
48
}
49
}
50
51
</%init>
utf8/plugins/payments/lib/payments/Provider/PayTure.pm
10
10
use Data::Dumper;
11
11
use URI;
12
12
use URI::QueryParam;
13
use XML::Fast;
13
14
14
15
our $init_url = '';
15
16
…
…
26
27
$self->{currency} = $state->{payments}->{$prefix."_currency_code"};
27
28
$self->{test_mode} = $state->{payments}->{$prefix."_test_mode"};
28
29
29
$self->{api}{init} = 'https://sandbox.payture.com/apim/Init';
30
$self->{api} = $self->{test_mode} ?
31
{
32
init => 'https://sandbox.payture.com/apim/Init',
33
pay => 'https://sandbox.payture.com/apim/Pay',
34
status => 'https://sandbox.payture.com/apim/PayStatus',
35
} : {
36
};
37
$self->{result} = {};
30
38
31
39
bless $self, $class;
32
40
…
…
43
51
my $session_type = $opts{session_type} || 'Pay';
44
52
45
53
my $order_id = $opts{order_id};
46
return 0 unless $order_id;
54
unless ( $order_id ) {
55
$self->{result}{error} = 'Не указан order_id';
56
return $self;
57
}
47
58
59
my $uid = $opts{uid};
60
unless ( $uid ) {
61
$self->{result}{error} = 'Не указан user id';
62
return $self;
63
}
64
48
65
### Сумма в копейках. Если дробное - преобразуем
66
my $total;
49
67
my $sum = $opts{sum};
50
return 0 unless $sum;
51
if ( $sum =~ /[\.\,]/ ) {
52
$sum =~ s/\,/\./;
53
$sum = int($sum * 100);
68
if ( !$sum || $sum !~ /^[\d\,\.]+$/ ) {
69
$self->{result}{error} = 'Не указана или неправильно указана сумма транзакции';
70
return $self;
54
71
}
72
$sum =~ s/\,/\./;
73
$total = sprintf("%.02f", $sum);
74
$sum = int($sum * 100);
55
75
76
my $operation = $keeper->get_documents(
77
class => 'payments::Operation',
78
status => $state->{payments}->{payture_test_mode},
79
order_id => $order_id,
80
order_by => 'ctime',
81
return_mode => 'array_ref',
82
);
83
if ( ref $operation eq 'ARRAY' && @$operation ) {
84
my $last = $operation->[-1];
85
if ( $last->name eq 'suspend' || $last->name eq 'cancel' ) {
86
$self->{result}{error} = 'Заказ был отменен или заморожен';
87
return $self;
88
} else {
89
$operation = $last;
90
}
91
} else {
92
$operation = payments::Operation->new( $keeper );
93
$operation->status( $state->{payments}->{payture_test_mode} );
94
$operation->name( 'create' );
95
$operation->order_id( $order_id );
96
$operation->uid( $uid );
97
$operation->sum( $sum );
98
$operation->store;
99
}
100
56
101
my $ip = $opts{ip};
57
return 0 unless $ip && $ip =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/;
58
warn "IP: $ip\n";
102
unless ( $ip && $ip =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ ) {
103
$self->{result}{error} = 'Неверный IP-адрес';
104
return $self;
105
}
106
warn "IP: $ip\n" if $DEBUG;
59
107
60
108
### Пример: http://yoursite.com/result?orderid={orderid}&result={success}
61
109
my $url = $opts{url};
…
…
64
112
my $lang = $opts{lang};
65
113
my $params = $opts{params};
66
114
67
my $ua = LWP::UserAgent->new;
68
$ua->agent('Mozilla/5.0');
69
$ua->ssl_opts( verify_hostname => 0, SSL_ca_path => '/usr/local/share/certs' );
70
my $req = URI->new( $self->{api}{init} );
71
# $req->query_param( Key => $self->id );
72
my @data = ( "SessionType=$session_type", "OrderId=$order_id", "Amount=$sum", "IP=$ip");
73
push @data, "Url=$url" if $url;
74
push @data, "TemplateTag=$template" if $template;
75
push @data, "Url=$url" if $url;
76
push @data, "Language=$lang" if $lang;
77
if ( ref $params eq 'HASH' ) {
78
while ( my ($param, $val) = each %$params ) {
79
push @data, "$param=$val";
115
my ($transaction) = $keeper->get_documents(
116
class => 'payments::Transaction',
117
status => $state->{payments}->{payture_test_mode},
118
limit => 1,
119
order_id => $order_id,
120
provider => $self->{payment_system},
121
);
122
if ( ref $transaction ) {
123
### Init already exists
124
$self->{result}{success} = 1;
125
$self->{result}{session_id} = $transaction->session_id;
126
$self->{result}{transaction} = $transaction;
127
} else {
128
my $ua = LWP::UserAgent->new;
129
my $is_https = $ua->is_protocol_supported( 'https' );
130
warn "PayTure Init: protocol 'https' is $is_https\n" if $DEBUG;
131
$ua->agent('Mozilla/5.0');
132
$ua->ssl_opts( verify_hostname => 0, SSL_ca_path => '/usr/local/share/certs' );
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
}
80
144
}
145
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
}
81
189
}
82
my $data_unescaped = join ';', @data;
83
warn Dumper "PayTure Init data: $data_unescaped\n";
84
my $data_str = URI::Escape::uri_escape( $data_unescaped );
85
# $req->query_param( Data => $data_str );
86
warn "PayTure Init query: ".Dumper($req) if $DEBUG;
190
return $self;
191
}
87
192
88
my $result = $ua->post( $req, Content => { Key => $self->id, Data => $data_str } );
89
if ( $result->code == 200 ) {
90
warn Dumper $result->content;
91
return 1;
193
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
}
92
261
} else {
93
warn Dumper $result;
94
warn "PayTure Init failed; ".$result->status_line."\n";
262
$self->{result}{error} = "Не найдена транзакция для order_id=$order_id";
263
return $self;
95
264
}
96
return 0;
265
266
return $self;
97
267
}
98
268
99
269
1;
utf8/plugins/payments/lib/payments/SQL/TransactionsTable.pm
26
26
27
27
_operation_id_filter
28
28
_order_id_filter
29
_session_id_filter
30
_success_filter
29
31
_provider_filter
32
_name_exact_filter
30
33
);
31
34
32
35
return \@available_filters;
…
…
66
69
'db_type' => 'numeric',
67
70
'db_opts' => "default 0",
68
71
},
72
{
73
'attr' => 'session_id',
74
'type' => 'string',
75
'rusname' => 'Ключ сессии',
76
'db_field' => 'session_id',
77
'db_type' => 'text',
78
},
69
79
{ # ID заказа
70
80
'attr' => 'order_id',
71
81
'type' => 'integer',
…
…
74
84
'db_type' => 'integer',
75
85
'db_opts' => "not null",
76
86
},
77
{ # ID заказа
87
{ # ID операции в таблице операций
78
88
'attr' => 'operation_id',
79
89
'type' => 'integer',
80
90
'rusname' => 'ID транзакции',
…
…
117
127
'db_field' => 'payment_system',
118
128
'db_type' => 'text',
119
129
},
130
{ # Результат транзакции
131
'attr' => 'success',
132
'type' => 'checkbox',
133
'rusname' => 'Транзакция прошла успешно',
134
'db_field' => 'success',
135
'db_type' => 'smallint',
136
'db_opts' => "default 1",
137
},
120
138
);
121
139
}
122
140
…
…
134
152
return &SQL::Common::_generic_int_filter('d.order_id', $opts{order_id});
135
153
}
136
154
155
sub _success_filter {
156
my ($self,%opts)=@_;
157
return undef unless ( exists $opts{success} );
158
return &SQL::Common::_generic_int_filter('d.success', $opts{success});
159
}
160
137
161
sub _provider_filter {
138
162
my ($self,%opts)=@_;
139
163
return undef unless ( exists $opts{provider} );
140
164
return &SQL::Common::_generic_text_filter('d.provider', $opts{provider});
141
165
}
142
166
167
sub _session_id_filter {
168
my ($self,%opts)=@_;
169
return undef unless ( exists $opts{session_id} );
170
return &SQL::Common::_generic_int_filter('d.session_id', $opts{session_id});
171
}
172
173
sub _name_exact_filter {
174
my ($self,%opts)=@_;
175
return undef unless ( exists $opts{name_exact} );
176
return &SQL::Common::_generic_text_filter('d.name', $opts{name_exact});
177
}
178
143
179
1;
utf8/plugins/payments/sql/TOAST/transactions.552.sql
1
ALTER TABLE payments_transactions ADD COLUMN session_id text;
2
ALTER TABLE payments_transactions ADD COLUMN success smallint default 1;
3
CREATE INDEX payments_transactions_sessions ON payments_transactions USING btree (provider, session_id) WHERE session_id is not null;
utf8/plugins/payments/sql/TOAST/transactions.sql
9
9
provider text,
10
10
name text,
11
11
account_id numeric,
12
session_id text,
12
13
order_id integer not null,
13
14
operation_id numeric not null,
14
15
currency_code varchar(4),
…
…
16
17
account_user text,
17
18
payment_system text,
18
19
account_corr text,
20
success smallint default 1,
19
21
data text
20
22
);
21
23
CREATE INDEX payments_transactions_operations ON payments_transactions USING btree (provider, operation_id);
24
CREATE INDEX payments_transactions_sessions ON payments_transactions USING btree (provider, session_id) WHERE session_id is not null;
22
25
CREATE INDEX payments_transactions_orders ON payments_transactions USING btree (order_id);
Небольшая справка по веткам
cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.
koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.
utf8 – актуальная ветка, заточенная под UTF-8.
Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.