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 – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.