Revision 693
Date:
2018/08/13 11:24:32
Author:
ahitrov
Revision Log:
Initial import
Files:
Legend:
Added
Removed
Modified
utf8/plugins/money/comps/contenido/money/autohandler
1
<%init>
2
3
$r->content_type('text/html');
4
$m->call_next();
5
6
</%init>
utf8/plugins/money/comps/contenido/money/dhandler
1
<& $call, %ARGS &>
2
<%init>
3
4
my $call;
5
if ( $r->uri eq '/contenido/money/' ) {
6
$call = 'index.html';
7
} else {
8
&abort404;
9
}
10
11
</%init>
utf8/plugins/money/comps/contenido/money/index.html
1
<& "/contenido/components/header.msn" &>
2
<& "/contenido/components/naviline.msn" &>
3
4
<p>PLugin [money]</p>
5
6
</body>
7
</html>
utf8/plugins/money/comps/www/money.backend/dreamkas.check.html
1
<%args>
2
</%args>
3
<%init>
4
5
&abort404;
6
7
</%init>
utf8/plugins/money/config.proto
1
#############################################################################
2
#
3
# Параметры данного шаблона необходимо ВРУЧНУЮ добавить в config.mk проекта
4
# и привести в соответствие с требованиями проекта
5
#
6
#############################################################################
7
PLUGINS += money
8
9
# dreamkas.ru
10
############################################################
11
DREAMKAS_ID =
12
DREAMKAS_SECRET = 123
13
DREAMKAS_CURRENCY_CODE = RUB
14
DREAMKAS_TAX_MODE = DEFAULT # DEFAULT or SIMPLE or SIMPLE_WO or ENVD or AGRICULT or PATENT
15
DREAMKAS_TAX_NDS = NDS_NO_TAX # NDS_NO_TAX or NDS_0 or NDS_10 or NDS_18 or NDS_20 or NDS_10_CALCULATED or NDS_18_CALCULATED
16
DREAMKAS_TEST_MODE = 1 # 0 - для боевого режима
17
18
REWRITE += DREAMKAS_ID DREAMKAS_SECRET DREAMKAS_CURRENCY_CODE DREAMKAS_TAX_MODE DREAMKAS_TAX_NDS DREAMKAS_TEST_MODE
19
############################################################
20
# /dreamkas.ru
utf8/plugins/money/lib/money/Apache.pm
1
package money::Apache;
2
3
use strict;
4
use warnings 'all';
5
6
use money::State;
7
use Contenido::Globals;
8
9
10
sub child_init {
11
# встраиваем keeper плагина в keeper проекта
12
$keeper->{money} = money::Keeper->new($state->money);
13
}
14
15
sub request_init {
16
}
17
18
sub child_exit {
19
}
20
21
1;
utf8/plugins/money/lib/money/Init.pm
1
package money::Init;
2
3
use strict;
4
use warnings 'all';
5
6
use Contenido::Globals;
7
use money::Apache;
8
use money::Keeper;
9
10
11
# загрузка всех необходимых плагину классов
12
# money::SQL::SomeTable
13
# money::SomeClass
14
Contenido::Init::load_classes(qw(
15
money::SQL::MovementsTable
16
money::Movement
17
18
money::MovementSection
19
20
money::Provider::Base
21
));
22
23
sub init {
24
0;
25
}
26
27
1;
utf8/plugins/money/lib/money/Keeper.pm
1
package money::Keeper;
2
3
use strict;
4
use warnings 'all';
5
use base qw(Contenido::Keeper);
6
7
8
use Contenido::Globals;
9
10
11
1;
utf8/plugins/money/lib/money/Movement.pm
1
package money::Movement;
2
3
use base "Contenido::Document";
4
sub extra_properties
5
{
6
return (
7
{ 'attr' => 'status', 'type' => 'status', 'rusname' => 'Статус тестирования',
8
'cases' => [
9
[0, 'Реальный чек'],
10
[1, 'Тестовый чек'],
11
],
12
},
13
)
14
}
15
16
sub class_name
17
{
18
return 'Money: онлайн-чек';
19
}
20
21
sub class_description
22
{
23
return 'Money: онлайн-чек';
24
}
25
26
sub class_table
27
{
28
return 'money::SQL::MovementsTable';
29
}
30
31
1;
utf8/plugins/money/lib/money/MovementSection.pm
1
package money::MovementSection;
2
3
use base 'Contenido::Section';
4
5
sub extra_properties
6
{
7
return (
8
{ 'attr' => 'brief', 'type' => 'text', 'rusname' => 'Описание секции' },
9
{ 'attr' => 'default_document_class', 'default' => 'money::Movement' },
10
{ 'attr' => '_sorted', 'hidden' => 1 },
11
{ 'attr' => 'order_by', 'hidden' => 1 },
12
)
13
}
14
15
sub class_name
16
{
17
return 'Money: Секция чеков';
18
}
19
20
sub class_description
21
{
22
return 'Money: Секция транзакций';чеков
23
}
24
25
1;
utf8/plugins/money/lib/money/Provider/Base.pm
1
package money::Provider::Base;
2
3
use strict;
4
use warnings 'all';
5
use Contenido::Globals;
6
use payments::Keeper;
7
8
9
sub new {
10
my ($proto, %params) = @_;
11
my $class = ref($proto) || $proto;
12
my $self = {};
13
my $prefix = $class =~ /\:\:(\w+)$/ ? lc($1) : undef;
14
return unless $prefix;
15
16
$self->{provider} = $prefix;
17
$self->{app_id} = $state->{money}->{$prefix."_app_id"};
18
$self->{secret} = $state->{money}->{$prefix."_app_secret"};
19
$self->{currency} = $state->{money}->{$prefix."_currency_code"};
20
$self->{test_mode} = $state->{money}->{$prefix."_test_mode"};
21
22
bless $self, $class;
23
24
return $self;
25
}
26
27
28
sub id {
29
my $self = shift;
30
return $self->{app_id};
31
}
32
33
sub app_id {
34
my $self = shift;
35
return $self->{app_id};
36
}
37
38
sub secret {
39
my $self = shift;
40
return $self->{secret};
41
}
42
43
sub test_mode {
44
my $self = shift;
45
return $self->{test_mode};
46
}
47
48
sub currency {
49
my $self = shift;
50
return $self->{currency};
51
}
52
53
sub currency_code {
54
my $self = shift;
55
return $self->{currency};
56
}
57
58
sub provider {
59
my $self = shift;
60
return $self->{provider};
61
}
62
63
#################################
64
# Пытается зарегистрировать движение средств по order_id.
65
# В случае успеха возвращает объект money::Movement
66
# В случае неуспеха выставляет ошибку и возвращает undef.
67
# Сумма чека в копейках
68
##########################################################
69
sub money_movement_register {
70
my $self = shift;
71
my $opts = shift // {};
72
unless ( $opts->{order_id} && $opts->{uid} && $opts->{sum} && $opts->{name} ) {
73
$self->{result}{error} = 'Переданы не все обязательные параметры';
74
return undef;
75
}
76
77
my $mm = $keeper->get_documents(
78
class => 'money::Movement',
79
status => $self->{test_mode},
80
order_id => $opts->{order_id},
81
order_by => 'ctime',
82
return_mode => 'array_ref',
83
);
84
my $new = 0;
85
if ( ref $mm eq 'ARRAY' && @$mm ) {
86
my $last = $mm->[-1];
87
if ( $opts->{name} eq 'payment' && $last->name eq 'payment' ) {
88
return $last;
89
} elsif ( $opts->{name} eq 'refund' && (grep { $_->name eq 'payment' } @$mm) ) {
90
$new = 1;
91
}
92
} elsif ( $opts->{name} eq 'payment' ) {
93
$new = 1;
94
}
95
if ( $new ) {
96
$mm = money::Movement->new( $keeper );
97
$mm->status( $self->{test_mode} );
98
$mm->name( $opts->{name} );
99
$mm->order_id( $opts->{order_id} );
100
$mm->uid( $opts->{uid} );
101
$mm->sum( sprintf("%.2f", $opts->{sum} / 100) );
102
$mm->store;
103
}
104
return $mm;
105
}
106
107
sub get_mm_by_order_id {
108
my $self = shift;
109
my $order_id = shift;
110
111
my ($mm) = $keeper->get_documents(
112
class => 'money::Movement',
113
status => $self->{test_mode},
114
limit => 1,
115
order_id => $order_id,
116
order_by => 'ctime desc',
117
provider => $self->{provider},
118
);
119
120
return $mm;
121
}
122
123
1;
utf8/plugins/money/lib/money/Provider/Dreamkas.pm
1
package money::Provider::Dreamkas;
2
3
use strict;
4
use warnings 'all';
5
6
use base 'money::Provider::Base';
7
use Contenido::Globals;
8
use money::Keeper;
9
use MIME::Base64;
10
use URI;
11
use URI::QueryParam;
12
use JSON::XS;
13
use Data::Dumper;
14
15
use constant (
16
TAXMODE_DEFAULT => 'DEFAULT',
17
);
18
19
our %TAX_MODES = (
20
'DEFAULT' => 1, # Общая
21
'SIMPLE' => 1, # Упрощенная доход
22
'SIMPLE_WO' => 1, # Упрощенная доход минус расход
23
'ENVD' => 1, # Единый налог на вмененный доход
24
'AGRICULT' => 1, # Единый сельскохозяйственный
25
'PATENT' => 1, # Патентная система налогообложения
26
);
27
28
our %TAX_NDS = (
29
'NDS_NO_TAX' => 0,
30
'NDS_0' => 0,
31
'NDS_10' => 0.10,
32
'NDS_18' => 0.18,
33
'NDS_20' => 0.20,
34
'NDS_10_CALCULATED' => 0.10/110,
35
'NDS_18_CALCULATED' => 0.10/110,
36
);
37
38
our %OP_STATUS = (
39
'PENDING' => 0,
40
'IN_PROGRESS' => 1,
41
'SUCCESS' => 2,
42
'ERROR' => -1,
43
);
44
45
sub new {
46
my ($proto, %params) = @_;
47
my $class = ref($proto) || $proto;
48
my $self = {};
49
my $prefix = $class =~ /\:\:(\w+)$/ ? lc($1) : undef;
50
return unless $prefix;
51
52
$self->{prefix} = $prefix;
53
$self->{app_id} = $state->{money}{$prefix."_app_id"};
54
$self->{secret} = $state->{money}{$prefix."_app_secret"};
55
$self->{token} = $state->{money}{$prefix."_app_token"};
56
$self->{tax_mode} = $state->{money}{$prefix."_tax_mode"};
57
unless ( exists $TAX_MODES{$self->{tax_mode}} ) {
58
warn "Неверная мнемоника типа налоговой системы\n";
59
return undef;
60
}
61
$self->{tax_nds} = $state->{money}{$prefix."_tax_nds"};
62
unless ( exists $TAX_NDS{$self->{tax_nds}} ) {
63
warn "Неверная мнемоника типа НДС\n";
64
return undef;
65
}
66
$self->{device_id} = $state->{money}{$prefix."_device_id"};
67
unless ( $self->{device_id} ) {
68
warn "Не указан или неверно указан ID кассового аппарата\n";
69
return undef;
70
}
71
$self->{test_mode} = exists $params{test_mode} ? $params{test_mode} : $state->{money}->{$prefix."_test_mode"};
72
$self->{return_url} = $params{return_url} || $state->{money}{$prefix."_return_url"};
73
$self->{fail_url} = $params{fail_url} || $state->{money}{$prefix."_fail_url"};
74
75
$self->{currency} = $state->{money}{$prefix."_currency_code"};
76
77
$self->{$base_url} = 'https://'. ($self->{test_mode} ? 'private-anon-f6c2f7b545-kabinet.apiary-mock.com' : 'kabinet.dreamkas.ru').'/api';
78
$self->{result} = {};
79
80
bless $self, $class;
81
return $self;
82
}
83
84
85
=for rem RECEIPT
86
# Фискализация чека (только для Дримкас-Ф)
87
88
$mm->receipt({
89
# обязательные:
90
order => webshop::Order
91
# или
92
order_id => ID от webshop::Order
93
total => общая сумма заказа, если не передан order
94
95
profile => Профиль пользователя, объект
96
# или
97
attributes => Атрибуты, пример внизу
98
# или
99
email => E-mail
100
phone => Phone в формате +79163332222
101
102
# необязательные:
103
basket => Если есть order или order_id, можно не передавать
104
# или
105
positions => ARRAY_REF, если не передан order или basket
106
107
type => SALE || REFUND || OUTFLOW || OUTFLOW_REFUND || SALE
108
timeout => Таймаут фискализации в секундах (по умолчанию - 300 секунд).
109
Если в течение этого времени не удастся произвести фискализацию,
110
то операция будет отменена с ошибкой.
111
payment_type => CASH || CASHLESS
112
});
113
114
JSON тела вызова:
115
116
{
117
"deviceId": 1385,
118
"type": "SALE",
119
"timeout": 180,
120
"taxMode": "DEFAULT",
121
"positions": [
122
{
123
"name": "Шоколад Сникерс",
124
"type": "COUNTABLE",
125
"quantity": 2,
126
"price": 4500,
127
"priceSum": 9000,
128
"tax": "NDS_18",
129
"taxSum": 1620
130
}
131
],
132
"payments": [
133
{
134
"sum": 9000,
135
"type": "CASHLESS"
136
}
137
],
138
"attributes": {
139
"email": "john.smith@example.com",
140
"phone": "+71239994499"
141
},
142
"total": {
143
"priceSum": 9000
144
}
145
}
146
147
148
Результат:
149
150
{
151
"id": "5956889136fdd7733f19cfe6",
152
"createdAt": "2017-06-20 12:01:47.990Z",
153
"status": "PENDING"
154
}
155
156
status:
157
158
PENDING - В обработке
159
IN_PROGRESS - Задача принята в обработку (например, устройство приняло чек на фискализацию)
160
SUCCESS - Завершено успешно
161
ERROR - Завершено с ошибкой
162
163
=cut
164
##########################################################
165
sub receipt {
166
my $self = shift;
167
my $opts = shift // {};
168
169
my $type = delete $opts->{type};
170
if ( $type && $type =~ /^(SALE|REFUND|OUTFLOW|OUTFLOW_REFUND)$/ ) {
171
$self->{result}{error} = 'Неверно указан тип операции';
172
return $self;
173
}
174
$opts->{type} ||= 'SALE';
175
176
my $data = {
177
type => $type,
178
deviceId=> $self->{device_id},
179
taxMode => $self->{tax_mode},
180
};
181
182
if ( exists $opts->{order_id} ) {
183
$opts->{order} = $keeper->{webshop}->get_orders( id => $opts->{order_id} );
184
unless ( ref $opts->{order} eq 'webshop::Order' ) {
185
$self->{result}{error} = 'Заказ не найден. Передан неверный order_id';
186
return $self;
187
}
188
}
189
190
my $MM;
191
if ( exists $opts->{order} ) {
192
$MM = $self->_GetLastMoneyMovement( $opts->{order}->id );
193
}
194
if ( ref $MM && $MM->session_id && $MM->name eq $opts->{type} ) {
195
$self->{result}{money_movement} = $MM;
196
return $self;
197
}
198
unless ( $MM ) {
199
$MM = money::Movement->new( $keeper );
200
$MM->name( $opts->{type} );
201
$MM->provider( $self->{prefix} );
202
$MM->status( $self->{test_mode} );
203
$MM->success( 0 );
204
if ( ref $opts->{order} ) {
205
$MM->order_id( $opts->{order}->id );
206
}
207
$MM->currency_code( $self->{currency} );
208
}
209
210
if ( exists $opts->{order} && !exists $opts->{basket} ) {
211
$opts->{basket} = $keeper->{webshop}->get_basket( order_id => $opts->{order}->id, with_products => 1 );
212
unless ( ref $opts->{basket} eq 'ARRAY' && @{$opts->{basket}} ) {
213
$self->{result}{error} = 'Невозможно получить список товарных позиций в заказе';
214
return $self;
215
}
216
}
217
218
if ( exists $opts->{basket} && ref $opts->{basket} eq 'ARRAY' ) {
219
my $positions = [];
220
foreach my $bi ( @{$opts->{basket}} ) {
221
my $item = $bi->{item};
222
next unless ref $item;
223
my $price = int($bi->{item}->price * 100)
224
my $pos = {
225
name => $bi->name,
226
type => 'COUNTABLE',
227
quantity => $bi->number,
228
price => $price,
229
priceSum => $price * $bi->number,
230
tax => $self->{tax_nds},
231
taxSum => ($price * $bi->number) * $TAX_NDS{$self->{tax_nds}},
232
};
233
push @$positions, $pos;
234
}
235
unless ( @$positions ) {
236
$self->{result}{error} = 'Cписок товарных позиций в заказе неверный. Возможно, в состав корзины не включенны товары';
237
return $self;
238
}
239
$data->{positions} = $positions;
240
} elsif ( exists $opts->{positions} && ref $opts->{positions} eq 'ARRAY' && @{$opts->{positions}} ) {
241
$data->{positions} = $opts->{positions};
242
}
243
244
# Заполняем атрибуты плательщика
245
if ( exists $opts->{profile} && ref $opts->{profile} eq $state->{users}->profile_document_class ) {
246
my $profile = $opts->{profile};
247
my $email = $profile->email;
248
my $attributes = { email => "$email" };
249
$data->{attributes} = $attributes;
250
} elsif ( exists $opts->{attributes} ) {
251
$data->{attributes} = $data->{attributes};
252
} elsif ( exists $opts->{email} || $opts->{phone} ) {
253
my $attributes = {};
254
if ( exists $opts->{email} && $opts->{email} ) {
255
if ( ref $opts->{email} ) {
256
$arrtibutes->{email} = $opts->{email}->name;
257
} else {
258
$arrtibutes->{email} = $opts->{email};
259
}
260
}
261
if ( exists $opts->{phone} && $opts->{phone} ) {
262
if ( ref $opts->{phone} ) {
263
$arrtibutes->{phone} = $opts->{phone}->name;
264
} else {
265
$arrtibutes->{phone} = $opts->{phone};
266
}
267
}
268
$data->{attributes} = $attributes;
269
}
270
271
# Заполняем параметры оплаты: total и payments
272
if ( exists $opts->{order} && ref $opts->{order} eq 'webshop::Order' ) {
273
$data->{total}{priceSum} = int($opts->{order}->sum_total * 10);
274
} else {
275
if ( $opts->{total} ) {
276
$data->{total}{priceSum} = $opts->{total};
277
}
278
}
279
unless ( $data->{total}{priceSum} ) {
280
$self->{result}{error} = 'Не указана итоговая сумма. Необходимо передать параметр total или order';
281
return $self;
282
}
283
$data->{payments}{sum} = $data->{total}{priceSum};
284
$MM->sum( $data->{total}{priceSum} );
285
if ( exists $opts->{payment_type} && $opts->{payment_type} eq 'CASH' ) {
286
$data->{payments}{type} = 'CASH';
287
}
288
289
my $api_url = '/api/receipts';
290
291
$self->_MakeRequest( $api_url, 'post', $data );
292
if ( $self->{result}{code} == 202 ) {
293
$MM->success( $OP_STATUS{$self->{result}{content}{status}} );
294
$MM->session_id( $self->{result}{content}{id} );
295
$MM->store;
296
$self->{result}{money_movement} = $MM;
297
} else {
298
$self->{result}{error} = $self->{result}{status};
299
}
300
301
return $self;
302
}
303
304
305
=for rem RECEIPT
306
# Информация о статусе операции
307
308
$mm->check( $operation_id );
309
310
Передается ID, полученное на этапе запроса на фискализацию чека
311
312
Результат:
313
314
{
315
"id": "5956889136fdd7733f19cfe6",
316
"createdAt": "2017-06-20 12:01:47.990Z",
317
"status": "ERROR",
318
"completedAt": "2017-06-20 12:03:12.440Z",
319
"data": {
320
"error": {
321
"code": "NeedUpdateCash",
322
"message": "Требуется обновление кассы"
323
}
324
}
325
}
326
327
status:
328
329
PENDING - В обработке
330
IN_PROGRESS - Задача принята в обработку (например, устройство приняло чек на фискализацию)
331
SUCCESS - Завершено успешно
332
ERROR - Завершено с ошибкой
333
334
=cut
335
##########################################################
336
sub check {
337
my $self = shift;
338
my $opts = shift // {};
339
if ( exists $self->{result} && exists $self->{result}{error} ) {
340
return $self;
341
}
342
343
my $MM;
344
if ( exists $self->{result}{money_movement} ) {
345
$MM = $self->{result}{money_movement};
346
} elsif ( exists $opts->{money_movement} ) {
347
$MM = $opts->{money_movement};
348
} elsif ( $opts->{operation_id} ) {
349
($MM) = $self->_GetMMByOperationId( $opts->{operation_id} );
350
}
351
unless ( ref $MM ) {
352
$self->{result}{error} = 'Не найден объект "движение денежных средств". Проверьте входные параметры';
353
return $self;
354
}
355
356
my $api_url = '/api/operations/'.$MM->session_id;
357
358
$self->_MakeRequest( $api_url, 'get' );
359
if ( $self->{result}{code} == 200 ) {
360
$MM->success( $OP_STATUS{$self->{result}{content}{status}} );
361
$MM->store;
362
$self->{result}{money_movement} = $MM;
363
if ( $self->{result}{content}{status} eq 'ERROR' ) {
364
$self->{result}{error} = $self->{result}{content}{data}{error}{message};
365
$self->{result}{code} = $self->{result}{content}{data}{error}{code};
366
}
367
} else {
368
$self->{result}{error} = $self->{result}{status};
369
}
370
371
return $self;
372
}
373
374
375
sub _MakeRequest {
376
my ($self, $url, $type, $body) = @_;
377
$type ||= 'post';
378
379
my $ua = LWP::UserAgent->new;
380
$ua->timeout( 10 );
381
$ua->agent('Mozilla/5.0');
382
383
my $auth = encode_base64($self->{app_id}.':'.$self->{secret});
384
$ua->default_header( 'Authorization' => "Application: {$auth}" );
385
$ua->default_header( 'Content-Type' => 'application/json' );
386
387
if ( ref $body ) {
388
$body = encode_json( $body );
389
}
390
391
my $req = URI->new( $self->{host}.($url =~ /^\// ? '' : '/').$url );
392
my $res;
393
if ( $type eq 'post' ) {
394
$res = $ua->post( $req, Content => $body );
395
} elsif ( $type eq 'delete' ) {
396
$res = $ua->delete( $req );
397
} else {
398
$res = $ua->get( $req );
399
}
400
$self->{result} = {
401
code => $res->code,
402
status => $res->status_line,
403
content => JSON::XS->new->decode( $res->decoded_content ),
404
}
405
return $self;
406
}
407
408
sub _GetLastMoneyMovement {
409
my $self = shift;
410
my $order_id = shift;
411
my ($mm) = $keeper->get_documents(
412
class => 'money::Movement',
413
limit => 1,
414
order_id => $order_id
415
order_by => 'id desc',
416
);
417
return $mm;
418
}
419
420
sub _GetMMByOperationId {
421
my $self = shift;
422
my $op_id = shift;
423
my ($mm) = $keeper->get_documents(
424
class => 'money::Movement',
425
limit => 1,
426
session_id => $op_id,
427
);
428
return $mm;
429
}
430
431
1;
utf8/plugins/money/lib/money/SQL/MovementsTable.pm
1
package money::SQL::MovementsTable;
2
3
use base 'SQL::DocumentTable';
4
5
sub db_table
6
{
7
return 'money_movements';
8
}
9
10
sub db_id_sequence {
11
return 'money_movements_id_seq';
12
}
13
14
sub available_filters {
15
my @available_filters = qw(
16
17
_class_filter
18
_status_filter
19
_in_id_filter
20
_id_filter
21
_name_filter
22
_class_excludes_filter
23
_sfilter_filter
24
_excludes_filter
25
_datetime_filter
26
_date_equal_filter
27
_date_filter
28
_previous_days_filter
29
30
_provider_filter
31
_session_id_filter
32
_order_id_filter
33
_success_filter
34
_name_exact_filter
35
);
36
37
return \@available_filters;
38
}
39
40
# ----------------------------------------------------------------------------
41
# Свойства храним в массивах, потому что порядок важен!
42
# Это общие свойства - одинаковые для всех документов.
43
#
44
# attr - обязательный параметр, название атрибута;
45
# type - тип аттрибута, требуется для отображдения;
46
# rusname - русское название, опять же требуется для отображения;
47
# hidden - равен 1, когда
48
# readonly - инициализации при записи только без изменения в дальнейшем
49
# db_field - поле в таблице
50
# default - значение по умолчанию (поле всегда имеет это значение)
51
# ----------------------------------------------------------------------------
52
sub required_properties
53
{
54
my $self = shift;
55
56
my @parent_properties = grep { $_->{attr} ne 'sections' } $self->SUPER::required_properties;
57
return (
58
@parent_properties,
59
{
60
'attr' => 'provider',
61
'type' => 'string',
62
'rusname' => 'Провайдер',
63
'db_field' => 'provider',
64
'db_type' => 'text',
65
},
66
{
67
'attr' => 'session_id',
68
'type' => 'string',
69
'rusname' => 'Ключ сессии',
70
'db_field' => 'session_id',
71
'db_type' => 'text',
72
},
73
{ # ID заказа
74
'attr' => 'order_id',
75
'type' => 'integer',
76
'rusname' => 'ID заказа',
77
'db_field' => 'order_id',
78
'db_type' => 'integer',
79
'db_opts' => "not null",
80
},
81
{
82
'attr' => 'currency_code',
83
'type' => 'string',
84
'rusname' => 'ID валюты',
85
'db_field' => 'currency_code',
86
'db_type' => 'varchar(4)',
87
},
88
{
89
'attr' => 'sum',
90
'type' => 'string',
91
'rusname' => 'Сумма чека',
92
'db_field' => 'sum',
93
'db_type' => 'float',
94
},
95
{ # Результат транзакции
96
'attr' => 'success',
97
'type' => 'checkbox',
98
'rusname' => 'Транзакция прошла успешно',
99
'db_field' => 'success',
100
'db_type' => 'smallint',
101
'db_opts' => "default 0",
102
},
103
);
104
}
105
106
107
########### FILTERS DESCRIPTION ###############################################################################
108
sub _order_id_filter {
109
my ($self,%opts)=@_;
110
return undef unless ( exists $opts{order_id} );
111
return &SQL::Common::_generic_int_filter('d.order_id', $opts{order_id});
112
}
113
114
sub _success_filter {
115
my ($self,%opts)=@_;
116
return undef unless ( exists $opts{success} );
117
return &SQL::Common::_generic_int_filter('d.success', $opts{success});
118
}
119
120
sub _provider_filter {
121
my ($self,%opts)=@_;
122
return undef unless ( exists $opts{provider} );
123
return &SQL::Common::_generic_text_filter('d.provider', $opts{provider});
124
}
125
126
sub _session_id_filter {
127
my ($self,%opts)=@_;
128
return undef unless ( exists $opts{session_id} );
129
return &SQL::Common::_generic_int_filter('d.session_id', $opts{session_id});
130
}
131
132
sub _name_exact_filter {
133
my ($self,%opts)=@_;
134
return undef unless ( exists $opts{name_exact} );
135
return &SQL::Common::_generic_text_filter('d.name', $opts{name_exact});
136
}
137
138
1;
utf8/plugins/money/lib/money/State.pm.proto
1
package money::State;
2
3
use strict;
4
use warnings 'all';
5
use vars qw($AUTOLOAD);
6
7
8
sub new {
9
my ($proto) = @_;
10
my $class = ref($proto) || $proto;
11
my $self = {};
12
bless $self, $class;
13
14
# configured
15
$self->{project} = '@PROJECT@';
16
$self->{debug} = (lc('@DEBUG@') eq 'yes');
17
$self->{contenido_notab} = 1;
18
$self->{tab_name} = 'money';
19
$self->{project_name} = '@PROJECT_NAME@';
20
$self->{default_expire} = '@DEFAULT_EXPIRE@' || 300;
21
$self->{default_object_expire} = '@DEFAULT_OBJECT_EXPIRE@' || 600;
22
23
# зашитая конфигурация плагина
24
$self->{db_type} = 'none'; ### For REAL database use 'remote'
25
$self->{db_keepalive} = 0;
26
$self->{db_host} = '';
27
$self->{db_name} = '';
28
$self->{db_user} = '';
29
$self->{db_password} = '';
30
$self->{db_port} = '';
31
$self->{store_method} = 'toast';
32
$self->{cascade} = 1;
33
$self->{db_prepare} = 0;
34
35
$self->{memcached_enable} = lc( '@MEMCACHED_ENABLE@' ) eq 'yes' ? 1 : 0;
36
$self->{memcached_backend} = '@MEMCACHED_BACKEND@';
37
$self->{memcached_select_timeout} = '@MEMCACHED_SELECT_TIMEOUT@' || 0.2;
38
$self->{memcached_servers} = [qw(@MEMCACHED_SERVERS@)];
39
$self->{memcached_enable_compress} = lc( '@MEMCACHED_ENABLE_COMPRESS@' ) eq 'yes' ? 1 : 0;
40
$self->{memcached_delayed} = lc('@MEMCACHED_DELAYED@') eq 'yes' ? 1 : 0;
41
$self->{memcached_set_mode} = lc('@MEMCACHED_SET_MODE@') eq 'add' ? 'add' : 'set';
42
$self->{memcached_busy_lock} = 60;
43
$self->{memcached_namespace} = lc( $self->{'project'} ).'|plugin_payments|';
44
45
$self->{serialize_with} = 'json'; ### or 'dumper'
46
47
# not implemented really (core compatibility)
48
$self->{binary_directory} = '/nonexistent';
49
$self->{data_directory} = '/nonexistent';
50
$self->{images_directory} = '/nonexistent';
51
$self->{preview} = '0';
52
53
$self->{dreamkas_app_id} = '@DREAMKAS_ID@';
54
$self->{dreamkas_app_secret} = '@DREAMKAS_SECRET@';
55
$self->{dreamkas_currency_code} = '@DREAMKAS_CURRENCY_CODE@';
56
$self->{dreamkas_tax_mode} = '@DREAMKAS_TAX_MODE@' || 'DEFAULT';
57
$self->{dreamkas_tax_nds} = '@DREAMKAS_TAX_NDS@' || 'NDS_NO_TAX';
58
$self->{dreamkas_device_id} = int('@DREAMKAS_DEVICE_ID@' || 0);
59
$self->{dreamkas_test_mode} = int('@DREAMKAS_TEST_MODE@' || 0);
60
61
$self->_init_();
62
$self;
63
}
64
65
sub info {
66
my $self = shift;
67
return unless ref $self;
68
69
for (sort keys %{$self->{attributes}}) {
70
my $la = length $_;
71
warn "\t$_".("\t" x (2-int($la/8))).": $self->{$_}\n";
72
}
73
}
74
75
sub _init_ {
76
my $self = shift;
77
78
# зашитая конфигурация плагина
79
$self->{attributes}->{$_} = 'SCALAR' for qw(
80
debug
81
project
82
tab_name
83
84
db_type
85
db_keepalive
86
db_host
87
db_port
88
db_name
89
db_user
90
db_password
91
store_method
92
cascade
93
db_prepare
94
db_client_encoding
95
96
memcached_enable
97
memcached_enable_compress
98
memcached_backend
99
memcached_servers
100
memcached_busy_lock
101
memcached_delayed
102
103
binary_directory
104
data_directory
105
images_directory
106
preview
107
);
108
}
109
110
sub AUTOLOAD {
111
my $self = shift;
112
my $attribute = $AUTOLOAD;
113
114
$attribute =~ s/.*:://;
115
return unless $attribute =~ /[^A-Z]/; # Отключаем методы типа DESTROY
116
117
if (!exists $self->{attributes}->{$attribute}) {
118
warn "Contenido Error (money::State): Вызов метода, для которого не существует обрабатываемого свойства: ->$attribute()\n";
119
return;
120
}
121
122
$self->{$attribute} = shift @_ if $#_>=0;
123
$self->{$attribute};
124
}
125
126
1;
utf8/plugins/money/sql/TOAST/money_movement.sql
1
create sequence money_movements_id_seq;
2
select setval('money_movements_id_seq', 1, true);
3
4
create table money_movements
5
(
6
id integer not null primary key default nextval('public.documents_id_seq'::text),
7
class text not null,
8
ctime timestamp not null default now(),
9
mtime timestamp not null default now(),
10
dtime timestamp not null default now(),
11
status smallint not null default 0,
12
provider text,
13
session_id text,
14
name text,
15
order_id integer not null,
16
currency_code varchar(4),
17
sum float,
18
success smallint default 0,
19
data text
20
);
21
CREATE INDEX money_movements_sessions ON money_movements USING btree (provider, session_id) WHERE session_id is not null;
22
CREATE INDEX money_movements_orders ON money_movements USING btree (order_id);
Небольшая справка по веткам
cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.
koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.
utf8 – актуальная ветка, заточенная под UTF-8.
Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.