Revision 827
- Date:
- 2021/09/08 15:14:01
- Files:
Legend:
- Added
- Removed
- Modified
-
utf8/plugins/webshop/lib/webshop/Service/Base.pm
1 package webshop::Service::Base; 2 3 use strict; 4 use warnings; 5 6 use Digest::MD5; 7 use Data::Dumper; 8 use MIME::Base64; 9 use JSON::XS; 10 use JSON::XS::Boolean; 11 12 use parent 'Contenido::Accessor'; 13 __PACKAGE__->mk_accessors(qw(token key api_url error)); 14 15 use Contenido::Globals; 16 17 sub new { 18 my ($proto, $args) = @_; 19 my $class = ref($proto) || $proto; 20 $args //= {}; 21 my $self = {}; 22 bless $self, $class; 23 24 if ( ref $args ne 'HASH' ) { 25 $self->error("INIT: argument list is empty"); 26 return $self; 27 } 28 29 if ( exists $args->{api_url} && $args->{api_url} ) { 30 $self->api_url($args->{api_url}); 31 } else { 32 $self->error("INIT: API url is empty"); 33 return $self; 34 } 35 36 if ( exists $args->{token} && $args->{token} ) { 37 $self->token($args->{token}); 38 } else { 39 $self->error("INIT: API token is empty"); 40 return $self; 41 } 42 43 if ( exists $args->{key} && $args->{key} ) { 44 $self->key($args->{auth_key}); 45 } else { 46 $self->error("INIT: API auth key is empty"); 47 return $self; 48 } 49 50 warn Dumper $self if $DEBUG; 51 return $self; 52 } 53 54 55 sub _MakeRequest { 56 my ($self, $url, $type, $body, $cache_control) = @_; 57 58 $type //= 'get'; 59 60 my ($key, $file, $exists, $expired); 61 my $time = time(); 62 my $cache_container = $state->{data_dir}. '/webshop_service_cache'; 63 if ( ref $cache_control ) { 64 unless ( -e $cache_container ) { 65 `mkdir -p $cache_container`; 66 } 67 $key = 'service_api_'.Digest::MD5::md5_hex( $url ); 68 $file = $cache_container . '/' . $key; 69 if ( -e $file ) { 70 $exists = 1; 71 my $created = (stat $file)[9]; 72 if ( exists $cache_control->{expires_at} && $cache_control->{expires_at} > 0 ) { 73 if ( $time - $created > $cache_control->{expires_at} ) { 74 $expired = 1; 75 } 76 } elsif ( exists $cache_control->{expires_at} && $cache_control->{expires_at} < 0 ) { 77 $expired = 0; 78 } else { 79 $expired = 1; 80 } 81 } else { 82 $expired = 1; 83 } 84 if ( $exists && !$expired ) { 85 my $content = do $file; 86 return $content; 87 } 88 } 89 90 91 my $ua = LWP::UserAgent->new; 92 $ua->timeout( 10 ); 93 $ua->agent('Mozilla/5.0'); 94 95 if ( $self->token ) { 96 warn "Auth by token: ".$self->token."\n" if $DEBUG; 97 $ua->default_header( 'Authorization' => "AccessToken ".$self->token ); 98 } 99 if ( $self->key ) { 100 warn "Auth by auth key: ".$self->key."\n" if $DEBUG; 101 $ua->default_header( 'X-User-Authorization' => "Basic ".$self->key ); 102 } 103 $ua->default_header( 'Content-Type' => 'application/json' ); 104 $ua->default_header( 'Accept' => 'application/json;charset=UTF-8' ); 105 106 if ( ref $body ) { 107 $body = encode_json( $body ); 108 } 109 110 my $uri; 111 if ( $url =~ /^http/ ) { 112 $uri = URI->new( $url ); 113 } else { 114 $uri = URI->new( $self->api_url.($url =~ /^\// ? '' : '/').$url ); 115 } 116 117 my $res; 118 if ( $type eq 'post' ) { 119 my $req = HTTP::Request->new(POST => $uri); 120 $req->content_type('application/json'); 121 $req->content($body); 122 # warn "RUPOST post JSON: $body\n" if $DEBUG; 123 $res = $ua->request($req); 124 } elsif ( $type eq 'delete' ) { 125 $res = $ua->delete( $uri ); 126 } else { 127 $res = $ua->get( $uri ); 128 } 129 130 warn Dumper($res) if $DEBUG && $res->code != 200; 131 if ( $res->code != 200 && $exists ) { 132 my $content = do $file; 133 return $content; 134 } 135 136 my $result = { 137 code => $res->code, 138 status => $res->status_line, 139 content => JSON::XS->new->utf8->decode( $res->decoded_content ), 140 }; 141 142 if ( $res->code == 200 && ref $cache_control ) { 143 my $fh; 144 if ( open($fh, '>', $file) ) { 145 local $Data::Dumper::Indent = 0; 146 print $fh Dumper( $result ); 147 close $fh; 148 } 149 } 150 151 return $result; 152 } 153 154 1; -
utf8/plugins/webshop/lib/webshop/Service/CDEK.pm
1 package webshop::Service::CDEK; 2 3 use strict; 4 use warnings 'all'; 5 6 use base 'webshop::Service::Base'; 7 use Contenido::Globals; 8 use payments::Keeper; 9 use URI::Escape; 10 use Types::Serialiser; 11 use Digest::MD5; 12 use Data::Dumper; 13 use utf8; 14 15 use constant { 16 COURIER_TARIFF_PRIORITY => 'courier', 17 PICKUP_TARIFF_PRIORITY => 'pickup', 18 }; 19 20 our $translate = { 21 'rus' => { 22 'YOURCITY' => 'Ваш город', 23 'COURIER' => 'Курьер', 24 'PICKUP' => 'Самовывоз', 25 'TERM' => 'Срок', 26 'PRICE' => 'Стоимость', 27 'DAY' => 'дн.', 28 'RUB' => ' руб.', 29 'KZT' => 'KZT', 30 'USD' => 'USD', 31 'EUR' => 'EUR', 32 'GBP' => 'GBP', 33 'CNY' => 'CNY', 34 'BYN' => 'BYN', 35 'UAH' => 'UAH', 36 'KGS' => 'KGS', 37 'AMD' => 'AMD', 38 'TRY' => 'TRY', 39 'THB' => 'THB', 40 'KRW' => 'KRW', 41 'AED' => 'AED', 42 'UZS' => 'UZS', 43 'MNT' => 'MNT', 44 'NODELIV' => 'Нет доставки', 45 'CITYSEARCH' => 'Поиск города', 46 'ALL' => 'Все', 47 'PVZ' => 'Пункты выдачи', 48 'POSTOMAT' => 'Постаматы', 49 'MOSCOW' => 'Москва', 50 'RUSSIA' => 'Россия', 51 'COUNTING' => 'Идет расчет', 52 53 'NO_AVAIL' => 'Нет доступных способов доставки', 54 'CHOOSE_TYPE_AVAIL' => 'Выберите способ доставки', 55 'CHOOSE_OTHER_CITY' => 'Выберите другой населенный пункт', 56 57 'TYPE_ADDRESS' => 'Уточните адрес', 58 'TYPE_ADDRESS_HERE' => 'Введите адрес доставки', 59 60 'L_ADDRESS' => 'Адрес пункта выдачи заказов', 61 'L_TIME' => 'Время работы', 62 'L_WAY' => 'Как к нам проехать', 63 'L_CHOOSE' => 'Выбрать', 64 65 'H_LIST' => 'Список пунктов выдачи заказов', 66 'H_PROFILE' => 'Способ доставки', 67 'H_CASH' => 'Расчет картой', 68 'H_DRESS' => 'С примеркой', 69 'H_POSTAMAT' => 'Постаматы СДЭК', 70 'H_SUPPORT' => 'Служба поддержки', 71 'H_QUESTIONS' => 'Если у вас есть вопросы, можете<br> задать их нашим специалистам', 72 'ADDRESS_WRONG' => 'Невозможно определить выбранное местоположение. Уточните адрес из выпадающего списка в адресной строке.', 73 'ADDRESS_ANOTHER' => 'Ознакомьтесь с новыми условиями доставки для выбранного местоположения.' 74 }, 75 'eng' => { 76 'YOURCITY' => 'Your city', 77 'COURIER' => 'Courier', 78 'PICKUP' => 'Pickup', 79 'TERM' => 'Term', 80 'PRICE' => 'Price', 81 'DAY' => 'days', 82 'RUB' => 'RUB', 83 'KZT' => 'KZT', 84 'USD' => 'USD', 85 'EUR' => 'EUR', 86 'GBP' => 'GBP', 87 'CNY' => 'CNY', 88 'BYN' => 'BYN', 89 'UAH' => 'UAH', 90 'KGS' => 'KGS', 91 'AMD' => 'AMD', 92 'TRY' => 'TRY', 93 'THB' => 'THB', 94 'KRW' => 'KRW', 95 'AED' => 'AED', 96 'UZS' => 'UZS', 97 'MNT' => 'MNT', 98 'NODELIV' => 'Not delivery', 99 'CITYSEARCH' => 'Search for a city', 100 'ALL' => 'All', 101 'PVZ' => 'Points of self-delivery', 102 'POSTOMAT' => 'Postamats', 103 'MOSCOW' => 'Moscow', 104 'RUSSIA' => 'Russia', 105 'COUNTING' => 'Calculation', 106 107 'NO_AVAIL' => 'No shipping methods available', 108 'CHOOSE_TYPE_AVAIL' => 'Choose a shipping method', 109 'CHOOSE_OTHER_CITY' => 'Choose another location', 110 111 'TYPE_ADDRESS' => 'Specify the address', 112 'TYPE_ADDRESS_HERE' => 'Enter the delivery address', 113 114 'L_ADDRESS' => 'Adress of self-delivery', 115 'L_TIME' => 'Working hours', 116 'L_WAY' => 'How to get to us', 117 'L_CHOOSE' => 'Choose', 118 119 'H_LIST' => 'List of self-delivery', 120 'H_PROFILE' => 'Shipping method', 121 'H_CASH' => 'Payment by card', 122 'H_DRESS' => 'Dressing room', 123 'H_POSTAMAT' => 'Postamats CDEK', 124 'H_SUPPORT' => 'Support', 125 'H_QUESTIONS' => 'If you have any questions,<br> you can ask them to our specialists', 126 127 'ADDRESS_WRONG' => 'Impossible to define address. Please, recheck the address.', 128 'ADDRESS_ANOTHER' => 'Read the new terms and conditions.' 129 }, 130 }; 131 132 133 sub new { 134 my ($proto, %params) = @_; 135 my $class = ref($proto) || $proto; 136 my $m = shift; 137 138 my $self = {}; 139 bless $self, $class; 140 141 my $obj = $self->SUPER::new( { api_url => 'http://api.cdek.ru' } ); 142 $obj->{m} = $m; 143 144 $obj->{courierTariffPriority} = $state->{webshop}->cdek_courier_tariff_priority; 145 $obj->{pickupTariffPriority} = $state->{webshop}->cdek_pickup_tariff_priority; 146 $obj->{account} = $state->{webshop}->cdek_account; 147 $obj->{key} = $state->{webshop}->cdek_key; 148 149 return $obj; 150 } 151 152 153 ############################################################################################# 154 ### Actions 155 ############################################################################################# 156 sub getPVZ { 157 my $self = shift; 158 my $args = shift || {}; 159 my $langPart = $args->{'lang'} && exists $translate->{$args->{'lang'}} ? '&lang=' . $args->{'lang'} : ''; 160 my $cacheKey = 'cdek_getPVZ_' . $args->{'lang'} . '_cnt_' . $args->{country}; 161 if ( $keeper->MEMD && !$args->{no_cache} ) { 162 my $cached = $keeper->MEMD->get($cacheKey); 163 if ( $cached ) { 164 return { 'pvz' => $cached }; 165 } 166 } 167 my $result = $args->{raw_data} ? $args->{raw_data} : $self->_MakeRequest( 'https://integration.cdek.ru/pvzlist/v1/json?type=ALL' . $langPart, 'get', undef, { expires_at => 30 * 3600 } ); 168 my $out = { 169 'PVZ' => {}, 170 'CITY' => {}, 171 'REGIONS' => {}, 172 'CITYFULL' => {}, 173 'COUNTRIES' => {}, 174 }; 175 my $countryRequested = Encode::decode('utf-8', $args->{country}); 176 if ( $result->{code} == 200 && exists $result->{content}{pvz} && ref $result->{content}{pvz} eq 'ARRAY' ) { 177 # warn "getPVZ pvz found: ".scalar(@{$result->{content}{pvz}})."\n"; 178 foreach my $val ( @{$result->{content}{pvz}} ) { 179 if ( $countryRequested ne 'all' && $countryRequested ne $val->{countryName} ) { 180 next; 181 } 182 my $cityCode = $val->{cityCode}; 183 my $type = 'PVZ'; 184 my $city = $val->{city}; 185 if ( my $pos = index($city, '(') >= 0 ) { 186 $city = trim(substr($city, 0, $pos)); 187 } 188 if ( my $pos = index($city, ',') >= 0 ) { 189 $city = trim(substr($city, 0, $pos)); 190 } 191 my $code = $val->{code}; 192 $out->{$type}{$cityCode}{$code} = { 193 'Name' => $val->{'name'}, 194 'WorkTime' => $val->{'workTime'}, 195 'Address' => $val->{'address'}, 196 'Phone' => $val->{'phone'}, 197 'Note' => $val->{'note'}, 198 'cX' => $val->{'coordX'}, 199 'cY' => $val->{'coordY'}, 200 'Dressing' => Types::Serialiser::as_bool($val->{'isDressingRoom'}), 201 'Cash' => Types::Serialiser::as_bool($val->{'haveCashless'}), 202 'Postamat' => Types::Serialiser::as_bool(lc($val->{'type'}) eq 'postamat'), 203 'Station' => $val->{'nearestStation'}, 204 'Site' => $val->{'site'}, 205 'Metro' => $val->{'metroStation'}, 206 'AddressComment' => $val->{'addressComment'}, 207 'CityCode' => $val->{'CityCode'}, 208 }; 209 if ( exists $val->{weightLimit} ) { 210 $out->{$type}{$cityCode}{$code}{'WeightLim'} = { 211 'MIN' => $val->{weightLimit}{'weightMin'} * 1.0, 212 'MAX' => $val->{weightLimit}{'weightMax'} * 1.0, 213 }; 214 } 215 my @arImgs; 216 if ( exists $val->{officeImageList} && ref $val->{officeImageList} eq 'ARRAY' ) { 217 foreach my $img ( @{$val->{officeImageList}} ) { 218 unless ( $img->{'url'} =~ /^http/ ) { 219 next; 220 } 221 push @arImgs, $img->{'url'}; 222 } 223 } 224 225 if ( @arImgs ) { 226 $out->{$type}{$cityCode}{$code}{'Picture'} = \@arImgs; 227 } 228 if ( exists $val->{officeHowGo} ) { 229 $out->{$type}{$cityCode}{$code}{'Path'} = $val->{officeHowGo}{'url'}; 230 } 231 232 if (!exists $out->{'CITY'}{$cityCode} ) { 233 $out->{'CITY'}{$cityCode} = $city; 234 $out->{'CITYREG'}{$cityCode} = int($val->{'regionCode'}); 235 push @{ $out->{'REGIONSMAP'}{int($val->{'regionCode'})} }, int($cityCode); 236 $out->{'CITYFULL'}{$cityCode} = $val->{'countryName'} . ' ' . $val->{'regionName'} . ' ' . $city; 237 $out->{'REGIONS'}{$cityCode} = join(', ', $val->{'regionName'}, $val->{'countryName'} ); 238 } 239 } 240 if ( $keeper->MEMD ) { 241 $keeper->MEMD->set( $cacheKey, $out, 108000 ); 242 } 243 return { 'pvz' => $out }; 244 } 245 return { 'error' => 'Some error PVZ' }; 246 } 247 248 sub getLang { 249 my $self = shift; 250 my $args = shift || {}; 251 my $lang = $args->{'lang'} && exists $translate->{$args->{'lang'}} ? $args->{'lang'} : ''; 252 253 return { 'LANG' => $self->getValue( $translate, $lang, $translate->{'rus'} ) }; 254 } 255 256 257 sub getCity { 258 my $self = shift; 259 my $args = shift || {}; 260 261 warn Dumper $args; 262 my $city = $self->getValue($args, 'city'); 263 warn "Let's find for $city\n"; 264 if ( $city ) { 265 return $self->getCityByName( $city ); 266 } 267 268 if ( my $address = $self->getValue($args, 'address') ) { 269 return $self->getCityByAddress(Encode::decode('utf-8', $address)); 270 } 271 272 return { 'error' => 'No city to search given' }; 273 } 274 275 sub calc { 276 my $self = shift; 277 my $args = shift || {}; 278 279 my $shipment = $self->getRequestValue($args, 'shipment', {}); 280 warn Dumper($shipment); 281 282 if ( !exists $shipment->{'tariffList'} ) { 283 $shipment->{'tariffList'} = $self->getTariffPriority( $shipment->{'type'} ); 284 } 285 286 if ( $args->{referer} ) { 287 $shipment->{'ref'} = $args->{referer}; 288 } 289 290 if ( !exists $shipment->{'cityToId'} ) { 291 my $cityTo = $self->getCityByName( $shipment->{'cityTo'} ); 292 if ( !exists $cityTo->{error} && exists $cityTo->{id} ) { 293 $shipment->{'cityToId'} = $cityTo->{id}; 294 } 295 } 296 warn Dumper($shipment) if $DEBUG; 297 298 if ( $shipment->{'cityToId'} ) { 299 my $answer = $self->calculate($shipment); 300 301 if ( $answer ) { 302 my $returnData = { 303 'result' => $answer, 304 'type' => $shipment->{'type'}, 305 }; 306 if ( $shipment->{'timestamp'} ) { 307 $returnData->{'timestamp'} = $shipment->{'timestamp'}; 308 } 309 310 return $returnData; 311 } 312 } 313 314 315 return { 'error' => 'City to not found' }; 316 } 317 ### /Actions 318 319 ############################################################################################# 320 ### Helpers 321 ############################################################################################# 322 sub calculate { 323 my $self = shift; 324 my $shipment = shift || {}; 325 326 if ( !exists $shipment->{'goods'} || scalar @{$shipment->{'goods'}} == 0 ) { 327 return { 'error' => 'The dimensions of the goods are not defined' }; 328 } 329 330 my $headers = $self->getHeaders(); 331 332 my $arData = { 333 'dateExecute' => $self->getValue($headers, 'date'), 334 'version' => '1.0', 335 'authLogin' => $self->getValue($headers, 'account'), 336 'secure' => $self->getValue($headers, 'secure'), 337 'senderCityId' => $self->getValue( $shipment, 'cityFromId' ), 338 'receiverCityId' => $self->getValue($shipment, 'cityToId'), 339 'ref' => $self->getValue($shipment, 'ref'), 340 'widget' => 1, 341 'currency' => $self->getValue($shipment, 'currency', 'RUB'), 342 }; 343 344 if ( exists $shipment->{'tariffList'} ) { 345 my $priority = 1; 346 foreach my $tariffId ( @{$shipment->{'tariffList'}} ) { 347 $tariffId = int($tariffId); 348 push @{$arData->{'tariffList'}}, { 349 'priority' => $priority++, 350 'id' => $tariffId, 351 }; 352 } 353 } 354 355 $arData->{'goods'} = []; 356 foreach my $arGood ( @{$shipment->{'goods'}} ) { 357 push @{$arData->{'goods'}}, { 358 'weight' => $arGood->{'weight'}, 359 'length' => $arGood->{'length'}, 360 'width' => $arGood->{'width'}, 361 'height' => $arGood->{'height'}, 362 }; 363 } 364 365 my $type = $self->getValue($shipment, 'type'); 366 367 my $resultTariffs = $self->_MakeRequest( 368 '/calculator/calculate_tarifflist.php', 369 'post', 370 $arData, 371 ); 372 if ( $resultTariffs && $resultTariffs->{'code'} == 200 ) { 373 if ( ref $resultTariffs->{'content'} && exists $resultTariffs->{'content'}{'result'} ) { 374 my @resultTariffs = grep { $_->{status} } @{$resultTariffs->{'content'}{'result'}}; 375 my %resultTariffs; 376 foreach my $t ( @resultTariffs ) { 377 $resultTariffs{$t->{'tariffId'}} = $t->{result}; 378 } 379 warn Dumper \@resultTariffs; 380 381 if ( $type && !(exists $arData->{'tariffId'} && $arData->{'tariffId'}) ) { 382 my $tariffListSorted = $self->getTariffPriority($type); 383 foreach my $id ( @$tariffListSorted ) { 384 if ( exists $resultTariffs{$id} ) { 385 return $resultTariffs{$id}; 386 } 387 } 388 } 389 return shift @resultTariffs; 390 } 391 392 return { 'error' => 'Wrong server answer' }; 393 } 394 395 return { 'error' => 'Wrong answer code from server : ' . $resultTariffs->{'status'} }; 396 } 397 398 sub getTariffPriority { 399 my $self = shift; 400 my $type = shift // &COURIER_TARIFF_PRIORITY; 401 402 if ( $type ne &COURIER_TARIFF_PRIORITY && $type ne &PICKUP_TARIFF_PRIORITY ) { 403 warn "Unknown tariff type $type\n"; 404 } 405 406 return $type eq &COURIER_TARIFF_PRIORITY ? $self->{courierTariffPriority} : $self->{pickupTariffPriority}; 407 } 408 409 sub getCityByName { 410 my $self = shift; 411 my $name = shift; 412 my $single = shift // 1; 413 414 my $result = $self->_MakeRequest( '/city/getListByTerm/json.php?q=' . uri_escape($name) ); 415 if ( $result->{code} == 200 ) { 416 unless ( $result->{content}{geonames} ) { 417 return (undef, 'No cities found'); 418 } else { 419 if ($single) { 420 return { 421 'id' => $result->{content}->{geonames}->[0]->{id}, 422 'city' => $result->{content}->{geonames}->[0]->{cityName}, 423 'region' => $result->{content}->{geonames}->[0]->{regionName}, 424 'country' => $result->{content}->{geonames}->[0]->{countryName} 425 }; 426 } else { 427 my $arReturn = {'cities' => []}; 428 foreach my $city ( @{$result->{content}{geonames}} ) { 429 push @{$arReturn->{'cities'}}, { 430 'id' => $city->{id}, 431 'city' => $city->{cityName}, 432 'region' => $city->{regionName}, 433 'country' => $city->{countryName} 434 }; 435 } 436 return $arReturn; 437 } 438 } 439 } else { 440 return { 'error' => 'Wrong answer code from server : ' . $result->{'code'} }; 441 } 442 } 443 444 sub getCityByAddress { 445 my $self = shift; 446 my $address = shift; 447 448 my $arReturn = {}; 449 my $arStages = { 'country' => undef, 'region' => undef, 'subregion' => undef }; 450 my @arAddress = split /,/, $address; 451 452 my $ind = 0; 453 ### finging country in address 454 if ( grep { Encode::decode('utf-8', $arAddress[0]) =~ /$_/i } $self->getCountries() ) { 455 my $country = lc(Encode::decode('utf-8', $arAddress[0])); 456 for ($country) { 457 s/^\s+//; 458 s/\s+$//; 459 } 460 $arStages->{'country'} = Encode::encode('utf-8', $country); 461 $ind++; 462 } 463 464 ### finding region in address 465 foreach my $regionStr ( $self->getRegion() ) { 466 my $search = lc(Encode::decode('utf-8', $arAddress[$ind])); 467 for ($search) { 468 s/^\s+//; 469 s/\s+$//; 470 } 471 my $indSearch = index($search, $regionStr); 472 if ( $indSearch >= 0 ) { 473 if ($indSearch) { 474 $arStages->{'region'} = substr($search, 0, $indSearch); 475 } else { 476 $arStages->{'region'} = substr($search, length($regionStr)); 477 } 478 for ( $arStages->{'region'} ) { 479 s/^\s+//; 480 s/\s+$//; 481 } 482 $ind++; 483 last; 484 } 485 } 486 487 ### finding subregions 488 foreach my $subRegionStr ( $self->getSubRegion() ) { 489 my $search = lc(Encode::decode('utf-8', $arAddress[$ind])); 490 my $indSearch = index($search, $subRegionStr); 491 if ( $indSearch >= 0 ) { 492 if ($indSearch) { 493 $arStages->{'subregion'} = substr($search, 0, $indSearch); 494 } else { 495 $arStages->{'subregion'} = substr($search, length($subRegionStr)); 496 } 497 for ( $arStages->{'subregion'} ) { 498 s/^\s+//; 499 s/\s+$//; 500 } 501 $ind++; 502 last; 503 } 504 } 505 506 ### finding city 507 my $cityName = Encode::decode('utf-8', $arAddress[$ind]); 508 for ($cityName) { 509 s/^\s+//; 510 s/\s+$//; 511 } 512 my $cdekCity = $self->getCityByName(Encode::encode('utf-8', $cityName), 0); 513 514 if ( exists $cdekCity->{'error'} && $cdekCity->{'error'} ) { 515 foreach my $placeLbl ( $self->getCityDef() ) { 516 my $search = lc(Encode::decode('utf-8', $arAddress[$ind])); 517 for ( $search ) { 518 s/^\s+//; 519 s/\s+$//; 520 s/ё/е/sgi; 521 } 522 my $indSearch = index($search, $placeLbl); 523 if ( $indSearch >= 0 ) { 524 if ($indSearch) { 525 $search = substr($search, 0, $indSearch); 526 } else { 527 $search = substr($search, length($placeLbl)); 528 } 529 for ( $search ) { 530 s/^\s+//; 531 s/\s+$//; 532 } 533 $cityName = $search; 534 $cdekCity = $self->getCityByName(Encode::encode('utf-8', $search), 0); 535 last; 536 } 537 } 538 } 539 540 my $pretend; 541 if ( $cdekCity->{'error'} ) { 542 $arReturn->{'error'} = $cdekCity->{'error'}; 543 } else { 544 if ( ref $cdekCity->{'cities'} eq 'ARRAY' && scalar(@{$cdekCity->{'cities'}}) > 0) { 545 my $arPretend = []; 546 547 ## parseCountry 548 if ( $arStages->{'country'} ) { 549 foreach my $arCity ( @{$cdekCity->{'cities'}} ) { 550 my $possCountry = lc( Encode::decode('utf-8', $arCity->{'country'}) ); 551 if ( !$possCountry || index( Encode::decode('utf-8', $arStages->{'country'}), $possCountry) >= 0 ) { 552 push @$arPretend, $arCity; 553 } 554 } 555 } else { 556 $arPretend = $cdekCity->{'cities'}; 557 } 558 559 ## parseRegion 560 if ( $arStages->{'region'} && scalar @$arPretend > 1 ) { 561 my $_arPretend = []; 562 foreach my $arCity ( @$arPretend ) { 563 my $possRegion = lc( Encode::decode('utf-8', $arCity->{'region'}) ); 564 my $arStagesRegion = lc( Encode::decode('utf-8', $arStages->{'region'}) ); 565 foreach my $regpart ( $self->getRegion() ) { 566 $possRegion =~ s/$regpart//i; 567 $arStagesRegion =~ s/$regpart//i; 568 } 569 for ( $possRegion, $arStagesRegion ) { 570 s/^\s+//; 571 s/\s+$//; 572 } 573 if ( !$possRegion || index($possRegion, $arStagesRegion) >= 0 ) { 574 push @$_arPretend, $arCity; 575 } 576 } 577 $arPretend = $_arPretend; 578 } 579 580 ## parseSubRegion 581 if ( $arStages->{'subregion'} && scalar @$arPretend > 1 ) { 582 my $_arPretend = []; 583 foreach my $arCity ( @$arPretend ) { 584 my $possSubRegion = lc( Encode::decode('utf-8', $arCity->{'city'}) ); 585 if ( !$possSubRegion || index($possSubRegion, lc( Encode::decode('utf-8', $arStages->{'subregion'}) ) ) >= 0 ) { 586 push @$_arPretend, $arCity; 587 } 588 } 589 $arPretend = $_arPretend; 590 } 591 592 ## parseUndefined 593 ## not full city name 594 if ( scalar @$arPretend > 1 ) { 595 my $_arPretend = []; 596 foreach my $arCity ( @$arPretend ) { 597 if ( index($arCity->{'city'}, ',') >= 0 ) { 598 push @$_arPretend, $arCity; 599 } 600 } 601 $arPretend = $_arPretend; 602 } 603 604 if ( scalar @$arPretend > 1) { 605 my $_arPretend = []; 606 foreach my $arCity ( @$arPretend ) { 607 if ( length(Encode::decode('utf-8', $arCity->{'city'})) == length($cityName)) { 608 push @$_arPretend, $arCity; 609 } 610 } 611 $arPretend = $_arPretend; 612 } 613 614 ## federalCities 615 if ( scalar @$arPretend > 1 ) { 616 my $_arPretend = []; 617 foreach my $arCity ( @$arPretend ) { 618 if ( $arCity->{'city'} eq $arCity->{'region'} ) { 619 push @$_arPretend, $arCity; 620 } 621 } 622 $arPretend = $_arPretend; 623 } 624 625 626 ## end 627 if ( scalar @$arPretend ) { 628 $pretend = pop @$arPretend; 629 } 630 } else { 631 $pretend = $cdekCity->{'cities'}->[0]; 632 } 633 if ( $pretend ) { 634 $arReturn->{'city'} = $pretend; 635 } else { 636 $arReturn->{'error'} = 'Undefined city'; 637 } 638 } 639 return $arReturn; 640 } 641 642 sub getCountries { 643 return ('Россия', 'Беларусь', 'Армения', 'Казахстан', 'Киргизия', 'Молдова', 'Таджикистан', 'Узбекистан'); 644 } 645 646 sub getRegion { 647 return ('автономная область', 'область', 'республика', 'автономный округ', 'округ', 'край', 'обл.'); 648 } 649 650 sub getSubRegion { 651 return ('муниципальный район', 'район', 'городской округ'); 652 } 653 654 sub getCityDef { 655 return( 656 'поселок городского типа', 657 'населенный пункт', 658 'курортный поселок', 659 'дачный поселок', 660 'рабочий поселок', 661 'почтовое отделение', 662 'сельское поселение', 663 'ж/д станция', 664 'станция', 665 'городок', 666 'деревня', 667 'микрорайон', 668 'станица', 669 'хутор', 670 'аул', 671 'поселок', 672 'село', 673 'снт' 674 ); 675 } 676 677 sub getValue { 678 my ($self, $args, $key, $default) = @_; 679 $args //= {}; 680 681 return (exists $args->{$key} && $args->{$key} ? $args->{$key} : $default); 682 } 683 684 sub getHeaders { 685 my $self = shift; 686 687 my $date = Contenido::DateTime->new()->ymd('-'); 688 my $headers = { 689 'date' => $date, 690 }; 691 if ( $self->{account} && $self->{key} ) { 692 $headers = { 693 'date' => $date, 694 'account' => $self->{account}, 695 'secure' => Digest::MD5::md5_hex( $date . "&" . $self->{key} ), 696 }; 697 } 698 699 return $headers; 700 } 701 702 sub getRequestValue { 703 my ($self, $args, $key, $default) = @_; 704 $args //= {}; 705 706 my $out = {}; 707 foreach my $k ( keys %$args ) { 708 if ( $k eq $key ) { 709 return $self->getValue($args, $key, $default); 710 } elsif ( $k =~ /^$key/ && index($k, '[') > 0 ) { 711 my @levels; 712 while ( $k =~ /\[(.*?)\]/g ) { 713 my $name = $1; 714 push @levels, { type => $name =~ /\D/ ? 'hash' : 'array', key => $name }; 715 } 716 my $data = $out; 717 for ( my $i = 0; $i < scalar @levels; $i++ ) { 718 my $curr = $levels[$i]; 719 my $next = $i+1 < scalar @levels ? $levels[$i+1] : undef; 720 if ( $next ) { 721 if ( $curr->{type} eq 'hash' && !exists $data->{$curr->{key}} ) { 722 $data->{$curr->{key}} = $next->{type} eq 'hash' ? {} : []; 723 } elsif ( $curr->{type} eq 'array' && !defined $data->[$curr->{key}] ) { 724 $data->[$curr->{key}] = $next->{type} eq 'hash' ? {} : []; 725 } 726 } else { 727 $data->{$curr->{key}} = $args->{$k}; 728 last; 729 } 730 $data = $curr->{type} eq 'array' ? $data->[$curr->{key}] : $data->{$curr->{key}}; 731 } 732 } 733 } 734 735 return scalar %$out ? $out : $default; 736 } 737 738 sub trim { 739 my $val = shift; 740 741 for ( $val ) { 742 s/^\s+//; 743 s/\s+$//; 744 } 745 746 return $val; 747 } 748 749 1;