Revision 693
- Date:
- 2018/08/13 11:24:32
- Files:
-
- /utf8/plugins/money/comps
- /utf8/plugins/money/comps/contenido
- /utf8/plugins/money/comps/contenido/money
- /utf8/plugins/money/comps/contenido/money/autohandler (Diff) (Checkout)
- /utf8/plugins/money/comps/contenido/money/dhandler (Diff) (Checkout)
- /utf8/plugins/money/comps/contenido/money/index.html (Diff) (Checkout)
- /utf8/plugins/money/comps/www
- /utf8/plugins/money/comps/www/money.backend
- /utf8/plugins/money/comps/www/money.backend/dreamkas.check.html (Diff) (Checkout)
- /utf8/plugins/money/config.proto (Diff) (Checkout)
- /utf8/plugins/money/lib
- /utf8/plugins/money/lib/money
- /utf8/plugins/money/lib/money/Apache.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/Init.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/Keeper.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/Movement.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/MovementSection.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/Provider
- /utf8/plugins/money/lib/money/Provider/Base.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/Provider/Dreamkas.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/SQL
- /utf8/plugins/money/lib/money/SQL/MovementsTable.pm (Diff) (Checkout)
- /utf8/plugins/money/lib/money/State.pm.proto (Diff) (Checkout)
- /utf8/plugins/money/sql
- /utf8/plugins/money/sql/TOAST
- /utf8/plugins/money/sql/TOAST/money_movement.sql (Diff) (Checkout)
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);