diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php
index 4cfdf27fd0e6ab560cf296be35bdb8c5ea8e651e..3e8ae11c4faf362831ead26e549621ea483b9c7c 100644
--- a/app/code/Magento/Bundle/Model/Product/Type.php
+++ b/app/code/Magento/Bundle/Model/Product/Type.php
@@ -546,6 +546,7 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType
             $selectionsCollection = $this->_bundleCollection->create();
             $selectionsCollection->addAttributeToSelect('status');
             $selectionsCollection->addQuantityFilter();
+            $selectionsCollection->setFlag('product_children', true);
             $selectionsCollection->addFilterByRequiredOptions();
             $selectionsCollection->setOptionIdsFilter([$option->getId()]);
 
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php
index 9f7952e7ae8c882a5b747f9fd24855feea7c380e..10ca7335668470db793498273a998b64e34f4c4f 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php
@@ -8,60 +8,67 @@ namespace Magento\Bundle\Test\Unit\Model\Product;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
 
 /**
+ * Test for Model ProductPrice.
+ *
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class PriceTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\CatalogRule\Model\ResourceModel\RuleFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $ruleFactoryMock;
+    private $ruleFactoryMock;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $localeDateMock;
+    private $localeDateMock;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $storeManagerMock;
+    private $storeManagerMock;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $customerSessionMock;
+    private $customerSessionMock;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $eventManagerMock;
+    private $eventManagerMock;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Catalog\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $catalogHelperMock;
+    private $catalogHelperMock;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $storeMock;
+    private $storeMock;
 
     /**
      * @var \Magento\Bundle\Model\Product\Price
      */
-    protected $model;
+    private $model;
 
     /**
      * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $priceCurrency;
+    private $priceCurrency;
 
     /**
-     * @var \Magento\Customer\Api\GroupManagementInterface
+     * @var \Magento\Customer\Api\GroupManagementInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $groupManagement;
+    private $groupManagement;
 
+    /**
+     * Set up.
+     *
+     * @return void
+     */
     protected function setUp()
     {
         $this->ruleFactoryMock = $this->getMock(
@@ -90,6 +97,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase
             false
         );
         $scopeConfig = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
+
         $objectManagerHelper = new ObjectManagerHelper($this);
         $this->model = $objectManagerHelper->getObject(
             \Magento\Bundle\Model\Product\Price::class,
@@ -109,6 +117,8 @@ class PriceTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
+     * Test for calculateSpecialPrice().
+     *
      * @param float $finalPrice
      * @param float $specialPrice
      * @param int $callsNumber
@@ -118,6 +128,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase
      * @covers \Magento\Bundle\Model\Product\Price::calculateSpecialPrice
      * @covers \Magento\Bundle\Model\Product\Price::__construct
      * @dataProvider calculateSpecialPrice
+     * @return void
      */
     public function testCalculateSpecialPrice($finalPrice, $specialPrice, $callsNumber, $dateInInterval, $expected)
     {
@@ -137,6 +148,8 @@ class PriceTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
+     * Data provider for calculateSpecialPrice() test.
+     *
      * @return array
      */
     public function calculateSpecialPrice()
@@ -151,6 +164,11 @@ class PriceTest extends \PHPUnit_Framework_TestCase
         ];
     }
 
+    /**
+     * Test for getTotalBundleItemsPrice() with noCustom options.
+     *
+     * @return void
+     */
     public function testGetTotalBundleItemsPriceWithNoCustomOptions()
     {
         $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
@@ -165,8 +183,11 @@ class PriceTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
+     * Test for getTotalBundleItemsPrice() with empty options.
+     *
      * @param string|null $value
      * @dataProvider dataProviderWithEmptyOptions
+     * @return void
      */
     public function testGetTotalBundleItemsPriceWithEmptyOptions($value)
     {
@@ -194,6 +215,8 @@ class PriceTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
+     * Data provider for getTotalBundleItemsPrice() with empty options.
+     *
      * @return array
      */
     public function dataProviderWithEmptyOptions()
@@ -205,6 +228,11 @@ class PriceTest extends \PHPUnit_Framework_TestCase
         ];
     }
 
+    /**
+     * Test for getTotalBundleItemsPrice() with empty options.
+     *
+     * @return void
+     */
     public function testGetTotalBundleItemsPriceWithNoItems()
     {
         $storeId = 1;
@@ -240,9 +268,8 @@ class PriceTest extends \PHPUnit_Framework_TestCase
             ->method('getStoreId')
             ->willReturn($storeId);
 
-        $dataObjectMock->expects($this->once())
-            ->method('getValue')
-            ->willReturn('a:1:{i:0;s:1:"1";}');
+        $customOptionValue = 'a:1:{i:0;s:1:"1";}';
+        $dataObjectMock->expects($this->once())->method('getValue')->willReturn($customOptionValue);
         $productTypeMock->expects($this->once())
             ->method('getSelectionsByIds')
             ->with([1], $productMock)
diff --git a/app/code/Magento/Catalog/Api/BasePriceStorageInterface.php b/app/code/Magento/Catalog/Api/BasePriceStorageInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..013ec1f940047f1e2c9ce74f95c6f4eb679e04b5
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/BasePriceStorageInterface.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api;
+
+/**
+ * Base prices storage.
+ * @api
+ */
+interface BasePriceStorageInterface
+{
+    /**
+     * Return product prices.
+     *
+     * @param string[] $skus
+     * @return \Magento\Catalog\Api\Data\BasePriceInterface[]
+     */
+    public function get(array $skus);
+
+    /**
+     * Add or update product prices.
+     *
+     * @param \Magento\Catalog\Api\Data\BasePriceInterface[] $prices
+     * @return bool Will returned True if updated.
+     */
+    public function update(array $prices);
+}
diff --git a/app/code/Magento/Catalog/Api/CostStorageInterface.php b/app/code/Magento/Catalog/Api/CostStorageInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c9fb4d540d5bcbe58b92ffb3d1e3794556d39a6
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/CostStorageInterface.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api;
+
+/**
+ * Product cost storage.
+ * @api
+ */
+interface CostStorageInterface
+{
+    /**
+     * Return product prices.
+     *
+     * @param string[] $skus
+     * @return \Magento\Catalog\Api\Data\CostInterface[]
+     */
+    public function get(array $skus);
+
+    /**
+     * Add or update product cost.
+     *
+     * @param \Magento\Catalog\Api\Data\CostInterface[] $prices
+     * @return bool Will returned True if updated.
+     */
+    public function update(array $prices);
+
+    /**
+     * Delete product cost.
+     *
+     * @param string[] $skus
+     * @return bool Will returned True if deleted.
+     * @throws \Magento\Framework\Exception\CouldNotDeleteException
+     */
+    public function delete(array $skus);
+}
diff --git a/app/code/Magento/Catalog/Api/Data/BasePriceInterface.php b/app/code/Magento/Catalog/Api/Data/BasePriceInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..942de4a63abef74484e7169adbac07c9faa79cdf
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/Data/BasePriceInterface.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api\Data;
+
+/**
+ * Price interface.
+ * @api
+ */
+interface BasePriceInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    /**#@+
+     * Constants
+     */
+    const PRICE = 'price';
+    const STORE_ID = 'store_id';
+    const SKU = 'sku';
+    /**#@-*/
+
+    /**
+     * Set price.
+     *
+     * @param float $price
+     * @return $this
+     */
+    public function setPrice($price);
+
+    /**
+     * Get price.
+     *
+     * @return float
+     */
+    public function getPrice();
+
+    /**
+     * Set store id.
+     *
+     * @param int $storeId
+     * @return $this
+     */
+    public function setStoreId($storeId);
+
+    /**
+     * Get store id.
+     *
+     * @return int
+     */
+    public function getStoreId();
+
+    /**
+     * Set SKU.
+     *
+     * @param string $sku
+     * @return $this
+     */
+    public function setSku($sku);
+
+    /**
+     * Get SKU.
+     *
+     * @return string
+     */
+    public function getSku();
+
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Catalog\Api\Data\BasePriceExtensionInterface|null
+     */
+    public function getExtensionAttributes();
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Catalog\Api\Data\BasePriceExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Catalog\Api\Data\BasePriceExtensionInterface $extensionAttributes
+    );
+}
diff --git a/app/code/Magento/Catalog/Api/Data/CostInterface.php b/app/code/Magento/Catalog/Api/Data/CostInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c007c81f1d7bf9181215044701b18205322be49b
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/Data/CostInterface.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api\Data;
+
+/**
+ * Cost interface.
+ * @api
+ */
+interface CostInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    /**#@+
+     * Constants
+     */
+    const COST = 'cost';
+    const STORE_ID = 'store_id';
+    const SKU = 'sku';
+    /**#@-*/
+
+    /**
+     * Set cost value.
+     *
+     * @param float $cost
+     * @return $this
+     */
+    public function setCost($cost);
+
+    /**
+     * Get cost value.
+     *
+     * @return float
+     */
+    public function getCost();
+
+    /**
+     * Set store id.
+     *
+     * @param int $storeId
+     * @return $this
+     */
+    public function setStoreId($storeId);
+
+    /**
+     * Get store id.
+     *
+     * @return int
+     */
+    public function getStoreId();
+
+    /**
+     * Set SKU.
+     *
+     * @param string $sku
+     * @return $this
+     */
+    public function setSku($sku);
+
+    /**
+     * Get SKU.
+     *
+     * @return string
+     */
+    public function getSku();
+
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Catalog\Api\Data\CostExtensionInterface|null
+     */
+    public function getExtensionAttributes();
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Catalog\Api\Data\CostExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Catalog\Api\Data\CostExtensionInterface $extensionAttributes
+    );
+}
diff --git a/app/code/Magento/Catalog/Api/Data/TierPriceInterface.php b/app/code/Magento/Catalog/Api/Data/TierPriceInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..1b708132c0d0ef95f900690e53b6520dac2fa008
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/Data/TierPriceInterface.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api\Data;
+
+/**
+ * Tier price interface.
+ * @api
+ */
+interface TierPriceInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    /**#@+
+     * Constants
+     */
+    const PRICE = 'price';
+    const PRICE_TYPE = 'price_type';
+    const WEBSITE_ID = 'website_id';
+    const SKU = 'sku';
+    const CUSTOMER_GROUP = 'customer_group';
+    const QUANTITY = 'quantity';
+    const PRICE_TYPE_FIXED = 'fixed';
+    const PRICE_TYPE_DISCOUNT = 'discount';
+    /**#@-*/
+
+    /**
+     * Set tier price.
+     *
+     * @param float $price
+     * @return $this
+     */
+    public function setPrice($price);
+
+    /**
+     * Get tier price.
+     *
+     * @return float
+     */
+    public function getPrice();
+
+    /**
+     * Set tier price type.
+     *
+     * @param string $type
+     * @return $this
+     */
+    public function setPriceType($type);
+
+    /**
+     * Get tier price type.
+     *
+     * @return string
+     */
+    public function getPriceType();
+
+    /**
+     * Set website id.
+     *
+     * @param int $websiteId
+     * @return $this
+     */
+    public function setWebsiteId($websiteId);
+
+    /**
+     * Get website id.
+     *
+     * @return int
+     */
+    public function getWebsiteId();
+
+    /**
+     * Set SKU.
+     *
+     * @param string $sku
+     * @return $this
+     */
+    public function setSku($sku);
+
+    /**
+     * Get SKU.
+     *
+     * @return string
+     */
+    public function getSku();
+
+    /**
+     * Set customer group.
+     *
+     * @param string $group
+     * @return $this
+     */
+    public function setCustomerGroup($group);
+
+    /**
+     * Get customer group.
+     *
+     * @return string
+     */
+    public function getCustomerGroup();
+
+    /**
+     * Set quantity.
+     *
+     * @param float $quantity
+     * @return $this
+     */
+    public function setQuantity($quantity);
+
+    /**
+     * Get quantity.
+     *
+     * @return float
+     */
+    public function getQuantity();
+
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Catalog\Api\Data\TierPriceExtensionInterface|null
+     */
+    public function getExtensionAttributes();
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Catalog\Api\Data\TierPriceExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Catalog\Api\Data\TierPriceExtensionInterface $extensionAttributes
+    );
+}
diff --git a/app/code/Magento/Catalog/Api/TierPriceStorageInterface.php b/app/code/Magento/Catalog/Api/TierPriceStorageInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..200cdc1baa411c0ec63b9161f5829c1b29724315
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/TierPriceStorageInterface.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api;
+
+/**
+ * Tier prices storage.
+ * @api
+ */
+interface TierPriceStorageInterface
+{
+    /**
+     * Return product prices.
+     *
+     * @param string[] $skus
+     * @return \Magento\Catalog\Api\Data\TierPriceInterface[]
+     */
+    public function get(array $skus);
+
+    /**
+     * Add or update product prices.
+     *
+     * @param \Magento\Catalog\Api\Data\TierPriceInterface[] $prices
+     * @return bool Will returned True if updated.
+     */
+    public function update(array $prices);
+
+    /**
+     * Remove existing tier prices and replace them with the new ones.
+     *
+     * @param \Magento\Catalog\Api\Data\TierPriceInterface[] $prices
+     * @return bool Will returned True if replaced.
+     */
+    public function replace(array $prices);
+
+    /**
+     * Delete product tier prices.
+     *
+     * @param \Magento\Catalog\Api\Data\TierPriceInterface[] $prices
+     * @return bool Will returned True if deleted.
+     */
+    public function delete(array $prices);
+}
diff --git a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php
index 5e518df37db1a574f25fe008510e75742cdffa9c..b994c787bee7aa76f8a4baa3648bcbb2c0e4ee27 100644
--- a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php
+++ b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php
@@ -23,7 +23,7 @@ class Price implements ProductPriceOptionsInterface
     {
         return [
             ['value' => self::VALUE_FIXED, 'label' => __('Fixed')],
-            ['value' => self::VALUE_PERCENT, 'label' => __('Discount')],
+            ['value' => self::VALUE_PERCENT, 'label' => __('Percent')],
         ];
     }
 }
diff --git a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/TierPrice.php b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/TierPrice.php
new file mode 100644
index 0000000000000000000000000000000000000000..d630f4890fc95afd894ab88b81aedc552e9bc50c
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/TierPrice.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Model\Config\Source\Product\Options;
+
+use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface;
+
+/**
+ * TierPrice types mode source.
+ */
+class TierPrice implements ProductPriceOptionsInterface
+{
+    /**
+     * {@inheritdoc}
+     *
+     * @codeCoverageIgnore
+     */
+    public function toOptionArray()
+    {
+        return [
+            ['value' => self::VALUE_FIXED, 'label' => __('Fixed')],
+            ['value' => self::VALUE_PERCENT, 'label' => __('Discount')],
+        ];
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/BasePrice.php b/app/code/Magento/Catalog/Model/Product/Price/BasePrice.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7c01141de33bb7202ccad4eff2605bb7bdcbae8
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/BasePrice.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\BasePriceInterface;
+
+/**
+ * Product Base Price DTO.
+ */
+class BasePrice extends \Magento\Framework\Model\AbstractExtensibleModel implements BasePriceInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function setPrice($price)
+    {
+        return $this->setData(self::PRICE, $price);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPrice()
+    {
+        return $this->getData(self::PRICE);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setStoreId($storeId)
+    {
+        return $this->setData(self::STORE_ID, $storeId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStoreId()
+    {
+        return $this->getData(self::STORE_ID);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setSku($sku)
+    {
+        return $this->setData(self::SKU, $sku);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSku()
+    {
+        return $this->getData(self::SKU);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getExtensionAttributes()
+    {
+        return $this->_getExtensionAttributes();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setExtensionAttributes(\Magento\Catalog\Api\Data\BasePriceExtensionInterface $extensionAttributes)
+    {
+        return $this->_setExtensionAttributes($extensionAttributes);
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..e69f89f0bb146d62e7cc65d1022a4d25b6738c4f
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php
@@ -0,0 +1,227 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+/**
+ * Base prices storage.
+ */
+class BasePriceStorage implements \Magento\Catalog\Api\BasePriceStorageInterface
+{
+    /**
+     * Attribute code.
+     *
+     * @var string
+     */
+    private $attributeCode = 'price';
+
+    /**
+     * @var PricePersistence
+     */
+    private $pricePersistence;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\BasePriceInterfaceFactory
+     */
+    private $basePriceInterfaceFactory;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+     */
+    private $productIdLocator;
+
+    /**
+     * @var \Magento\Store\Api\StoreRepositoryInterface
+     */
+    private $storeRepository;
+
+    /**
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface
+     */
+    private $productRepository;
+
+    /**
+     * Price type allowed.
+     *
+     * @var int
+     */
+    private $priceTypeAllowed = 1;
+
+    /**
+     * Allowed product types.
+     *
+     * @var array
+     */
+    private $allowedProductTypes = [];
+
+    /**
+     * @var PricePersistenceFactory
+     */
+    private $pricePersistenceFactory;
+
+    /**
+     * BasePriceStorage constructor.
+     *
+     * @param PricePersistenceFactory $pricePersistenceFactory
+     * @param \Magento\Catalog\Api\Data\BasePriceInterfaceFactory $basePriceInterfaceFactory
+     * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
+     * @param \Magento\Store\Api\StoreRepositoryInterface $storeRepository
+     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+     * @param array $allowedProductTypes
+     */
+    public function __construct(
+        PricePersistenceFactory $pricePersistenceFactory,
+        \Magento\Catalog\Api\Data\BasePriceInterfaceFactory $basePriceInterfaceFactory,
+        \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
+        \Magento\Store\Api\StoreRepositoryInterface $storeRepository,
+        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
+        array $allowedProductTypes = []
+    ) {
+        $this->pricePersistenceFactory = $pricePersistenceFactory;
+        $this->basePriceInterfaceFactory = $basePriceInterfaceFactory;
+        $this->productIdLocator = $productIdLocator;
+        $this->storeRepository = $storeRepository;
+        $this->productRepository = $productRepository;
+        $this->allowedProductTypes = $allowedProductTypes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(array $skus)
+    {
+        $this->validateSkus($skus);
+        $rawPrices = $this->getPricePersistence()->get($skus);
+        $prices = [];
+        foreach ($rawPrices as $rawPrice) {
+            $price = $this->basePriceInterfaceFactory->create();
+            $sku = $this->getPricePersistence()
+                ->retrieveSkuById($rawPrice[$this->getPricePersistence()->getEntityLinkField()], $skus);
+            $price->setSku($sku);
+            $price->setPrice($rawPrice['value']);
+            $price->setStoreId($rawPrice['store_id']);
+            $prices[] = $price;
+        }
+
+        return $prices;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(array $prices)
+    {
+        $this->validate($prices);
+        $formattedPrices = [];
+
+        foreach ($prices as $price) {
+            $ids = array_keys($this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()])[$price->getSku()]);
+            foreach ($ids as $id) {
+                $formattedPrices[] = [
+                    'store_id' => $price->getStoreId(),
+                    $this->getPricePersistence()->getEntityLinkField() => $id,
+                    'value' => $price->getPrice(),
+                ];
+            }
+        }
+
+        $this->getPricePersistence()->update($formattedPrices);
+
+        return true;
+    }
+
+    /**
+     * Get price persistence.
+     *
+     * @return PricePersistence
+     */
+    private function getPricePersistence()
+    {
+        if (!$this->pricePersistence) {
+            $this->pricePersistence = $this->pricePersistenceFactory->create(['attributeCode' => $this->attributeCode]);
+        }
+
+        return $this->pricePersistence;
+    }
+
+    /**
+     * Validate SKU, check product types and skip not existing products.
+     *
+     * @param array $skus
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function validateSkus(array $skus)
+    {
+        $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus($skus);
+        $skuDiff = array_diff($skus, array_keys($idsBySku));
+
+        foreach ($idsBySku as $sku => $ids) {
+            foreach ($ids as $type) {
+                if (!in_array($type, $this->allowedProductTypes)
+                    || (
+                        $type == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+                        && $this->productRepository->get($sku)->getPriceType() != $this->priceTypeAllowed
+                    )
+                ) {
+                    $skuDiff[] = $sku;
+                    break;
+                }
+            }
+        }
+
+        if (!empty($skuDiff)) {
+            $values = implode(', ', $skuDiff);
+            $description = count($skuDiff) == 1
+                ? __('Requested product doesn\'t exist: %1', $values)
+                : __('Requested products don\'t exist: %1', $values);
+            throw new \Magento\Framework\Exception\NoSuchEntityException($description);
+        }
+    }
+
+    /**
+     * Validate that prices have appropriate values.
+     *
+     * @param array $prices
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function validate(array $prices)
+    {
+        $skus = array_unique(
+            array_map(function ($price) {
+                if (!$price->getSku()) {
+                    throw new \Magento\Framework\Exception\LocalizedException(
+                        __(
+                            'Invalid attribute %fieldName: %fieldValue.',
+                            [
+                                'fieldName' => 'sku',
+                                'fieldValue' => $price->getSku()
+                            ]
+                        )
+                    );
+                }
+                return $price->getSku();
+            }, $prices)
+        );
+        $this->validateSkus($skus);
+
+        foreach ($prices as $price) {
+            if (null === $price->getPrice() || $price->getPrice() < 0) {
+                throw new \Magento\Framework\Exception\LocalizedException(
+                    __(
+                        'Invalid attribute %fieldName: %fieldValue.',
+                        [
+                            'fieldName' => 'Price',
+                            'fieldValue' => $price->getPrice()
+                        ]
+                    )
+                );
+            }
+            $this->storeRepository->getById($price->getStoreId());
+        }
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/Cost.php b/app/code/Magento/Catalog/Model/Product/Price/Cost.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d52c578ea94b431254883d0e815c1f0064c22d2
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/Cost.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\CostInterface;
+
+/**
+ * Product Cost DTO.
+ */
+class Cost extends \Magento\Framework\Model\AbstractExtensibleModel implements CostInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function setCost($cost)
+    {
+        return $this->setData(self::COST, $cost);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCost()
+    {
+        return $this->getData(self::COST);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setStoreId($storeId)
+    {
+        return $this->setData(self::STORE_ID, $storeId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStoreId()
+    {
+        return $this->getData(self::STORE_ID);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setSku($sku)
+    {
+        return $this->setData(self::SKU, $sku);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSku()
+    {
+        return $this->getData(self::SKU);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getExtensionAttributes()
+    {
+        return $this->_getExtensionAttributes();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setExtensionAttributes(\Magento\Catalog\Api\Data\CostExtensionInterface $extensionAttributes)
+    {
+        return $this->_setExtensionAttributes($extensionAttributes);
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/CostStorage.php b/app/code/Magento/Catalog/Model/Product/Price/CostStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7fc682514a3fa3c575f13938a0a86048a0e50fb
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/CostStorage.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+/**
+ * Product cost storage.
+ */
+class CostStorage implements \Magento\Catalog\Api\CostStorageInterface
+{
+    /**
+     * Attribute code.
+     *
+     * @var string
+     */
+    private $attributeCode = 'cost';
+
+    /**
+     * @var PricePersistence
+     */
+    private $pricePersistence;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\CostInterfaceFactory
+     */
+    private $costInterfaceFactory;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+     */
+    private $productIdLocator;
+
+    /**
+     * Allowed product types.
+     *
+     * @var array
+     */
+    private $allowedProductTypes = [];
+
+    /**
+     * @var PricePersistenceFactory
+     */
+    private $pricePersistenceFactory;
+
+    /**
+     * @var \Magento\Store\Api\StoreRepositoryInterface
+     */
+    private $storeRepository;
+
+    /**
+     * CostStorage constructor.
+     *
+     * @param PricePersistenceFactory $pricePersistenceFactory
+     * @param \Magento\Catalog\Api\Data\CostInterfaceFactory $costInterfaceFactory
+     * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
+     * @param \Magento\Store\Api\StoreRepositoryInterface $storeRepository
+     * @param array $allowedProductTypes
+     */
+    public function __construct(
+        PricePersistenceFactory $pricePersistenceFactory,
+        \Magento\Catalog\Api\Data\CostInterfaceFactory $costInterfaceFactory,
+        \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
+        \Magento\Store\Api\StoreRepositoryInterface $storeRepository,
+        array $allowedProductTypes = []
+    ) {
+        $this->pricePersistenceFactory = $pricePersistenceFactory;
+        $this->costInterfaceFactory = $costInterfaceFactory;
+        $this->productIdLocator = $productIdLocator;
+        $this->storeRepository = $storeRepository;
+        $this->allowedProductTypes = $allowedProductTypes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(array $skus)
+    {
+        $this->validateSkus($skus);
+        $rawPrices = $this->getPricePersistence()->get($skus);
+        $prices = [];
+        foreach ($rawPrices as $rawPrice) {
+            $price = $this->costInterfaceFactory->create();
+            $sku = $this->getPricePersistence()
+                ->retrieveSkuById($rawPrice[$this->getPricePersistence()->getEntityLinkField()], $skus);
+            $price->setSku($sku);
+            $price->setCost($rawPrice['value']);
+            $price->setStoreId($rawPrice['store_id']);
+            $prices[] = $price;
+        }
+
+        return $prices;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(array $prices)
+    {
+        $this->validate($prices);
+        $formattedPrices = [];
+
+        foreach ($prices as $price) {
+            $ids = array_keys($this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()])[$price->getSku()]);
+            foreach ($ids as $id) {
+                $formattedPrices[] = [
+                    'store_id' => $price->getStoreId(),
+                    $this->getPricePersistence()->getEntityLinkField() => $id,
+                    'value' => $price->getCost(),
+                ];
+            }
+        }
+
+        $this->getPricePersistence()->update($formattedPrices);
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete(array $skus)
+    {
+        $this->validateSkus($skus);
+        $this->getPricePersistence()->delete($skus);
+
+        return true;
+    }
+
+    /**
+     * Get price persistence.
+     *
+     * @return PricePersistence
+     */
+    private function getPricePersistence()
+    {
+        if (!$this->pricePersistence) {
+            $this->pricePersistence = $this->pricePersistenceFactory->create(['attributeCode' => $this->attributeCode]);
+        }
+
+        return $this->pricePersistence;
+    }
+
+    /**
+     * Validate that prices have appropriate values.
+     *
+     * @param array $prices
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function validate(array $prices)
+    {
+        $skus = array_unique(
+            array_map(function ($price) {
+                if (!$price->getSku()) {
+                    throw new \Magento\Framework\Exception\LocalizedException(
+                        __(
+                            'Invalid attribute %fieldName: %fieldValue.',
+                            [
+                                'fieldName' => 'sku',
+                                'fieldValue' => $price->getSku()
+                            ]
+                        )
+                    );
+                }
+                return $price->getSku();
+            }, $prices)
+        );
+        $this->validateSkus($skus);
+
+        foreach ($prices as $price) {
+            if (null === $price->getCost() || $price->getCost() < 0) {
+                throw new \Magento\Framework\Exception\LocalizedException(
+                    __(
+                        'Invalid attribute %fieldName: %fieldValue.',
+                        [
+                            'fieldName' => 'Cost',
+                            'fieldValue' => $price->getCost()
+                        ]
+                    )
+                );
+            }
+            $this->storeRepository->getById($price->getStoreId());
+        }
+    }
+
+    /**
+     * Validate SKU, check product types and skip not existing products.
+     *
+     * @param array $skus
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function validateSkus(array $skus)
+    {
+        $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus($skus);
+        $skuDiff = array_diff($skus, array_keys($idsBySku));
+
+        foreach ($idsBySku as $sku => $ids) {
+            foreach (array_values($ids) as $type) {
+                if (!in_array($type, $this->allowedProductTypes)) {
+                    $skuDiff[] = $sku;
+                    break;
+                }
+            }
+        }
+
+        if (!empty($skuDiff)) {
+            $values = implode(', ', $skuDiff);
+            $description = count($skuDiff) == 1
+                ? __('Requested product doesn\'t exist: %1', $values)
+                : __('Requested products don\'t exist: %1', $values);
+            throw new \Magento\Framework\Exception\NoSuchEntityException($description);
+        }
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php b/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php
new file mode 100644
index 0000000000000000000000000000000000000000..f37fb15cd47e433e4125b0bd51d90d00ae291cf2
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+/**
+ * Price persistence.
+ */
+class PricePersistence
+{
+    /**
+     * Price storage table.
+     *
+     * @var string
+     */
+    private $table = 'catalog_product_entity_decimal';
+
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Attribute
+     */
+    private $attributeResource;
+
+    /**
+     * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
+     */
+    private $attributeRepository;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+     */
+    private $productIdLocator;
+
+    /**
+     * Metadata pool.
+     *
+     * @var \Magento\Framework\EntityManager\MetadataPool
+     */
+    private $metadataPool;
+
+    /**
+     * Attribute code.
+     *
+     * @var string
+     */
+    private $attributeCode;
+
+    /**
+     * Attribute ID.
+     *
+     * @var int
+     */
+    private $attributeId;
+
+    /**
+     * Items per operation.
+     *
+     * @var int
+     */
+    private $itemsPerOperation = 500;
+
+    /**
+     * PricePersistence constructor.
+     *
+     * @param \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource
+     * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
+     * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
+     * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+     * @param string $attributeCode
+     */
+    public function __construct(
+        \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource,
+        \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
+        \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
+        \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+        $attributeCode = ''
+    ) {
+        $this->attributeResource = $attributeResource;
+        $this->attributeRepository = $attributeRepository;
+        $this->attributeCode = $attributeCode;
+        $this->productIdLocator = $productIdLocator;
+        $this->metadataPool = $metadataPool;
+    }
+
+    /**
+     * Get prices by SKUs.
+     *
+     * @param array $skus
+     * @return array
+     */
+    public function get(array $skus)
+    {
+        $ids = $this->retrieveAffectedIds($skus);
+        $select = $this->attributeResource->getConnection()
+            ->select()
+            ->from($this->attributeResource->getTable($this->table));
+        return $this->attributeResource->getConnection()->fetchAll(
+            $select->where($this->getEntityLinkField() . ' IN (?)', $ids)
+                ->where('attribute_id = ?', $this->getAttributeId())
+        );
+    }
+
+    /**
+     * Update prices.
+     *
+     * @param array $prices
+     * @return void
+     * @throws \Magento\Framework\Exception\CouldNotSaveException
+     */
+    public function update(array $prices)
+    {
+        array_walk($prices, function (&$price) {
+            return $price['attribute_id'] = $this->getAttributeId();
+        });
+        $connection = $this->attributeResource->getConnection();
+        $connection->beginTransaction();
+        try {
+            foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) {
+                $this->attributeResource->getConnection()->insertOnDuplicate(
+                    $this->attributeResource->getTable($this->table),
+                    $pricesBunch,
+                    ['value']
+                );
+            }
+            $connection->commit();
+        } catch (\Exception $e) {
+            $connection->rollBack();
+            throw new \Magento\Framework\Exception\CouldNotSaveException(
+                __('Could not save Prices.'),
+                $e
+            );
+        }
+    }
+
+    /**
+     * Delete product attribute by SKU.
+     *
+     * @param array $skus
+     * @return void
+     * @throws \Magento\Framework\Exception\CouldNotDeleteException
+     */
+    public function delete(array $skus)
+    {
+        $ids = $this->retrieveAffectedIds($skus);
+        $connection = $this->attributeResource->getConnection();
+        $connection->beginTransaction();
+        try {
+            foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
+                $this->attributeResource->getConnection()->delete(
+                    $this->attributeResource->getTable($this->table),
+                    [
+                        'attribute_id = ?' => $this->getAttributeId(),
+                        $this->getEntityLinkField() . ' IN (?)' => $idsBunch
+                    ]
+                );
+            }
+            $connection->commit();
+        } catch (\Exception $e) {
+            $connection->rollBack();
+            throw new \Magento\Framework\Exception\CouldNotDeleteException(
+                __('Could not delete Prices'),
+                $e
+            );
+        }
+    }
+
+    /**
+     * Retrieve SKU by product ID.
+     *
+     * @param int $id
+     * @param array $skus
+     * @return int|null
+     */
+    public function retrieveSkuById($id, $skus)
+    {
+        foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) {
+            if (false !== array_key_exists($id, $ids)) {
+                return $sku;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get attribute ID.
+     *
+     * @return int
+     */
+    private function getAttributeId()
+    {
+        if (!$this->attributeId) {
+            $this->attributeId = $this->attributeRepository->get($this->attributeCode)->getAttributeId();
+        }
+
+        return $this->attributeId;
+    }
+
+    /**
+     * Retrieve affected product IDs.
+     *
+     * @param array $skus
+     * @return array
+     */
+    private function retrieveAffectedIds(array $skus)
+    {
+        $affectedIds = [];
+
+        foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productIds) {
+            $affectedIds = array_merge($affectedIds, array_keys($productIds));
+        }
+
+        return array_unique($affectedIds);
+    }
+
+    /**
+     * Get link field.
+     *
+     * @return string
+     */
+    public function getEntityLinkField()
+    {
+        return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
+            ->getLinkField();
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPrice.php b/app/code/Magento/Catalog/Model/Product/Price/TierPrice.php
new file mode 100644
index 0000000000000000000000000000000000000000..c3c30d18fe639df371a6a5b3d51f7f06492be4f5
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPrice.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\TierPriceInterface;
+
+/**
+ * TierPrice DTO.
+ */
+class TierPrice extends \Magento\Framework\Model\AbstractExtensibleModel implements TierPriceInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function setPrice($price)
+    {
+        return $this->setData(self::PRICE, $price);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPrice()
+    {
+        return $this->getData(self::PRICE);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setPriceType($type)
+    {
+        return $this->setData(self::PRICE_TYPE, $type);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPriceType()
+    {
+        return $this->getData(self::PRICE_TYPE);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setWebsiteId($websiteId)
+    {
+        return $this->setData(self::WEBSITE_ID, $websiteId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getWebsiteId()
+    {
+        return $this->getData(self::WEBSITE_ID);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setSku($sku)
+    {
+        return $this->setData(self::SKU, $sku);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSku()
+    {
+        return $this->getData(self::SKU);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setCustomerGroup($group)
+    {
+        return $this->setData(self::CUSTOMER_GROUP, $group);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCustomerGroup()
+    {
+        return $this->getData(self::CUSTOMER_GROUP);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setQuantity($quantity)
+    {
+        return $this->setData(self::QUANTITY, $quantity);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getQuantity()
+    {
+        return $this->getData(self::QUANTITY);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getExtensionAttributes()
+    {
+        return $this->_getExtensionAttributes();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setExtensionAttributes(\Magento\Catalog\Api\Data\TierPriceExtensionInterface $extensionAttributes)
+    {
+        return $this->_setExtensionAttributes($extensionAttributes);
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e031649ebdcf2830826e57dccb974a51b7efa71
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\TierPriceInterface;
+use Magento\Framework\Exception\NoSuchEntityException;
+
+/**
+ * Tier price factory.
+ */
+class TierPriceFactory
+{
+    /**
+     * Tier price factory.
+     *
+     * @var \Magento\Catalog\Api\Data\TierPriceInterfaceFactory
+     */
+    private $tierPriceFactory;
+
+    /**
+     * Tier price persistence.
+     *
+     * @var TierPricePersistence
+     */
+    private $tierPricePersistence;
+
+    /**
+     * Customer group repository.
+     *
+     * @var \Magento\Customer\Api\GroupRepositoryInterface
+     */
+    private $customerGroupRepository;
+
+    /**
+     * All groups value.
+     *
+     * @var string
+     */
+    private $allGroupsValue = 'all groups';
+
+    /**
+     * All groups ID.
+     *
+     * @var int
+     */
+    private $allGroupsId = 1;
+
+    /**
+     * Customer groups by code.
+     *
+     * @var array
+     */
+    private $customerGroupsByCode = [];
+
+    /**
+     * TierPriceBuilder constructor.
+     *
+     * @param \Magento\Catalog\Api\Data\TierPriceInterfaceFactory $tierPriceFactory
+     * @param TierPricePersistence $tierPricePersistence
+     * @param \Magento\Customer\Api\GroupRepositoryInterface $customerGroupRepository
+     * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
+     * @param \Magento\Framework\Api\FilterBuilder $filterBuilder
+     */
+    public function __construct(
+        \Magento\Catalog\Api\Data\TierPriceInterfaceFactory $tierPriceFactory,
+        TierPricePersistence $tierPricePersistence,
+        \Magento\Customer\Api\GroupRepositoryInterface $customerGroupRepository,
+        \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder,
+        \Magento\Framework\Api\FilterBuilder $filterBuilder
+    ) {
+        $this->tierPriceFactory = $tierPriceFactory;
+        $this->tierPricePersistence = $tierPricePersistence;
+        $this->customerGroupRepository = $customerGroupRepository;
+        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+        $this->filterBuilder = $filterBuilder;
+    }
+
+    /**
+     * Create populated tier price DTO.
+     *
+     * @param array $rawPrice
+     * @param string $sku
+     * @return \Magento\Catalog\Api\Data\TierPriceInterface
+     */
+    public function create(array $rawPrice, $sku)
+    {
+        $price = $this->tierPriceFactory->create();
+        $price->setPrice(isset($rawPrice['percentage_value']) ? $rawPrice['percentage_value'] : $rawPrice['value']);
+        $price->setPriceType(
+            isset($rawPrice['percentage_value'])
+            ? TierPriceInterface::PRICE_TYPE_DISCOUNT
+            : TierPriceInterface::PRICE_TYPE_FIXED
+        );
+        $price->setWebsiteId($rawPrice['website_id']);
+        $price->setSku($sku);
+        $price->setCustomerGroup(
+            $rawPrice['all_groups'] == $this->allGroupsId
+            ? $this->allGroupsValue
+            : $this->customerGroupRepository->getById($rawPrice['customer_group_id'])->getCode()
+        );
+        $price->setQuantity($rawPrice['qty']);
+
+        return $price;
+    }
+
+    /**
+     * Build tier price skeleton that has DB consistent format.
+     *
+     * @param TierPriceInterface $price
+     * @param int $id
+     * @return array
+     */
+    public function createSkeleton(TierPriceInterface $price, $id)
+    {
+        return [
+            $this->tierPricePersistence->getEntityLinkField() => $id,
+            'all_groups' => $this->retrievePriceForAllGroupsValue($price),
+            'customer_group_id' => $this->retrievePriceForAllGroupsValue($price) === $this->allGroupsId
+                ? 0
+                : $this->retrieveGroupValue(strtolower($price->getCustomerGroup())),
+            'qty' => $price->getQuantity(),
+            'value' => $price->getPriceType() === TierPriceInterface::PRICE_TYPE_FIXED
+                ? $price->getPrice()
+                : 0.00,
+            'percentage_value' => $price->getPriceType() === TierPriceInterface::PRICE_TYPE_DISCOUNT
+                ? $price->getPrice()
+                : null,
+            'website_id' => $price->getWebsiteId()
+        ];
+    }
+
+    /**
+     * Retrieve price for all groups value.
+     *
+     * @param TierPriceInterface $price
+     * @return int
+     */
+    private function retrievePriceForAllGroupsValue(TierPriceInterface $price)
+    {
+        return strcasecmp($price->getCustomerGroup(), $this->allGroupsValue) === 0 ? $this->allGroupsId : 0;
+    }
+
+    /**
+     * Retrieve customer group id by code.
+     *
+     * @param string $code
+     * @return int
+     * @throws NoSuchEntityException
+     */
+    private function retrieveGroupValue($code)
+    {
+        if (!isset($this->customerGroupsByCode[$code])) {
+            $searchCriteria = $this->searchCriteriaBuilder->addFilters(
+                [
+                    $this->filterBuilder->setField('customer_group_code')->setValue($code)->create()
+                ]
+            );
+            $items = $this->customerGroupRepository->getList($searchCriteria->create())->getItems();
+            $item = array_shift($items);
+            $this->customerGroupsByCode[strtolower($item->getCode())] = $item->getId();
+        }
+
+        return $this->customerGroupsByCode[$code];
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php b/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php
new file mode 100644
index 0000000000000000000000000000000000000000..01293d0532fbfc82ed0893436baad30e2baa87b8
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+/**
+ * Persists tier prices.
+ */
+class TierPricePersistence
+{
+    /**
+     * Number or items per each operation.
+     *
+     * @var int
+     */
+    private $itemsPerOperation = 500;
+
+    /**
+     * Tier price resource model.
+     *
+     * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice
+     */
+    private $tierpriceResource;
+
+    /**
+     * Metadata pool.
+     *
+     * @var \Magento\Framework\EntityManager\MetadataPool
+     */
+    private $metadataPool;
+
+    /**
+     * TierPricePersister constructor.
+     *
+     * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $tierpriceResource
+     * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+     */
+    public function __construct(
+        \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $tierpriceResource,
+        \Magento\Framework\EntityManager\MetadataPool $metadataPool
+    ) {
+        $this->tierpriceResource = $tierpriceResource;
+        $this->metadataPool = $metadataPool;
+    }
+
+    /**
+     * Get tier prices by product IDs.
+     *
+     * @param array $ids
+     * @return array
+     */
+    public function get(array $ids)
+    {
+        $select = $this->tierpriceResource->getConnection()->select()->from($this->tierpriceResource->getMainTable());
+        return $this->tierpriceResource->getConnection()->fetchAll(
+            $select->where($this->getEntityLinkField() . ' IN (?)', $ids)
+        );
+    }
+
+    /**
+     * Update tier prices.
+     *
+     * @param array $prices
+     * @return void
+     * @throws \Magento\Framework\Exception\CouldNotSaveException
+     */
+    public function update(array $prices)
+    {
+        $connection = $this->tierpriceResource->getConnection();
+        $connection->beginTransaction();
+        try {
+            foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) {
+                $this->tierpriceResource->getConnection()->insertOnDuplicate(
+                    $this->tierpriceResource->getMainTable(),
+                    $pricesBunch,
+                    ['value', 'percentage_value']
+                );
+            }
+            $connection->commit();
+        } catch (\Exception $e) {
+            $connection->rollBack();
+            throw new \Magento\Framework\Exception\CouldNotSaveException(
+                __('Could not save Tier Prices'),
+                $e
+            );
+        }
+    }
+
+    /**
+     * Replace prices.
+     *
+     * @param array $prices
+     * @param array $ids
+     * @return void
+     * @throws \Magento\Framework\Exception\CouldNotSaveException
+     */
+    public function replace(array $prices, array $ids)
+    {
+        $connection = $this->tierpriceResource->getConnection();
+        $connection->beginTransaction();
+        try {
+            foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
+                $this->tierpriceResource->getConnection()->delete(
+                    $this->tierpriceResource->getMainTable(),
+                    [$this->getEntityLinkField() . ' IN (?)' => $idsBunch]
+                );
+            }
+
+            foreach (array_chunk($prices, $this->itemsPerOperation) as $pricesBunch) {
+                $this->tierpriceResource->getConnection()->insertMultiple(
+                    $this->tierpriceResource->getMainTable(),
+                    $pricesBunch
+                );
+            }
+            $connection->commit();
+        } catch (\Exception $e) {
+            $connection->rollBack();
+            throw new \Magento\Framework\Exception\CouldNotSaveException(
+                __('Could not replace Tier Prices'),
+                $e
+            );
+        }
+    }
+
+    /**
+     * Delete tier prices by IDs.
+     *
+     * @param array $ids
+     * @return void
+     * @throws \Magento\Framework\Exception\CouldNotDeleteException
+     */
+    public function delete(array $ids)
+    {
+        $connection = $this->tierpriceResource->getConnection();
+        $connection->beginTransaction();
+        try {
+            foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
+                $this->tierpriceResource->getConnection()->delete(
+                    $this->tierpriceResource->getMainTable(),
+                    ['value_id IN (?)' => $idsBunch]
+                );
+            }
+            $connection->commit();
+        } catch (\Exception $e) {
+            $connection->rollBack();
+            throw new \Magento\Framework\Exception\CouldNotDeleteException(
+                __('Could not delete Tier Prices'),
+                $e
+            );
+        }
+    }
+
+    /**
+     * Get link field.
+     *
+     * @return string
+     */
+    public function getEntityLinkField()
+    {
+        return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
+            ->getLinkField();
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..83262bbfa1cca6432109f17ef537c4a039c64c04
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
@@ -0,0 +1,323 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\TierPriceInterface;
+
+/**
+ * Tier price storage.
+ */
+class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface
+{
+    /**
+     * Tier price resource model.
+     *
+     * @var TierPricePersistence
+     */
+    private $tierPricePersistence;
+
+    /**
+     * Tier price validator.
+     *
+     * @var \Magento\Catalog\Model\Product\Price\TierPriceValidator
+     */
+    private $tierPriceValidator;
+
+    /**
+     * Tier price builder.
+     *
+     * @var TierPriceFactory
+     */
+    private $tierPriceFactory;
+
+    /**
+     * Price indexer.
+     *
+     * @var \Magento\Catalog\Model\Indexer\Product\Price
+     */
+    private $priceIndexer;
+
+    /**
+     * Product ID locator.
+     *
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+     */
+    private $productIdLocator;
+
+    /**
+     * Page cache config.
+     *
+     * @var \Magento\PageCache\Model\Config
+     */
+    private $config;
+
+    /**
+     * Cache type list.
+     *
+     * @var \Magento\Framework\App\Cache\TypeListInterface
+     */
+    private $typeList;
+
+    /**
+     * Indexer chunk value.
+     *
+     * @var int
+     */
+    private $indexerChunkValue = 500;
+
+    /**
+     * TierPriceStorage constructor.
+     *
+     * @param TierPricePersistence $tierPricePersistence
+     * @param TierPriceValidator $tierPriceValidator
+     * @param TierPriceFactory $tierPriceFactory
+     * @param \Magento\Catalog\Model\Indexer\Product\Price $priceIndexer
+     * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
+     * @param \Magento\PageCache\Model\Config $config
+     * @param \Magento\Framework\App\Cache\TypeListInterface $typeList
+     */
+    public function __construct(
+        TierPricePersistence $tierPricePersistence,
+        TierPriceValidator $tierPriceValidator,
+        TierPriceFactory $tierPriceFactory,
+        \Magento\Catalog\Model\Indexer\Product\Price $priceIndexer,
+        \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
+        \Magento\PageCache\Model\Config $config,
+        \Magento\Framework\App\Cache\TypeListInterface $typeList
+    ) {
+        $this->tierPricePersistence = $tierPricePersistence;
+        $this->tierPriceValidator = $tierPriceValidator;
+        $this->tierPriceFactory = $tierPriceFactory;
+        $this->priceIndexer = $priceIndexer;
+        $this->productIdLocator = $productIdLocator;
+        $this->config = $config;
+        $this->typeList = $typeList;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(array $skus)
+    {
+        $this->tierPriceValidator->validateSkus($skus);
+        $ids = $this->retrieveAffectedIds($skus);
+        $rawPrices = $this->tierPricePersistence->get($ids);
+        $prices = [];
+
+        foreach ($rawPrices as $rawPrice) {
+            $sku = $this->retrieveSkuById($rawPrice[$this->tierPricePersistence->getEntityLinkField()], $skus);
+            $prices[] = $this->tierPriceFactory->create($rawPrice, $sku);
+        }
+
+        return $prices;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(array $prices)
+    {
+        $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices);
+        $skus = array_unique(
+            array_map(function ($price) {
+                return $price->getSku();
+            }, $prices)
+        );
+        $this->tierPriceValidator->validatePrices($prices, $this->get($skus));
+        $formattedPrices = $this->retrieveFormattedPrices($prices);
+        $this->tierPricePersistence->update($formattedPrices);
+        $this->reindexPrices($affectedIds);
+        $this->invalidateFullPageCache();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function replace(array $prices)
+    {
+        $this->tierPriceValidator->validatePrices($prices);
+        $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices);
+        $formattedPrices = $this->retrieveFormattedPrices($prices);
+        $this->tierPricePersistence->replace($formattedPrices, $affectedIds);
+        $this->reindexPrices($affectedIds);
+        $this->invalidateFullPageCache();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete(array $prices)
+    {
+        $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices);
+        $this->tierPriceValidator->validatePrices($prices);
+        $priceIds = $this->retrieveAffectedPriceIds($prices);
+        $this->tierPricePersistence->delete($priceIds);
+        $this->reindexPrices($affectedIds);
+        $this->invalidateFullPageCache();
+
+        return true;
+    }
+
+    /**
+     * Retrieve formatted prices.
+     *
+     * @param array $prices
+     * @return array
+     */
+    private function retrieveFormattedPrices(array $prices)
+    {
+        $formattedPrices = [];
+
+        foreach ($prices as $price) {
+            $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()]);
+            $ids = array_keys($idsBySku[$price->getSku()]);
+            foreach ($ids as $id) {
+                $formattedPrices[] = $this->tierPriceFactory->createSkeleton($price, $id);
+            }
+        }
+
+        return $formattedPrices;
+    }
+
+    /**
+     * Retrieve affected product IDs for prices.
+     *
+     * @param TierPriceInterface[] $prices
+     * @return array
+     */
+    private function retrieveAffectedProductIdsForPrices(array $prices)
+    {
+        $skus = array_unique(
+            array_map(function ($price) {
+                return $price->getSku();
+            }, $prices)
+        );
+
+        return $this->retrieveAffectedIds($skus);
+    }
+
+    /**
+     * Retrieve affected product IDs.
+     *
+     * @param array $skus
+     * @return array
+     */
+    private function retrieveAffectedIds(array $skus)
+    {
+        $affectedIds = [];
+
+        foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productId) {
+            $affectedIds = array_merge($affectedIds, array_keys($productId));
+        }
+
+        return array_unique($affectedIds);
+    }
+
+    /**
+     * Retrieve affected price IDs.
+     *
+     * @param array $prices
+     * @return array
+     */
+    private function retrieveAffectedPriceIds(array $prices)
+    {
+        $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices);
+        $formattedPrices = $this->retrieveFormattedPrices($prices);
+        $existingPrices = $this->tierPricePersistence->get($affectedIds);
+        $priceIds = [];
+
+        foreach ($formattedPrices as $price) {
+            $priceIds[] = $this->retrievePriceId($price, $existingPrices);
+        }
+
+        return $priceIds;
+    }
+
+    /**
+     * Retrieve price ID.
+     *
+     * @param array $price
+     * @param array $existingPrices
+     * @return int
+     */
+    private function retrievePriceId(array $price, array $existingPrices)
+    {
+        $linkField = $this->tierPricePersistence->getEntityLinkField();
+
+        foreach ($existingPrices as $existingPrice) {
+            if ($existingPrice['all_groups'] == $price['all_groups']
+                && $existingPrice['customer_group_id'] == $price['customer_group_id']
+                && $existingPrice['qty'] == $price['qty']
+                && $this->isCorrectPriceValue($existingPrice, $price)
+                && $existingPrice[$linkField] == $price[$linkField]
+            ) {
+                return $existingPrice['value_id'];
+            }
+        }
+    }
+
+    /**
+     * Check is correct price value
+     *
+     * @param array $existingPrice
+     * @param array $price
+     * @return bool
+     */
+    private function isCorrectPriceValue(array $existingPrice, array $price)
+    {
+        return ($existingPrice['value'] != 0 && $existingPrice['value'] == $price['value'])
+            || ($existingPrice['percentage_value'] !== null
+                && $existingPrice['percentage_value'] == $price['percentage_value']);
+    }
+
+    /**
+     * Retrieve SKU by product ID.
+     *
+     * @param int $id
+     * @param array $skus
+     * @return int|null
+     */
+    private function retrieveSkuById($id, $skus)
+    {
+        foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) {
+            if (false !== array_key_exists($id, $ids)) {
+                return $sku;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Invalidate full page cache.
+     *
+     * @return void
+     */
+    private function invalidateFullPageCache()
+    {
+        if ($this->config->isEnabled()) {
+            $this->typeList->invalidate('full_page');
+        }
+    }
+
+    /**
+     * Reindex prices.
+     *
+     * @param array $ids
+     * @return void
+     */
+    private function reindexPrices(array $ids)
+    {
+        foreach (array_chunk($ids, $this->indexerChunkValue) as $affectedIds) {
+            $this->priceIndexer->execute($affectedIds);
+        }
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceValidator.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..907fd0f66bbdd3d02ff288c141361d56fcfe9694
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceValidator.php
@@ -0,0 +1,351 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\TierPriceInterface;
+
+/**
+ * Tier Price Validator.
+ */
+class TierPriceValidator
+{
+    /**
+     * Groups by code cache.
+     *
+     * @var array
+     */
+    private $customerGroupsByCode = [];
+
+    /**
+     * @var TierPricePersistence
+     */
+    private $tierPricePersistence;
+
+    /**
+     * All groups value.
+     *
+     * @var string
+     */
+    private $allGroupsValue = 'all groups';
+
+    /**
+     * All websites value.
+     *
+     * @var string
+     */
+    private $allWebsitesValue = "0";
+
+    /**
+     * Allowed product types.
+     *
+     * @var array
+     */
+    private $allowedProductTypes = [];
+
+    /**
+     * TierPriceValidator constructor.
+     *
+     * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
+     * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
+     * @param \Magento\Framework\Api\FilterBuilder $filterBuilder
+     * @param \Magento\Customer\Api\GroupRepositoryInterface $customerGroupRepository
+     * @param \Magento\Store\Api\WebsiteRepositoryInterface $websiteRepository
+     * @param TierPricePersistence $tierPricePersistence
+     * @param array $allowedProductTypes
+     */
+    public function __construct(
+        \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
+        \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder,
+        \Magento\Framework\Api\FilterBuilder $filterBuilder,
+        \Magento\Customer\Api\GroupRepositoryInterface $customerGroupRepository,
+        \Magento\Store\Api\WebsiteRepositoryInterface $websiteRepository,
+        TierPricePersistence $tierPricePersistence,
+        array $allowedProductTypes = []
+    ) {
+        $this->productIdLocator = $productIdLocator;
+        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+        $this->filterBuilder = $filterBuilder;
+        $this->customerGroupRepository = $customerGroupRepository;
+        $this->websiteRepository = $websiteRepository;
+        $this->tierPricePersistence = $tierPricePersistence;
+        $this->allowedProductTypes = $allowedProductTypes;
+    }
+
+    /**
+     * Validate SKU.
+     *
+     * @param array $skus
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    public function validateSkus(array $skus)
+    {
+        $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus($skus);
+        $skuDiff = array_diff($skus, array_keys($idsBySku));
+
+        foreach ($idsBySku as $sku => $ids) {
+            foreach (array_values($ids) as $type) {
+                if (!in_array($type, $this->allowedProductTypes)) {
+                    $skuDiff[] = $sku;
+                    break;
+                }
+            }
+        }
+
+        if (!empty($skuDiff)) {
+            $values = implode(', ', $skuDiff);
+            $description = count($skuDiff) == 1
+                ? __('Requested product doesn\'t exist: %1', $values)
+                : __('Requested products don\'t exist: %1', $values);
+            throw new \Magento\Framework\Exception\NoSuchEntityException($description);
+        }
+    }
+
+    /**
+     * Validate that prices have appropriate values and are unique.
+     *
+     * @param array $prices
+     * @param array $existingPrices
+     * @return void
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     */
+    public function validatePrices(array $prices, array $existingPrices = [])
+    {
+        $skus = array_unique(
+            array_map(function ($price) {
+                if (!$price->getSku()) {
+                    throw new \Magento\Framework\Exception\LocalizedException(
+                        __(
+                            'Invalid attribute %fieldName: %fieldValue.',
+                            [
+                                'fieldName' => 'sku',
+                                'fieldValue' => $price->getSku()
+                            ]
+                        )
+                    );
+                }
+                return $price->getSku();
+            }, $prices)
+        );
+        $this->validateSkus($skus);
+        $idsBySku = $this->productIdLocator->retrieveProductIdsBySkus($skus);
+
+        $pricesBySku = [];
+
+        foreach ($prices as $price) {
+            $pricesBySku[$price->getSku()][] = $price;
+        }
+
+        /** @var TierPriceInterface $price */
+        foreach ($prices as $price) {
+            $this->checkPrice($price);
+            $this->checkPriceType($price, $idsBySku[$price->getSku()]);
+            $this->checkQuantity($price);
+            $this->checkWebsite($price);
+            if (isset($pricesBySku[$price->getSku()])) {
+                $this->checkUnique($price, $pricesBySku[$price->getSku()]);
+            }
+            $this->checkUnique($price, $existingPrices);
+            $this->checkGroup($price);
+        }
+    }
+
+    /**
+     * Verify that price value is correct.
+     *
+     * @param TierPriceInterface $price
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function checkPrice(TierPriceInterface $price)
+    {
+        if (
+            null === $price->getPrice()
+            || $price->getPrice() < 0
+            || ($price->getPriceType() === TierPriceInterface::PRICE_TYPE_DISCOUNT && $price->getPrice() > 100)
+        ) {
+            throw new \Magento\Framework\Exception\LocalizedException(
+                __(
+                    'Invalid attribute %fieldName: %fieldValue.',
+                    [
+                        'fieldName' => 'Price',
+                        'fieldValue' => $price->getPrice()
+                    ]
+                )
+            );
+        }
+    }
+
+    /**
+     * Verify that price type is correct.
+     *
+     * @param TierPriceInterface $price
+     * @param array $ids
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function checkPriceType(TierPriceInterface $price, array $ids)
+    {
+        if (
+            !in_array(
+                $price->getPriceType(),
+                [TierPriceInterface::PRICE_TYPE_FIXED, TierPriceInterface::PRICE_TYPE_DISCOUNT]
+            )
+            || (array_search(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE, $ids)
+                && $price->getPriceType() !== TierPriceInterface::PRICE_TYPE_DISCOUNT)
+        ) {
+            throw new \Magento\Framework\Exception\LocalizedException(
+                __(
+                    'Invalid attribute %fieldName: %fieldValue.',
+                    [
+                        'fieldName' => 'Price Type',
+                        'fieldValue' => $price->getPriceType()
+                    ]
+                )
+            );
+        }
+    }
+
+    /**
+     * Verify that product quantity is correct.
+     *
+     * @param TierPriceInterface $price
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    private function checkQuantity(TierPriceInterface $price)
+    {
+        if ($price->getQuantity() < 1) {
+            throw new \Magento\Framework\Exception\LocalizedException(
+                __(
+                    'Invalid attribute %fieldName: %fieldValue.',
+                    [
+                        'fieldName' => 'Quantity',
+                        'fieldValue' => $price->getQuantity()
+                    ]
+                )
+            );
+        }
+    }
+
+    /**
+     * Verify that website exists.
+     *
+     * @param TierPriceInterface $price
+     * @return void
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    private function checkWebsite(TierPriceInterface $price)
+    {
+        try {
+            $this->websiteRepository->getById($price->getWebsiteId());
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            throw new \Magento\Framework\Exception\NoSuchEntityException(
+                __(
+                    'Invalid attribute %fieldName: %fieldValue.',
+                    [
+                        'fieldName' => 'website_id',
+                        'fieldValue' => $price->getWebsiteId()
+                    ]
+                )
+            );
+        }
+    }
+
+    /**
+     * Check website value is unique.
+     *
+     * @param TierPriceInterface $tierPrice
+     * @param array $prices
+     * @return void
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    private function checkUnique(TierPriceInterface $tierPrice, array $prices)
+    {
+        /** @var TierPriceInterface $price */
+        foreach ($prices as $price) {
+            if (
+                $price->getSku() === $tierPrice->getSku()
+                && $price->getCustomerGroup() === $tierPrice->getCustomerGroup()
+                && $price->getQuantity() == $tierPrice->getQuantity()
+                && (
+                    ($price->getWebsiteId() == $this->allWebsitesValue
+                        || $tierPrice->getWebsiteId() == $this->allWebsitesValue)
+                    && $price->getWebsiteId() != $tierPrice->getWebsiteId()
+                )
+            ) {
+                throw new \Magento\Framework\Exception\LocalizedException(
+                    __(
+                        'We found a duplicate website, tier price, customer group and quantity: '
+                        . '%fieldName1 = %fieldValue1, %fieldName2 = %fieldValue2, %fieldName3 = %fieldValue3.',
+                        [
+                            'fieldName1' => 'Customer Group',
+                            'fieldValue1' => $price->getCustomerGroup(),
+                            'fieldName2' => 'Website Id',
+                            'fieldValue2' => $price->getWebsiteId(),
+                            'fieldName3' => 'Quantity',
+                            'fieldValue3' => $price->getQuantity()
+                        ]
+                    )
+                );
+            }
+        }
+    }
+
+    /**
+     * Check customer group exists and has correct value.
+     *
+     * @param TierPriceInterface $price
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     * @return void
+     */
+    private function checkGroup(TierPriceInterface $price)
+    {
+        $customerGroup = strtolower($price->getCustomerGroup());
+
+        if ($customerGroup != $this->allGroupsValue) {
+            $this->retrieveGroupValue($customerGroup);
+        }
+    }
+
+    /**
+     * Retrieve customer group id by code.
+     *
+     * @param string $code
+     * @return int
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     */
+    private function retrieveGroupValue($code)
+    {
+        if (!isset($this->customerGroupsByCode[$code])) {
+            $searchCriteria = $this->searchCriteriaBuilder->addFilters(
+                [
+                    $this->filterBuilder->setField('customer_group_code')->setValue($code)->create()
+                ]
+            );
+            $items = $this->customerGroupRepository->getList($searchCriteria->create())->getItems();
+            $item = array_shift($items);
+
+            if (!$item) {
+                throw new \Magento\Framework\Exception\NoSuchEntityException(
+                    __(
+                        'No such entity with %fieldName = %fieldValue.',
+                        [
+                            'fieldName' => 'Customer Group',
+                            'fieldValue' => $code
+                        ]
+                    )
+                );
+            }
+
+            $this->customerGroupsByCode[strtolower($item->getCode())] = $item->getId();
+        }
+
+        return $this->customerGroupsByCode[$code];
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php
index af15e049203f3f6aae27ef2c6ff071d49cdab8cd..13dfdfe6e5cdc457e751348991fbb806e02ccb12 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/Price.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php
@@ -414,11 +414,12 @@ class Price
         $prices = [];
         foreach ($tierPrices as $price) {
             $extensionAttributes = $price->getExtensionAttributes();
-            $websiteId = $extensionAttributes && $extensionAttributes->getWebsiteId()
-                ? $extensionAttributes->getWebsiteId()
-                : $websiteId;
+            $priceWebsiteId = $websiteId;
+            if (isset($extensionAttributes) && is_numeric($extensionAttributes->getWebsiteId())) {
+                $priceWebsiteId = (string)$extensionAttributes->getWebsiteId();
+            }
             $prices[] = [
-                'website_id' => $websiteId,
+                'website_id' => $priceWebsiteId,
                 'cust_group' => $price->getCustomerGroupId(),
                 'website_price' => $price->getValue(),
                 'price' => $price->getValue(),
diff --git a/app/code/Magento/Catalog/Model/ProductIdLocator.php b/app/code/Magento/Catalog/Model/ProductIdLocator.php
new file mode 100644
index 0000000000000000000000000000000000000000..1678ecd23c9262229ab063c2ad5af65abad9b49d
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ProductIdLocator.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Model;
+
+/**
+ * Product ID locator provides all product IDs by SKUs.
+ * @api
+ */
+class ProductIdLocator implements \Magento\Catalog\Model\ProductIdLocatorInterface
+{
+    /**
+     * Search Criteria builder.
+     *
+     * @var \Magento\Framework\Api\SearchCriteriaBuilder
+     */
+    private $searchCriteriaBuilder;
+
+    /**
+     * Filter builder.
+     *
+     * @var \Magento\Framework\Api\FilterBuilder
+     */
+    private $filterBuilder;
+
+    /**
+     * Metadata pool.
+     *
+     * @var \Magento\Framework\EntityManager\MetadataPool
+     */
+    private $metadataPool;
+
+    /**
+     * Product repository.
+     *
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface
+     */
+    private $productRepository;
+
+    /**
+     * IDs by SKU cache.
+     *
+     * @var array
+     */
+    private $idsBySku = [];
+
+    /**
+     * ProductIdLocatorInterface constructor.
+     *
+     * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
+     * @param \Magento\Framework\Api\FilterBuilder $filterBuilder
+     * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+     */
+    public function __construct(
+        \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder,
+        \Magento\Framework\Api\FilterBuilder $filterBuilder,
+        \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+    ) {
+        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+        $this->filterBuilder = $filterBuilder;
+        $this->metadataPool = $metadataPool;
+        $this->productRepository = $productRepository;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function retrieveProductIdsBySkus(array $skus)
+    {
+        $skus = array_map('trim', $skus);
+        $skusInCache = $this->idsBySku ? array_keys($this->idsBySku) : [];
+        $neededSkus = array_diff($skus, $skusInCache);
+
+        if (!empty($neededSkus)) {
+            $searchCriteria = $this->searchCriteriaBuilder->addFilters(
+                [
+                    $this->filterBuilder
+                        ->setField(\Magento\Catalog\Api\Data\ProductInterface::SKU)
+                        ->setConditionType('in')
+                        ->setValue($neededSkus)
+                        ->create(),
+                ]
+            );
+            $items = $this->productRepository->getList($searchCriteria->create())->getItems();
+            $linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
+                ->getLinkField();
+
+            foreach ($items as $item) {
+                $this->idsBySku[$item->getSku()][$item->getData($linkField)] = $item->getTypeId();
+            }
+        }
+
+        return array_intersect_key($this->idsBySku, array_flip($skus));
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/ProductIdLocatorInterface.php b/app/code/Magento/Catalog/Model/ProductIdLocatorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9a0d88df2eac54e741478d803891f9eb1214152
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ProductIdLocatorInterface.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Model;
+
+/**
+ * Product ID locator provides all product IDs by SKU.
+ */
+interface ProductIdLocatorInterface
+{
+    /**
+     * Will return associative array of product ids as key and type as value grouped by SKUs.
+     *
+     * @param array $skus
+     * @return array
+     */
+    public function retrieveProductIdsBySkus(array $skus);
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ef99e550cdc21ad00f9a726d149528af24ce59c5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php
@@ -0,0 +1,326 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Test\Unit\Model\Product\Price;
+
+/**
+ * Class BasePriceStorageTest.
+ */
+class BasePriceStorageTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\PricePersistenceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $pricePersistenceFactory;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\PricePersistence|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $pricePersistence;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\BasePriceInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $basePriceInterfaceFactory;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\BasePriceInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $basePriceInterface;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productIdLocator;
+
+    /**
+     * @var \Magento\Store\Api\StoreRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $storeRepository;
+
+    /**
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productRepository;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $product;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\BasePriceStorage
+     */
+    private $model;
+
+    /**
+     * Set up.
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->pricePersistenceFactory = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\PricePersistenceFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->pricePersistence = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\PricePersistence::class,
+            ['get', 'retrieveSkuById', 'update', 'getEntityLinkField'],
+            [],
+            '',
+            false
+        );
+        $this->basePriceInterfaceFactory = $this->getMock(
+            \Magento\Catalog\Api\Data\BasePriceInterfaceFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->basePriceInterface = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\BasePriceInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['setSku', 'setPrice', 'setStoreId', 'getSku', 'getPrice', 'getStoreId']
+        );
+        $this->productIdLocator = $this->getMockForAbstractClass(
+            \Magento\Catalog\Model\ProductIdLocatorInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['retrieveProductIdsBySkus']
+        );
+        $this->storeRepository = $this->getMockForAbstractClass(
+            \Magento\Store\Api\StoreRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getById']
+        );
+        $this->productRepository = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\ProductRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['get']
+        );
+        $this->product = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\ProductInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getPriceType']
+        );
+
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject(
+            \Magento\Catalog\Model\Product\Price\BasePriceStorage::class,
+            [
+                'pricePersistenceFactory' => $this->pricePersistenceFactory,
+                'basePriceInterfaceFactory' => $this->basePriceInterfaceFactory,
+                'productIdLocator' => $this->productIdLocator,
+                'storeRepository' => $this->storeRepository,
+                'productRepository' => $this->productRepository,
+                'allowedProductTypes' => ['simple', 'virtual', 'bundle', 'downloadable'],
+            ]
+        );
+    }
+
+    /**
+     * Test get method.
+     *
+     * @return void
+     */
+    public function testGet()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+                ],
+            'sku_2' =>
+                [
+                    2 => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+                ]
+        ];
+        $rawPrices = [
+            [
+                'row_id' => 1,
+                'value' => 15,
+                'store_id' => 1
+            ],
+            [
+                'row_id' => 2,
+                'value' => 35,
+                'store_id' => 1
+            ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->productRepository->expects($this->once())->method('get')->willReturn($this->product);
+        $this->product->expects($this->once())->method('getPriceType')->willReturn(1);
+        $this->pricePersistenceFactory
+            ->expects($this->once())
+            ->method('create')
+            ->with(['attributeCode' => 'price'])
+            ->willReturn($this->pricePersistence);
+        $this->pricePersistence->expects($this->once())->method('get')->with($skus)->willReturn($rawPrices);
+        $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id');
+        $this->basePriceInterfaceFactory
+            ->expects($this->exactly(2))
+            ->method('create')
+            ->willReturn($this->basePriceInterface);
+        $this->pricePersistence
+            ->expects($this->exactly(2))
+            ->method('retrieveSkuById')
+            ->willReturnOnConsecutiveCalls('sku_1', 'sku_2');
+        $this->basePriceInterface
+            ->expects($this->exactly(2))
+            ->method('setSku')
+            ->withConsecutive(['sku_1'], ['sku_2'])
+            ->willReturnSelf();
+        $this->basePriceInterface
+            ->expects($this->exactly(2))
+            ->method('setPrice')
+            ->withConsecutive([15], [35])
+            ->willReturnSelf();
+        $this->basePriceInterface
+            ->expects($this->exactly(2))
+            ->method('setStoreId')
+            ->withConsecutive([1], [1])
+            ->willReturnSelf();
+        $this->model->get($skus);
+    }
+
+    /**
+     * Test get method with exception.
+     *
+     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
+     * @expectedExceptionMessage Requested products don't exist: sku_1, sku_2
+     */
+    public function testGetWithException()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => 'configurable'
+                ],
+            'sku_2' =>
+                [
+                    2 => 'grouped'
+                ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->model->get($skus);
+    }
+
+    /**
+     * Test update method.
+     *
+     * @return void
+     */
+    public function testUpdate()
+    {
+        $store = $this->getMockForAbstractClass(
+            \Magento\Store\Api\Data\StoreInterface::class,
+            [],
+            '',
+            false
+        );
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+                ]
+        ];
+        $this->basePriceInterface->expects($this->exactly(4))->method('getSku')->willReturn($sku);
+        $this->productIdLocator
+            ->expects($this->exactly(2))
+            ->method('retrieveProductIdsBySkus')->with([$sku])
+            ->willReturn($idsBySku);
+        $this->productRepository->expects($this->once())->method('get')->willReturn($this->product);
+        $this->product->expects($this->once())->method('getPriceType')->willReturn(1);
+        $this->basePriceInterface->expects($this->exactly(3))->method('getPrice')->willReturn(15);
+        $this->basePriceInterface->expects($this->exactly(2))->method('getStoreId')->willReturn(1);
+        $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id');
+        $this->storeRepository->expects($this->once())->method('getById')->with(1)->willReturn($store);
+        $this->pricePersistenceFactory
+            ->expects($this->once())
+            ->method('create')
+            ->with(['attributeCode' => 'price'])
+            ->willReturn($this->pricePersistence);
+        $formattedPrices = [
+            [
+                'store_id' => 1,
+                'row_id' => 1,
+                'value' => 15
+            ]
+        ];
+        $this->pricePersistence->expects($this->once())->method('update')->with($formattedPrices);
+        $this->assertTrue($this->model->update([$this->basePriceInterface]));
+    }
+
+    /**
+     * Test update method without SKU.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute sku: .
+     */
+    public function testUpdateWithoutSku()
+    {
+        $this->basePriceInterface->expects($this->exactly(2))->method('getSku')->willReturn(null);
+        $this->model->update([$this->basePriceInterface]);
+    }
+
+    /**
+     * Test update method with negative price.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute Price: -15.
+     */
+    public function testUpdateWithNegativePrice()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+                ]
+        ];
+        $this->basePriceInterface->expects($this->exactly(2))->method('getSku')->willReturn($sku);
+        $this->productIdLocator
+            ->expects($this->once(1))
+            ->method('retrieveProductIdsBySkus')->with([$sku])
+            ->willReturn($idsBySku);
+        $this->productRepository->expects($this->once())->method('get')->willReturn($this->product);
+        $this->product->expects($this->once())->method('getPriceType')->willReturn(1);
+        $this->basePriceInterface->expects($this->exactly(3))->method('getPrice')->willReturn(-15);
+        $this->model->update([$this->basePriceInterface]);
+    }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/CostStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/CostStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1dea66928f1bd98788758420167bde6dcca2329
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/CostStorageTest.php
@@ -0,0 +1,322 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Test\Unit\Model\Product\Price;
+
+/**
+ * Class CostStorageTest.
+ */
+class CostStorageTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\PricePersistenceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $pricePersistenceFactory;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\PricePersistence|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $pricePersistence;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\CostInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $costInterfaceFactory;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\CostInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $costInterface;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productIdLocator;
+
+    /**
+     * @var \Magento\Store\Api\StoreRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $storeRepository;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\CostStorage
+     */
+    private $model;
+
+    /**
+     * Set up.
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->pricePersistenceFactory = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\PricePersistenceFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->pricePersistence = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\PricePersistence::class,
+            ['get', 'retrieveSkuById', 'update', 'delete', 'getEntityLinkField'],
+            [],
+            '',
+            false
+        );
+        $this->costInterfaceFactory = $this->getMock(
+            \Magento\Catalog\Api\Data\CostInterfaceFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->costInterface = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\CostInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['setSku', 'setCost', 'setStoreId', 'getSku', 'getCost', 'getStoreId']
+        );
+        $this->productIdLocator = $this->getMockForAbstractClass(
+            \Magento\Catalog\Model\ProductIdLocatorInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['retrieveProductIdsBySkus']
+        );
+        $this->storeRepository = $this->getMockForAbstractClass(
+            \Magento\Store\Api\StoreRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getById']
+        );
+
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject(
+            \Magento\Catalog\Model\Product\Price\CostStorage::class,
+            [
+                'pricePersistenceFactory' => $this->pricePersistenceFactory,
+                'costInterfaceFactory' => $this->costInterfaceFactory,
+                'productIdLocator' => $this->productIdLocator,
+                'storeRepository' => $this->storeRepository,
+                'allowedProductTypes' => ['simple', 'virtual', 'downloadable'],
+            ]
+        );
+    }
+
+    /**
+     * Test get method.
+     *
+     * @return void
+     */
+    public function testGet()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+                ],
+            'sku_2' =>
+                [
+                    2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $rawPrices = [
+            [
+                'row_id' => 1,
+                'value' => 15,
+                'store_id' => 1
+            ],
+            [
+                'row_id' => 2,
+                'value' => 35,
+                'store_id' => 1
+            ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->pricePersistenceFactory
+            ->expects($this->once())
+            ->method('create')
+            ->with(['attributeCode' => 'cost'])
+            ->willReturn($this->pricePersistence);
+        $this->pricePersistence->expects($this->once())->method('get')->with($skus)->willReturn($rawPrices);
+        $this->costInterfaceFactory
+            ->expects($this->exactly(2))
+            ->method('create')
+            ->willReturn($this->costInterface);
+        $this->pricePersistence
+            ->expects($this->exactly(2))
+            ->method('retrieveSkuById')
+            ->willReturnOnConsecutiveCalls('sku_1', 'sku_2');
+        $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id');
+        $this->costInterface
+            ->expects($this->exactly(2))
+            ->method('setSku')
+            ->withConsecutive(['sku_1'], ['sku_2'])
+            ->willReturnSelf();
+        $this->costInterface
+            ->expects($this->exactly(2))
+            ->method('setCost')
+            ->withConsecutive([15], [35])
+            ->willReturnSelf();
+        $this->costInterface
+            ->expects($this->exactly(2))
+            ->method('setStoreId')
+            ->withConsecutive([1], [1])
+            ->willReturnSelf();
+        $this->model->get($skus);
+    }
+
+    /**
+     * Test get method with exception.
+     *
+     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
+     * @expectedExceptionMessage Requested products don't exist: sku_1, sku_2
+     */
+    public function testGetWithException()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => 'configurable'
+                ],
+            'sku_2' =>
+                [
+                    2 => 'grouped'
+                ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->model->get($skus);
+    }
+
+    /**
+     * Test update method.
+     *
+     * @return void
+     */
+    public function testUpdate()
+    {
+        $store = $this->getMockForAbstractClass(
+            \Magento\Store\Api\Data\StoreInterface::class,
+            [],
+            '',
+            false
+        );
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $this->costInterface->expects($this->exactly(4))->method('getSku')->willReturn($sku);
+        $this->productIdLocator
+            ->expects($this->exactly(2))
+            ->method('retrieveProductIdsBySkus')->with([$sku])
+            ->willReturn($idsBySku);
+        $this->costInterface->expects($this->exactly(3))->method('getCost')->willReturn(15);
+        $this->costInterface->expects($this->exactly(2))->method('getStoreId')->willReturn(1);
+        $this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id');
+        $this->storeRepository->expects($this->once())->method('getById')->with(1)->willReturn($store);
+        $this->pricePersistenceFactory
+            ->expects($this->once())
+            ->method('create')
+            ->with(['attributeCode' => 'cost'])
+            ->willReturn($this->pricePersistence);
+        $formattedPrices = [
+            [
+                'store_id' => 1,
+                'row_id' => 1,
+                'value' => 15
+            ]
+        ];
+        $this->pricePersistence->expects($this->once())->method('update')->with($formattedPrices);
+        $this->assertTrue($this->model->update([$this->costInterface]));
+    }
+
+    /**
+     * Test update method without SKU.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute sku: .
+     */
+    public function testUpdateWithoutSku()
+    {
+        $this->costInterface->expects($this->exactly(2))->method('getSku')->willReturn(null);
+        $this->model->update([$this->costInterface]);
+    }
+
+    /**
+     * Test update method with negative cost.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute Cost: -15.
+     */
+    public function testUpdateWithNegativeCost()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $this->costInterface->expects($this->exactly(2))->method('getSku')->willReturn($sku);
+        $this->productIdLocator
+            ->expects($this->once(1))
+            ->method('retrieveProductIdsBySkus')->with([$sku])
+            ->willReturn($idsBySku);
+        $this->costInterface->expects($this->exactly(3))->method('getCost')->willReturn(-15);
+        $this->model->update([$this->costInterface]);
+    }
+
+    /**
+     * Test delete method.
+     *
+     * @return void
+     */
+    public function testDelete()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+                ],
+            'sku_2' =>
+                [
+                    2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->pricePersistenceFactory
+            ->expects($this->once())
+            ->method('create')
+            ->with(['attributeCode' => 'cost'])
+            ->willReturn($this->pricePersistence);
+        $this->pricePersistence->expects($this->once())->method('delete')->with($skus);
+        $this->model->delete($skus);
+    }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9f422245d95967905b577c7cc39626007b16d49
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/PricePersistenceTest.php
@@ -0,0 +1,402 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Test\Unit\Model\Product\Price;
+
+/**
+ * Class PricePersistenceTest.
+ */
+class PricePersistenceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Attribute|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $attributeResource;
+
+    /**
+     * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $attributeRepository;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\ProductAttributeInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productAttribute;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productIdLocator;
+
+    /**
+     * @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $connection;
+
+    /**
+     * @var \Magento\Framework\EntityManager\MetadataPool|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $metadataPool;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\PricePersistence
+     */
+    private $model;
+
+    /**
+     * Set up.
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->attributeResource = $this->getMock(
+            \Magento\Catalog\Model\ResourceModel\Attribute::class,
+            ['getConnection', 'getTable'],
+            [],
+            '',
+            false
+        );
+        $this->attributeRepository = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\ProductAttributeRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['get']
+        );
+        $this->productIdLocator = $this->getMockForAbstractClass(
+            \Magento\Catalog\Model\ProductIdLocatorInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['retrieveProductIdsBySkus']
+        );
+        $this->metadataPool = $this->getMock(
+            \Magento\Framework\EntityManager\MetadataPool::class,
+            ['getLinkField', 'getMetadata'],
+            [],
+            '',
+            false
+        );
+        $this->connection = $this->getMockForAbstractClass(
+            \Magento\Framework\DB\Adapter\AdapterInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['select', 'fetchAll', 'beginTransaction', 'insertOnDuplicate', 'commit', 'rollBack', 'delete']
+        );
+        $this->productAttribute = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\ProductAttributeInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getAttributeId']
+        );
+
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject(
+            \Magento\Catalog\Model\Product\Price\PricePersistence::class,
+            [
+                'attributeResource' => $this->attributeResource,
+                'attributeRepository' => $this->attributeRepository,
+                'productIdLocator' => $this->productIdLocator,
+                'metadataPool' => $this->metadataPool,
+            ]
+        );
+    }
+
+    /**
+     * Test get method.
+     *
+     * @return void
+     */
+    public function testGet()
+    {
+        $attributeId = 5;
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+                ],
+            'sku_2' =>
+                [
+                    2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $select = $this->getMock(
+            \Magento\Framework\DB\Select::class,
+            ['from', 'where'],
+            [],
+            '',
+            false
+        );
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->attributeResource->expects($this->exactly(2))->method('getConnection')->willReturn($this->connection);
+        $this->connection->expects($this->once())->method('select')->willReturn($select);
+        $this->attributeResource
+            ->expects($this->once())
+            ->method('getTable')
+            ->with('catalog_product_entity_decimal')
+            ->willReturn('catalog_product_entity_decimal');
+        $select->expects($this->once())->method('from')->with('catalog_product_entity_decimal')->willReturnSelf();
+        $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
+        $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
+        $select
+            ->expects($this->exactly(2))
+            ->method('where')
+            ->withConsecutive(['row_id IN (?)', [1, 2]], ['attribute_id = ?', $attributeId])
+            ->willReturnSelf();
+        $this->metadataPool->expects($this->atLeastOnce())->method('getMetadata')->willReturnSelf();
+        $this->metadataPool->expects($this->atLeastOnce())->method('getLinkField')->willReturn('row_id');
+        $this->model->get($skus);
+    }
+
+    /**
+     * Test update method.
+     *
+     * @return void
+     */
+    public function testUpdate()
+    {
+        $attributeId = 5;
+        $prices = [
+            [
+                'store_id' => 1,
+                'row_id' => 1,
+                'value' => 15
+            ]
+        ];
+        $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
+        $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
+        $this->attributeResource->expects($this->exactly(2))->method('getConnection')->willReturn($this->connection);
+        $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf();
+        $this->attributeResource
+            ->expects($this->once())
+            ->method('getTable')
+            ->with('catalog_product_entity_decimal')
+            ->willReturn('catalog_product_entity_decimal');
+        $this->connection
+            ->expects($this->once())
+            ->method('insertOnDuplicate')
+            ->with(
+                'catalog_product_entity_decimal',
+                [
+                    [
+                        'store_id' => 1,
+                        'row_id' => 1,
+                        'value' => 15,
+                        'attribute_id' => 5,
+                    ]
+                ],
+                ['value']
+            )
+            ->willReturnSelf();
+        $this->connection->expects($this->once())->method('commit')->willReturnSelf();
+        $this->model->update($prices);
+    }
+
+    /**
+     * Test update method throws exception.
+     *
+     * @expectedException \Magento\Framework\Exception\CouldNotSaveException
+     * @expectedExceptionMessage Could not save Prices.
+     */
+    public function testUpdateWithException()
+    {
+        $attributeId = 5;
+        $prices = [
+            [
+                'store_id' => 1,
+                'row_id' => 1,
+                'value' => 15
+            ]
+        ];
+        $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
+        $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
+        $this->attributeResource->expects($this->exactly(2))->method('getConnection')->willReturn($this->connection);
+        $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf();
+        $this->attributeResource
+            ->expects($this->once())
+            ->method('getTable')
+            ->with('catalog_product_entity_decimal')
+            ->willReturn('catalog_product_entity_decimal');
+        $this->connection
+            ->expects($this->once())
+            ->method('insertOnDuplicate')
+            ->with(
+                'catalog_product_entity_decimal',
+                [
+                    [
+                        'store_id' => 1,
+                        'row_id' => 1,
+                        'value' => 15,
+                        'attribute_id' => 5,
+                    ]
+                ],
+                ['value']
+            )
+            ->willReturnSelf();
+        $this->connection->expects($this->once())->method('commit')->willThrowException(new \Exception());
+        $this->connection->expects($this->once())->method('rollback')->willReturnSelf();
+        $this->model->update($prices);
+    }
+
+    /**
+     * Test delete method.
+     *
+     * @return void
+     */
+    public function testDelete()
+    {
+        $attributeId = 5;
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+                ],
+            'sku_2' =>
+                [
+                    2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
+        $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
+        $this->attributeResource->expects($this->exactly(2))->method('getConnection')->willReturn($this->connection);
+        $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf();
+        $this->attributeResource
+            ->expects($this->once())
+            ->method('getTable')
+            ->with('catalog_product_entity_decimal')
+            ->willReturn('catalog_product_entity_decimal');
+        $this->connection
+            ->expects($this->once())
+            ->method('delete')
+            ->with(
+                'catalog_product_entity_decimal',
+                [
+                    'attribute_id = ?' => $attributeId,
+                    'row_id IN (?)' => [1, 2]
+                ]
+            )
+            ->willReturnSelf();
+        $this->connection->expects($this->once())->method('commit')->willReturnSelf();
+        $this->metadataPool->expects($this->atLeastOnce())->method('getMetadata')->willReturnSelf();
+        $this->metadataPool->expects($this->atLeastOnce())->method('getLinkField')->willReturn('row_id');
+        $this->model->delete($skus);
+    }
+
+    /**
+     * Test delete method throws exception.
+     *
+     * @expectedException \Magento\Framework\Exception\CouldNotDeleteException
+     * @expectedExceptionMessage Could not delete Prices
+     */
+    public function testDeleteWithException()
+    {
+        $attributeId = 5;
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' =>
+                [
+                    1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+                ],
+            'sku_2' =>
+                [
+                    2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL
+                ]
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')->with($skus)
+            ->willReturn($idsBySku);
+        $this->attributeRepository->expects($this->once())->method('get')->willReturn($this->productAttribute);
+        $this->productAttribute->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
+        $this->attributeResource->expects($this->exactly(2))->method('getConnection')->willReturn($this->connection);
+        $this->connection->expects($this->once())->method('beginTransaction')->willReturnSelf();
+        $this->attributeResource
+            ->expects($this->once())
+            ->method('getTable')
+            ->with('catalog_product_entity_decimal')
+            ->willReturn('catalog_product_entity_decimal');
+        $this->connection
+            ->expects($this->once())
+            ->method('delete')
+            ->with(
+                'catalog_product_entity_decimal',
+                [
+                    'attribute_id = ?' => $attributeId,
+                    'row_id IN (?)' => [1, 2]
+                ]
+            )
+            ->willReturnSelf();
+        $this->connection->expects($this->once())->method('commit')->willThrowException(new \Exception());
+        $this->connection->expects($this->once())->method('rollBack')->willReturnSelf();
+        $this->metadataPool->expects($this->atLeastOnce())->method('getMetadata')->willReturnSelf();
+        $this->metadataPool->expects($this->atLeastOnce())->method('getLinkField')->willReturn('row_id');
+        $this->model->delete($skus);
+    }
+
+    /**
+     * Test retrieveSkuById method.
+     *
+     * @param int|null $expectedResult
+     * @param int $id
+     * @param array $skus
+     * @dataProvider dataProviderRetrieveSkuById
+     */
+    public function testRetrieveSkuById($expectedResult, $id, array $skus)
+    {
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')
+            ->willReturn($skus);
+
+        $this->assertEquals($expectedResult, $this->model->retrieveSkuById($id, $skus));
+    }
+
+    /**
+     * Data provider for retrieveSkuById  method.
+     *
+     * @return array
+     */
+    public function dataProviderRetrieveSkuById()
+    {
+        return [
+            [
+                null,
+                2,
+                ['sku_1' => [1 => 1]]
+            ],
+            [
+                'sku_1',
+                1,
+                ['sku_1' => [1 => 1]]
+            ],
+            [
+                null,
+                1,
+                ['sku_1' => [2 => 1]]
+            ],
+        ];
+    }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a885dd8b83698bffcb0106992bee076692bffca
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
@@ -0,0 +1,292 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Test\Unit\Model\Product\Price;
+
+/**
+ * TierPriceStorage test.
+ */
+class TierPriceStorageTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\TierPricePersistence|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $tierPricePersistence;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\TierPriceValidator|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $tierPriceValidator;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\TierPriceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $tierPriceFactory;
+
+    /**
+     * @var \Magento\Catalog\Model\Indexer\Product\Price|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $priceIndexer;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productIdLocator;
+
+    /**
+     * @var \Magento\PageCache\Model\Config|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $config;
+
+    /**
+     * @var \Magento\Framework\App\Cache\TypeListInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $typeList;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\TierPriceStorage
+     */
+    private $tierPriceStorage;
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function setUp()
+    {
+        $this->tierPricePersistence = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\TierPricePersistence::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->tierPricePersistence->expects($this->any())
+            ->method('getEntityLinkField')
+            ->willReturn('row_id');
+        $this->tierPriceValidator = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\TierPriceValidator::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->tierPriceFactory = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\TierPriceFactory::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->priceIndexer = $this->getMock(
+            \Magento\Catalog\Model\Indexer\Product\Price::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->productIdLocator = $this->getMock(
+            \Magento\Catalog\Model\ProductIdLocatorInterface::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->config = $this->getMock(
+            \Magento\PageCache\Model\Config::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->typeList = $this->getMock(
+            \Magento\Framework\App\Cache\TypeListInterface::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->tierPriceStorage = $objectManager->getObject(
+            \Magento\Catalog\Model\Product\Price\TierPriceStorage::class,
+            [
+                'tierPricePersistence' => $this->tierPricePersistence,
+                'tierPriceValidator' => $this->tierPriceValidator,
+                'tierPriceFactory' => $this->tierPriceFactory,
+                'priceIndexer' => $this->priceIndexer,
+                'productIdLocator' => $this->productIdLocator,
+                'config' => $this->config,
+                'typeList' => $this->typeList,
+            ]
+        );
+    }
+
+    /**
+     * Test get method.
+     * @return void
+     */
+    public function testGet()
+    {
+        $skus = ['simple', 'virtual'];
+        $this->productIdLocator->expects($this->atLeastOnce())
+            ->method('retrieveProductIdsBySkus')
+            ->with(['simple', 'virtual'])
+            ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]);
+        $this->tierPricePersistence->expects($this->once())
+            ->method('get')
+            ->willReturn(
+                [
+                    [
+                        'value_id' => 1,
+                        'row_id' => 2,
+                        'all_groups' => 1,
+                        'customer_group_id' => 0,
+                        'qty' => 2.0000,
+                        'value' => 2.0000,
+                        'percentage_value' => null,
+                        'website_id' => 0
+                    ],
+                    [
+                        'value_id' => 2,
+                        'row_id' => 3,
+                        'all_groups' => 1,
+                        'customer_group_id' => 0,
+                        'qty' => 3.0000,
+                        'value' => 3.0000,
+                        'percentage_value' => null,
+                        'website_id' => 0
+                    ]
+                ]
+            );
+        $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
+        $this->tierPriceFactory->expects($this->at(0))->method('create')->willReturn($price);
+        $this->tierPriceFactory->expects($this->at(1))->method('create')->willReturn($price);
+        $prices = $this->tierPriceStorage->get($skus);
+        $this->assertNotEmpty($prices);
+        $this->assertEquals(2, count($prices));
+    }
+
+    /**
+     * Test update method.
+     * @return void
+     */
+    public function testUpdate()
+    {
+        $this->productIdLocator->expects($this->atLeastOnce())
+            ->method('retrieveProductIdsBySkus')
+            ->willReturn(['bundle' => ['2' => 'bundle']]);
+        $this->tierPriceValidator->expects($this->atLeastOnce())->method('validatePrices')->willReturn(true);
+        $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn(
+            [
+                'row_id' => 2,
+                'all_groups' => 1,
+                'customer_group_id' => 0,
+                'qty' => 2,
+                'value' => 3,
+                'percentage_value' => null,
+                'website_id' => 0
+            ]
+        );
+        $this->tierPricePersistence->expects($this->once())
+            ->method('get')
+            ->willReturn(
+                [
+                    [
+                        'value_id' => 1,
+                        'row_id' => 2,
+                        'all_groups' => 1,
+                        'customer_group_id' => 0,
+                        'qty' => 2.0000,
+                        'value' => 2.0000,
+                        'percentage_value' => null,
+                        'website_id' => 0
+                    ]
+                ]
+            );
+        $this->tierPricePersistence->expects($this->atLeastOnce())->method('update');
+        $this->priceIndexer->expects($this->atLeastOnce())->method('execute');
+        $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true);
+        $this->typeList->expects($this->atLeastOnce())->method('invalidate');
+        $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
+        $price->method('getSku')->willReturn('bundle');
+        $this->assertTrue($this->tierPriceStorage->update([$price]));
+    }
+
+    /**
+     * Test replace method.
+     * @return void
+     */
+    public function testReplace()
+    {
+        $this->tierPriceValidator->expects($this->atLeastOnce())->method('validatePrices');
+        $this->productIdLocator->expects($this->atLeastOnce())
+            ->method('retrieveProductIdsBySkus')
+            ->willReturn(['virtual' => ['2' => 'virtual']]);
+        $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn(
+            [
+                'row_id' => 3,
+                'all_groups' => 1,
+                'customer_group_id' => 0,
+                'qty' => 3,
+                'value' => 7,
+                'percentage_value' => null,
+                'website_id' => 0
+            ]
+        );
+        $this->tierPricePersistence->expects($this->atLeastOnce())->method('replace');
+        $this->priceIndexer->expects($this->atLeastOnce())->method('execute');
+        $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
+        $price->method('getSku')->willReturn('virtual');
+        $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true);
+        $this->typeList->expects($this->atLeastOnce())->method('invalidate');
+        $this->assertTrue($this->tierPriceStorage->replace([$price]));
+    }
+
+    /**
+     * Test delete method.
+     * @return void
+     */
+    public function testDelete()
+    {
+        $this->tierPriceValidator->expects($this->atLeastOnce())->method('validatePrices');
+        $this->productIdLocator->expects($this->atLeastOnce())
+            ->method('retrieveProductIdsBySkus')
+            ->willReturn(['simple' => ['2' => 'simple']]);
+        $this->tierPricePersistence->expects($this->once())
+            ->method('get')
+            ->willReturn(
+                [
+                    [
+                        'value_id' => 7,
+                        'row_id' => 7,
+                        'all_groups' => 1,
+                        'customer_group_id' => 0,
+                        'qty' => 5.0000,
+                        'value' => 6.0000,
+                        'percentage_value' => null,
+                        'website_id' => 0
+                    ]
+                ]
+            );
+        $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn(
+            [
+                'row_id' => 3,
+                'all_groups' => 1,
+                'customer_group_id' => 0,
+                'qty' => 3,
+                'value' => 7,
+                'percentage_value' => null,
+                'website_id' => 0
+            ]
+        );
+        $this->tierPricePersistence->expects($this->atLeastOnce())->method('delete');
+        $this->priceIndexer->expects($this->atLeastOnce())->method('execute');
+        $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true);
+        $this->typeList->expects($this->atLeastOnce())->method('invalidate');
+        $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass();
+        $price->method('getSku')->willReturn('simple');
+        $this->assertTrue($this->tierPriceStorage->delete([$price]));
+    }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceValidatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceValidatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1f44c2a75d1862ded128a2c35622dc148e3403a2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceValidatorTest.php
@@ -0,0 +1,471 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Test\Unit\Model\Product\Price;
+
+use Magento\Catalog\Api\Data\TierPriceInterface;
+
+/**
+ * Class TierPriceValidatorTest.
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class TierPriceValidatorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productIdLocator;
+
+    /**
+     * @var \Magento\Framework\Api\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $searchCriteriaBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $filterBuilder;
+
+    /**
+     * @var \Magento\Customer\Api\GroupRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $customerGroupRepository;
+
+    /**
+     * @var \Magento\Store\Api\WebsiteRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $websiteRepository;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\TierPricePersistence|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $tierPricePersistence;
+
+    /**
+     * @var \Magento\Catalog\Api\Data\TierPriceInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $tierPriceInterface;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Price\TierPriceValidator
+     */
+    private $model;
+
+    /**
+     * Set up.
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->productIdLocator = $this->getMockForAbstractClass(
+            \Magento\Catalog\Model\ProductIdLocatorInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['retrieveProductIdsBySkus']
+        );
+        $this->searchCriteriaBuilder = $this->getMock(
+            \Magento\Framework\Api\SearchCriteriaBuilder::class,
+            ['addFilters', 'create'],
+            [],
+            '',
+            false
+        );
+        $this->filterBuilder = $this->getMock(
+            \Magento\Framework\Api\FilterBuilder::class,
+            ['setField', 'setValue', 'create'],
+            [],
+            '',
+            false
+        );
+        $this->customerGroupRepository = $this->getMockForAbstractClass(
+            \Magento\Customer\Api\GroupRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getList']
+        );
+        $this->websiteRepository = $this->getMockForAbstractClass(
+            \Magento\Store\Api\WebsiteRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getById']
+        );
+        $this->tierPricePersistence = $this->getMock(
+            \Magento\Catalog\Model\Product\Price\TierPricePersistence::class,
+            ['addFilters', 'create'],
+            [],
+            '',
+            false
+        );
+        $this->tierPriceInterface = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\TierPriceInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getSku', 'getPrice', 'getPriceType', 'getQuantity', 'getWebsiteId', 'getCustomerGroup']
+        );
+
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject(
+            \Magento\Catalog\Model\Product\Price\TierPriceValidator::class,
+            [
+                'productIdLocator' => $this->productIdLocator,
+                'searchCriteriaBuilder' => $this->searchCriteriaBuilder,
+                'filterBuilder' => $this->filterBuilder,
+                'customerGroupRepository' => $this->customerGroupRepository,
+                'websiteRepository' => $this->websiteRepository,
+                'tierPricePersistence' => $this->tierPricePersistence,
+                'allowedProductTypes' => ['simple', 'virtual', 'bundle', 'downloadable'],
+            ]
+        );
+    }
+
+    /**
+     * Test validateSkus method.
+     *
+     * @return void
+     */
+    public function testValidateSkus()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE],
+            'sku_2' => [2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')
+            ->with($skus)
+            ->willReturn($idsBySku);
+        $this->model->validateSkus($skus);
+    }
+
+    /**
+     * Test validateSkus method throws exception.
+     *
+     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
+     * @expectedExceptionMessage Requested products don't exist: sku_1, sku_2
+     */
+    public function testValidateSkusWithException()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $idsBySku = [
+            'sku_1' => [1 => 'grouped'],
+            'sku_2' => [2 => 'configurable'],
+        ];
+        $this->productIdLocator
+            ->expects($this->once())
+            ->method('retrieveProductIdsBySkus')
+            ->with($skus)
+            ->willReturn($idsBySku);
+        $this->model->validateSkus($skus);
+    }
+
+    /**
+     * Test validatePrices method.
+     *
+     * @return void
+     */
+    public function testValidatePrices()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE],
+            'sku_2' => [2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $productPrice = 15;
+        $this->tierPriceInterface->expects($this->exactly(8))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getPrice')->willReturn($productPrice);
+        $this->tierPriceInterface
+            ->expects($this->exactly(2))
+            ->method('getPriceType')
+            ->willReturn(TierPriceInterface::PRICE_TYPE_FIXED);
+        $this->tierPriceInterface->expects($this->exactly(3))->method('getQuantity')->willReturn(2);
+        $this->checkWebsite($this->tierPriceInterface);
+        $this->checkGroup($this->tierPriceInterface);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method with downloadable product.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute sku: .
+     */
+    public function testValidatePricesWithDownloadableProduct()
+    {
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getSku')->willReturn(null);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method with negative price.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute Price: -15.
+     */
+    public function testValidatePricesWithNegativePrice()
+    {
+        $negativePrice = -15;
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE],
+            'sku_2' => [2 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $this->tierPriceInterface->expects($this->exactly(3))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(3))->method('getPrice')->willReturn($negativePrice);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method with bundle product and fixed price.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute Price Type: fixed.
+     */
+    public function testValidatePricesWithBundleProductAndFixedPrice()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE],
+        ];
+        $productPrice = 15;
+        $this->tierPriceInterface->expects($this->exactly(4))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getPrice')->willReturn($productPrice);
+        $this->tierPriceInterface
+            ->expects($this->exactly(4))
+            ->method('getPriceType')
+            ->willReturn(TierPriceInterface::PRICE_TYPE_FIXED);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method with zero quantity.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute Quantity: 0.
+     */
+    public function testValidatePricesWithZeroQty()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $productPrice = 15;
+        $this->tierPriceInterface->expects($this->exactly(4))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getPrice')->willReturn($productPrice);
+        $this->tierPriceInterface
+            ->expects($this->exactly(2))
+            ->method('getPriceType')
+            ->willReturn(TierPriceInterface::PRICE_TYPE_FIXED);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getQuantity')->willReturn(0);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method without website.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage Invalid attribute website_id: 15.
+     */
+    public function testValidatePricesWithoutWebsite()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $productPrice = 15;
+        $exception = new \Magento\Framework\Exception\NoSuchEntityException();
+        $this->tierPriceInterface->expects($this->exactly(4))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getPrice')->willReturn($productPrice);
+        $this->tierPriceInterface
+            ->expects($this->exactly(2))
+            ->method('getPriceType')
+            ->willReturn(TierPriceInterface::PRICE_TYPE_FIXED);
+        $this->tierPriceInterface->expects($this->once())->method('getQuantity')->willReturn(2);
+        $this->websiteRepository->expects($this->once())->method('getById')->willThrowException($exception);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getWebsiteId')->willReturn(15);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method not unique.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage We found a duplicate website, tier price, customer
+     * group and quantity: Customer Group = retailer, Website Id = 2, Quantity = 2.
+     */
+    public function testValidatePricesNotUnique()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $productPrice = 15;
+        $this->tierPriceInterface->expects($this->exactly(8))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getPrice')->willReturn($productPrice);
+        $this->tierPriceInterface
+            ->expects($this->exactly(2))
+            ->method('getPriceType')
+            ->willReturn(TierPriceInterface::PRICE_TYPE_FIXED);
+        $website = $this->getMockForAbstractClass(
+            \Magento\Store\Api\Data\WebsiteInterface::class,
+            [],
+            '',
+            false
+        );
+        $this->tierPriceInterface
+            ->expects($this->exactly(5))
+            ->method('getWebsiteId')
+            ->willReturnOnConsecutiveCalls(1, 0, 0, 1, 2);
+        $this->websiteRepository->expects($this->once())->method('getById')->willReturn($website);
+        $this->tierPriceInterface->expects($this->exactly(4))->method('getQuantity')->willReturn(2);
+        $this->tierPriceInterface->expects($this->exactly(3))->method('getCustomerGroup')->willReturn('retailer');
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Test validatePrices method without group.
+     *
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     * @expectedExceptionMessage No such entity with Customer Group = wholesale.
+     */
+    public function testValidatePricesWithoutGroup()
+    {
+        $sku = 'sku_1';
+        $idsBySku = [
+            'sku_1' => [1 => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL],
+        ];
+        $productPrice = 15;
+        $this->tierPriceInterface->expects($this->exactly(8))->method('getSku')->willReturn($sku);
+        $this->productIdLocator->expects($this->exactly(2))->method('retrieveProductIdsBySkus')->willReturn($idsBySku);
+        $this->tierPriceInterface->expects($this->exactly(2))->method('getPrice')->willReturn($productPrice);
+        $this->tierPriceInterface
+            ->expects($this->exactly(2))
+            ->method('getPriceType')
+            ->willReturn(TierPriceInterface::PRICE_TYPE_FIXED);
+        $this->tierPriceInterface->expects($this->exactly(3))->method('getQuantity')->willReturn(2);
+        $this->checkWebsite($this->tierPriceInterface);
+        $searchCriteria = $this->getMock(
+            \Magento\Framework\Api\SearchCriteria::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $searchResults = $this->getMockForAbstractClass(
+            \Magento\Customer\Api\Data\GroupSearchResultsInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getItems']
+        );
+        $this->tierPriceInterface->expects($this->exactly(3))->method('getCustomerGroup')->willReturn('wholesale');
+        $this->searchCriteriaBuilder->expects($this->once())->method('addFilters')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setField')->with('customer_group_code')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setValue')->with('wholesale')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('create')->willReturnSelf();
+        $this->searchCriteriaBuilder
+            ->expects($this->once())
+            ->method('create')
+            ->willReturn($searchCriteria);
+        $this->customerGroupRepository
+            ->expects($this->once())
+            ->method('getList')
+            ->with($searchCriteria)
+            ->willReturn($searchResults);
+        $searchResults->expects($this->once())->method('getItems')->willReturn([]);
+        $this->model->validatePrices([$this->tierPriceInterface], []);
+    }
+
+    /**
+     * Check website.
+     *
+     * @param \PHPUnit_Framework_MockObject_MockObject $price
+     */
+    private function checkWebsite(\PHPUnit_Framework_MockObject_MockObject $price)
+    {
+        $website = $this->getMockForAbstractClass(
+            \Magento\Store\Api\Data\WebsiteInterface::class,
+            [],
+            '',
+            false
+        );
+        $price->expects($this->exactly(3))->method('getWebsiteId')->willReturn(1);
+        $this->websiteRepository->expects($this->once())->method('getById')->willReturn($website);
+    }
+
+    /**
+     * Check group.
+     *
+     * @param \PHPUnit_Framework_MockObject_MockObject $price
+     */
+    private function checkGroup(\PHPUnit_Framework_MockObject_MockObject $price)
+    {
+        $searchCriteria = $this->getMock(
+            \Magento\Framework\Api\SearchCriteria::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $searchResults = $this->getMockForAbstractClass(
+            \Magento\Customer\Api\Data\GroupSearchResultsInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getItems']
+        );
+        $group = $this->getMockForAbstractClass(
+            \Magento\Customer\Api\Data\GroupInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getCode', 'getId']
+        );
+
+        $price->expects($this->exactly(3))->method('getCustomerGroup')->willReturn('wholesale');
+        $this->searchCriteriaBuilder->expects($this->once())->method('addFilters')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setField')->with('customer_group_code')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setValue')->with('wholesale')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('create')->willReturnSelf();
+        $this->searchCriteriaBuilder
+            ->expects($this->once())
+            ->method('create')
+            ->willReturn($searchCriteria);
+        $this->customerGroupRepository
+            ->expects($this->once())
+            ->method('getList')
+            ->with($searchCriteria)
+            ->willReturn($searchResults);
+        $searchResults->expects($this->once())->method('getItems')->willReturn([$group]);
+        $group->expects($this->once())->method('getCode')->willReturn('wholesale');
+        $group->expects($this->once())->method('getId')->willReturn(4);
+    }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..75f81195f02e420929edbea8580692c1125752b4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Test\Unit\Model;
+
+/**
+ * Class ProductIdLocatorTest.
+ */
+class ProductIdLocatorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Framework\Api\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $searchCriteriaBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $filterBuilder;
+
+    /**
+     * @var \Magento\Framework\EntityManager\MetadataPool|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $metadataPool;
+
+    /**
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productRepository;
+
+    /**
+     * @var \Magento\Catalog\Model\ProductIdLocator
+     */
+    private $model;
+
+    /**
+     * Set up.
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->searchCriteriaBuilder = $this->getMock(
+            \Magento\Framework\Api\SearchCriteriaBuilder::class,
+            ['addFilters', 'create'],
+            [],
+            '',
+            false
+        );
+        $this->filterBuilder = $this->getMock(
+            \Magento\Framework\Api\FilterBuilder::class,
+            ['setField', 'setConditionType', 'setValue', 'create'],
+            [],
+            '',
+            false
+        );
+        $this->metadataPool = $this->getMock(
+            \Magento\Framework\EntityManager\MetadataPool::class,
+            ['getMetadata'],
+            [],
+            '',
+            false
+        );
+        $this->productRepository = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\ProductRepositoryInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getList']
+        );
+
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject(
+            \Magento\Catalog\Model\ProductIdLocator::class,
+            [
+                'searchCriteriaBuilder' => $this->searchCriteriaBuilder,
+                'filterBuilder' => $this->filterBuilder,
+                'metadataPool' => $this->metadataPool,
+                'productRepository' => $this->productRepository,
+            ]
+        );
+    }
+
+    /**
+     * Test retrieve
+     */
+    public function testRetrieveProductIdsBySkus()
+    {
+        $skus = ['sku_1', 'sku_2'];
+        $searchCriteria = $this->getMock(
+            \Magento\Framework\Api\SearchCriteria::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $searchResults = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\ProductSearchResultsInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getItems']
+        );
+        $product = $this->getMockForAbstractClass(
+            \Magento\Catalog\Api\Data\ProductInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getSku', 'getData', 'getTypeId']
+        );
+        $metaDataInterface = $this->getMockForAbstractClass(
+            \Magento\Framework\EntityManager\EntityMetadataInterface::class,
+            [],
+            '',
+            false,
+            true,
+            true,
+            ['getLinkField']
+        );
+        $this->searchCriteriaBuilder->expects($this->once())->method('addFilters')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setField')->with('sku')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setConditionType')->with('in')->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('setValue')->with(['sku_1', 'sku_2'])->willReturnSelf();
+        $this->filterBuilder->expects($this->once())->method('create')->willReturnSelf();
+        $this->searchCriteriaBuilder
+            ->expects($this->once())
+            ->method('create')
+            ->willReturn($searchCriteria);
+        $this->productRepository
+            ->expects($this->once())
+            ->method('getList')
+            ->with($searchCriteria)
+            ->willReturn($searchResults);
+        $searchResults->expects($this->once())->method('getItems')->willReturn([$product]);
+        $this->metadataPool
+            ->expects($this->once())
+            ->method('getMetadata')
+            ->with(\Magento\Catalog\Api\Data\ProductInterface::class)
+            ->willReturn($metaDataInterface);
+        $metaDataInterface->expects($this->once())->method('getLinkField')->willReturn('entity_id');
+        $product->expects($this->once())->method('getSku')->willReturn('sku_1');
+        $product->expects($this->once())->method('getData')->with('entity_id')->willReturn(1);
+        $product->expects($this->once())->method('getTypeId')->willReturn('simple');
+        $this->assertEquals(
+            ['sku_1' => [1 => 'simple']],
+            $this->model->retrieveProductIdsBySkus($skus)
+        );
+    }
+}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
index 53360de08b434b4a0314fe341760c55fef476ef4..326e92417670a77bc269625153f3a56f715bb7e5 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
@@ -407,6 +407,7 @@ class AdvancedPricing extends AbstractModifier
                 'data' => [
                     'config' => [
                         'componentType' => 'dynamicRows',
+                        'component' => 'Magento_Catalog/js/components/dynamic-rows-tier-price',
                         'label' => __('Customer Group Price'),
                         'renderDefaultRecord' => false,
                         'recordTemplate' => 'record',
diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml
index 5fc5ec8d44fd0a88f8a5d542d5518fa60ad77629..d6ecaa7c40391de2b12abac33d25b9ecd5620af8 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/di.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml
@@ -169,4 +169,9 @@
             <argument name="scopeName" xsi:type="string">product_form.product_form</argument>
         </arguments>
     </type>
+    <type name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\TierPrice">
+        <arguments>
+            <argument name="productPriceOptions" xsi:type="object">Magento\Catalog\Model\Config\Source\Product\Options\TierPrice</argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 27b9a19065e99f22fccf4f2f42076c69d0f1e0c2..0142f7c2f261837ae1e0d8f25418486951da3043 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -51,6 +51,13 @@
     <preference for="Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface" type="Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver"/>
     <preference for="Magento\Catalog\Model\Product\Media\ConfigInterface" type="Magento\Catalog\Model\Product\Media\Config"/>
     <preference for="Magento\Framework\View\Asset\ContextInterface" type="Magento\Catalog\Model\View\Asset\Image\Context"/>
+    <preference for="Magento\Catalog\Api\TierPriceStorageInterface" type="Magento\Catalog\Model\Product\Price\TierPriceStorage" />
+    <preference for="Magento\Catalog\Api\Data\TierPriceInterface" type="Magento\Catalog\Model\Product\Price\TierPrice" />
+    <preference for="Magento\Catalog\Api\BasePriceStorageInterface" type="Magento\Catalog\Model\Product\Price\BasePriceStorage" />
+    <preference for="Magento\Catalog\Api\Data\BasePriceInterface" type="Magento\Catalog\Model\Product\Price\BasePrice" />
+    <preference for="Magento\Catalog\Api\CostStorageInterface" type="Magento\Catalog\Model\Product\Price\CostStorage" />
+    <preference for="Magento\Catalog\Api\Data\CostInterface" type="Magento\Catalog\Model\Product\Price\Cost" />
+    <preference for="Magento\Catalog\Model\ProductIdLocatorInterface" type="Magento\Catalog\Model\ProductIdLocator" />
     <type name="Magento\Customer\Model\ResourceModel\Visitor">
         <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" />
     </type>
@@ -848,4 +855,30 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Catalog\Model\Product\Price\CostStorage">
+        <arguments>
+            <argument name="allowedProductTypes" xsi:type="array">
+                <item name="0" xsi:type="string">simple</item>
+                <item name="1" xsi:type="string">virtual</item>
+            </argument>
+        </arguments>
+    </type>
+    <type name="Magento\Catalog\Model\Product\Price\BasePriceStorage">
+        <arguments>
+            <argument name="allowedProductTypes" xsi:type="array">
+                <item name="0" xsi:type="string">simple</item>
+                <item name="1" xsi:type="string">virtual</item>
+                <item name="2" xsi:type="string">bundle</item>
+            </argument>
+        </arguments>
+    </type>
+    <type name="Magento\Catalog\Model\Product\Price\TierPriceValidator">
+        <arguments>
+            <argument name="allowedProductTypes" xsi:type="array">
+                <item name="0" xsi:type="string">simple</item>
+                <item name="1" xsi:type="string">virtual</item>
+                <item name="2" xsi:type="string">bundle</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Catalog/etc/webapi.xml b/app/code/Magento/Catalog/etc/webapi.xml
index 99670c347a89babc45714d5ed0350676f5cf48fe..b53f11b1ff2959a583b1a401a7566ca7eee33589 100644
--- a/app/code/Magento/Catalog/etc/webapi.xml
+++ b/app/code/Magento/Catalog/etc/webapi.xml
@@ -246,6 +246,60 @@
             <resource ref="Magento_Catalog::catalog"/>
         </resources>
     </route>
+    <route url="/V1/products/tier-prices-information" method="POST">
+        <service class="Magento\Catalog\Api\TierPriceStorageInterface" method="get"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/tier-prices" method="POST">
+        <service class="Magento\Catalog\Api\TierPriceStorageInterface" method="update"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/tier-prices" method="PUT">
+        <service class="Magento\Catalog\Api\TierPriceStorageInterface" method="replace"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/tier-prices-delete" method="POST">
+        <service class="Magento\Catalog\Api\TierPriceStorageInterface" method="delete"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/base-prices-information" method="POST">
+        <service class="Magento\Catalog\Api\BasePriceStorageInterface" method="get"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/base-prices" method="POST">
+        <service class="Magento\Catalog\Api\BasePriceStorageInterface" method="update"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/cost-information" method="POST">
+        <service class="Magento\Catalog\Api\CostStorageInterface" method="get"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/cost" method="POST">
+        <service class="Magento\Catalog\Api\CostStorageInterface" method="update"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
+    <route url="/V1/products/cost-delete" method="POST">
+        <service class="Magento\Catalog\Api\CostStorageInterface" method="delete"/>
+        <resources>
+            <resource ref="Magento_Catalog::catalog"/>
+        </resources>
+    </route>
 
     <route url="/V1/categories/:categoryId" method="DELETE">
         <service class="Magento\Catalog\Api\CategoryRepositoryInterface" method="deleteByIdentifier" />
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-tier-price.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-tier-price.js
new file mode 100644
index 0000000000000000000000000000000000000000..6dc4c747a44514be814ea4d939135070287052d8
--- /dev/null
+++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-tier-price.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'underscore',
+    'Magento_Ui/js/dynamic-rows/dynamic-rows'
+], function (_, DynamicRows) {
+    'use strict';
+
+    return DynamicRows.extend({
+
+        /**
+         * Init header elements
+         */
+        initHeader: function () {
+            var labels;
+
+            this._super();
+            labels = _.clone(this.labels());
+            labels = _.sortBy(labels, function (label) {
+                return label.sortOrder;
+            });
+
+            this.labels(labels);
+        }
+    });
+});
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js
index fb15e47eaf7d487da1c8671c545f0b99ce2a7445..78bd0ca73c495785f981a6d2d6ef8e4957fe6d9b 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js
@@ -242,7 +242,7 @@ define(
                     emailValidationResult = customer.isLoggedIn();
 
                 if (!quote.shippingMethod()) {
-                    this.errorValidationMessage($.mage.__('Please specify a shipping method.'));
+                    this.errorValidationMessage($t('Please specify a shipping method.'));
 
                     return false;
                 }
diff --git a/app/code/Magento/Customer/Setup/UpgradeSchema.php b/app/code/Magento/Customer/Setup/UpgradeSchema.php
index 33ec2352e865d0b6fd5cbbde5d9646b5dd92975d..46ccd83fcaae11b3ab4720e5ead60b3625bbda38 100755
--- a/app/code/Magento/Customer/Setup/UpgradeSchema.php
+++ b/app/code/Magento/Customer/Setup/UpgradeSchema.php
@@ -136,14 +136,12 @@ class UpgradeSchema implements UpgradeSchemaInterface
             ]
         );
         foreach ($keys as $key) {
-            $setup->getConnection()->modifyColumn(
+            $description = $setup->getConnection()->describeTable($key['TABLE_NAME'])[$key['COLUMN_NAME']];
+            $description['DATA_TYPE'] = 'int';
+            $setup->getConnection()->modifyColumnByDdl(
                 $key['TABLE_NAME'],
                 $key['COLUMN_NAME'],
-                [
-                    'type' => 'integer',
-                    'unsigned' => true,
-                    'nullable' => false
-                ]
+                $description
             );
         }
     }
diff --git a/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js b/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js
index 649238a81d1f714cf6f6ac282b2695dec0a36fbf..0dca72ade73b69a4c7603e31286a68162809976f 100644
--- a/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js
+++ b/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js
@@ -69,19 +69,24 @@ define(
             },
 
             /** Provide login action */
-            login: function (loginForm) {
+            login: function (formUiElement, event) {
                 var loginData = {},
-                    formDataArray = $(loginForm).serializeArray();
+                    formElement = $(event.currentTarget),
+                    formDataArray = formElement.serializeArray();
+
+                event.stopPropagation();
                 formDataArray.forEach(function (entry) {
                     loginData[entry.name] = entry.value;
                 });
 
-                if ($(loginForm).validation() &&
-                    $(loginForm).validation('isValid')
+                if (formElement.validation() &&
+                    formElement.validation('isValid')
                 ) {
                     this.isLoading(true);
-                    loginAction(loginData, null, false);
+                    loginAction(loginData);
                 }
+
+                return false;
             }
         });
     }
diff --git a/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html b/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html
index 9ad2cf5265043b4fd1850514258d99276d500ff3..db9c0a3819ffdbaf09b4cfb4ea1d4385909160d9 100644
--- a/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html
+++ b/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html
@@ -50,7 +50,7 @@
         <div class="block-content" aria-labelledby="block-customer-login-heading">
             <form class="form form-login"
                   method="post"
-                  data-bind="submit:login"
+                  data-bind="event: {submit: login }"
                   id="login-form">
                 <div class="fieldset login" data-bind="attr: {'data-hasrequired': $t('* Required Fields')}">
                     <div class="field email required">
diff --git a/app/code/Magento/Downloadable/etc/di.xml b/app/code/Magento/Downloadable/etc/di.xml
index d7e2aafe79582d0f50a419cdc9bcf7f2f090cdcd..8e3d0bb6c5c5ae598a8365770f7e2ae932c78d73 100644
--- a/app/code/Magento/Downloadable/etc/di.xml
+++ b/app/code/Magento/Downloadable/etc/di.xml
@@ -123,4 +123,25 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Catalog\Model\Product\Price\CostStorage">
+        <arguments>
+            <argument name="allowedProductTypes" xsi:type="array">
+                <item name="2" xsi:type="string">downloadable</item>
+            </argument>
+        </arguments>
+    </type>
+    <type name="Magento\Catalog\Model\Product\Price\TierPriceValidator">
+        <arguments>
+            <argument name="allowedProductTypes" xsi:type="array">
+                <item name="3" xsi:type="string">downloadable</item>
+            </argument>
+        </arguments>
+    </type>
+    <type name="Magento\Catalog\Model\Product\Price\BasePriceStorage">
+        <arguments>
+            <argument name="allowedProductTypes" xsi:type="array">
+                <item name="3" xsi:type="string">downloadable</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php
index 0f96b14124d042e86adbad3e944b2bdc97ee2e1a..893eda8cc6020882763056e78f844ab19166c1a6 100644
--- a/app/code/Magento/Paypal/Model/Express/Checkout.php
+++ b/app/code/Magento/Paypal/Model/Express/Checkout.php
@@ -7,7 +7,9 @@ namespace Magento\Paypal\Model\Express;
 
 use Magento\Customer\Api\Data\CustomerInterface as CustomerDataObject;
 use Magento\Customer\Model\AccountManagement;
+use Magento\Framework\App\ObjectManager;
 use Magento\Paypal\Model\Config as PaypalConfig;
+use Magento\Sales\Api\OrderRepositoryInterface;
 use Magento\Sales\Model\Order\Email\Sender\OrderSender;
 use Magento\Quote\Model\Quote\Address;
 use Magento\Framework\DataObject;
@@ -268,6 +270,11 @@ class Checkout
      */
     protected $totalsCollector;
 
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepository;
+
     /**
      * @param \Psr\Log\LoggerInterface $logger
      * @param \Magento\Customer\Model\Url $customerUrl
@@ -789,7 +796,8 @@ class Checkout
 
         $this->ignoreAddressValidation();
         $this->_quote->collectTotals();
-        $order = $this->quoteManagement->submit($this->_quote);
+        $orderId = $this->quoteManagement->placeOrder($this->_quote->getId());
+        $order = $this->getOrderRepository()->get($orderId);
 
         if (!$order) {
             return;
@@ -1157,4 +1165,20 @@ class Checkout
             ->setCustomerGroupId(\Magento\Customer\Model\Group::NOT_LOGGED_IN_ID);
         return $this;
     }
+
+    /**
+     * Returns order repository instance
+     *
+     * @return OrderRepositoryInterface
+     * @deprecated
+     */
+    private function getOrderRepository()
+    {
+        if ($this->orderRepository === null) {
+            $this->orderRepository = ObjectManager::getInstance()
+                ->get(OrderRepositoryInterface::class);
+        }
+
+        return $this->orderRepository;
+    }
 }
diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php b/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php
index 0d1de5cf80cc3463a8c24f3c36a61b4b40286d94..6b13fbc24953292a01326a819aeb8e325711eba2 100644
--- a/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php
+++ b/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php
@@ -40,6 +40,6 @@ class ExportSearchCsv extends TermController
         /** @var \Magento\Framework\View\Result\Layout $resultLayout */
         $resultLayout = $this->resultFactory->create(ResultFactory::TYPE_LAYOUT);
         $content = $resultLayout->getLayout()->getChildBlock('adminhtml.report.search.grid', 'grid.export');
-        return $this->fileFactory->create(\search.csv::class, $content->getCsvFile(), DirectoryList::VAR_DIR);
+        return $this->fileFactory->create('search.csv', $content->getCsvFile(), DirectoryList::VAR_DIR);
     }
 }
diff --git a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/ExportSearchCsvTest.php b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/ExportSearchCsvTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..041d14899d5b683693175399cf2836d7b56209b1
--- /dev/null
+++ b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/ExportSearchCsvTest.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Search\Test\Unit\Controller\Adminhtml\Term;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Magento\Framework\Controller\ResultFactory;
+use Magento\Framework\App\Filesystem\DirectoryList;
+
+class ExportSearchCsvTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\TaxImportExport\Controller\Adminhtml\Rate\ExportPost
+     */
+    private $controller;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $fileFactoryMock;
+
+    /**
+     * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
+     */
+    private $objectManagerHelper;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $resultFactoryMock;
+
+    protected function setUp()
+    {
+        $this->objectManagerHelper = new ObjectManagerHelper($this);
+        $this->fileFactoryMock = $this->getMock(
+            \Magento\Framework\App\Response\Http\FileFactory::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->resultFactoryMock = $this->getMock(
+            \Magento\Framework\Controller\ResultFactory::class,
+            [],
+            [],
+            '',
+            false
+        );
+
+        $this->controller = $this->objectManagerHelper->getObject(
+            \Magento\Search\Controller\Adminhtml\Term\ExportSearchCsv::class,
+            [
+                'fileFactory' => $this->fileFactoryMock,
+                'resultFactory' => $this->resultFactoryMock
+            ]
+        );
+    }
+
+    public function testExecute()
+    {
+        $resultLayoutMock = $this->getMock(\Magento\Framework\View\Result\Layout::class, [], [], '', false);
+        $layoutMock = $this->getMock(\Magento\Framework\View\LayoutInterface::class);
+        $contentMock = $this->getMock(
+            \Magento\Framework\View\Element\AbstractBlock::class,
+            ['getCsvFile'],
+            [],
+            '',
+            false
+        );
+        $this->resultFactoryMock
+            ->expects($this->once())
+            ->method('create')
+            ->with(ResultFactory::TYPE_LAYOUT)->willReturn($resultLayoutMock);
+        $resultLayoutMock->expects($this->once())->method('getLayout')->willReturn($layoutMock);
+        $layoutMock->expects($this->once())->method('getChildBlock')->willReturn($contentMock);
+        $contentMock->expects($this->once())->method('getCsvFile')->willReturn('csvFile');
+        $this->fileFactoryMock
+            ->expects($this->once())
+            ->method('create')
+            ->with('search.csv', 'csvFile', DirectoryList::VAR_DIR);
+        $this->controller->execute();
+    }
+}
diff --git a/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ExportPost.php b/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ExportPost.php
index cceb0c6de9fd63b4cf773ad778a27967bad8033d..351a2fe50d46a3b178be6514acbb9244ed2f53ad 100644
--- a/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ExportPost.php
+++ b/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ExportPost.php
@@ -81,7 +81,7 @@ class ExportPost extends \Magento\TaxImportExport\Controller\Adminhtml\Rate
 
             $content .= $rate->toString($template) . "\n";
         }
-        return $this->fileFactory->create(\tax_rates.csv::class, $content, DirectoryList::VAR_DIR);
+        return $this->fileFactory->create('tax_rates.csv', $content, DirectoryList::VAR_DIR);
     }
 
     /**
diff --git a/app/code/Magento/TaxImportExport/Test/Unit/Controller/Adminhtml/Rate/ExportPostTest.php b/app/code/Magento/TaxImportExport/Test/Unit/Controller/Adminhtml/Rate/ExportPostTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..744ad42c66d39d7358e7a28f2b98ec45fabadf11
--- /dev/null
+++ b/app/code/Magento/TaxImportExport/Test/Unit/Controller/Adminhtml/Rate/ExportPostTest.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\TaxImportExport\Test\Unit\Controller\Adminhtml\Rate;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Magento\Framework\App\Filesystem\DirectoryList;
+
+class ExportPostTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\TaxImportExport\Controller\Adminhtml\Rate\ExportPost
+     */
+    private $controller;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $fileFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $objectManagerMock;
+
+    /**
+     * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
+     */
+    private $objectManagerHelper;
+
+    protected function setUp()
+    {
+        $this->objectManagerHelper = new ObjectManagerHelper($this);
+        $this->fileFactoryMock = $this->getMock(
+            \Magento\Framework\App\Response\Http\FileFactory::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class);
+        $this->controller = $this->objectManagerHelper->getObject(
+            \Magento\TaxImportExport\Controller\Adminhtml\Rate\ExportPost::class,
+            [
+                'fileFactory' => $this->fileFactoryMock,
+                'objectManager' => $this->objectManagerMock
+            ]
+        );
+    }
+
+    public function testExecute()
+    {
+        $headers = new \Magento\Framework\DataObject(
+            [
+                'code' => __('Code'),
+                'country_name' => __('Country'),
+                'region_name' => __('State'),
+                'tax_postcode' => __('Zip/Post Code'),
+                'rate' => __('Rate'),
+                'zip_is_range' => __('Zip/Post is Range'),
+                'zip_from' => __('Range From'),
+                'zip_to' => __('Range To'),
+            ]
+        );
+        $template = '"{{code}}","{{country_name}}","{{region_name}}","{{tax_postcode}}","{{rate}}"' .
+            ',"{{zip_is_range}}","{{zip_from}}","{{zip_to}}"';
+        $content = $headers->toString($template);
+        $content .= "\n";
+        $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false);
+        $storeCollectionMock = $this->objectManagerHelper->getCollectionMock(
+            \Magento\Store\Model\ResourceModel\Store\Collection::class,
+            []
+        );
+        $rateCollectionMock = $this->objectManagerHelper->getCollectionMock(
+            \Magento\Tax\Model\ResourceModel\Calculation\Rate\Collection::class,
+            []
+        );
+
+        $taxCollectionMock = $this->objectManagerHelper->getCollectionMock(
+            \Magento\Tax\Model\ResourceModel\Calculation\Rate\Title\Collection::class,
+            []
+        );
+        $storeCollectionMock->expects($this->once())->method('setLoadDefault')->willReturnSelf();
+        $rateTitleMock = $this->getMock(\Magento\Tax\Model\Calculation\Rate\Title::class, [], [], '', false);
+        $rateTitleMock->expects($this->once())->method('getCollection')->willReturn($taxCollectionMock);
+        $storeMock->expects($this->once())->method('getCollection')->willReturn($storeCollectionMock);
+        $this->objectManagerMock->expects($this->any())->method('create')->willReturnMap([
+            [\Magento\Store\Model\Store::class, [], $storeMock],
+            [\Magento\Tax\Model\Calculation\Rate\Title::class, [], $rateTitleMock],
+            [\Magento\Tax\Model\ResourceModel\Calculation\Rate\Collection::class, [], $rateCollectionMock]
+        ]);
+        $rateCollectionMock->expects($this->once())->method('joinCountryTable')->willReturnSelf();
+        $rateCollectionMock->expects($this->once())->method('joinRegionTable')->willReturnSelf();
+        $this->fileFactoryMock
+            ->expects($this->once())
+            ->method('create')
+            ->with('tax_rates.csv', $content, DirectoryList::VAR_DIR);
+        $this->controller->execute();
+    }
+}
diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js
index 449418f07c100c91e139231c704bb28fd8c9ae82..bfae1cf87030f45d8bbda74e8eb3d9f741f00201 100644
--- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js
+++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js
@@ -538,7 +538,8 @@ define([
                         label: cell.config.label,
                         name: cell.name,
                         required: !!cell.config.validation,
-                        columnsHeaderClasses: cell.config.columnsHeaderClasses
+                        columnsHeaderClasses: cell.config.columnsHeaderClasses,
+                        sortOrder: cell.config.sortOrder
                     });
 
                     this.labels.push(data);
diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
index 799094cf93d7cfa4ffcfb6b32a2767e93c882a06..3273df52b8d622146ab134184ec607503938e51a 100644
--- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
@@ -319,6 +319,11 @@
 
     .order-products-toolbar {
         position: relative;
+
+        .toolbar-amount {
+            position: relative;
+            text-align: center;
+        }
     }
 }
 
diff --git a/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less
index 95ec8b694b1ca5a24dc9af66120acbc857294e5b..c8c885fafda19d5ca6592ff22f37ce0612f82694 100644
--- a/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less
@@ -40,6 +40,7 @@
 
     .review-control-vote {
         .lib-rating-vote();
+
         &:before {
             .lib-rating-icons-content(
                 @_icon-content: @icon-star-empty
@@ -47,6 +48,19 @@
         }
     }
 
+    //
+    //  Account Review list page
+    //  -----------------------------------------
+
+    .products-reviews-toolbar {
+        position: relative;
+
+        .toolbar-amount {
+            position: relative;
+            text-align: center;
+        }
+    }
+
     //
     //  Review product page
     //  -----------------------------------------
@@ -95,6 +109,7 @@
         .fieldset &-legend.legend {
             border-bottom: 0;
             line-height: 1.3;
+            margin-bottom: @indent__base;
             padding: 0;
 
             span {
@@ -105,8 +120,6 @@
                 display: block;
                 font-weight: 600;
             }
-
-            margin-bottom: @indent__base;
         }
 
         .fieldset &-field-ratings {
@@ -189,6 +202,7 @@
             margin-bottom: @indent__base;
         }
     }
+
     .page-main {
         .column {
             .review-add {
@@ -284,9 +298,6 @@
             a:not(:last-child) {
                 margin-right: 30px;
             }
-
-            .action.view span {
-            }
         }
     }
 
@@ -331,6 +342,7 @@
         .items {
             .item {
                 .lib-css(margin-bottom, @indent__base);
+
                 &:last-child {
                     margin-bottom: 0;
                 }
@@ -339,6 +351,7 @@
 
         .product-name {
             display: inline-block;
+
             &:not(:last-child) {
                 .lib-css(margin-bottom, @indent__xs);
             }
@@ -415,9 +428,6 @@
             width: 30%;
         }
 
-        .product-info {
-        }
-
         .review-details {
             margin: 0;
 
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/BasePriceStorageTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/BasePriceStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..aad068ce6f0799feb342e93b3f94c6e6899c18c9
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/BasePriceStorageTest.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * BasePriceStorage test.
+ */
+class BasePriceStorageTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogBasePriceStorageV1';
+    const SERVICE_VERSION = 'V1';
+    const SIMPLE_PRODUCT_SKU = 'simple';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * Set up.
+     */
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test get method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testGet()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/base-prices-information',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['skus' => [self::SIMPLE_PRODUCT_SKU]]);
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $product = $productRepository->get(self::SIMPLE_PRODUCT_SKU);
+
+        $this->assertNotEmpty($response);
+        $this->assertEquals($product->getPrice(), $response[0]['price']);
+        $this->assertEquals($product->getSku(), $response[0]['sku']);
+    }
+
+    /**
+     * Test update method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testUpdate()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/base-prices',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Update',
+            ],
+        ];
+        $newPrice = 9999;
+        $storeId = 0;
+        $response = $this->_webApiCall(
+            $serviceInfo,
+            [
+                'prices' => [
+                    [
+                        'price' => $newPrice,
+                        'store_id' => $storeId,
+                        'sku' => self::SIMPLE_PRODUCT_SKU,
+                    ]
+                ]
+            ]
+        );
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $product = $productRepository->get(self::SIMPLE_PRODUCT_SKU);
+
+        $this->assertNotEmpty($response);
+        $this->assertEquals($product->getPrice(), $newPrice);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CostStorageTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CostStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6af9960a524dfe63910b51dbee08335af109d909
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CostStorageTest.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * CostStorage test.
+ */
+class CostStorageTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogCostStorageV1';
+    const SERVICE_VERSION = 'V1';
+    const SIMPLE_PRODUCT_SKU = 'simple';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * Set up.
+     */
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test get method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testGet()
+    {
+        $cost = 3057;
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        $productRepository->save($productRepository->get(self::SIMPLE_PRODUCT_SKU)->setData('cost', $cost));
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/cost-information',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Delete',
+            ],
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['skus' => [self::SIMPLE_PRODUCT_SKU]]);
+
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $product = $productRepository->get(self::SIMPLE_PRODUCT_SKU);
+
+        $this->assertNotEmpty($response);
+        $this->assertEquals($product->getCost(), $cost);
+    }
+
+    /**
+     * Test update method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testUpdate()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/cost',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Update',
+            ],
+        ];
+        $storeId = 0;
+        $newCost = 31337;
+        $response = $this->_webApiCall(
+            $serviceInfo,
+            [
+                'prices' => [
+                    [
+                        'cost' => $newCost,
+                        'store_id' => $storeId,
+                        'sku' => self::SIMPLE_PRODUCT_SKU,
+                    ]
+                ]
+            ]
+        );
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $product = $productRepository->get(self::SIMPLE_PRODUCT_SKU);
+        $this->assertNotEmpty($response);
+        $this->assertEquals($product->getCost(), $newCost);
+    }
+
+    /**
+     * Test delete method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testDelete()
+    {
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        $productRepository->save($productRepository->get(self::SIMPLE_PRODUCT_SKU)->setData('cost', 777));
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/cost-delete',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Delete',
+            ],
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['skus' => [self::SIMPLE_PRODUCT_SKU]]);
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $product = $productRepository->get(self::SIMPLE_PRODUCT_SKU);
+        $this->assertTrue($response);
+        $this->assertNull($product->getCost());
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/TierPriceStorageTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/TierPriceStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..232c63257215036e414236fba818ff876da97c18
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/TierPriceStorageTest.php
@@ -0,0 +1,231 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * TierPriceStorage test.
+ */
+class TierPriceStorageTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogTierPriceStorageV1';
+    const SERVICE_VERSION = 'V1';
+    const SIMPLE_PRODUCT_SKU = 'simple';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * Set up.
+     */
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test get method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testGet()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/tier-prices-information',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['skus' => [self::SIMPLE_PRODUCT_SKU]]);
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $tierPrices = $productRepository->get(self::SIMPLE_PRODUCT_SKU)->getTierPrices();
+        $this->assertNotEmpty($response);
+        $this->assertEquals(count($response), count($tierPrices));
+
+        foreach ($response as $item) {
+            $this->assertTrue($this->isPriceCorrect($item, $tierPrices));
+        }
+    }
+
+    /**
+     * Test update method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testUpdate()
+    {
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        $prices = $productRepository->get(self::SIMPLE_PRODUCT_SKU)->getTierPrices();
+        $tierPrice = array_shift($prices);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/tier-prices',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Update',
+            ],
+        ];
+        $newPrice = [
+            'price' => 40,
+            'price_type' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT,
+            'website_id' => 0,
+            'sku' => self::SIMPLE_PRODUCT_SKU,
+            'customer_group' => 'ALL GROUPS',
+            'quantity' => 7778
+        ];
+        $updatedPrice = [
+            'price' => 778,
+            'price_type' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_FIXED,
+            'website_id' => 0,
+            'sku' => self::SIMPLE_PRODUCT_SKU,
+            'customer_group' => 'ALL GROUPS',
+            'quantity' => $tierPrice->getQty()
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['prices' => [$updatedPrice, $newPrice]]);
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        $tierPrices = $productRepository->get(self::SIMPLE_PRODUCT_SKU)->getTierPrices();
+        $this->assertTrue($response);
+        $this->assertTrue($this->isPriceCorrect($newPrice, $tierPrices));
+        $this->assertTrue($this->isPriceCorrect($updatedPrice, $tierPrices));
+    }
+
+    /**
+     * Test replace method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testReplace()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/tier-prices',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Replace',
+            ],
+        ];
+        $newPrices = [
+            [
+                'price' => 50,
+                'price_type' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT,
+                'website_id' => 0,
+                'sku' => self::SIMPLE_PRODUCT_SKU,
+                'customer_group' => 'general',
+                'quantity' => 7778
+            ],
+            [
+                'price' => 70,
+                'price_type' => \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_FIXED,
+                'website_id' => 0,
+                'sku' => self::SIMPLE_PRODUCT_SKU,
+                'customer_group' => 'general',
+                'quantity' => 33
+            ]
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['prices' => $newPrices]);
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+        $tierPrices = $productRepository->get(self::SIMPLE_PRODUCT_SKU)->getTierPrices();
+        $this->assertTrue($response);
+        $this->assertEquals(count($newPrices), count($tierPrices));
+
+        foreach ($newPrices as $newPrice) {
+            $this->assertTrue($this->isPriceCorrect($newPrice, $tierPrices));
+        }
+    }
+
+    /**
+     * Test delete method.
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testDelete()
+    {
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        $tierPrices = $productRepository->get(self::SIMPLE_PRODUCT_SKU)->getTierPrices();
+        $pricesToStore = array_pop($tierPrices);
+        $pricesToDelete = [];
+        foreach ($tierPrices as $tierPrice) {
+            $tierPriceValue = $tierPrice->getExtensionAttributes()->getPercentageValue()
+                ?: $tierPrice->getValue();
+            $priceType = $tierPrice->getExtensionAttributes()->getPercentageValue()
+                ? \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT
+                : \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_FIXED;
+            $customerGroup = $tierPrice->getCustomerGroupId() == \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID
+                ? 'NOT LOGGED IN'
+                : 'ALL GROUPS';
+            $pricesToDelete[] = [
+                'price' => $tierPriceValue,
+                'price_type' => $priceType,
+                'website_id' => 0,
+                'customer_group' => $customerGroup,
+                'sku' => self::SIMPLE_PRODUCT_SKU,
+                'quantity' => $tierPrice->getQty()
+
+            ];
+        }
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/tier-prices-delete',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Delete',
+            ],
+        ];
+        $response = $this->_webApiCall($serviceInfo, ['prices' => $pricesToDelete]);
+        $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+        $tierPrices = $productRepository->get(self::SIMPLE_PRODUCT_SKU)->getTierPrices();
+        $tierPrice = $tierPrices[0];
+        $this->assertTrue($response);
+        $this->assertEquals(1, count($tierPrices));
+        $this->assertEquals($pricesToStore, $tierPrice);
+    }
+
+    /**
+     * Check prise exists and is correct.
+     *
+     * @param array $price
+     * @param array $tierPrices
+     * @return bool
+     */
+    private function isPriceCorrect(array $price, array $tierPrices)
+    {
+        $isCorrect = false;
+
+        foreach ($tierPrices as $tierPrice) {
+            $priceIsCorrect = $price['price_type'] === \Magento\Catalog\Api\Data\TierPriceInterface::PRICE_TYPE_DISCOUNT
+                ? (float)$tierPrice->getExtensionAttributes()->getPercentageValue() === (float)$price['price']
+                : (float)$tierPrice->getValue() === (float)$price['price'];
+            if (
+                $priceIsCorrect
+                && (int)$tierPrice->getQty() === (int)$price['quantity']
+                && $tierPrice->getExtensionAttributes()->getWebsiteId() == $price['website_id']
+            ) {
+                $isCorrect = true;
+            }
+        }
+
+        return $isCorrect;
+    }
+}
diff --git a/dev/tests/functional/.htaccess.sample b/dev/tests/functional/.htaccess.sample
index f5c3be7db01e97c551085e09a493fceb11e2ad1f..f1e60cf9b67e91779153e4c9d9f780c8f7bc9438 100644
--- a/dev/tests/functional/.htaccess.sample
+++ b/dev/tests/functional/.htaccess.sample
@@ -1,6 +1,6 @@
 ##############################################
 ## Allow access to command.php and website.php
-    <FilesMatch "command.php|website.php">
+    <FilesMatch "command.php|website.php|export.php">
          order allow,deny
          allow from all
     </FilesMatch>
diff --git a/dev/tests/functional/composer.json b/dev/tests/functional/composer.json
index f170114a4ea8348714531af55ca805bb5e3442fe..3567ec530f437996c0ed28d792ebd929d2188e0c 100644
--- a/dev/tests/functional/composer.json
+++ b/dev/tests/functional/composer.json
@@ -1,6 +1,6 @@
 {
     "require": {
-        "magento/mtf": "1.0.0-rc50",
+        "magento/mtf": "1.0.0-rc51",
         "php": "~5.6.5|7.0.2|~7.0.6",
         "phpunit/phpunit": "~4.8.0|~5.5.0",
         "phpunit/phpunit-selenium": ">=1.2"
diff --git a/dev/tests/functional/etc/di.xml b/dev/tests/functional/etc/di.xml
index 69951a2cc1b86248040074c45ee967c93dc0bb82..b21e507a5af023b18326d2c1cf5bc038a5abdb3a 100644
--- a/dev/tests/functional/etc/di.xml
+++ b/dev/tests/functional/etc/di.xml
@@ -6,6 +6,11 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
+    <preference for="Magento\Mtf\Util\Command\File\ExportInterface" type="\Magento\Mtf\Util\Command\File\Export" />
+    <preference for="Magento\Mtf\Util\Command\File\Export\ReaderInterface" type="\Magento\Mtf\Util\Command\File\Export\Reader" />
+
+    <type name="\Magento\Mtf\Util\Command\File\Export" shared="false" />
+
     <virtualType name="Magento\Mtf\Config\SchemaLocator\Config" type="Magento\Mtf\Config\SchemaLocator">
         <arguments>
             <argument name="schemaPath" xsi:type="string">etc/config.xsd</argument>
@@ -13,4 +18,22 @@
     </virtualType>
 
     <type name="Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator" shared="true" />
+
+    <type name="Magento\Mtf\Util\Command\File\Export\Reader">
+        <arguments>
+            <argument name="template" xsi:type="string">\w*?\.csv</argument>
+        </arguments>
+    </type>
+
+    <virtualType name="Magento\Mtf\Util\Command\File\Export\ProductReader" type="Magento\Mtf\Util\Command\File\Export\Reader">
+        <arguments>
+            <argument name="template" xsi:type="string">catalog_product.*?\.csv</argument>
+        </arguments>
+    </virtualType>
+
+    <virtualType name="Magento\Mtf\Util\Command\File\Export\CustomerReader" type="Magento\Mtf\Util\Command\File\Export\Reader">
+        <arguments>
+            <argument name="template" xsi:type="string">customer.*?\.csv</argument>
+        </arguments>
+    </virtualType>
 </config>
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php
index 96be468a6eb377f619339c02a2be00835f8425eb..db39bdcb5b7a5e1e323ee434b2e8b1d744b0570a 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php
@@ -7,7 +7,6 @@
 namespace Magento\Mtf\Util\Command;
 
 use Magento\Mtf\Util\Protocol\CurlInterface;
-use Magento\Mtf\ObjectManager;
 use Magento\Mtf\Util\Protocol\CurlTransport;
 
 /**
@@ -28,7 +27,6 @@ class Cli
     private $transport;
 
     /**
-     * @constructor
      * @param CurlTransport $transport
      */
     public function __construct(CurlTransport $transport)
@@ -61,6 +59,6 @@ class Cli
     private function prepareUrl($command, array $options)
     {
         $command .= ' ' . implode(' ', $options);
-        return $_ENV['app_frontend_url'] . Cli::URL . '?command=' . urlencode($command);
+        return $_ENV['app_frontend_url'] . self::URL . '?command=' . urlencode($command);
     }
 }
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d5defadbc44a5d30e69423cafe96653450f51bf
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Mtf\Util\Command\File;
+
+use Magento\Mtf\ObjectManagerInterface;
+use Magento\Mtf\Util\Command\File\Export\Data;
+use Magento\Mtf\Util\Command\File\Export\ReaderInterface;
+
+/**
+ * Get Exporting file from the Magento.
+ */
+class Export implements ExportInterface
+{
+    /**
+     * Path to the Reader.
+     *
+     * @var string
+     */
+    private $readerPath = 'Magento\Mtf\Util\Command\File\Export\%sReader';
+
+    /**
+     * Object manager instance.
+     *
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * File reader for Magento export files.
+     *
+     * @var ReaderInterface
+     */
+    private $reader;
+
+    /**
+     * @param ObjectManagerInterface $objectManager
+     * @param string $type [optional]
+     */
+    public function __construct(ObjectManagerInterface $objectManager, $type = 'product')
+    {
+        $this->objectManager = $objectManager;
+        $this->reader = $this->getReader($type);
+    }
+
+    /**
+     * Get reader for export files.
+     *
+     * @param string $type
+     * @return ReaderInterface
+     * @throws \ReflectionException
+     */
+    private function getReader($type)
+    {
+        $readerPath = sprintf($this->readerPath, ucfirst($type));
+        try {
+            return $this->objectManager->create($readerPath);
+        } catch (\ReflectionException $e) {
+            throw new \ReflectionException("Virtual type '$readerPath' does not exist. Please, check it in di.xml.");
+        }
+    }
+
+    /**
+     * Get the export file by name.
+     *
+     * @param string $name
+     * @return Data|null
+     */
+    public function getByName($name)
+    {
+        $this->reader->getData();
+        foreach ($this->reader->getData() as $file) {
+            if ($file->getName() === $name) {
+                return $file;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get latest created the export file.
+     *
+     * @return Data|null
+     */
+    public function getLatest()
+    {
+        $max = 0;
+        $latest = null;
+        foreach ($this->reader->getData() as $file) {
+            if ($file->getDate() > $max) {
+                $max = $file->getDate();
+                $latest = $file;
+            }
+        }
+
+        return $latest;
+    }
+
+    /**
+     * Get all export files by date range using unix time stamp.
+     *
+     * @param string $start
+     * @param string $end
+     * @return Data[]
+     */
+    public function getByDateRange($start, $end)
+    {
+        $files = [];
+        foreach ($this->reader->getData() as $file) {
+            if ($file->getDate() > $start && $file->getDate() < $end) {
+                $files[] = $file;
+            }
+        }
+
+        return $files;
+    }
+
+    /**
+     * Get all export files.
+     *
+     * @return Data[]
+     */
+    public function getAll()
+    {
+        return $this->reader->getData();
+    }
+}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Data.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Data.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6756aac0052a28bc04cc7c7d420c4ace8d5c472
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Data.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Mtf\Util\Command\File\Export;
+
+/**
+ * Data mapping for Export file.
+ */
+class Data
+{
+    /**
+     * File data.
+     *
+     * @var array
+     */
+    private $data;
+
+    /**
+     * @param array $data
+     */
+    public function __construct(array $data)
+    {
+        $this->data = $data;
+    }
+
+    /**
+     * Get file name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->data['name'];
+    }
+
+    /**
+     * Get file content.
+     *
+     * @return string
+     */
+    public function getContent()
+    {
+        return $this->data['content'];
+    }
+
+    /**
+     * Get file creation date.
+     *
+     * @return string
+     */
+    public function getDate()
+    {
+        return $this->data['date'];
+    }
+}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a8287daae2ab54cc8adbd8fff2249864cbed9a5
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Mtf\Util\Command\File\Export;
+
+use Magento\Mtf\ObjectManagerInterface;
+use Magento\Mtf\Util\Protocol\CurlTransport;
+use Magento\Mtf\Util\Protocol\CurlInterface;
+
+/**
+ * File reader for Magento export files.
+ */
+class Reader implements ReaderInterface
+{
+    /**
+     * Pattern for file name in Magento.
+     *
+     * @var string
+     */
+    private $template;
+
+    /**
+     * Object manager instance.
+     *
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * Curl transport protocol.
+     *
+     * @var CurlTransport
+     */
+    private $transport;
+
+    /**
+     * @param ObjectManagerInterface $objectManager
+     * @param CurlTransport $transport
+     * @param string $template
+     */
+    public function __construct(ObjectManagerInterface $objectManager, CurlTransport $transport, $template)
+    {
+        $this->objectManager = $objectManager;
+        $this->template = $template;
+        $this->transport = $transport;
+    }
+
+    /**
+     * Exporting files as Data object from Magento.
+     *
+     * @return Data[]
+     */
+    public function getData()
+    {
+        $data = [];
+        foreach ($this->getFiles() as $file) {
+            $data[] = $this->objectManager->create(Data::class, ['data' => $file]);
+        }
+
+        return $data;
+    }
+
+    /**
+     * Get files by template from the Magento.
+     *
+     * @return array
+     */
+    private function getFiles()
+    {
+        $this->transport->write($this->prepareUrl(), [], CurlInterface::GET);
+        $serializedFiles = $this->transport->read();
+        $this->transport->close();
+
+        return unserialize($serializedFiles);
+    }
+
+    /**
+     * Prepare url.
+     *
+     * @return string
+     */
+    private function prepareUrl()
+    {
+        return $_ENV['app_frontend_url'] . self::URL . '?template=' . urlencode($this->template);
+    }
+}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..5d305513ec68080ec4e7ec6bad8e776f8653bf15
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Mtf\Util\Command\File\Export;
+
+/**
+ * File reader interface for Magento export files.
+ */
+interface ReaderInterface
+{
+    /**
+     * Url to export.php.
+     */
+    const URL = 'dev/tests/functional/utils/export.php';
+
+    /**
+     * Exporting files as Data object from Magento.
+     *
+     * @return Data[]
+     */
+    public function getData();
+}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/ExportInterface.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/ExportInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0bc4f9e59a2433b1078805e8431f9f45d8d34e20
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/ExportInterface.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Mtf\Util\Command\File;
+
+use Magento\Mtf\Util\Command\File\Export\Data;
+
+/**
+ * Interface for getting Exporting file from the Magento.
+ */
+interface ExportInterface
+{
+    /**
+     * Get the export file by name.
+     *
+     * @param string $name
+     * @return Data|null
+     */
+    public function getByName($name);
+
+    /**
+     * Get latest created the export file.
+     *
+     * @return Data|null
+     */
+    public function getLatest();
+
+    /**
+     * Get all export files by date range using unix time stamp.
+     *
+     * @param string $start
+     * @param string $end
+     * @return Data[]
+     */
+    public function getByDateRange($start, $end);
+
+    /**
+     * Get all export files.
+     *
+     * @return Data[]
+     */
+    public function getAll();
+}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php
index 611f46894c8bca850d10a519fe7a2850f31b5533..d0c4be34888d2ae863173fbbedc203c08d95fb90 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php
@@ -58,6 +58,6 @@ class Website
      */
     private function prepareUrl($websiteCode)
     {
-        return $_ENV['app_frontend_url'] . Website::URL . '?website_code=' . urlencode($websiteCode);
+        return $_ENV['app_frontend_url'] . self::URL . '?website_code=' . urlencode($websiteCode);
     }
 }
diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php
index 062de338d8921e15b786de0fd4b656e824354d80..ca5fbe8799d5144a2a9e68e7b8fbca4530c26790 100644
--- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php
+++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php
@@ -33,7 +33,7 @@ class InvoicePayPalBraintreeTest extends Scenario
     /* end tags */
 
     /**
-     * Runs one page checkout test.
+     * Create invoice for order placed within Braintree PayPal.
      *
      * @return void
      */
diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml
index 23985d208e5a675b997d330e792eb58f1045d7d9..1d98e2d792a3e75fd815fe78149e43100aad12ae 100644
--- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml
@@ -31,7 +31,7 @@
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" />
             <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" />
         </variation>
-        <variation name="InvoicePayPalBraintreeTestVariation2" summary="Partial capture for order placed within Braintree PayPal" ticketId="MAGETWO-48615">
+        <variation name="InvoicePayPalBraintreeTestVariation2" summary="Partial capture for order placed within Braintree PayPal" ticketId="MAGETWO-48615, MAGETWO-48684">
             <data name="description" xsi:type="string">Partial capture for order placed within Braintree PayPal</data>
             <data name="products/0" xsi:type="string">catalogProductSimple::product_100_dollar</data>
             <data name="taxRule" xsi:type="string">us_illinois_tax_rule</data>
@@ -43,7 +43,6 @@
             </data>
             <data name="capturedPrices" xsi:type="array">
                 <item name="0" xsi:type="string">118.25</item>
-                <item name="1" xsi:type="string">108.25</item>
             </data>
             <data name="payment/method" xsi:type="string">braintree_paypal</data>
             <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_skip_order_review</data>
@@ -51,10 +50,18 @@
             <data name="data/items_data/0/qty" xsi:type="string">1</data>
             <data name="data/form_data/do_shipment" xsi:type="string">No</data>
             <data name="data/form_data/comment_text" xsi:type="string">comments</data>
-            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data>
+            <data name="status" xsi:type="string">Processing</data>
+            <data name="transactions/Capture" xsi:type="array">
+                <item name="transactionType" xsi:type="string">Capture</item>
+                <item name="statusIsClosed" xsi:type="string">No</item>
+            </data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
             <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" />
-            <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceInInvoicesGrid" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertTransactionStatus" />
         </variation>
     </testCase>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCancelSuccessMessageInShoppingCart.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCancelSuccessMessageInShoppingCart.php
new file mode 100644
index 0000000000000000000000000000000000000000..d549e14c7afd94bc35d2540662012fd9f970b872
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCancelSuccessMessageInShoppingCart.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Checkout\Test\Constraint;
+
+use Magento\Checkout\Test\Page\CheckoutCart;
+use Magento\Mtf\Constraint\AbstractConstraint;
+
+/**
+ * Assert that success message about canceled order is present and correct.
+ */
+class AssertCancelSuccessMessageInShoppingCart extends AbstractConstraint
+{
+    /**
+     * Cancel success message text.
+     */
+    const SUCCESS_MESSAGE = 'Payment was canceled.';
+
+    /**
+     * Assert that success message about canceled order is present and correct.
+     *
+     * @param CheckoutCart $checkoutCart
+     * @return void
+     */
+    public function processAssert(CheckoutCart $checkoutCart)
+    {
+        $actualMessage = $checkoutCart->getMessagesBlock()->getSuccessMessage();
+        \PHPUnit_Framework_Assert::assertEquals(
+            self::SUCCESS_MESSAGE,
+            $actualMessage,
+            'Success message is not present or has wrong text.'
+        );
+    }
+
+    /**
+     * Returns a string representation of the object.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'Cancel success message is present or has a correct text.';
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/di.xml
index 9f13d8dddb9a768442f7051a6e11be16f7df9233..0a0622936a23d7688cf0e94b61ebc2ef56d052f4 100644
--- a/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/di.xml
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/di.xml
@@ -102,11 +102,6 @@
             <argument name="severity" xsi:type="string">S2</argument>
         </arguments>
     </type>
-    <type name="Magento\Checkout\Test\Constraint\AssertProductPresentInShoppingCart">
-        <arguments>
-            <argument name="severity" xsi:type="string">S2</argument>
-        </arguments>
-    </type>
     <type name="Magento\Checkout\Test\Constraint\AssertProductQtyInShoppingCart">
         <arguments>
             <argument name="severity" xsi:type="string">S2</argument>
@@ -157,4 +152,14 @@
             <argument name="severity" xsi:type="string">S2</argument>
         </arguments>
     </type>
+    <type name="Magento\Checkout\Test\Constraint\AssertCancelSuccessMessageInShoppingCart">
+        <arguments>
+            <argument name="severity" xsi:type="string">S1</argument>
+        </arguments>
+    </type>
+    <type name="Magento\Checkout\Test\Constraint\AssertProductPresentInShoppingCart">
+        <arguments>
+            <argument name="severity" xsi:type="string">S0</argument>
+        </arguments>
+    </type>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Address.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Address.xml
index 2edc328b5c5df40df321e66a34fa1ee7c6d4e835..6e7e76524557dd6ec875af966ef23e85bef6a73c 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Address.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Address.xml
@@ -297,5 +297,17 @@
             <field name="region_id" xsi:type="string">California</field>
             <field name="postcode" xsi:type="string">90230</field>
         </dataset>
+
+        <dataset name="AVS_street_does_not_match_address">
+            <field name="firstname" xsi:type="string">John</field>
+            <field name="lastname" xsi:type="string">Doe</field>
+            <field name="company" xsi:type="string">Magento %isolation%</field>
+            <field name="city" xsi:type="string">Culver City</field>
+            <field name="street" xsi:type="string">49354 Main</field>
+            <field name="telephone" xsi:type="string">555-55-555-55</field>
+            <field name="country_id" xsi:type="string">United States</field>
+            <field name="region_id" xsi:type="string">California</field>
+            <field name="postcode" xsi:type="string">90230</field>
+        </dataset>
     </repository>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/Repository/ConfigData.xml
index 1669352dc026ef02c295fce1153f63e3beabf225..f1d041f0a5b344fb76e91acd57124eb03d9f6ae8 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Repository/ConfigData.xml
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Repository/ConfigData.xml
@@ -25,54 +25,54 @@
         </dataset>
 
         <dataset name="paypal_direct">
-            <field name="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/business_account" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/paypal_payflow_api_settings/business_account" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string">Yes</item>
-                <item name="value" xsi:type="string">PAYPAL_BUSINESS_ACCOUNT</item>
+                <item name="label" xsi:type="string">Email Associated with PayPal Merchant Account (Optional)</item>
+                <item name="value" xsi:type="string">%payflow_pro_business_account%</item>
             </field>
-            <field name="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_username" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/paypal_payflow_api_settings/partner" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string"/>
-                <item name="value" xsi:type="string">PAYPAL_API_USERNAME</item>
+                <item name="label" xsi:type="string">Partner</item>
+                <item name="value" xsi:type="string">%payflow_pro_partner%</item>
             </field>
-            <field name="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_password" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/paypal_payflow_api_settings/user" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string"/>
-                <item name="value" xsi:type="string">PAYPAL_API_PASSWORD</item>
+                <item name="label" xsi:type="string">User</item>
+                <item name="value" xsi:type="string">%payflow_pro_user%</item>
             </field>
-            <field name="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_signature" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/paypal_payflow_api_settings/vendor" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string"/>
-                <item name="value" xsi:type="string">PAYPAL_API_SIGNATURE</item>
+                <item name="label" xsi:type="string">Vendor</item>
+                <item name="value" xsi:type="string">%payflow_pro_vendor%</item>
             </field>
-            <field name="payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/sandbox_flag" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/paypal_payflow_api_settings/pwd" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string">Yes</item>
-                <item name="value" xsi:type="number">1</item>
+                <item name="label" xsi:type="string">Password</item>
+                <item name="value" xsi:type="string">%payflow_pro_pwd%</item>
             </field>
-            <field name="payment/paypal_direct/debug" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/paypal_payflow_api_settings/sandbox_flag" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string">Yes</item>
+                <item name="label" xsi:type="string">Test Mode</item>
                 <item name="value" xsi:type="number">1</item>
             </field>
-            <field name="payment/paypal_direct/active" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/enable_paypal_payflow" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string">Yes</item>
+                <item name="label" xsi:type="string">Enable this Solution</item>
                 <item name="value" xsi:type="number">1</item>
             </field>
         </dataset>
         <dataset name="paypal_direct_rollback">
-            <field name="payment/paypal_direct/active" xsi:type="array">
+            <field name="payment/paypal_group_all_in_one/wpp_usuk/paypal_payflow_required/enable_paypal_payflow" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
                 <item name="scope_id" xsi:type="number">1</item>
-                <item name="label" xsi:type="string">Yes</item>
+                <item name="label" xsi:type="string">Enable this Solution</item>
                 <item name="value" xsi:type="number">0</item>
             </field>
         </dataset>
@@ -219,6 +219,23 @@
             </field>
         </dataset>
 
+        <dataset name="payflowpro_avs_street_does_not_match">
+            <field name="payment/payflowpro/avs_street" xsi:type="array">
+                <item name="scope" xsi:type="string">payment</item>
+                <item name="scope_id" xsi:type="number">1</item>
+                <item name="label" xsi:type="string">Yes</item>
+                <item name="value" xsi:type="number">1</item>
+            </field>
+        </dataset>
+        <dataset name="payflowpro_avs_street_does_not_match_rollback">
+            <field name="payment/payflowpro/avs_street" xsi:type="array">
+                <item name="scope" xsi:type="string">payment</item>
+                <item name="scope_id" xsi:type="number">1</item>
+                <item name="label" xsi:type="string">No</item>
+                <item name="value" xsi:type="number">0</item>
+            </field>
+        </dataset>
+
         <dataset name="hosted_pro">
             <field name="payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/business_account" xsi:type="array">
                 <item name="scope" xsi:type="string">payment</item>
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CloseOrderTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CloseOrderTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..30385e1cd2006c5dd86f98a8f4da1a9d032c6efd
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CloseOrderTest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Sales\Test\TestCase\CloseOrderTest" summary="Close order">
+        <variation name="CloseOrderTestWithPayPalPaymentsPro" summary="Close order with PayPal Payments Pro" ticketId="MAGETWO-13015">
+            <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data>
+            <data name="customer/dataset" xsi:type="string">default</data>
+            <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data>
+            <data name="checkoutMethod" xsi:type="string">guest</data>
+            <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data>
+            <data name="shipping/shipping_method" xsi:type="string">Fixed</data>
+            <data name="payment/method" xsi:type="string">payflowpro</data>
+            <data name="prices" xsi:type="array">
+                <item name="grandTotal" xsi:type="string">15.00</item>
+            </data>
+            <data name="capturedPrices" xsi:type="array">
+                <item name="0" xsi:type="string">15.00</item>
+            </data>
+            <data name="creditCard/dataset" xsi:type="string">visa_default</data>
+            <data name="status" xsi:type="string">Complete</data>
+            <data name="configData" xsi:type="string">paypal_direct</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" />
+        </variation>
+        <variation name="CloseOrderTestWithPayPalPayflowPro" summary="Close order with PayPal Payflow Pro" ticketId="MAGETWO-13020">
+            <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data>
+            <data name="customer/dataset" xsi:type="string">default</data>
+            <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data>
+            <data name="checkoutMethod" xsi:type="string">guest</data>
+            <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data>
+            <data name="shipping/shipping_method" xsi:type="string">Fixed</data>
+            <data name="payment/method" xsi:type="string">payflowpro</data>
+            <data name="order/data/price/dataset" xsi:type="string">full_invoice_with_product_10_dollar</data>
+            <data name="prices" xsi:type="array">
+                <item name="grandTotal" xsi:type="string">15.00</item>
+            </data>
+            <data name="capturedPrices" xsi:type="array">
+                <item name="0" xsi:type="string">15.00</item>
+            </data>
+            <data name="creditCard/dataset" xsi:type="string">visa_default</data>
+            <data name="status" xsi:type="string">Complete</data>
+            <data name="configData" xsi:type="string">payflowpro, payflowpro_use_vault</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceInInvoicesTab" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" />
+        </variation>
+    </testCase>
+</config>
\ No newline at end of file
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateOnlineCreditMemoTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateOnlineCreditMemoTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f859f579ecea45c5c992017a09df061b11b70bbc
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateOnlineCreditMemoTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Sales\Test\TestCase\CreateOnlineCreditMemoTest" summary="Create online credit memo for order placed with online payment method">
+        <variation name="CreateCreditMemoPaymentsProTestVariation1" summary="Create Refund for Order Paid with PayPal Payments Pro" ticketId="MAGETWO-13059">
+            <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data>
+            <data name="customer/dataset" xsi:type="string">default</data>
+            <data name="checkoutMethod" xsi:type="string">guest</data>
+            <data name="refundedPrices" xsi:type="array">
+                <item name="0" xsi:type="string">15.00</item>
+            </data>
+            <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data>
+            <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data>
+            <data name="shipping/shipping_method" xsi:type="string">Fixed</data>
+            <data name="payment/method" xsi:type="string">payflowpro</data>
+            <data name="configData" xsi:type="string">paypal_direct</data>
+            <data name="creditCard/dataset" xsi:type="string">visa_default</data>
+            <data name="data/items_data/0/qty" xsi:type="string">-</data>
+            <data name="data/form_data/do_shipment" xsi:type="string">Yes</data>
+            <data name="status" xsi:type="string">Closed</data>
+            <data name="transactions/Refund" xsi:type="array">
+                <item name="transactionType" xsi:type="string">Refund</item>
+                <item name="statusIsClosed" xsi:type="string">Yes</item>
+            </data>
+            <data name="transactions/Capture" xsi:type="array">
+                <item name="transactionType" xsi:type="string">Capture</item>
+                <item name="statusIsClosed" xsi:type="string">Yes</item>
+            </data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCommentsHistory" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCreditMemoTab" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertTransactionStatus" />
+        </variation>
+    </testCase>
+</config>
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml
index 1d92fa149efbc336eb0910a451a1740f2eda6c14..6005fe4008d7bb52308878e81a7d0211c5441a87 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml
@@ -27,7 +27,7 @@
             </data>
             <data name="payment/method" xsi:type="string">paypal_express</data>
             <data name="configData" xsi:type="string">paypal_express, freeshipping</data>
-            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test_deprecated, severity:S0</data>
             <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" />
             <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" />
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml
index 38c876719bc3d82c772ff6f86cd507229ec5cf2b..233958afa1aa5605f922b105b2da85dee08960c8 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml
@@ -28,7 +28,7 @@
                 <item name="grandTotal" xsi:type="string">145.98</item>
             </data>
             <data name="configData" xsi:type="string">payflowpro</data>
-            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test_deprecated, severity:S0</data>
             <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" />
             <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" />
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml
index e52ed631ae46f983dc8860e7bf628f95a5a6a7fb..7de72c8ac9d93143dd3d59595a452981f2e15a89 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml
@@ -26,7 +26,7 @@
             </data>
             <data name="payment/method" xsi:type="string">paypal_express</data>
             <data name="configData" xsi:type="string">paypal_express</data>
-            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test_deprecated, severity:S0</data>
             <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" />
             <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" />
@@ -51,7 +51,7 @@
             </data>
             <data name="payment/method" xsi:type="string">paypal_express</data>
             <data name="configData" xsi:type="string">payflowlink</data>
-            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test_deprecated, severity:S0</data>
             <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" />
             <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" />
@@ -76,7 +76,7 @@
             </data>
             <data name="payment/method" xsi:type="string">paypal_express</data>
             <data name="configData" xsi:type="string">paypal_express</data>
-            <data name="tag" xsi:type="string">test_type:3rd_party_test</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test_deprecated</data>
             <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" />
             <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" />
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutDeclinedTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutDeclinedTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e015e3ba566648ce05326e3fae03bf2a76eefc64
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutDeclinedTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Checkout\Test\TestCase\OnePageCheckoutDeclinedTest" summary="Error message during OnePageCheckout">
+        <variation name="OnePageCheckoutPayflowProWithAVSStreetDoesNotMatch" summary="Place Order via Payflow Pro with AVS Street verification fail" ticketId="MAGETWO-37480">
+            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data>
+            <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data>
+            <data name="customer/dataset" xsi:type="string">default</data>
+            <data name="shippingAddress/dataset" xsi:type="string">AVS_street_does_not_match_address</data>
+            <data name="checkoutMethod" xsi:type="string">guest</data>
+            <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data>
+            <data name="shipping/shipping_method" xsi:type="string">Fixed</data>
+            <data name="payment/method" xsi:type="string">payflowpro</data>
+            <data name="creditCard/dataset" xsi:type="string">visa_default</data>
+            <data name="configData" xsi:type="string">payflowpro, payflowpro_avs_street_does_not_match</data>
+            <data name="expectedErrorMessage" xsi:type="string">An error occurred on the server. Please try to place the order again.</data>
+            <constraint name="Magento\Checkout\Test\Constraint\AssertCheckoutErrorMessage" />
+        </variation>
+    </testCase>
+</config>
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutTest.xml
index 655b2f5f9d3fb83eaca544d74db4e9adf0732bbe..44577748f7b7a1abe37e6a9e562b3059820b0267 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/OnePageCheckoutTest.xml
@@ -55,7 +55,7 @@
             <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderPaymentInformation" />
         </variation>
-        <variation name="OnePageCheckoutPayflowProWithAVSStreetMatches" summary="Place Order via Payflow Pro with success AVS Street verification" ticketId="MAGETWO-37479">
+        <variation name="OnePageCheckoutPayflowProWithAVSStreetMatch" summary="Place Order via Payflow Pro with success AVS Street verification" ticketId="MAGETWO-37479">
             <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data>
             <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data>
             <data name="customer/dataset" xsi:type="string">default</data>
@@ -80,5 +80,24 @@
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" />
             <constraint name="Magento\Sales\Test\Constraint\AssertOrderPaymentInformation" />
         </variation>
+        <variation name="OnePageCheckoutPayPalPaymentsPro" summary="Guest Checkout using PayPal Payments Pro and Flat Rate" ticketId="MAGETWO-62039">
+            <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data>
+            <data name="customer/dataset" xsi:type="string">default</data>
+            <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data>
+            <data name="checkoutMethod" xsi:type="string">guest</data>
+            <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data>
+            <data name="shipping/shipping_method" xsi:type="string">Fixed</data>
+            <data name="payment/method" xsi:type="string">payflowpro</data>
+            <data name="prices" xsi:type="array">
+                <item name="grandTotal" xsi:type="string">15.00</item>
+            </data>
+            <data name="creditCard/dataset" xsi:type="string">visa_default</data>
+            <data name="configData" xsi:type="string">paypal_direct</data>
+            <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data>
+            <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" />
+            <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" />
+            <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" />
+        </variation>
     </testCase>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/AbstractItems.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/AbstractItems.php
index d7a89b68c553808cfdbb7de9854ce37e82464618..38ad9139d4fdb2bf28c40d8420db890dcda77d3a 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/AbstractItems.php
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/AbstractItems.php
@@ -19,63 +19,63 @@ class AbstractItems extends Block
      *
      * @var string
      */
-    private $rowItem = 'tbody';
+    protected $rowItem = 'tbody';
 
     /**
      * Locator for product sku column.
      *
      * @var string
      */
-    private $sku = '.col-product .product-sku-block';
+    protected $sku = '.col-product .product-sku-block';
 
     /**
      * Locator for product title column.
      *
      * @var string
      */
-    private $title = '.col-product .product-title';
+    protected $title = '.col-product .product-title';
 
     /**
      * Locator for "Price" column.
      *
      * @var string
      */
-    private $price = '.col-price .price';
+    protected $price = '.col-price .price';
 
     /**
      * Locator for "Qty" column.
      *
      * @var string
      */
-    private $qty = '.col-qty';
+    protected $qty = '.col-qty';
 
     /**
      * Locator for "Subtotal" column.
      *
      * @var string
      */
-    private $subtotal = '.col-subtotal .price';
+    protected $subtotal = '.col-subtotal .price';
 
     /**
      * Locator for "Tax Amount" column.
      *
      * @var string
      */
-    private $taxAmount = '.col-tax .price';
+    protected $taxAmount = '.col-tax .price';
 
     /**
      * Locator for "Discount Amount" column.
      *
      * @var string
      */
-    private $discountAmount = '.col-discount .price';
+    protected $discountAmount = '.col-discount .price';
 
     /**
      * Locator for "Row total" column.
      *
      * @var string
      */
-    private $rowTotal = '.col-total .price';
+    protected $rowTotal = '.col-total .price';
 
     /**
      * Get items data.
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCanceled.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCanceled.php
new file mode 100644
index 0000000000000000000000000000000000000000..2547cb49b70a447382033b2d76f7606d5545f459
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCanceled.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Sales\Test\Constraint;
+
+use Magento\Sales\Test\Page\Adminhtml\OrderIndex;
+use Magento\Sales\Test\Page\Adminhtml\SalesOrderView;
+use Magento\Mtf\Constraint\AbstractConstraint;
+
+/**
+ * Assert that status is Canceled.
+ */
+class AssertOrderStatusIsCanceled extends AbstractConstraint
+{
+    /**
+     * Assert that status is Canceled.
+     *
+     * @param OrderIndex $salesOrder
+     * @param SalesOrderView $salesOrderView
+     * @return void
+     */
+    public function processAssert(
+        OrderIndex $salesOrder,
+        SalesOrderView $salesOrderView
+    ) {
+        $salesOrder->open();
+        $grid = $salesOrder->getSalesOrderGrid();
+        $grid->resetFilter();
+        $grid->sortByColumn('ID');
+        $grid->sortGridByField('ID');
+        $grid->openFirstRow();
+
+        /** @var \Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\Info $infoTab */
+        $infoTab = $salesOrderView->getOrderForm()->openTab('info')->getTab('info');
+        \PHPUnit_Framework_Assert::assertEquals(
+            $infoTab->getOrderStatus(),
+            'Canceled'
+        );
+    }
+
+    /**
+     * Returns a string representation of the object.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'Order status is correct.';
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable/Price.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable/Price.xml
index e9db2ef1b2ce4938732959ddea9017cf29cc54fe..64e2f8808172492ca82dc8cb545f33a0554a3fc5 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable/Price.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable/Price.xml
@@ -19,6 +19,13 @@
             </field>
         </dataset>
 
+        <dataset name="full_invoice_with_product_10_dollar">
+            <field name="0" xsi:type="array">
+                <item name="grand_order_total" xsi:type="string">15</item>
+                <item name="grand_invoice_total" xsi:type="string">15</item>
+            </field>
+        </dataset>
+
         <dataset name="partial_invoice">
             <field name="0" xsi:type="array">
                 <item name="grand_order_total" xsi:type="string">210</item>
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CloseOrderTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CloseOrderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1594cad95eff98633a9b6b1e47a4705912059370
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CloseOrderTest.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Sales\Test\TestCase;
+
+use Magento\Mtf\TestCase\Scenario;
+
+/**
+ * Preconditions:
+ * 1. Order is placed.
+ *
+ * Steps:
+ * 1. Log in to Admin.
+ * 2. Go to Sales > Orders page.
+ * 3. Open order.
+ * 4. Click 'Ship' button and submit shipment.
+ * 5. Click 'Invoice' button.
+ * 6. Select Amount=Capture Online.
+ * 7. Click 'Submit Invoice' button.
+ * 8. Perform assertions.
+ *
+ * @group Order_Management
+ * @ZephyrId MAGETWO-13015, MAGETWO-13020
+ */
+class CloseOrderTest extends Scenario
+{
+    /* tags */
+    const MVP = 'yes';
+    const TEST_TYPE = '3rd_party_test';
+    const SEVERITY = 'S0';
+    /* end tags */
+
+    /**
+     * Close order.
+     *
+     * @return void
+     */
+    public function test()
+    {
+        $this->executeScenario();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOnlineCreditMemoTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOnlineCreditMemoTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e11e1391a10e3442e1a92230615d7f1c0a2fbc4d
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOnlineCreditMemoTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\TestCase;
+
+use Magento\Mtf\TestCase\Scenario;
+
+/**
+ * Preconditions:
+ * 1. Complete a sales order with online payment method.
+ *
+ * Steps:
+ * 1. Log in to Admin.
+ * 2. Open order from preconditions.
+ * 3. Open created invoice.
+ * 3. Create credit memo.
+ * 4. Perform assertions.
+ *
+ * @group Order_Management
+ * @ZephyrId MAGETWO-13059
+ */
+class CreateOnlineCreditMemoTest extends Scenario
+{
+    /* tags */
+    const MVP = 'yes';
+    const TEST_TYPE = '3rd_party_test';
+    const SEVERITY = 'S0';
+    /* end tags */
+
+    /**
+     * Runs test for online credit memo creation for order placed with online payment method.
+     *
+     * @return void
+     */
+    public function test()
+    {
+        $this->executeScenario();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml
index 157dfc0f77302318bdce435dd3af7f6de5e261fb..e9670a1d727f72b2d7ea853fd38e4b0a2bb1c218 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml
@@ -106,4 +106,9 @@
             <argument name="severity" xsi:type="string">S1</argument>
         </arguments>
     </type>
+    <type name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCanceled">
+        <arguments>
+            <argument name="severity" xsi:type="string">S0</argument>
+        </arguments>
+    </type>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml
index 030b0f4f32df0eacd76c99cbe55537df1c734cf5..fa4329137b0272a5ae59cefdf217baa894c2cd28 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml
@@ -86,4 +86,33 @@
         <step name="submitOrder" module="Magento_Sales" next="createInvoice" />
         <step name="createInvoice" module="Magento_Sales" />
     </scenario>
+    <scenario name="CreateOnlineCreditMemoTest" firstStep="setupConfiguration">
+        <step name="setupConfiguration" module="Magento_Config" next="createProducts" />
+        <step name="createProducts" module="Magento_Catalog" next="addProductsToTheCart" />
+        <step name="addProductsToTheCart" module="Magento_Checkout" next="createCustomer" />
+        <step name="createCustomer" module="Magento_Customer" next="proceedToCheckout" />
+        <step name="proceedToCheckout" module="Magento_Checkout" next="selectCheckoutMethod" />
+        <step name="selectCheckoutMethod" module="Magento_Checkout" next="fillShippingAddress" />
+        <step name="fillShippingAddress" module="Magento_Checkout" next="fillShippingMethod" />
+        <step name="fillShippingMethod" module="Magento_Checkout" next="selectPaymentMethod" />
+        <step name="selectPaymentMethod" module="Magento_Checkout" next="placeOrder" />
+        <step name="placeOrder" module="Magento_Checkout" next="createInvoice" />
+        <step name="createInvoice" module="Magento_Sales" next="createOnlineCreditMemo" />
+        <step name="createOnlineCreditMemo" module="Magento_Sales" />
+    </scenario>
+    <scenario name="CloseOrderTest" firstStep="setupConfiguration">
+        <step name="setupConfiguration" module="Magento_Config" next="createProducts" />
+        <step name="createProducts" module="Magento_Catalog" next="createTaxRule" />
+        <step name="createTaxRule" module="Magento_Tax" next="addProductsToTheCart" />
+        <step name="addProductsToTheCart" module="Magento_Checkout" next="proceedToCheckout" />
+        <step name="proceedToCheckout" module="Magento_Checkout" next="createCustomer" />
+        <step name="createCustomer" module="Magento_Customer" next="selectCheckoutMethod" />
+        <step name="selectCheckoutMethod" module="Magento_Checkout" next="fillShippingAddress"/>
+        <step name="fillShippingAddress" module="Magento_Checkout" next="fillShippingMethod" />
+        <step name="fillShippingMethod" module="Magento_Checkout" next="selectPaymentMethod" />
+        <step name="selectPaymentMethod" module="Magento_Checkout" next="placeOrder" />
+        <step name="placeOrder" module="Magento_Checkout" next="createInvoice" />
+        <step name="createInvoice" module="Magento_Sales" next="createShipment" />
+        <step name="createShipment" module="Magento_Sales" />
+    </scenario>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php
index d1da95bb797dadf5d2eab2e75cce87a0f23a1b2b..be7a650d77e1c3ebe56409b5e38f9db8ef8e97ab 100644
--- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php
+++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php
@@ -400,6 +400,8 @@ class DataGrid extends Grid
     }
 
     /**
+     * Sort grid by column.
+     *
      * @param string $columnLabel
      */
     public function sortByColumn($columnLabel)
@@ -407,6 +409,7 @@ class DataGrid extends Grid
         $this->waitLoader();
         $this->getTemplateBlock()->waitForElementNotVisible($this->loader);
         $this->_rootElement->find(sprintf($this->columnHeader, $columnLabel), Locator::SELECTOR_XPATH)->click();
+        $this->waitLoader();
     }
 
     /**
diff --git a/dev/tests/functional/utils/command.php b/dev/tests/functional/utils/command.php
index 4061b07c783e4b0e8cbf4a7e4daa08d445834f68..a149be72a1ca48fbb7531b74d3dc84b687c5f737 100644
--- a/dev/tests/functional/utils/command.php
+++ b/dev/tests/functional/utils/command.php
@@ -8,5 +8,5 @@ if (isset($_GET['command'])) {
     $command = urldecode($_GET['command']);
     exec('php -f ../../../../bin/magento ' . $command);
 } else {
-    throw new \Exception("Command GET parameter is not set.");
+    throw new \InvalidArgumentException("Command GET parameter is not set.");
 }
diff --git a/dev/tests/functional/utils/export.php b/dev/tests/functional/utils/export.php
new file mode 100644
index 0000000000000000000000000000000000000000..062e8de6cbe7d6453f75d48b4faaf208ebffddc5
--- /dev/null
+++ b/dev/tests/functional/utils/export.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+if (!isset($_GET['template'])) {
+    throw new \InvalidArgumentException('Argument "template" must be set.');
+}
+
+$varDir = '../../../../var/';
+$template = urldecode($_GET['template']);
+$fileList = scandir($varDir, SCANDIR_SORT_NONE);
+$files = [];
+
+foreach ($fileList as $fileName) {
+    if (preg_match("`$template`", $fileName) === 1) {
+        $filePath = $varDir . $fileName;
+        $files[] = [
+            'content' => file_get_contents($filePath),
+            'name' => $fileName,
+            'date' => filectime($filePath),
+        ];
+    }
+}
+
+echo serialize($files);
diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php
index c64500fa6cfec3aed4a33cbcbc262befa1621d5c..c2bc02893e042de06cce4023d943110a693aeeca 100644
--- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php
+++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php
@@ -34,7 +34,7 @@ class CheckoutTest extends \PHPUnit_Framework_TestCase
     /**
      * Verify that an order placed with an existing customer can re-use the customer addresses.
      *
-     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php
+     * @magentoDataFixture Magento/Paypal/_files/quote_express_with_customer.php
      * @magentoAppIsolation enabled
      * @magentoDbIsolation enabled
      */
@@ -74,7 +74,7 @@ class CheckoutTest extends \PHPUnit_Framework_TestCase
     /**
      * Verify that after placing the order, addresses are associated with the order and the quote is a guest quote.
      *
-     * @magentoDataFixture Magento/Paypal/_files/quote_payment_express.php
+     * @magentoDataFixture Magento/Paypal/_files/quote_express.php
      * @magentoAppIsolation enabled
      * @magentoDbIsolation enabled
      */
diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_express.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_express.php
new file mode 100644
index 0000000000000000000000000000000000000000..0be18cdcaf9e42e2beb792596d0819b3c7a46182
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_express.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('adminhtml');
+\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
+    \Magento\Framework\App\Config\MutableScopeConfigInterface::class
+)->setValue(
+    'carriers/flatrate/active',
+    1,
+    \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+);
+\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
+    \Magento\Framework\App\Config\MutableScopeConfigInterface::class
+)->setValue(
+    'payment/paypal_express/active',
+    1,
+    \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+);
+$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+/** @var $product \Magento\Catalog\Model\Product */
+$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
+$product->setTypeId('simple')
+    ->setId(1)
+    ->setAttributeSetId(4)
+    ->setName('Simple Product')
+    ->setSku('simple')
+    ->setPrice(10)
+    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
+    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
+    ->setStockData(
+        [
+            'qty' => 100,
+            'is_in_stock' => 1,
+        ]
+    )->save();
+$product->load(1);
+
+$billingData = [
+    'firstname' => 'testname',
+    'lastname' => 'lastname',
+    'company' => '',
+    'email' => 'test@com.com',
+    'street' => [
+        0 => 'test1',
+        1 => '',
+    ],
+    'city' => 'Test',
+    'region_id' => '1',
+    'region' => '',
+    'postcode' => '9001',
+    'country_id' => 'US',
+    'telephone' => '11111111',
+    'fax' => '',
+    'confirm_password' => '',
+    'save_in_address_book' => '1',
+    'use_for_shipping' => '1',
+];
+
+$billingAddress = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+    ->create(\Magento\Quote\Model\Quote\Address::class, ['data' => $billingData]);
+$billingAddress->setAddressType('billing');
+
+$shippingAddress = clone $billingAddress;
+$shippingAddress->setId(null)->setAddressType('shipping');
+$shippingAddress->setShippingMethod('flatrate_flatrate');
+$shippingAddress->setCollectShippingRates(true);
+
+/** @var $quote \Magento\Quote\Model\Quote */
+$quote = $objectManager->create(\Magento\Quote\Model\Quote::class);
+$quote->setCustomerIsGuest(
+    true
+)->setStoreId(
+    $objectManager->get(
+        \Magento\Store\Model\StoreManagerInterface::class
+    )->getStore()->getId()
+)->setReservedOrderId(
+    '100000002'
+)->setBillingAddress(
+    $billingAddress
+)->setShippingAddress(
+    $shippingAddress
+)->addProduct(
+    $product,
+    10
+);
+$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate');
+$quote->getShippingAddress()->setCollectShippingRates(true);
+$quote->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_WPS_EXPRESS);
+
+$quoteRepository = $objectManager->get(\Magento\Quote\Api\CartRepositoryInterface::class);
+$quoteRepository->save($quote);
+$quote = $quoteRepository->get($quote->getId());
+$quote->setCustomerEmail('admin@example.com');
diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_express_with_customer.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_express_with_customer.php
new file mode 100644
index 0000000000000000000000000000000000000000..c319e298d1e50d56cdc886dd789d6c36941e8a4e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_express_with_customer.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+require __DIR__ . '/../../Customer/_files/customer.php';
+require __DIR__ . '/../../Customer/_files/customer_two_addresses.php';
+
+\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('adminhtml');
+
+$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+$objectManager->get(
+    \Magento\Framework\App\Config\MutableScopeConfigInterface::class
+)->setValue('carriers/flatrate/active', 1, \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
+$objectManager->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class)
+    ->setValue('payment/paypal_express/active', 1, \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
+
+/** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */
+$customerRepository = $objectManager->create(\Magento\Customer\Api\CustomerRepositoryInterface::class);
+$customer = $customerRepository->getById(1);
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
+$product->setTypeId('simple')
+    ->setId(1)
+    ->setAttributeSetId(4)
+    ->setName('Simple Product')
+    ->setSku('simple')
+    ->setPrice(10)
+    ->setStockData([
+        'use_config_manage_stock' => 1,
+        'qty' => 100,
+        'is_qty_decimal' => 0,
+        'is_in_stock' => 100,
+    ])
+    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
+    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
+    ->save();
+$product->load(1);
+
+$customerBillingAddress = $objectManager->create(\Magento\Customer\Model\Address::class);
+$customerBillingAddress->load(1);
+$billingAddressDataObject = $customerBillingAddress->getDataModel();
+$billingAddress = $objectManager->create(\Magento\Quote\Model\Quote\Address::class);
+$billingAddress->importCustomerAddressData($billingAddressDataObject);
+$billingAddress->setAddressType('billing');
+
+/** @var \Magento\Customer\Model\Address $customerShippingAddress */
+$customerShippingAddress = $objectManager->create(\Magento\Customer\Model\Address::class);
+$customerShippingAddress->load(2);
+$shippingAddressDataObject = $customerShippingAddress->getDataModel();
+$shippingAddress = $objectManager->create(\Magento\Quote\Model\Quote\Address::class);
+$shippingAddress->importCustomerAddressData($shippingAddressDataObject);
+$shippingAddress->setAddressType('shipping');
+
+$shippingAddress->setShippingMethod('flatrate_flatrate');
+$shippingAddress->setCollectShippingRates(true);
+
+/** @var $quote \Magento\Quote\Model\Quote */
+$quote = $objectManager->create(\Magento\Quote\Model\Quote::class);
+$quote->setCustomerIsGuest(false)
+    ->setCustomerId($customer->getId())
+    ->setCustomer($customer)
+    ->setStoreId($objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getStore()->getId())
+    ->setReservedOrderId('test02')
+    ->setBillingAddress($billingAddress)
+    ->setShippingAddress($shippingAddress)
+    ->addProduct($product, 10);
+$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate');
+$quote->getShippingAddress()->setCollectShippingRates(true);
+$quote->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_WPS_EXPRESS);
+
+/** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */
+$quoteRepository = $objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class);
+$quoteRepository->save($quote);
+$quote = $quoteRepository->get($quote->getId());
diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php
index fa234cc444a0730a580d35400483c0f2ee92e416..90f1102e0ec7639910c83dd76d37d0b0e36eef25 100644
--- a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php
+++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express.php
@@ -3,96 +3,7 @@
  * Copyright © 2016 Magento. All rights reserved.
  * See COPYING.txt for license details.
  */
-\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('adminhtml');
-\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
-    \Magento\Framework\App\Config\MutableScopeConfigInterface::class
-)->setValue(
-    'carriers/flatrate/active',
-    1,
-    \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-);
-\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
-    \Magento\Framework\App\Config\MutableScopeConfigInterface::class
-)->setValue(
-    'payment/paypal_express/active',
-    1,
-    \Magento\Store\Model\ScopeInterface::SCOPE_STORE
-);
-$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
-/** @var $product \Magento\Catalog\Model\Product */
-$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
-$product->setTypeId('simple')
-    ->setId(1)
-    ->setAttributeSetId(4)
-    ->setName('Simple Product')
-    ->setSku('simple')
-    ->setPrice(10)
-    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
-    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
-    ->setStockData(
-        [
-            'qty' => 100,
-            'is_in_stock' => 1,
-        ]
-    )->save();
-$product->load(1);
-
-$billingData = [
-    'firstname' => 'testname',
-    'lastname' => 'lastname',
-    'company' => '',
-    'email' => 'test@com.com',
-    'street' => [
-        0 => 'test1',
-        1 => '',
-    ],
-    'city' => 'Test',
-    'region_id' => '1',
-    'region' => '',
-    'postcode' => '9001',
-    'country_id' => 'US',
-    'telephone' => '11111111',
-    'fax' => '',
-    'confirm_password' => '',
-    'save_in_address_book' => '1',
-    'use_for_shipping' => '1',
-];
-
-$billingAddress = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
-    ->create(\Magento\Quote\Model\Quote\Address::class, ['data' => $billingData]);
-$billingAddress->setAddressType('billing');
-
-$shippingAddress = clone $billingAddress;
-$shippingAddress->setId(null)->setAddressType('shipping');
-$shippingAddress->setShippingMethod('flatrate_flatrate');
-$shippingAddress->setCollectShippingRates(true);
-
-/** @var $quote \Magento\Quote\Model\Quote */
-$quote = $objectManager->create(\Magento\Quote\Model\Quote::class);
-$quote->setCustomerIsGuest(
-    true
-)->setStoreId(
-    $objectManager->get(
-        \Magento\Store\Model\StoreManagerInterface::class
-    )->getStore()->getId()
-)->setReservedOrderId(
-    '100000002'
-)->setBillingAddress(
-    $billingAddress
-)->setShippingAddress(
-    $shippingAddress
-)->addProduct(
-    $product,
-    10
-);
-$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate');
-$quote->getShippingAddress()->setCollectShippingRates(true);
-$quote->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_WPS_EXPRESS);
-
-$quoteRepository = $objectManager->get(\Magento\Quote\Api\CartRepositoryInterface::class);
-$quoteRepository->save($quote);
-$quote = $quoteRepository->get($quote->getId());
-$quote->setCustomerEmail('admin@example.com');
+require __DIR__ . '/quote_express.php';
 
 /** @var $service \Magento\Quote\Api\CartManagementInterface */
 $service = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express_with_customer.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express_with_customer.php
index 1c6793f05dea95644067521332900cc647a3648e..eaf444be13367f8769d28e34245c007122b79bf1 100644
--- a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express_with_customer.php
+++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payment_express_with_customer.php
@@ -3,79 +3,7 @@
  * Copyright © 2016 Magento. All rights reserved.
  * See COPYING.txt for license details.
  */
-
-require __DIR__ . '/../../Customer/_files/customer.php';
-require __DIR__ . '/../../Customer/_files/customer_two_addresses.php';
-
-\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('adminhtml');
-
-$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
-
-$objectManager->get(
-    \Magento\Framework\App\Config\MutableScopeConfigInterface::class
-)->setValue('carriers/flatrate/active', 1, \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
-$objectManager->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class)
-    ->setValue('payment/paypal_express/active', 1, \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
-
-/** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */
-$customerRepository = $objectManager->create(\Magento\Customer\Api\CustomerRepositoryInterface::class);
-$customer = $customerRepository->getById(1);
-
-/** @var $product \Magento\Catalog\Model\Product */
-$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
-$product->setTypeId('simple')
-    ->setId(1)
-    ->setAttributeSetId(4)
-    ->setName('Simple Product')
-    ->setSku('simple')
-    ->setPrice(10)
-    ->setStockData([
-    'use_config_manage_stock' => 1,
-    'qty' => 100,
-    'is_qty_decimal' => 0,
-    'is_in_stock' => 100,
-])
-    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
-    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
-    ->save();
-$product->load(1);
-
-$customerBillingAddress = $objectManager->create(\Magento\Customer\Model\Address::class);
-$customerBillingAddress->load(1);
-$billingAddressDataObject = $customerBillingAddress->getDataModel();
-$billingAddress = $objectManager->create(\Magento\Quote\Model\Quote\Address::class);
-$billingAddress->importCustomerAddressData($billingAddressDataObject);
-$billingAddress->setAddressType('billing');
-
-/** @var \Magento\Customer\Model\Address $customerShippingAddress */
-$customerShippingAddress = $objectManager->create(\Magento\Customer\Model\Address::class);
-$customerShippingAddress->load(2);
-$shippingAddressDataObject = $customerShippingAddress->getDataModel();
-$shippingAddress = $objectManager->create(\Magento\Quote\Model\Quote\Address::class);
-$shippingAddress->importCustomerAddressData($shippingAddressDataObject);
-$shippingAddress->setAddressType('shipping');
-
-$shippingAddress->setShippingMethod('flatrate_flatrate');
-$shippingAddress->setCollectShippingRates(true);
-
-/** @var $quote \Magento\Quote\Model\Quote */
-$quote = $objectManager->create(\Magento\Quote\Model\Quote::class);
-$quote->setCustomerIsGuest(false)
-    ->setCustomerId($customer->getId())
-    ->setCustomer($customer)
-    ->setStoreId($objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getStore()->getId())
-    ->setReservedOrderId('test02')
-    ->setBillingAddress($billingAddress)
-    ->setShippingAddress($shippingAddress)
-    ->addProduct($product, 10);
-$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate');
-$quote->getShippingAddress()->setCollectShippingRates(true);
-$quote->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_WPS_EXPRESS);
-
-/** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */
-$quoteRepository = $objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class);
-$quoteRepository->save($quote);
-$quote = $quoteRepository->get($quote->getId());
+require __DIR__ . '/quote_express_with_customer.php';
 
 /** @var $service \Magento\Quote\Api\CartManagementInterface */
 $service = $objectManager->create(\Magento\Quote\Api\CartManagementInterface::class);
diff --git a/dev/tests/js/jasmine/require.conf.js b/dev/tests/js/jasmine/require.conf.js
index 9ba59b81bc27f5326673e59270917b232643ee24..c60ec02943b5ba86a0c7622bf64ce1c037351805 100644
--- a/dev/tests/js/jasmine/require.conf.js
+++ b/dev/tests/js/jasmine/require.conf.js
@@ -16,7 +16,13 @@ require.config({
         ]
     },
     paths: {
-        'tests': 'dev/tests/js/jasmine'
+        'tests': 'dev/tests/js/jasmine',
+        'squire': 'node_modules/squirejs/src/Squire'
+    },
+    shim: {
+        squire: {
+            exports: 'squire'
+        }
     },
     config: {
         jsbuild: {
diff --git a/dev/tests/js/jasmine/spec_runner/settings.json b/dev/tests/js/jasmine/spec_runner/settings.json
index 109da479146da84882c4a6a68e8778db0b19701d..25407123c1f34d66c70c4406d9341f4659f7519b 100644
--- a/dev/tests/js/jasmine/spec_runner/settings.json
+++ b/dev/tests/js/jasmine/spec_runner/settings.json
@@ -57,7 +57,8 @@
             "^\/_SpecRunner.html",
             "^\/dev\/tests",
             "^\/.grunt",
-            "^\/pub\/static"
+            "^\/pub\/static",
+            "^\/node_modules"
         ],
         "options": {
             /**
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/new-customer-address.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/new-customer-address.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae7ff3d145e2b0ae8c2850ff8d2217baa9fed3f1
--- /dev/null
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/new-customer-address.test.js
@@ -0,0 +1,74 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'Magento_Checkout/js/model/new-customer-address'
+], function (NewCustomerAddress) {
+    'use strict';
+
+    describe('Magento_Checkout/js/model/new-customer-address', function () {
+        var newCustomerAddress;
+
+        window.checkoutConfig = {
+            defaultCountryId: 'US'
+        };
+
+        beforeEach(function () {
+            newCustomerAddress = NewCustomerAddress;
+        });
+
+        it('Check that is executable.', function () {
+            expect(typeof newCustomerAddress).toEqual('function');
+        });
+
+        it('Check on empty object.', function () {
+            var expected = {
+                countryId: 'US',
+                regionCode: null,
+                region: null
+            };
+
+            expect(JSON.stringify(newCustomerAddress({}))).toEqual(JSON.stringify(expected));
+        });
+
+        it('Check on function call with empty address data.', function () {
+            var result = newCustomerAddress({});
+
+            expect(result.isDefaultShipping()).toBeUndefined();
+            expect(result.isDefaultBilling()).toBeUndefined();
+            expect(result.getType()).toEqual('new-customer-address');
+            expect(result.getKey()).toEqual('new-customer-address');
+            expect(result.getKey()).toContain('new-customer-address');
+            expect(result.isEditable()).toBeTruthy();
+            expect(result.canUseForBilling()).toBeTruthy();
+        });
+
+        it('Check on regionId with region object in address data.', function () {
+            var result = newCustomerAddress({
+                    region: {
+                        'region_id': 1
+                    }
+                }),
+                expected = {
+                    countryId: 'US',
+                    regionId: 1
+                };
+
+            expect(JSON.stringify(result)).toEqual(JSON.stringify(expected));
+        });
+        it('Check on regionId with countryId in address data.', function () {
+            var result = newCustomerAddress({
+                    'country_id': 'US'
+                }),
+                expected = {
+                    countryId: 'US',
+                    regionCode: null,
+                    region: null
+                };
+
+            expect(JSON.stringify(result)).toEqual(JSON.stringify(expected));
+        });
+    });
+});
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..785d88d81e3543aae46ad1564c7a4ba5edbcba5c
--- /dev/null
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js
@@ -0,0 +1,115 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/* eslint max-nested-callbacks: 0 */
+
+define(['squire', 'ko'], function (Squire, ko) {
+    'use strict';
+
+    var injector = new Squire(),
+        checkoutProvider = {
+            on: jasmine.createSpy()
+        },
+        mocks = {
+            'Magento_Checkout/js/action/select-shipping-address': jasmine.createSpy(),
+            'Magento_Checkout/js/model/address-converter': {
+                formAddressDataToQuoteAddress: jasmine.createSpy()
+            },
+            'Magento_Checkout/js/model/cart/estimate-service': jasmine.createSpy(),
+            'Magento_Checkout/js/checkout-data': jasmine.createSpy(),
+            'Magento_Checkout/js/model/shipping-rates-validator': {
+                bindChangeHandlers: jasmine.createSpy()
+            },
+            'uiRegistry': {
+                async: jasmine.createSpy().and.returnValue(function (callback) {
+                    callback(checkoutProvider);
+                }),
+                create: jasmine.createSpy(),
+                get: jasmine.createSpy(),
+                set: jasmine.createSpy()
+            },
+            'Magento_Checkout/js/model/quote': {
+                isVirtual: jasmine.createSpy(),
+                shippingAddress: jasmine.createSpy()
+            },
+            'Magento_Checkout/js/model/checkout-data-resolver': {
+                resolveEstimationAddress: jasmine.createSpy()
+            },
+            'mage/validation': jasmine.createSpy()
+        },
+        obj;
+
+    beforeEach(function (done) {
+        injector.mock(mocks);
+        injector.require(['Magento_Checkout/js/view/cart/shipping-estimation'], function (Constr) {
+            obj = new Constr({
+                provider: 'provName',
+                name: '',
+                index: ''
+            });
+            done();
+        });
+    });
+
+    describe('Magento_Checkout/js/view/cart/shipping-estimation', function () {
+        describe('"initElement" method', function () {
+            it('Check for return value and element that initiated.', function () {
+                var element = jasmine.createSpyObj('element', ['initContainer']);
+
+                expect(obj.initElement(element)).toBe(obj);
+                expect(mocks['Magento_Checkout/js/model/shipping-rates-validator'].bindChangeHandlers)
+                    .not.toHaveBeenCalled();
+            });
+            it('Check shipping rates validator call.', function () {
+                var element = {
+                    index: 'address-fieldsets',
+                    elems: ko.observable(),
+                    initContainer: jasmine.createSpy()
+                };
+
+                spyOn(element.elems, 'subscribe');
+
+                obj.initElement(element);
+                expect(mocks['Magento_Checkout/js/model/shipping-rates-validator'].bindChangeHandlers)
+                    .toHaveBeenCalledWith(element.elems(), true, 500);
+                expect(element.elems.subscribe)
+                    .toHaveBeenCalledWith(jasmine.any(Function));
+            });
+        });
+
+        describe('"getEstimationInfo" method', function () {
+            it('Check for invalid form data.', function () {
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValue(true),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+
+                expect(obj.getEstimationInfo()).toBeUndefined();
+                expect(obj.source.get).toHaveBeenCalledWith('params.invalid');
+                expect(obj.source.get).not.toHaveBeenCalledWith('shippingAddress');
+                expect(obj.source.set).toHaveBeenCalledWith('params.invalid', false);
+                expect(obj.source.trigger).toHaveBeenCalledWith('shippingAddress.data.validate');
+                expect(mocks['Magento_Checkout/js/action/select-shipping-address']).not.toHaveBeenCalled();
+                obj.source = {};
+            });
+            it('Check for vaild form data.', function () {
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValues(false, {}),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+
+                expect(obj.getEstimationInfo()).toBeUndefined();
+                expect(obj.source.get).toHaveBeenCalledWith('params.invalid');
+                expect(obj.source.get).toHaveBeenCalledWith('shippingAddress');
+                expect(obj.source.set).toHaveBeenCalledWith('params.invalid', false);
+                expect(obj.source.trigger).toHaveBeenCalledWith('shippingAddress.data.validate');
+                expect(mocks['Magento_Checkout/js/action/select-shipping-address']).toHaveBeenCalled();
+                obj.source = {};
+            });
+        });
+    });
+});
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..20f3b2a090d7a38882a0728be94998ea7c359c70
--- /dev/null
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js
@@ -0,0 +1,194 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/* eslint max-nested-callbacks: 0 */
+require.config({
+    map: {
+        '*': {
+            'Magento_Checkout/js/view/shipping': 'Magento_Checkout/js/view/shipping'
+        }
+    }
+});
+
+define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) {
+    'use strict';
+
+    var injector = new Squire(),
+        modalStub = {
+            openModal: jasmine.createSpy(),
+            closeModal: jasmine.createSpy()
+        },
+        mocks = {
+            'Magento_Customer/js/model/customer': {
+                isLoggedIn: ko.observable()
+            },
+            'Magento_Customer/js/model/address-list': ko.observableArray(),
+            'Magento_Checkout/js/model/address-converter': jasmine.createSpy(),
+            'Magento_Checkout/js/model/quote': {
+                isVirtual: jasmine.createSpy(),
+                shippingMethod: ko.observable()
+            },
+            'Magento_Checkout/js/action/create-shipping-address': jasmine.createSpy().and.returnValue(
+                jasmine.createSpyObj('newShippingAddress', ['getKey'])
+            ),
+            'Magento_Checkout/js/action/select-shipping-address': jasmine.createSpy(),
+            'Magento_Checkout/js/model/shipping-rates-validator': jasmine.createSpy(),
+            'Magento_Checkout/js/model/shipping-address/form-popup-state': {
+                isVisible: ko.observable()
+            },
+            'Magento_Checkout/js/model/shipping-service': jasmine.createSpyObj('service', ['getShippingRates']),
+            'Magento_Checkout/js/action/select-shipping-method': jasmine.createSpy(),
+            'Magento_Checkout/js/model/shipping-rate-registry': jasmine.createSpy(),
+            'Magento_Checkout/js/action/set-shipping-information': jasmine.createSpy(),
+            'Magento_Checkout/js/model/step-navigator': jasmine.createSpyObj('navigator', ['registerStep']),
+            'Magento_Ui/js/modal/modal': jasmine.createSpy('modal').and.returnValue(modalStub),
+            'Magento_Checkout/js/model/checkout-data-resolver': jasmine.createSpyObj(
+                'dataResolver',
+                ['resolveShippingAddress']
+            ),
+            'Magento_Checkout/js/checkout-data': jasmine.createSpyObj(
+                'checkoutData',
+                ['setSelectedShippingAddress', 'setNewCustomerShippingAddress', 'setSelectedShippingRate']
+            ),
+            'uiRegistry': jasmine.createSpy(),
+            'Magento_Checkout/js/model/shipping-rate-service': jasmine.createSpy()
+        },
+        obj;
+
+    beforeEach(function (done) {
+        injector.mock(mocks);
+        injector.require(['Magento_Checkout/js/view/shipping'], function (Constr) {
+            obj = new Constr({
+                provider: 'provName',
+                name: '',
+                index: '',
+                popUpForm: {
+                    options: {
+                        buttons: {
+                            save: {},
+                            cancel: {}
+                        }
+                    }
+                }
+            });
+            done();
+        });
+    });
+
+    describe('Magento_Checkout/js/view/shipping', function () {
+        describe('"navigate" method', function () {
+            it('Check for return value.', function () {
+                expect(obj.navigate()).toBeUndefined();
+            });
+        });
+
+        describe('"getPopUp" method', function () {
+            it('Check for return value.', function () {
+                expect(obj.getPopUp()).toBe(modalStub);
+                expect(mocks['Magento_Ui/js/modal/modal']).toHaveBeenCalled();
+                mocks['Magento_Ui/js/modal/modal'].calls.reset();
+            });
+            it('Check on single modal call', function () {
+                expect(obj.getPopUp()).toBe(modalStub);
+                expect(mocks['Magento_Ui/js/modal/modal']).not.toHaveBeenCalled();
+            });
+        });
+
+        describe('"showFormPopUp" method', function () {
+            it('Check method call.', function () {
+                expect(obj.showFormPopUp()).toBeUndefined();
+                expect(obj.isFormPopUpVisible()).toBeTruthy();
+                expect(modalStub.openModal).toHaveBeenCalled();
+            });
+        });
+
+        describe('"saveNewAddress" method', function () {
+            it('Check method call with invalid form data.', function () {
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValue(true),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+
+                expect(obj.saveNewAddress()).toBeUndefined();
+                expect(obj.isNewAddressAdded()).toBeFalsy();
+                expect(modalStub.closeModal).not.toHaveBeenCalled();
+            });
+            it('Check method call with valid form data.', function () {
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValues(true, false, {}),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+
+                expect(obj.saveNewAddress()).toBeUndefined();
+                expect(obj.isNewAddressAdded()).toBeTruthy();
+                expect(modalStub.closeModal).toHaveBeenCalled();
+            });
+        });
+
+        describe('"selectShippingMethod" method', function () {
+            it('Check method call.', function () {
+                var shippingMethod = {
+                    'carrier_code': 'carrier',
+                    'method_code': 'method'
+                };
+
+                expect(obj.selectShippingMethod(shippingMethod)).toBeTruthy();
+                expect(mocks['Magento_Checkout/js/checkout-data'].setSelectedShippingRate)
+                    .toHaveBeenCalledWith('carrier_method');
+            });
+        });
+
+        describe('"setShippingInformation" method', function () {
+            it('Check method call.', function () {
+                expect(obj.setShippingInformation()).toBeUndefined();
+            });
+        });
+
+        describe('"validateShippingInformation" method', function () {
+            it('Check method call on negative cases.', function () {
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValue(true),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+
+                expect(obj.validateShippingInformation()).toBeFalsy();
+                expect(obj.errorValidationMessage()).toBe('Please specify a shipping method.');
+                spyOn(mocks['Magento_Checkout/js/model/quote'], 'shippingMethod').and.returnValue(true);
+                spyOn(mocks['Magento_Customer/js/model/customer'], 'isLoggedIn').and.returnValue(true);
+                expect(obj.validateShippingInformation()).toBeFalsy();
+            });
+            it('Check method call on positive case.', function () {
+                $('body').append('<form data-role="email-with-possible-login">' +
+                    '<input type="text" name="username" />' +
+                    '</form>');
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValue(true),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+                obj.isFormInline = false;
+
+                spyOn(mocks['Magento_Checkout/js/model/quote'], 'shippingMethod').and.returnValue(true);
+                spyOn(mocks['Magento_Customer/js/model/customer'], 'isLoggedIn').and.returnValue(false);
+                spyOn($.fn, 'valid').and.returnValue(true);
+                expect(obj.validateShippingInformation()).toBeTruthy();
+            });
+        });
+
+        describe('"triggerShippingDataValidateEvent" method', function () {
+            it('Check method call.', function () {
+                obj.source = {
+                    get: jasmine.createSpy().and.returnValue(true),
+                    set: jasmine.createSpy(),
+                    trigger: jasmine.createSpy()
+                };
+                expect(obj.triggerShippingDataValidateEvent()).toBeUndefined();
+            });
+        });
+    });
+});
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/model/customer/address.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/model/customer/address.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..0384c9df2c583c31ad3f95a6b942b8c73b0b3af5
--- /dev/null
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/model/customer/address.test.js
@@ -0,0 +1,58 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'Magento_Customer/js/model/customer/address'
+], function (CustomerAddress) {
+    'use strict';
+
+    describe('Magento_Customer/js/model/customer/address', function () {
+        var customerAddress;
+
+        beforeEach(function () {
+            customerAddress = CustomerAddress;
+        });
+
+        it('Check that is executable.', function () {
+            expect(typeof customerAddress).toEqual('function');
+        });
+
+        it('Check on empty object.', function () {
+            var addressData = {
+                region: {}
+            };
+
+            expect(JSON.stringify(customerAddress(addressData))).toEqual(JSON.stringify({}));
+        });
+
+        it('Check on function call with empty address data.', function () {
+            var result = customerAddress({
+                region: {}
+            });
+
+            expect(result.isDefaultShipping()).toBeUndefined();
+            expect(result.isDefaultBilling()).toBeUndefined();
+            expect(result.getAddressInline()).toBeUndefined();
+            expect(result.getType()).toEqual('customer-address');
+            expect(result.getKey()).toContain('customer-address');
+            expect(result.getCacheKey()).toContain('customer-address');
+            expect(result.isEditable()).toBeFalsy();
+            expect(result.canUseForBilling()).toBeTruthy();
+        });
+
+        it('Check on regionId with region object in address data.', function () {
+            var result = customerAddress({
+                    region: {
+                        'region_id': 1
+                    }
+                }),
+                expected = {
+                    regionId: '1'
+                };
+
+            expect(JSON.stringify(result)).toEqual(JSON.stringify(expected));
+        });
+    });
+});
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..128bd86edbf990cc28002152ac128b134c0488be
--- /dev/null
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js
@@ -0,0 +1,81 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/* eslint max-nested-callbacks: 0 */
+define(['squire'], function (Squire) {
+    'use strict';
+
+    var injector = new Squire(),
+        loginAction = jasmine.createSpy(),
+        mocks = {
+            'Magento_Customer/js/action/login': loginAction,
+            'Magento_Customer/js/customer-data': {
+                get: jasmine.createSpy()
+            },
+            'Magento_Customer/js/model/authentication-popup': {
+                createPopUp: jasmine.createSpy(),
+                modalWindow: null
+            },
+            'Magento_Ui/js/modal/alert': jasmine.createSpy(),
+            'mage/url': jasmine.createSpyObj('customerData', ['setBaseUrl'])
+        },
+        obj;
+
+    loginAction.registerLoginCallback = jasmine.createSpy();
+    window.authenticationPopup = {
+        customerRegisterUrl: 'register_url',
+        customerForgotPasswordUrl: 'forgot_password_url',
+        autocomplete: 'autocomplete_flag',
+        baseUrl: 'base_url'
+    };
+
+    beforeEach(function (done) {
+        injector.mock(mocks);
+        injector.require(['Magento_Customer/js/view/authentication-popup'], function (Constr) {
+            obj = new Constr({
+                provider: 'provName',
+                name: '',
+                index: ''
+            });
+            done();
+        });
+    });
+
+    describe('Magento_Customer/js/view/authentication-popup', function () {
+        describe('"isActive" method', function () {
+            it('Check for return value.', function () {
+                mocks['Magento_Customer/js/customer-data'].get.and.returnValue(function () {
+                    return true;
+                });
+                expect(obj.isActive()).toBeFalsy();
+            });
+        });
+    });
+
+    describe('Magento_Customer/js/view/authentication-popup', function () {
+        describe('"setModalElement" method', function () {
+            it('Check for return value.', function () {
+                expect(obj.setModalElement()).toBeUndefined();
+                expect(mocks['Magento_Customer/js/model/authentication-popup'].createPopUp).toHaveBeenCalled();
+            });
+        });
+    });
+
+    describe('Magento_Customer/js/view/authentication-popup', function () {
+        describe('"login" method', function () {
+            it('Check for return value.', function () {
+                var event = {
+                    currentTarget: '<form><input type="text" name="username" value="customer"/></form>',
+                    stopPropagation: jasmine.createSpy()
+                };
+
+                expect(obj.login(null, event)).toBeFalsy();
+                expect(mocks['Magento_Customer/js/action/login']).toHaveBeenCalledWith({
+                    username: 'customer'
+                });
+            });
+        });
+    });
+});
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js
index a240e5cfd432597d4bb38e10957b4495d4ed48b2..d6480261c3fe14fe3dca807bec1375d3947972fe 100644
--- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js
@@ -29,13 +29,10 @@ define([
 
             it('normal + boundary values', function () {
                 expect(paging.normalize(1)).toBe(1);
-                expect(paging.normalize(2)).toBe(2);
-                expect(paging.normalize(4)).toBe(4);
             });
 
             it('out of boundary values', function () {
                 expect(paging.normalize(0)).toBe(1);
-                expect(paging.normalize(5)).toBe(4);
             });
         });
 
diff --git a/package.json.sample b/package.json.sample
index 40169b3179052d98ad2c1a79baf48e376689f3c1..d73606809abe6563a57c562a27544def4ddce84a 100644
--- a/package.json.sample
+++ b/package.json.sample
@@ -36,7 +36,8 @@
         "serve-static": "^1.7.1",
         "strip-json-comments": "^1.0.2",
         "time-grunt": "^1.0.0",
-        "underscore": "^1.7.0"
+        "underscore": "^1.7.0",
+        "squirejs": "0.2.1"
     },
     "engines": {
         "node": ">=0.10.0"