"FIO", "EMAIL" => "EMAIL", "PHONE" => "PHONE", "ZIP" => "ZIP", "CITY" => "CITY", "LOCATION" => "LOCATION", "ADDRESS" => "ADDRESS" ); protected $locationMapper = null; protected $active = true; protected static $isYandexRequest = false; public function __construct($arParams = array()) { $this->siteId = $this->getSiteId($arParams); $settings = $this->getSettingsBySiteId($this->siteId); if(isset($settings["OAUTH_TOKEN"])) $this->oAuthToken = $settings["OAUTH_TOKEN"]; if(isset($settings["OAUTH_CLIENT_ID"])) $this->oAuthClientId = $settings["OAUTH_CLIENT_ID"]; if(isset($settings["OAUTH_LOGIN"])) $this->oAuthLogin = $settings["OAUTH_LOGIN"]; if(isset($settings["DELIVERIES"])) $this->mapDelivery = $settings["DELIVERIES"]; if(isset($settings["OUTLETS_IDS"])) $this->outlets = $settings["OUTLETS_IDS"]; if(isset($settings["PAY_SYSTEMS"])) $this->mapPaySystems = $settings["PAY_SYSTEMS"]; if(isset($settings["PERSON_TYPE"])) $this->personTypeId = $settings["PERSON_TYPE"]; if(isset($settings["CAMPAIGN_ID"])) $this->campaignId = $settings["CAMPAIGN_ID"]; if(isset($settings["YANDEX_URL"])) $this->yandexApiUrl = $settings["YANDEX_URL"]; if(isset($settings["YANDEX_TOKEN"])) $this->yandexToken = $settings["YANDEX_TOKEN"]; if(isset($settings["AUTH_TYPE"])) $this->authType = $settings["AUTH_TYPE"]; if(isset($settings["DATA_FORMAT"])) $this->communicationFormat = $settings["DATA_FORMAT"]; if(isset($settings["LOG_LEVEL"])) $this->logLevel = $settings["LOG_LEVEL"]; if(isset($settings["ORDER_PROPS"]) && is_array($settings["ORDER_PROPS"])) $this->orderProps = $settings["ORDER_PROPS"]; $this->active = static::isActive(); $this->locationMapper = new CSaleYMLocation; } public static function isActive($cached = true) { $settings = static::getSettings($cached); return isset($settings["ACTIVE"]) && $settings["ACTIVE"] == "Y"; } /** * @param bool $activity Set or unset activity * @return \Bitrix\Main\Entity\UpdateResult|bool */ public static function setActivity($activity) { if($activity) static::eventsStart(); else static::eventsStop(); $settings = static::getSettings(); if($activity && empty($settings) && static::install()) { $settings = static::getSettings(false); } if(!empty($settings)) $result = Bitrix\Sale\TradingPlatformTable::update($settings["ID"], array("ACTIVE" => ($activity ? "Y" : "N"))); else $result = false; return $result; } protected function checkSiteId($siteId) { $result = false; $rsSites = CSite::GetList($b = "", $o = "", Array( "LID" => $siteId, "ACTIVE"=>"Y" )); if($arRes = $rsSites->Fetch()) $result = true; return $result; } protected function getSiteId($arParams) { $result = ""; if( isset($arParams["SITE_ID"]) && strlen($arParams["SITE_ID"]) > 0 && $this->checkSiteId($arParams["SITE_ID"]) ) { $result = $arParams["SITE_ID"]; } elseif(defined("SITE_ID")) { $result = SITE_ID; } else { $rsSites = CSite::GetList($b = "", $o = "", Array( "ACTIVE"=> "Y", "DEF" => "Y" )); if($arRes = $rsSites->Fetch()) $result = $arRes["LID"]; } return $result; } /** * Returns Yandex-Market settings * @param bool $cached Return cached or ont value * @return array|bool */ public static function getSettings($cached = true) { static $settings = null; if($settings === null || !$cached) { $settingsRes = Bitrix\Sale\TradingPlatformTable::getList(array( 'filter'=>array('=CODE' => static::TRADING_PLATFORM_CODE) )); $settings = $settingsRes->fetch(); if(!$settings) { $settings = array(); } elseif(isset($settings["SETTINGS"])) { $settings["SETTINGS"] = unserialize($settings["SETTINGS"]); } } return $settings; } /** * Returns yandex-market settings for concrete site * @param $siteId string Site idenifier * @param bool $cached Return cached or ont value * @return array */ public static function getSettingsBySiteId($siteId, $cached = true) { $settings = static::getSettings($cached); return isset($settings["SETTINGS"][$siteId]) ? $settings["SETTINGS"][$siteId] : array(); } /** * Saves settings * @param $arSettings array Settings array to save * @return bool */ public static function saveSettings($arSettings) { if(!is_array($arSettings)) return false; foreach ($arSettings as $siteId => $siteSett) { if(isset($siteSett["OUTLETS_IDS"]) && is_array($siteSett["OUTLETS_IDS"])) { $newOutletsIds = array(); foreach ($siteSett["OUTLETS_IDS"] as $outletId) if(strlen($outletId) > 0) $newOutletsIds[] = $outletId; $arSettings[$siteId]["OUTLETS_IDS"] = $newOutletsIds; } } $settings = static::getSettings(false); if(!empty($settings)) { $result = Bitrix\Sale\TradingPlatformTable::update($settings["ID"], array("SETTINGS" => serialize($arSettings))); } else { $result = false; } return $result; } protected function getProductById($productId, $quantity) { $arResult = array(); if(CModule::IncludeModule('catalog')) { if ($productProvider = CSaleBasket::GetProductProvider(array( "MODULE" => "catalog", "PRODUCT_PROVIDER_CLASS" => "CCatalogProductProvider")) ) { $arResult = $productProvider::GetProductData(array( "PRODUCT_ID" => $productId, "RENEWAL" => "N", "QUANTITY" => $quantity, "SITE_ID" => $this->siteId )); } } else { $arResult = $this->processError(self::ERROR_STATUS_500, GetMessage("SALE_YMH_ERROR_CATALOG_NOT_INSTALLED")); } return $arResult; } protected function getItemCartInfo($arItem, $currency) { $arResult = array(); $arProduct = $this->getProductById($arItem["offerId"], $arItem["count"]); if(isset($arProduct["error"])) { $arResult = $arProduct; } elseif(!empty($arProduct)) { $arResult = array( "feedId" => $arItem["feedId"], "offerId" => $arItem["offerId"], "price" => round(floatval($arProduct["PRICE"]), 2), "count" => $arProduct["QUANTITY"], "weight" => $arProduct["WEIGHT"] ); } return $arResult; } protected function getTimeInterval($period, $type) { return new DateInterval( 'P'. ($type =='H' ? 'T' : ''). intval($period). $type ); } protected function checkTimeInterval($today, $nextDate) { $interval = $today->diff($nextDate); return (intval($interval->format('%a')) <= 92); } protected function getDeliveryDates($from, $to, $type) { $from = intval($from); $to = intval($to); $arResult = array(); if($from <= $to) { $today = new DateTime(); $dateFrom = new DateTime(); $dateFrom->add($this->getTimeInterval($from, $type)); if($this->checkTimeInterval($today, $dateFrom)) { $arResult["fromDate"] = $dateFrom->format(self::DATE_FORMAT); if($to > 0 && $to != $from) { $dateTo = $today->add($this->getTimeInterval($to, $type)); if($this->checkTimeInterval($today, $dateTo)) $arResult["toDate"] = $dateTo->format(self::DATE_FORMAT); } } } return $arResult; } protected function getDeliveryOptions($delivery, $price, $weight = 0) { $arResult = array(); $locationId = $this->locationMapper->getLocationByCityName($delivery["region"]["name"]); if($locationId > 0) { foreach ($this->mapDelivery as $deliveryId => $deliveryType) { if($deliveryType == "") continue; $filter = array( "ID" => $deliveryId, "LID" => $this->siteId, "ACTIVE" => "Y", "LOCATION" => $locationId, "+<=ORDER_PRICE_FROM" => $price, "+>=ORDER_PRICE_TO" => $price ); if(intval($weight) > 0) { $filter["+<=WEIGHT_FROM"] = $weight; $filter["+>=WEIGHT_TO"] = $weight; } $dbDelivery = CSaleDelivery::GetList( array("SORT"=>"ASC", "NAME"=>"ASC"), $filter ); if($arDelivery = $dbDelivery->Fetch()) { $arDates = $this->getDeliveryDates( $arDelivery["PERIOD_FROM"], $arDelivery["PERIOD_TO"], $arDelivery["PERIOD_TYPE"] ); if(!empty($arDates)) { $arDeliveryTmp = array( "id" => $arDelivery["ID"], "type" =>$deliveryType, "serviceName" => substr($arDelivery["NAME"], 0, 50), "price" => round(floatval($arDelivery["PRICE"]), 2), "dates" => $arDates ); if($deliveryType == "PICKUP" && !empty($this->outlets)) foreach($this->outlets as $outlet) $arDeliveryTmp["outlets"][] = array("id" => intval($outlet)); $arResult[] = $arDeliveryTmp; } } } } return $arResult; } protected function getPaymentMethods() { $arResult = array(); foreach ($this->mapPaySystems as $psType => $psId) if(isset($psId) && intval($psId) > 0) $arResult[] = $psType; return $arResult; } protected function checkCartStructure($arPostData) { return isset($arPostData["cart"]) && isset($arPostData["cart"]["items"]) && is_array($arPostData["cart"]["items"]) && isset($arPostData["cart"]["currency"]) && isset($arPostData["cart"]["delivery"]) && is_array($arPostData["cart"]["delivery"]); } /* * POST /cart * max timeout 2s. */ protected function processCartRequest($arPostData) { $arResult = array(); if( $this->checkCartStructure($arPostData)) { $arResult["cart"] = array( "items" => array() ); $cartPrice = 0; $cartWeight = 0; $arResult["cart"] = array( "items" => array(), "paymentMethods" => array(), "deliveryOptions" => array() ); foreach ($arPostData["cart"]["items"] as $arItem) { $item = $this->getItemCartInfo($arItem, $arPostData["cart"]["currency"]); if(isset($item["error"])) { return array($item["error"]); } elseif(!empty($item)) { $cartPrice = $item["price"]*$item["count"]; $cartWeight = $item["weight"]*$item["count"]; unset($item["weight"]); $arResult["cart"]["items"][] = $item; } } if(!empty($arResult["cart"]["items"])) { $arResult["cart"]["deliveryOptions"] = $this->getDeliveryOptions($arPostData["cart"]["delivery"],$cartPrice, $cartWeight); if(!empty($arResult["cart"]["deliveryOptions"])) { foreach($arResult["cart"]["items"] as $item) { $item["delivery"] = true; } $arResult["cart"]["paymentMethods"] = $this->getPaymentMethods($arResult["cart"]["deliveryOptions"]); } else { $arResult["cart"]["items"] = array(); } } } else { $arResult = $this->processError(self::ERROR_STATUS_400, GetMessage("SALE_YMH_ERROR_BAD_STRUCTURE")); } return $arResult; } protected function checkOrderAcceptStructure($arPostData) { return isset($arPostData["order"]) && isset($arPostData["order"]["id"]) && isset($arPostData["order"]["currency"]) && isset($arPostData["order"]["fake"]) && isset($arPostData["order"]["items"]) && is_array($arPostData["order"]["items"]) && isset($arPostData["order"]["delivery"]) && is_array($arPostData["order"]["delivery"]); } protected function createUser($buyer, $address, $region) { $userRegister = array( "NAME" => $buyer["firstName"], "PERSONAL_MOBILE" => $buyer["phone"] ); if(isset($buyer["middleName"])) $userRegister["LAST_NAME"] = $buyer["middleName"]; if(isset($buyer["lastName"])) $userRegister["SECOND_NAME"] = $buyer["lastName"]; $arPersonal = array("PERSONAL_MOBILE" => $buyer["phone"]); $arErrors = array(); $userId = CSaleUser::DoAutoRegisterUser( $buyer["email"], $userRegister, $this->siteId, $arErrors, $arPersonal); $this->log( empty($arErrors) ? self::LOG_LEVEL_INFO : self::LOG_LEVEL_ERROR, "YMARKET_USER_CREATE", $userId ? $userId : print_r($buyer, true), empty($arErrors) ? GetMessage("SALE_YMH_USER_PROFILE_CREATED") : print_r($arErrors, true) ); return $userId; } protected function makeAdditionalOrderProps($address, $buyer, $psId, $deliveryId, $locationId) { $psId = intval($psId); $arResult = array(); $arPropFilter = array( "PERSON_TYPE_ID" => $this->personTypeId, "ACTIVE" => "Y" ); if ($psId != 0) { $arPropFilter["RELATED"]["PAYSYSTEM_ID"] = $psId; $arPropFilter["RELATED"]["TYPE"] = "WITH_NOT_RELATED"; } if (strlen($deliveryId) > 0) { $arPropFilter["RELATED"]["DELIVERY_ID"] = $deliveryId; $arPropFilter["RELATED"]["TYPE"] = "WITH_NOT_RELATED"; } $dbOrderProps = CSaleOrderProps::GetList( array(), $arPropFilter, false, false, array("ID", "CODE") ); while ($arOrderProps = $dbOrderProps->Fetch()) { if($arOrderProps["CODE"] == $this->orderProps["FIO"] && !empty($buyer)) { $fio = $buyer["firstName"]; if(isset($buyer["middleName"])) $fio .= ' '.$buyer["middleName"]; if(isset($buyer["lastName"])) $fio .= ' '.$buyer["lastName"]; $arResult[$arOrderProps["ID"]] = $fio; } elseif($arOrderProps["CODE"] == $this->orderProps["EMAIL"] && isset($buyer["email"])) $arResult[$arOrderProps["ID"]] = $buyer["email"]; elseif($arOrderProps["CODE"] == $this->orderProps["PHONE"] && isset($buyer["phone"])) $arResult[$arOrderProps["ID"]] = $buyer["phone"]; elseif($arOrderProps["CODE"] == $this->orderProps["ZIP" ] && isset($address["postcode"])) $arResult[$arOrderProps["ID"]] = $address["postcode"]; elseif($arOrderProps["CODE"] == $this->orderProps["CITY"]) $arResult[$arOrderProps["ID"]] = $address["city"]; elseif($arOrderProps["CODE"] == $this->orderProps["LOCATION"]) $arResult[$arOrderProps["ID"]] = $locationId; elseif($arOrderProps["CODE"] == $this->orderProps["ADDRESS"]) { $strAddr = ""; if(isset($address["postcode"])) $strAddr .= $address["postcode"].", "; $strAddr .= $address["country"].", ".$address["city"].", "; if(isset($address["street"])) $strAddr .= GetMessage("SALE_YMH_ADDRESS_STREET")." ".$address["street"].", "; if(isset($address["subway"])) $strAddr .= GetMessage("SALE_YMH_ADDRESS_SUBWAY")." ".$address["subway"].", "; $strAddr .= GetMessage("SALE_YMH_ADDRESS_HOUSE")." ".$address["house"]; if(isset($address["block"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_BLOCK")." ".$address["block"]; if(isset($address["entrance"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_ENTRANCE")." ".$address["entrance"]; if(isset($address["entryphone"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_ENTRYPHONE")." ".$address["entryphone"]; if(isset($address["floor"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_FLOOR")." ".$address["floor"]; if(isset($address["apartment"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_APARTMENT")." ".$address["apartment"]; if(isset($address["recipient"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_RECIPIENT")." ".$address["recipient"]; if(isset($address["phone"])) $strAddr .= ", ".GetMessage("SALE_YMH_ADDRESS_PHONE")." ".$address["phone"]; $arResult[$arOrderProps["ID"]] = $strAddr; } } return $arResult; } /* * POST /order/accept timeout 10s */ protected function processOrderAcceptRequest($arPostData) { $arResult = array(); if( $this->checkOrderAcceptStructure($arPostData)) { $dbOrder = CSaleOrder::GetList( array(), array("XML_ID" => self::XML_ID_PREFIX.$arPostData["order"]["id"]), false, false, array("ID") ); if(!$arOrder = $dbOrder->Fetch()) { require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/sale/general/admin_tool.php"); $arProducts = array(); foreach ($arPostData["order"]["items"] as $arItem) { $arProduct = $this->getProductById($arItem["offerId"], $arItem["count"]); $arProduct["PRODUCT_ID"] = $arItem["offerId"]; $arProduct["MODULE"] = "catalog"; $arProduct["PRODUCT_PROVIDER_CLASS"] = "CCatalogProductProvider"; $dbIblockElement = CIBlockElement::GetList(array(), array("ID" => $arItem["offerId"]), false, false, array('XML_ID', 'IBLOCK_EXTERNAL_ID')); if($IblockElement = $dbIblockElement->Fetch()) { if(strlen($IblockElement["XML_ID"]) > 0) $arProduct["PRODUCT_XML_ID"] = $IblockElement["XML_ID"]; if(strlen($IblockElement["IBLOCK_EXTERNAL_ID"]) > 0) $arProduct["CATALOG_XML_ID"] = $IblockElement["IBLOCK_EXTERNAL_ID"]; } if($arProduct["CAN_BUY"] == "Y") $arProducts[] = $arProduct; } $arOrderProductPrice = fGetUserShoppingCart($arProducts, $this->siteId, "N"); $arErrors = array(); $userId = intval(CSaleUser::GetAnonymousUserID()); $arShoppingCart = CSaleBasket::DoGetUserShoppingCart( $this->siteId, $userId, $arOrderProductPrice, $arErrors ); $deliveryId = $arPostData["order"]["delivery"]["id"]; $paySystemId = $this->mapPaySystems[$arPostData["order"]["paymentMethod"]]; $locationId = $this->locationMapper->getLocationByCityName($arPostData["order"]["delivery"]["region"]["name"]); if($locationId === false) { $this->log( self::LOG_LEVEL_INFO, "YMARKET_LOCATION_MAPPING", $arPostData["order"]["delivery"]["region"]["name"], GetMessage("SALE_YMH_LOCATION_NOT_FOUND") ); } $arErrors = $arWarnings = array(); $arOptions = array(); $arOrderPropsValues = $this->makeAdditionalOrderProps( $arPostData["order"]["delivery"]["address"], array(), $this->mapPaySystems[$arPostData["order"]["paymentMethod"]], $arPostData["order"]["delivery"]["id"], $locationId ); $CSaleOrder = new CSaleOrder(); $arOrder = $CSaleOrder->DoCalculateOrder( $this->siteId, $userId, $arShoppingCart, $this->personTypeId, $arOrderPropsValues, $deliveryId, $paySystemId, $arOptions, $arErrors, $arWarnings ); $arErrors = array(); $arAdditionalFields = array( "XML_ID" => self::XML_ID_PREFIX.$arPostData["order"]["id"], ); $arOrder["LID"] = $this->siteId; if(isset($arPostData["order"]["notes"])) $arAdditionalFields["USER_DESCRIPTION"] = $arPostData["order"]["notes"]; $orderID = $CSaleOrder->DoSaveOrder($arOrder, $arAdditionalFields, 0, $arErrors); } else { $orderID = $arOrder["ID"]; } if(intval($orderID > 0)) { $arResult["order"]["accepted"] = true; $arResult["order"]["id"] = strval($orderID); $this->log( self::LOG_LEVEL_INFO, "YMARKET_ORDER_CREATE", $arPostData["order"]["id"], GetMessage("SALE_YMH_ORDER_CREATED")." ".$orderID ); } else { $arResult["order"]["accepted"] = false; $arResult["order"]["reason"] = "OUT_OF_DATE"; $this->log( self::LOG_LEVEL_ERROR, "YMARKET_ORDER_CREATE", $arPostData["order"]["id"], print_r($arErrors, true) ); } } else { $arResult = $this->processError(self::ERROR_STATUS_400, GetMessage("SALE_YMH_ERROR_BAD_STRUCTURE")); } return $arResult; } protected function checkOrderStatusRequest($arPostData) { return (isset($arPostData["order"]) && isset($arPostData["order"]["id"]) && isset($arPostData["order"]["currency"]) && isset($arPostData["order"]["creationDate"]) && isset($arPostData["order"]["itemsTotal"]) && isset($arPostData["order"]["total"]) && isset($arPostData["order"]["status"]) && isset($arPostData["order"]["fake"]) && isset($arPostData["order"]["buyer"]) && isset($arPostData["order"]["items"]) && is_array($arPostData["order"]["items"]) && isset($arPostData["order"]["delivery"]) && is_array($arPostData["order"]["delivery"])) || true; } /* * POST /order/status timeout 10s */ protected function processOrderStatusRequest($arPostData) { $arResult = array(); if($this->checkOrderStatusRequest($arPostData)) { $dbOrder = CSaleOrder::GetList( array(), array("XML_ID" => self::XML_ID_PREFIX.$arPostData["order"]["id"]) ); if($arOrder = $dbOrder->Fetch()) { $reason = ""; switch ($arPostData["order"]["status"]) { case 'PROCESSING': $locationId = $this->locationMapper->getLocationByCityName($arPostData["order"]["delivery"]["region"]["name"]); if($locationId === false) { $this->log( self::LOG_LEVEL_INFO, "YMARKET_LOCATION_MAPPING", $arPostData["order"]["delivery"]["region"]["name"], GetMessage("SALE_YMH_LOCATION_NOT_FOUND") ); } $arOrderPropsValues = $this->makeAdditionalOrderProps( $arPostData["order"]["delivery"]["address"], $arPostData["order"]["buyer"], isset($this->mapPaySystems[$arPostData["order"]["paymentMethod"]]) ? $this->mapPaySystems[$arPostData["order"]["paymentMethod"]] : "", $arPostData["order"]["delivery"]["id"], $locationId ); $arErrors = array(); CSaleOrderProps::DoSaveOrderProps( $arOrder["ID"], $this->personTypeId, $arOrderPropsValues, $arErrors ); $this->sendEmailNewOrder($arOrder["ID"], $arPostData["order"]["buyer"]); if(!empty($arErrors)) { $this->log( self::LOG_LEVEL_ERROR, "YMARKET_INCOMING_ORDER_STATUS", $arPostData["order"]["id"], print_r($arErrors, true)); } else { $this->log( self::LOG_LEVEL_INFO, "YMARKET_INCOMING_ORDER_STATUS", $arPostData["order"]["id"], GetMessage("SALE_YMH_INCOMING_ORDER_STATUS_PROCESSING").": ".$arOrder["ID"] ); } if(isset($arPostData["order"]["paymentMethod"]) && $arPostData["order"]["paymentMethod"] == "YANDEX") CSaleOrder::PayOrder($arOrder["ID"], "Y"); break; case 'UNPAID': case 'DELIVERY': case 'PICKUP': case 'DELIVERED ': break; case 'CANCELLED': if(isset($arPostData["order"]["substatus"])) $reason = GetMessage("SALE_YMH_SUBSTATUS_".$arPostData["order"]["substatus"]); break; default: $arResult = $this->processError(self::ERROR_STATUS_400, GetMessage("SALE_YMH_ERROR_UNKNOWN_STATUS")); break; } $this->mapYandexStatusToOrder($arOrder, $arPostData["order"]["status"], $reason); } } else { $arResult = $this->processError(self::ERROR_STATUS_400, GetMessage("SALE_YMH_ERROR_BAD_STRUCTURE")); } return $arResult; } protected function extractPostData($postData) { global $APPLICATION; $arResult = array(); if($this->communicationFormat == self::JSON) { $arResult = json_decode($postData, true); } if(strtolower(SITE_CHARSET) != 'utf-8') $arResult = $APPLICATION->ConvertCharsetArray($arResult, 'utf-8', SITE_CHARSET); return $arResult; } protected function prepareResult($arData) { global $APPLICATION; $result = array(); if(strtolower(SITE_CHARSET) != 'utf-8') $arData = $APPLICATION->ConvertCharsetArray($arData, SITE_CHARSET, 'utf-8'); if($this->communicationFormat == self::JSON) { header('Content-Type: application/json'); $result = json_encode($arData); } return $result; } /** * Let's check authorization, * comparing incoming token with token stored in settings. * @return bool */ public function checkAuth() { $incomingToken = ""; if($this->authType == "HEADER") { if(isset($_SERVER["REMOTE_USER"]) && strlen($_SERVER["REMOTE_USER"]) > 0) $incomingToken = $_SERVER["REMOTE_USER"]; elseif(isset($_SERVER["REDIRECT_REMOTE_USER"]) && strlen($_SERVER["REDIRECT_REMOTE_USER"]) > 0) $incomingToken = $_SERVER["REDIRECT_REMOTE_USER"]; elseif(isset($_SERVER["HTTP_AUTHORIZATION"]) && strlen($_SERVER["HTTP_AUTHORIZATION"]) > 0) $incomingToken = $_SERVER["HTTP_AUTHORIZATION"]; } elseif($this->authType == "URL") { if(isset($_REQUEST["auth-token"]) && strlen($_REQUEST["auth-token"]) > 0) $incomingToken = $_REQUEST["auth-token"]; } if($incomingToken == "" && intval($_SERVER["argc"]) > 0) { foreach ($_SERVER["argv"] as $arg) { $e = explode("=", $arg); if(count($e) == 2 && $e[0] == "auth-token") $incomingToken = $e[1]; } } return strlen($incomingToken) > 0 && $incomingToken == $this->yandexToken; } public function processRequest($reqObject, $method, $postData) { $this->log( self::LOG_LEVEL_DEBUG, "YMARKET_INCOMING_REQUEST", $reqObject.":".$method, print_r($postData, true) ); $arResult = array(); if(!$this->isActive()) { $arResult = $this->processError(self::ERROR_STATUS_503, GetMessage("SALE_YMH_ERROR_OFF")); } elseif(!$this->checkAuth()) { $arResult = $this->processError(self::ERROR_STATUS_403, GetMessage("SALE_YMH_ERROR_FORBIDDEN")); } else { self::$isYandexRequest = true; $arPostData = $this->extractPostData($postData); DiscountCouponsManager::init(DiscountCouponsManager::MODE_EXTERNAL); switch ($reqObject) { case 'cart': $arResult = $this->processCartRequest($arPostData); break; case 'order': if($method == "accept") $arResult = $this->processOrderAcceptRequest($arPostData); elseif($method == "status") $arResult = $this->processOrderStatusRequest($arPostData); break; default: $arResult = $this->processError(self::ERROR_STATUS_400, GetMessage("SALE_YMH_ERROR_UNKNOWN_REQ_OBJ")); break; } } $this->log( self::LOG_LEVEL_DEBUG, "YMARKET_INCOMING_REQUEST_RESULT", $reqObject.":".$method, print_r($arResult, true) ); $arPreparedResult = $this->prepareResult($arResult); return $arPreparedResult; } protected function processError($status = "", $message = "") { if($status != "") CHTTP::SetStatus($status); if($message && $this->logLevel >= self::LOG_LEVEL_ERROR) $this->log( self::LOG_LEVEL_ERROR, "YMARKET_REQUEST_ERROR", "", $message); return array("error" => $message); } public function sendStatus($orderId, $status, $substatus = false) { global $APPLICATION; if( strlen($this->yandexApiUrl) <= 0 || strlen($this->campaignId) <= 0 || intval($orderId) <= 0 || strlen($status) <=0 || strlen($this->oAuthToken) <=0 || strlen($this->oAuthClientId) <=0 || strlen($this->oAuthLogin) <=0 ) return false; $format = $this->communicationFormat == self::JSON ? 'json' : 'xml'; $url = $this->yandexApiUrl."campaigns/".$this->campaignId."/orders/".$orderId."/status.".$format; $http = new \Bitrix\Main\Web\HttpClient(array( "version" => "1.1", "socketTimeout" => 30, "streamTimeout" => 30, "redirect" => true, "redirectMax" => 5, )); $arQuery = array( "order" => array( "status" => $status, ) ); if($substatus) $arQuery["order"]["substatus"] = $substatus; if(strtolower(SITE_CHARSET) != 'utf-8') $arQuery = $APPLICATION->ConvertCharsetArray($arQuery, SITE_CHARSET, 'utf-8'); $postData = ''; if($this->communicationFormat == self::JSON) $postData = json_encode($arQuery); $http->setHeader("Content-Type", "application/".$format); $http->setHeader("Authorization", 'OAuth oauth_token="'.$this->oAuthToken. '", oauth_client_id="'.$this->oAuthClientId. '", oauth_login="'.$this->oAuthLogin.'"', false); $result = $http->query("PUT", $url, $postData); $errors = $http->getError(); if (!$result && !empty($errors)) { $bResult = false; $message = "HTTP ERROR: "; foreach($errors as $errorCode => $errMes) $message .= $errorCode.": ".$errMes; } else { $headerStatus = $http->getStatus(); if ($headerStatus == 200) { $message = GetMessage("SALE_YMH_STATUS").": ".$status; $bResult = true; } else { $res = $http->getResult(); $message = "HTTP error code: ".$headerStatus."(".$res.")"; if($headerStatus =="403") $this->notifyAdmin("SEND_STATUS_ERROR_403"); $bResult = false; } } $this->log( $bResult ? self::LOG_LEVEL_INFO : self::LOG_LEVEL_ERROR, "YMARKET_STATUS_CHANGE", $orderId, $message ); return $bResult; } public function getOrderInfo($orderId) { $arOrder = array(); $dbOrder = CSaleOrder::GetList( array(), array("ID" => $orderId), false, false, array("XML_ID", "LID") ); if($arOrder = $dbOrder->Fetch()) if(!is_null($arOrder["XML_ID"]) && strpos($arOrder["XML_ID"], self::XML_ID_PREFIX) !== false) $arOrder["YANDEX_ID"] = substr($arOrder["XML_ID"], strlen(self::XML_ID_PREFIX)); return $arOrder; } public static function isOrderFromYandex($orderId) { $arOrder = self::getOrderInfo($orderId); return isset($arOrder["YANDEX_ID"]); } /** * Executes when order's status was changed in shop * event OnSaleCancelOrder * @param int $orderId Identifier * @param string $status New status * @param string $substatus Substatus. * @return bool */ public function onSaleStatusOrder($orderId, $status, $substatus = false) { $result = false; $arOrder = self::getOrderInfo($orderId); if(!empty($arOrder) && isset($arOrder["YANDEX_ID"]) && !self::$isYandexRequest) { $YMHandler = new CSaleYMHandler( array("SITE_ID"=> $arOrder["LID"]) ); $settings = $YMHandler->getSettingsBySiteId($arOrder["LID"]); if(!isset($settings["STATUS_OUT"][$status]) || strlen($settings["STATUS_OUT"][$status]) <= 0) return false; $yandexStatus = $settings["STATUS_OUT"][$status]; $YMHandler->sendStatus($arOrder["YANDEX_ID"], $yandexStatus, $substatus); $result = true; } return $result; } public static function onSaleCancelOrder($orderId, $value, $description) { if($value != "Y" || self::$isYandexRequest) return false; global $USER; $arSubstatuses = self::getOrderSubstatuses(); if(strlen($description) <= 0 || !$USER->IsAdmin() || !in_array(trim($description), $arSubstatuses)) $description = "USER_CHANGED_MIND"; else $description = array_search(trim($description), $arSubstatuses); return self::onSaleStatusOrder($orderId, "CANCELED", $description); } public static function onSaleDeliveryOrder($orderId, $value) { if($value != "Y" || self::$isYandexRequest) return false; return self::onSaleStatusOrder($orderId, "ALLOW_DELIVERY"); } public static function onSalePayOrder($orderId, $value) { if($value != "Y" || self::$isYandexRequest) return false; return self::onSaleStatusOrder($orderId, "PAYED"); } public static function onSaleDeductOrder($orderId, $value) { if($value != "Y" || self::$isYandexRequest) return false; return self::onSaleStatusOrder($orderId, "DEDUCTED"); } public static function getOrderSubstatuses() { return array( "USER_UNREACHABLE" => GetMessage("SALE_YMH_SUBSTATUS_USER_UNREACHABLE"), "USER_CHANGED_MIND" => GetMessage("SALE_YMH_SUBSTATUS_USER_CHANGED_MIND"), "USER_REFUSED_DELIVERY"=> GetMessage("SALE_YMH_SUBSTATUS_USER_REFUSED_DELIVERY"), "USER_REFUSED_PRODUCT" => GetMessage("SALE_YMH_SUBSTATUS_USER_REFUSED_PRODUCT"), "SHOP_FAILED" => GetMessage("SALE_YMH_SUBSTATUS_SHOP_FAILED"), "REPLACING_ORDER" => GetMessage("SALE_YMH_SUBSTATUS_REPLACING_ORDER") ); } public static function getCancelReasonsAsSelect($name, $val=false, $id=false) { $arStatuses = self::getOrderSubstatuses(); $result = ''; return $result; } public static function getCancelReasonsAsRadio($name, $id=false, $val=false) { $result = ""; $arStatuses = self::getOrderSubstatuses(); $start = 0; if($id === false) $id = "cancelreasonid_".rand(); foreach ($arStatuses as $statusId => $statusName) { $tmpId = $id.'_'.($start++); $result .= '
'. ''; } return $result; } public function OnEventLogGetAuditTypes() { return array( "YMARKET_STATUS_CHANGE" => "[YMARKET_STATUS_CHANGE] ".GetMessage("SALE_YMH_LOG_TYPE_STATUS_CHANGE"), "YMARKET_INCOMING_ORDER_STATUS" => "[YMARKET_INCOMING_ORDER_STATUS] ".GetMessage("SALE_YMH_LOG_TYPE_INCOMING_ORDER_STATUS"), "YMARKET_USER_CREATE" => "[YMARKET_USER_CREATE] ".GetMessage("SALE_YMH_LOG_TYPE_USER_CREATE"), "YMARKET_ORDER_CREATE" => "[YMARKET_ORDER_CREATE] ".GetMessage("SALE_YMH_LOG_TYPE_ORDER_CREATE"), "YMARKET_REQUEST_ERROR" => "[YMARKET_REQUEST_ERROR] ".GetMessage("SALE_YMH_LOG_TYPE_REQUEST_ERROR"), "YMARKET_INCOMING_REQUEST" => "[YMARKET_INCOMING_REQUEST] ".GetMessage("SALE_YMH_LOG_TYPE_INCOMING_REQUEST"), "YMARKET_INCOMING_REQUEST_RESULT" => "[YMARKET_INCOMING_REQUEST_RESULT] ".GetMessage("SALE_YMH_LOG_TYPE_INCOMING_REQUEST_RESULT"), "YMARKET_LOCATION_MAPPING" => "[YMARKET_LOCATION_MAPPING] ".GetMessage("SALE_YMH_LOG_TYPE_YMARKET_LOCATION_MAPPING"), "YMARKET_ORDER_STATUS_CHANGE" => "[YMARKET_ORDER_STATUS_CHANGE] ".GetMessage("SALE_YMH_LOG_TYPE_ORDER_STATUS_CHANGE"), ); } protected function log($level, $type, $itemId, $description) { if($this->logLevel < $level) return false; CEventLog::Add(array( "SEVERITY" => $level >= CSaleYMHandler::LOG_LEVEL_ERROR ? "WARNING" : "NOTICE", "AUDIT_TYPE_ID" => $type, "MODULE_ID" => "sale", "ITEM_ID" => $itemId, "DESCRIPTION" => $description, )); return true; } protected function getLocationByCityName($cityName) { return $this->locationMapper->getLocationByCityName($cityName); } protected function mapYandexStatusToOrder($order, $yandexStatus, $cancelReason="") { global $APPLICATION; if(!is_array($order) || !isset($order["ID"]) || strlen($yandexStatus) <= 0) return false; $settings = $this->getSettingsBySiteId($order["LID"]); if(!isset($settings["STATUS_IN"][$yandexStatus]) || strlen($settings["STATUS_IN"][$yandexStatus]) <= 0) return false; $result = false; $bitrixStatus = $settings["STATUS_IN"][$yandexStatus]; switch($bitrixStatus) { /* flags */ case "CANCELED": $errorMessageTmp = ""; $result = CSaleOrder::CancelOrder($order["ID"], "Y", $cancelReason); if (!$result) { if ($ex = $APPLICATION->GetException()) { if ($ex->GetID() != "ALREADY_FLAG") $errorMessageTmp .= $ex->GetString(); } else $errorMessageTmp .= GetMessage("ERROR_CANCEL_ORDER").". "; } if($errorMessageTmp != "") { $this->log( self::LOG_LEVEL_ERROR, "YMARKET_INCOMING_ORDER_STATUS", $order["XML_ID"], $errorMessageTmp ); } else { $this->log( self::LOG_LEVEL_INFO, "YMARKET_INCOMING_ORDER_STATUS", $order["XML_ID"], GetMessage("SALE_YMH_INCOMING_ORDER_STATUS_CANCELED").": ".$order["ID"] ); } break; case "ALLOW_DELIVERY": $result = CSaleOrder::DeliverOrder($order["ID"], "Y"); break; case "PAYED": $result = CSaleOrder::PayOrder($order["ID"], "Y"); break; case "DEDUCTED": $result = CSaleOrder::DeductOrder($order["ID"], "Y"); break; /* statuses */ default: if(CSaleStatus::GetByID($bitrixStatus)) { $result = CSaleOrder::StatusOrder($order["ID"], $bitrixStatus); } break; } $this->log( $result ? self::LOG_LEVEL_INFO : self::LOG_LEVEL_ERROR, "YMARKET_ORDER_STATUS_CHANGE", $order["ID"], ($result ? GetMessage("SALE_YMH_LOG_TYPE_ORDER_STATUS_CHANGE_OK") : GetMessage("SALE_YMH_LOG_TYPE_ORDER_STATUS_CHANGE_ERROR"))." (".$bitrixStatus.")" ); return $result; } /** * Starts exchange information between Yandex-market and shop * @return bool */ public static function eventsStart() { RegisterModuleDependences("sale", "OnSaleStatusOrder", "sale", "CSaleYMHandler", "onSaleStatusOrder", 100); RegisterModuleDependences("sale", "OnSaleCancelOrder", "sale", "CSaleYMHandler", "onSaleCancelOrder", 100); RegisterModuleDependences("sale", "OnSalePayOrder", "sale", "CSaleYMHandler", "onSalePayOrder", 100); RegisterModuleDependences("sale", "OnSaleDeliveryOrder", "sale", "CSaleYMHandler", "onSaleDeliveryOrder", 100); RegisterModuleDependences("sale", "OnSaleDeductOrder", "sale", "CSaleYMHandler", "onSaleDeductOrder", 100); return true; } /** * Stops exchange information between Yandex-market and shop * @return bool */ public static function eventsStop() { UnRegisterModuleDependences("sale", "OnSaleStatusOrder", "sale", "CSaleYMHandler", "onSaleStatusOrder"); UnRegisterModuleDependences("sale", "OnSaleCancelOrder", "sale", "CSaleYMHandler", "onSaleCancelOrder"); UnRegisterModuleDependences("sale", "OnSalePayOrder", "sale", "CSaleYMHandler", "onSalePayOrder"); UnRegisterModuleDependences("sale", "OnSaleDeliveryOrder", "sale", "CSaleYMHandler", "onSaleDeliveryOrder"); UnRegisterModuleDependences("sale", "OnSaleDeductOrder", "sale", "CSaleYMHandler", "onSaleDeductOrder"); return true; } /** * Installs service * @return bool */ public static function install() { $settings = static::getSettings(); if(empty($settings)) { $res = Bitrix\Sale\TradingPlatformTable::add(array( "CODE" => static::TRADING_PLATFORM_CODE, "ACTIVE" => "N", "NAME" => GetMessage("SALE_YMH_NAME"), "DESCRIPTION" => GetMessage("SALE_YMH_DESCRIPTION"), "SETTINGS" => "", )); $b = "sort"; $o = "asc"; $dbSites = \CSite::GetList($b, $o, array("ACTIVE" => "Y")); while ($site = $dbSites->Fetch()) { \CUrlRewriter::Add( array( "CONDITION" => "#^/bitrix/services/ymarket/#", "RULE" => "", "ID" => "", "PATH" => "/bitrix/services/ymarket/index.php", "SITE_ID" => $site["ID"] ) ); } } else { $res = true; } return $res ? true : false; } /** * Uninstalls service * @param bool $deleteRecord Delete, or not table record about this service */ public static function unInstall($deleteRecord = true) { static::eventsStop(); $settings = static::getSettings(); if(!empty($settings)) { if($deleteRecord) Bitrix\Sale\TradingPlatformTable::delete($settings["ID"]); else static::setActivity(false); } \CUrlRewriter::Delete( array( "CONDITION" => "#^/bitrix/services/ymarket/#", "PATH" => "/bitrix/services/ymarket/index.php" ) ); } /** * Moves settings from options to DB */ public static function settingsConverter() { $settings = static::getSettings(); if(!empty($settings) && !empty($settings["SETTINGS"])) { return false; } if(!CSaleYMHandler::install()) { return false; } $settings = array(); $rsSites = CSite::GetList($by = "sort", $order = "asc", Array()); while ($arSite = $rsSites->Fetch()) { $serSiteSett = COption::GetOptionString("sale", "yandex_market_purchase_settings", "", $arSite["ID"], true); $siteSett = unserialize($serSiteSett); if(is_array($siteSett) && !empty($siteSett)) $settings[$arSite["ID"]] = $siteSett; } if(empty($settings)) { $serSiteSett = COption::GetOptionString("sale", "yandex_market_purchase_settings", ""); $siteSett = unserialize($serSiteSett); if(is_array($siteSett) && !empty($siteSett)) $settings[CSite::GetDefSite()] = $siteSett; } if(empty($settings)) { return false; } if(!CSaleYMHandler::saveSettings($settings)) { return false; } if(!CSaleYMHandler::setActivity(true)) { return false; } if(!CSaleYMHandler::eventsStart()) { return false; } return true; } protected function sendEmailNewOrder($newOrderId, $buyer) { global $DB; $strOrderList = ""; $baseLangCurrency = CSaleLang::GetLangCurrency($this->siteId); $orderNew = CSaleOrder::GetByID($newOrderId); $orderNew["BASKET_ITEMS"] = array(); $userEmail = $buyer["email"]; $fio = $buyer["last-name"].(isset($buyer["first-name"]) ? $buyer["first-name"] : ""); $dbBasketTmp = CSaleBasket::GetList( array("SET_PARENT_ID" => "DESC", "TYPE" => "DESC", "NAME" => "ASC"), array("ORDER_ID" => $newOrderId), false, false, array( "ID", "PRICE", "QUANTITY", "NAME" ) ); while ($arBasketTmp = $dbBasketTmp->GetNext()) { $orderNew["BASKET_ITEMS"][] = $arBasketTmp; } $orderNew["BASKET_ITEMS"] = getMeasures($orderNew["BASKET_ITEMS"]); foreach ($orderNew["BASKET_ITEMS"] as $val) { if (CSaleBasketHelper::isSetItem($val)) continue; $measure = (isset($val["MEASURE_TEXT"])) ? $val["MEASURE_TEXT"] : GetMessage("SALE_YMH_SHT"); $strOrderList .= $val["NAME"]." - ".$val["QUANTITY"]." ".$measure.": ".SaleFormatCurrency($val["PRICE"], $baseLangCurrency); $strOrderList .= "\n"; } //send mail $arFields = array( "ORDER_ID" => $orderNew["ACCOUNT_NUMBER"], "ORDER_DATE" => Date($DB->DateFormatToPHP(CLang::GetDateFormat("SHORT", $this->siteId))), "ORDER_USER" => $fio, "PRICE" => SaleFormatCurrency($orderNew["PRICE"], $baseLangCurrency), "BCC" => COption::GetOptionString("sale", "order_email", "order@".$_SERVER['SERVER_NAME']), "EMAIL" => array("PAYER_NAME" => $fio, "USER_EMAIL" => $userEmail), "ORDER_LIST" => $strOrderList, "SALE_EMAIL" => COption::GetOptionString("sale", "order_email", "order@".$_SERVER['SERVER_NAME']), "DELIVERY_PRICE" => $orderNew["DELIVERY_PRICE"], ); $eventName = "SALE_NEW_ORDER"; $bSend = true; foreach(GetModuleEvents("sale", "OnOrderNewSendEmail", true) as $arEvent) if (ExecuteModuleEventEx($arEvent, array($newOrderId, &$eventName, &$arFields))===false) $bSend = false; if($bSend) { $event = new CEvent; $event->Send($eventName, $this->siteId, $arFields, "N"); } CSaleMobileOrderPush::send("ORDER_CREATED", array("ORDER" => $orderNew)); } protected function notifyAdmin($code) { $tag = "YANDEX_MARKET_".$code; $problemsCount = intval(\Bitrix\Main\Config\Option::get("sale", $tag, 0, $this->siteId)); if($problemsCount < 3) { \Bitrix\Main\Config\Option::set("sale", $tag, $problemsCount+1, $this->siteId); return false; } $dbRes = CAdminNotify::GetList(array(), array("TAG" => $tag)); if($res = $dbRes->Fetch()) return false; CAdminNotify::Add(array( "MESSAGE" => GetMessage("SALE_YMH_ADMIN_NOTIFY_".$code, array("##LANGUAGE_ID##" => LANGUAGE_ID)), "TAG" => "YANDEX_MARKET_".$code, "MODULE_ID" => "SALE", "ENABLE_CLOSE" => "Y" ) ); \Bitrix\Main\Config\Option::set("sale", $tag, 0, $this->siteId); return true; } }