diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml
index 5f62a9d7cf1920ebdfc8020befa0f78f00c396bc..e4f4e11983892f98cfc8945ee2a541c428b441dd 100644
--- a/app/code/Magento/Braintree/etc/adminhtml/system.xml
+++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml
@@ -33,7 +33,7 @@
                         </requires>
                     </field>
                     <field id="braintree_cc_vault_active" translate="label" type="select" sortOrder="12" showInDefault="1" showInWebsite="1" showInStore="0">
-                        <label>Vault enabled</label>
+                        <label>Vault Enabled</label>
                         <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                         <config_path>payment/braintree_cc_vault/active</config_path>
                         <requires>
@@ -155,7 +155,7 @@
                             <comment>It is recommended to set this value to "PayPal" per store views.</comment>
                         </field>
                         <field id="braintree_paypal_vault_active" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1" showInStore="0">
-                            <label>Vault enabled</label>
+                            <label>Vault Enabled</label>
                             <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                             <config_path>payment/braintree_paypal_vault/active</config_path>
                             <requires>
diff --git a/app/code/Magento/Braintree/view/adminhtml/web/styles.css b/app/code/Magento/Braintree/view/adminhtml/web/styles.css
index 31f48cd0b28df1d8235b9ccc9d6b46b13b410bd4..81378f636eb61362ad2840bc859161c91ee8add1 100644
--- a/app/code/Magento/Braintree/view/adminhtml/web/styles.css
+++ b/app/code/Magento/Braintree/view/adminhtml/web/styles.css
@@ -3,6 +3,6 @@
  * See COPYING.txt for license details.
  */
 
-.braintree-section .heading {display: inline-block; background: url("images/braintree_logo.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;}
-.braintree-section .button-container {display: inline-block; float: right;}
+.braintree-section .heading {background: url("images/braintree_logo.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;}
+.braintree-section .button-container {float: right;}
 .braintree-section .config-alt {background: url("images/braintree_allinone.png") no-repeat scroll 0 0 / 100% auto; height: 28px; margin: 0.5rem 0 0; width: 230px;}
\ No newline at end of file
diff --git a/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php b/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php
index 9eda5f84e16b0e45d8621ce26d75c79f139f6192..51345c2b7609cb8e39cf25fb9a4f4f202cba6f65 100644
--- a/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php
+++ b/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php
@@ -15,12 +15,10 @@ use Symfony\Component\Console\Input\InputArgument;
 use Magento\Framework\App\ObjectManagerFactory;
 use Magento\Framework\ObjectManagerInterface;
 use Magento\Framework\Validator\Locale;
-use Magento\Framework\Console\Cli;
-use Magento\Deploy\Model\ProcessManager;
-use Magento\Deploy\Model\Process;
 use Magento\Framework\Exception\LocalizedException;
 use Magento\Framework\App\State;
 use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+use Magento\Deploy\Model\DeployManager;
 
 /**
  * Deploy static content command
@@ -121,109 +119,116 @@ class DeployStaticContentCommand extends Command
                     Options::FORCE_RUN,
                     '-f',
                     InputOption::VALUE_NONE,
-                    'If specified, then run files will be deployed in any mode.'
+                    'Deploy files in any mode.'
                 ),
                 new InputOption(
                     Options::NO_JAVASCRIPT,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no JavaScript will be deployed.'
+                    'Do not deploy JavaScript files'
                 ),
                 new InputOption(
                     Options::NO_CSS,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no CSS will be deployed.'
+                    'Do not deploy CSS files.'
                 ),
                 new InputOption(
                     Options::NO_LESS,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no LESS will be deployed.'
+                    'Do not deploy LESS files.'
                 ),
                 new InputOption(
                     Options::NO_IMAGES,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no images will be deployed.'
+                    'Do not deploy images.'
                 ),
                 new InputOption(
                     Options::NO_FONTS,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no font files will be deployed.'
+                    'Do not deploy font files.'
                 ),
                 new InputOption(
                     Options::NO_HTML,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no html files will be deployed.'
+                    'Do not deploy HTML files.'
                 ),
                 new InputOption(
                     Options::NO_MISC,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, no miscellaneous files will be deployed.'
+                    'Do not deploy other types of files (.md, .jbf, .csv, etc...).'
                 ),
                 new InputOption(
                     Options::NO_HTML_MINIFY,
                     null,
                     InputOption::VALUE_NONE,
-                    'If specified, html will not be minified.'
+                    'Do not minify HTML files.'
                 ),
                 new InputOption(
                     Options::THEME,
                     '-t',
                     InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
-                    'If specified, just specific theme(s) will be actually deployed.',
+                    'Generate static view files for only the specified themes.',
                     ['all']
                 ),
                 new InputOption(
                     Options::EXCLUDE_THEME,
                     null,
                     InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
-                    'If specified, exclude specific theme(s) from deployment.',
+                    'Do not generate files for the specified themes.',
                     ['none']
                 ),
                 new InputOption(
                     Options::LANGUAGE,
                     '-l',
                     InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
-                    'List of languages you want the tool populate files for.',
+                    'Generate files only for the specified languages.',
                     ['all']
                 ),
                 new InputOption(
                     Options::EXCLUDE_LANGUAGE,
                     null,
                     InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
-                    'List of langiages you do not want the tool populate files for.',
+                    'Do not generate files for the specified languages.',
                     ['none']
                 ),
                 new InputOption(
                     Options::AREA,
                     '-a',
                     InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
-                    'List of areas you want the tool populate files for.',
+                    'Generate files only for the specified areas.',
                     ['all']
                 ),
                 new InputOption(
                     Options::EXCLUDE_AREA,
                     null,
                     InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
-                    'List of areas you do not want the tool populate files for.',
+                    'Do not generate files for the specified areas.',
                     ['none']
                 ),
                 new InputOption(
                     Options::JOBS_AMOUNT,
                     '-j',
                     InputOption::VALUE_OPTIONAL,
-                    'Amount of jobs to which script can be paralleled.',
+                    'Enable parallel processing using the specified number of jobs.',
                     self::DEFAULT_JOBS_AMOUNT
                 ),
+                new InputOption(
+                    Options::SYMLINK_LOCALE,
+                    null,
+                    InputOption::VALUE_NONE,
+                    'Create symlinks for the files of those locales, which are passed for deployment, '
+                    . 'but have no customizations'
+                ),
                 new InputArgument(
                     self::LANGUAGES_ARGUMENT,
                     InputArgument::IS_ARRAY,
-                    'List of languages you want the tool populate files for.'
+                    'Space-separated list of ISO-636 language codes for which to output static view files.'
                 ),
             ]);
 
@@ -374,8 +379,11 @@ class DeployStaticContentCommand extends Command
         if (!$input->getOption(Options::FORCE_RUN) && $this->getAppState()->getMode() !== State::MODE_PRODUCTION) {
             throw new LocalizedException(
                 __(
-                    "Deploy static content is applicable only for production mode.\n"
-                    . "Please use command 'bin/magento deploy:mode:set production' for set up production mode."
+                    'NOTE: Manual static content deployment is not required in "default" and "developer" modes.'
+                    . PHP_EOL . 'In "default" and "developer" modes static contents are being deployed '
+                    . 'automatically on demand.'
+                    . PHP_EOL . 'If you still want to deploy in these modes, use -f option: '
+                    . "'bin/magento setup:static-content:deploy -f'"
                 )
             );
         }
@@ -390,20 +398,24 @@ class DeployStaticContentCommand extends Command
         $output->writeln("Requested areas: " . implode(', ', array_keys($deployableAreaThemeMap)));
         $output->writeln("Requested themes: " . implode(', ', $requestedThemes));
 
-        $deployer = $this->objectManager->create(
-            \Magento\Deploy\Model\Deployer::class,
+        /** @var $deployManager DeployManager */
+        $deployManager = $this->objectManager->create(
+            DeployManager::class,
             [
-                'filesUtil' => $filesUtil,
                 'output' => $output,
                 'options' => $this->input->getOptions(),
             ]
         );
 
-        if ($this->isCanBeParalleled()) {
-            return $this->runProcessesInParallel($deployer, $deployableAreaThemeMap, $deployableLanguages);
-        } else {
-            return $this->deploy($deployer, $deployableLanguages, $deployableAreaThemeMap);
+        foreach ($deployableAreaThemeMap as $area => $themes) {
+            foreach ($deployableLanguages as $locale) {
+                foreach ($themes as $themePath) {
+                    $deployManager->addPack($area, $themePath, $locale);
+                }
+            }
         }
+
+        return $deployManager->deploy();
     }
 
     /**
@@ -464,89 +476,4 @@ class DeployStaticContentCommand extends Command
 
         return [$deployableLanguages, $deployableAreaThemeMap, $requestedThemes];
     }
-
-    /**
-     * @param \Magento\Deploy\Model\Deployer $deployer
-     * @param array $deployableLanguages
-     * @param array $deployableAreaThemeMap
-     * @return int
-     */
-    private function deploy($deployer, $deployableLanguages, $deployableAreaThemeMap)
-    {
-        return $deployer->deploy(
-            $this->objectManagerFactory,
-            $deployableLanguages,
-            $deployableAreaThemeMap
-        );
-    }
-
-    /**
-     * @param \Magento\Deploy\Model\Deployer $deployer
-     * @param array $deployableAreaThemeMap
-     * @param array $deployableLanguages
-     * @return int
-     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
-     */
-    private function runProcessesInParallel($deployer, $deployableAreaThemeMap, $deployableLanguages)
-    {
-        /** @var ProcessManager $processManager */
-        $processManager = $this->objectManager->create(ProcessManager::class);
-        $processNumber = 0;
-        $processQueue = [];
-        foreach ($deployableAreaThemeMap as $area => &$themes) {
-            foreach ($themes as $theme) {
-                foreach ($deployableLanguages as $lang) {
-                    $deployerFunc = function (Process $process) use ($area, $theme, $lang, $deployer) {
-                        return $this->deploy($deployer, [$lang], [$area => [$theme]]);
-                    };
-                    if ($processNumber >= $this->getProcessesAmount()) {
-                        $processQueue[] = $deployerFunc;
-                    } else {
-                        $processManager->fork($deployerFunc);
-                    }
-                    $processNumber++;
-                }
-            }
-        }
-        $returnStatus = null;
-        while (count($processManager->getProcesses()) > 0) {
-            foreach ($processManager->getProcesses() as $process) {
-                if ($process->isCompleted()) {
-                    $processManager->delete($process);
-                    $returnStatus |= $process->getStatus();
-                    if ($queuedProcess = array_shift($processQueue)) {
-                        $processManager->fork($queuedProcess);
-                    }
-                    if (count($processManager->getProcesses()) >= $this->getProcessesAmount()) {
-                        break 1;
-                    }
-                }
-            }
-            usleep(5000);
-        }
-
-        return $returnStatus === Cli::RETURN_SUCCESS ?: Cli::RETURN_FAILURE;
-    }
-
-    /**
-     * @return bool
-     */
-    private function isCanBeParalleled()
-    {
-        return function_exists('pcntl_fork') && $this->getProcessesAmount() > 1;
-    }
-
-    /**
-     * @return int
-     */
-    private function getProcessesAmount()
-    {
-        $jobs = (int)$this->input->getOption(Options::JOBS_AMOUNT);
-        if ($jobs < 1) {
-            throw new \InvalidArgumentException(
-                Options::JOBS_AMOUNT . ' argument has invalid value. It must be greater than 0'
-            );
-        }
-        return $jobs;
-    }
 }
diff --git a/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php b/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php
index 729ccbe8096286fa75adce196a1858371d64c732..25457f5505fb974240e4924c9c7713555ec8e97d 100644
--- a/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php
+++ b/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php
@@ -92,4 +92,9 @@ interface DeployStaticOptionsInterface
      * Force run of static deploy
      */
     const FORCE_RUN = 'force';
+
+    /**
+     * Symlink locale if it not customized
+     */
+    const SYMLINK_LOCALE = 'symlink-locale';
 }
diff --git a/app/code/Magento/Deploy/Model/Deploy/DeployInterface.php b/app/code/Magento/Deploy/Model/Deploy/DeployInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..16168c2b284c5bb7cd34d878d4448dae21d164d6
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/Deploy/DeployInterface.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model\Deploy;
+
+interface DeployInterface
+{
+    /**
+     * Base locale option without customizations
+     */
+    const DEPLOY_BASE_LOCALE = 'deploy_base_locale';
+
+    /**
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @return int
+     */
+    public function deploy($area, $themePath, $locale);
+}
diff --git a/app/code/Magento/Deploy/Model/Deploy/LocaleDeploy.php b/app/code/Magento/Deploy/Model/Deploy/LocaleDeploy.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa112a700133185cf06980fb6b465de509630371
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/Deploy/LocaleDeploy.php
@@ -0,0 +1,453 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model\Deploy;
+
+use Magento\Framework\App\Utility\Files;
+use Magento\Framework\App\View\Asset\Publisher;
+use Magento\Framework\View\Asset\ContentProcessorException;
+use Magento\Framework\View\Asset\PreProcessor\AlternativeSourceInterface;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Magento\Framework\Config\Theme;
+use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+use Magento\Framework\Translate\Js\Config as JsTranslationConfig;
+use Magento\Framework\View\Asset\Minification;
+use Psr\Log\LoggerInterface;
+use Magento\Framework\Console\Cli;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
+ */
+class LocaleDeploy implements DeployInterface
+{
+    /**
+     * @var int
+     */
+    private $count = 0;
+
+    /**
+     * @var int
+     */
+    private $errorCount = 0;
+
+    /**
+     * @var OutputInterface
+     */
+    private $output;
+
+    /**
+     * @var \Magento\Framework\View\Asset\Repository
+     */
+    private $assetRepo;
+
+    /**
+     * @var Publisher
+     */
+    private $assetPublisher;
+
+    /**
+     * @var \Magento\Framework\View\Asset\Bundle\Manager
+     */
+    private $bundleManager;
+
+    /**
+     * @var Files
+     */
+    private $filesUtil;
+
+    /**
+     * @var ThemeProviderInterface
+     */
+    private $themeProvider;
+
+    /**
+     * @var array
+     */
+    private $options = [];
+
+    /**
+     * @var JsTranslationConfig
+     */
+    private $jsTranslationConfig;
+
+    /**
+     * @var Minification
+     */
+    private $minification;
+
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    /**
+     * @var \Magento\Framework\View\Asset\RepositoryFactory
+     */
+    private $assetRepoFactory;
+
+    /**
+     * @var \Magento\RequireJs\Model\FileManagerFactory
+     */
+    private $fileManagerFactory;
+
+    /**
+     * @var \Magento\Framework\RequireJs\ConfigFactory
+     */
+    private $requireJsConfigFactory;
+
+    /**
+     * @var \Magento\Framework\View\DesignInterfaceFactory
+     */
+    private $designFactory;
+
+    /**
+     * @var \Magento\Framework\Locale\ResolverInterface
+     */
+    private $localeResolver;
+
+    /**
+     * @var \Magento\Framework\View\Asset\PreProcessor\AlternativeSourceInterface[]
+     */
+    private $alternativeSources;
+
+    /**
+     * @var array
+     */
+    private static $fileExtensionOptionMap = [
+        'js' => Options::NO_JAVASCRIPT,
+        'map' => Options::NO_JAVASCRIPT,
+        'css' => Options::NO_CSS,
+        'less' => Options::NO_LESS,
+        'html' => Options::NO_HTML,
+        'htm' => Options::NO_HTML,
+        'jpg' => Options::NO_IMAGES,
+        'jpeg' => Options::NO_IMAGES,
+        'gif' => Options::NO_IMAGES,
+        'png' => Options::NO_IMAGES,
+        'ico' => Options::NO_IMAGES,
+        'svg' => Options::NO_IMAGES,
+        'eot' => Options::NO_FONTS,
+        'ttf' => Options::NO_FONTS,
+        'woff' => Options::NO_FONTS,
+        'woff2' => Options::NO_FONTS,
+        'md' => Options::NO_MISC,
+        'jbf' => Options::NO_MISC,
+        'csv' => Options::NO_MISC,
+        'json' => Options::NO_MISC,
+        'txt' => Options::NO_MISC,
+        'htc' => Options::NO_MISC,
+        'swf' => Options::NO_MISC,
+        'LICENSE' => Options::NO_MISC,
+        '' => Options::NO_MISC,
+    ];
+
+    /**
+     * @param OutputInterface $output
+     * @param JsTranslationConfig $jsTranslationConfig
+     * @param Minification $minification
+     * @param \Magento\Framework\View\Asset\Repository $assetRepo
+     * @param \Magento\Framework\View\Asset\RepositoryFactory $assetRepoFactory
+     * @param \Magento\RequireJs\Model\FileManagerFactory $fileManagerFactory
+     * @param \Magento\Framework\RequireJs\ConfigFactory $requireJsConfigFactory
+     * @param Publisher $assetPublisher
+     * @param \Magento\Framework\View\Asset\Bundle\Manager $bundleManager
+     * @param ThemeProviderInterface $themeProvider
+     * @param LoggerInterface $logger
+     * @param Files $filesUtil
+     * @param \Magento\Framework\View\DesignInterfaceFactory $designFactory
+     * @param \Magento\Framework\Locale\ResolverInterface $localeResolver
+     * @param array $alternativeSources
+     * @param array $options
+     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+     */
+    public function __construct(
+        OutputInterface $output,
+        JsTranslationConfig $jsTranslationConfig,
+        Minification $minification,
+        \Magento\Framework\View\Asset\Repository $assetRepo,
+        \Magento\Framework\View\Asset\RepositoryFactory $assetRepoFactory,
+        \Magento\RequireJs\Model\FileManagerFactory $fileManagerFactory,
+        \Magento\Framework\RequireJs\ConfigFactory $requireJsConfigFactory,
+        \Magento\Framework\App\View\Asset\Publisher $assetPublisher,
+        \Magento\Framework\View\Asset\Bundle\Manager $bundleManager,
+        \Magento\Framework\View\Design\Theme\ThemeProviderInterface $themeProvider,
+        LoggerInterface $logger,
+        Files $filesUtil,
+        \Magento\Framework\View\DesignInterfaceFactory $designFactory,
+        \Magento\Framework\Locale\ResolverInterface $localeResolver,
+        array $alternativeSources,
+        $options = []
+    ) {
+        $this->output = $output;
+        $this->assetRepo = $assetRepo;
+        $this->assetPublisher = $assetPublisher;
+        $this->bundleManager = $bundleManager;
+        $this->filesUtil = $filesUtil;
+        $this->jsTranslationConfig = $jsTranslationConfig;
+        $this->minification = $minification;
+        $this->logger = $logger;
+        $this->assetRepoFactory = $assetRepoFactory;
+        $this->fileManagerFactory = $fileManagerFactory;
+        $this->requireJsConfigFactory = $requireJsConfigFactory;
+        $this->themeProvider = $themeProvider;
+        $this->alternativeSources = array_map(
+            function (AlternativeSourceInterface $alternativeSource) {
+                return $alternativeSource;
+            },
+            $alternativeSources
+        );
+        $this->designFactory = $designFactory;
+        $this->localeResolver = $localeResolver;
+        $this->options = $options;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deploy($area, $themePath, $locale)
+    {
+        $this->output->writeln("=== {$area} -> {$themePath} -> {$locale} ===");
+
+        // emulate application locale needed for correct file path resolving
+        $this->localeResolver->setLocale($locale);
+
+        $this->deployRequireJsConfig($area, $themePath);
+        $this->deployAppFiles($area, $themePath, $locale);
+        $this->deployLibFiles($area, $themePath, $locale);
+
+        if (!$this->getOption(Options::NO_JAVASCRIPT)) {
+            if ($this->jsTranslationConfig->dictionaryEnabled()) {
+                $dictionaryFileName = $this->jsTranslationConfig->getDictionaryFileName();
+                $this->deployFile($dictionaryFileName, $area, $themePath, $locale, null);
+            }
+        }
+        if (!$this->getOption(Options::NO_JAVASCRIPT)) {
+            $this->bundleManager->flush();
+        }
+        $this->output->writeln("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n");
+
+        return $this->errorCount ? Cli::RETURN_FAILURE : Cli::RETURN_SUCCESS;
+    }
+
+    /**
+     * @param string $area
+     * @param string $themePath
+     * @return void
+     */
+    private function deployRequireJsConfig($area, $themePath)
+    {
+        if (!$this->getOption(Options::DRY_RUN) && !$this->getOption(Options::NO_JAVASCRIPT)) {
+            $design = $this->designFactory->create()->setDesignTheme($themePath, $area);
+            $assetRepo = $this->assetRepoFactory->create(['design' => $design]);
+            /** @var \Magento\RequireJs\Model\FileManager $fileManager */
+            $fileManager = $this->fileManagerFactory->create(
+                [
+                    'config' => $this->requireJsConfigFactory->create(
+                        [
+                            'assetRepo' => $assetRepo,
+                            'design' => $design,
+                        ]
+                    ),
+                    'assetRepo' => $assetRepo,
+                ]
+            );
+            $fileManager->createRequireJsConfigAsset();
+            if ($this->minification->isEnabled('js')) {
+                $fileManager->createMinResolverAsset();
+            }
+        }
+    }
+
+    /**
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @return void
+     */
+    private function deployAppFiles($area, $themePath, $locale)
+    {
+        foreach ($this->filesUtil->getStaticPreProcessingFiles() as $info) {
+            list($fileArea, $fileTheme, , $module, $filePath, $fullPath) = $info;
+
+            if ($this->checkSkip($filePath)) {
+                continue;
+            }
+
+            if ($this->isCanBeDeployed($fileArea, $fileTheme, $area, $themePath)) {
+                $compiledFile = $this->deployFile(
+                    $filePath,
+                    $area,
+                    $themePath,
+                    $locale,
+                    $module,
+                    $fullPath
+                );
+                if ($compiledFile !== '' && !$this->checkSkip($compiledFile)) {
+                    $this->deployFile($compiledFile, $area, $themePath, $locale, $module, $fullPath);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param string $fileArea
+     * @param string $fileTheme
+     * @param string $area
+     * @param string $themePath
+     * @return bool
+     */
+    private function isCanBeDeployed($fileArea, $fileTheme, $area, $themePath)
+    {
+        return ($fileArea == $area || $fileArea == 'base')
+        && ($fileTheme == '' || $fileTheme == $themePath
+            || in_array(
+                $fileArea . Theme::THEME_PATH_SEPARATOR . $fileTheme,
+                $this->findAncestors($area . Theme::THEME_PATH_SEPARATOR . $themePath)
+            )
+        );
+    }
+
+    /**
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @return void
+     */
+    private function deployLibFiles($area, $themePath, $locale)
+    {
+        foreach ($this->filesUtil->getStaticLibraryFiles() as $filePath) {
+
+            if ($this->checkSkip($filePath)) {
+                continue;
+            }
+
+            $compiledFile = $this->deployFile($filePath, $area, $themePath, $locale, null);
+
+            if ($compiledFile !== '' && !$this->checkSkip($compiledFile)) {
+                $this->deployFile($compiledFile, $area, $themePath, $locale, null);
+            }
+        }
+    }
+
+    /**
+     * Deploy a static view file
+     *
+     * @param string $filePath
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @param string $module
+     * @param string|null $fullPath
+     * @return string
+     *
+     * @SuppressWarnings(PHPMD.NPathComplexity)
+     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+     */
+    private function deployFile($filePath, $area, $themePath, $locale, $module, $fullPath = null)
+    {
+        $compiledFile = '';
+        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
+
+        foreach ($this->alternativeSources as $name => $alternative) {
+            if (in_array($extension, $alternative->getAlternativesExtensionsNames(), true)
+                && strpos(basename($filePath), '_') !== 0
+            ) {
+                $compiledFile = substr($filePath, 0, strlen($filePath) - strlen($extension) - 1);
+                $compiledFile = $compiledFile . '.' . $name;
+            }
+        }
+
+        if ($this->output->isVeryVerbose()) {
+            $logMessage = "Processing file '$filePath' for area '$area', theme '$themePath', locale '$locale'";
+            if ($module) {
+                $logMessage .= ", module '$module'";
+            }
+            $this->output->writeln($logMessage);
+        }
+
+        try {
+            $asset = $this->assetRepo->createAsset(
+                $filePath,
+                ['area' => $area, 'theme' => $themePath, 'locale' => $locale, 'module' => $module]
+            );
+            if ($this->output->isVeryVerbose()) {
+                $this->output->writeln("\tDeploying the file to '{$asset->getPath()}'");
+            } else {
+                $this->output->write('.');
+            }
+            if ($this->getOption(Options::DRY_RUN)) {
+                $asset->getContent();
+            } else {
+                $this->assetPublisher->publish($asset);
+                if (!$this->getOption(Options::NO_JAVASCRIPT)) {
+                    $this->bundleManager->addAsset($asset);
+                }
+            }
+            $this->count++;
+        } catch (ContentProcessorException $exception) {
+            $pathInfo = $fullPath ?: $filePath;
+            $errorMessage =  __('Compilation from source: ') . $pathInfo . PHP_EOL . $exception->getMessage();
+            $this->errorCount++;
+            $this->output->write(PHP_EOL . PHP_EOL . $errorMessage . PHP_EOL, true);
+
+            $this->logger->critical($errorMessage);
+        } catch (\Exception $exception) {
+            $this->output->write('.');
+            if ($this->output->isVerbose()) {
+                $this->output->writeln($exception->getTraceAsString());
+            }
+            $this->errorCount++;
+        }
+
+        return $compiledFile;
+    }
+
+    /**
+     * @param string $name
+     * @return mixed|null
+     */
+    private function getOption($name)
+    {
+        return isset($this->options[$name]) ? $this->options[$name] : null;
+    }
+
+    /**
+     * Check if skip flag is affecting file by extension
+     *
+     * @param string $filePath
+     * @return boolean
+     */
+    private function checkSkip($filePath)
+    {
+        if ($filePath != '.') {
+            $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
+            $option = isset(self::$fileExtensionOptionMap[$ext]) ? self::$fileExtensionOptionMap[$ext] : null;
+
+            return $option ? $this->getOption($option) : false;
+        }
+
+        return false;
+    }
+
+    /**
+     * Find ancestor themes' full paths
+     *
+     * @param string $themeFullPath
+     * @return string[]
+     */
+    private function findAncestors($themeFullPath)
+    {
+        $theme = $this->themeProvider->getThemeByFullPath($themeFullPath);
+        $ancestors = $theme->getInheritedThemes();
+        $ancestorThemeFullPath = [];
+        foreach ($ancestors as $ancestor) {
+            $ancestorThemeFullPath[] = $ancestor->getFullPath();
+        }
+        return $ancestorThemeFullPath;
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/Deploy/LocaleQuickDeploy.php b/app/code/Magento/Deploy/Model/Deploy/LocaleQuickDeploy.php
new file mode 100644
index 0000000000000000000000000000000000000000..8102afb363024b583068543ed319ade7e1e85965
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/Deploy/LocaleQuickDeploy.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model\Deploy;
+
+use Magento\Deploy\Model\DeployManager;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\App\Utility\Files;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Filesystem\Directory\WriteInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Magento\Framework\Console\Cli;
+use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+use \Magento\Framework\RequireJs\Config as RequireJsConfig;
+
+class LocaleQuickDeploy implements DeployInterface
+{
+    /**
+     * @var Filesystem
+     */
+    private $filesystem;
+
+    /**
+     * @var WriteInterface
+     */
+    private $staticDirectory;
+
+    /**
+     * @var OutputInterface
+     */
+    private $output;
+
+    /**
+     * @var array
+     */
+    private $options = [];
+
+    /**
+     * @param Filesystem $filesystem
+     * @param OutputInterface $output
+     * @param array $options
+     */
+    public function __construct(\Magento\Framework\Filesystem $filesystem, OutputInterface $output, $options = [])
+    {
+        $this->filesystem = $filesystem;
+        $this->output = $output;
+        $this->options = $options;
+    }
+
+    /**
+     * @return WriteInterface
+     */
+    private function getStaticDirectory()
+    {
+        if ($this->staticDirectory === null) {
+            $this->staticDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW);
+        }
+
+        return $this->staticDirectory;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deploy($area, $themePath, $locale)
+    {
+        if (isset($this->options[Options::DRY_RUN]) && $this->options[Options::DRY_RUN]) {
+            return Cli::RETURN_SUCCESS;
+        }
+
+        $this->output->writeln("=== {$area} -> {$themePath} -> {$locale} ===");
+
+        if (!isset($this->options[self::DEPLOY_BASE_LOCALE])) {
+            throw new \InvalidArgumentException('Deploy base locale must be set for Quick Deploy');
+        }
+        $processedFiles = 0;
+        $errorAmount = 0;
+
+        $baseLocale = $this->options[self::DEPLOY_BASE_LOCALE];
+        $newLocalePath = $this->getLocalePath($area, $themePath, $locale);
+        $baseLocalePath = $this->getLocalePath($area, $themePath, $baseLocale);
+        $baseRequireJsPath = RequireJsConfig::DIR_NAME . DIRECTORY_SEPARATOR . $baseLocalePath;
+        $newRequireJsPath = RequireJsConfig::DIR_NAME . DIRECTORY_SEPARATOR . $newLocalePath;
+
+        $this->deleteLocaleResource($newLocalePath);
+        $this->deleteLocaleResource($newRequireJsPath);
+
+        if (isset($this->options[Options::SYMLINK_LOCALE]) && $this->options[Options::SYMLINK_LOCALE]) {
+            $this->getStaticDirectory()->createSymlink($baseLocalePath, $newLocalePath);
+            $this->getStaticDirectory()->createSymlink($baseRequireJsPath, $newRequireJsPath);
+
+            $this->output->writeln("\nSuccessful symlinked\n---\n");
+        } else {
+            $localeFiles = array_merge(
+                $this->getStaticDirectory()->readRecursively($baseLocalePath),
+                $this->getStaticDirectory()->readRecursively($baseRequireJsPath)
+            );
+            foreach ($localeFiles as $path) {
+                if ($this->getStaticDirectory()->isFile($path)) {
+                    $destination = $this->replaceLocaleInPath($path, $baseLocale, $locale);
+                    $this->getStaticDirectory()->copyFile($path, $destination);
+                    $processedFiles++;
+                }
+            }
+
+            $this->output->writeln("\nSuccessful copied: {$processedFiles} files; errors: {$errorAmount}\n---\n");
+        }
+
+        return Cli::RETURN_SUCCESS;
+    }
+
+    /**
+     * @param string $path
+     * @return void
+     */
+    private function deleteLocaleResource($path)
+    {
+        if ($this->getStaticDirectory()->isExist($path)) {
+            $absolutePath = $this->getStaticDirectory()->getAbsolutePath($path);
+            if (is_link($absolutePath)) {
+                $this->getStaticDirectory()->getDriver()->deleteFile($absolutePath);
+            } else {
+                $this->getStaticDirectory()->getDriver()->deleteDirectory($absolutePath);
+            }
+        }
+    }
+
+    /**
+     * @param string $path
+     * @param string $search
+     * @param string $replace
+     * @return string
+     */
+    private function replaceLocaleInPath($path, $search, $replace)
+    {
+        return preg_replace('~' . $search . '~', $replace, $path, 1);
+    }
+
+    /**
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @return string
+     */
+    private function getLocalePath($area, $themePath, $locale)
+    {
+        return $area . DIRECTORY_SEPARATOR . $themePath . DIRECTORY_SEPARATOR . $locale;
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/Deploy/TemplateMinifier.php b/app/code/Magento/Deploy/Model/Deploy/TemplateMinifier.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a5d8f92f3c32d3b9f45f5addb5c4f76894cb931
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/Deploy/TemplateMinifier.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model\Deploy;
+
+use Magento\Framework\View\Template\Html\MinifierInterface;
+use Magento\Framework\App\Utility\Files;
+
+class TemplateMinifier
+{
+    /**
+     * @var Files
+     */
+    private $filesUtils;
+
+    /**
+     * @var MinifierInterface
+     */
+    private $htmlMinifier;
+
+    /**
+     * @param Files $filesUtils
+     * @param MinifierInterface $htmlMinifier
+     */
+    public function __construct(
+        Files $filesUtils,
+        MinifierInterface $htmlMinifier
+    ) {
+        $this->filesUtils = $filesUtils;
+        $this->htmlMinifier = $htmlMinifier;
+    }
+
+    /**
+     * Minify template files
+     * @return int
+     */
+    public function minifyTemplates()
+    {
+        $minified = 0;
+        foreach ($this->filesUtils->getPhtmlFiles(false, false) as $template) {
+            $this->htmlMinifier->minify($template);
+            $minified++;
+        }
+        return $minified;
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/DeployManager.php b/app/code/Magento/Deploy/Model/DeployManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..c54aa38f5a33ab21283537c90c717f3844d08a0e
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/DeployManager.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model;
+
+use Magento\Framework\App\View\Deployment\Version\StorageInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+use Magento\Deploy\Model\Deploy\TemplateMinifier;
+use Magento\Framework\App\State;
+
+class DeployManager
+{
+    /**
+     * @var array
+     */
+    private $packages = [];
+
+    /**
+     * @var OutputInterface
+     */
+    private $output;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    /**
+     * @var StorageInterface
+     */
+    private $versionStorage;
+
+    /**
+     * @var DeployStrategyProviderFactory
+     */
+    private $deployStrategyProviderFactory;
+
+    /**
+     * @var ProcessQueueManagerFactory
+     */
+    private $processQueueManagerFactory;
+
+    /**
+     * @var TemplateMinifier
+     */
+    private $templateMinifier;
+
+    /**
+     * @var bool
+     */
+    private $idDryRun;
+
+    /**
+     * @var State
+     */
+    private $state;
+
+    /**
+     * @param OutputInterface $output
+     * @param StorageInterface $versionStorage
+     * @param DeployStrategyProviderFactory $deployStrategyProviderFactory
+     * @param ProcessQueueManagerFactory $processQueueManagerFactory
+     * @param TemplateMinifier $templateMinifier
+     * @param State $state
+     * @param array $options
+     */
+    public function __construct(
+        OutputInterface $output,
+        StorageInterface $versionStorage,
+        DeployStrategyProviderFactory $deployStrategyProviderFactory,
+        ProcessQueueManagerFactory $processQueueManagerFactory,
+        TemplateMinifier $templateMinifier,
+        State $state,
+        array $options
+    ) {
+        $this->output = $output;
+        $this->options = $options;
+        $this->versionStorage = $versionStorage;
+        $this->deployStrategyProviderFactory = $deployStrategyProviderFactory;
+        $this->processQueueManagerFactory = $processQueueManagerFactory;
+        $this->templateMinifier = $templateMinifier;
+        $this->state = $state;
+        $this->idDryRun = !empty($this->options[Options::DRY_RUN]);
+    }
+
+    /**
+     * Add package tie to area and theme
+     *
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @return void
+     */
+    public function addPack($area, $themePath, $locale)
+    {
+        $this->packages[$area . '-' . $themePath][$locale] = [$area, $themePath];
+    }
+
+    /**
+     * Deploy local packages with chosen deploy strategy
+     * @return int
+     */
+    public function deploy()
+    {
+        if ($this->idDryRun) {
+            $this->output->writeln('Dry run. Nothing will be recorded to the target directory.');
+        }
+
+        /** @var DeployStrategyProvider $strategyProvider */
+        $strategyProvider = $this->deployStrategyProviderFactory->create(
+            ['output' => $this->output, 'options' => $this->options]
+        );
+
+        if ($this->isCanBeParalleled()) {
+            $result = $this->runInParallel($strategyProvider);
+        } else {
+            $result = 0;
+            foreach ($this->packages as $package) {
+                $locales = array_keys($package);
+                list($area, $themePath) = current($package);
+                foreach ($strategyProvider->getDeployStrategies($area, $themePath, $locales) as $locale => $strategy) {
+                    $result |= $this->state->emulateAreaCode(
+                        $area,
+                        [$strategy, 'deploy'],
+                        [$area, $themePath, $locale]
+                    );
+                }
+            }
+        }
+
+        $this->minifyTemplates();
+        $this->saveDeployedVersion();
+
+        return $result;
+    }
+
+    /**
+     * @return void
+     */
+    private function minifyTemplates()
+    {
+        $noHtmlMinify = isset($this->options[Options::NO_HTML_MINIFY]) ? $this->options[Options::NO_HTML_MINIFY] : null;
+        if (!$noHtmlMinify && !$this->idDryRun) {
+            $this->output->writeln('=== Minify templates ===');
+            $minified = $this->templateMinifier->minifyTemplates();
+            $this->output->writeln("\nSuccessful: {$minified} files modified\n---\n");
+        }
+    }
+
+    /**
+     * @param DeployStrategyProvider $strategyProvider
+     * @return int
+     */
+    private function runInParallel(DeployStrategyProvider $strategyProvider)
+    {
+        $processQueueManager = $this->processQueueManagerFactory->create(
+            ['maxProcesses' => $this->getProcessesAmount()]
+        );
+        foreach ($this->packages as $package) {
+            $locales = array_keys($package);
+            list($area, $themePath) = current($package);
+            $baseStrategy = null;
+            $dependentStrategy = [];
+            foreach ($strategyProvider->getDeployStrategies($area, $themePath, $locales) as $locale => $strategy) {
+                $deploymentFunc = function () use ($area, $themePath, $locale, $strategy) {
+                    return $this->state->emulateAreaCode($area, [$strategy, 'deploy'], [$area, $themePath, $locale]);
+                };
+                if (null === $baseStrategy) {
+                    $baseStrategy = $deploymentFunc;
+                } else {
+                    $dependentStrategy[] = $deploymentFunc;
+                }
+
+            }
+            $processQueueManager->addTaskToQueue($baseStrategy, $dependentStrategy);
+        }
+
+        return $processQueueManager->process();
+    }
+
+    /**
+     * @return bool
+     */
+    private function isCanBeParalleled()
+    {
+        return function_exists('pcntl_fork') && $this->getProcessesAmount() > 1;
+    }
+
+    /**
+     * @return int
+     */
+    private function getProcessesAmount()
+    {
+        return isset($this->options[Options::JOBS_AMOUNT]) ? (int)$this->options[Options::JOBS_AMOUNT] : 0;
+    }
+
+    /**
+     * Save version of deployed files
+     * @return void
+     */
+    private function saveDeployedVersion()
+    {
+        if (!$this->idDryRun) {
+            $version = (new \DateTime())->getTimestamp();
+            $this->output->writeln("New version of deployed files: {$version}");
+            $this->versionStorage->save($version);
+        }
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/DeployStrategyFactory.php b/app/code/Magento/Deploy/Model/DeployStrategyFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..884ff5b6284d4dae3a3983e0a08ccfa8b68cf306
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/DeployStrategyFactory.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model;
+
+use Magento\Deploy\Model\Deploy\DeployInterface;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\ObjectManagerInterface;
+
+class DeployStrategyFactory
+{
+    /**
+     * Standard deploy strategy
+     */
+    const DEPLOY_STRATEGY_STANDARD = 'standard';
+
+    /**
+     * Quick deploy strategy
+     */
+    const DEPLOY_STRATEGY_QUICK = 'quick';
+
+    /**
+     * @param ObjectManagerInterface $objectManager
+     */
+    public function __construct(ObjectManagerInterface $objectManager)
+    {
+        $this->objectManager = $objectManager;
+    }
+
+    /**
+     * @param string $type
+     * @param array $arguments
+     * @return DeployInterface
+     * @throws InputException
+     */
+    public function create($type, array $arguments = [])
+    {
+        $strategyMap = [
+            self::DEPLOY_STRATEGY_STANDARD => Deploy\LocaleDeploy::class,
+            self::DEPLOY_STRATEGY_QUICK => Deploy\LocaleQuickDeploy::class,
+        ];
+
+        if (!isset($strategyMap[$type])) {
+            throw new InputException(__('Wrong deploy strategy type: %1', $type));
+        }
+
+        return $this->objectManager->create($strategyMap[$type], $arguments);
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/DeployStrategyProvider.php b/app/code/Magento/Deploy/Model/DeployStrategyProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..9893aa1768f55d16f5ca0119788c54287cc6a16f
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/DeployStrategyProvider.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model;
+
+use Magento\Deploy\Model\Deploy\DeployInterface;
+use Magento\Framework\Component\ComponentRegistrar;
+use Magento\Framework\Module\Dir;
+use Magento\Framework\View\Design\Fallback\Rule\RuleInterface;
+use Magento\Framework\View\DesignInterface;
+use Magento\Framework\View\Design\Fallback\RulePool;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class DeployStrategyProvider
+{
+    /**
+     * @var RulePool
+     */
+    private $rulePool;
+
+    /**
+     * @var RuleInterface
+     */
+    private $fallBackRule;
+
+    /**
+     * @var array
+     */
+    private $moduleDirectories;
+
+    /**
+     * @var DesignInterface
+     */
+    private $design;
+
+    /**
+     * @var OutputInterface
+     */
+    private $output;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    /**
+     * @var DeployStrategyFactory
+     */
+    private $deployStrategyFactory;
+
+    /**
+     * @param OutputInterface $output
+     * @param RulePool $rulePool
+     * @param DesignInterface $design
+     * @param DeployStrategyFactory $deployStrategyFactory
+     * @param array $options
+     */
+    public function __construct(
+        OutputInterface $output,
+        RulePool $rulePool,
+        DesignInterface $design,
+        DeployStrategyFactory $deployStrategyFactory,
+        array $options
+    ) {
+        $this->rulePool = $rulePool;
+        $this->design = $design;
+        $this->output = $output;
+        $this->options = $options;
+        $this->deployStrategyFactory = $deployStrategyFactory;
+    }
+
+    /**
+     * @param string $area
+     * @param string $themePath
+     * @param array $locales
+     * @return DeployInterface[]
+     */
+    public function getDeployStrategies($area, $themePath, array $locales)
+    {
+        if (count($locales) == 1) {
+            $locale = current($locales);
+            return [$locale => $this->getDeployStrategy(DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD)];
+        }
+
+        $baseLocale = null;
+        $deployStrategies = [];
+
+        foreach ($locales as $locale) {
+            $hasCustomization = false;
+            foreach ($this->getCustomizationDirectories($area, $themePath, $locale) as $directory) {
+                if (glob($directory . DIRECTORY_SEPARATOR . '*', GLOB_NOSORT)) {
+                    $hasCustomization = true;
+                    break;
+                }
+            }
+            if ($baseLocale === null && !$hasCustomization) {
+                $baseLocale = $locale;
+            } else {
+                $deployStrategies[$locale] = $hasCustomization
+                    ? DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD
+                    : DeployStrategyFactory::DEPLOY_STRATEGY_QUICK;
+            }
+        }
+        $deployStrategies = array_merge(
+            [$baseLocale => DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD],
+            $deployStrategies
+        );
+
+        return array_map(function ($strategyType) use ($area, $baseLocale) {
+            return $this->getDeployStrategy($strategyType, $baseLocale);
+        }, $deployStrategies);
+    }
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    private function getLocaleDirectories($params)
+    {
+        $dirs = $this->getFallbackRule()->getPatternDirs($params);
+
+        return array_filter($dirs, function ($dir) {
+            return strpos($dir, Dir::MODULE_I18N_DIR);
+        });
+    }
+
+    /**
+     * Get directories which can contains theme customization
+     * @param string $area
+     * @param string $themePath
+     * @param string $locale
+     * @return array
+     */
+    private function getCustomizationDirectories($area, $themePath, $locale)
+    {
+        $customizationDirectories = [];
+        $this->design->setDesignTheme($themePath, $area);
+
+        $params = ['area' => $area, 'theme' => $this->design->getDesignTheme(), 'locale' => $locale];
+        foreach ($this->getLocaleDirectories($params) as $patternDir) {
+            $customizationDirectories[] = $patternDir;
+        }
+
+        if ($this->moduleDirectories === null) {
+            $this->moduleDirectories = [];
+            $componentRegistrar = new ComponentRegistrar();
+            $this->moduleDirectories = array_keys($componentRegistrar->getPaths(ComponentRegistrar::MODULE));
+        }
+
+        foreach ($this->moduleDirectories as $moduleDir) {
+            $params['module_name'] = $moduleDir;
+            $patternDirs = $this->getLocaleDirectories($params);
+            foreach ($patternDirs as $patternDir) {
+                $customizationDirectories[] = $patternDir;
+            }
+        }
+
+        return $customizationDirectories;
+    }
+
+    /**
+     * @return \Magento\Framework\View\Design\Fallback\Rule\RuleInterface
+     */
+    private function getFallbackRule()
+    {
+        if (null === $this->fallBackRule) {
+            $this->fallBackRule = $this->rulePool->getRule(RulePool::TYPE_STATIC_FILE);
+        }
+
+        return $this->fallBackRule;
+    }
+
+    /**
+     * @param string $type
+     * @param null|string $baseLocale
+     * @return DeployInterface
+     */
+    private function getDeployStrategy($type, $baseLocale = null)
+    {
+        $options = $this->options;
+        if ($baseLocale) {
+            $options[DeployInterface::DEPLOY_BASE_LOCALE] = $baseLocale;
+        }
+
+        return $this->deployStrategyFactory->create(
+            $type,
+            ['output' => $this->output, 'options' => $options]
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/Deployer.php b/app/code/Magento/Deploy/Model/Deployer.php
index bef65532fd4238edf75c84ff01f6846b878faf04..9c1781dcb08ad14b40753478e3997c68b3a0b206 100644
--- a/app/code/Magento/Deploy/Model/Deployer.php
+++ b/app/code/Magento/Deploy/Model/Deployer.php
@@ -6,125 +6,41 @@
 
 namespace Magento\Deploy\Model;
 
-use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\View\Asset\ContentProcessorException;
+use Magento\Deploy\Console\Command\DeployStaticOptionsInterface;
 use Magento\Framework\View\Asset\PreProcessor\AlternativeSourceInterface;
 use Magento\Framework\App\ObjectManagerFactory;
 use Magento\Framework\App\View\Deployment\Version;
-use Magento\Framework\App\View\Asset\Publisher;
 use Magento\Framework\App\Utility\Files;
-use Magento\Framework\Config\Theme;
-use Magento\Framework\ObjectManagerInterface;
 use Magento\Framework\Translate\Js\Config as JsTranslationConfig;
 use Symfony\Component\Console\Output\OutputInterface;
-use Psr\Log\LoggerInterface;
-use Magento\Framework\View\Asset\Minification;
 use Magento\Framework\App\ObjectManager;
-use Magento\Framework\View\Asset\ConfigInterface;
-use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+use Magento\Deploy\Model\DeployManagerFactory;
 
 /**
  * A service for deploying Magento static view files for production mode
  *
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @SuppressWarnings(PHPMD.UnusedLocalVariable)
- * @SuppressWarnings(PHPMD.TooManyFields)
+ * @deprecated
+ * @see Use DeployManager::deploy instead
  */
 class Deployer
 {
-    /** @var Files */
-    private $filesUtil;
-
-    /** @var ObjectManagerFactory */
-    private $omFactory;
-
     /** @var OutputInterface */
     private $output;
 
-    /** @var Version\StorageInterface */
-    private $versionStorage;
-
-    /** @var \Magento\Framework\View\Asset\Repository */
-    private $assetRepo;
-
-    /** @var Publisher */
-    private $assetPublisher;
-
-    /** @var \Magento\Framework\View\Asset\Bundle\Manager */
-    private $bundleManager;
-
-    /** @var int */
-    private $count;
-
-    /** @var int */
-    private $errorCount;
-
-    /** @var \Magento\Framework\View\Template\Html\MinifierInterface */
-    private $htmlMinifier;
-
-    /**
-     * @var ObjectManagerInterface
-     */
-    private $objectManager;
-
     /**
      * @var JsTranslationConfig
      */
     protected $jsTranslationConfig;
 
-    /**
-     * @var AlternativeSourceInterface[]
-     */
-    private $alternativeSources;
-
     /**
      * @var array
      */
-    private static $fileExtensionOptionMap = [
-        'js' => Options::NO_JAVASCRIPT,
-        'map' => Options::NO_JAVASCRIPT,
-        'css' => Options::NO_CSS,
-        'less' => Options::NO_LESS,
-        'html' => Options::NO_HTML,
-        'htm' => Options::NO_HTML,
-        'jpg' => Options::NO_IMAGES,
-        'jpeg' => Options::NO_IMAGES,
-        'gif' => Options::NO_IMAGES,
-        'png' => Options::NO_IMAGES,
-        'ico' => Options::NO_IMAGES,
-        'svg' => Options::NO_IMAGES,
-        'eot' => Options::NO_FONTS,
-        'ttf' => Options::NO_FONTS,
-        'woff' => Options::NO_FONTS,
-        'woff2' => Options::NO_FONTS,
-        'md' => Options::NO_MISC,
-        'jbf' => Options::NO_MISC,
-        'csv' => Options::NO_MISC,
-        'json' => Options::NO_MISC,
-        'txt' => Options::NO_MISC,
-        'htc' => Options::NO_MISC,
-        'swf' => Options::NO_MISC,
-        'LICENSE' => Options::NO_MISC,
-        '' => Options::NO_MISC,
-    ];
-
-    /**
-     * @var Minification
-     */
-    private $minification;
-
-    /**
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /** @var ConfigInterface */
-    private $assetConfig;
+    private $options;
 
     /**
-     * @var array
+     * @var DeployManagerFactory
      */
-    private $options;
+    private $deployManagerFactory;
 
     /**
      * Constructor
@@ -134,7 +50,9 @@ class Deployer
      * @param Version\StorageInterface $versionStorage
      * @param JsTranslationConfig $jsTranslationConfig
      * @param AlternativeSourceInterface[] $alternativeSources
+     * @param DeployManagerFactory $deployManagerFactory
      * @param array $options
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function __construct(
         Files $filesUtil,
@@ -142,56 +60,29 @@ class Deployer
         Version\StorageInterface $versionStorage,
         JsTranslationConfig $jsTranslationConfig,
         array $alternativeSources,
+        DeployManagerFactory $deployManagerFactory = null,
         $options = []
     ) {
-        $this->filesUtil = $filesUtil;
         $this->output = $output;
-        $this->versionStorage = $versionStorage;
-        $this->jsTranslationConfig = $jsTranslationConfig;
+        $this->deployManagerFactory = $deployManagerFactory;
         if (is_array($options)) {
             $this->options = $options;
         } else {
             // backward compatibility support
-            $this->options = [Options::DRY_RUN => (bool)$options];
+            $this->options = [DeployStaticOptionsInterface::DRY_RUN => (bool)$options];
         }
-        $this->parentTheme = [];
-
-        array_map(
-            function (AlternativeSourceInterface $alternative) {
-            },
-            $alternativeSources
-        );
-        $this->alternativeSources = $alternativeSources;
-
     }
 
     /**
-     * @param string $name
-     * @return mixed|null
+     * @return \Magento\Deploy\Model\DeployManagerFactory
      */
-    private function getOption($name)
+    private function getDeployManagerFactory()
     {
-        return isset($this->options[$name]) ? $this->options[$name] : null;
-    }
-
-    /**
-     * Check if skip flag is affecting file by extension
-     *
-     * @param string $filePath
-     * @return boolean
-     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
-     * @SuppressWarnings(PHPMD.NPathComplexity)
-     */
-    private function checkSkip($filePath)
-    {
-        if ($filePath != '.') {
-            $ext = pathinfo($filePath, PATHINFO_EXTENSION);
-            $option = isset(self::$fileExtensionOptionMap[$ext]) ? self::$fileExtensionOptionMap[$ext] : null;
-
-            return $option ? $this->getOption($option) : false;
+        if (null === $this->deployManagerFactory) {
+            $this->deployManagerFactory = ObjectManager::getInstance()->get(DeployManagerFactory::class);
         }
 
-        return false;
+        return $this->deployManagerFactory;
     }
 
     /**
@@ -201,170 +92,24 @@ class Deployer
      * @param array $locales
      * @param array $deployableAreaThemeMap
      * @return int
-     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
-     * @SuppressWarnings(PHPMD.NPathComplexity)
-     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     * @deprecated
      */
     public function deploy(ObjectManagerFactory $omFactory, array $locales, array $deployableAreaThemeMap = [])
     {
-        $this->omFactory = $omFactory;
-
-        if ($this->getOption(Options::DRY_RUN)) {
-            $this->output->writeln('Dry run. Nothing will be recorded to the target directory.');
-        }
-        $libFiles = $this->filesUtil->getStaticLibraryFiles();
-        $appFiles = $this->filesUtil->getStaticPreProcessingFiles();
+        /** @var DeployManager $deployerManager */
+        $deployerManager = $this->getDeployManagerFactory()->create(
+            ['options' => $this->options, 'output' => $this->output]
+        );
 
         foreach ($deployableAreaThemeMap as $area => $themes) {
-            $this->emulateApplicationArea($area);
             foreach ($locales as $locale) {
-                $this->emulateApplicationLocale($locale, $area);
                 foreach ($themes as $themePath) {
-
-                    $this->output->writeln("=== {$area} -> {$themePath} -> {$locale} ===");
-                    $this->count = 0;
-                    $this->errorCount = 0;
-
-                    /** @var \Magento\Theme\Model\View\Design $design */
-                    $design = $this->objectManager->create(\Magento\Theme\Model\View\Design::class);
-                    $design->setDesignTheme($themePath, $area);
-
-                    $assetRepo = $this->objectManager->create(
-                        \Magento\Framework\View\Asset\Repository::class,
-                        [
-                            'design' => $design,
-                        ]
-                    );
-                    /** @var \Magento\RequireJs\Model\FileManager $fileManager */
-                    $fileManager = $this->objectManager->create(
-                        \Magento\RequireJs\Model\FileManager::class,
-                        [
-                            'config' => $this->objectManager->create(
-                                \Magento\Framework\RequireJs\Config::class,
-                                [
-                                    'assetRepo' => $assetRepo,
-                                    'design' => $design,
-                                ]
-                            ),
-                            'assetRepo' => $assetRepo,
-                        ]
-                    );
-                    $fileManager->createRequireJsConfigAsset();
-
-                    foreach ($appFiles as $info) {
-                        list($fileArea, $fileTheme, , $module, $filePath, $fullPath) = $info;
-
-                        if ($this->checkSkip($filePath)) {
-                            continue;
-                        }
-
-                        if (($fileArea == $area || $fileArea == 'base') &&
-                            ($fileTheme == '' || $fileTheme == $themePath ||
-                                in_array(
-                                    $fileArea . Theme::THEME_PATH_SEPARATOR . $fileTheme,
-                                    $this->findAncestors($area . Theme::THEME_PATH_SEPARATOR . $themePath)
-                                ))
-                        ) {
-                            $compiledFile = $this->deployFile(
-                                $filePath,
-                                $area,
-                                $themePath,
-                                $locale,
-                                $module,
-                                $fullPath
-                            );
-                            if ($compiledFile !== '') {
-                                $this->deployFile($compiledFile, $area, $themePath, $locale, $module, $fullPath);
-                            }
-                        }
-                    }
-                    foreach ($libFiles as $filePath) {
-
-                        if ($this->checkSkip($filePath)) {
-                            continue;
-                        }
-
-                        $compiledFile = $this->deployFile($filePath, $area, $themePath, $locale, null);
-
-                        if ($compiledFile !== '') {
-                            $this->deployFile($compiledFile, $area, $themePath, $locale, null);
-                        }
-                    }
-                    if (!$this->getOption(Options::NO_JAVASCRIPT)) {
-                        if ($this->jsTranslationConfig->dictionaryEnabled()) {
-                            $dictionaryFileName = $this->jsTranslationConfig->getDictionaryFileName();
-                            $this->deployFile($dictionaryFileName, $area, $themePath, $locale, null);
-                        }
-                        if ($this->getMinification()->isEnabled('js')) {
-                            $fileManager->createMinResolverAsset();
-                        }
-                    }
-                    $this->bundleManager->flush();
-                    $this->output->writeln("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n");
+                    $deployerManager->addPack($area, $themePath, $locale);
                 }
             }
         }
-        if (!($this->getOption(Options::NO_HTML_MINIFY) ?: !$this->getAssetConfig()->isMinifyHtml())) {
-            $this->output->writeln('=== Minify templates ===');
-            $this->count = 0;
-            foreach ($this->filesUtil->getPhtmlFiles(false, false) as $template) {
-                $this->htmlMinifier->minify($template);
-                if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
-                    $this->output->writeln($template . " minified\n");
-                } else {
-                    $this->output->write('.');
-                }
-                $this->count++;
-            }
-            $this->output->writeln("\nSuccessful: {$this->count} files modified\n---\n");
-        }
-
-        $version = (new \DateTime())->getTimestamp();
-        $this->output->writeln("New version of deployed files: {$version}");
-        if (!$this->getOption(Options::DRY_RUN)) {
-            $this->versionStorage->save($version);
-        }
-
-        if ($this->errorCount > 0) {
-            // we must have an exit code higher than zero to indicate something was wrong
-            return \Magento\Framework\Console\Cli::RETURN_FAILURE;
-        }
-        return \Magento\Framework\Console\Cli::RETURN_SUCCESS;
-    }
-
-    /**
-     * Get Minification instance
-     *
-     * @deprecated
-     * @return Minification
-     */
-    private function getMinification()
-    {
-        if (null === $this->minification) {
-            $this->minification = ObjectManager::getInstance()->get(Minification::class);
-        }
-
-        return $this->minification;
-    }
-
-    /**
-     * Emulate application area and various services that are necessary for populating files
-     *
-     * @param string $areaCode
-     * @return void
-     */
-    private function emulateApplicationArea($areaCode)
-    {
-        $this->objectManager = $this->omFactory->create(
-            [\Magento\Framework\App\State::PARAM_MODE => \Magento\Framework\App\State::MODE_PRODUCTION]
-        );
-        /** @var \Magento\Framework\App\State $appState */
-        $appState = $this->objectManager->get(\Magento\Framework\App\State::class);
-        $appState->setAreaCode($areaCode);
-        $this->assetRepo = $this->objectManager->get(\Magento\Framework\View\Asset\Repository::class);
-        $this->assetPublisher = $this->objectManager->create(\Magento\Framework\App\View\Asset\Publisher::class);
-        $this->htmlMinifier = $this->objectManager->get(\Magento\Framework\View\Template\Html\MinifierInterface::class);
-        $this->bundleManager = $this->objectManager->get(\Magento\Framework\View\Asset\Bundle\Manager::class);
+        return $deployerManager->deploy();
     }
 
     /**
@@ -373,146 +118,10 @@ class Deployer
      * @param string $locale
      * @param string $area
      * @return void
-     */
-    protected function emulateApplicationLocale($locale, $area)
-    {
-        /** @var \Magento\Framework\TranslateInterface $translator */
-        $translator = $this->objectManager->get(\Magento\Framework\TranslateInterface::class);
-        $translator->setLocale($locale);
-        $translator->loadData($area, true);
-        /** @var \Magento\Framework\Locale\ResolverInterface $localeResolver */
-        $localeResolver = $this->objectManager->get(\Magento\Framework\Locale\ResolverInterface::class);
-        $localeResolver->setLocale($locale);
-    }
-
-    /**
-     * Deploy a static view file
-     *
-     * @param string $filePath
-     * @param string $area
-     * @param string $themePath
-     * @param string $locale
-     * @param string $module
-     * @param string|null $fullPath
-     * @return string
-     * @throws \InvalidArgumentException
-     * @throws LocalizedException
-     *
-     * @SuppressWarnings(PHPMD.NPathComplexity)
-     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
-     */
-    private function deployFile($filePath, $area, $themePath, $locale, $module, $fullPath = null)
-    {
-        $compiledFile = '';
-        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
-
-        foreach ($this->alternativeSources as $name => $alternative) {
-            if (in_array($extension, $alternative->getAlternativesExtensionsNames(), true)
-                && strpos(basename($filePath), '_') !== 0
-            ) {
-                $compiledFile = substr($filePath, 0, strlen($filePath) - strlen($extension) - 1);
-                $compiledFile = $compiledFile . '.' . $name;
-            }
-        }
-
-        if ($this->output->isVeryVerbose()) {
-            $logMessage = "Processing file '$filePath' for area '$area', theme '$themePath', locale '$locale'";
-            if ($module) {
-                $logMessage .= ", module '$module'";
-            }
-            $this->output->writeln($logMessage);
-        }
-
-        try {
-            $asset = $this->assetRepo->createAsset(
-                $filePath,
-                ['area' => $area, 'theme' => $themePath, 'locale' => $locale, 'module' => $module]
-            );
-            if ($this->output->isVeryVerbose()) {
-                $this->output->writeln("\tDeploying the file to '{$asset->getPath()}'");
-            } else {
-                $this->output->write('.');
-            }
-            if ($this->getOption(Options::DRY_RUN)) {
-                $asset->getContent();
-            } else {
-                $this->assetPublisher->publish($asset);
-                $this->bundleManager->addAsset($asset);
-            }
-            $this->count++;
-        } catch (ContentProcessorException $exception) {
-            $pathInfo = $fullPath ?: $filePath;
-            $errorMessage =  __('Compilation from source: ') . $pathInfo
-                . PHP_EOL . $exception->getMessage();
-            $this->errorCount++;
-            $this->output->write(PHP_EOL . PHP_EOL . $errorMessage . PHP_EOL, true);
-
-            $this->getLogger()->critical($errorMessage);
-        } catch (\Exception $exception) {
-            $this->output->write('.');
-            $this->verboseLog($exception->getTraceAsString());
-            $this->errorCount++;
-        }
-
-        return $compiledFile;
-    }
-
-    /**
-     * Find ancestor themes' full paths
-     *
-     * @param string $themeFullPath
-     * @return string[]
-     */
-    private function findAncestors($themeFullPath)
-    {
-        /** @var \Magento\Framework\View\Design\Theme\ListInterface $themeCollection */
-        $themeCollection = $this->objectManager->get(\Magento\Framework\View\Design\Theme\ListInterface::class);
-        $theme = $themeCollection->getThemeByFullPath($themeFullPath);
-        $ancestors = $theme->getInheritedThemes();
-        $ancestorThemeFullPath = [];
-        foreach ($ancestors as $ancestor) {
-            $ancestorThemeFullPath[] = $ancestor->getFullPath();
-        }
-        return $ancestorThemeFullPath;
-    }
-
-    /**
-     * @return \Magento\Framework\View\Asset\ConfigInterface
-     * @deprecated
-     */
-    private function getAssetConfig()
-    {
-        if (null === $this->assetConfig) {
-            $this->assetConfig = ObjectManager::getInstance()->get(ConfigInterface::class);
-        }
-        return $this->assetConfig;
-    }
-
-    /**
-     * Verbose log
-     *
-     * @param string $message
-     * @return void
-     */
-    private function verboseLog($message)
-    {
-        if ($this->output->isVerbose()) {
-            $this->output->writeln($message);
-        }
-    }
-
-    /**
-     * Retrieves LoggerInterface instance
-     *
-     * @return LoggerInterface
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      * @deprecated
      */
-    private function getLogger()
+    protected function emulateApplicationLocale($locale, $area)
     {
-        if (!$this->logger) {
-            $this->logger = $this->objectManager->get(LoggerInterface::class);
-        }
-
-        return $this->logger;
     }
 }
diff --git a/app/code/Magento/Deploy/Model/ProcessManager.php b/app/code/Magento/Deploy/Model/ProcessManager.php
index cff7e347ade9d9133e6d6cacb837118dd7178c23..9490f6dc67101eb63a14e8b6123e11b11008feeb 100644
--- a/app/code/Magento/Deploy/Model/ProcessManager.php
+++ b/app/code/Magento/Deploy/Model/ProcessManager.php
@@ -11,6 +11,20 @@ class ProcessManager
     /** @var Process[] */
     private $processes = [];
 
+    /**
+     * @var ProcessFactory
+     */
+    private $processFactory;
+
+    /**
+     * ProcessManager constructor.
+     * @param ProcessFactory $processFactory
+     */
+    public function __construct(ProcessFactory $processFactory)
+    {
+        $this->processFactory = $processFactory;
+    }
+
     /**
      * Forks the currently running process.
      *
@@ -66,7 +80,7 @@ class ProcessManager
      */
     private function createProcess(callable $handler)
     {
-        return new Process($handler);
+        return $this->processFactory->create(['handler' => $handler]);
     }
 
     /**
diff --git a/app/code/Magento/Deploy/Model/ProcessQueueManager.php b/app/code/Magento/Deploy/Model/ProcessQueueManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..c96a2dbb30bd0c169dee84ee9bd358e6dac49228
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/ProcessQueueManager.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model;
+
+use Magento\Framework\App\ResourceConnection;
+
+class ProcessQueueManager
+{
+    /**
+     * Default max amount of processes
+     */
+    const DEFAULT_MAX_PROCESSES_AMOUNT = 4;
+
+    /**
+     * @var ProcessTask[]
+     */
+    private $tasksQueue = [];
+
+    /**
+     * @var ProcessTask[]
+     */
+    private $processTaskMap = [];
+
+    /**
+     * @var int
+     */
+    private $maxProcesses;
+
+    /**
+     * @var ProcessManager
+     */
+    private $processManager;
+
+    /**
+     * @var ResourceConnection
+     */
+    private $resourceConnection;
+
+    /**
+     * @var ProcessTaskFactory
+     */
+    private $processTaskFactory;
+
+    /**
+     * @param ProcessManager $processManager
+     * @param ResourceConnection $resourceConnection
+     * @param ProcessTaskFactory $processTaskFactory
+     * @param int $maxProcesses
+     */
+    public function __construct(
+        ProcessManager $processManager,
+        ResourceConnection $resourceConnection,
+        ProcessTaskFactory $processTaskFactory,
+        $maxProcesses = self::DEFAULT_MAX_PROCESSES_AMOUNT
+    ) {
+        $this->processManager = $processManager;
+        $this->resourceConnection = $resourceConnection;
+        $this->processTaskFactory = $processTaskFactory;
+        $this->maxProcesses = $maxProcesses;
+    }
+
+    /**
+     * @param callable $task
+     * @param callable[] $dependentTasks
+     * @return void
+     */
+    public function addTaskToQueue(callable $task, $dependentTasks = [])
+    {
+        $dependentTasks = array_map(function (callable $task) {
+            return $this->createTask($task);
+        }, $dependentTasks);
+
+        $task = $this->createTask($task, $dependentTasks);
+        $this->tasksQueue[$task->getId()] = $task;
+    }
+
+    /**
+     * Process tasks queue
+     * @return int
+     */
+    public function process()
+    {
+        $processQueue = [];
+        $this->internalQueueProcess($this->tasksQueue, $processQueue);
+
+        $returnStatus = 0;
+        while (count($this->processManager->getProcesses()) > 0) {
+            foreach ($this->processManager->getProcesses() as $process) {
+                if ($process->isCompleted()) {
+                    $dependedTasks = isset($this->processTaskMap[$process->getPid()])
+                        ? $this->processTaskMap[$process->getPid()]
+                        : [];
+
+                    $this->processManager->delete($process);
+                    $returnStatus |= $process->getStatus();
+
+                    $this->internalQueueProcess(array_merge($processQueue, $dependedTasks), $processQueue);
+
+                    if (count($this->processManager->getProcesses()) >= $this->maxProcesses) {
+                        break 1;
+                    }
+                }
+            }
+            usleep(5000);
+        }
+        $this->resourceConnection->closeConnection();
+
+        return $returnStatus;
+    }
+
+    /**
+     * @param ProcessTask[] $taskQueue
+     * @param ProcessTask[] $processQueue
+     * @return void
+     */
+    private function internalQueueProcess($taskQueue, &$processQueue)
+    {
+        $processNumber = count($this->processManager->getProcesses());
+        foreach ($taskQueue as $task) {
+            if ($processNumber >= $this->maxProcesses) {
+                if (!isset($processQueue[$task->getId()])) {
+                    $processQueue[$task->getId()] = $task;
+                }
+            } else {
+                unset($processQueue[$task->getId()]);
+                $this->fork($task);
+                $processNumber++;
+            }
+        }
+    }
+
+    /**
+     * @param callable $handler
+     * @param array $dependentTasks
+     * @return ProcessTask
+     */
+    private function createTask($handler, $dependentTasks = [])
+    {
+        return $this->processTaskFactory->create(['handler' => $handler, 'dependentTasks' => $dependentTasks]);
+    }
+
+    /**
+     * @param ProcessTask $task
+     * @return void
+     */
+    private function fork(ProcessTask $task)
+    {
+        $process = $this->processManager->fork($task->getHandler());
+        if ($task->getDependentTasks()) {
+            $pid = $process->getPid();
+            foreach ($task->getDependentTasks() as $dependentTask) {
+                $this->processTaskMap[$pid][$dependentTask->getId()] = $dependentTask;
+            }
+        }
+    }
+}
diff --git a/app/code/Magento/Deploy/Model/ProcessTask.php b/app/code/Magento/Deploy/Model/ProcessTask.php
new file mode 100644
index 0000000000000000000000000000000000000000..8db7439c3d6f0be14b98ce937f33e8e01a76c85a
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/ProcessTask.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Deploy\Model;
+
+class ProcessTask
+{
+    /**
+     * @var string
+     */
+    private $taskId;
+
+    /**
+     * @var callable
+     */
+    private $handler;
+
+    /**
+     * @var array
+     */
+    private $dependentTasks;
+
+    /**
+     * @param callable $handler
+     * @param array $dependentTasks
+     */
+    public function __construct($handler, array $dependentTasks = [])
+    {
+        $this->taskId = uniqid('', true);
+        $this->handler = $handler;
+        $this->dependentTasks = $dependentTasks;
+    }
+
+    /**
+     * @return callable
+     */
+    public function getHandler()
+    {
+        return $this->handler;
+    }
+
+    /**
+     * @return string
+     */
+    public function getId()
+    {
+        return $this->taskId;
+    }
+
+    /**
+     * @return ProcessTask[]
+     */
+    public function getDependentTasks()
+    {
+        return $this->dependentTasks;
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php
index 24ccf34bb894ceaf802b0031a461db612920d28c..c8fa2138e7c81fb69d562fb5ec55e049cb5deabc 100644
--- a/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php
+++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php
@@ -16,7 +16,7 @@ require 'FunctionExistMock.php';
 class DeployStaticContentCommandTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \Magento\Deploy\Model\Deployer|\PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Deploy\Model\DeployManager|\PHPUnit_Framework_MockObject_MockObject
      */
     private $deployer;
 
@@ -60,7 +60,7 @@ class DeployStaticContentCommandTest extends \PHPUnit_Framework_TestCase
             '',
             false
         );
-        $this->deployer = $this->getMock(\Magento\Deploy\Model\Deployer::class, [], [], '', false);
+        $this->deployer = $this->getMock(\Magento\Deploy\Model\DeployManager::class, [], [], '', false);
         $this->filesUtil = $this->getMock(\Magento\Framework\App\Utility\Files::class, [], [], '', false);
         $this->appState = $this->getMock(\Magento\Framework\App\State::class, [], [], '', false);
 
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleDeployTest.php b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleDeployTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..757da133ddbc3df30eac5a590c1c8dbf00fd002a
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleDeployTest.php
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model\Deploy;
+
+use Magento\Framework\App\Utility\Files;
+use Magento\Framework\App\View\Asset\Publisher;
+use Magento\Framework\Translate\Js\Config;
+use Magento\Framework\View\Asset\Minification;
+use Magento\Framework\View\Asset\Repository;
+use Magento\Framework\View\Asset\RepositoryFactory;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class LocaleDeployTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|Config
+     */
+    private $jsTranslationMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|Minification
+     */
+    private $minificationMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|RepositoryFactory
+     */
+    private $assetRepoFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\RequireJs\Model\FileManagerFactory
+     */
+    private $fileManagerFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\RequireJs\ConfigFactory
+     */
+    private $configFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\Asset\Bundle\Manager
+     */
+    private $bundleManagerMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|Files
+     */
+    private $filesUtilMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\DesignInterfaceFactory
+     */
+    private $designFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Locale\ResolverInterface
+     */
+    private $localeResolverMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|OutputInterface
+     */
+    private $outputMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|LoggerInterface
+     */
+    private $loggerMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $assetRepoMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $assetPublisherMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $themeProviderMock;
+
+    protected function setUp()
+    {
+        $this->outputMock = $this->getMock(OutputInterface::class, [], [], '', false);
+        $this->loggerMock = $this->getMock(LoggerInterface::class, [], [], '', false);
+        $this->filesUtilMock = $this->getMock(Files::class, [], [], '', false);
+        $this->assetRepoMock = $this->getMock(Repository::class, [], [], '', false);
+        $this->minificationMock = $this->getMock(Minification::class, [], [], '', false);
+        $this->jsTranslationMock = $this->getMock(Config::class, [], [], '', false);
+        $this->assetPublisherMock = $this->getMock(Publisher::class, [], [], '', false);
+        $this->assetRepoFactoryMock = $this->getMock(
+            RepositoryFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->fileManagerFactoryMock = $this->getMock(
+            \Magento\RequireJs\Model\FileManagerFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->configFactoryMock = $this->getMock(
+            \Magento\Framework\RequireJs\ConfigFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->bundleManagerMock = $this->getMock(
+            \Magento\Framework\View\Asset\Bundle\Manager::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->themeProviderMock = $this->getMock(
+            \Magento\Framework\View\Design\Theme\ThemeProviderInterface::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->designFactoryMock = $this->getMock(
+            \Magento\Framework\View\DesignInterfaceFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->localeResolverMock = $this->getMock(
+            \Magento\Framework\Locale\ResolverInterface::class,
+            [],
+            [],
+            '',
+            false
+        );
+    }
+
+    public function testDeploy()
+    {
+        $area = 'adminhtml';
+        $themePath = '/theme/path';
+        $locale = 'en_US';
+
+        $designMock = $this->getMock(\Magento\Framework\View\DesignInterface::class, [], [], '', false);
+        $assetRepoMock = $this->getMock(Repository::class, [], [], '', false);
+        $requireJsConfigMock = $this->getMock(\Magento\Framework\RequireJs\Config::class, [], [], '', false);
+        $fileManagerMock = $this->getMock(\Magento\RequireJs\Model\FileManager::class, [], [], '', false);
+
+        $model = $this->getModel([\Magento\Deploy\Console\Command\DeployStaticOptionsInterface::NO_JAVASCRIPT => 0]);
+
+        $this->localeResolverMock->expects($this->once())->method('setLocale')->with($locale);
+        $this->designFactoryMock->expects($this->once())->method('create')->willReturn($designMock);
+        $designMock->expects($this->once())->method('setDesignTheme')->with($themePath, $area)->willReturnSelf();
+        $this->assetRepoFactoryMock->expects($this->once())->method('create')->with(['design' => $designMock])
+            ->willReturn($assetRepoMock);
+        $this->configFactoryMock->expects($this->once())->method('create')->willReturn($requireJsConfigMock);
+        $this->fileManagerFactoryMock->expects($this->once())->method('create')->willReturn($fileManagerMock);
+
+        $fileManagerMock->expects($this->once())->method('createRequireJsConfigAsset')->willReturnSelf();
+        $this->filesUtilMock->expects($this->once())->method('getStaticPreProcessingFiles')->willReturn([]);
+        $this->filesUtilMock->expects($this->once())->method('getStaticLibraryFiles')->willReturn([]);
+
+        $this->jsTranslationMock->expects($this->once())->method('dictionaryEnabled')->willReturn(false);
+        $this->minificationMock->expects($this->once())->method('isEnabled')->with('js')->willReturn(true);
+        $fileManagerMock->expects($this->once())->method('createMinResolverAsset')->willReturnSelf();
+
+        $this->bundleManagerMock->expects($this->once())->method('flush');
+
+        $this->assertEquals(
+            \Magento\Framework\Console\Cli::RETURN_SUCCESS,
+            $model->deploy($area, $themePath, $locale)
+        );
+    }
+
+    /**
+     * @param array $options
+     * @return \Magento\Deploy\Model\Deploy\LocaleDeploy
+     */
+    private function getModel($options = [])
+    {
+        return new \Magento\Deploy\Model\Deploy\LocaleDeploy(
+            $this->outputMock,
+            $this->jsTranslationMock,
+            $this->minificationMock,
+            $this->assetRepoMock,
+            $this->assetRepoFactoryMock,
+            $this->fileManagerFactoryMock,
+            $this->configFactoryMock,
+            $this->assetPublisherMock,
+            $this->bundleManagerMock,
+            $this->themeProviderMock,
+            $this->loggerMock,
+            $this->filesUtilMock,
+            $this->designFactoryMock,
+            $this->localeResolverMock,
+            [],
+            $options
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleQuickDeployTest.php b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleQuickDeployTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c7d459964b7d028e081b85440a58b991acb5661
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleQuickDeployTest.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model\Deploy;
+
+use Magento\Deploy\Model\Deploy\DeployInterface;
+use Magento\Deploy\Model\Deploy\LocaleQuickDeploy;
+use Magento\Framework\Filesystem\Directory\WriteInterface;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Symfony\Component\Console\Output\OutputInterface;
+use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+use \Magento\Framework\RequireJs\Config as RequireJsConfig;
+
+class LocaleQuickDeployTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var OutputInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $outputMock;
+
+    /**
+     * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $staticDirectoryMock;
+
+    protected function setUp()
+    {
+        $this->outputMock = $this->getMockBuilder(OutputInterface::class)
+            ->setMethods(['writeln'])
+            ->getMockForAbstractClass();
+
+        $this->staticDirectoryMock = $this->getMockBuilder(WriteInterface::class)
+            ->setMethods(['createSymlink', 'getAbsolutePath', 'getRelativePath', 'copyFile', 'readRecursively'])
+            ->getMockForAbstractClass();
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage Deploy base locale must be set for Quick Deploy
+     */
+    public function testDeployWithoutBaseLocale()
+    {
+        $this->getModel()->deploy('adminhtml', 'Magento/backend', 'en_US');
+    }
+
+    public function testDeployWithSymlinkStrategy()
+    {
+        $area = 'adminhtml';
+        $themePath = 'Magento/backend';
+        $locale = 'uk_UA';
+        $baseLocal = 'en_US';
+
+        $this->staticDirectoryMock->expects(self::exactly(2))
+            ->method('createSymlink')
+            ->withConsecutive(
+                ['adminhtml/Magento/backend/en_US', 'adminhtml/Magento/backend/uk_UA'],
+                ['_requirejs/adminhtml/Magento/backend/en_US', '_requirejs/adminhtml/Magento/backend/uk_UA']
+            );
+
+        $model = $this->getModel([
+            DeployInterface::DEPLOY_BASE_LOCALE => $baseLocal,
+            Options::SYMLINK_LOCALE => 1,
+        ]);
+        $model->deploy($area, $themePath, $locale);
+    }
+
+    public function testDeployWithCopyStrategy()
+    {
+
+        $area = 'adminhtml';
+        $themePath = 'Magento/backend';
+        $locale = 'uk_UA';
+        $baseLocal = 'en_US';
+
+        $this->staticDirectoryMock->expects(self::never())->method('createSymlink');
+        $this->staticDirectoryMock->expects(self::exactly(2))->method('readRecursively')->willReturnMap([
+            ['adminhtml/Magento/backend/en_US', [$baseLocal . 'file1', $baseLocal . 'dir']],
+            [RequireJsConfig::DIR_NAME  . '/adminhtml/Magento/backend/en_US', [$baseLocal . 'file2']]
+        ]);
+        $this->staticDirectoryMock->expects(self::exactly(3))->method('isFile')->willReturnMap([
+            [$baseLocal . 'file1', true],
+            [$baseLocal . 'dir', false],
+            [$baseLocal . 'file2', true],
+        ]);
+        $this->staticDirectoryMock->expects(self::exactly(2))->method('copyFile')->withConsecutive(
+            [$baseLocal . 'file1', $locale . 'file1', null],
+            [$baseLocal . 'file2', $locale . 'file2', null]
+        );
+
+        $model = $this->getModel([
+            DeployInterface::DEPLOY_BASE_LOCALE => $baseLocal,
+            Options::SYMLINK_LOCALE => 0,
+        ]);
+        $model->deploy($area, $themePath, $locale);
+    }
+
+    /**
+     * @param array $options
+     * @return LocaleQuickDeploy
+     */
+    private function getModel($options = [])
+    {
+        return (new ObjectManager($this))->getObject(
+            LocaleQuickDeploy::class,
+            [
+                'output' => $this->outputMock,
+                'staticDirectory' => $this->staticDirectoryMock,
+                'options' => $options
+            ]
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/Deploy/TemplateMinifierTest.php b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/TemplateMinifierTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e6859faeca88a34692cf8b8c71f2532757207147
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/TemplateMinifierTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model\Deploy;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class TemplateMinifierTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Deploy\Model\Deploy\TemplateMinifier
+     */
+    private $model;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\Utility\Files
+     */
+    private $filesUtilsMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\Template\Html\MinifierInterface
+     */
+    private $minifierMock;
+
+    protected function setUp()
+    {
+        $this->minifierMock = $this->getMock(
+            \Magento\Framework\View\Template\Html\MinifierInterface::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->filesUtilsMock = $this->getMock(\Magento\Framework\App\Utility\Files::class, [], [], '', false);
+
+        $this->model = new \Magento\Deploy\Model\Deploy\TemplateMinifier(
+            $this->filesUtilsMock,
+            $this->minifierMock
+        );
+    }
+
+    public function testMinifyTemplates()
+    {
+        $templateMock = "template.phtml";
+        $templatesMock = [$templateMock];
+
+        $this->filesUtilsMock->expects($this->once())->method('getPhtmlFiles')->with(false, false)
+            ->willReturn($templatesMock);
+        $this->minifierMock->expects($this->once())->method('minify')->with($templateMock);
+
+        self::assertEquals(1, $this->model->minifyTemplates());
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeployManagerTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeployManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9a9f6049f0210ca3ad19a3ced6cc042b178e312
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/DeployManagerTest.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model;
+
+use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class DeployManagerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\DeployStrategyProviderFactory
+     */
+    private $deployStrategyProviderFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Console\Output\OutputInterface
+     */
+    private $outputMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\View\Deployment\Version\StorageInterface
+     */
+    private $versionStorageMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\Deploy\TemplateMinifier
+     */
+    private $minifierTemplateMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\ProcessQueueManagerFactory
+     */
+    private $processQueueManagerFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\State
+     */
+    private $stateMock;
+
+    protected function setUp()
+    {
+        $this->deployStrategyProviderFactoryMock = $this->getMock(
+            \Magento\Deploy\Model\DeployStrategyProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->versionStorageMock = $this->getMock(
+            \Magento\Framework\App\View\Deployment\Version\StorageInterface::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->minifierTemplateMock = $this->getMock(
+            \Magento\Deploy\Model\Deploy\TemplateMinifier::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->processQueueManagerFactoryMock = $this->getMock(
+            \Magento\Deploy\Model\ProcessQueueManagerFactory::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->stateMock = $this->getMockBuilder(\Magento\Framework\App\State::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->outputMock = $this->getMock(\Symfony\Component\Console\Output\OutputInterface::class, [], [], '', false);
+    }
+
+    public function testSaveDeployedVersion()
+    {
+        $version = (new \DateTime())->getTimestamp();
+        $this->outputMock->expects($this->once())->method('writeln')->with("New version of deployed files: {$version}");
+        $this->versionStorageMock->expects($this->once())->method('save')->with($version);
+
+        $this->assertEquals(
+            \Magento\Framework\Console\Cli::RETURN_SUCCESS,
+            $this->getModel([Options::NO_HTML_MINIFY => true])->deploy()
+        );
+    }
+
+    public function testSaveDeployedVersionDryRun()
+    {
+        $options = [Options::DRY_RUN => true, Options::NO_HTML_MINIFY => true];
+
+        $this->outputMock->expects(self::once())->method('writeln')->with(
+            'Dry run. Nothing will be recorded to the target directory.'
+        );
+        $this->versionStorageMock->expects($this->never())->method('save');
+
+        $this->getModel($options)->deploy();
+    }
+
+    public function testMinifyTemplates()
+    {
+        $this->minifierTemplateMock->expects($this->once())->method('minifyTemplates')->willReturn(2);
+        $this->outputMock->expects($this->atLeastOnce())->method('writeln')->withConsecutive(
+            ["=== Minify templates ==="],
+            ["\nSuccessful: 2 files modified\n---\n"]
+        );
+
+        $this->getModel([Options::NO_HTML_MINIFY => false])->deploy();
+    }
+
+    public function testMinifyTemplatesNoHtmlMinify()
+    {
+        $version = (new \DateTime())->getTimestamp();
+        $this->outputMock->expects($this->once())->method('writeln')->with("New version of deployed files: {$version}");
+        $this->versionStorageMock->expects($this->once())->method('save')->with($version);
+
+        $this->getModel([Options::NO_HTML_MINIFY => true])->deploy();
+    }
+
+    public function testDeploy()
+    {
+        $area = 'frontend';
+        $themePath = 'themepath';
+        $locale = 'en_US';
+        $options = [Options::NO_HTML_MINIFY => true];
+        $strategyProviderMock = $this->getMock(\Magento\Deploy\Model\DeployStrategyProvider::class, [], [], '', false);
+        $deployStrategyMock = $this->getMock(\Magento\Deploy\Model\Deploy\DeployInterface::class, [], [], '', false);
+
+        $model = $this->getModel($options);
+        $model->addPack($area, $themePath, $locale);
+        $this->deployStrategyProviderFactoryMock->expects($this->once())->method('create')->with(
+            ['output' => $this->outputMock, 'options' => $options]
+        )->willReturn($strategyProviderMock);
+        $strategyProviderMock->expects($this->once())->method('getDeployStrategies')->with($area, $themePath, [$locale])
+            ->willReturn([$locale => $deployStrategyMock]);
+        $this->stateMock->expects(self::once())->method('emulateAreaCode')
+            ->with($area, [$deployStrategyMock, 'deploy'], [$area, $themePath, $locale])
+            ->willReturn(\Magento\Framework\Console\Cli::RETURN_SUCCESS);
+
+        $version = (new \DateTime())->getTimestamp();
+        $this->outputMock->expects(self::once())->method('writeln')->with("New version of deployed files: {$version}");
+        $this->versionStorageMock->expects($this->once())->method('save')->with($version);
+
+        $this->assertEquals(\Magento\Framework\Console\Cli::RETURN_SUCCESS, $model->deploy());
+    }
+
+    /**
+     * @param array $options
+     * @return \Magento\Deploy\Model\DeployManager
+     */
+    private function getModel(array $options)
+    {
+        return new \Magento\Deploy\Model\DeployManager(
+            $this->outputMock,
+            $this->versionStorageMock,
+            $this->deployStrategyProviderFactoryMock,
+            $this->processQueueManagerFactoryMock,
+            $this->minifierTemplateMock,
+            $this->stateMock,
+            $options
+        );
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeployStrategyFactoryTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeployStrategyFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2af443ef061e887cf6181adb2ff945812660f099
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/DeployStrategyFactoryTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model;
+
+use Magento\Deploy\Model\Deploy\LocaleDeploy;
+use Magento\Deploy\Model\DeployStrategyFactory;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
+class DeployStrategyFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $objectManagerMock;
+
+    /**
+     * @var DeployStrategyFactory
+     */
+    private $unit;
+
+    protected function setUp()
+    {
+        $this->objectManagerMock = $this->getMock(ObjectManagerInterface::class);
+
+        $this->unit = (new ObjectManager($this))->getObject(
+            DeployStrategyFactory::class,
+            [
+                'objectManager' => $this->objectManagerMock,
+            ]
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\InputException
+     * @expectedExceptionMessage Wrong deploy strategy type: wrong-type
+     */
+    public function testCreateWithWrongStrategyType()
+    {
+        $this->unit->create('wrong-type');
+    }
+
+    public function testCreate()
+    {
+        $this->objectManagerMock->expects(self::once())->method('create')
+            ->with(LocaleDeploy::class, ['arg1' => 1]);
+
+        $this->unit->create(DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD, ['arg1' => 1]);
+    }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ProcessQueueManagerTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ProcessQueueManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e77068755036ed4983b920de4f49173a1337b753
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/ProcessQueueManagerTest.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Deploy\Test\Unit\Model;
+
+use Magento\Deploy\Model\ProcessManager;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Deploy\Model\ProcessTaskFactory;
+use Magento\Deploy\Model\ProcessTask;
+
+class ProcessQueueManagerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Deploy\Model\ProcessQueueManager
+     */
+    private $model;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\ProcessManager
+     */
+    private $processManagerMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ResourceConnection
+     */
+    private $resourceConnectionMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|ProcessTaskFactory
+     */
+    private $processTaskFactoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|ProcessTask
+     */
+    private $processTaskMock;
+
+    protected function setUp()
+    {
+        $this->processManagerMock = $this->getMock(ProcessManager::class, [], [], '', false);
+        $this->resourceConnectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+        $this->processTaskFactoryMock = $this->getMock(ProcessTaskFactory::class, ['create'], [], '', false);
+        $this->processTaskMock = $this->getMock(ProcessTask::class, [], [], '', false);
+        $this->processTaskFactoryMock->expects($this->any())->method('create')->willReturn($this->processTaskMock);
+        $this->model = (new ObjectManager($this))->getObject(
+            \Magento\Deploy\Model\ProcessQueueManager::class,
+            [
+                'processManager' => $this->processManagerMock,
+                'resourceConnection' => $this->resourceConnectionMock,
+                'processTaskFactory' => $this->processTaskFactoryMock
+            ]
+        );
+    }
+
+    public function testProcess()
+    {
+        $callableMock = function () {
+            return true;
+        };
+        $this->processTaskMock->expects($this->any())->method('getHandler')->willReturn($callableMock);
+
+        $processMock = $this->getMock(\Magento\Deploy\Model\Process::class, [], [], '', false);
+
+        $this->model->addTaskToQueue($callableMock, []);
+        $this->processManagerMock->expects($this->atLeastOnce())->method('getProcesses')->willReturnOnConsecutiveCalls(
+            [$processMock],
+            [$processMock],
+            [$processMock],
+            [$processMock],
+            [$processMock],
+            []
+        );
+        $processMock->expects($this->once())->method('isCompleted')->willReturn(true);
+        $processMock->expects($this->atLeastOnce())->method('getPid')->willReturn(42);
+        $processMock->expects($this->once())->method('getStatus')->willReturn(0);
+        $this->processManagerMock->expects($this->once())->method('delete')->with($processMock);
+
+        $this->resourceConnectionMock->expects(self::once())->method('closeConnection');
+
+        $this->assertEquals(0, $this->model->process());
+    }
+}
diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json
index 05db53f039027e2ca8e62f490005a782e0dfc088..856e0d8b3e542a37cdd7fcf7554b9e4e158de245 100644
--- a/app/code/Magento/Deploy/composer.json
+++ b/app/code/Magento/Deploy/composer.json
@@ -5,7 +5,6 @@
         "php": "~5.6.0|7.0.2|7.0.4|~7.0.6",
         "magento/framework": "100.2.*",
         "magento/module-store": "100.2.*",
-        "magento/module-theme": "100.2.*",
         "magento/module-require-js": "100.2.*",
         "magento/module-user": "100.2.*"
     },
diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml
index e1a0295a0fe32b8a916755f717ad54876c474558..52c880c28d0a71ad7eaeb34b6c2096c6c7b5060a 100644
--- a/app/code/Magento/Deploy/etc/di.xml
+++ b/app/code/Magento/Deploy/etc/di.xml
@@ -13,6 +13,13 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Deploy\Model\Deploy\LocaleDeploy">
+        <arguments>
+            <argument name="alternativeSources" xsi:type="array">
+                <item name="css" xsi:type="object">AlternativeSourceProcessors</item>
+            </argument>
+        </arguments>
+    </type>
     <type name="Magento\Framework\Console\CommandListInterface">
         <arguments>
             <argument name="commands" xsi:type="array">
diff --git a/app/code/Magento/Email/Model/Template/Css/Processor.php b/app/code/Magento/Email/Model/Template/Css/Processor.php
new file mode 100644
index 0000000000000000000000000000000000000000..ae7d083750863d2d7dbd5869341bbee772544421
--- /dev/null
+++ b/app/code/Magento/Email/Model/Template/Css/Processor.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Email\Model\Template\Css;
+
+use Magento\Framework\View\Asset\NotationResolver\Variable;
+use Magento\Framework\View\Asset\Repository;
+
+class Processor
+{
+    /**
+     * @var Repository
+     */
+    private $assetRepository;
+
+    /**
+     * @param Repository $assetRepository
+     */
+    public function __construct(Repository $assetRepository)
+    {
+        $this->assetRepository = $assetRepository;
+    }
+
+    /**
+     * Process css placeholders
+     *
+     * @param string $css
+     * @return string
+     */
+    public function process($css)
+    {
+        $matches = [];
+        if (preg_match_all(Variable::VAR_REGEX, $css, $matches, PREG_SET_ORDER)) {
+            $replacements = [];
+            foreach ($matches as $match) {
+                if (!isset($replacements[$match[0]])) {
+                    $replacements[$match[0]] = $this->getPlaceholderValue($match[1]);
+                }
+            }
+            $css = str_replace(array_keys($replacements), $replacements, $css);
+        }
+        return $css;
+    }
+
+    /**
+     * Retrieve placeholder value
+     *
+     * @param string $placeholder
+     * @return string
+     */
+    private function getPlaceholderValue($placeholder)
+    {
+        /** @var \Magento\Framework\View\Asset\File\FallbackContext $context */
+        $context = $this->assetRepository->getStaticViewFileContext();
+
+        switch ($placeholder) {
+            case 'base_url_path':
+                return $context->getBaseUrl();
+            case 'locale':
+                return $context->getLocale();
+            default:
+                return '';
+        }
+    }
+}
diff --git a/app/code/Magento/Email/Model/Template/Filter.php b/app/code/Magento/Email/Model/Template/Filter.php
index 2bb9fc742f258090d1cda46b538053e00fb896e8..d9409d62f159cfd7c2ab1e6c32aae6d07a33b8fc 100644
--- a/app/code/Magento/Email/Model/Template/Filter.php
+++ b/app/code/Magento/Email/Model/Template/Filter.php
@@ -5,6 +5,9 @@
  */
 namespace Magento\Email\Model\Template;
 
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Filesystem\Directory\ReadInterface;
 use Magento\Framework\View\Asset\ContentProcessorException;
 use Magento\Framework\View\Asset\ContentProcessorInterface;
 
@@ -153,6 +156,16 @@ class Filter extends \Magento\Framework\Filter\Template
      */
     protected $configVariables;
 
+    /**
+     * @var \Magento\Email\Model\Template\Css\Processor
+     */
+    private $cssProcessor;
+
+    /**
+     * @var ReadInterface
+     */
+    private $pubDirectory;
+
     /**
      * @param \Magento\Framework\Stdlib\StringUtils $string
      * @param \Psr\Log\LoggerInterface $logger
@@ -203,6 +216,31 @@ class Filter extends \Magento\Framework\Filter\Template
         parent::__construct($string, $variables);
     }
 
+    /**
+     * @deprecated
+     * @return Css\Processor
+     */
+    private function getCssProcessor()
+    {
+        if (!$this->cssProcessor) {
+            $this->cssProcessor = ObjectManager::getInstance()->get(Css\Processor::class);
+        }
+        return $this->cssProcessor;
+    }
+
+    /**
+     * @deprecated
+     * @param string $dirType
+     * @return ReadInterface
+     */
+    private function getPubDirectory($dirType)
+    {
+        if (!$this->pubDirectory) {
+            $this->pubDirectory = ObjectManager::getInstance()->get(Filesystem::class)->getDirectoryRead($dirType);
+        }
+        return $this->pubDirectory;
+    }
+
     /**
      * Set use absolute links flag
      *
@@ -788,7 +826,9 @@ class Filter extends \Magento\Framework\Filter\Template
             return '/* ' . __('"file" parameter must be specified') . ' */';
         }
 
-        $css = $this->getCssFilesContent([$params['file']]);
+        $css = $this->getCssProcessor()->process(
+            $this->getCssFilesContent([$params['file']])
+        );
 
         if (strpos($css, ContentProcessorInterface::ERROR_MESSAGE_PREFIX) !== false) {
             // Return compilation error wrapped in CSS comment
@@ -889,7 +929,12 @@ class Filter extends \Magento\Framework\Filter\Template
         try {
             foreach ($files as $file) {
                 $asset = $this->_assetRepo->createAsset($file, $designParams);
-                $css .= $asset->getContent();
+                $pubDirectory = $this->getPubDirectory($asset->getContext()->getBaseDirType());
+                if ($pubDirectory->isExist($asset->getPath())) {
+                    $css .= $pubDirectory->readFile($asset->getPath());
+                } else {
+                    $css .= $asset->getContent();
+                }
             }
         } catch (ContentProcessorException $exception) {
             $css = $exception->getMessage();
@@ -914,6 +959,8 @@ class Filter extends \Magento\Framework\Filter\Template
         $cssToInline = $this->getCssFilesContent(
             $this->getInlineCssFiles()
         );
+        $cssToInline = $this->getCssProcessor()->process($cssToInline);
+
         // Only run Emogrify if HTML and CSS contain content
         if ($html && $cssToInline) {
             try {
diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/Css/ProcessorTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/Css/ProcessorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fac9ba0f1b5aa902a22f08141e5f0c824a1a1382
--- /dev/null
+++ b/app/code/Magento/Email/Test/Unit/Model/Template/Css/ProcessorTest.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Email\Test\Unit\Model\Template\Css;
+
+use Magento\Email\Model\Template\Css\Processor;
+use Magento\Framework\View\Asset\File\FallbackContext;
+use Magento\Framework\View\Asset\Repository;
+
+class ProcessorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Processor
+     */
+    protected $processor;
+
+    /**
+     * @var Repository|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $assetRepository;
+
+    /**
+     * @var FallbackContext|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $fallbackContext;
+
+    public function setUp()
+    {
+        $this->assetRepository = $this->getMockBuilder(Repository::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->fallbackContext = $this->getMockBuilder(FallbackContext::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->processor = new Processor($this->assetRepository);
+    }
+
+    public function testProcess()
+    {
+        $url = 'http://magento.local/pub/static/';
+        $locale = 'en_US';
+        $css = '@import url("{{base_url_path}}frontend/_view/{{locale}}/css/email.css");';
+        $expectedCss = '@import url("' . $url . 'frontend/_view/' . $locale . '/css/email.css");';
+
+        $this->assetRepository->expects($this->exactly(2))
+            ->method('getStaticViewFileContext')
+            ->willReturn($this->fallbackContext);
+        $this->fallbackContext->expects($this->once())
+            ->method('getBaseUrl')
+            ->willReturn($url);
+        $this->fallbackContext->expects($this->once())
+            ->method('getLocale')
+            ->willReturn($locale);
+        $this->assertEquals($expectedCss, $this->processor->process($css));
+    }
+}
diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php
index 84e87e9e8154156b7323e1cbfd5d6d11ebdaab39..bcfd95d897d298faf58543b08ad3385e71f66476 100644
--- a/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php
+++ b/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php
@@ -5,6 +5,13 @@
  */
 namespace Magento\Email\Test\Unit\Model\Template;
 
+use Magento\Email\Model\Template\Css\Processor;
+use Magento\Email\Model\Template\Filter;
+use Magento\Framework\App\Area;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem\Directory\ReadInterface;
+use Magento\Framework\View\Asset\File\FallbackContext;
+
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
@@ -94,7 +101,6 @@ class FilterTest extends \PHPUnit_Framework_TestCase
 
         $this->escaper = $this->getMockBuilder(\Magento\Framework\Escaper::class)
             ->disableOriginalConstructor()
-            ->enableProxyingToOriginalMethods()
             ->getMock();
 
         $this->assetRepo = $this->getMockBuilder(\Magento\Framework\View\Asset\Repository::class)
@@ -138,7 +144,7 @@ class FilterTest extends \PHPUnit_Framework_TestCase
 
     /**
      * @param array|null $mockedMethods Methods to mock
-     * @return \Magento\Email\Model\Template\Filter|\PHPUnit_Framework_MockObject_MockObject
+     * @return Filter|\PHPUnit_Framework_MockObject_MockObject
      */
     protected function getModel($mockedMethods = null)
     {
@@ -252,13 +258,23 @@ class FilterTest extends \PHPUnit_Framework_TestCase
     public function testApplyInlineCss($html, $css, $expectedResults)
     {
         $filter = $this->getModel(['getCssFilesContent']);
+        $cssProcessor = $this->getMockBuilder(Processor::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $reflectionClass = new \ReflectionClass(Filter::class);
+        $reflectionProperty = $reflectionClass->getProperty('cssProcessor');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($filter, $cssProcessor);
+        $cssProcessor->expects($this->any())
+            ->method('process')
+            ->willReturnArgument(0);
 
         $filter->expects($this->exactly(count($expectedResults)))
             ->method('getCssFilesContent')
             ->will($this->returnValue($css));
 
         $designParams = [
-            'area' => \Magento\Framework\App\Area::AREA_FRONTEND,
+            'area' => Area::AREA_FRONTEND,
             'theme' => 'themeId',
             'locale' => 'localeId',
         ];
@@ -269,6 +285,60 @@ class FilterTest extends \PHPUnit_Framework_TestCase
         }
     }
 
+    public function testGetCssFilesContent()
+    {
+        $file = 'css/email.css';
+        $path = Area::AREA_FRONTEND . '/themeId/localeId';
+        $css = 'p{color:black}';
+        $designParams = [
+            'area' => Area::AREA_FRONTEND,
+            'theme' => 'themeId',
+            'locale' => 'localeId',
+        ];
+        $filter = $this->getModel();
+
+        $asset = $this->getMockBuilder(\Magento\Framework\View\Asset\File::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $fallbackContext = $this->getMockBuilder(FallbackContext::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $fallbackContext->expects($this->once())
+            ->method('getBaseDirType')
+            ->willReturn(DirectoryList::STATIC_VIEW);
+        $asset->expects($this->atLeastOnce())
+            ->method('getContext')
+            ->willReturn($fallbackContext);
+
+        $asset->expects($this->atLeastOnce())
+            ->method('getPath')
+            ->willReturn($path . DIRECTORY_SEPARATOR . $file);
+        $this->assetRepo->expects($this->once())
+            ->method('createAsset')
+            ->with($file, $designParams)
+            ->willReturn($asset);
+
+        $pubDirectory = $this->getMockBuilder(ReadInterface::class)
+            ->getMockForAbstractClass();
+        $reflectionClass = new \ReflectionClass(Filter::class);
+        $reflectionProperty = $reflectionClass->getProperty('pubDirectory');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($filter, $pubDirectory);
+        $pubDirectory->expects($this->once())
+            ->method('isExist')
+            ->with($path . DIRECTORY_SEPARATOR . $file)
+            ->willReturn(true);
+        $pubDirectory->expects($this->once())
+            ->method('readFile')
+            ->with($path . DIRECTORY_SEPARATOR . $file)
+            ->willReturn($css);
+
+        $filter->setDesignParams($designParams);
+
+        $this->assertEquals($css, $filter->getCssFilesContent([$file]));
+    }
+
     /**
      * @return array
      */
@@ -301,7 +371,19 @@ class FilterTest extends \PHPUnit_Framework_TestCase
      */
     public function testApplyInlineCssThrowsExceptionWhenDesignParamsNotSet()
     {
-        $this->getModel()->applyInlineCss('test');
+        $filter = $this->getModel();
+        $cssProcessor = $this->getMockBuilder(Processor::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $reflectionClass = new \ReflectionClass(Filter::class);
+        $reflectionProperty = $reflectionClass->getProperty('cssProcessor');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($filter, $cssProcessor);
+        $cssProcessor->expects($this->any())
+            ->method('process')
+            ->willReturnArgument(0);
+
+        $filter->applyInlineCss('test');
     }
 
     /**
@@ -348,7 +430,10 @@ class FilterTest extends \PHPUnit_Framework_TestCase
         $construction = ["{{config path={$path}}}", 'config', " path={$path}"];
         $scopeConfigValue = 'value';
 
-        $storeMock = $this->getMock(\Magento\Store\Api\Data\StoreInterface::class, [], [], '', false);
+        $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
         $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock);
         $storeMock->expects($this->once())->method('getId')->willReturn(1);
 
@@ -369,7 +454,9 @@ class FilterTest extends \PHPUnit_Framework_TestCase
         $construction = ["{{config path={$path}}}", 'config', " path={$path}"];
         $scopeConfigValue = '';
 
-        $storeMock = $this->getMock(\Magento\Store\Api\Data\StoreInterface::class, [], [], '', false);
+        $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
         $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock);
         $storeMock->expects($this->once())->method('getId')->willReturn(1);
 
diff --git a/app/code/Magento/Multishipping/Block/Checkout/Billing.php b/app/code/Magento/Multishipping/Block/Checkout/Billing.php
index f34ed5fb2671a649f7f8abc2d201b9ffc19dc0b0..34570e12a29efc0071cad327af15f50d64686f35 100644
--- a/app/code/Magento/Multishipping/Block/Checkout/Billing.php
+++ b/app/code/Magento/Multishipping/Block/Checkout/Billing.php
@@ -35,6 +35,7 @@ class Billing extends \Magento\Payment\Block\Form\Container
      * @param \Magento\Checkout\Model\Session $checkoutSession
      * @param \Magento\Payment\Model\Method\SpecificationInterface $paymentSpecification
      * @param array $data
+     * @param array $additionalChecks
      */
     public function __construct(
         \Magento\Framework\View\Element\Template\Context $context,
@@ -43,12 +44,13 @@ class Billing extends \Magento\Payment\Block\Form\Container
         \Magento\Multishipping\Model\Checkout\Type\Multishipping $multishipping,
         \Magento\Checkout\Model\Session $checkoutSession,
         \Magento\Payment\Model\Method\SpecificationInterface $paymentSpecification,
-        array $data = []
+        array $data = [],
+        array $additionalChecks = []
     ) {
         $this->_multishipping = $multishipping;
         $this->_checkoutSession = $checkoutSession;
         $this->paymentSpecification = $paymentSpecification;
-        parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data);
+        parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data, $additionalChecks);
         $this->_isScopePrivate = true;
     }
 
diff --git a/app/code/Magento/Payment/Api/Data/PaymentMethodInterface.php b/app/code/Magento/Payment/Api/Data/PaymentMethodInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..881595f60e5415fdeacf3968ccfd8f01b8539ec1
--- /dev/null
+++ b/app/code/Magento/Payment/Api/Data/PaymentMethodInterface.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Payment\Api\Data;
+
+/**
+ * Payment method interface.
+ *
+ * @api
+ */
+interface PaymentMethodInterface
+{
+    /**
+     * Get code.
+     *
+     * @return string
+     */
+    public function getCode();
+
+    /**
+     * Get title.
+     *
+     * @return string
+     */
+    public function getTitle();
+
+    /**
+     * Get store id.
+     *
+     * @return int
+     */
+    public function getStoreId();
+
+    /**
+     * Get is active.
+     *
+     * @return bool
+     * @SuppressWarnings(PHPMD.BooleanGetMethodName)
+     */
+    public function getIsActive();
+}
diff --git a/app/code/Magento/Payment/Api/PaymentMethodListInterface.php b/app/code/Magento/Payment/Api/PaymentMethodListInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..faf231562d9ed202073161a5dd7ac927e353a0ae
--- /dev/null
+++ b/app/code/Magento/Payment/Api/PaymentMethodListInterface.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Payment\Api;
+
+/**
+ * Payment method list interface.
+ *
+ * @api
+ */
+interface PaymentMethodListInterface
+{
+    /**
+     * Get list of payment methods.
+     *
+     * @param int $storeId
+     * @return \Magento\Payment\Api\Data\PaymentMethodInterface[]
+     */
+    public function getList($storeId);
+
+    /**
+     * Get list of active payment methods.
+     *
+     * @param int $storeId
+     * @return \Magento\Payment\Api\Data\PaymentMethodInterface[]
+     */
+    public function getActiveList($storeId);
+}
diff --git a/app/code/Magento/Payment/Block/Form/Container.php b/app/code/Magento/Payment/Block/Form/Container.php
index cdcd4137159a1994face7ed1ca5cd89e3c0ad3c2..d91c0e3dc39a454d39d6882da41374f4da71383f 100644
--- a/app/code/Magento/Payment/Block/Form/Container.php
+++ b/app/code/Magento/Payment/Block/Form/Container.php
@@ -5,6 +5,7 @@
  */
 namespace Magento\Payment\Block\Form;
 
+use Magento\Framework\App\ObjectManager;
 use Magento\Payment\Model\Method\AbstractMethod;
 
 /**
@@ -24,20 +25,38 @@ class Container extends \Magento\Framework\View\Element\Template
     /** @var  \Magento\Payment\Model\Checks\SpecificationFactory */
     protected $methodSpecificationFactory;
 
+    /**
+     * @var \Magento\Payment\Api\PaymentMethodListInterface
+     */
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory
+     */
+    private $paymentMethodInstanceFactory;
+
+    /**
+     * @var array
+     */
+    protected $additionalChecks;
+
     /**
      * @param \Magento\Framework\View\Element\Template\Context $context
      * @param \Magento\Payment\Helper\Data $paymentHelper
      * @param \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory
      * @param array $data
+     * @param array $additionalChecks
      */
     public function __construct(
         \Magento\Framework\View\Element\Template\Context $context,
         \Magento\Payment\Helper\Data $paymentHelper,
         \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory,
-        array $data = []
+        array $data = [],
+        array $additionalChecks = []
     ) {
         $this->_paymentHelper = $paymentHelper;
         $this->methodSpecificationFactory = $methodSpecificationFactory;
+        $this->additionalChecks = $additionalChecks;
         parent::__construct($context, $data);
     }
 
@@ -69,13 +88,17 @@ class Container extends \Magento\Framework\View\Element\Template
      */
     protected function _canUseMethod($method)
     {
-        return $this->methodSpecificationFactory->create(
+        $checks = array_merge(
             [
                 AbstractMethod::CHECK_USE_FOR_COUNTRY,
                 AbstractMethod::CHECK_USE_FOR_CURRENCY,
                 AbstractMethod::CHECK_ORDER_TOTAL_MIN_MAX,
-            ]
-        )->isApplicable(
+                AbstractMethod::CHECK_ZERO_TOTAL
+            ],
+            $this->additionalChecks
+        );
+
+        return $this->methodSpecificationFactory->create($checks)->isApplicable(
             $method,
             $this->getQuote()
         );
@@ -124,11 +147,11 @@ class Container extends \Magento\Framework\View\Element\Template
             $quote = $this->getQuote();
             $store = $quote ? $quote->getStoreId() : null;
             $methods = [];
-            $specification = $this->methodSpecificationFactory->create([AbstractMethod::CHECK_ZERO_TOTAL]);
-            foreach ($this->_paymentHelper->getStoreMethods($store, $quote) as $method) {
-                if ($this->_canUseMethod($method) && $specification->isApplicable($method, $this->getQuote())) {
-                    $this->_assignMethod($method);
-                    $methods[] = $method;
+            foreach ($this->getPaymentMethodList()->getActiveList($store) as $method) {
+                $methodInstance = $this->getPaymentMethodInstanceFactory()->create($method);
+                if ($methodInstance->isAvailable($quote) && $this->_canUseMethod($methodInstance)) {
+                    $this->_assignMethod($methodInstance);
+                    $methods[] = $methodInstance;
                 }
             }
             $this->setData('methods', $methods);
@@ -150,4 +173,36 @@ class Container extends \Magento\Framework\View\Element\Template
         }
         return false;
     }
+
+    /**
+     * Get payment method list.
+     *
+     * @return \Magento\Payment\Api\PaymentMethodListInterface
+     * @deprecated
+     */
+    private function getPaymentMethodList()
+    {
+        if ($this->paymentMethodList === null) {
+            $this->paymentMethodList = ObjectManager::getInstance()->get(
+                \Magento\Payment\Api\PaymentMethodListInterface::class
+            );
+        }
+        return $this->paymentMethodList;
+    }
+
+    /**
+     * Get payment method instance factory.
+     *
+     * @return \Magento\Payment\Model\Method\InstanceFactory
+     * @deprecated
+     */
+    private function getPaymentMethodInstanceFactory()
+    {
+        if ($this->paymentMethodInstanceFactory === null) {
+            $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get(
+                \Magento\Payment\Model\Method\InstanceFactory::class
+            );
+        }
+        return $this->paymentMethodInstanceFactory;
+    }
 }
diff --git a/app/code/Magento/Payment/Helper/Data.php b/app/code/Magento/Payment/Helper/Data.php
index 78cde93ba6717a85a04feb6a2bec21090d551695..0c5518a5c4c9f98e0e9f615b8df78bc85163abc9 100644
--- a/app/code/Magento/Payment/Helper/Data.php
+++ b/app/code/Magento/Payment/Helper/Data.php
@@ -118,6 +118,7 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
      * @param null|string|bool|int $store
      * @param Quote|null $quote
      * @return AbstractMethod[]
+     * @deprecated
      */
     public function getStoreMethods($store = null, $quote = null)
     {
diff --git a/app/code/Magento/Payment/Model/Method/InstanceFactory.php b/app/code/Magento/Payment/Model/Method/InstanceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..c273c71e78330df854eabfb4b564675af028cb23
--- /dev/null
+++ b/app/code/Magento/Payment/Model/Method/InstanceFactory.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Payment\Model\Method;
+
+use Magento\Payment\Api\Data\PaymentMethodInterface;
+
+/**
+ * Payment method instance factory.
+ */
+class InstanceFactory
+{
+    /**
+     * @var \Magento\Payment\Helper\Data
+     */
+    private $helper;
+
+    /**
+     * @param \Magento\Payment\Helper\Data $helper
+     */
+    public function __construct(
+        \Magento\Payment\Helper\Data $helper
+    ) {
+        $this->helper = $helper;
+    }
+
+    /**
+     * Create payment method instance.
+     *
+     * @param PaymentMethodInterface $paymentMethod
+     * @return \Magento\Payment\Model\MethodInterface
+     */
+    public function create(PaymentMethodInterface $paymentMethod)
+    {
+        $methodInstance = $this->helper->getMethodInstance($paymentMethod->getCode());
+        $methodInstance->setStore($paymentMethod->getStoreId());
+
+        return $methodInstance;
+    }
+}
diff --git a/app/code/Magento/Payment/Model/MethodList.php b/app/code/Magento/Payment/Model/MethodList.php
index d547fe42f332ca4c49f0ff51ac16c0b59e538556..ff1f786c77f6175ab8d78feac5ee7d270a64fc45 100644
--- a/app/code/Magento/Payment/Model/MethodList.php
+++ b/app/code/Magento/Payment/Model/MethodList.php
@@ -6,12 +6,14 @@
 
 namespace Magento\Payment\Model;
 
+use Magento\Framework\App\ObjectManager;
 use Magento\Payment\Model\Method\AbstractMethod;
 
 class MethodList
 {
     /**
      * @var \Magento\Payment\Helper\Data
+     * @deprecated
      */
     protected $paymentHelper;
 
@@ -20,6 +22,16 @@ class MethodList
      */
     protected $methodSpecificationFactory;
 
+    /**
+     * @var \Magento\Payment\Api\PaymentMethodListInterface
+     */
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory
+     */
+    private $paymentMethodInstanceFactory;
+
     /**
      * @param \Magento\Payment\Helper\Data $paymentHelper
      * @param Checks\SpecificationFactory $specificationFactory
@@ -40,14 +52,16 @@ class MethodList
     public function getAvailableMethods(\Magento\Quote\Api\Data\CartInterface $quote = null)
     {
         $store = $quote ? $quote->getStoreId() : null;
-        $methods = [];
-        foreach ($this->paymentHelper->getStoreMethods($store, $quote) as $method) {
-            if ($this->_canUseMethod($method, $quote)) {
-                $method->setInfoInstance($quote->getPayment());
-                $methods[] = $method;
+        $availableMethods = [];
+
+        foreach ($this->getPaymentMethodList()->getActiveList($store) as $method) {
+            $methodInstance = $this->getPaymentMethodInstanceFactory()->create($method);
+            if ($methodInstance->isAvailable($quote) && $this->_canUseMethod($methodInstance, $quote)) {
+                $methodInstance->setInfoInstance($quote->getPayment());
+                $availableMethods[] = $methodInstance;
             }
         }
-        return $methods;
+        return $availableMethods;
     }
 
     /**
@@ -71,4 +85,36 @@ class MethodList
             $quote
         );
     }
+
+    /**
+     * Get payment method list.
+     *
+     * @return \Magento\Payment\Api\PaymentMethodListInterface
+     * @deprecated
+     */
+    private function getPaymentMethodList()
+    {
+        if ($this->paymentMethodList === null) {
+            $this->paymentMethodList = ObjectManager::getInstance()->get(
+                \Magento\Payment\Api\PaymentMethodListInterface::class
+            );
+        }
+        return $this->paymentMethodList;
+    }
+
+    /**
+     * Get payment method instance factory.
+     *
+     * @return \Magento\Payment\Model\Method\InstanceFactory
+     * @deprecated
+     */
+    private function getPaymentMethodInstanceFactory()
+    {
+        if ($this->paymentMethodInstanceFactory === null) {
+            $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get(
+                \Magento\Payment\Model\Method\InstanceFactory::class
+            );
+        }
+        return $this->paymentMethodInstanceFactory;
+    }
 }
diff --git a/app/code/Magento/Payment/Model/PaymentMethod.php b/app/code/Magento/Payment/Model/PaymentMethod.php
new file mode 100644
index 0000000000000000000000000000000000000000..581c4703f777e1a853dfd15f9afb8c8c7bcc67a6
--- /dev/null
+++ b/app/code/Magento/Payment/Model/PaymentMethod.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Payment\Model;
+
+/**
+ * Payment method class.
+ */
+class PaymentMethod implements \Magento\Payment\Api\Data\PaymentMethodInterface
+{
+    /**
+     * @var string
+     */
+    private $code;
+
+    /**
+     * @var string
+     */
+    private $title;
+
+    /**
+     * @var int
+     */
+    private $storeId;
+
+    /**
+     * @var bool
+     */
+    private $isActive;
+
+    /**
+     * @param string $code
+     * @param string $title
+     * @param int $storeId
+     * @param bool $isActive
+     */
+    public function __construct($code, $title, $storeId, $isActive)
+    {
+        $this->code = $code;
+        $this->title = $title;
+        $this->storeId = $storeId;
+        $this->isActive = $isActive;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getStoreId()
+    {
+        return $this->storeId;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getIsActive()
+    {
+        return $this->isActive;
+    }
+}
diff --git a/app/code/Magento/Payment/Model/PaymentMethodList.php b/app/code/Magento/Payment/Model/PaymentMethodList.php
new file mode 100644
index 0000000000000000000000000000000000000000..5968d201ca6869ab9d38327dc422b7f0b935956d
--- /dev/null
+++ b/app/code/Magento/Payment/Model/PaymentMethodList.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Payment\Model;
+
+use Magento\Payment\Api\Data\PaymentMethodInterface;
+
+/**
+ * Payment method list class.
+ */
+class PaymentMethodList implements \Magento\Payment\Api\PaymentMethodListInterface
+{
+    /**
+     * @var \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory
+     */
+    private $methodFactory;
+
+    /**
+     * @var \Magento\Payment\Helper\Data
+     */
+    private $helper;
+
+    /**
+     * @param \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory $methodFactory
+     * @param \Magento\Payment\Helper\Data $helper
+     */
+    public function __construct(
+        \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory $methodFactory,
+        \Magento\Payment\Helper\Data $helper
+    ) {
+        $this->methodFactory = $methodFactory;
+        $this->helper = $helper;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getList($storeId)
+    {
+        $methodsCodes = array_keys($this->helper->getPaymentMethods());
+
+        $methodsInstances = array_map(
+            function ($code) {
+                return $this->helper->getMethodInstance($code);
+            },
+            $methodsCodes
+        );
+
+        $methodsInstances = array_filter($methodsInstances, function (MethodInterface $method) {
+            return !($method instanceof \Magento\Payment\Model\Method\Substitution);
+        });
+
+        @uasort(
+            $methodsInstances,
+            function (MethodInterface $a, MethodInterface $b) use ($storeId) {
+                return (int)$a->getConfigData('sort_order', $storeId) - (int)$b->getConfigData('sort_order', $storeId);
+            }
+        );
+
+        $methodList = array_map(
+            function (MethodInterface $methodInstance) use ($storeId) {
+
+                return $this->methodFactory->create([
+                    'code' => (string)$methodInstance->getCode(),
+                    'title' => (string)$methodInstance->getTitle(),
+                    'storeId' => (int)$storeId,
+                    'isActive' => (bool)$methodInstance->isActive($storeId)
+                ]);
+            },
+            $methodsInstances
+        );
+
+        return array_values($methodList);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getActiveList($storeId)
+    {
+        $methodList = array_filter(
+            $this->getList($storeId),
+            function (PaymentMethodInterface $method) {
+                return $method->getIsActive();
+            }
+        );
+
+        return array_values($methodList);
+    }
+}
diff --git a/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php b/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php
index b8e1b0dcdf973dd9311c67dceccccbe3ad2f203b..2a3ae9c3063730aa8743b33c44aa831b6664f19c 100644
--- a/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php
+++ b/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/SoapTest.php
@@ -48,6 +48,7 @@ class SoapTest extends \PHPUnit_Framework_TestCase
             \Magento\Payment\Gateway\Http\ConverterInterface::class
         )->getMockForAbstractClass();
         $this->client = $this->getMockBuilder(\SoapClient::class)
+            ->setMethods(['__setSoapHeaders', '__soapCall', '__getLastRequest'])
             ->disableOriginalConstructor()
             ->getMock();
 
diff --git a/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php b/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php
index 915ccddc05c89ac7b3bbbf836aaa80dc7f0f1c4e..872782407c12994e43aa4a026f4a340450b26b88 100644
--- a/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php
+++ b/app/code/Magento/Payment/Test/Unit/Model/MethodListTest.php
@@ -24,9 +24,14 @@ class MethodListTest extends \PHPUnit_Framework_TestCase
     protected $objectManager;
 
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Payment\Api\PaymentMethodListInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $paymentHelperMock;
+    private $paymentMethodInstanceFactory;
 
     /**
      * @var \PHPUnit_Framework_MockObject_MockObject
@@ -36,17 +41,35 @@ class MethodListTest extends \PHPUnit_Framework_TestCase
     protected function setUp()
     {
         $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
-        $this->paymentHelperMock = $this->getMock(\Magento\Payment\Helper\Data::class, [], [], '', false);
-        $this->specificationFactoryMock = $this->getMock(
+
+        $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->paymentMethodInstanceFactory = $this->getMockBuilder(
+            \Magento\Payment\Model\Method\InstanceFactory::class
+        )->disableOriginalConstructor()->getMock();
+
+        $this->specificationFactoryMock = $this->getMock(
             \Magento\Payment\Model\Checks\SpecificationFactory::class, [], [], '', false
         );
-        $this->methodList = $this->objectManager->getObject(
+        $this->methodList = $this->objectManager->getObject(
             \Magento\Payment\Model\MethodList::class,
             [
-                'paymentHelper' => $this->paymentHelperMock,
                 'specificationFactory' => $this->specificationFactoryMock
             ]
         );
+
+        $this->objectManager->setBackwardCompatibleProperty(
+            $this->methodList,
+            'paymentMethodList',
+            $this->paymentMethodList
+        );
+        $this->objectManager->setBackwardCompatibleProperty(
+            $this->methodList,
+            'paymentMethodInstanceFactory',
+            $this->paymentMethodInstanceFactory
+        );
     }
 
     public function testGetAvailableMethods()
@@ -58,12 +81,15 @@ class MethodListTest extends \PHPUnit_Framework_TestCase
             ->method('getPayment')
             ->will($this->returnValue($this->getMock(\Magento\Quote\Model\Quote\Payment::class, [], [], '', false)));
 
-        $methodMock = $this->getMock(\Magento\Payment\Model\Method\AbstractMethod::class, [], [], '', false);
+        $methodInstanceMock = $this->getMock(\Magento\Payment\Model\Method\AbstractMethod::class, [], [], '', false);
+        $methodInstanceMock->expects($this->once())
+            ->method('isAvailable')
+            ->willReturn(true);
 
         $compositeMock = $this->getMock(\Magento\Payment\Model\Checks\Composite::class, [], [], '', false);
         $compositeMock->expects($this->atLeastOnce())
             ->method('isApplicable')
-            ->with($methodMock, $quoteMock)
+            ->with($methodInstanceMock, $quoteMock)
             ->will($this->returnValue(true));
 
         $this->specificationFactoryMock->expects($this->atLeastOnce())
@@ -76,18 +102,19 @@ class MethodListTest extends \PHPUnit_Framework_TestCase
             ])
             ->will($this->returnValue($compositeMock));
 
-        $storeMethods = [$methodMock];
-
-        $this->paymentHelperMock->expects($this->once())
-            ->method('getStoreMethods')
-            ->with($storeId, $quoteMock)
-            ->will($this->returnValue($storeMethods));
+        $methodMock = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class);
+        $this->paymentMethodList->expects($this->once())
+            ->method('getActiveList')
+            ->willReturn([$methodMock]);
+        $this->paymentMethodInstanceFactory->expects($this->once())
+            ->method('create')
+            ->willReturn($methodInstanceMock);
 
-        $methodMock->expects($this->atLeastOnce())
+        $methodInstanceMock->expects($this->atLeastOnce())
             ->method('setInfoInstance')
             ->with($this->getMock(\Magento\Quote\Model\Quote\Payment::class, [], [], '', false))
             ->will($this->returnSelf());
 
-        $this->assertEquals([$methodMock], $this->methodList->getAvailableMethods($quoteMock));
+        $this->assertEquals([$methodInstanceMock], $this->methodList->getAvailableMethods($quoteMock));
     }
 }
diff --git a/app/code/Magento/Payment/Test/Unit/Model/PaymentMethodListTest.php b/app/code/Magento/Payment/Test/Unit/Model/PaymentMethodListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c41e5ac2a1aac326970c6a7a2d39eab30b8a8bcc
--- /dev/null
+++ b/app/code/Magento/Payment/Test/Unit/Model/PaymentMethodListTest.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Payment\Test\Unit\Model;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+
+/**
+ * Class PaymentMethodListTest.
+ */
+class PaymentMethodListTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ObjectManagerHelper
+     */
+    private $objectManagerHelper;
+
+    /**
+     * @var \Magento\Payment\Model\PaymentMethodList|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Api\Data\PaymentMethodInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $methodFactoryMock;
+
+    /**
+     * @var \Magento\Payment\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $helperMock;
+
+    /**
+     * Setup.
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        $this->methodFactoryMock = $this->getMockBuilder(\Magento\Payment\Api\Data\PaymentMethodInterfaceFactory::class)
+            ->setMethods(['create'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->helperMock = $this->getMockBuilder(\Magento\Payment\Helper\Data::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->objectManagerHelper = new ObjectManagerHelper($this);
+        $this->paymentMethodList = $this->objectManagerHelper->getObject(
+            \Magento\Payment\Model\PaymentMethodList::class,
+            [
+                'methodFactory' => $this->methodFactoryMock,
+                'helper' => $this->helperMock
+            ]
+        );
+    }
+
+    /**
+     * Setup getList method.
+     *
+     * @param array $paymentMethodConfig
+     * @param array $methodInstancesMap
+     * @return void
+     */
+    private function setUpGetList($paymentMethodConfig, $methodInstancesMap)
+    {
+        $this->helperMock->expects($this->once())
+            ->method('getPaymentMethods')
+            ->willReturn($paymentMethodConfig);
+        $this->helperMock->expects($this->any())
+            ->method('getMethodInstance')
+            ->willReturnMap($methodInstancesMap);
+
+        $this->methodFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturnCallback(function ($data) {
+                $paymentMethod = $this->getMockBuilder(\Magento\Payment\Api\Data\PaymentMethodInterface::class)
+                    ->getMockForAbstractClass();
+                $paymentMethod->expects($this->any())
+                    ->method('getCode')
+                    ->willReturn($data['code']);
+                $paymentMethod->expects($this->any())
+                    ->method('getIsActive')
+                    ->willReturn($data['isActive']);
+
+                return $paymentMethod;
+            });
+    }
+
+    /**
+     * Test getList.
+     *
+     * @param int $storeId
+     * @param array $paymentMethodConfig
+     * @param array $methodInstancesMap
+     * @param array $expected
+     * @return void
+     *
+     * @dataProvider getListDataProvider
+     */
+    public function testGetList($storeId, $paymentMethodConfig, $methodInstancesMap, $expected)
+    {
+        $this->setUpGetList($paymentMethodConfig, $methodInstancesMap);
+
+        $codes = array_map(
+            function ($method) {
+                return $method->getCode();
+            },
+            $this->paymentMethodList->getList($storeId)
+        );
+
+        $this->assertEquals($expected, $codes);
+    }
+
+    /**
+     * Data provider for getList.
+     *
+     * @return array
+     */
+    public function getListDataProvider()
+    {
+        return [
+            [
+                1,
+                ['method_code_1' => [], 'method_code_2' => []],
+                [
+                    ['method_code_1', $this->mockPaymentMethodInstance(1, 10, 'method_code_1', 'title', true)],
+                    ['method_code_2', $this->mockPaymentMethodInstance(1, 5, 'method_code_2', 'title', true)]
+                ],
+                ['method_code_2', 'method_code_1']
+            ]
+        ];
+    }
+
+    /**
+     * Test getActiveList.
+     *
+     * @param int $storeId
+     * @param array $paymentMethodConfig
+     * @param array $methodInstancesMap
+     * @param array $expected
+     * @return void
+     *
+     * @dataProvider getActiveListDataProvider
+     */
+    public function testGetActiveList($storeId, $paymentMethodConfig, $methodInstancesMap, $expected)
+    {
+        $this->setUpGetList($paymentMethodConfig, $methodInstancesMap);
+
+        $codes = array_map(
+            function ($method) {
+                return $method->getCode();
+            },
+            $this->paymentMethodList->getActiveList($storeId)
+        );
+
+        $this->assertEquals($expected, $codes);
+    }
+
+    /**
+     * Data provider for getActiveList.
+     *
+     * @return array
+     */
+    public function getActiveListDataProvider()
+    {
+        return [
+            [
+                1,
+                ['method_code_1' => [], 'method_code_2' => []],
+                [
+                    ['method_code_1', $this->mockPaymentMethodInstance(1, 10, 'method_code_1', 'title', false)],
+                    ['method_code_2', $this->mockPaymentMethodInstance(1, 5, 'method_code_2', 'title', true)]
+                ],
+                ['method_code_2']
+            ]
+        ];
+    }
+
+    /**
+     * Mock payment method instance.
+     *
+     * @param int $storeId
+     * @param int $sortOrder
+     * @param string $code
+     * @param string $title
+     * @param bool $isActive
+     * @return \PHPUnit_Framework_MockObject_MockObject
+     */
+    private function mockPaymentMethodInstance($storeId, $sortOrder, $code, $title, $isActive)
+    {
+        $paymentMethodInstance = $this->getMockBuilder(\Magento\Payment\Model\Method\AbstractMethod::class)
+            ->setMethods(['getCode', 'getTitle', 'isActive', 'getConfigData'])
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $paymentMethodInstance->expects($this->any())
+            ->method('getConfigData')
+            ->willReturnMap([
+                ['sort_order', $storeId, $sortOrder]
+            ]);
+        $paymentMethodInstance->expects($this->any())
+            ->method('getCode')
+            ->willReturn($code);
+        $paymentMethodInstance->expects($this->any())
+            ->method('getTitle')
+            ->willReturn($title);
+        $paymentMethodInstance->expects($this->any())
+            ->method('isActive')
+            ->willReturn($isActive);
+
+        return $paymentMethodInstance;
+    }
+}
diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml
index 73f8e2a76334be8c0bde6f1a2c69a063818ca928..c12c2d2cfd51c2be7688aacef7b023e618f2f77a 100644
--- a/app/code/Magento/Payment/etc/di.xml
+++ b/app/code/Magento/Payment/etc/di.xml
@@ -6,6 +6,8 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
+    <preference for="Magento\Payment\Api\Data\PaymentMethodInterface" type="Magento\Payment\Model\PaymentMethod"/>
+    <preference for="Magento\Payment\Api\PaymentMethodListInterface" type="Magento\Payment\Model\PaymentMethodList"/>
     <preference for="Magento\Payment\Gateway\Validator\ResultInterface" type="Magento\Payment\Gateway\Validator\Result"/>
     <preference for="Magento\Payment\Gateway\ConfigFactoryInterface" type="Magento\Payment\Gateway\Config\ConfigFactory" />
     <preference for="Magento\Payment\Gateway\Command\CommandManagerPoolInterface" type="Magento\Payment\Gateway\Command\CommandManagerPool" />
diff --git a/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js b/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js
index c8a6fef58d31ea24dcb930b5e63c6912f8738eff..7d01c195791e482618ae2073a82564cb14aaf339 100644
--- a/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js
+++ b/app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js
@@ -154,6 +154,7 @@ define(
              */
             clearTimeout: function () {
                 clearTimeout(this.timeoutId);
+                this.fail();
 
                 return this;
             },
diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php
index 4abe12cbc3715d5192a6b1a4f0a2ae44685a368d..725ebb2282418e9e6747c3fc28c72ff600f31e07 100644
--- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php
+++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Fieldset/Payment.php
@@ -78,18 +78,10 @@ class Payment extends \Magento\Config\Block\System\Config\Form\Fieldset
      */
     protected function _getHeaderTitleHtml($element)
     {
-        $html = '<div class="config-heading" ><div class="heading"><strong>' . $element->getLegend();
+        $html = '<div class="config-heading" >';
 
         $groupConfig = $element->getGroup();
 
-        $html .= '</strong>';
-
-        if ($element->getComment()) {
-            $html .= '<span class="heading-intro">' . $element->getComment() . '</span>';
-        }
-        $html .= '<div class="config-alt"></div>';
-        $html .= '</div>';
-
         $disabledAttributeString = $this->_isPaymentEnabled($element) ? '' : ' disabled="disabled"';
         $disabledClassString = $this->_isPaymentEnabled($element) ? '' : ' disabled';
         $htmlId = $element->getHtmlId();
@@ -122,6 +114,13 @@ class Payment extends \Magento\Config\Block\System\Config\Form\Fieldset
             ) . '</a>';
         }
 
+        $html .= '</div>';
+        $html .= '<div class="heading"><strong>' . $element->getLegend() . '</strong>';
+
+        if ($element->getComment()) {
+            $html .= '<span class="heading-intro">' . $element->getComment() . '</span>';
+        }
+        $html .= '<div class="config-alt"></div>';
         $html .= '</div></div>';
 
         return $html;
diff --git a/app/code/Magento/Paypal/Helper/Data.php b/app/code/Magento/Paypal/Helper/Data.php
index 0ecde3bdc5697488e33e3c027a6d0c9a2439d03d..ea69d2ffcf0794f09497d66444d35600a3d8e077 100644
--- a/app/code/Magento/Paypal/Helper/Data.php
+++ b/app/code/Magento/Paypal/Helper/Data.php
@@ -5,6 +5,8 @@
  */
 namespace Magento\Paypal\Helper;
 
+use Magento\Framework\App\ObjectManager;
+use Magento\Payment\Model\Method\AbstractMethod;
 use Magento\Paypal\Model\Billing\Agreement\MethodInterface;
 
 /**
@@ -42,6 +44,16 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
      */
     private $configFactory;
 
+    /**
+     * @var \Magento\Payment\Api\PaymentMethodListInterface
+     */
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory
+     */
+    private $paymentMethodInstanceFactory;
+
     /**
      * @param \Magento\Framework\App\Helper\Context $context
      * @param \Magento\Payment\Helper\Data $paymentData
@@ -92,12 +104,20 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
      */
     public function getBillingAgreementMethods($store = null, $quote = null)
     {
-        $result = [];
-        foreach ($this->_paymentData->getStoreMethods($store, $quote) as $method) {
-            if ($method instanceof MethodInterface) {
-                $result[] = $method;
+        $activeMethods = array_map(
+            function (\Magento\Payment\Api\Data\PaymentMethodInterface $method) {
+                return $this->getPaymentMethodInstanceFactory()->create($method);
+            },
+            $this->getPaymentMethodList()->getActiveList($store)
+        );
+
+        $result = array_filter(
+            $activeMethods,
+            function (AbstractMethod $method) use ($quote) {
+                return $method->isAvailable($quote) && $method instanceof MethodInterface;
             }
-        }
+        );
+
         return $result;
     }
 
@@ -118,4 +138,36 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
         }
         return $txnId;
     }
+
+    /**
+     * Get payment method list.
+     *
+     * @return \Magento\Payment\Api\PaymentMethodListInterface
+     * @deprecated
+     */
+    private function getPaymentMethodList()
+    {
+        if ($this->paymentMethodList === null) {
+            $this->paymentMethodList = ObjectManager::getInstance()->get(
+                \Magento\Payment\Api\PaymentMethodListInterface::class
+            );
+        }
+        return $this->paymentMethodList;
+    }
+
+    /**
+     * Get payment method instance factory.
+     *
+     * @return \Magento\Payment\Model\Method\InstanceFactory
+     * @deprecated
+     */
+    private function getPaymentMethodInstanceFactory()
+    {
+        if ($this->paymentMethodInstanceFactory === null) {
+            $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get(
+                \Magento\Payment\Model\Method\InstanceFactory::class
+            );
+        }
+        return $this->paymentMethodInstanceFactory;
+    }
 }
diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php
index e02615d24ad3d2fbd4e90a768ce9594531acc602..0af8b18355ec32b045c4e609137cb0eed7944373 100644
--- a/app/code/Magento/Paypal/Model/Config.php
+++ b/app/code/Magento/Paypal/Model/Config.php
@@ -866,7 +866,9 @@ class Config extends AbstractConfig
      */
     public function getExpressCheckoutStartUrl($token)
     {
-        return $this->getPaypalUrl(['cmd' => '_express-checkout', 'token' => $token]);
+        return sprintf('https://www.%spaypal.com/checkoutnow/2%s',
+            $this->getValue('sandboxFlag') ? 'sandbox.' : '',
+            '?token=' . urlencode($token));
     }
 
     /**
diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php
index d377e8ed626e15a65a3772041bb837328a4942f3..db1ecdf6b33799673cd22926f5d9dd1b7999f965 100644
--- a/app/code/Magento/Paypal/Model/Express.php
+++ b/app/code/Magento/Paypal/Model/Express.php
@@ -672,19 +672,13 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod
         
         $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
 
-        if (
-            !is_array($additionalData)
-            || !isset($additionalData[ExpressCheckout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT])
-        ) {
+        if (!is_array($additionalData)) {
             return $this;
         }
 
-        $this->getInfoInstance()
-            ->setAdditionalInformation(
-                ExpressCheckout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT,
-                $additionalData[ExpressCheckout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT]
-            );
-        
+        foreach ($additionalData as $key => $value) {
+            $this->getInfoInstance()->setAdditionalInformation($key, $value);
+        }
         return $this;
     }
 
diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php
index f0383dffc9b2443f96bf903b1b423306ff09b4ea..b5803c5ace3925b75e81df43992f714a74ed9905 100644
--- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php
+++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php
@@ -166,6 +166,9 @@ class Transparent extends Payflowpro implements TransparentInterface
         $request->setData('trxtype', self::TRXTYPE_AUTH_ONLY);
         $request->setData('origid', $token);
         $request->setData('amt', $this->formatPrice($amount));
+        $request->setData('currency', $order->getBaseCurrencyCode());
+        $request->setData('taxamt', $this->formatPrice($order->getBaseTaxAmount()));
+        $request->setData('freightamt', $this->formatPrice($order->getBaseShippingAmount()));
 
         $response = $this->postRequest($request, $this->getConfig());
         $this->processErrors($response);
diff --git a/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php b/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php
index fa0620a36a681eae6a48413044c6784876aaa889..239c5d6cda746d62fdc438791bb29bfc43f61c19 100644
--- a/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Helper/DataTest.php
@@ -8,9 +8,14 @@ namespace Magento\Paypal\Test\Unit\Helper;
 class DataTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var \Magento\Payment\Api\PaymentMethodListInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $_paymentDataMock;
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentMethodInstanceFactory;
 
     /**
      * @var \Magento\Paypal\Model\Config | \PHPUnit_Framework_MockObject_MockObject
@@ -24,11 +29,13 @@ class DataTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->_paymentDataMock = $this->getMockBuilder(
-            \Magento\Payment\Helper\Data::class
-        )->disableOriginalConstructor()->setMethods(
-            ['getStoreMethods', 'getPaymentMethods']
-        )->getMock();
+        $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->paymentMethodInstanceFactory = $this->getMockBuilder(
+            \Magento\Payment\Model\Method\InstanceFactory::class
+        )->disableOriginalConstructor()->getMock();
 
         $this->configMock = $this->getMock(
             \Magento\Paypal\Model\Config::class,
@@ -48,32 +55,41 @@ class DataTest extends \PHPUnit_Framework_TestCase
         $this->_helper = $objectManager->getObject(
             \Magento\Paypal\Helper\Data::class,
             [
-                'paymentData' => $this->_paymentDataMock,
                 'methodCodes' => ['expressCheckout' => 'paypal_express', 'hostedPro' => 'hosted_pro'],
                 'configFactory' => $configMockFactory
             ]
         );
+
+        $objectManager->setBackwardCompatibleProperty(
+            $this->_helper,
+            'paymentMethodList',
+            $this->paymentMethodList
+        );
+        $objectManager->setBackwardCompatibleProperty(
+            $this->_helper,
+            'paymentMethodInstanceFactory',
+            $this->paymentMethodInstanceFactory
+        );
     }
 
     /**
      * @dataProvider getBillingAgreementMethodsDataProvider
      * @param $store
      * @param $quote
-     * @param $paymentMethods
+     * @param $paymentMethodsMap
      * @param $expectedResult
      */
-    public function testGetBillingAgreementMethods($store, $quote, $paymentMethods, $expectedResult)
+    public function testGetBillingAgreementMethods($store, $quote, $paymentMethodsMap, $expectedResult)
     {
-        $this->_paymentDataMock->expects(
-            $this->any()
-        )->method(
-            'getStoreMethods'
-        )->with(
-            $store,
-            $quote
-        )->will(
-            $this->returnValue($paymentMethods)
-        );
+        $this->paymentMethodList->expects(static::once())
+            ->method('getActiveList')
+            ->with($store)
+            ->willReturn(array_column($paymentMethodsMap, 0));
+
+        $this->paymentMethodInstanceFactory->expects(static::any())
+            ->method('create')
+            ->willReturnMap($paymentMethodsMap);
+
         $this->assertEquals($expectedResult, $this->_helper->getBillingAgreementMethods($store, $quote));
     }
 
@@ -84,16 +100,40 @@ class DataTest extends \PHPUnit_Framework_TestCase
     {
         $quoteMock = $this->getMockBuilder(
             \Magento\Quote\Model\Quote::class
-        )->disableOriginalConstructor()->setMethods(
-            null
-        );
-        $methodInterfaceMock = $this->getMockBuilder(
-            \Magento\Paypal\Model\Billing\Agreement\MethodInterface::class
+        )->disableOriginalConstructor()->getMock();
+
+        $methodMock = $this->getMockBuilder(
+            \Magento\Payment\Api\Data\PaymentMethodInterface::class
         )->getMock();
 
+        $agreementMethodInstanceMock = $this->getMockBuilder(
+            \Magento\Paypal\Model\Method\Agreement::class
+        )->disableOriginalConstructor()->getMock();
+        $agreementMethodInstanceMock->expects($this->any())
+            ->method('isAvailable')
+            ->willReturn(true);
+
+        $methodInstanceMock = $this->getMockBuilder(
+            \Magento\Payment\Model\Method\Cc::class
+        )->disableOriginalConstructor()->getMock();
+
         return [
-            ['1', $quoteMock, [$methodInterfaceMock], [$methodInterfaceMock]],
-            ['1', $quoteMock, [new \StdClass()], []]
+            [
+                '1',
+                $quoteMock,
+                [
+                    [$methodMock, $agreementMethodInstanceMock]
+                ],
+                [$agreementMethodInstanceMock]
+            ],
+            [
+                '1',
+                $quoteMock,
+                [
+                    [$methodMock, $methodInstanceMock]
+                ],
+                []
+            ]
         ];
     }
 
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php
index 33065cc22713bcb654f8304505717288a9f21e0b..9816c37fa01267fa1e1952af91c76bab1fd4cf5c 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php
@@ -182,7 +182,9 @@ class ExpressTest extends \PHPUnit_Framework_TestCase
         $data = new DataObject(
             [
                 PaymentInterface::KEY_ADDITIONAL_DATA => [
-                    Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT => $transportValue
+                    Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT => $transportValue,
+                    Express\Checkout::PAYMENT_INFO_TRANSPORT_PAYER_ID => $transportValue,
+                    Express\Checkout::PAYMENT_INFO_TRANSPORT_TOKEN => $transportValue
                 ]
             ]
         );
@@ -202,11 +204,12 @@ class ExpressTest extends \PHPUnit_Framework_TestCase
 
         $this->parentAssignDataExpectation($data);
 
-        $paymentInfo->expects(static::once())
+        $paymentInfo->expects(static::exactly(3))
             ->method('setAdditionalInformation')
-            ->with(
-                Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT,
-                $transportValue
+            ->withConsecutive(
+                [Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT, $transportValue],
+                [Express\Checkout::PAYMENT_INFO_TRANSPORT_PAYER_ID, $transportValue],
+                [Express\Checkout::PAYMENT_INFO_TRANSPORT_TOKEN, $transportValue]
             );
 
         $this->_model->assignData($data);
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php
index a107918d16513b2fdce133e43aef00c5109798c8..2d757147cec6a89dc1b6b6b28f283fd29b98f640 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php
@@ -122,7 +122,7 @@ class TransparentTest extends \PHPUnit_Framework_TestCase
         $this->orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
             ->setMethods([
                 'getCustomerId', 'getBillingAddress', 'getShippingAddress', 'getCustomerEmail',
-                'getId', 'getIncrementId'
+                'getId', 'getIncrementId', 'getBaseCurrencyCode'
             ])
             ->disableOriginalConstructor()
             ->getMock();
@@ -164,6 +164,9 @@ class TransparentTest extends \PHPUnit_Framework_TestCase
         $this->paymentMock->expects($this->once())
             ->method('getOrder')
             ->willReturn($this->orderMock);
+        $this->orderMock->expects($this->once())
+            ->method('getBaseCurrencyCode')
+            ->willReturn('USD');
         $this->orderMock->expects($this->once())
             ->method('getBillingAddress')
             ->willReturn($this->addressBillingMock);
diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml
index b567ac87c4fe253b7b0e6423fb7e25f575521462..98f340f0a271135b241bcdc9a90f2e195f3d76b5 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml
@@ -84,7 +84,7 @@
                 </requires>
             </field>
             <field id="payflowpro_cc_vault_active" translate="label" type="select" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="0">
-                <label>Vault enabled</label>
+                <label>Vault Enabled</label>
                 <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                 <config_path>payment/payflowpro_cc_vault/active</config_path>
                 <attribute type="shared">1</attribute>
diff --git a/app/code/Magento/Paypal/view/adminhtml/web/styles.css b/app/code/Magento/Paypal/view/adminhtml/web/styles.css
index 9e7cfb2afc18e570c18fdc3fe96920606cbe255a..9119a2e317fb75062e74a9e6543732645fab76d5 100644
--- a/app/code/Magento/Paypal/view/adminhtml/web/styles.css
+++ b/app/code/Magento/Paypal/view/adminhtml/web/styles.css
@@ -14,12 +14,11 @@
 .payflow-settings-notice { border:1px solid #d1d0ce;padding:0 10px;margin: 0;}
 .payflow-settings-notice .important-label {color:#f00;}
 .payflow-settings-notice ul.options-list {list-style:disc;padding:0 2em;}
-.paypal-express-section .heading {display: inline-block; background: url("images/pp-logo-200px.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;}
-.paypal-express-section .button-container {display: inline-block; float: right;}
-.paypal-express-section .config-alt {background: url("images/pp-alt.png") no-repeat; height: 26px; margin: .5rem 0 0; width: 158px;}
+.paypal-express-section .heading {background: url("images/pp-logo-200px.png") no-repeat 0 50% / 18rem auto; padding-left: 20rem;}
+.paypal-express-section .button-container {float: right;}
+.paypal-express-section .config-alt {background: url("images/pp-alt.png") no-repeat; height: 26px; margin: 0.5rem 0 0; width: 158px;}
 .paypal-express-section .link-more {margin-left: 5px;}
-.paypal-other-section .heading {display: inline-block;}
-.paypal-other-section .button-container {display: inline-block; float: right; margin: 1rem 0 0 !important;}
+.paypal-other-section .button-container {float: right; margin: 1rem 0 0 !important;}
 .paypal-other-section > .entry-edit-head > a::before {left: auto !important; right: 1.3rem !important;}
 .paypal-all-in-one-section > .entry-edit-head {background: url("images/AM_mc_vs_dc_ae.jpg") no-repeat scroll 0 50% / 18rem auto; padding-left: 18rem;}
 .paypal-gateways-section > .entry-edit-head {background: url("images/pp-payflow-mark.png") no-repeat scroll 0 50% / 18rem auto; padding-left: 18rem;}
diff --git a/app/code/Magento/Sales/Api/Data/CommentInterface.php b/app/code/Magento/Sales/Api/Data/CommentInterface.php
index fcab786319340535c1c6309c884a678a91341d90..6e447e72ab1c21330ecd56ed81c1fd7bd5114865 100644
--- a/app/code/Magento/Sales/Api/Data/CommentInterface.php
+++ b/app/code/Magento/Sales/Api/Data/CommentInterface.php
@@ -23,14 +23,14 @@ interface CommentInterface
     const COMMENT = 'comment';
 
     /**
-     * Gets the comment for the invoice.
+     * Gets the comment text.
      *
      * @return string Comment.
      */
     public function getComment();
 
     /**
-     * Sets the comment for the invoice.
+     * Sets the comment text.
      *
      * @param string $comment
      * @return $this
@@ -38,14 +38,14 @@ interface CommentInterface
     public function setComment($comment);
 
     /**
-     * Gets the is-visible-on-storefront flag value for the invoice.
+     * Gets the is-visible-on-storefront flag value for the comment.
      *
      * @return int Is-visible-on-storefront flag value.
      */
     public function getIsVisibleOnFront();
 
     /**
-     * Sets the is-visible-on-storefront flag value for the invoice.
+     * Sets the is-visible-on-storefront flag value for the comment.
      *
      * @param int $isVisibleOnFront
      * @return $this
diff --git a/app/code/Magento/Sales/Api/Data/CreditmemoCommentCreationInterface.php b/app/code/Magento/Sales/Api/Data/CreditmemoCommentCreationInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..283e8600738e731ee498a444309adc36c8123d21
--- /dev/null
+++ b/app/code/Magento/Sales/Api/Data/CreditmemoCommentCreationInterface.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Api\Data;
+
+use Magento\Framework\Api\ExtensibleDataInterface;
+
+/**
+ * Interface CreditmemoCommentCreationInterface
+ *
+ * @api
+ */
+interface CreditmemoCommentCreationInterface extends ExtensibleDataInterface, CommentInterface
+{
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface|null
+     */
+    public function getExtensionAttributes();
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes
+    );
+}
diff --git a/app/code/Magento/Sales/Api/Data/CreditmemoCreationArgumentsInterface.php b/app/code/Magento/Sales/Api/Data/CreditmemoCreationArgumentsInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..2735a94a777f095ed5f4b2c97e114fe75085ddc8
--- /dev/null
+++ b/app/code/Magento/Sales/Api/Data/CreditmemoCreationArgumentsInterface.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Api\Data;
+
+/**
+ * Interface CreditmemoCreationArgumentsInterface
+ *
+ * @api
+ */
+interface CreditmemoCreationArgumentsInterface
+{
+    /**
+     * Gets the credit memo shipping amount.
+     *
+     * @return float|null Credit memo shipping amount.
+     */
+    public function getShippingAmount();
+
+    /**
+     * Gets the credit memo positive adjustment.
+     *
+     * @return float|null Credit memo positive adjustment.
+     */
+    public function getAdjustmentPositive();
+
+    /**
+     * Gets the credit memo negative adjustment.
+     *
+     * @return float|null Credit memo negative adjustment.
+     */
+    public function getAdjustmentNegative();
+
+    /**
+     * Sets the credit memo shipping amount.
+     *
+     * @param float $amount
+     * @return $this
+     */
+    public function setShippingAmount($amount);
+
+    /**
+     * Sets the credit memo positive adjustment.
+     *
+     * @param float $amount
+     * @return $this
+     */
+    public function setAdjustmentPositive($amount);
+
+    /**
+     * Sets the credit memo negative adjustment.
+     *
+     * @param float $amount
+     * @return $this
+     */
+    public function setAdjustmentNegative($amount);
+
+    /**
+     * Gets existing extension attributes.
+     *
+     * @return \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface|null
+     */
+    public function getExtensionAttributes();
+
+    /**
+     * Sets extension attributes.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface $extensionAttributes
+     *
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface $extensionAttributes
+    );
+}
diff --git a/app/code/Magento/Sales/Api/Data/CreditmemoItemCreationInterface.php b/app/code/Magento/Sales/Api/Data/CreditmemoItemCreationInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..1cd5f3c43be33aa555780af662013a7dda9838d4
--- /dev/null
+++ b/app/code/Magento/Sales/Api/Data/CreditmemoItemCreationInterface.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Api\Data;
+
+use Magento\Framework\Api\ExtensibleDataInterface;
+
+/**
+ * Interface CreditmemoItemCreationInterface
+ * @api
+ */
+interface CreditmemoItemCreationInterface extends LineItemInterface, ExtensibleDataInterface
+{
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface|null
+     */
+    public function getExtensionAttributes();
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes
+    );
+}
diff --git a/app/code/Magento/Sales/Api/Exception/CouldNotRefundExceptionInterface.php b/app/code/Magento/Sales/Api/Exception/CouldNotRefundExceptionInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..dee24735af620f08fdedd21f2c95d2d778235959
--- /dev/null
+++ b/app/code/Magento/Sales/Api/Exception/CouldNotRefundExceptionInterface.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Api\Exception;
+
+/**
+ * @api
+ */
+interface CouldNotRefundExceptionInterface
+{
+}
diff --git a/app/code/Magento/Sales/Api/RefundInvoiceInterface.php b/app/code/Magento/Sales/Api/RefundInvoiceInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..dc86ccbb58006b9d8417776ecf731151ea5225da
--- /dev/null
+++ b/app/code/Magento/Sales/Api/RefundInvoiceInterface.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Api;
+
+/**
+ * Interface RefundInvoiceInterface
+ *
+ * @api
+ */
+interface RefundInvoiceInterface
+{
+    /**
+     * Create refund for invoice
+     *
+     * @param int $invoiceId
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items
+     * @param bool|null $isOnline
+     * @param bool|null $notify
+     * @param bool|null $appendComment
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments
+     * @return int
+     */
+    public function execute(
+        $invoiceId,
+        array $items = [],
+        $isOnline = false,
+        $notify = false,
+        $appendComment = false,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    );
+}
diff --git a/app/code/Magento/Sales/Api/RefundOrderInterface.php b/app/code/Magento/Sales/Api/RefundOrderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..20a782de421989abfa2c1466c7cd9675a6b20008
--- /dev/null
+++ b/app/code/Magento/Sales/Api/RefundOrderInterface.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Api;
+
+/**
+ * Interface RefundOrderInterface
+ *
+ * @api
+ */
+interface RefundOrderInterface
+{
+    /**
+     * Create offline refund for order
+     *
+     * @param int $orderId
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items
+     * @param bool|null $notify
+     * @param bool|null $appendComment
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments
+     * @return int
+     */
+    public function execute(
+        $orderId,
+        array $items = [],
+        $notify = false,
+        $appendComment = false,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    );
+}
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php
index 9a910e48ec0f2e8344c35795dd544cfe9b1dc506..892faf66140180eb791d4bd8ff25c4b4c48ed930 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Billing/Method/Form.php
@@ -25,16 +25,18 @@ class Form extends \Magento\Payment\Block\Form\Container
      * @param \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory
      * @param \Magento\Backend\Model\Session\Quote $sessionQuote
      * @param array $data
+     * @param array $additionalChecks
      */
     public function __construct(
         \Magento\Framework\View\Element\Template\Context $context,
         \Magento\Payment\Helper\Data $paymentHelper,
         \Magento\Payment\Model\Checks\SpecificationFactory $methodSpecificationFactory,
         \Magento\Backend\Model\Session\Quote $sessionQuote,
-        array $data = []
+        array $data = [],
+        array $additionalChecks = []
     ) {
         $this->_sessionQuote = $sessionQuote;
-        parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data);
+        parent::__construct($context, $paymentHelper, $methodSpecificationFactory, $data, $additionalChecks);
     }
 
     /**
diff --git a/app/code/Magento/Sales/Exception/CouldNotRefundException.php b/app/code/Magento/Sales/Exception/CouldNotRefundException.php
new file mode 100644
index 0000000000000000000000000000000000000000..59ef4d18b442eda80a5c72c66282c855ca878bd1
--- /dev/null
+++ b/app/code/Magento/Sales/Exception/CouldNotRefundException.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Exception;
+
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Sales\Api\Exception\CouldNotRefundExceptionInterface;
+
+/**
+ * Class CouldNotRefundException
+ */
+class CouldNotRefundException extends LocalizedException implements CouldNotRefundExceptionInterface
+{
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php
index 02b2826f14f6e4d320e7d5b7ad3a2c9b41ec6710..fa7dd86dbef19568afb2ff84e650518bc629d503 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php
@@ -532,11 +532,24 @@ class Creditmemo extends AbstractModel implements EntityInterface, CreditmemoInt
      */
     public function isLast()
     {
-        foreach ($this->getAllItems() as $item) {
+        $items = $this->getAllItems();
+        foreach ($items as $item) {
             if (!$item->isLast()) {
                 return false;
             }
         }
+
+        if (empty($items)) {
+            $order = $this->getOrder();
+            if ($order) {
+                foreach ($order->getItems() as $orderItem) {
+                    if ($orderItem->canRefund()) {
+                        return false;
+                    }
+                }
+            }
+        }
+
         return true;
     }
 
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CommentCreation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CommentCreation.php
new file mode 100644
index 0000000000000000000000000000000000000000..b110b2dbd3986bae566914eef3e164b2410939f3
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CommentCreation.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface;
+
+/**
+ * Class CommentCreation
+ */
+class CommentCreation implements CreditmemoCommentCreationInterface
+{
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface
+     */
+    private $extensionAttributes;
+
+    /**
+     * @var string
+     */
+    private $comment;
+
+    /**
+     * @var int
+     */
+    private $isVisibleOnFront;
+
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface|null
+     */
+    public function getExtensionAttributes()
+    {
+        return $this->extensionAttributes;
+    }
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationExtensionInterface $extensionAttributes
+    ) {
+        $this->extensionAttributes = $extensionAttributes;
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getComment()
+    {
+        return $this->comment;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setComment($comment)
+    {
+        $this->comment = $comment;
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIsVisibleOnFront()
+    {
+        return $this->isVisibleOnFront;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setIsVisibleOnFront($isVisibleOnFront)
+    {
+        $this->isVisibleOnFront = $isVisibleOnFront;
+        return $this;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreationArguments.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreationArguments.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd082bb1dd474b03704b51aec67eba5ea20b23be
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreationArguments.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface;
+
+/**
+ * Class CreationArguments
+ */
+class CreationArguments implements CreditmemoCreationArgumentsInterface
+{
+    /**
+     * @var float|null
+     */
+    private $shippingAmount;
+
+    /**
+     * @var float|null
+     */
+    private $adjustmentPositive;
+
+    /**
+     * @var float|null
+     */
+    private $adjustmentNegative;
+
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface
+     */
+    private $extensionAttributes;
+
+    /**
+     * @inheritdoc
+     */
+    public function getShippingAmount()
+    {
+        return $this->shippingAmount;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getAdjustmentPositive()
+    {
+        return $this->adjustmentPositive;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getAdjustmentNegative()
+    {
+        return $this->adjustmentNegative;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setShippingAmount($amount)
+    {
+        $this->shippingAmount = $amount;
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setAdjustmentPositive($amount)
+    {
+        $this->adjustmentPositive = $amount;
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setAdjustmentNegative($amount)
+    {
+        $this->adjustmentNegative = $amount;
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getExtensionAttributes()
+    {
+        return $this->extensionAttributes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setExtensionAttributes(
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface $extensionAttributes
+    ) {
+        $this->extensionAttributes = $extensionAttributes;
+
+        return $this;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..e49a08e32d83945f1524fb7edaf2f0b6ca5539df
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidator.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoInterface;
+
+/**
+ * Class CreditmemoValidator
+ */
+class CreditmemoValidator implements CreditmemoValidatorInterface
+{
+    /**
+     * @var \Magento\Sales\Model\Validator
+     */
+    private $validator;
+
+    /**
+     * InvoiceValidatorRunner constructor.
+     * @param \Magento\Sales\Model\Validator $validator
+     */
+    public function __construct(\Magento\Sales\Model\Validator $validator)
+    {
+        $this->validator = $validator;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate(CreditmemoInterface $entity, array $validators)
+    {
+        return $this->validator->validate($entity, $validators);
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..3889f3b985ff02603009084fa956fab65e505556
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Exception\DocumentValidationException;
+use Magento\Sales\Model\ValidatorInterface;
+
+/**
+ * Interface CreditmemoValidatorInterface
+ */
+interface CreditmemoValidatorInterface
+{
+    /**
+     * @param CreditmemoInterface $entity
+     * @param ValidatorInterface[] $validators
+     * @return string[]
+     * @throws DocumentValidationException
+     */
+    public function validate(CreditmemoInterface $entity, array $validators);
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Item/Validation/CreationQuantityValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Item/Validation/CreationQuantityValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..edad1a2520632b31a89e355bb99913d0287df748
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Item/Validation/CreationQuantityValidator.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo\Item\Validation;
+
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\Data\OrderInterfaceFactory;
+use Magento\Sales\Api\Data\OrderItemInterface;
+use Magento\Sales\Api\OrderItemRepositoryInterface;
+use Magento\Sales\Model\Order\Item;
+use Magento\Sales\Model\ValidatorInterface;
+
+/**
+ * Class CreationQuantityValidator
+ */
+class CreationQuantityValidator implements ValidatorInterface
+{
+    /**
+     * @var OrderItemRepositoryInterface
+     */
+    private $orderItemRepository;
+
+    /**
+     * @var OrderInterfaceFactory
+     */
+    private $context;
+
+    /**
+     * ItemCreationQuantityValidator constructor.
+     * @param OrderItemRepositoryInterface $orderItemRepository
+     * @param object $context
+     */
+    public function __construct(OrderItemRepositoryInterface $orderItemRepository, $context = null)
+    {
+        $this->orderItemRepository = $orderItemRepository;
+        $this->context = $context;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate($entity)
+    {
+        try {
+            $orderItem = $this->orderItemRepository->get($entity->getOrderItemId());
+            if (!$this->isItemPartOfContextOrder($orderItem)) {
+                return [__('The creditmemo contains product item that is not part of the original order.')];
+            }
+        } catch (NoSuchEntityException $e) {
+            return [__('The creditmemo contains product item that is not part of the original order.')];
+        }
+
+        if (!$this->isQtyAvailable($orderItem, $entity->getQty())) {
+            return [__('The quantity to refund must not be greater than the unrefunded quantity.')];
+        }
+
+        return [];
+    }
+
+    /**
+     * @param Item $orderItem
+     * @param int $qty
+     * @return bool
+     */
+    private function isQtyAvailable(Item $orderItem, $qty)
+    {
+        return $qty <= $orderItem->getQtyToRefund() || $orderItem->isDummy();
+    }
+
+    /**
+     * @param OrderItemInterface $orderItem
+     * @return bool
+     */
+    private function isItemPartOfContextOrder(OrderItemInterface $orderItem)
+    {
+        return $this->context instanceof OrderInterface && $this->context->getEntityId() === $orderItem->getOrderId();
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php
new file mode 100644
index 0000000000000000000000000000000000000000..10e70402be6e4d4e6848f7f1065d44b22058a3d7
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoItemCreationInterface;
+
+/**
+ * Class LineItem
+ */
+class ItemCreation implements CreditmemoItemCreationInterface
+{
+    /**
+     * @var int
+     */
+    private $orderItemId;
+
+    /**
+     * @var float
+     */
+    private $qty;
+
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface
+     */
+    private $extensionAttributes;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getOrderItemId()
+    {
+        return $this->orderItemId;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setOrderItemId($orderItemId)
+    {
+        $this->orderItemId = $orderItemId;
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getQty()
+    {
+        return $this->qty;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setQty($qty)
+    {
+        $this->qty = $qty;
+        return $this;
+    }
+
+    /**
+     * Retrieve existing extension attributes object or create a new one.
+     *
+     * @return \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface|null
+     */
+    public function getExtensionAttributes()
+    {
+        return $this->extensionAttributes;
+    }
+
+    /**
+     * Set an extension attributes object.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes
+     * @return $this
+     */
+    public function setExtensionAttributes(
+        \Magento\Sales\Api\Data\CreditmemoItemCreationExtensionInterface $extensionAttributes
+    ) {
+        $this->extensionAttributes = $extensionAttributes;
+        return $this;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..f1b8e6d9cab9ba4324d8ca01ae1b9267f6e4c496
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidator.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoItemCreationInterface;
+use Magento\Sales\Api\Data\OrderInterface;
+
+/**
+ * Class ItemCreationValidator
+ */
+class ItemCreationValidator implements ItemCreationValidatorInterface
+{
+    /**
+     * @var \Magento\Sales\Model\Validator
+     */
+    private $validator;
+
+    /**
+     * InvoiceValidatorRunner constructor.
+     * @param \Magento\Sales\Model\Validator $validator
+     */
+    public function __construct(\Magento\Sales\Model\Validator $validator)
+    {
+        $this->validator = $validator;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate(
+        CreditmemoItemCreationInterface $entity,
+        array $validators,
+        OrderInterface $context = null
+    ) {
+        return $this->validator->validate($entity, $validators, $context);
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f8bb84ccd16a175ee23de755169281026dc053b
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+use Magento\Sales\Api\Data\CreditmemoItemCreationInterface;
+use Magento\Sales\Api\Data\OrderInterface;
+
+/**
+ * Interface ItemCreationValidatorInterface
+ */
+interface ItemCreationValidatorInterface
+{
+    /**
+     * @param CreditmemoItemCreationInterface $item
+     * @param array $validators
+     * @param OrderInterface|null $context
+     * @return mixed
+     */
+    public function validate(CreditmemoItemCreationInterface $item, array $validators, OrderInterface $context = null);
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Notifier.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Notifier.php
new file mode 100644
index 0000000000000000000000000000000000000000..47dbecca6b59bdc727f1b58b18e3c88a73c5993f
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Notifier.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+/**
+ * CreditMemo notifier.
+ *
+ * @api
+ */
+class Notifier implements \Magento\Sales\Model\Order\Creditmemo\NotifierInterface
+{
+    /**
+     * @var \Magento\Sales\Model\Order\CreditMemo\SenderInterface[]
+     */
+    private $senders;
+
+    /**
+     * @param \Magento\Sales\Model\Order\CreditMemo\SenderInterface[] $senders
+     */
+    public function __construct(array $senders = [])
+    {
+        $this->senders = $senders;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function notify(
+        \Magento\Sales\Api\Data\OrderInterface $order,
+        \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        $forceSyncMode = false
+    ) {
+        foreach ($this->senders as $sender) {
+            $sender->send($order, $creditmemo, $comment, $forceSyncMode);
+        }
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/NotifierInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/NotifierInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..ef42bd18633cfc21f0b92df28dde52438ed22c52
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/NotifierInterface.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+/**
+ * Interface for CreditMemo notifier.
+ *
+ * @api
+ */
+interface NotifierInterface
+{
+    /**
+     * Notifies customer.
+     *
+     * @param \Magento\Sales\Api\Data\OrderInterface $order
+     * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param bool $forceSyncMode
+     *
+     * @return void
+     */
+    public function notify(
+        \Magento\Sales\Api\Data\OrderInterface $order,
+        \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        $forceSyncMode = false
+    );
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php
index ef60ad2ce51bea1c2610f8ca6289d3cf4686c6c9..f01dd0dc3732e12a287306edd7ed173e604e9dc9 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php
@@ -114,9 +114,8 @@ class RefundOperation
                 $order->getBaseTotalInvoicedCost() - $creditmemo->getBaseCost()
             );
 
-            if ($online) {
-                $order->getPayment()->refund($creditmemo);
-            }
+            $creditmemo->setDoTransaction($online);
+            $order->getPayment()->refund($creditmemo);
 
             $this->eventManager->dispatch('sales_order_creditmemo_refund', ['creditmemo' => $creditmemo]);
         }
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php
new file mode 100644
index 0000000000000000000000000000000000000000..76210505fd4672778cfae0742de64bc990ccac86
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo\Sender;
+
+use Magento\Sales\Model\Order\Email\Sender;
+use Magento\Sales\Model\Order\Creditmemo\SenderInterface;
+
+/**
+ * Email notification sender for Creditmemo.
+ */
+class EmailSender extends Sender implements SenderInterface
+{
+    /**
+     * @var \Magento\Payment\Helper\Data
+     */
+    private $paymentHelper;
+
+    /**
+     * @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo
+     */
+    private $creditmemoResource;
+
+    /**
+     * @var \Magento\Framework\App\Config\ScopeConfigInterface
+     */
+    private $globalConfig;
+
+    /**
+     * @var \Magento\Framework\Event\ManagerInterface
+     */
+    private $eventManager;
+
+    /**
+     * @param \Magento\Sales\Model\Order\Email\Container\Template $templateContainer
+     * @param \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity $identityContainer
+     * @param \Magento\Sales\Model\Order\Email\SenderBuilderFactory $senderBuilderFactory
+     * @param \Psr\Log\LoggerInterface $logger
+     * @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer
+     * @param \Magento\Payment\Helper\Data $paymentHelper
+     * @param \Magento\Sales\Model\ResourceModel\Order\Creditmemo $creditmemoResource
+     * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig
+     * @param \Magento\Framework\Event\ManagerInterface $eventManager
+     */
+    public function __construct(
+        \Magento\Sales\Model\Order\Email\Container\Template $templateContainer,
+        \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity $identityContainer,
+        \Magento\Sales\Model\Order\Email\SenderBuilderFactory $senderBuilderFactory,
+        \Psr\Log\LoggerInterface $logger,
+        \Magento\Sales\Model\Order\Address\Renderer $addressRenderer,
+        \Magento\Payment\Helper\Data $paymentHelper,
+        \Magento\Sales\Model\ResourceModel\Order\Creditmemo $creditmemoResource,
+        \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig,
+        \Magento\Framework\Event\ManagerInterface $eventManager
+    ) {
+        parent::__construct(
+            $templateContainer,
+            $identityContainer,
+            $senderBuilderFactory,
+            $logger,
+            $addressRenderer
+        );
+
+        $this->paymentHelper = $paymentHelper;
+        $this->creditmemoResource = $creditmemoResource;
+        $this->globalConfig = $globalConfig;
+        $this->eventManager = $eventManager;
+    }
+
+    /**
+     * Sends order creditmemo email to the customer.
+     *
+     * Email will be sent immediately in two cases:
+     *
+     * - if asynchronous email sending is disabled in global settings
+     * - if $forceSyncMode parameter is set to TRUE
+     *
+     * Otherwise, email will be sent later during running of
+     * corresponding cron job.
+     *
+     * @param \Magento\Sales\Api\Data\OrderInterface $order
+     * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param bool $forceSyncMode
+     *
+     * @return bool
+     */
+    public function send(
+        \Magento\Sales\Api\Data\OrderInterface $order,
+        \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        $forceSyncMode = false
+    ) {
+        $creditmemo->setSendEmail(true);
+
+        if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) {
+            $transport = [
+                'order' => $order,
+                'creditmemo' => $creditmemo,
+                'comment' => $comment ? $comment->getComment() : '',
+                'billing' => $order->getBillingAddress(),
+                'payment_html' => $this->getPaymentHtml($order),
+                'store' => $order->getStore(),
+                'formattedShippingAddress' => $this->getFormattedShippingAddress($order),
+                'formattedBillingAddress' => $this->getFormattedBillingAddress($order),
+            ];
+
+            $this->eventManager->dispatch(
+                'email_creditmemo_set_template_vars_before',
+                ['sender' => $this, 'transport' => $transport]
+            );
+
+            $this->templateContainer->setTemplateVars($transport);
+
+            if ($this->checkAndSend($order)) {
+                $creditmemo->setEmailSent(true);
+
+                $this->creditmemoResource->saveAttribute($creditmemo, ['send_email', 'email_sent']);
+
+                return true;
+            }
+        } else {
+            $creditmemo->setEmailSent(null);
+
+            $this->creditmemoResource->saveAttribute($creditmemo, 'email_sent');
+        }
+
+        $this->creditmemoResource->saveAttribute($creditmemo, 'send_email');
+
+        return false;
+    }
+
+    /**
+     * Returns payment info block as HTML.
+     *
+     * @param \Magento\Sales\Api\Data\OrderInterface $order
+     *
+     * @return string
+     */
+    private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order)
+    {
+        return $this->paymentHelper->getInfoBlockHtml(
+            $order->getPayment(),
+            $this->identityContainer->getStore()->getStoreId()
+        );
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/SenderInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/SenderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..00d316a8ec98aa79f3ef991b187e9070f2c15901
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/SenderInterface.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo;
+
+/**
+ * Interface for notification sender for CreditMemo.
+ */
+interface SenderInterface
+{
+    /**
+     * Sends notification to a customer.
+     *
+     * @param \Magento\Sales\Api\Data\OrderInterface $order
+     * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param bool $forceSyncMode
+     *
+     * @return bool
+     */
+    public function send(
+        \Magento\Sales\Api\Data\OrderInterface $order,
+        \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        $forceSyncMode = false
+    );
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..82fd0479166e5257730b9c0ec83079eb5d13b37e
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php
@@ -0,0 +1,253 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo\Validation;
+
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\Data\OrderItemInterface;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Api\InvoiceRepositoryInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Creditmemo;
+use Magento\Sales\Model\Order\Item;
+use Magento\Sales\Model\ValidatorInterface;
+
+/**
+ * Class QuantityValidator
+ */
+class QuantityValidator implements ValidatorInterface
+{
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepository;
+
+    /**
+     * @var InvoiceRepositoryInterface
+     */
+    private $invoiceRepository;
+
+    /**
+     * @var \Magento\Framework\Pricing\PriceCurrencyInterface
+     */
+    private $priceCurrency;
+
+    /**
+     * InvoiceValidator constructor.
+     *
+     * @param OrderRepositoryInterface $orderRepository
+     * @param InvoiceRepositoryInterface $invoiceRepository
+     * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
+     */
+    public function __construct(
+        OrderRepositoryInterface $orderRepository,
+        InvoiceRepositoryInterface $invoiceRepository,
+        \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
+    ) {
+        $this->orderRepository = $orderRepository;
+        $this->invoiceRepository = $invoiceRepository;
+        $this->priceCurrency = $priceCurrency;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate($entity)
+    {
+        /**
+         * @var $entity CreditmemoInterface
+         */
+        if ($entity->getOrderId() === null) {
+            return [__('Order Id is required for creditmemo document')];
+        }
+
+        $messages = [];
+
+        $order = $this->orderRepository->get($entity->getOrderId());
+        $orderItemsById = $this->getOrderItems($order);
+        $invoiceQtysRefundLimits = $this->getInvoiceQtysRefundLimits($entity, $order);
+
+        $totalQuantity = 0;
+        foreach ($entity->getItems() as $item) {
+            if (!isset($orderItemsById[$item->getOrderItemId()])) {
+                $messages[] = __(
+                    'The creditmemo contains product SKU "%1" that is not part of the original order.',
+                    $item->getSku()
+                );
+                continue;
+            }
+            $orderItem = $orderItemsById[$item->getOrderItemId()];
+
+            if (
+                !$this->canRefundItem($orderItem, $item->getQty(), $invoiceQtysRefundLimits) ||
+                !$this->isQtyAvailable($orderItem, $item->getQty())
+            ) {
+                $messages[] =__(
+                    'The quantity to creditmemo must not be greater than the unrefunded quantity'
+                    . ' for product SKU "%1".',
+                    $orderItem->getSku()
+                );
+            } else {
+                $totalQuantity += $item->getQty();
+            }
+        }
+
+        if ($entity->getGrandTotal() <= 0) {
+            $messages[] = __('The credit memo\'s total must be positive.');
+        } elseif ($totalQuantity <= 0 && !$this->canRefundShipping($order)) {
+            $messages[] = __('You can\'t create a creditmemo without products.');
+        }
+
+        return $messages;
+    }
+
+    /**
+     * We can have problem with float in php (on some server $a=762.73;$b=762.73; $a-$b!=0)
+     * for this we have additional diapason for 0
+     * TotalPaid - contains amount, that were not rounded.
+     *
+     * @param OrderInterface $order
+     * @return bool
+     */
+    private function canRefundShipping(OrderInterface $order)
+    {
+        return !abs($this->priceCurrency->round($order->getShippingAmount()) - $order->getShippingRefunded()) < .0001;
+    }
+
+    /**
+     * @param CreditmemoInterface $creditmemo
+     * @param OrderInterface $order
+     * @return array
+     */
+    private function getInvoiceQtysRefundLimits(CreditmemoInterface $creditmemo, OrderInterface $order)
+    {
+        $invoiceQtysRefundLimits = [];
+        if ($creditmemo->getInvoiceId() !== null) {
+            $invoiceQtysRefunded = [];
+            $invoice = $this->invoiceRepository->get($creditmemo->getInvoiceId());
+            foreach ($order->getCreditmemosCollection() as $createdCreditmemo) {
+                if (
+                    $createdCreditmemo->getState() != Creditmemo::STATE_CANCELED &&
+                    $createdCreditmemo->getInvoiceId() == $invoice->getId()
+                ) {
+                    foreach ($createdCreditmemo->getAllItems() as $createdCreditmemoItem) {
+                        $orderItemId = $createdCreditmemoItem->getOrderItem()->getId();
+                        if (isset($invoiceQtysRefunded[$orderItemId])) {
+                            $invoiceQtysRefunded[$orderItemId] += $createdCreditmemoItem->getQty();
+                        } else {
+                            $invoiceQtysRefunded[$orderItemId] = $createdCreditmemoItem->getQty();
+                        }
+                    }
+                }
+            }
+
+            foreach ($invoice->getItems() as $invoiceItem) {
+                $invoiceQtyCanBeRefunded = $invoiceItem->getQty();
+                $orderItemId = $invoiceItem->getOrderItem()->getId();
+                if (isset($invoiceQtysRefunded[$orderItemId])) {
+                    $invoiceQtyCanBeRefunded = $invoiceQtyCanBeRefunded - $invoiceQtysRefunded[$orderItemId];
+                }
+                $invoiceQtysRefundLimits[$orderItemId] = $invoiceQtyCanBeRefunded;
+            }
+        }
+
+        return $invoiceQtysRefundLimits;
+    }
+
+    /**
+     * @param OrderInterface $order
+     * @return OrderItemInterface[]
+     */
+    private function getOrderItems(OrderInterface $order)
+    {
+        $orderItemsById = [];
+        foreach ($order->getItems() as $item) {
+            $orderItemsById[$item->getItemId()] = $item;
+        }
+
+        return $orderItemsById;
+    }
+
+    /**
+     * @param Item $orderItem
+     * @param int $qty
+     * @return bool
+     */
+    private function isQtyAvailable(Item $orderItem, $qty)
+    {
+        return $qty <= $orderItem->getQtyToRefund() || $orderItem->isDummy();
+    }
+
+    /**
+     * Check if order item can be refunded
+     *
+     * @param \Magento\Sales\Model\Order\Item $item
+     * @param double $qty
+     * @param array $invoiceQtysRefundLimits
+     * @return bool
+     */
+    private function canRefundItem(\Magento\Sales\Model\Order\Item $item, $qty, array $invoiceQtysRefundLimits)
+    {
+        if ($item->isDummy()) {
+            return $this->canRefundDummyItem($item, $qty, $invoiceQtysRefundLimits);
+        }
+
+        return $this->canRefundNoDummyItem($item, $invoiceQtysRefundLimits);
+    }
+
+    /**
+     * Check if no dummy order item can be refunded
+     *
+     * @param \Magento\Sales\Model\Order\Item $item
+     * @param array $invoiceQtysRefundLimits
+     * @return bool
+     */
+    private function canRefundNoDummyItem(\Magento\Sales\Model\Order\Item $item, array $invoiceQtysRefundLimits = [])
+    {
+        if ($item->getQtyToRefund() < 0) {
+            return false;
+        }
+        if (isset($invoiceQtysRefundLimits[$item->getId()])) {
+            return $invoiceQtysRefundLimits[$item->getId()] > 0;
+        }
+        return true;
+    }
+
+    /**
+     * @param Item $item
+     * @param int $qty
+     * @param array $invoiceQtysRefundLimits
+     * @return bool
+     */
+    private function canRefundDummyItem(\Magento\Sales\Model\Order\Item $item, $qty, array $invoiceQtysRefundLimits)
+    {
+        if ($item->getHasChildren()) {
+            foreach ($item->getChildrenItems() as $child) {
+                if ($this->canRefundRequestedQty($child, $qty, $invoiceQtysRefundLimits)) {
+                    return true;
+                }
+            }
+        } elseif ($item->getParentItem()) {
+            return $this->canRefundRequestedQty($item->getParentItem(), $qty, $invoiceQtysRefundLimits);
+        }
+
+        return false;
+    }
+
+    /**
+     * @param Item $item
+     * @param int $qty
+     * @param array $invoiceQtysRefundLimits
+     * @return bool
+     */
+    private function canRefundRequestedQty(
+        \Magento\Sales\Model\Order\Item $item,
+        $qty,
+        array $invoiceQtysRefundLimits
+    ) {
+        return $qty === null ? $this->canRefundNoDummyItem($item, $invoiceQtysRefundLimits) : $qty > 0;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/TotalsValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/TotalsValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..2cefc377b0674392b69c57d47d96ee418d57d788
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/TotalsValidator.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Creditmemo\Validation;
+
+use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Sales\Model\ValidatorInterface;
+
+/**
+ * Class TotalsValidator
+ */
+class TotalsValidator implements ValidatorInterface
+{
+    /**
+     * @var PriceCurrencyInterface
+     */
+    private $priceCurrency;
+
+    /**
+     * TotalsValidator constructor.
+     *
+     * @param PriceCurrencyInterface $priceCurrency
+     */
+    public function __construct(PriceCurrencyInterface $priceCurrency)
+    {
+        $this->priceCurrency = $priceCurrency;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validate($entity)
+    {
+        $messages = [];
+        $baseOrderRefund = $this->priceCurrency->round(
+            $entity->getOrder()->getBaseTotalRefunded() + $entity->getBaseGrandTotal()
+        );
+        if ($baseOrderRefund > $this->priceCurrency->round($entity->getOrder()->getBaseTotalPaid())) {
+            $baseAvailableRefund = $entity->getOrder()->getBaseTotalPaid()
+                - $entity->getOrder()->getBaseTotalRefunded();
+
+            $messages[] = __(
+                'The most money available to refund is %1.',
+                $baseAvailableRefund
+            );
+        }
+
+        return $messages;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..469b226053cdd4e2b0658dcd596d7d740572f0c7
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php
@@ -0,0 +1,151 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order;
+
+/**
+ * Class CreditmemoDocumentFactory
+ */
+class CreditmemoDocumentFactory
+{
+    /**
+     * @var \Magento\Sales\Model\Order\CreditmemoFactory
+     */
+    private $creditmemoFactory;
+
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory
+     */
+    private $commentFactory;
+
+    /**
+     * @var \Magento\Framework\EntityManager\HydratorPool
+     */
+    private $hydratorPool;
+
+    /**
+     * @var \Magento\Sales\Api\OrderRepositoryInterface
+     */
+    private $orderRepository;
+
+    /**
+     * CreditmemoDocumentFactory constructor.
+     *
+     * @param \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory $commentFactory
+     * @param \Magento\Framework\EntityManager\HydratorPool $hydratorPool
+     * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
+     */
+    public function __construct(
+        \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory,
+        \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory $commentFactory,
+        \Magento\Framework\EntityManager\HydratorPool $hydratorPool,
+        \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
+    ) {
+        $this->creditmemoFactory = $creditmemoFactory;
+        $this->commentFactory = $commentFactory;
+        $this->hydratorPool = $hydratorPool;
+        $this->orderRepository = $orderRepository;
+    }
+
+    /**
+     * Get array with original data for new Creditmemo document
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items
+     * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments
+     * @return array
+     */
+    private function getCreditmemoCreationData(
+        array $items = [],
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    ) {
+        $data = ['qtys' => []];
+        foreach ($items as $item) {
+            $data['qtys'][$item->getOrderItemId()] = $item->getQty();
+        }
+        if ($arguments) {
+            $hydrator = $this->hydratorPool->getHydrator(
+                \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface::class
+            );
+            $data = array_merge($hydrator->extract($arguments), $data);
+        }
+        return $data;
+    }
+
+    /**
+     *  Attach comment to the Creditmemo document.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment
+     * @param bool $appendComment
+     * @return \Magento\Sales\Api\Data\CreditmemoInterface
+     */
+    private function attachComment(
+        \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment,
+        $appendComment = false
+    ) {
+        $commentData = $this->hydratorPool->getHydrator(
+            \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface::class
+        )->extract($comment);
+        $comment = $this->commentFactory->create(['data' => $commentData]);
+        $comment->setParentId($creditmemo->getEntityId())
+            ->setStoreId($creditmemo->getStoreId())
+            ->setCreditmemo($creditmemo)
+            ->setIsCustomerNotified($appendComment);
+        $creditmemo->setComments([$comment]);
+        return $creditmemo;
+
+    }
+
+    /**
+     * Create new Creditmemo
+     * @param \Magento\Sales\Api\Data\OrderInterface $order
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param bool|null $appendComment
+     * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments
+     * @return \Magento\Sales\Api\Data\CreditmemoInterface
+     */
+    public function createFromOrder(
+        \Magento\Sales\Api\Data\OrderInterface $order,
+        array $items = [],
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        $appendComment = false,
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    ) {
+        $data = $this->getCreditmemoCreationData($items, $arguments);
+        $creditmemo = $this->creditmemoFactory->createByOrder($order, $data);
+        if ($comment) {
+            $creditmemo = $this->attachComment($creditmemo, $comment, $appendComment);
+        }
+        return $creditmemo;
+    }
+
+    /**
+     * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice
+     * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items
+     * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment
+     * @param bool|null $appendComment
+     * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments
+     * @return \Magento\Sales\Api\Data\CreditmemoInterface
+     */
+    public function createFromInvoice(
+        \Magento\Sales\Api\Data\InvoiceInterface $invoice,
+        array $items = [],
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        $appendComment = false,
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    ) {
+        $data = $this->getCreditmemoCreationData($items, $arguments);
+        /** @var $invoice \Magento\Sales\Model\Order\Invoice */
+        $invoice->setOrder($this->orderRepository->get($invoice->getOrderId()));
+        $creditmemo = $this->creditmemoFactory->createByInvoice($invoice, $data);
+        if ($comment) {
+            $creditmemo = $this->attachComment($creditmemo, $comment, $appendComment);
+        }
+        return $creditmemo;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php b/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e68cade3caa7e1a83ad1b3522eab2d43d2672cc
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Invoice\Validation;
+
+use Magento\Payment\Model\InfoInterface;
+use Magento\Payment\Model\MethodInterface;
+use Magento\Sales\Api\Data\InvoiceInterface;
+use Magento\Sales\Api\OrderPaymentRepositoryInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\Order\Invoice;
+use Magento\Sales\Model\ValidatorInterface;
+
+/**
+ * Class CanRefund
+ */
+class CanRefund implements ValidatorInterface
+{
+    /**
+     * @var OrderPaymentRepositoryInterface
+     */
+    private $paymentRepository;
+
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepository;
+
+    /**
+     * CanRefund constructor.
+     *
+     * @param OrderPaymentRepositoryInterface $paymentRepository
+     * @param OrderRepositoryInterface $orderRepository
+     */
+    public function __construct(
+        OrderPaymentRepositoryInterface $paymentRepository,
+        OrderRepositoryInterface $orderRepository
+    ) {
+        $this->paymentRepository = $paymentRepository;
+        $this->orderRepository = $orderRepository;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate($entity)
+    {
+        if (
+            $entity->getState() == Invoice::STATE_PAID &&
+            $this->isGrandTotalEnoughToRefund($entity) &&
+            $this->isPaymentAllowRefund($entity)
+        ) {
+            return [];
+        }
+
+        return [__('We can\'t create creditmemo for the invoice.')];
+    }
+
+    /**
+     * @param InvoiceInterface $invoice
+     * @return bool
+     */
+    private function isPaymentAllowRefund(InvoiceInterface $invoice)
+    {
+        $order = $this->orderRepository->get($invoice->getOrderId());
+        $payment = $order->getPayment();
+        if (!$payment instanceof InfoInterface) {
+            return false;
+        }
+        $method = $payment->getMethodInstance();
+        return $this->canPartialRefund($method, $payment) || $this->canFullRefund($invoice, $method);
+    }
+
+    /**
+     * @param InvoiceInterface $entity
+     * @return bool
+     */
+    private function isGrandTotalEnoughToRefund(InvoiceInterface $entity)
+    {
+        return abs($entity->getBaseGrandTotal() - $entity->getBaseTotalRefunded()) >= .0001;
+    }
+
+    /**
+     * @param MethodInterface $method
+     * @param InfoInterface $payment
+     * @return bool
+     */
+    private function canPartialRefund(MethodInterface $method, InfoInterface $payment)
+    {
+        return $method->canRefund() &&
+        $method->canRefundPartialPerInvoice() &&
+        $payment->getAmountPaid() > $payment->getAmountRefunded();
+    }
+
+    /**
+     * @param InvoiceInterface $invoice
+     * @param MethodInterface $method
+     * @return bool
+     */
+    private function canFullRefund(InvoiceInterface $invoice, MethodInterface $method)
+    {
+        return $method->canRefund() && !$invoice->getIsUsedForRefund();
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php b/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6fc1a0d705e8d49f49918f7919ec5cfaa3ee199
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model\Order\Validation;
+
+use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\ValidatorInterface;
+
+/**
+ * Class CanRefund
+ */
+class CanRefund implements ValidatorInterface
+{
+    /**
+     * @var PriceCurrencyInterface
+     */
+    private $priceCurrency;
+
+    /**
+     * CanRefund constructor.
+     *
+     * @param PriceCurrencyInterface $priceCurrency
+     */
+    public function __construct(PriceCurrencyInterface $priceCurrency)
+    {
+        $this->priceCurrency = $priceCurrency;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate($entity)
+    {
+        $messages = [];
+        if ($entity->getState() === Order::STATE_PAYMENT_REVIEW ||
+            $entity->getState() === Order::STATE_HOLDED ||
+            $entity->getState() === Order::STATE_CANCELED ||
+            $entity->getState() === Order::STATE_CLOSED
+        ) {
+            $messages[] = __(
+                'A creditmemo can not be created when an order has a status of %1',
+                $entity->getStatus()
+            );
+        } elseif (!$this->isTotalPaidEnoughForRefund($entity)) {
+            $messages[] = __('The order does not allow a creditmemo to be created.');
+        }
+
+        return $messages;
+    }
+
+    /**
+     * We can have problem with float in php (on some server $a=762.73;$b=762.73; $a-$b!=0)
+     * for this we have additional diapason for 0
+     * TotalPaid - contains amount, that were not rounded.
+     *
+     * @param OrderInterface $order
+     * @return bool
+     */
+    private function isTotalPaidEnoughForRefund(OrderInterface $order)
+    {
+        return !abs($this->priceCurrency->round($order->getTotalPaid()) - $order->getTotalRefunded()) < .0001;
+    }
+}
diff --git a/app/code/Magento/Sales/Model/RefundInvoice.php b/app/code/Magento/Sales/Model/RefundInvoice.php
new file mode 100644
index 0000000000000000000000000000000000000000..37bf915aa505c6a757208b40a2c888515c6c8bc0
--- /dev/null
+++ b/app/code/Magento/Sales/Model/RefundInvoice.php
@@ -0,0 +1,252 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Sales\Api\CreditmemoRepositoryInterface;
+use Magento\Sales\Api\InvoiceRepositoryInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Api\RefundInvoiceInterface;
+use Magento\Sales\Model\Order\Config as OrderConfig;
+use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface;
+use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface;
+use Magento\Sales\Model\Order\Creditmemo\NotifierInterface;
+use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator;
+use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator;
+use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator;
+use Magento\Sales\Model\Order\CreditmemoDocumentFactory;
+use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface;
+use Magento\Sales\Model\Order\OrderStateResolverInterface;
+use Magento\Sales\Model\Order\OrderValidatorInterface;
+use Magento\Sales\Model\Order\PaymentAdapterInterface;
+use Magento\Sales\Model\Order\Validation\CanRefund;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class RefundInvoice
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class RefundInvoice implements RefundInvoiceInterface
+{
+    /**
+     * @var ResourceConnection
+     */
+    private $resourceConnection;
+
+    /**
+     * @var OrderStateResolverInterface
+     */
+    private $orderStateResolver;
+
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepository;
+
+    /**
+     * @var InvoiceRepositoryInterface
+     */
+    private $invoiceRepository;
+
+    /**
+     * @var OrderValidatorInterface
+     */
+    private $orderValidator;
+
+    /**
+     * @var InvoiceValidatorInterface
+     */
+    private $invoiceValidator;
+
+    /**
+     * @var CreditmemoValidatorInterface
+     */
+    private $creditmemoValidator;
+
+    /**
+     * @var ItemCreationValidatorInterface
+     */
+    private $itemCreationValidator;
+
+    /**
+     * @var CreditmemoRepositoryInterface
+     */
+    private $creditmemoRepository;
+
+    /**
+     * @var Order\PaymentAdapterInterface
+     */
+    private $paymentAdapter;
+
+    /**
+     * @var CreditmemoDocumentFactory
+     */
+    private $creditmemoDocumentFactory;
+
+    /**
+     * @var Order\Creditmemo\NotifierInterface
+     */
+    private $notifier;
+
+    /**
+     * @var OrderConfig
+     */
+    private $config;
+
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    /**
+     * RefundInvoice constructor.
+     *
+     * @param ResourceConnection $resourceConnection
+     * @param OrderStateResolverInterface $orderStateResolver
+     * @param OrderRepositoryInterface $orderRepository
+     * @param InvoiceRepositoryInterface $invoiceRepository
+     * @param OrderValidatorInterface $orderValidator
+     * @param InvoiceValidatorInterface $invoiceValidator
+     * @param CreditmemoValidatorInterface $creditmemoValidator
+     * @param Order\Creditmemo\ItemCreationValidatorInterface $itemCreationValidator
+     * @param CreditmemoRepositoryInterface $creditmemoRepository
+     * @param PaymentAdapterInterface $paymentAdapter
+     * @param CreditmemoDocumentFactory $creditmemoDocumentFactory
+     * @param NotifierInterface $notifier
+     * @param OrderConfig $config
+     * @param LoggerInterface $logger
+     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+     */
+    public function __construct(
+        ResourceConnection $resourceConnection,
+        OrderStateResolverInterface $orderStateResolver,
+        OrderRepositoryInterface $orderRepository,
+        InvoiceRepositoryInterface $invoiceRepository,
+        OrderValidatorInterface $orderValidator,
+        InvoiceValidatorInterface $invoiceValidator,
+        CreditmemoValidatorInterface $creditmemoValidator,
+        ItemCreationValidatorInterface $itemCreationValidator,
+        CreditmemoRepositoryInterface $creditmemoRepository,
+        PaymentAdapterInterface $paymentAdapter,
+        CreditmemoDocumentFactory $creditmemoDocumentFactory,
+        NotifierInterface $notifier,
+        OrderConfig $config,
+        LoggerInterface $logger
+    ) {
+        $this->resourceConnection = $resourceConnection;
+        $this->orderStateResolver = $orderStateResolver;
+        $this->orderRepository = $orderRepository;
+        $this->invoiceRepository = $invoiceRepository;
+        $this->orderValidator = $orderValidator;
+        $this->creditmemoValidator = $creditmemoValidator;
+        $this->itemCreationValidator = $itemCreationValidator;
+        $this->creditmemoRepository = $creditmemoRepository;
+        $this->paymentAdapter = $paymentAdapter;
+        $this->creditmemoDocumentFactory = $creditmemoDocumentFactory;
+        $this->notifier = $notifier;
+        $this->config = $config;
+        $this->logger = $logger;
+        $this->invoiceValidator = $invoiceValidator;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function execute(
+        $invoiceId,
+        array $items = [],
+        $isOnline = false,
+        $notify = false,
+        $appendComment = false,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    ) {
+        $connection = $this->resourceConnection->getConnection('sales');
+        $invoice = $this->invoiceRepository->get($invoiceId);
+        $order = $this->orderRepository->get($invoice->getOrderId());
+        $creditmemo = $this->creditmemoDocumentFactory->createFromInvoice(
+            $invoice,
+            $items,
+            $comment,
+            ($appendComment && $notify),
+            $arguments
+        );
+        $orderValidationResult = $this->orderValidator->validate(
+            $order,
+            [
+                CanRefund::class
+            ]
+        );
+        $invoiceValidationResult = $this->invoiceValidator->validate(
+            $invoice,
+            [
+                \Magento\Sales\Model\Order\Invoice\Validation\CanRefund::class
+            ]
+        );
+        $creditmemoValidationResult = $this->creditmemoValidator->validate(
+            $creditmemo,
+            [
+                QuantityValidator::class,
+                TotalsValidator::class
+            ]
+        );
+        $itemsValidation = [];
+        foreach ($items as $item) {
+            $itemsValidation = array_merge(
+                $itemsValidation,
+                $this->itemCreationValidator->validate(
+                    $item,
+                    [CreationQuantityValidator::class],
+                    $order
+                )
+            );
+        }
+        $validationMessages = array_merge(
+            $orderValidationResult,
+            $invoiceValidationResult,
+            $creditmemoValidationResult,
+            $itemsValidation
+        );
+        if (!empty($validationMessages )) {
+            throw new \Magento\Sales\Exception\DocumentValidationException(
+                __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages))
+            );
+        }
+        $connection->beginTransaction();
+        try {
+            $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED);
+            $order = $this->paymentAdapter->refund($creditmemo, $order, $isOnline);
+            $order->setState(
+                $this->orderStateResolver->getStateForOrder($order, [])
+            );
+            $order->setStatus($this->config->getStateDefaultStatus($order->getState()));
+            if (!$isOnline) {
+                $invoice->setIsUsedForRefund(true);
+                $invoice->setBaseTotalRefunded(
+                    $invoice->getBaseTotalRefunded() + $creditmemo->getBaseGrandTotal()
+                );
+            }
+            $this->invoiceRepository->save($invoice);
+            $order = $this->orderRepository->save($order);
+            $creditmemo = $this->creditmemoRepository->save($creditmemo);
+            $connection->commit();
+        } catch (\Exception $e) {
+            $this->logger->critical($e);
+            $connection->rollBack();
+            throw new \Magento\Sales\Exception\CouldNotRefundException(
+                __('Could not save a Creditmemo, see error log for details')
+            );
+        }
+        if ($notify) {
+            if (!$appendComment) {
+                $comment = null;
+            }
+            $this->notifier->notify($order, $creditmemo, $comment);
+        }
+
+        return $creditmemo->getEntityId();
+    }
+}
diff --git a/app/code/Magento/Sales/Model/RefundOrder.php b/app/code/Magento/Sales/Model/RefundOrder.php
new file mode 100644
index 0000000000000000000000000000000000000000..65d81df0d1de9fc4d2b2a7803369a0eda440ba12
--- /dev/null
+++ b/app/code/Magento/Sales/Model/RefundOrder.php
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Model;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Sales\Api\CreditmemoRepositoryInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Api\RefundOrderInterface;
+use Magento\Sales\Model\Order\Config as OrderConfig;
+use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface;
+use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface;
+use Magento\Sales\Model\Order\Creditmemo\NotifierInterface;
+use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator;
+use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator;
+use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator;
+use Magento\Sales\Model\Order\CreditmemoDocumentFactory;
+use Magento\Sales\Model\Order\OrderStateResolverInterface;
+use Magento\Sales\Model\Order\OrderValidatorInterface;
+use Magento\Sales\Model\Order\PaymentAdapterInterface;
+use Magento\Sales\Model\Order\Validation\CanRefund;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class RefundOrder
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class RefundOrder implements RefundOrderInterface
+{
+    /**
+     * @var ResourceConnection
+     */
+    private $resourceConnection;
+
+    /**
+     * @var OrderStateResolverInterface
+     */
+    private $orderStateResolver;
+
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepository;
+
+    /**
+     * @var OrderValidatorInterface
+     */
+    private $orderValidator;
+
+    /**
+     * @var CreditmemoValidatorInterface
+     */
+    private $creditmemoValidator;
+
+    /**
+     * @var Order\Creditmemo\ItemCreationValidatorInterface
+     */
+    private $itemCreationValidator;
+
+    /**
+     * @var CreditmemoRepositoryInterface
+     */
+    private $creditmemoRepository;
+
+    /**
+     * @var Order\PaymentAdapterInterface
+     */
+    private $paymentAdapter;
+
+    /**
+     * @var CreditmemoDocumentFactory
+     */
+    private $creditmemoDocumentFactory;
+
+    /**
+     * @var Order\Creditmemo\NotifierInterface
+     */
+    private $notifier;
+
+    /**
+     * @var OrderConfig
+     */
+    private $config;
+
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    /**
+     * RefundOrder constructor.
+     * @param ResourceConnection $resourceConnection
+     * @param OrderStateResolverInterface $orderStateResolver
+     * @param OrderRepositoryInterface $orderRepository
+     * @param OrderValidatorInterface $orderValidator
+     * @param CreditmemoValidatorInterface $creditmemoValidator
+     * @param ItemCreationValidatorInterface $itemCreationValidator
+     * @param CreditmemoRepositoryInterface $creditmemoRepository
+     * @param PaymentAdapterInterface $paymentAdapter
+     * @param CreditmemoDocumentFactory $creditmemoDocumentFactory
+     * @param NotifierInterface $notifier
+     * @param OrderConfig $config
+     * @param LoggerInterface $logger
+     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+     */
+    public function __construct(
+        ResourceConnection $resourceConnection,
+        OrderStateResolverInterface $orderStateResolver,
+        OrderRepositoryInterface $orderRepository,
+        OrderValidatorInterface $orderValidator,
+        CreditmemoValidatorInterface $creditmemoValidator,
+        ItemCreationValidatorInterface $itemCreationValidator,
+        CreditmemoRepositoryInterface $creditmemoRepository,
+        PaymentAdapterInterface $paymentAdapter,
+        CreditmemoDocumentFactory $creditmemoDocumentFactory,
+        NotifierInterface $notifier,
+        OrderConfig $config,
+        LoggerInterface $logger
+    ) {
+        $this->resourceConnection = $resourceConnection;
+        $this->orderStateResolver = $orderStateResolver;
+        $this->orderRepository = $orderRepository;
+        $this->orderValidator = $orderValidator;
+        $this->creditmemoValidator = $creditmemoValidator;
+        $this->itemCreationValidator = $itemCreationValidator;
+        $this->creditmemoRepository = $creditmemoRepository;
+        $this->paymentAdapter = $paymentAdapter;
+        $this->creditmemoDocumentFactory = $creditmemoDocumentFactory;
+        $this->notifier = $notifier;
+        $this->config = $config;
+        $this->logger = $logger;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function execute(
+        $orderId,
+        array $items = [],
+        $notify = false,
+        $appendComment = false,
+        \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null,
+        \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null
+    ) {
+        $connection = $this->resourceConnection->getConnection('sales');
+        $order = $this->orderRepository->get($orderId);
+        $creditmemo = $this->creditmemoDocumentFactory->createFromOrder(
+            $order,
+            $items,
+            $comment,
+            ($appendComment && $notify),
+            $arguments
+        );
+        $orderValidationResult = $this->orderValidator->validate(
+            $order,
+            [
+                CanRefund::class
+            ]
+        );
+        $creditmemoValidationResult = $this->creditmemoValidator->validate(
+            $creditmemo,
+            [
+                QuantityValidator::class,
+                TotalsValidator::class
+            ]
+        );
+        $itemsValidation = [];
+        foreach ($items as $item) {
+            $itemsValidation = array_merge(
+                $itemsValidation,
+                $this->itemCreationValidator->validate(
+                    $item,
+                    [CreationQuantityValidator::class],
+                    $order
+                )
+            );
+        }
+        $validationMessages = array_merge($orderValidationResult, $creditmemoValidationResult, $itemsValidation);
+        if (!empty($validationMessages)) {
+            throw new \Magento\Sales\Exception\DocumentValidationException(
+                __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages))
+            );
+        }
+        $connection->beginTransaction();
+        try {
+            $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED);
+            $order = $this->paymentAdapter->refund($creditmemo, $order);
+            $order->setState(
+                $this->orderStateResolver->getStateForOrder($order, [])
+            );
+            $order->setStatus($this->config->getStateDefaultStatus($order->getState()));
+
+            $order = $this->orderRepository->save($order);
+            $creditmemo = $this->creditmemoRepository->save($creditmemo);
+            $connection->commit();
+        } catch (\Exception $e) {
+            $this->logger->critical($e);
+            $connection->rollBack();
+            throw new \Magento\Sales\Exception\CouldNotRefundException(
+                __('Could not save a Creditmemo, see error log for details')
+            );
+        }
+        if ($notify) {
+            if (!$appendComment) {
+                $comment = null;
+            }
+            $this->notifier->notify($order, $creditmemo, $comment);
+        }
+
+        return $creditmemo->getEntityId();
+    }
+}
diff --git a/app/code/Magento/Sales/Model/Validator.php b/app/code/Magento/Sales/Model/Validator.php
index b8d57ded2970209d5d2184945fbb22f13de47dbd..10aa0735b952e99e0d889b27ff378411d0d1d6d1 100644
--- a/app/code/Magento/Sales/Model/Validator.php
+++ b/app/code/Magento/Sales/Model/Validator.php
@@ -33,14 +33,20 @@ class Validator
     /**
      * @param object $entity
      * @param ValidatorInterface[] $validators
-     * @return string[]
+     * @param object|null $context
+     * @return \string[]
      * @throws ConfigurationMismatchException
      */
-    public function validate($entity, array $validators)
+    public function validate($entity, array $validators, $context = null)
     {
         $messages = [];
+        $validatorArguments = [];
+        if ($context !== null) {
+            $validatorArguments['context'] = $context;
+        }
+
         foreach ($validators as $validatorName) {
-            $validator = $this->objectManager->get($validatorName);
+            $validator = $this->objectManager->create($validatorName, $validatorArguments);
             if (!$validator instanceof ValidatorInterface) {
                 throw new ConfigurationMismatchException(
                     __(
diff --git a/app/code/Magento/Sales/Model/ValidatorInterface.php b/app/code/Magento/Sales/Model/ValidatorInterface.php
index 1882782e314f7b47b507c042697f223eadbf36b7..4489af44f40361f036e92671f776af479e45a4d7 100644
--- a/app/code/Magento/Sales/Model/ValidatorInterface.php
+++ b/app/code/Magento/Sales/Model/ValidatorInterface.php
@@ -15,7 +15,7 @@ interface ValidatorInterface
 {
     /**
      * @param object $entity
-     * @return array
+     * @return \Magento\Framework\Phrase[]
      * @throws DocumentValidationException
      * @throws NoSuchEntityException
      */
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Item/Validation/CreateQuantityValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Item/Validation/CreateQuantityValidatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1466a0f4fc9fe8d44841bcf4d31d4a34ab8abe44
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Item/Validation/CreateQuantityValidatorTest.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Item\Validation;
+
+use Magento\Sales\Api\OrderItemRepositoryInterface;
+use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Model\Order\Item;
+
+/**
+ * Class CreateQuantityValidatorTest
+ */
+class CreateQuantityValidatorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var OrderItemRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderItemRepositoryMock;
+
+    /**
+     * @var Item|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderItemMock;
+
+    /**
+     * @var CreationQuantityValidator|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $createQuantityValidator;
+
+    /**
+     * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $contexMock;
+
+    /**
+     * @var \stdClass|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $entity;
+
+    protected function setUp()
+    {
+        $this->orderItemRepositoryMock = $this->getMockBuilder(OrderItemRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['get'])
+            ->getMockForAbstractClass();
+
+        $this->orderItemMock = $this->getMockBuilder(Item::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->entity = $this->getMockBuilder(\stdClass::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getOrderItemId', 'getQty'])
+            ->getMock();
+    }
+
+    /**
+     * @dataProvider dataProvider
+     */
+    public function testValidateCreditMemoProductItems($orderItemId, $expectedResult, $withContext = false)
+    {
+        if ($orderItemId) {
+            $this->entity->expects($this->once())
+                ->method('getOrderItemId')
+                ->willReturn($orderItemId);
+
+            $this->orderItemRepositoryMock->expects($this->once())
+                ->method('get')
+                ->with($orderItemId)
+                ->willReturn($this->orderItemMock);
+        } else {
+            $this->entity->expects($this->once())
+                ->method('getOrderItemId')
+                ->willThrowException(new NoSuchEntityException());
+        }
+
+        $this->contexMock = null;
+        if ($withContext) {
+            $this->contexMock = $this->getMockBuilder(OrderInterface::class)
+                ->disableOriginalConstructor()
+                ->getMockForAbstractClass();
+
+            $this->entity->expects($this->once())
+                ->method('getQty')
+                ->willReturn(11);
+        }
+
+        $this->createQuantityValidator = new CreationQuantityValidator(
+            $this->orderItemRepositoryMock,
+            $this->contexMock
+        );
+
+        $this->assertEquals($expectedResult, $this->createQuantityValidator->validate($this->entity));
+    }
+
+    public function dataProvider()
+    {
+        return [
+            'testValidateCreditMemoProductItems' => [
+                1,
+                [__('The creditmemo contains product item that is not part of the original order.')],
+            ],
+            'testValidateWithException' => [
+                null,
+                [__('The creditmemo contains product item that is not part of the original order.')]
+            ],
+            'testValidateWithContext' => [
+                1,
+                [__('The quantity to refund must not be greater than the unrefunded quantity.')],
+                true
+            ],
+        ];
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php
index 1b8d40d0427165f5c8587deb422b1ac9e3f4dbd2..2c5173507d997797c8d47cef8d7c70a6ceb56370 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php
@@ -50,7 +50,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
 
         $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class)
             ->disableOriginalConstructor()
-            ->setMethods(['getBaseCost'])
+            ->setMethods(['getBaseCost', 'setDoTransaction'])
             ->getMockForAbstractClass();
 
         $this->paymentMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)
@@ -142,6 +142,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
     public function testExecuteOffline($amounts)
     {
         $orderId = 1;
+        $online = false;
         $this->creditmemoMock->expects($this->once())
             ->method('getState')
             ->willReturn(Creditmemo::STATE_REFUNDED);
@@ -174,8 +175,17 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
         $this->orderMock->expects($this->never())
             ->method('setTotalOnlineRefunded');
 
-        $this->orderMock->expects($this->never())
-            ->method('getPayment');
+        $this->orderMock->expects($this->once())
+            ->method('getPayment')
+            ->willReturn($this->paymentMock);
+
+        $this->paymentMock->expects($this->once())
+            ->method('refund')
+            ->with($this->creditmemoMock);
+
+        $this->creditmemoMock->expects($this->once())
+            ->method('setDoTransaction')
+            ->with($online);
 
         $this->eventManagerMock->expects($this->once())
             ->method('dispatch')
@@ -186,7 +196,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             $this->orderMock,
-            $this->subject->execute($this->creditmemoMock, $this->orderMock, false)
+            $this->subject->execute($this->creditmemoMock, $this->orderMock, $online)
         );
     }
 
@@ -197,6 +207,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
     public function testExecuteOnline($amounts)
     {
         $orderId = 1;
+        $online = true;
         $this->creditmemoMock->expects($this->once())
             ->method('getState')
             ->willReturn(Creditmemo::STATE_REFUNDED);
@@ -229,6 +240,10 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
         $this->orderMock->expects($this->never())
             ->method('setTotalOfflineRefunded');
 
+        $this->creditmemoMock->expects($this->once())
+            ->method('setDoTransaction')
+            ->with($online);
+
         $this->orderMock->expects($this->once())
             ->method('getPayment')
             ->willReturn($this->paymentMock);
@@ -238,7 +253,7 @@ class RefundOperationTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             $this->orderMock,
-            $this->subject->execute($this->creditmemoMock, $this->orderMock, true)
+            $this->subject->execute($this->creditmemoMock, $this->orderMock, $online)
         );
     }
 
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d1fe28b21b59b47558c0e44d0b55eacc0f6b847c
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php
@@ -0,0 +1,361 @@
+<?php
+
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Sender;
+
+/**
+ * Unit test for email notification sender for Creditmemo.
+ * @SuppressWarnings(PHPMD.TooManyFields)
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class EmailSenderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender
+     */
+    private $subject;
+
+    /**
+     * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderMock;
+
+    /**
+     * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $storeMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Email\Sender|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $senderMock;
+
+    /**
+     * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $loggerMock;
+
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoMock;
+
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $commentMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Address|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $addressMock;
+
+    /**
+     * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $globalConfigMock;
+
+    /**
+     * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $eventManagerMock;
+
+    /**
+     * @var \Magento\Payment\Model\Info|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentInfoMock;
+
+    /**
+     * @var \Magento\Payment\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentHelperMock;
+
+    /**
+     * @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoResourceMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Address\Renderer|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $addressRendererMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Email\Container\Template|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $templateContainerMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $identityContainerMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Email\SenderBuilderFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $senderBuilderFactoryMock;
+
+    /**
+     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     */
+    protected function setUp()
+    {
+        $this->orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
+            ->setMethods(['getStoreId'])
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->storeMock->expects($this->any())
+            ->method('getStoreId')
+            ->willReturn(1);
+        $this->orderMock->expects($this->any())
+            ->method('getStore')
+            ->willReturn($this->storeMock);
+
+        $this->senderMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Sender::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['send', 'sendCopyTo'])
+            ->getMock();
+
+        $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Creditmemo::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['setSendEmail', 'setEmailSent'])
+            ->getMock();
+
+        $this->commentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoCommentCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->commentMock->expects($this->any())
+            ->method('getComment')
+            ->willReturn('Comment text');
+
+        $this->addressMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->orderMock->expects($this->any())
+            ->method('getBillingAddress')
+            ->willReturn($this->addressMock);
+        $this->orderMock->expects($this->any())
+            ->method('getShippingAddress')
+            ->willReturn($this->addressMock);
+
+        $this->globalConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->paymentInfoMock = $this->getMockBuilder(\Magento\Payment\Model\Info::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->orderMock->expects($this->any())
+            ->method('getPayment')
+            ->willReturn($this->paymentInfoMock);
+
+        $this->paymentHelperMock = $this->getMockBuilder(\Magento\Payment\Helper\Data::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->paymentHelperMock->expects($this->any())
+            ->method('getInfoBlockHtml')
+            ->with($this->paymentInfoMock, 1)
+            ->willReturn('Payment Info Block');
+
+        $this->creditmemoResourceMock = $this->getMockBuilder(
+            \Magento\Sales\Model\ResourceModel\Order\Creditmemo::class
+        )->disableOriginalConstructor()
+            ->getMock();
+
+        $this->addressRendererMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address\Renderer::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->addressRendererMock->expects($this->any())
+            ->method('format')
+            ->with($this->addressMock, 'html')
+            ->willReturn('Formatted address');
+
+        $this->templateContainerMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Container\Template::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->identityContainerMock = $this->getMockBuilder(
+            \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity::class
+        )
+        ->disableOriginalConstructor()
+        ->getMock();
+
+        $this->identityContainerMock->expects($this->any())
+            ->method('getStore')
+            ->willReturn($this->storeMock);
+
+        $this->senderBuilderFactoryMock = $this->getMockBuilder(
+            \Magento\Sales\Model\Order\Email\SenderBuilderFactory::class
+        )
+        ->disableOriginalConstructor()
+        ->setMethods(['create'])
+        ->getMock();
+
+        $this->subject = new \Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender(
+            $this->templateContainerMock,
+            $this->identityContainerMock,
+            $this->senderBuilderFactoryMock,
+            $this->loggerMock,
+            $this->addressRendererMock,
+            $this->paymentHelperMock,
+            $this->creditmemoResourceMock,
+            $this->globalConfigMock,
+            $this->eventManagerMock
+        );
+    }
+
+    /**
+     * @param int $configValue
+     * @param bool $forceSyncMode
+     * @param bool $isComment
+     * @param bool $emailSendingResult
+     *
+     * @dataProvider sendDataProvider
+     *
+     * @return void
+     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     */
+    public function testSend($configValue, $forceSyncMode, $isComment, $emailSendingResult)
+    {
+        $this->globalConfigMock->expects($this->once())
+            ->method('getValue')
+            ->with('sales_email/general/async_sending')
+            ->willReturn($configValue);
+
+        if (!$isComment) {
+            $this->commentMock = null;
+        }
+
+        $this->creditmemoMock->expects($this->once())
+            ->method('setSendEmail')
+            ->with(true);
+
+        if (!$configValue || $forceSyncMode) {
+            $transport = [
+                'order' => $this->orderMock,
+                'creditmemo' => $this->creditmemoMock,
+                'comment' => $isComment ? 'Comment text' : '',
+                'billing' => $this->addressMock,
+                'payment_html' => 'Payment Info Block',
+                'store' => $this->storeMock,
+                'formattedShippingAddress' => 'Formatted address',
+                'formattedBillingAddress' => 'Formatted address',
+            ];
+
+            $this->eventManagerMock->expects($this->once())
+                ->method('dispatch')
+                ->with(
+                    'email_creditmemo_set_template_vars_before',
+                    [
+                        'sender' => $this->subject,
+                        'transport' => $transport,
+                    ]
+                );
+
+            $this->templateContainerMock->expects($this->once())
+                ->method('setTemplateVars')
+                ->with($transport);
+
+            $this->identityContainerMock->expects($this->once())
+                ->method('isEnabled')
+                ->willReturn($emailSendingResult);
+
+            if ($emailSendingResult) {
+                $this->senderBuilderFactoryMock->expects($this->once())
+                    ->method('create')
+                    ->willReturn($this->senderMock);
+
+                $this->senderMock->expects($this->once())
+                    ->method('send');
+
+                $this->senderMock->expects($this->once())
+                    ->method('sendCopyTo');
+
+                $this->creditmemoMock->expects($this->once())
+                    ->method('setEmailSent')
+                    ->with(true);
+
+                $this->creditmemoResourceMock->expects($this->once())
+                    ->method('saveAttribute')
+                    ->with($this->creditmemoMock, ['send_email', 'email_sent']);
+
+                $this->assertTrue(
+                    $this->subject->send(
+                        $this->orderMock,
+                        $this->creditmemoMock,
+                        $this->commentMock,
+                        $forceSyncMode
+                    )
+                );
+            } else {
+                $this->creditmemoResourceMock->expects($this->once())
+                    ->method('saveAttribute')
+                    ->with($this->creditmemoMock, 'send_email');
+
+                $this->assertFalse(
+                    $this->subject->send(
+                        $this->orderMock,
+                        $this->creditmemoMock,
+                        $this->commentMock,
+                        $forceSyncMode
+                    )
+                );
+            }
+        } else {
+            $this->creditmemoMock->expects($this->once())
+                ->method('setEmailSent')
+                ->with(null);
+
+            $this->creditmemoResourceMock->expects($this->at(0))
+                ->method('saveAttribute')
+                ->with($this->creditmemoMock, 'email_sent');
+            $this->creditmemoResourceMock->expects($this->at(1))
+                ->method('saveAttribute')
+                ->with($this->creditmemoMock, 'send_email');
+
+            $this->assertFalse(
+                $this->subject->send(
+                    $this->orderMock,
+                    $this->creditmemoMock,
+                    $this->commentMock,
+                    $forceSyncMode
+                )
+            );
+        }
+    }
+
+    /**
+     * @return array
+     */
+    public function sendDataProvider()
+    {
+        return [
+            'Successful sync sending with comment' => [0, false, true, true],
+            'Successful sync sending without comment' => [0, false, false, true],
+            'Failed sync sending with comment' => [0, false, true, false],
+            'Successful forced sync sending with comment' => [1, true, true, true],
+            'Async sending' => [1, false, false, false],
+        ];
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..838c062956c24ac900835e53e746998f1de1271d
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php
@@ -0,0 +1,247 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Validation;
+
+use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\InvoiceRepositoryInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator;
+
+/**
+ * Class QuantityValidatorTest
+ */
+class QuantityValidatorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderRepositoryMock;
+
+    /**
+     * @var InvoiceRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $invoiceRepositoryMock;
+
+    /**
+     * @var QuantityValidator
+     */
+    private $validator;
+
+    /**
+     * @var PriceCurrencyInterface
+     */
+    private $priceCurrencyMock;
+
+    /**
+     * @inheritDoc
+     */
+    protected function setUp()
+    {
+        $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->invoiceRepositoryMock = $this->getMockBuilder(InvoiceRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->priceCurrencyMock = $this->getMockBuilder(PriceCurrencyInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->validator = new QuantityValidator(
+            $this->orderRepositoryMock,
+            $this->invoiceRepositoryMock,
+            $this->priceCurrencyMock
+        );
+    }
+
+    public function testValidateWithoutItems()
+    {
+        $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $creditmemoMock->expects($this->exactly(2))->method('getOrderId')
+            ->willReturn(1);
+        $creditmemoMock->expects($this->once())->method('getItems')
+            ->willReturn([]);
+        $orderMock = $this->getMockBuilder(OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $orderMock->expects($this->once())->method('getItems')
+            ->willReturn([]);
+
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->with(1)
+            ->willReturn($orderMock);
+        $creditmemoMock->expects($this->once())->method('getGrandTotal')
+            ->willReturn(0);
+        $this->assertEquals(
+            [
+                __('The credit memo\'s total must be positive.')
+            ],
+            $this->validator->validate($creditmemoMock)
+        );
+    }
+
+    public function testValidateWithoutOrder()
+    {
+        $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $creditmemoMock->expects($this->once())->method('getOrderId')
+            ->willReturn(null);
+        $creditmemoMock->expects($this->never())->method('getItems');
+        $this->assertEquals(
+            [__('Order Id is required for creditmemo document')],
+            $this->validator->validate($creditmemoMock)
+        );
+    }
+
+    public function testValidateWithWrongItemId()
+    {
+        $orderId = 1;
+        $orderItemId = 1;
+        $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $creditmemoMock->expects($this->exactly(2))->method('getOrderId')
+            ->willReturn($orderId);
+        $creditmemoItemMock = $this->getMockBuilder(
+            \Magento\Sales\Api\Data\CreditmemoItemInterface::class
+        )->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $creditmemoItemMock->expects($this->once())->method('getOrderItemId')
+            ->willReturn($orderItemId);
+        $creditmemoItemSku = 'sku';
+        $creditmemoItemMock->expects($this->once())->method('getSku')
+            ->willReturn($creditmemoItemSku);
+        $creditmemoMock->expects($this->exactly(1))->method('getItems')
+            ->willReturn([$creditmemoItemMock]);
+
+        $orderMock = $this->getMockBuilder(OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $orderMock->expects($this->once())->method('getItems')
+            ->willReturn([]);
+
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->with($orderId)
+            ->willReturn($orderMock);
+        $creditmemoMock->expects($this->once())->method('getGrandTotal')
+            ->willReturn(12);
+
+        $this->assertEquals(
+            [
+                __(
+                    'The creditmemo contains product SKU "%1" that is not part of the original order.',
+                    $creditmemoItemSku
+                ),
+                __('You can\'t create a creditmemo without products.')
+            ],
+            $this->validator->validate($creditmemoMock)
+        );
+    }
+
+    /**
+     * @param int $orderId
+     * @param int $orderItemId
+     * @param int $qtyToRequest
+     * @param int $qtyToRefund
+     * @param string $sku
+     * @param array $expected
+     * @dataProvider dataProviderForValidateQty
+     */
+    public function testValidate($orderId, $orderItemId, $qtyToRequest, $qtyToRefund, $sku, $total, array $expected)
+    {
+        $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $creditmemoMock->expects($this->exactly(2))->method('getOrderId')
+            ->willReturn($orderId);
+        $creditmemoMock->expects($this->once())->method('getGrandTotal')
+            ->willReturn($total);
+        $creditmemoItemMock = $this->getMockBuilder(
+            \Magento\Sales\Api\Data\CreditmemoItemInterface::class
+        )->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $creditmemoItemMock->expects($this->exactly(2))->method('getOrderItemId')
+            ->willReturn($orderItemId);
+        $creditmemoItemMock->expects($this->never())->method('getSku')
+            ->willReturn($sku);
+        $creditmemoItemMock->expects($this->atLeastOnce())->method('getQty')
+            ->willReturn($qtyToRequest);
+        $creditmemoMock->expects($this->exactly(1))->method('getItems')
+            ->willReturn([$creditmemoItemMock]);
+
+        $orderMock = $this->getMockBuilder(OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $orderItemMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Item::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $orderItemMock->expects($this->exactly(2))->method('getQtyToRefund')
+            ->willReturn($qtyToRefund);
+        $creditmemoItemMock->expects($this->any())->method('getQty')
+            ->willReturn($qtyToRequest);
+        $orderMock->expects($this->once())->method('getItems')
+            ->willReturn([$orderItemMock]);
+        $orderItemMock->expects($this->once())->method('getItemId')
+            ->willReturn($orderItemId);
+        $orderItemMock->expects($this->any())->method('getSku')
+            ->willReturn($sku);
+
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->with($orderId)
+            ->willReturn($orderMock);
+
+        $this->assertEquals(
+            $expected,
+            $this->validator->validate($creditmemoMock)
+        );
+    }
+
+    /**
+     * @return array
+     */
+    public function dataProviderForValidateQty()
+    {
+        $sku = 'sku';
+
+        return [
+            [
+                'orderId' => 1,
+                'orderItemId' => 1,
+                'qtyToRequest' => 1,
+                'qtyToRefund' => 1,
+                'sku',
+                'total' => 15,
+                'expected' => []
+            ],
+            [
+                'orderId' => 1,
+                'orderItemId' => 1,
+                'qtyToRequest' => 2,
+                'qtyToRefund' => 1,
+                'sku',
+                'total' => 0,
+                'expected' => [
+                    __(
+                        'The quantity to creditmemo must not be greater than the unrefunded quantity'
+                        . ' for product SKU "%1".',
+                        $sku
+                    ),
+                    __('The credit memo\'s total must be positive.')
+                ]
+            ],
+        ];
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoDocumentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoDocumentFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..521167f10aebd0993adebfef05e88f4b28ef1f54
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoDocumentFactoryTest.php
@@ -0,0 +1,265 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model\Order;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\Order\CreditmemoDocumentFactory;
+use Magento\Sales\Api\Data\CreditmemoCommentInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Invoice;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Api\Data\CreditmemoItemCreationInterface;
+use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface;
+use Magento\Framework\EntityManager\HydratorPool;
+use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface;
+use Magento\Sales\Model\Order\CreditmemoFactory;
+use Magento\Framework\EntityManager\HydratorInterface;
+
+/**
+ * Class CreditmemoDocumentFactoryTest
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class CreditmemoDocumentFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var CreditmemoDocumentFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $factory;
+
+    /**
+     * @var CreditmemoFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoFactoryMock;
+
+    /**
+     * @var \Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $commentFactoryMock;
+
+    /**
+     * @var HydratorPool|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $hydratorPoolMock;
+
+    /**
+     * @var HydratorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $hydratorMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Invoice|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $invoiceMock;
+
+    /**
+     * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoItemCreationMock;
+
+    /**
+     * @var CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $commentCreationMock;
+
+    /**
+     * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $commentCreationArgumentsMock;
+
+    /**
+     * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoMock;
+
+    /**
+     * @var CreditmemoCommentInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $commentMock;
+
+    /**
+     * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderRepositoryMock;
+
+    public function setUp()
+    {
+        $this->objectManager = new ObjectManager($this);
+        $this->creditmemoFactoryMock = $this->getMockBuilder(CreditmemoFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->commentFactoryMock =
+            $this->getMockBuilder('Magento\Sales\Api\Data\CreditmemoCommentInterfaceFactory')
+                ->setMethods(['create'])
+                ->getMock();
+        $this->hydratorPoolMock = $this->getMockBuilder(HydratorPool::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderMock = $this->getMockBuilder(Order::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->invoiceMock = $this->getMockBuilder(Invoice::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->hydratorMock = $this->getMockBuilder(HydratorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->commentCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->commentCreationMock = $this->getMockBuilder(CreditmemoCommentCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoMock->expects($this->once())
+            ->method('getEntityId')
+            ->willReturn(11);
+
+        $this->commentMock = $this->getMockBuilder(CreditmemoCommentInterface::class)
+            ->disableOriginalConstructor()
+            ->setMethods(
+                array_merge(
+                    get_class_methods(CreditmemoCommentInterface::class),
+                    ['setStoreId', 'setCreditmemo']
+                )
+            )
+            ->getMock();
+        $this->factory = $this->objectManager->getObject(
+            CreditmemoDocumentFactory::class,
+            [
+                'creditmemoFactory' => $this->creditmemoFactoryMock,
+                'commentFactory' => $this->commentFactoryMock,
+                'hydratorPool' => $this->hydratorPoolMock,
+                'orderRepository' => $this->orderRepositoryMock
+            ]
+        );
+    }
+
+    private function commonFactoryFlow()
+    {
+        $this->creditmemoItemCreationMock->expects($this->once())
+            ->method('getOrderItemId')
+            ->willReturn(7);
+        $this->creditmemoItemCreationMock->expects($this->once())
+            ->method('getQty')
+            ->willReturn(3);
+        $this->hydratorPoolMock->expects($this->exactly(2))
+            ->method('getHydrator')
+            ->willReturnMap(
+                [
+                    [CreditmemoCreationArgumentsInterface::class, $this->hydratorMock],
+                    [CreditmemoCommentCreationInterface::class, $this->hydratorMock],
+                ]
+            );
+        $this->hydratorMock->expects($this->exactly(2))
+            ->method('extract')
+            ->willReturnMap([
+                [$this->commentCreationArgumentsMock, ['shipping_amount' => '20.00']],
+                [$this->commentCreationMock, ['comment' => 'text']]
+            ]);
+        $this->commentFactoryMock->expects($this->once())
+            ->method('create')
+            ->with(
+                [
+                    'data' => [
+                        'comment' => 'text'
+                    ]
+                ]
+            )
+            ->willReturn($this->commentMock);
+        $this->creditmemoMock->expects($this->once())
+            ->method('getEntityId')
+            ->willReturn(11);
+        $this->creditmemoMock->expects($this->once())
+            ->method('getStoreId')
+            ->willReturn(1);
+        $this->commentMock->expects($this->once())
+            ->method('setParentId')
+            ->with(11)
+            ->willReturnSelf();
+        $this->commentMock->expects($this->once())
+            ->method('setStoreId')
+            ->with(1)
+            ->willReturnSelf();
+        $this->commentMock->expects($this->once())
+            ->method('setIsCustomerNotified')
+            ->with(true)
+            ->willReturnSelf();
+        $this->commentMock->expects($this->once())
+            ->method('setCreditmemo')
+            ->with($this->creditmemoMock)
+            ->willReturnSelf();
+    }
+
+    public function testCreateFromOrder()
+    {
+        $this->commonFactoryFlow();
+        $this->creditmemoFactoryMock->expects($this->once())
+            ->method('createByOrder')
+            ->with(
+                $this->orderMock,
+                [
+                    'shipping_amount' => '20.00',
+                    'qtys' => [7 => 3]
+                ]
+            )
+            ->willReturn($this->creditmemoMock);
+        $this->factory->createFromOrder(
+            $this->orderMock,
+            [$this->creditmemoItemCreationMock],
+            $this->commentCreationMock,
+            true,
+            $this->commentCreationArgumentsMock
+        );
+    }
+
+    public function testCreateFromInvoice()
+    {
+        $this->commonFactoryFlow();
+        $this->creditmemoFactoryMock->expects($this->once())
+            ->method('createByInvoice')
+            ->with(
+                $this->invoiceMock,
+                [
+                    'shipping_amount' => '20.00',
+                    'qtys' => [7 => 3]
+                ]
+            )
+            ->willReturn($this->creditmemoMock);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+        $this->invoiceMock->expects($this->once())
+            ->method('setOrder')
+            ->with($this->orderMock)
+            ->willReturnSelf();
+        $this->factory->createFromInvoice(
+            $this->invoiceMock,
+            [$this->creditmemoItemCreationMock],
+            $this->commentCreationMock,
+            true,
+            $this->commentCreationArgumentsMock
+        );
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Validation/CanRefundTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Validation/CanRefundTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..773f3b75c91f5c4f6aad03576b0cfe9dd4746ab1
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Validation/CanRefundTest.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model\Order\Invoice\Validation;
+
+use Magento\Sales\Api\Data\OrderInterface;
+
+/**
+ * Class CanRefundTest
+ */
+class CanRefundTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Sales\Model\Order\Invoice|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $invoiceMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderPaymentRepositoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderRepositoryMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentMock;
+
+    /**
+     * @var \Magento\Sales\Model\Order\Invoice\Validation\CanRefund
+     */
+    private $validator;
+
+    protected function setUp()
+    {
+        $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Invoice::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderPaymentRepositoryMock = $this->getMockBuilder(
+            \Magento\Sales\Api\OrderPaymentRepositoryInterface::class
+        )
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->paymentMock = $this->getMockBuilder(\Magento\Payment\Model\InfoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->validator = new \Magento\Sales\Model\Order\Invoice\Validation\CanRefund(
+            $this->orderPaymentRepositoryMock,
+            $this->orderRepositoryMock
+        );
+    }
+
+    public function testValidateWrongInvoiceState()
+    {
+        $this->invoiceMock->expects($this->exactly(2))
+            ->method('getState')
+            ->willReturnOnConsecutiveCalls(
+                \Magento\Sales\Model\Order\Invoice::STATE_OPEN,
+                \Magento\Sales\Model\Order\Invoice::STATE_CANCELED
+            );
+        $this->assertEquals(
+            [__('We can\'t create creditmemo for the invoice.')],
+            $this->validator->validate($this->invoiceMock)
+        );
+        $this->assertEquals(
+            [__('We can\'t create creditmemo for the invoice.')],
+            $this->validator->validate($this->invoiceMock)
+        );
+    }
+
+    public function testValidateInvoiceSumWasRefunded()
+    {
+        $this->invoiceMock->expects($this->once())
+            ->method('getState')
+            ->willReturn(\Magento\Sales\Model\Order\Invoice::STATE_PAID);
+        $this->invoiceMock->expects($this->once())
+            ->method('getBaseGrandTotal')
+            ->willReturn(1);
+        $this->invoiceMock->expects($this->once())
+            ->method('getBaseTotalRefunded')
+            ->willReturn(1);
+        $this->assertEquals(
+            [__('We can\'t create creditmemo for the invoice.')],
+            $this->validator->validate($this->invoiceMock)
+        );
+    }
+
+    public function testValidate()
+    {
+        $this->invoiceMock->expects($this->once())
+            ->method('getState')
+            ->willReturn(\Magento\Sales\Model\Order\Invoice::STATE_PAID);
+        $orderMock = $this->getMockBuilder(OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($orderMock);
+        $orderMock->expects($this->once())
+            ->method('getPayment')
+            ->willReturn($this->paymentMock);
+        $methodInstanceMock = $this->getMockBuilder(\Magento\Payment\Model\MethodInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->paymentMock->expects($this->once())
+            ->method('getMethodInstance')
+            ->willReturn($methodInstanceMock);
+        $methodInstanceMock->expects($this->atLeastOnce())
+            ->method('canRefund')
+            ->willReturn(true);
+        $this->invoiceMock->expects($this->once())
+            ->method('getBaseGrandTotal')
+            ->willReturn(1);
+        $this->invoiceMock->expects($this->once())
+            ->method('getBaseTotalRefunded')
+            ->willReturn(0);
+        $this->assertEquals(
+            [],
+            $this->validator->validate($this->invoiceMock)
+        );
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b4246d46944444041d92089ddc19a4d270df86f
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model\Order\Validation;
+
+use Magento\Sales\Model\Order;
+
+/**
+ * Class CanRefundTest
+ */
+class CanRefundTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Sales\Model\Order\Validation\CanRefund|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $model;
+
+    /**
+     * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var \Magento\Sales\Api\Data\OrderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderMock;
+
+    /**
+     * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $priceCurrencyMock;
+
+    protected function setUp()
+    {
+        $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getStatus', 'getItems'])
+            ->getMockForAbstractClass();
+
+        $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->priceCurrencyMock->expects($this->any())
+            ->method('round')
+            ->willReturnArgument(0);
+        $this->model = new \Magento\Sales\Model\Order\Validation\CanRefund(
+            $this->priceCurrencyMock
+        );
+    }
+
+    /**
+     * @param string $state
+     *
+     * @dataProvider canCreditmemoWrongStateDataProvider
+     */
+    public function testCanCreditmemoWrongState($state)
+    {
+        $this->orderMock->expects($this->any())
+            ->method('getState')
+            ->willReturn($state);
+        $this->orderMock->expects($this->once())
+            ->method('getStatus')
+            ->willReturn('status');
+        $this->orderMock->expects($this->never())
+            ->method('getTotalPaid')
+            ->willReturn(15);
+        $this->orderMock->expects($this->never())
+            ->method('getTotalRefunded')
+            ->willReturn(14);
+        $this->assertEquals(
+            [__('A creditmemo can not be created when an order has a status of %1', 'status')],
+            $this->model->validate($this->orderMock)
+        );
+    }
+
+    /**
+     * Data provider for testCanCreditmemoWrongState
+     * @return array
+     */
+    public function canCreditmemoWrongStateDataProvider()
+    {
+        return [
+            [Order::STATE_PAYMENT_REVIEW],
+            [Order::STATE_HOLDED],
+            [Order::STATE_CANCELED],
+            [Order::STATE_CLOSED],
+        ];
+    }
+
+    public function testCanCreditmemoNoMoney()
+    {
+        $this->orderMock->expects($this->any())
+            ->method('getState')
+            ->willReturn(Order::STATE_PROCESSING);
+        $this->orderMock->expects($this->once())
+            ->method('getTotalPaid')
+            ->willReturn(15);
+        $this->orderMock->expects($this->once())
+            ->method('getTotalRefunded')
+            ->willReturn(15);
+        $this->assertEquals(
+            [
+                __('The order does not allow a creditmemo to be created.')
+            ],
+            $this->model->validate($this->orderMock)
+        );
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c4aab6dd2feb1f2ac4d260bc44245633bc17467
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php
@@ -0,0 +1,491 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Sales\Api\CreditmemoRepositoryInterface;
+use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface;
+use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Api\Data\InvoiceInterface;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\InvoiceRepositoryInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Config as OrderConfig;
+use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface;
+use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface;
+use Magento\Sales\Api\Data\CreditmemoItemCreationInterface;
+use Magento\Sales\Model\Order\CreditmemoDocumentFactory;
+use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface;
+use Magento\Sales\Model\Order\OrderStateResolverInterface;
+use Magento\Sales\Model\Order\OrderValidatorInterface;
+use Magento\Sales\Model\Order\PaymentAdapterInterface;
+use Magento\Sales\Model\Order\Creditmemo\NotifierInterface;
+use Magento\Sales\Model\RefundInvoice;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class RefundInvoiceTest
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
+ */
+class RefundInvoiceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $resourceConnectionMock;
+
+    /**
+     * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderRepositoryMock;
+
+    /**
+     * @var InvoiceRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $invoiceRepositoryMock;
+
+    /**
+     * @var CreditmemoDocumentFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoDocumentFactoryMock;
+
+    /**
+     * @var CreditmemoValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoValidatorMock;
+
+    /**
+     * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderValidatorMock;
+
+    /**
+     * @var InvoiceValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $invoiceValidatorMock;
+
+    /**
+     * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentAdapterMock;
+
+    /**
+     * @var OrderStateResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderStateResolverMock;
+
+    /**
+     * @var OrderConfig|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $configMock;
+
+    /**
+     * @var Order\CreditmemoRepository|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoRepositoryMock;
+
+    /**
+     * @var NotifierInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $notifierMock;
+
+    /**
+     * @var RefundInvoice|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $refundInvoice;
+
+    /**
+     * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoCommentCreationMock;
+
+    /**
+     * @var CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoCreationArgumentsMock;
+
+    /**
+     * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderMock;
+
+    /**
+     * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $invoiceMock;
+
+    /**
+     * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoMock;
+
+    /**
+     * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $adapterInterface;
+
+    /**
+     * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoItemCreationMock;
+
+    /**
+     * @var ItemCreationValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $itemCreationValidatorMock;
+
+    /**
+     * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $loggerMock;
+
+    protected function setUp()
+    {
+        $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->invoiceRepositoryMock = $this->getMockBuilder(InvoiceRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoDocumentFactoryMock = $this->getMockBuilder(CreditmemoDocumentFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoValidatorMock = $this->getMockBuilder(CreditmemoValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->invoiceValidatorMock = $this->getMockBuilder(InvoiceValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        
+        $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->configMock = $this->getMockBuilder(OrderConfig::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->notifierMock = $this->getMockBuilder(NotifierInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->creditmemoCommentCreationMock = $this->getMockBuilder(CreditmemoCommentCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->orderMock = $this->getMockBuilder(OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->invoiceMock = $this->getMockBuilder(InvoiceInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->adapterInterface = $this->getMockBuilder(AdapterInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->itemCreationValidatorMock = $this->getMockBuilder(ItemCreationValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->refundInvoice = new RefundInvoice(
+            $this->resourceConnectionMock,
+            $this->orderStateResolverMock,
+            $this->orderRepositoryMock,
+            $this->invoiceRepositoryMock,
+            $this->orderValidatorMock,
+            $this->invoiceValidatorMock,
+            $this->creditmemoValidatorMock,
+            $this->itemCreationValidatorMock,
+            $this->creditmemoRepositoryMock,
+            $this->paymentAdapterMock,
+            $this->creditmemoDocumentFactoryMock,
+            $this->notifierMock,
+            $this->configMock,
+            $this->loggerMock
+        );
+    }
+
+    /**
+     * @dataProvider dataProvider
+     */
+    public function testOrderCreditmemo($invoiceId, $items, $notify, $appendComment)
+    {
+        $this->resourceConnectionMock->expects($this->once())
+            ->method('getConnection')
+            ->with('sales')
+            ->willReturn($this->adapterInterface);
+
+        $this->invoiceRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->invoiceMock);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+
+        $this->creditmemoDocumentFactoryMock->expects($this->once())
+            ->method('createFromInvoice')
+            ->with(
+                $this->invoiceMock,
+                $items,
+                $this->creditmemoCommentCreationMock,
+                ($appendComment && $notify),
+                $this->creditmemoCreationArgumentsMock
+            )->willReturn($this->creditmemoMock);
+
+        $this->creditmemoValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoMock)
+            ->willReturn([]);
+        $this->orderValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->orderMock)
+            ->willReturn([]);
+        $this->invoiceValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->invoiceMock)
+            ->willReturn([]);
+        $this->itemCreationValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoItemCreationMock)
+            ->willReturn([]);
+        $this->paymentAdapterMock->expects($this->once())
+            ->method('refund')
+            ->with($this->creditmemoMock, $this->orderMock)
+            ->willReturn($this->orderMock);
+        $this->orderStateResolverMock->expects($this->once())
+            ->method('getStateForOrder')
+            ->with($this->orderMock, [])
+            ->willReturn(Order::STATE_CLOSED);
+        $this->orderMock->expects($this->once())
+            ->method('setState')
+            ->with(Order::STATE_CLOSED)
+            ->willReturnSelf();
+        $this->orderMock->expects($this->once())
+            ->method('getState')
+            ->willReturn(Order::STATE_CLOSED);
+        $this->configMock->expects($this->once())
+            ->method('getStateDefaultStatus')
+            ->with(Order::STATE_CLOSED)
+            ->willReturn('Closed');
+        $this->orderMock->expects($this->once())
+            ->method('setStatus')
+            ->with('Closed')
+            ->willReturnSelf();
+        $this->creditmemoMock->expects($this->once())
+            ->method('setState')
+            ->with(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED)
+            ->willReturnSelf();
+
+        $this->creditmemoRepositoryMock->expects($this->once())
+            ->method('save')
+            ->with($this->creditmemoMock)
+            ->willReturn($this->creditmemoMock);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('save')
+            ->with($this->orderMock)
+            ->willReturn($this->orderMock);
+        if ($notify) {
+            $this->notifierMock->expects($this->once())
+                ->method('notify')
+                ->with($this->orderMock, $this->creditmemoMock, $this->creditmemoCommentCreationMock);
+        }
+        $this->creditmemoMock->expects($this->once())
+            ->method('getEntityId')
+            ->willReturn(2);
+
+        $this->assertEquals(
+            2,
+            $this->refundInvoice->execute(
+                $invoiceId,
+                $items,
+                false,
+                $notify,
+                $appendComment,
+                $this->creditmemoCommentCreationMock,
+                $this->creditmemoCreationArgumentsMock
+            )
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
+     */
+    public function testDocumentValidationException()
+    {
+        $invoiceId = 1;
+        $items = [1 => $this->creditmemoItemCreationMock];
+        $notify = true;
+        $appendComment = true;
+        $errorMessages = ['error1', 'error2'];
+
+        $this->invoiceRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->invoiceMock);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+
+        $this->creditmemoDocumentFactoryMock->expects($this->once())
+            ->method('createFromInvoice')
+            ->with(
+                $this->invoiceMock,
+                $items,
+                $this->creditmemoCommentCreationMock,
+                ($appendComment && $notify),
+                $this->creditmemoCreationArgumentsMock
+            )->willReturn($this->creditmemoMock);
+
+        $this->creditmemoValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoMock)
+            ->willReturn($errorMessages);
+        $this->orderValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->orderMock)
+            ->willReturn([]);
+        $this->invoiceValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->invoiceMock)
+            ->willReturn([]);
+        $this->itemCreationValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoItemCreationMock)
+            ->willReturn([]);
+
+        $this->assertEquals(
+            $errorMessages,
+            $this->refundInvoice->execute(
+                $invoiceId,
+                $items,
+                false,
+                $notify,
+                $appendComment,
+                $this->creditmemoCommentCreationMock,
+                $this->creditmemoCreationArgumentsMock
+            )
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Sales\Api\Exception\CouldNotRefundExceptionInterface
+     */
+    public function testCouldNotCreditmemoException()
+    {
+        $invoiceId = 1;
+        $items = [1 => $this->creditmemoItemCreationMock];
+        $notify = true;
+        $appendComment = true;
+        $this->resourceConnectionMock->expects($this->once())
+            ->method('getConnection')
+            ->with('sales')
+            ->willReturn($this->adapterInterface);
+
+        $this->invoiceRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->invoiceMock);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+
+        $this->creditmemoDocumentFactoryMock->expects($this->once())
+            ->method('createFromInvoice')
+            ->with(
+                $this->invoiceMock,
+                $items,
+                $this->creditmemoCommentCreationMock,
+                ($appendComment && $notify),
+                $this->creditmemoCreationArgumentsMock
+            )->willReturn($this->creditmemoMock);
+
+        $this->creditmemoValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoMock)
+            ->willReturn([]);
+        $this->orderValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->orderMock)
+            ->willReturn([]);
+        $this->invoiceValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->invoiceMock)
+            ->willReturn([]);
+        $this->itemCreationValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoItemCreationMock)
+            ->willReturn([]);
+        $e = new \Exception();
+
+        $this->paymentAdapterMock->expects($this->once())
+            ->method('refund')
+            ->with($this->creditmemoMock, $this->orderMock)
+            ->willThrowException($e);
+
+        $this->loggerMock->expects($this->once())
+            ->method('critical')
+            ->with($e);
+
+        $this->adapterInterface->expects($this->once())
+            ->method('rollBack');
+
+        $this->refundInvoice->execute(
+            $invoiceId,
+            $items,
+            false,
+            $notify,
+            $appendComment,
+            $this->creditmemoCommentCreationMock,
+            $this->creditmemoCreationArgumentsMock
+        );
+    }
+
+    public function dataProvider()
+    {
+        $creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        return [
+            'TestWithNotifyTrue' => [1, [1 => $creditmemoItemCreationMock], true, true],
+            'TestWithNotifyFalse' => [1, [1 => $creditmemoItemCreationMock], false, true],
+        ];
+    }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d684695664ba1d1b63a5b78cf610da019cf39bb
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php
@@ -0,0 +1,423 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Test\Unit\Model;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Sales\Api\CreditmemoRepositoryInterface;
+use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface;
+use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Config as OrderConfig;
+use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface;
+use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator;
+use Magento\Sales\Model\Order\CreditmemoDocumentFactory;
+use Magento\Sales\Model\Order\OrderStateResolverInterface;
+use Magento\Sales\Model\Order\OrderValidatorInterface;
+use Magento\Sales\Model\Order\PaymentAdapterInterface;
+use Magento\Sales\Model\Order\Creditmemo\NotifierInterface;
+use Magento\Sales\Model\RefundOrder;
+use Psr\Log\LoggerInterface;
+use Magento\Sales\Api\Data\CreditmemoItemCreationInterface;
+
+/**
+ * Class RefundOrderTest
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
+ */
+class RefundOrderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $resourceConnectionMock;
+
+    /**
+     * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderRepositoryMock;
+
+    /**
+     * @var CreditmemoDocumentFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoDocumentFactoryMock;
+
+    /**
+     * @var CreditmemoValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoValidatorMock;
+
+    /**
+     * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderValidatorMock;
+
+    /**
+     * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $paymentAdapterMock;
+
+    /**
+     * @var OrderStateResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderStateResolverMock;
+
+    /**
+     * @var OrderConfig|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $configMock;
+
+    /**
+     * @var Order\CreditmemoRepository|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoRepositoryMock;
+
+    /**
+     * @var NotifierInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $notifierMock;
+
+    /**
+     * @var RefundOrder|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $refundOrder;
+
+    /**
+     * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoCommentCreationMock;
+
+    /**
+     * @var CreditmemoCommentCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoCreationArgumentsMock;
+
+    /**
+     * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $orderMock;
+
+    /**
+     * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoMock;
+
+    /**
+     * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $adapterInterface;
+
+    /**
+     * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $loggerMock;
+
+    /**
+     * @var Order\Creditmemo\ItemCreationValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $itemCreationValidatorMock;
+
+    /**
+     * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $creditmemoItemCreationMock;
+
+    protected function setUp()
+    {
+        $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoDocumentFactoryMock = $this->getMockBuilder(CreditmemoDocumentFactory::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoValidatorMock = $this->getMockBuilder(CreditmemoValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->configMock = $this->getMockBuilder(OrderConfig::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->notifierMock = $this->getMockBuilder(NotifierInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->creditmemoCommentCreationMock = $this->getMockBuilder(CreditmemoCommentCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->orderMock = $this->getMockBuilder(OrderInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->adapterInterface = $this->getMockBuilder(AdapterInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->itemCreationValidatorMock = $this->getMockBuilder(Order\Creditmemo\ItemCreationValidatorInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+        $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->refundOrder = new RefundOrder(
+            $this->resourceConnectionMock,
+            $this->orderStateResolverMock,
+            $this->orderRepositoryMock,
+            $this->orderValidatorMock,
+            $this->creditmemoValidatorMock,
+            $this->itemCreationValidatorMock,
+            $this->creditmemoRepositoryMock,
+            $this->paymentAdapterMock,
+            $this->creditmemoDocumentFactoryMock,
+            $this->notifierMock,
+            $this->configMock,
+            $this->loggerMock
+        );
+    }
+
+    /**
+     * @dataProvider dataProvider
+     */
+    public function testOrderCreditmemo($orderId, $notify, $appendComment)
+    {
+        $items = [$this->creditmemoItemCreationMock];
+        $this->resourceConnectionMock->expects($this->once())
+            ->method('getConnection')
+            ->with('sales')
+            ->willReturn($this->adapterInterface);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+        $this->creditmemoDocumentFactoryMock->expects($this->once())
+            ->method('createFromOrder')
+            ->with(
+                $this->orderMock,
+                $items,
+                $this->creditmemoCommentCreationMock,
+                ($appendComment && $notify),
+                $this->creditmemoCreationArgumentsMock
+            )->willReturn($this->creditmemoMock);
+        $this->creditmemoValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoMock)
+            ->willReturn([]);
+        $this->orderValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->orderMock)
+            ->willReturn([]);
+        $this->itemCreationValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with(
+                reset($items),
+                [CreationQuantityValidator::class],
+                $this->orderMock
+            )->willReturn([]);
+        $this->paymentAdapterMock->expects($this->once())
+            ->method('refund')
+            ->with($this->creditmemoMock, $this->orderMock)
+            ->willReturn($this->orderMock);
+        $this->orderStateResolverMock->expects($this->once())
+            ->method('getStateForOrder')
+            ->with($this->orderMock, [])
+            ->willReturn(Order::STATE_CLOSED);
+
+        $this->orderMock->expects($this->once())
+            ->method('setState')
+            ->with(Order::STATE_CLOSED)
+            ->willReturnSelf();
+
+        $this->orderMock->expects($this->once())
+            ->method('getState')
+            ->willReturn(Order::STATE_CLOSED);
+
+        $this->configMock->expects($this->once())
+            ->method('getStateDefaultStatus')
+            ->with(Order::STATE_CLOSED)
+            ->willReturn('Closed');
+
+        $this->orderMock->expects($this->once())
+            ->method('setStatus')
+            ->with('Closed')
+            ->willReturnSelf();
+
+        $this->creditmemoMock->expects($this->once())
+            ->method('setState')
+            ->with(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED)
+            ->willReturnSelf();
+
+        $this->creditmemoRepositoryMock->expects($this->once())
+            ->method('save')
+            ->with($this->creditmemoMock)
+            ->willReturn($this->creditmemoMock);
+
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('save')
+            ->with($this->orderMock)
+            ->willReturn($this->orderMock);
+
+        if ($notify) {
+            $this->notifierMock->expects($this->once())
+                ->method('notify')
+                ->with($this->orderMock, $this->creditmemoMock, $this->creditmemoCommentCreationMock);
+        }
+
+        $this->creditmemoMock->expects($this->once())
+            ->method('getEntityId')
+            ->willReturn(2);
+
+        $this->assertEquals(
+            2,
+            $this->refundOrder->execute(
+                $orderId,
+                $items,
+                $notify,
+                $appendComment,
+                $this->creditmemoCommentCreationMock,
+                $this->creditmemoCreationArgumentsMock
+            )
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
+     */
+    public function testDocumentValidationException()
+    {
+        $orderId = 1;
+        $items = [$this->creditmemoItemCreationMock];
+        $notify = true;
+        $appendComment = true;
+        $errorMessages = ['error1', 'error2'];
+
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+
+        $this->creditmemoDocumentFactoryMock->expects($this->once())
+            ->method('createFromOrder')
+            ->with(
+                $this->orderMock,
+                $items,
+                $this->creditmemoCommentCreationMock,
+                ($appendComment && $notify),
+                $this->creditmemoCreationArgumentsMock
+            )->willReturn($this->creditmemoMock);
+
+        $this->creditmemoValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoMock)
+            ->willReturn($errorMessages);
+        $this->orderValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->orderMock)
+            ->willReturn([]);
+        $this->itemCreationValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with(reset($items), [CreationQuantityValidator::class], $this->orderMock)
+            ->willReturn([]);
+
+        $this->assertEquals(
+            $errorMessages,
+            $this->refundOrder->execute(
+                $orderId,
+                $items,
+                $notify,
+                $appendComment,
+                $this->creditmemoCommentCreationMock,
+                $this->creditmemoCreationArgumentsMock
+            )
+        );
+    }
+
+    /**
+     * @expectedException \Magento\Sales\Api\Exception\CouldNotRefundExceptionInterface
+     */
+    public function testCouldNotCreditmemoException()
+    {
+        $orderId = 1;
+        $items = [$this->creditmemoItemCreationMock];
+        $notify = true;
+        $appendComment = true;
+        $this->resourceConnectionMock->expects($this->once())
+            ->method('getConnection')
+            ->with('sales')
+            ->willReturn($this->adapterInterface);
+        $this->orderRepositoryMock->expects($this->once())
+            ->method('get')
+            ->willReturn($this->orderMock);
+        $this->creditmemoDocumentFactoryMock->expects($this->once())
+            ->method('createFromOrder')
+            ->with(
+                $this->orderMock,
+                $items,
+                $this->creditmemoCommentCreationMock,
+                ($appendComment && $notify),
+                $this->creditmemoCreationArgumentsMock
+            )->willReturn($this->creditmemoMock);
+        $this->itemCreationValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with(reset($items), [CreationQuantityValidator::class], $this->orderMock)
+            ->willReturn([]);
+        $this->creditmemoValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->creditmemoMock)
+            ->willReturn([]);
+        $this->orderValidatorMock->expects($this->once())
+            ->method('validate')
+            ->with($this->orderMock)
+            ->willReturn([]);
+        $e = new \Exception();
+        $this->paymentAdapterMock->expects($this->once())
+            ->method('refund')
+            ->with($this->creditmemoMock, $this->orderMock)
+            ->willThrowException($e);
+        $this->loggerMock->expects($this->once())
+            ->method('critical')
+            ->with($e);
+        $this->adapterInterface->expects($this->once())
+            ->method('rollBack');
+
+        $this->refundOrder->execute(
+            $orderId,
+            $items,
+            $notify,
+            $appendComment,
+            $this->creditmemoCommentCreationMock,
+            $this->creditmemoCreationArgumentsMock
+        );
+    }
+
+    public function dataProvider()
+    {
+        return [
+            'TestWithNotifyTrue' => [1, true, true],
+            'TestWithNotifyFalse' => [1, false, true],
+        ];
+    }
+}
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index dfdb0f6a261c51490e99955bdd61fd1bae328e60..03437a8e9801324e9346f55a63ffb6c7d5582a31 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -47,6 +47,8 @@
     <preference for="Magento\Sales\Api\CreditmemoItemRepositoryInterface" type="Magento\Sales\Api\Data\CreditmemoItem\Repository"/>
     <preference for="Magento\Sales\Api\CreditmemoRepositoryInterface" type="Magento\Sales\Model\Order\CreditmemoRepository"/>
     <preference for="Magento\Sales\Api\CreditmemoManagementInterface" type="Magento\Sales\Model\Service\CreditmemoService"/>
+    <preference for="Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface" type="Magento\Sales\Model\Order\Creditmemo\CreationArguments"/>
+    <preference for="Magento\Sales\Api\Data\CreditmemoItemCreationInterface" type="Magento\Sales\Model\Order\Creditmemo\ItemCreation"/>
     <preference for="Magento\Sales\Api\InvoiceCommentRepositoryInterface" type="Magento\Sales\Api\Data\InvoiceComment\Repository"/>
     <preference for="Magento\Sales\Api\InvoiceItemRepositoryInterface" type="Magento\Sales\Api\Data\InvoiceItem\Repository"/>
     <preference for="Magento\Sales\Api\InvoiceRepositoryInterface" type="Magento\Sales\Model\Order\InvoiceRepository"/>
@@ -55,6 +57,7 @@
     <preference for="Magento\Sales\Api\Data\InvoiceItemCreationInterface" type="Magento\Sales\Model\Order\Invoice\ItemCreation"/>
     <preference for="Magento\Sales\Api\Data\InvoiceCommentCreationInterface" type="Magento\Sales\Model\Order\Invoice\CommentCreation"/>
     <preference for="Magento\Sales\Api\Data\ShipmentCommentCreationInterface" type="Magento\Sales\Model\Order\Shipment\CommentCreation"/>
+    <preference for="Magento\Sales\Api\Data\CreditmemoCommentCreationInterface" type="Magento\Sales\Model\Order\Creditmemo\CommentCreation"/>
     <preference for="Magento\Sales\Api\OrderAddressRepositoryInterface" type="Magento\Sales\Model\Order\AddressRepository"/>
     <preference for="Magento\Sales\Api\OrderCustomerManagementInterface" type="Magento\Sales\Model\Order\CustomerManagement"/>
     <preference for="Magento\Sales\Api\OrderItemRepositoryInterface" type="Magento\Sales\Model\Order\ItemRepository"/>
@@ -100,6 +103,11 @@
     <preference for="Magento\Sales\Model\Order\OrderValidatorInterface" type="Magento\Sales\Model\Order\OrderValidator"/>
     <preference for="Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface" type="Magento\Sales\Model\Order\Invoice\InvoiceValidator"/>
     <preference for="Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface" type="Magento\Sales\Model\Order\Shipment\ShipmentValidator"/>
+    <preference for="Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface" type="Magento\Sales\Model\Order\Creditmemo\CreditmemoValidator"/>
+    <preference for="Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface" type="Magento\Sales\Model\Order\Creditmemo\ItemCreationValidator"/>
+    <preference for="Magento\Sales\Model\Order\Creditmemo\NotifierInterface" type="Magento\Sales\Model\Order\Creditmemo\Notifier"/>
+    <preference for="Magento\Sales\Api\RefundOrderInterface" type="Magento\Sales\Model\RefundOrder"/>
+    <preference for="Magento\Sales\Api\RefundInvoiceInterface" type="Magento\Sales\Model\RefundInvoice"/>
     <type name="Magento\Sales\Model\ResourceModel\Report" shared="false"/>
     <type name="Magento\Sales\Model\Order\Pdf\Config\Reader">
         <arguments>
@@ -926,6 +934,13 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Sales\Model\Order\Creditmemo\Notifier">
+        <arguments>
+            <argument name="senders" xsi:type="array">
+                <item name="email" xsi:type="object">Magento\Sales\Model\Order\Creditmemo\Sender\EmailSender</item>
+            </argument>
+        </arguments>
+    </type>
     <type name="Magento\Framework\EntityManager\HydratorPool">
         <arguments>
             <argument name="hydrators" xsi:type="array">
diff --git a/app/code/Magento/Sales/etc/webapi.xml b/app/code/Magento/Sales/etc/webapi.xml
index 4c7fe03a201f8b25c2af83219fd0576da4c2cd3e..b2a88b8cad709d1b0726a66d70b9779241840409 100644
--- a/app/code/Magento/Sales/etc/webapi.xml
+++ b/app/code/Magento/Sales/etc/webapi.xml
@@ -133,6 +133,12 @@
             <resource ref="Magento_Sales::sales" />
         </resources>
     </route>
+    <route url="/V1/invoice/:invoiceId/refund" method="POST">
+        <service class="Magento\Sales\Api\RefundInvoiceInterface" method="execute"/>
+        <resources>
+            <resource ref="Magento_Sales::sales" />
+        </resources>
+    </route>
     <route url="/V1/creditmemo/:id/comments" method="GET">
         <service class="Magento\Sales\Api\CreditmemoManagementInterface" method="getCommentsList"/>
         <resources>
@@ -181,6 +187,12 @@
             <resource ref="Magento_Sales::sales" />
         </resources>
     </route>
+    <route url="/V1/order/:orderId/refund" method="POST">
+        <service class="Magento\Sales\Api\RefundOrderInterface" method="execute"/>
+        <resources>
+            <resource ref="Magento_Sales::sales" />
+        </resources>
+    </route>
     <route url="/V1/shipment/:id" method="GET">
         <service class="Magento\Sales\Api\ShipmentRepositoryInterface" method="get"/>
         <resources>
diff --git a/app/code/Magento/Theme/Model/Theme/ThemeProvider.php b/app/code/Magento/Theme/Model/Theme/ThemeProvider.php
index 29da9c3220ee29fa60b24085336eaff919d5b3dd..9fd3ce94dac45888687cbe6bff26f86a2ac9d2e7 100644
--- a/app/code/Magento/Theme/Model/Theme/ThemeProvider.php
+++ b/app/code/Magento/Theme/Model/Theme/ThemeProvider.php
@@ -22,6 +22,11 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide
      */
     protected $cache;
 
+    /**
+     * @var \Magento\Framework\View\Design\ThemeInterface[]
+     */
+    private $themes;
+
     /**
      * ThemeProvider constructor.
      *
@@ -44,10 +49,14 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide
      */
     public function getThemeByFullPath($fullPath)
     {
+        if (isset($this->themes[$fullPath])) {
+            return $this->themes[$fullPath];
+        }
         /** @var $themeCollection \Magento\Theme\Model\ResourceModel\Theme\Collection */
         $theme = $this->cache->load('theme'. $fullPath);
         if ($theme) {
-            return unserialize($theme);
+            $this->themes[$fullPath] = unserialize($theme);
+            return $this->themes[$fullPath];
         }
         $themeCollection = $this->collectionFactory->create();
         $item = $themeCollection->getThemeByFullPath($fullPath);
@@ -55,7 +64,9 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide
             $themeData = serialize($item);
             $this->cache->save($themeData, 'theme' . $fullPath);
             $this->cache->save($themeData, 'theme-by-id-' . $item->getId());
+            $this->themes[$fullPath] = $item;
         }
+
         return $item;
     }
 
@@ -77,15 +88,20 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide
      */
     public function getThemeById($themeId)
     {
+        if (isset($this->themes[$themeId])) {
+            return $this->themes[$themeId];
+        }
         $theme = $this->cache->load('theme-by-id-' . $themeId);
         if ($theme) {
-            return unserialize($theme);
+            $this->themes[$themeId] = unserialize($theme);
+            return $this->themes[$themeId];
         }
         /** @var $themeModel \Magento\Framework\View\Design\ThemeInterface */
         $themeModel = $this->themeFactory->create();
         $themeModel->load($themeId);
         if ($themeModel->getId()) {
             $this->cache->save(serialize($themeModel), 'theme-by-id-' . $themeId);
+            $this->themes[$themeId] = $themeModel;
         }
         return $themeModel;
     }
diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php
index 0e6689461e14b6d8647693aa2dc942a240bc2cc2..ce85936607192b81fcdd9da59fb48704fc92d48e 100644
--- a/app/code/Magento/Vault/Model/Method/Vault.php
+++ b/app/code/Magento/Vault/Model/Method/Vault.php
@@ -579,7 +579,7 @@ final class Vault implements VaultPaymentInterface
     public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null)
     {
         return $this->getVaultProvider()->isAvailable($quote)
-            && $this->config->getValue(self::$activeKey, $this->getStore() ?: $quote->getStoreId());
+            && $this->config->getValue(self::$activeKey, $this->getStore() ?: ($quote ? $quote->getStoreId() : null));
     }
 
     /**
diff --git a/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php b/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php
index 434a5875b5bb1b4bffb1b77547e64740107b902d..6a86ed694642c31407ba0c08e3ac39f646244cca 100644
--- a/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php
+++ b/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php
@@ -7,7 +7,7 @@ namespace Magento\Vault\Model\Ui;
 
 use Magento\Checkout\Model\ConfigProviderInterface;
 use Magento\Framework\App\ObjectManager;
-use Magento\Payment\Helper\Data;
+use Magento\Payment\Api\Data\PaymentMethodInterface;
 use Magento\Store\Model\StoreManagerInterface;
 use Magento\Vault\Model\CustomerTokenManagement;
 use Magento\Vault\Model\VaultPaymentInterface;
@@ -39,9 +39,14 @@ final class TokensConfigProvider implements ConfigProviderInterface
     private $customerTokenManagement;
 
     /**
-     * @var Data
+     * @var \Magento\Payment\Api\PaymentMethodListInterface
      */
-    private $paymentDataHelper;
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory
+     */
+    private $paymentMethodInstanceFactory;
 
     /**
      * Constructor
@@ -98,21 +103,16 @@ final class TokensConfigProvider implements ConfigProviderInterface
     }
 
     /**
-     * Get list of available vault ui token providers
+     * Get list of available vault ui token providers.
+     *
      * @return TokenUiComponentProviderInterface[]
      */
     private function getComponentProviders()
     {
         $providers = [];
-        $storeId = $this->storeManager->getStore()->getId();
-        $paymentMethods = $this->getPaymentDataHelper()->getStoreMethods($storeId);
+        $vaultPaymentMethods = $this->getVaultPaymentMethodList();
 
-        foreach ($paymentMethods as $method) {
-            /** VaultPaymentInterface $method */
-            if (!$method instanceof VaultPaymentInterface || !$method->isActive($storeId)) {
-                continue;
-            }
-            
+        foreach ($vaultPaymentMethods as $method) {
             $providerCode = $method->getProviderCode();
             $componentProvider = $this->getComponentProvider($providerCode);
             if ($componentProvider === null) {
@@ -139,15 +139,60 @@ final class TokensConfigProvider implements ConfigProviderInterface
     }
 
     /**
-     * Get payment data helper instance
-     * @return Data
+     * Get list of active Vault payment methods.
+     *
+     * @return VaultPaymentInterface[]
+     */
+    private function getVaultPaymentMethodList()
+    {
+        $storeId = $this->storeManager->getStore()->getId();
+
+        $paymentMethods = array_map(
+            function (PaymentMethodInterface $paymentMethod) {
+                return $this->getPaymentMethodInstanceFactory()->create($paymentMethod);
+            },
+            $this->getPaymentMethodList()->getActiveList($storeId)
+        );
+
+        $availableMethods = array_filter(
+            $paymentMethods,
+            function (\Magento\Payment\Model\MethodInterface $methodInstance) {
+                return $methodInstance instanceof VaultPaymentInterface;
+            }
+        );
+
+        return $availableMethods;
+    }
+
+    /**
+     * Get payment method list.
+     *
+     * @return \Magento\Payment\Api\PaymentMethodListInterface
+     * @deprecated
+     */
+    private function getPaymentMethodList()
+    {
+        if ($this->paymentMethodList === null) {
+            $this->paymentMethodList = ObjectManager::getInstance()->get(
+                \Magento\Payment\Api\PaymentMethodListInterface::class
+            );
+        }
+        return $this->paymentMethodList;
+    }
+
+    /**
+     * Get payment method instance factory.
+     *
+     * @return \Magento\Payment\Model\Method\InstanceFactory
      * @deprecated
      */
-    private function getPaymentDataHelper()
+    private function getPaymentMethodInstanceFactory()
     {
-        if ($this->paymentDataHelper === null) {
-            $this->paymentDataHelper = ObjectManager::getInstance()->get(Data::class);
+        if ($this->paymentMethodInstanceFactory === null) {
+            $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get(
+                \Magento\Payment\Model\Method\InstanceFactory::class
+            );
         }
-        return $this->paymentDataHelper;
+        return $this->paymentMethodInstanceFactory;
     }
 }
diff --git a/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php b/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php
index 8bf88e7a94f509ddd9d23c239dd59dccb15d4e85..9cd7b97562df98e309b9ddd182a5ced144b2e555 100644
--- a/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php
+++ b/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php
@@ -8,7 +8,7 @@ namespace Magento\Vault\Model\Ui;
 use Magento\Checkout\Model\ConfigProviderInterface;
 use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Session\SessionManagerInterface;
-use Magento\Payment\Helper\Data;
+use Magento\Payment\Api\Data\PaymentMethodInterface;
 use Magento\Store\Model\StoreManagerInterface;
 use Magento\Vault\Model\VaultPaymentInterface;
 
@@ -32,9 +32,14 @@ class VaultConfigProvider implements ConfigProviderInterface
     private $session;
 
     /**
-     * @var Data
+     * @var \Magento\Payment\Api\PaymentMethodListInterface
      */
-    private $paymentDataHelper;
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory
+     */
+    private $paymentMethodInstanceFactory;
 
     /**
      * VaultConfigProvider constructor.
@@ -73,36 +78,60 @@ class VaultConfigProvider implements ConfigProviderInterface
     }
 
     /**
-     * Get list of active Vault payment methods
-     * @return array
+     * Get list of active Vault payment methods.
+     *
+     * @return VaultPaymentInterface[]
      */
     private function getVaultPaymentMethodList()
     {
-        $availableMethods = [];
         $storeId = $this->storeManager->getStore()->getId();
 
-        $paymentMethods = $this->getPaymentDataHelper()->getStoreMethods($storeId);
-        foreach ($paymentMethods as $method) {
-            /** VaultPaymentInterface $method */
-            if (!$method instanceof VaultPaymentInterface) {
-                continue;
+        $paymentMethods = array_map(
+            function (PaymentMethodInterface $paymentMethod) {
+                return $this->getPaymentMethodInstanceFactory()->create($paymentMethod);
+            },
+            $this->getPaymentMethodList()->getActiveList($storeId)
+        );
+
+        $availableMethods = array_filter(
+            $paymentMethods,
+            function (\Magento\Payment\Model\MethodInterface $methodInstance) {
+                return $methodInstance instanceof VaultPaymentInterface;
             }
-            $availableMethods[] = $method;
-        }
+        );
 
         return $availableMethods;
     }
 
     /**
-     * Get payment data helper instance
-     * @return Data
+     * Get payment method list.
+     *
+     * @return \Magento\Payment\Api\PaymentMethodListInterface
+     * @deprecated
+     */
+    private function getPaymentMethodList()
+    {
+        if ($this->paymentMethodList === null) {
+            $this->paymentMethodList = ObjectManager::getInstance()->get(
+                \Magento\Payment\Api\PaymentMethodListInterface::class
+            );
+        }
+        return $this->paymentMethodList;
+    }
+
+    /**
+     * Get payment method instance factory.
+     *
+     * @return \Magento\Payment\Model\Method\InstanceFactory
      * @deprecated
      */
-    private function getPaymentDataHelper()
+    private function getPaymentMethodInstanceFactory()
     {
-        if ($this->paymentDataHelper === null) {
-            $this->paymentDataHelper = ObjectManager::getInstance()->get(Data::class);
+        if ($this->paymentMethodInstanceFactory === null) {
+            $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get(
+                \Magento\Payment\Model\Method\InstanceFactory::class
+            );
         }
-        return $this->paymentDataHelper;
+        return $this->paymentMethodInstanceFactory;
     }
 }
diff --git a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php
index fc6f3e4d2390b58303ff9261b86787fa50b1535a..e5c83911025246d10c56687604d343b233db382b 100644
--- a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php
+++ b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php
@@ -274,4 +274,32 @@ class VaultTest extends \PHPUnit_Framework_TestCase
             ['isAvailableProvider' => true, 'isActiveVault' => true, 'expected' => true],
         ];
     }
+
+    /**
+     * @covers \Magento\Vault\Model\Method\Vault::isAvailable
+     */
+    public function testIsAvailableWithoutQuote()
+    {
+        $quote = null;
+
+        $vaultProvider = $this->getMockForAbstractClass(MethodInterface::class);
+        $config = $this->getMockForAbstractClass(ConfigInterface::class);
+
+        $vaultProvider->expects(static::once())
+            ->method('isAvailable')
+            ->with($quote)
+            ->willReturn(true);
+
+        $config->expects(static::once())
+            ->method('getValue')
+            ->with('active', $quote)
+            ->willReturn(false);
+
+        /** @var Vault $model */
+        $model = $this->objectManager->getObject(Vault::class, [
+            'config' => $config,
+            'vaultProvider' => $vaultProvider
+        ]);
+        static::assertFalse($model->isAvailable($quote));
+    }
 }
diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php
index e64fff27ff14ca0ad0aa0ffc6d4c0eda539f8be1..3e4b8b145af67da759d6445234d18bd528832810 100644
--- a/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php
+++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php
@@ -7,7 +7,6 @@ namespace Magento\Vault\Test\Unit\Model\Ui;
 
 use Magento\Customer\Model\Session;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use Magento\Payment\Helper\Data;
 use Magento\Store\Api\Data\StoreInterface;
 use Magento\Store\Model\StoreManagerInterface;
 use Magento\Vault\Api\Data\PaymentTokenInterface;
@@ -33,10 +32,25 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase
     private $storeManager;
 
     /**
-     * @var VaultPaymentInterface|MockObject
+     * @var \Magento\Payment\Api\PaymentMethodListInterface|MockObject
+     */
+    private $paymentMethodList;
+
+    /**
+     * @var \Magento\Payment\Model\Method\InstanceFactory|MockObject
+     */
+    private $paymentMethodInstanceFactory;
+
+    /**
+     * @var \Magento\Payment\Api\Data\PaymentMethodInterface|MockObject
      */
     private $vaultPayment;
 
+    /**
+     * @var VaultPaymentInterface|MockObject
+     */
+    private $vaultPaymentInstance;
+
     /**
      * @var StoreInterface|MockObject
      */
@@ -47,11 +61,6 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase
      */
     private $customerTokenManagement;
 
-    /**
-     * @var Data|MockObject
-     */
-    private $paymentDataHelper;
-
     /**
      * @var ObjectManager
      */
@@ -59,13 +68,18 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->vaultPayment = $this->getMock(VaultPaymentInterface::class);
+        $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class)
+            ->disableOriginalConstructor()
+            ->getMockForAbstractClass();
+
+        $this->paymentMethodInstanceFactory = $this->getMockBuilder(
+            \Magento\Payment\Model\Method\InstanceFactory::class
+        )->disableOriginalConstructor()->getMock();
+
+        $this->vaultPayment = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class);
+        $this->vaultPaymentInstance = $this->getMockForAbstractClass(VaultPaymentInterface::class);
         $this->storeManager = $this->getMock(StoreManagerInterface::class);
         $this->store = $this->getMock(StoreInterface::class);
-        $this->paymentDataHelper = $this->getMockBuilder(Data::class)
-            ->disableOriginalConstructor()
-            ->setMethods(['getStoreMethods'])
-            ->getMock();
 
         $this->objectManager = new ObjectManager($this);
         $this->customerTokenManagement = $this->getMockBuilder(CustomerTokenManagement::class)
@@ -99,17 +113,17 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase
         $this->store->expects(static::once())
             ->method('getId')
             ->willReturn($storeId);
-        
-        $this->paymentDataHelper->expects(static::once())
-            ->method('getStoreMethods')
+
+        $this->paymentMethodList->expects(static::once())
+            ->method('getActiveList')
             ->with($storeId)
             ->willReturn([$this->vaultPayment]);
+
+        $this->paymentMethodInstanceFactory->expects($this->once())
+            ->method('create')
+            ->willReturn($this->vaultPaymentInstance);
         
-        $this->vaultPayment->expects(static::once())
-            ->method('isActive')
-            ->with($storeId)
-            ->willReturn(true);
-        $this->vaultPayment->expects(static::once())
+        $this->vaultPaymentInstance->expects(static::once())
             ->method('getProviderCode')
             ->willReturn($vaultProviderCode);
 
@@ -142,8 +156,13 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase
 
         $this->objectManager->setBackwardCompatibleProperty(
             $configProvider,
-            'paymentDataHelper',
-            $this->paymentDataHelper
+            'paymentMethodList',
+            $this->paymentMethodList
+        );
+        $this->objectManager->setBackwardCompatibleProperty(
+            $configProvider,
+            'paymentMethodInstanceFactory',
+            $this->paymentMethodInstanceFactory
         );
 
         static::assertEquals($expectedConfig, $configProvider->getConfig());
diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php
index 7d0d8339ab2b1825a77041ff4f2beb0a6d686ea1..d00531637b86d4e509662a31ce2da32752ba4a8b 100644
--- a/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php
+++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php
@@ -7,7 +7,6 @@ namespace Magento\Vault\Test\Unit\Model\Ui;
 
 use Magento\Customer\Model\Session;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use Magento\Payment\Helper\Data;
 use Magento\Store\Api\Data\StoreInterface;
 use Magento\Store\Model\StoreManagerInterface;
 use Magento\Vault\Model\Ui\VaultConfigProvider;
@@ -21,15 +20,25 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject;
 class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var Data|MockObject
+     * @var \Magento\Payment\Api\PaymentMethodListInterface|MockObject
      */
-    private $paymentDataHelper;
+    private $paymentMethodList;
 
     /**
-     * @var VaultPaymentInterface|MockObject
+     * @var \Magento\Payment\Model\Method\InstanceFactory|MockObject
+     */
+    private $paymentMethodInstanceFactory;
+
+    /**
+     * @var \Magento\Payment\Api\Data\PaymentMethodInterface|MockObject
      */
     private $vaultPayment;
 
+    /**
+     * @var VaultPaymentInterface|MockObject
+     */
+    private $vaultPaymentInstance;
+
     /**
      * @var Session|MockObject
      */
@@ -52,12 +61,16 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->paymentDataHelper = $this->getMockBuilder(Data::class)
+        $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class)
             ->disableOriginalConstructor()
-            ->setMethods(['getStoreMethods'])
-            ->getMock();
+            ->getMockForAbstractClass();
 
-        $this->vaultPayment = $this->getMockForAbstractClass(VaultPaymentInterface::class);
+        $this->paymentMethodInstanceFactory = $this->getMockBuilder(
+            \Magento\Payment\Model\Method\InstanceFactory::class
+        )->disableOriginalConstructor()->getMock();
+
+        $this->vaultPayment = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class);
+        $this->vaultPaymentInstance = $this->getMockForAbstractClass(VaultPaymentInterface::class);
         $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class);
         $this->store = $this->getMockForAbstractClass(StoreInterface::class);
         $this->session = $this->getMockBuilder(Session::class)
@@ -68,8 +81,13 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase
         $this->vaultConfigProvider = new VaultConfigProvider($this->storeManager, $this->session);
         $objectManager->setBackwardCompatibleProperty(
             $this->vaultConfigProvider,
-            'paymentDataHelper',
-            $this->paymentDataHelper
+            'paymentMethodList',
+            $this->paymentMethodList
+        );
+        $objectManager->setBackwardCompatibleProperty(
+            $this->vaultConfigProvider,
+            'paymentMethodInstanceFactory',
+            $this->paymentMethodInstanceFactory
         );
     }
 
@@ -101,15 +119,19 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase
             ->method('getId')
             ->willReturn($storeId);
 
-        $this->paymentDataHelper->expects(static::once())
-            ->method('getStoreMethods')
+        $this->paymentMethodList->expects(static::once())
+            ->method('getActiveList')
             ->with($storeId)
             ->willReturn([$this->vaultPayment]);
 
-        $this->vaultPayment->expects(static::once())
+        $this->paymentMethodInstanceFactory->expects($this->once())
+            ->method('create')
+            ->willReturn($this->vaultPaymentInstance);
+
+        $this->vaultPaymentInstance->expects(static::once())
             ->method('getCode')
             ->willReturn($vaultPaymentCode);
-        $this->vaultPayment->expects($customerId !== null ? static::once() : static::never())
+        $this->vaultPaymentInstance->expects($customerId !== null ? static::once() : static::never())
             ->method('isActive')
             ->with($storeId)
             ->willReturn($vaultEnabled);
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2050e01cbfdd70f5b26b40d4728d265b6b50fc77
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php
@@ -0,0 +1,314 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Sales\Service\V1;
+
+/**
+ * API test for creation of Creditmemo for certain Order.
+ */
+class RefundOrderTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_READ_NAME = 'salesRefundOrderV1';
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * @var \Magento\Sales\Api\CreditmemoRepositoryInterface
+     */
+    private $creditmemoRepository;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        $this->creditmemoRepository = $this->objectManager->get(
+            \Magento\Sales\Api\CreditmemoRepositoryInterface::class
+        );
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order_with_shipping_and_invoice.php
+     */
+    public function testShortRequest()
+    {
+        /** @var \Magento\Sales\Model\Order $existingOrder */
+        $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+            ->loadByIncrementId('100000001');
+
+        $result = $this->_webApiCall(
+            $this->getServiceData($existingOrder),
+            ['orderId' => $existingOrder->getEntityId()]
+        );
+
+        $this->assertNotEmpty(
+            $result,
+            'Failed asserting that the received response is correct'
+        );
+
+        /** @var \Magento\Sales\Model\Order $updatedOrder */
+        $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+            ->loadByIncrementId($existingOrder->getIncrementId());
+
+        try {
+            $creditmemo = $this->creditmemoRepository->get($result);
+
+            $expectedItems = $this->getOrderItems($existingOrder);
+            $actualCreditmemoItems = $this->getCreditmemoItems($creditmemo);
+            $actualRefundedOrderItems = $this->getRefundedOrderItems($updatedOrder);
+
+            $this->assertEquals(
+                $expectedItems,
+                $actualCreditmemoItems,
+                'Failed asserting that the Creditmemo contains all requested items'
+            );
+
+            $this->assertEquals(
+                $expectedItems,
+                $actualRefundedOrderItems,
+                'Failed asserting that all requested order items were refunded'
+            );
+
+            $this->assertEquals(
+                $creditmemo->getShippingAmount(),
+                $existingOrder->getShippingAmount(),
+                'Failed asserting that the Creditmemo contains correct shipping amount'
+            );
+
+            $this->assertEquals(
+                $creditmemo->getShippingAmount(),
+                $updatedOrder->getShippingRefunded(),
+                'Failed asserting that proper shipping amount of the Order was refunded'
+            );
+
+            $this->assertNotEquals(
+                $existingOrder->getStatus(),
+                $updatedOrder->getStatus(),
+                'Failed asserting that order status was changed'
+            );
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            $this->fail('Failed asserting that Creditmemo was created');
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order_with_shipping_and_invoice.php
+     */
+    public function testFullRequest()
+    {
+        /** @var \Magento\Sales\Model\Order $existingOrder */
+        $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+            ->loadByIncrementId('100000001');
+
+        $expectedItems = $this->getOrderItems($existingOrder);
+        $expectedItems[0]['qty'] = $expectedItems[0]['qty'] - 1;
+
+        $expectedComment = [
+            'comment' => 'Test Comment',
+            'is_visible_on_front' => 1
+        ];
+
+        $expectedShippingAmount = 15;
+        $expectedAdjustmentPositive = 5.53;
+        $expectedAdjustmentNegative = 5.53;
+
+        $result = $this->_webApiCall(
+            $this->getServiceData($existingOrder),
+            [
+                'orderId' => $existingOrder->getEntityId(),
+                'items' => $expectedItems,
+                'comment' => $expectedComment,
+                'arguments' => [
+                    'shipping_amount' => $expectedShippingAmount,
+                    'adjustment_positive' => $expectedAdjustmentPositive,
+                    'adjustment_negative' => $expectedAdjustmentNegative
+                ]
+            ]
+        );
+
+        $this->assertNotEmpty(
+            $result,
+            'Failed asserting that the received response is correct'
+        );
+
+        /** @var \Magento\Sales\Model\Order $updatedOrder */
+        $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+            ->loadByIncrementId($existingOrder->getIncrementId());
+
+        try {
+            $creditmemo = $this->creditmemoRepository->get($result);
+
+            $actualCreditmemoItems = $this->getCreditmemoItems($creditmemo);
+            $actualCreditmemoComment = $this->getRecentComment($creditmemo);
+            $actualRefundedOrderItems = $this->getRefundedOrderItems($updatedOrder);
+
+            $this->assertEquals(
+                $expectedItems,
+                $actualCreditmemoItems,
+                'Failed asserting that the Creditmemo contains all requested items'
+            );
+
+            $this->assertEquals(
+                $expectedItems,
+                $actualRefundedOrderItems,
+                'Failed asserting that all requested order items were refunded'
+            );
+
+            $this->assertEquals(
+                $expectedComment,
+                $actualCreditmemoComment,
+                'Failed asserting that the Creditmemo contains correct comment'
+            );
+
+            $this->assertEquals(
+                $expectedShippingAmount,
+                $creditmemo->getShippingAmount(),
+                'Failed asserting that the Creditmemo contains correct shipping amount'
+            );
+
+            $this->assertEquals(
+                $expectedShippingAmount,
+                $updatedOrder->getShippingRefunded(),
+                'Failed asserting that proper shipping amount of the Order was refunded'
+            );
+
+            $this->assertEquals(
+                $expectedAdjustmentPositive,
+                $creditmemo->getAdjustmentPositive(),
+                'Failed asserting that the Creditmemo contains correct positive adjustment'
+            );
+
+            $this->assertEquals(
+                $expectedAdjustmentNegative,
+                $creditmemo->getAdjustmentNegative(),
+                'Failed asserting that the Creditmemo contains correct negative adjustment'
+            );
+
+            $this->assertEquals(
+                $existingOrder->getStatus(),
+                $updatedOrder->getStatus(),
+                'Failed asserting that order status was NOT changed'
+            );
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            $this->fail('Failed asserting that Creditmemo was created');
+        }
+    }
+
+    /**
+     * Prepares and returns info for API service.
+     *
+     * @param \Magento\Sales\Api\Data\OrderInterface $order
+     *
+     * @return array
+     */
+    private function getServiceData(\Magento\Sales\Api\Data\OrderInterface $order)
+    {
+        return [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getEntityId() . '/refund',
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'execute',
+            ]
+        ];
+    }
+
+    /**
+     * Gets all items of given Order in proper format.
+     *
+     * @param \Magento\Sales\Model\Order $order
+     *
+     * @return array
+     */
+    private function getOrderItems(\Magento\Sales\Model\Order $order)
+    {
+        $items = [];
+
+        /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */
+        foreach ($order->getAllItems() as $item) {
+            $items[] = [
+                'order_item_id' => $item->getItemId(),
+                'qty' => $item->getQtyOrdered(),
+            ];
+        }
+
+        return $items;
+    }
+
+    /**
+     * Gets refunded items of given Order in proper format.
+     *
+     * @param \Magento\Sales\Model\Order $order
+     *
+     * @return array
+     */
+    private function getRefundedOrderItems(\Magento\Sales\Model\Order $order)
+    {
+        $items = [];
+
+        /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */
+        foreach ($order->getAllItems() as $item) {
+            if ($item->getQtyRefunded() > 0) {
+                $items[] = [
+                    'order_item_id' => $item->getItemId(),
+                    'qty' => $item->getQtyRefunded(),
+                ];
+            }
+        }
+
+        return $items;
+    }
+
+    /**
+     * Gets all items of given Creditmemo in proper format.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
+     *
+     * @return array
+     */
+    private function getCreditmemoItems(\Magento\Sales\Api\Data\CreditmemoInterface $creditmemo)
+    {
+        $items = [];
+
+        /** @var \Magento\Sales\Api\Data\CreditmemoItemInterface $item */
+        foreach ($creditmemo->getItems() as $item) {
+            $items[] = [
+                'order_item_id' => $item->getOrderItemId(),
+                'qty' => $item->getQty(),
+            ];
+        }
+
+        return $items;
+    }
+
+    /**
+     * Gets the most recent comment of given Creditmemo in proper format.
+     *
+     * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo
+     *
+     * @return array|null
+     */
+    private function getRecentComment(\Magento\Sales\Api\Data\CreditmemoInterface $creditmemo)
+    {
+        $comments = $creditmemo->getComments();
+
+        if ($comments) {
+            $comment = reset($comments);
+
+            return [
+                'comment' => $comment->getComment(),
+                'is_visible_on_front' => $comment->getIsVisibleOnFront(),
+            ];
+        }
+
+        return null;
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php
index f46f070216c6e59456281af4ae99762898b3da71..c85c857cbeb5450ae39c29bc6658ef920e984f96 100644
--- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php
+++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/System/Config/Braintree.php
@@ -33,7 +33,7 @@ class Braintree extends Block
     private $enablers = [
         'Enable this Solution' => "#payment_us_braintree_section_braintree_active",
         'Enable PayPal through Braintree' => '#payment_us_braintree_section_braintree_active_braintree_paypal',
-        'Vault enabled' => '#payment_us_braintree_section_braintree_braintree_cc_vault_active'
+        'Vault Enabled' => '#payment_us_braintree_section_braintree_braintree_cc_vault_active'
     ];
 
     /**
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php
index f05092745c7608ca1a83bdc54fba527d116e95cb..e3b01aa3af9a0dfa4517ad3fe4b527eec9d30ad0 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php
@@ -42,7 +42,7 @@ class PayflowPro extends Block
             '_payflow_required_enable_paypal_payflow',
         'Enable PayPal Credit' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal' .
             '_payflow_required_enable_express_checkout_bml_payflow',
-        'Vault enabled' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal_' .
+        'Vault Enabled' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal_' .
             'payflow_required_payflowpro_cc_vault_active'
     ];
 
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php
index 709dc4b54b53f275b222a1cc2981bce88f7fb605..cd146aa8f809cf5de0cbddcb02be83a19e59928f 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php
@@ -42,7 +42,7 @@ class PaymentsPro extends Block
             '_payflow',
         'Enable PayPal Credit' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_enable_' .
             'express_checkout_bml_payflow',
-        'Vault enabled' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_payflowpro_cc_vault' .
+        'Vault Enabled' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_payflowpro_cc_vault' .
             '_active'
     ];
 
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php
index a468fbde2f4d252997f1f84973063b397ea074b5..7e0c9d934161eb36d8b16454d8b263978cd6c21a 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPayflowProConfigStep.php
@@ -130,7 +130,7 @@ class CheckPayflowProConfigStep implements TestStepInterface
         $this->payflowProConfigBlock->enablePayflowPro();
         $this->assertFieldsAreActive->processAssert(
             $this->systemConfigEditSectionPayment,
-            [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault enabled']]
+            [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault Enabled']]
         );
         $this->assertFieldsAreEnabled->processAssert(
             $this->systemConfigEditSectionPayment,
diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php
index 2555eab030720c475f56aa28181607a0be452fac..4c3318d14f9611a7890e8621e29661cafc1ca620 100644
--- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php
+++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestStep/CheckPaymentsProConfigStep.php
@@ -130,7 +130,7 @@ class CheckPaymentsProConfigStep implements TestStepInterface
         $this->paymentsProConfigBlock->enablePaymentsPro();
         $this->assertFieldsAreActive->processAssert(
             $this->systemConfigEditSectionPayment,
-            [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault enabled']]
+            [$enablers['Enable this Solution'], $enablers['Enable PayPal Credit'], $enablers['Vault Enabled']]
         );
         $this->assertFieldsAreEnabled->processAssert(
             $this->systemConfigEditSectionPayment,
diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php
index 0ce9dd2867a13fe09cc24bfb11fe6dcd404a2522..437203e072e9626465f6ba9c2a44dd7e97551695 100644
--- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php
+++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php
@@ -30,6 +30,13 @@ class SelectVersion extends Form
      */
     protected $firstField = '#selectVersion';
 
+    /**
+     * Show all versions checkbox
+     *
+     * @var string
+     */
+    private $showAllVersions = '#showUnstable';
+
     /**
      * Click on 'Next' button.
      *
@@ -50,9 +57,24 @@ class SelectVersion extends Form
     public function fill(FixtureInterface $fixture, SimpleElement $element = null)
     {
         $this->waitForElementVisible($this->firstField);
+        $this->chooseShowAllVersions();
+
         return parent::fill($fixture, $element);
     }
 
+    /**
+     * Show all versions include unstable
+     *
+     * @return void
+     */
+    private function chooseShowAllVersions()
+    {
+        $element = $this->_rootElement->find($this->showAllVersions, Locator::SELECTOR_CSS);
+        if ($element->isVisible()) {
+            $element->click();
+        }
+    }
+
     /**
      * Choose 'yes' for upgrade option called 'Other components'
      *
diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php
index 7f7d17f60679c3ef1a04810505eaab0ad7533bf2..dcfb2922d5c787150487ecf95542ed9d24411b64 100644
--- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php
+++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessMessage.php
@@ -23,7 +23,7 @@ class AssertSuccessMessage extends AbstractConstraint
      */
     public function processAssert(SetupWizard $setupWizard, $package)
     {
-        $message = "You upgraded:";
+        $message = "You upgraded";
         \PHPUnit_Framework_Assert::assertContains(
             $message,
             $setupWizard->getSuccessMessage()->getUpdaterStatus(),
diff --git a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml
index 6a5c6e5d904ddb2b9de92ab7f851f97ffad7db44..e1f1395bc132c892f884ceb170dd3ade6bdf525b 100644
--- a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml
+++ b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/setup.xml
@@ -11,5 +11,8 @@
         <allow>
             <module value="Magento_Setup"/>
         </allow>
+        <deny>
+            <class value="Magento\Setup\Test\TestCase\UpgradeSystemTest"/>
+        </deny>
     </rule>
 </config>
diff --git a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/upgrade.xml b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/upgrade.xml
new file mode 100644
index 0000000000000000000000000000000000000000..71f50859db921163bf50a8f85d79e96c58f288a6
--- /dev/null
+++ b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/upgrade.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * 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/Magento/Mtf/TestRunner/etc/testRunner.xsd">
+    <rule scope="testsuite">
+        <allow>
+            <class value="Magento\Setup\Test\TestCase\UpgradeSystemTest"/>
+        </allow>
+    </rule>
+</config>
\ No newline at end of file
diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php
index f90fc157e37296e61d1dd15f9254d1803edb8752..c155ef8a5c9c9ee9ba708c3f74e33331ce330aaf 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php
@@ -140,43 +140,44 @@ class MinifierTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * @magentoConfigFixture current_store dev/css/minify_files 1
+     * @magentoConfigFixture current_store dev/css/minify_files 0
+     * @magentoAppIsolation enabled
      */
-    public function testCssMinification()
+    public function testCssMinificationOff()
     {
         $this->_testCssMinification(
-            '/frontend/FrameworkViewMinifier/default/en_US/css/styles.min.css',
+            '/frontend/FrameworkViewMinifier/default/en_US/css/styles.css',
             function ($path) {
-                $this->assertEquals(
+                $content = file_get_contents($path);
+                $this->assertNotEmpty($content);
+                $this->assertContains('FrameworkViewMinifier/frontend', $content);
+                $this->assertNotEquals(
                     file_get_contents(
                         dirname(__DIR__)
                         . '/_files/static/expected/styles.magento.min.css'
                     ),
-                    file_get_contents($path),
-                    'Minified files are not equal or minification did not work for requested CSS'
+                    $content,
+                    'CSS is minified when minification turned off'
                 );
             }
         );
     }
 
     /**
-     * @magentoConfigFixture current_store dev/css/minify_files 0
+     * @magentoConfigFixture current_store dev/css/minify_files 1
      */
-    public function testCssMinificationOff()
+    public function testCssMinification()
     {
         $this->_testCssMinification(
-            '/frontend/FrameworkViewMinifier/default/en_US/css/styles.css',
+            '/frontend/FrameworkViewMinifier/default/en_US/css/styles.min.css',
             function ($path) {
-                $content = file_get_contents($path);
-                $this->assertNotEmpty($content);
-                $this->assertContains('FrameworkViewMinifier/frontend', $content);
-                $this->assertNotEquals(
+                $this->assertEquals(
                     file_get_contents(
                         dirname(__DIR__)
                         . '/_files/static/expected/styles.magento.min.css'
                     ),
-                    $content,
-                    'CSS is minified when minification turned off'
+                    file_get_contents($path),
+                    'Minified files are not equal or minification did not work for requested CSS'
                 );
             }
         );
@@ -234,13 +235,13 @@ class MinifierTest extends \PHPUnit_Framework_TestCase
                 ]
             ));
 
-        /** @var \Magento\Deploy\Model\Deployer $deployer */
+        /** @var \Magento\Deploy\Model\Deploy\LocaleDeploy $deployer */
         $deployer = $this->objectManager->create(
-            \Magento\Deploy\Model\Deployer::class,
+            \Magento\Deploy\Model\Deploy\LocaleDeploy::class,
             ['filesUtil' => $filesUtil, 'output' => $output]
         );
 
-        $deployer->deploy($omFactory, ['en_US'], ['frontend' => ['FrameworkViewMinifier/default']]);
+        $deployer->deploy('frontend', 'FrameworkViewMinifier/default', 'en_US', []);
 
         $this->assertFileExists($fileToBePublished);
         $this->assertEquals(
diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml
index 40bf9602edb12ad06b710839876d7af438b5a831..f9f8f1ffba91a55536c9d2221b68dc85b702e7f0 100644
--- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml
+++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml
@@ -115,7 +115,7 @@
                         </requires>
                     </field>
                     <field id="payflowpro_cc_vault_active" translate="label" type="select" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="0">
-                        <label>Vault enabled</label>
+                        <label>Vault Enabled</label>
                         <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                         <config_path>payment/payflowpro_cc_vault/active</config_path>
                         <attribute type="shared">1</attribute>
diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php
new file mode 100644
index 0000000000000000000000000000000000000000..3dfa428c4aad48ee2bfbd8eadbc10fc516090d67
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+require 'order.php';
+
+$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+/** @var \Magento\Sales\Model\Order $order */
+$order = $objectManager->create(\Magento\Sales\Model\Order::class)
+    ->loadByIncrementId('100000001');
+
+/** @var \Magento\Sales\Model\Service\InvoiceService $invoiceService */
+$invoiceService = $objectManager->create(\Magento\Sales\Api\InvoiceManagementInterface::class);
+
+/** @var \Magento\Framework\DB\Transaction $transaction */
+$transaction = $objectManager->create(\Magento\Framework\DB\Transaction::class);
+
+$order->setData(
+    'base_to_global_rate',
+    1
+)->setData(
+    'base_to_order_rate',
+    1
+)->setData(
+    'shipping_amount',
+    20
+)->setData(
+    'base_shipping_amount',
+    20
+);
+
+$invoice = $invoiceService->prepareInvoice($order);
+$invoice->register();
+
+$order->setIsInProcess(true);
+
+$transaction->addObject($invoice)->addObject($order)->save();
diff --git a/lib/internal/Magento/Framework/App/Config/ScopePool.php b/lib/internal/Magento/Framework/App/Config/ScopePool.php
index d366349722f0fe8157be6b21ad1a290d17cf67b2..9e6a47d918f7602cd2e2b1d50286eb82f3f5cca6 100644
--- a/lib/internal/Magento/Framework/App/Config/ScopePool.php
+++ b/lib/internal/Magento/Framework/App/Config/ScopePool.php
@@ -92,16 +92,18 @@ class ScopePool
     {
         $scopeCode = $this->_getScopeCode($scopeType, $scopeCode);
 
-        // Key by url to support dynamic {{base_url}} and port assignments
-        $host = $this->getRequest()->getHttpHost();
-        $port = $this->getRequest()->getServer('SERVER_PORT');
-        $path = $this->getRequest()->getBasePath();
-        $urlInfo = $host . $port . trim($path, '/');
-        $code = $scopeType . '|' . $scopeCode . '|' . $urlInfo;
+        $code = $scopeType . '|' . $scopeCode;
 
         if (!isset($this->_scopes[$code])) {
-            $cacheKey = $this->_cacheId . '|' . $code;
+            // Key by url to support dynamic {{base_url}} and port assignments
+            $host = $this->getRequest()->getHttpHost();
+            $port = $this->getRequest()->getServer('SERVER_PORT');
+            $path = $this->getRequest()->getBasePath();
+
+            $urlInfo = $host . $port . trim($path, '/');
+            $cacheKey = $this->_cacheId . '|' . $code . '|' . $urlInfo;
             $data = $this->_cache->load($cacheKey);
+
             if ($data) {
                 $data = unserialize($data);
             } else {
diff --git a/lib/internal/Magento/Framework/App/ResourceConnection.php b/lib/internal/Magento/Framework/App/ResourceConnection.php
index 5a2e4a6710a187d3506dc96020e7f17209513ff6..d41a457d4559cf077259f70210a2318e61f7d93f 100644
--- a/lib/internal/Magento/Framework/App/ResourceConnection.php
+++ b/lib/internal/Magento/Framework/App/ResourceConnection.php
@@ -92,6 +92,18 @@ class ResourceConnection
         return $this->getConnectionByName($connectionName);
     }
 
+    /**
+     * @param string $resourceName
+     * @return void
+     */
+    public function closeConnection($resourceName = self::DEFAULT_CONNECTION)
+    {
+        $processConnectionName = $this->getProcessConnectionName($this->config->getConnectionName($resourceName));
+        if (isset($this->connections[$processConnectionName])) {
+            $this->connections[$processConnectionName] = null;
+        }
+    }
+
     /**
      * Retrieve connection by $connectionName
      *
@@ -101,8 +113,9 @@ class ResourceConnection
      */
     public function getConnectionByName($connectionName)
     {
-        if (isset($this->connections[$connectionName])) {
-            return $this->connections[$connectionName];
+        $processConnectionName = $this->getProcessConnectionName($connectionName);
+        if (isset($this->connections[$processConnectionName])) {
+            return $this->connections[$processConnectionName];
         }
 
         $connectionConfig = $this->deploymentConfig->get(
@@ -115,10 +128,19 @@ class ResourceConnection
             throw new \DomainException('Connection "' . $connectionName . '" is not defined');
         }
 
-        $this->connections[$connectionName] = $connection;
+        $this->connections[$processConnectionName] = $connection;
         return $connection;
     }
 
+    /**
+     * @param string $connectionName
+     * @return string
+     */
+    private function getProcessConnectionName($connectionName)
+    {
+        return  $connectionName . '_process_' . getmypid();
+    }
+
     /**
      * Get resource table name, validated by db adapter
      *
diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php
index 49d2545ea5bb807bdd7853047e0496966e5ba177..5f2414283ad89a8dd62ebca632e95844ed9a6575 100644
--- a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php
+++ b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php
@@ -81,9 +81,11 @@ class Processor implements ContentProcessorInterface
             }
 
             $tmpFilePath = $this->temporaryFile->createFile($path, $content);
-            $parser->parseFile($tmpFilePath, '');
 
+            gc_disable();
+            $parser->parseFile($tmpFilePath, '');
             $content = $parser->getCss();
+            gc_enable();
 
             if (trim($content) === '') {
                 $errorMessage = PHP_EOL . self::ERROR_MESSAGE_PREFIX . PHP_EOL . $path;
diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php b/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php
index a07c7eea16aeb452aac3b1561b313bf2296a51fb..ab5db005978b1ec4e96ee8295a2a75a8cd5986b5 100644
--- a/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php
+++ b/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php
@@ -5,15 +5,18 @@
  */
 namespace Magento\Framework\Css\PreProcessor\Instruction;
 
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Css\PreProcessor\ErrorHandlerInterface;
 use Magento\Framework\View\Asset\File\FallbackContext;
 use Magento\Framework\View\Asset\LocalInterface;
 use Magento\Framework\View\Asset\PreProcessorInterface;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
 use Magento\Framework\View\DesignInterface;
 use Magento\Framework\View\File\CollectorInterface;
 
 /**
  * @magento_import instruction preprocessor
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) Must be deleted after moving themeProvider to construct
  */
 class MagentoImport implements PreProcessorInterface
 {
@@ -45,9 +48,15 @@ class MagentoImport implements PreProcessorInterface
 
     /**
      * @var \Magento\Framework\View\Design\Theme\ListInterface
+     * @deprecated
      */
     protected $themeList;
 
+    /**
+     * @var ThemeProviderInterface
+     */
+    private $themeProvider;
+
     /**
      * @param DesignInterface $design
      * @param CollectorInterface $fileSource
@@ -120,8 +129,23 @@ class MagentoImport implements PreProcessorInterface
     {
         $context = $asset->getContext();
         if ($context instanceof FallbackContext) {
-            return $this->themeList->getThemeByFullPath($context->getAreaCode() . '/' . $context->getThemePath());
+            return $this->getThemeProvider()->getThemeByFullPath(
+                $context->getAreaCode() . '/' . $context->getThemePath()
+            );
         }
         return $this->design->getDesignTheme();
     }
+
+    /**
+     * @return ThemeProviderInterface
+     * @deprecated
+     */
+    private function getThemeProvider()
+    {
+        if (null === $this->themeProvider) {
+            $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class);
+        }
+
+        return $this->themeProvider;
+    }
 }
diff --git a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php
index f5c81d78436aa40e8218d22c6af90a3c388e1891..9bc04c8ff33e9183deebee50faa08765949503d4 100644
--- a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php
+++ b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php
@@ -8,6 +8,10 @@
 
 namespace Magento\Framework\Css\Test\Unit\PreProcessor\Instruction;
 
+use Magento\Framework\Css\PreProcessor\Instruction\MagentoImport;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
+
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
@@ -39,9 +43,9 @@ class MagentoImportTest extends \PHPUnit_Framework_TestCase
     private $assetRepo;
 
     /**
-     * @var \Magento\Framework\View\Design\Theme\ListInterface|\PHPUnit_Framework_MockObject_MockObject
+     * @var ThemeProviderInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    private $themeList;
+    private $themeProvider;
 
     /**
      * @var \Magento\Framework\Css\PreProcessor\Instruction\Import
@@ -58,14 +62,14 @@ class MagentoImportTest extends \PHPUnit_Framework_TestCase
         $this->asset = $this->getMock(\Magento\Framework\View\Asset\File::class, [], [], '', false);
         $this->asset->expects($this->any())->method('getContentType')->will($this->returnValue('css'));
         $this->assetRepo = $this->getMock(\Magento\Framework\View\Asset\Repository::class, [], [], '', false);
-        $this->themeList = $this->getMockForAbstractClass(\Magento\Framework\View\Design\Theme\ListInterface::class);
-        $this->object = new \Magento\Framework\Css\PreProcessor\Instruction\MagentoImport(
-            $this->design,
-            $this->fileSource,
-            $this->errorHandler,
-            $this->assetRepo,
-            $this->themeList
-        );
+        $this->themeProvider = $this->getMock(ThemeProviderInterface::class);
+        $this->object = (new ObjectManager($this))->getObject(MagentoImport::class, [
+            'design' => $this->design,
+            'fileSource' => $this->fileSource,
+            'errorHandler' => $this->errorHandler,
+            'assetRepo' => $this->assetRepo,
+            'themeProvider' => $this->themeProvider
+        ]);
     }
 
     /**
@@ -91,7 +95,7 @@ class MagentoImportTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue($relatedAsset));
         $relatedAsset->expects($this->once())->method('getContext')->will($this->returnValue($context));
         $theme = $this->getMockForAbstractClass(\Magento\Framework\View\Design\ThemeInterface::class);
-        $this->themeList->expects($this->once())->method('getThemeByFullPath')->will($this->returnValue($theme));
+        $this->themeProvider->expects($this->once())->method('getThemeByFullPath')->will($this->returnValue($theme));
         $files = [];
         foreach ($foundFiles as $file) {
             $fileObject = $this->getMock(\Magento\Framework\View\File::class, [], [], '', false);
diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php
index 1f69922b799893068da3d91958ca8d53f79e0424..b7752ddccf886b391e23b121ba29fa324348e68c 100644
--- a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php
+++ b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php
@@ -144,8 +144,6 @@ class Write extends Read implements WriteInterface
      */
     public function createSymlink($path, $destination, WriteInterface $targetDirectory = null)
     {
-        $this->assertIsFile($path);
-
         $targetDirectory = $targetDirectory ?: $this;
         $parentDirectory = $this->driver->getParentDirectory($destination);
         if (!$targetDirectory->isExist($parentDirectory)) {
diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php b/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php
index 472c54a08d202d2acfe5d0150949a5e254f04447..3fa0513dd21a0b7a5cd279204547b0aa4069879c 100644
--- a/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php
+++ b/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php
@@ -48,7 +48,7 @@ interface WriteInterface extends ReadInterface
     public function copyFile($path, $destination, WriteInterface $targetDirectory = null);
 
     /**
-     * Creates symlink on a file and places it to destination
+     * Creates symlink on a file or directory and places it to destination
      *
      * @param string $path
      * @param string $destination
diff --git a/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php b/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9cbdea93cf4fcd549b26731e670741ed8391be36
--- /dev/null
+++ b/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\Test\Unit\App;
+
+use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\Config\ConfigOptionsListConstants;
+use Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactoryInterface;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\App\ResourceConnection\ConfigInterface;
+
+class ResourceConnectionTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var ResourceConnection
+     */
+    private $unit;
+
+    /**
+     * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $deploymentConfigMock;
+
+    /**
+     * @var ConnectionFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $connectionFactoryMock;
+
+    /**
+     * @var ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var ConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $configMock;
+
+    protected function setUp()
+    {
+        $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->connectionFactoryMock = $this->getMockBuilder(ConnectionFactoryInterface::class)
+            ->getMock();
+
+        $this->configMock = $this->getMockBuilder(ConfigInterface::class)->getMock();
+
+        $this->objectManager = (new ObjectManager($this));
+        $this->unit = $this->objectManager->getObject(
+            ResourceConnection::class,
+            [
+                'deploymentConfig' => $this->deploymentConfigMock,
+                'connectionFactory' => $this->connectionFactoryMock,
+                'config' => $this->configMock,
+            ]
+        );
+    }
+
+    public function testGetConnectionByName()
+    {
+        $this->deploymentConfigMock->expects(self::once())->method('get')
+            ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS  . '/default')
+            ->willReturn(['config']);
+        $this->connectionFactoryMock->expects(self::once())->method('create')
+            ->with(['config'])
+            ->willReturn('connection');
+
+        self::assertEquals('connection', $this->unit->getConnectionByName('default'));
+    }
+
+    public function testGetExistingConnectionByName()
+    {
+        $unit = $this->objectManager->getObject(
+            ResourceConnection::class,
+            [
+                'deploymentConfig' => $this->deploymentConfigMock,
+                'connections' => ['default_process_' . getmypid() => 'existing_connection']
+            ]
+        );
+        $this->deploymentConfigMock->expects(self::never())->method('get');
+
+        self::assertEquals('existing_connection', $unit->getConnectionByName('default'));
+    }
+
+    public function testCloseConnection()
+    {
+        $this->configMock->expects(self::once())->method('getConnectionName')->with('default');
+
+        $this->unit->closeConnection('default');
+
+    }
+}
diff --git a/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php b/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php
index 21e974fca57e01b3154c614a6cb64f5f52850e2e..0fe15fd14af709d151259209872ebfeacb46bff0 100644
--- a/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php
+++ b/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php
@@ -6,10 +6,12 @@
 
 namespace Magento\Framework\View\Asset\Bundle;
 
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\View;
 use Magento\Framework\View\Asset\Bundle;
 use Magento\Framework\View\Design\Theme\ListInterface;
 use Magento\Framework\View\Asset\File\FallbackContext;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
 
 class Config implements Bundle\ConfigInterface
 {
@@ -30,6 +32,16 @@ class Config implements Bundle\ConfigInterface
      */
     protected $viewConfig;
 
+    /**
+     * @var ThemeProviderInterface
+     */
+    private $themeProvider;
+
+    /**
+     * @var \Magento\Framework\Config\View[]
+     */
+    private $config = [];
+
     /**
      * @param View\ConfigInterface $viewConfig
      * @param ListInterface $themeList
@@ -57,12 +69,17 @@ class Config implements Bundle\ConfigInterface
      */
     public function getConfig(FallbackContext $assetContext)
     {
-        return $this->viewConfig->getViewConfig([
-            'area' => $assetContext->getAreaCode(),
-            'themeModel' => $this->themeList->getThemeByFullPath(
-                $assetContext->getAreaCode() . '/' . $assetContext->getThemePath()
-            )
-        ]);
+        $themePath = $assetContext->getAreaCode() . '/' . $assetContext->getThemePath();
+        if (!isset($this->config[$themePath])) {
+            $this->config[$themePath] = $this->viewConfig->getViewConfig([
+                'area' => $assetContext->getAreaCode(),
+                'themeModel' => $this->getThemeProvider()->getThemeByFullPath(
+                    $themePath
+                )
+            ]);
+        }
+
+        return $this->config[$themePath];
     }
 
     /**
@@ -83,7 +100,19 @@ class Config implements Bundle\ConfigInterface
             case 'MB':
                 return (int)$size * 1024;
             default:
-                return (int)$size / 1024;
+                return (int)($size / 1024);
         }
     }
+
+    /**
+     * @return ThemeProviderInterface
+     */
+    private function getThemeProvider()
+    {
+        if (null === $this->themeProvider) {
+            $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class);
+        }
+
+        return $this->themeProvider;
+    }
 }
diff --git a/lib/internal/Magento/Framework/View/Asset/LockerProcess.php b/lib/internal/Magento/Framework/View/Asset/LockerProcess.php
index 660cf0418669ead047cb6713883d0a8039ab75bb..e8938211fecbf642ff3994515730e517013f1100 100644
--- a/lib/internal/Magento/Framework/View/Asset/LockerProcess.php
+++ b/lib/internal/Magento/Framework/View/Asset/LockerProcess.php
@@ -59,7 +59,6 @@ class LockerProcess implements LockerProcessInterface
 
     /**
      * @inheritdoc
-     * @throws FileSystemException
      */
     public function lockProcess($lockName)
     {
@@ -94,14 +93,18 @@ class LockerProcess implements LockerProcessInterface
      * Check whether generation process has already locked
      *
      * @return bool
-     * @throws FileSystemException
      */
     private function isProcessLocked()
     {
         if ($this->tmpDirectory->isExist($this->lockFilePath)) {
-            $lockTime = (int) $this->tmpDirectory->readFile($this->lockFilePath);
-            if ((time() - $lockTime) >= self::MAX_LOCK_TIME) {
-                $this->tmpDirectory->delete($this->lockFilePath);
+            try {
+                $lockTime = (int)$this->tmpDirectory->readFile($this->lockFilePath);
+                if ((time() - $lockTime) >= self::MAX_LOCK_TIME) {
+                    $this->tmpDirectory->delete($this->lockFilePath);
+
+                    return false;
+                }
+            } catch (FileSystemException $e) {
 
                 return false;
             }
diff --git a/lib/internal/Magento/Framework/View/Asset/Minification.php b/lib/internal/Magento/Framework/View/Asset/Minification.php
index 255c9690e3fa9f2515fc5ac50d97fb002b87ede8..1e32e32b99676e16e538445ae122221ff5e48ec6 100644
--- a/lib/internal/Magento/Framework/View/Asset/Minification.php
+++ b/lib/internal/Magento/Framework/View/Asset/Minification.php
@@ -21,18 +21,21 @@ class Minification
      * @var ScopeConfigInterface
      */
     private $scopeConfig;
+
     /**
      * @var State
      */
     private $appState;
+
     /**
      * @var string
      */
     private $scope;
+
     /**
      * @var array
      */
-    private $excludes = [];
+    private $configCache = [];
 
     /**
      * @param ScopeConfigInterface $scopeConfig
@@ -54,12 +57,16 @@ class Minification
      */
     public function isEnabled($contentType)
     {
-        return
-            $this->appState->getMode() != State::MODE_DEVELOPER &&
-            (bool)$this->scopeConfig->isSetFlag(
-                sprintf(self::XML_PATH_MINIFICATION_ENABLED, $contentType),
-                $this->scope
-            );
+        if (!isset($this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType])) {
+            $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType] =
+                $this->appState->getMode() != State::MODE_DEVELOPER &&
+                (bool)$this->scopeConfig->isSetFlag(
+                    sprintf(self::XML_PATH_MINIFICATION_ENABLED, $contentType),
+                    $this->scope
+                );
+        }
+
+        return $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType];
     }
 
     /**
@@ -131,15 +138,15 @@ class Minification
      */
     public function getExcludes($contentType)
     {
-        if (!isset($this->excludes[$contentType])) {
-            $this->excludes[$contentType] = [];
+        if (!isset($this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType])) {
+            $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType] = [];
             $key = sprintf(self::XML_PATH_MINIFICATION_EXCLUDES, $contentType);
             foreach (explode("\n", $this->scopeConfig->getValue($key, $this->scope)) as $exclude) {
                 if (trim($exclude) != '') {
-                    $this->excludes[$contentType][] = trim($exclude);
+                    $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType][] = trim($exclude);
                 }
             };
         }
-        return $this->excludes[$contentType];
+        return $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType];
     }
 }
diff --git a/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php b/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php
index 59108ea0f5c427acfe367344fbfe7b6f9dcdcfaf..4e6bba487a1b7388f49483e43049892660836b14 100644
--- a/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php
+++ b/lib/internal/Magento/Framework/View/Asset/NotationResolver/Variable.php
@@ -58,18 +58,21 @@ class Variable
     }
 
     /**
-     * Retrieves the value of a given placeholder
+     * Process placeholder
      *
      * @param string $placeholder
      * @return string
      */
     public function getPlaceholderValue($placeholder)
     {
+        /** @var \Magento\Framework\View\Asset\File\FallbackContext $context */
         $context = $this->assetRepo->getStaticViewFileContext();
 
         switch ($placeholder) {
             case self::VAR_BASE_URL_PATH:
-                return $context->getBaseUrl() . $context->getPath();
+                return '{{' . self::VAR_BASE_URL_PATH . '}}' . $context->getAreaCode() .
+                    ($context->getThemePath() ? '/' . $context->getThemePath() . '/' : '') .
+                    '{{locale}}';
             default:
                 return '';
         }
diff --git a/lib/internal/Magento/Framework/View/Asset/Repository.php b/lib/internal/Magento/Framework/View/Asset/Repository.php
index 072b3361cbefea99e2ebb5dd958cc8468bbda088..c898d57c11e7a6a26f205f1288097d4bb5a9a60c 100644
--- a/lib/internal/Magento/Framework/View/Asset/Repository.php
+++ b/lib/internal/Magento/Framework/View/Asset/Repository.php
@@ -8,7 +8,8 @@ namespace Magento\Framework\View\Asset;
 
 use Magento\Framework\UrlInterface;
 use Magento\Framework\App\Filesystem\DirectoryList;
-use Magento\Framework\Filesystem;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
 
 /**
  * A repository service for view assets
@@ -35,6 +36,7 @@ class Repository
 
     /**
      * @var \Magento\Framework\View\Design\Theme\ListInterface
+     * @deprecated
      */
     private $themeList;
 
@@ -72,11 +74,17 @@ class Repository
      * @var File\ContextFactory
      */
     private $contextFactory;
+
     /**
      * @var RemoteFactory
      */
     private $remoteFactory;
 
+    /**
+     * @var ThemeProviderInterface
+     */
+    private $themeProvider;
+
     /**
      * @param \Magento\Framework\UrlInterface $baseUrl
      * @param \Magento\Framework\View\DesignInterface $design
@@ -138,7 +146,7 @@ class Repository
         }
 
         if ($theme) {
-            $params['themeModel'] = $this->themeList->getThemeByFullPath($area . '/' . $theme);
+            $params['themeModel'] = $this->getThemeProvider()->getThemeByFullPath($area . '/' . $theme);
             if (!$params['themeModel']) {
                 throw new \UnexpectedValueException("Could not find theme '$theme' for area '$area'");
             }
@@ -158,6 +166,18 @@ class Repository
         return $this;
     }
 
+    /**
+     * @return ThemeProviderInterface
+     */
+    private function getThemeProvider()
+    {
+        if (null === $this->themeProvider) {
+            $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class);
+        }
+
+        return $this->themeProvider;
+    }
+
     /**
      * Get default design parameter
      *
diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php
index 1bde94402d1a868c80998329f3a6ccf8cc67ea1f..cf80bd0d1b3821cc3050cd94ee691de98e8385e4 100644
--- a/lib/internal/Magento/Framework/View/Asset/Source.php
+++ b/lib/internal/Magento/Framework/View/Asset/Source.php
@@ -8,8 +8,10 @@ namespace Magento\Framework\View\Asset;
 
 use Magento\Framework\App\Filesystem\DirectoryList;
 use Magento\Framework\Filesystem\Directory\ReadFactory;
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\View\Asset\PreProcessor\ChainFactoryInterface;
 use Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
 
 /**
  * A service for preprocessing content of assets
@@ -45,6 +47,7 @@ class Source
 
     /**
      * @var \Magento\Framework\View\Design\Theme\ListInterface
+     * @deprecated
      */
     private $themeList;
 
@@ -58,6 +61,11 @@ class Source
      */
     private $readFactory;
 
+    /**
+     * @var ThemeProviderInterface
+     */
+    private $themeProvider;
+
     /**
      * Constructor
      *
@@ -218,7 +226,9 @@ class Source
         LocalInterface $asset,
         \Magento\Framework\View\Asset\File\FallbackContext $context
     ) {
-        $themeModel = $this->themeList->getThemeByFullPath($context->getAreaCode() . '/' . $context->getThemePath());
+        $themeModel = $this->getThemeProvider()->getThemeByFullPath(
+            $context->getAreaCode() . '/' . $context->getThemePath()
+        );
         $sourceFile = $this->fallback->getFile(
             $context->getAreaCode(),
             $themeModel,
@@ -229,6 +239,18 @@ class Source
         return $sourceFile;
     }
 
+    /**
+     * @return ThemeProviderInterface
+     */
+    private function getThemeProvider()
+    {
+        if (null === $this->themeProvider) {
+            $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class);
+        }
+
+        return $this->themeProvider;
+    }
+
     /**
      * Find asset file by simply appending its path to the directory in context
      *
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php
index 77b81faf359aa316efb6415d3f2f506732d70169..b4bda4a93dfd51140e6cde6f6bf8e7fab5119b5a 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/NotationResolver/VariableTest.php
@@ -6,43 +6,51 @@
 
 namespace Magento\Framework\View\Test\Unit\Asset\NotationResolver;
 
-use Magento\Framework\App\Filesystem\DirectoryList;
-use Magento\Framework\View\Asset\NotationResolver;
+use Magento\Framework\UrlInterface;
+use Magento\Framework\View\Asset\File\FallbackContext;
+use Magento\Framework\View\Asset\NotationResolver\Variable;
+use Magento\Framework\View\Asset\Repository;
 
 class VariableTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \Magento\Framework\View\Asset\File\Context|\PHPUnit_Framework_MockObject_MockObject
+     * @var FallbackContext|\PHPUnit_Framework_MockObject_MockObject
      */
     private $context;
 
     /**
-     * @var \Magento\Framework\View\Asset\Repository|\PHPUnit_Framework_MockObject_MockObject
+     * @var Repository|\PHPUnit_Framework_MockObject_MockObject
      */
     private $assetRepo;
 
     /**
-     * @var \Magento\Framework\View\Asset\NotationResolver\Variable
+     * @var Variable
      */
     private $object;
 
     protected function setUp()
     {
-        $baseUrl = 'http://example.com/pub/static/';
-        $path = 'frontend/Magento/blank/en_US';
+        $area = 'frontend';
+        $themePath = 'Magento/blank';
 
-        $this->context = $this->getMock(
-            \Magento\Framework\View\Asset\File\Context::class,
-            null,
-            [$baseUrl, DirectoryList::STATIC_VIEW, $path]
-        );
+        $this->context = $this->getMockBuilder(FallbackContext::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->context->expects($this->once())
+            ->method('getAreaCode')
+            ->willReturn($area);
+        $this->context->expects($this->exactly(2))
+            ->method('getThemePath')
+            ->willReturn($themePath);
 
-        $this->assetRepo = $this->getMock(\Magento\Framework\View\Asset\Repository::class, [], [], '', false);
+        $this->assetRepo = $this->getMockBuilder(Repository::class)
+            ->disableOriginalConstructor()
+            ->getMock();
         $this->assetRepo->expects($this->any())
             ->method('getStaticViewFileContext')
             ->will($this->returnValue($this->context));
 
-        $this->object = new \Magento\Framework\View\Asset\NotationResolver\Variable($this->assetRepo);
+        $this->object = new Variable($this->assetRepo);
     }
 
     /**
@@ -61,7 +69,7 @@ class VariableTest extends \PHPUnit_Framework_TestCase
     public function convertVariableNotationDataProvider()
     {
         return [
-            ['{{base_url_path}}/file.ext', 'http://example.com/pub/static/frontend/Magento/blank/en_US/file.ext'],
+            ['{{base_url_path}}/file.ext', '{{base_url_path}}frontend/Magento/blank/{{locale}}/file.ext'],
         ];
     }
 }
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php
index e3c50843976588224b20a9b5236858cae1e2d4c1..3b51f7cc3df2a278561f5007f6380e235226a750 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php
@@ -6,7 +6,9 @@
 
 namespace Magento\Framework\View\Test\Unit\Asset;
 
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
 use Magento\Framework\View\Asset\Repository;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
 
 /**
  * Unit test for Magento\Framework\View\Asset\Repository
@@ -31,9 +33,9 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
     private $designMock;
 
     /**
-     * @var \Magento\Framework\View\Design\Theme\ListInterface|\PHPUnit_Framework_MockObject_MockObject
+     * @var ThemeProviderInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    private $listMock;
+    private $themeProvider;
 
     /**
      * @var \Magento\Framework\View\Asset\Source|\PHPUnit_Framework_MockObject_MockObject
@@ -76,9 +78,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
         $this->designMock = $this->getMockBuilder(\Magento\Framework\View\DesignInterface::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $this->listMock = $this->getMockBuilder(\Magento\Framework\View\Design\Theme\ListInterface::class)
-            ->disableOriginalConstructor()
-            ->getMock();
+        $this->themeProvider = $this->getMock(ThemeProviderInterface::class);
         $this->sourceMock = $this->getMockBuilder(\Magento\Framework\View\Asset\Source::class)
             ->disableOriginalConstructor()
             ->getMock();
@@ -103,17 +103,17 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $this->repository = new Repository(
-            $this->urlMock,
-            $this->designMock,
-            $this->listMock,
-            $this->sourceMock,
-            $this->httpMock,
-            $this->fileFactoryMock,
-            $this->fallbackFactoryMock,
-            $this->contextFactoryMock,
-            $this->remoteFactoryMock
-        );
+        $this->repository = (new ObjectManager($this))->getObject(Repository::class, [
+            'baseUrl' => $this->urlMock,
+            'design' => $this->designMock,
+            'themeProvider' => $this->themeProvider,
+            'assetSource' => $this->sourceMock,
+            'request' => $this->httpMock,
+            'fileFactory' => $this->fileFactoryMock,
+            'fallbackContextFactory' => $this->fallbackFactoryMock,
+            'contextFactory' => $this->contextFactoryMock,
+            'remoteFactory' => $this->remoteFactoryMock
+        ]);
     }
 
     /**
@@ -124,7 +124,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
     public function testUpdateDesignParamsWrongTheme()
     {
         $params = ['area' => 'area', 'theme' => 'nonexistent_theme'];
-        $this->listMock->expects($this->once())
+        $this->themeProvider->expects($this->once())
             ->method('getThemeByFullPath')
             ->with('area/nonexistent_theme')
             ->will($this->returnValue(null));
@@ -139,7 +139,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
      */
     public function testUpdateDesignParams($params, $result)
     {
-        $this->listMock
+        $this->themeProvider
             ->expects($this->any())
             ->method('getThemeByFullPath')
             ->willReturn('ThemeID');
@@ -169,7 +169,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
      */
     public function testCreateAsset()
     {
-        $this->listMock
+        $this->themeProvider
             ->expects($this->any())
             ->method('getThemeByFullPath')
             ->willReturnArgument(0);
@@ -231,7 +231,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
                     'locale' => 'locale'
                 ]
             );
-        $this->listMock
+        $this->themeProvider
             ->expects($this->any())
             ->method('getThemeByFullPath')
             ->willReturnArgument(0);
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php
index 66e33e4fe31401c4f874822551acfb85afb36b01..2a704650aa9dd6618f3925d197d4b9e1c732ade6 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php
@@ -10,9 +10,11 @@ namespace Magento\Framework\View\Test\Unit\Asset;
 
 use Magento\Framework\App\Filesystem\DirectoryList;
 use Magento\Framework\Filesystem\DriverPool;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
 use Magento\Framework\View\Asset\PreProcessor\ChainFactoryInterface;
 use Magento\Framework\View\Asset\PreProcessor\Chain;
 use Magento\Framework\View\Asset\Source;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
 
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -76,16 +78,16 @@ class SourceTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->preProcessorPool = $this->getMock(
+        $this->preProcessorPool = $this->getMock(
             \Magento\Framework\View\Asset\PreProcessor\Pool::class, [], [], '', false
         );
-        $this->viewFileResolution = $this->getMock(
+        $this->viewFileResolution = $this->getMock(
             \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile::class, [], [], '', false
         );
         $this->theme = $this->getMockForAbstractClass(\Magento\Framework\View\Design\ThemeInterface::class);
         /** @var \Magento\Framework\App\Config\ScopeConfigInterface $config */
 
-        $this->chainFactory = $this->getMockBuilder(
+        $this->chainFactory = $this->getMockBuilder(
             \Magento\Framework\View\Asset\PreProcessor\ChainFactoryInterface::class)
             ->getMock();
         $this->chain = $this->getMockBuilder(\Magento\Framework\View\Asset\PreProcessor\Chain::class)
@@ -96,30 +98,30 @@ class SourceTest extends \PHPUnit_Framework_TestCase
             ->method('create')
             ->willReturn($this->chain);
 
-        $themeList = $this->getMockForAbstractClass(\Magento\Framework\View\Design\Theme\ListInterface::class);
-        $themeList->expects($this->any())
+        $themeProvider = $this->getMock(ThemeProviderInterface::class);
+        $themeProvider->expects($this->any())
             ->method('getThemeByFullPath')
             ->with('frontend/magento_theme')
             ->willReturn($this->theme);
 
-        $this->readFactory = $this->getMock(
-            \Magento\Framework\Filesystem\Directory\ReadFactory::class,
-            [],
-            [],
-            '',
-            false
+        $this->readFactory = $this->getMock(
+            \Magento\Framework\Filesystem\Directory\ReadFactory::class,
+            [],
+            [],
+            '',
+            false
         );
 
         $this->initFilesystem();
 
-        $this->object = new Source(
-            $this->filesystem,
-            $this->readFactory,
-            $this->preProcessorPool,
-            $this->viewFileResolution,
-            $themeList,
-            $this->chainFactory
-        );
+        $this->object = (new ObjectManager($this))->getObject(Source::class, [
+            'filesystem' => $this->filesystem,
+            'readFactory' => $this->readFactory,
+            'preProcessorPool' => $this->preProcessorPool,
+            'fallback' => $this->viewFileResolution,
+            'themeProvider' => $themeProvider,
+            'chainFactory' => $this->chainFactory
+        ]);
     }
 
     /**
@@ -229,11 +231,11 @@ class SourceTest extends \PHPUnit_Framework_TestCase
     protected function initFilesystem()
     {
         $this->filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false);
-        $this->rootDirRead = $this->getMockForAbstractClass(
-            \Magento\Framework\Filesystem\Directory\ReadInterface::class
+        $this->rootDirRead = $this->getMockForAbstractClass(
+            \Magento\Framework\Filesystem\Directory\ReadInterface::class
         );
-        $this->staticDirRead = $this->getMockForAbstractClass(
-            \Magento\Framework\Filesystem\Directory\ReadInterface::class
+        $this->staticDirRead = $this->getMockForAbstractClass(
+            \Magento\Framework\Filesystem\Directory\ReadInterface::class
         );
         $this->varDir = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\WriteInterface::class);
 
diff --git a/setup/pub/magento/setup/updater-success.js b/setup/pub/magento/setup/updater-success.js
index d2e98394251c8cc5233e2852a25fbc71b00890a3..36e58958ed6911949ad0852b9f3b1d87f57ce8ca 100644
--- a/setup/pub/magento/setup/updater-success.js
+++ b/setup/pub/magento/setup/updater-success.js
@@ -8,8 +8,16 @@ angular.module('updater-success', ['ngStorage'])
     .controller('updaterSuccessController', ['$scope', '$state', '$localStorage', '$window', 'navigationService', function ($scope, $state, $localStorage, $window, navigationService) {
         if ($localStorage.successPageAction) {
             $scope.successPageAction = $localStorage.successPageAction;
-            $scope.successPageActionMessage = $scope.successPageAction +
-                ($scope.endsWith($scope.successPageAction, 'e')  ? 'd' : 'ed');
+            switch (true) {
+                case $scope.endsWith($scope.successPageAction, 'd'):
+                    $scope.successPageActionMessage = $scope.successPageAction;
+                    break;
+                case $scope.endsWith($scope.successPageAction, 'e'):
+                    $scope.successPageActionMessage = $scope.successPageAction + 'd';
+                    break;
+                default:
+                    $scope.successPageActionMessage = $scope.successPageAction + 'ed';
+            }
         }
         if ($localStorage.packages) {
             $scope.packages = $localStorage.packages;