diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php index e55d1e6bfe869..33f1b07e4d567 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php @@ -102,7 +102,7 @@ public function execute() $creditmemoManagement = $this->_objectManager->create( \Magento\Sales\Api\CreditmemoManagementInterface::class ); - $creditmemoManagement->refund($creditmemo, (bool)$data['do_offline'], !empty($data['send_email'])); + $creditmemoManagement->refund($creditmemo, (bool)$data['do_offline']); if (!empty($data['send_email'])) { $this->creditmemoSender->send($creditmemo); diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php new file mode 100644 index 0000000000000..ef60ad2ce51be --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/RefundOperation.php @@ -0,0 +1,126 @@ +eventManager = $context->getEventDispatcher(); + $this->priceCurrency = $priceCurrency; + } + + /** + * @param CreditmemoInterface $creditmemo + * @param OrderInterface $order + * @param bool $online + * @return OrderInterface + */ + public function execute(CreditmemoInterface $creditmemo, OrderInterface $order, $online = false) + { + if ($creditmemo->getState() == Creditmemo::STATE_REFUNDED + && $creditmemo->getOrderId() == $order->getEntityId() + ) { + foreach ($creditmemo->getItems() as $item) { + if ($item->isDeleted()) { + continue; + } + $item->setCreditMemo($creditmemo); + if ($item->getQty() > 0) { + $item->register(); + } else { + $item->isDeleted(true); + } + } + + $baseOrderRefund = $this->priceCurrency->round( + $order->getBaseTotalRefunded() + $creditmemo->getBaseGrandTotal() + ); + $orderRefund = $this->priceCurrency->round( + $order->getTotalRefunded() + $creditmemo->getGrandTotal() + ); + $order->setBaseTotalRefunded($baseOrderRefund); + $order->setTotalRefunded($orderRefund); + + $order->setBaseSubtotalRefunded($order->getBaseSubtotalRefunded() + $creditmemo->getBaseSubtotal()); + $order->setSubtotalRefunded($order->getSubtotalRefunded() + $creditmemo->getSubtotal()); + + $order->setBaseTaxRefunded($order->getBaseTaxRefunded() + $creditmemo->getBaseTaxAmount()); + $order->setTaxRefunded($order->getTaxRefunded() + $creditmemo->getTaxAmount()); + $order->setBaseDiscountTaxCompensationRefunded( + $order->getBaseDiscountTaxCompensationRefunded() + $creditmemo->getBaseDiscountTaxCompensationAmount() + ); + $order->setDiscountTaxCompensationRefunded( + $order->getDiscountTaxCompensationRefunded() + $creditmemo->getDiscountTaxCompensationAmount() + ); + + $order->setBaseShippingRefunded($order->getBaseShippingRefunded() + $creditmemo->getBaseShippingAmount()); + $order->setShippingRefunded($order->getShippingRefunded() + $creditmemo->getShippingAmount()); + + $order->setBaseShippingTaxRefunded( + $order->getBaseShippingTaxRefunded() + $creditmemo->getBaseShippingTaxAmount() + ); + $order->setShippingTaxRefunded($order->getShippingTaxRefunded() + $creditmemo->getShippingTaxAmount()); + + $order->setAdjustmentPositive($order->getAdjustmentPositive() + $creditmemo->getAdjustmentPositive()); + $order->setBaseAdjustmentPositive( + $order->getBaseAdjustmentPositive() + $creditmemo->getBaseAdjustmentPositive() + ); + + $order->setAdjustmentNegative($order->getAdjustmentNegative() + $creditmemo->getAdjustmentNegative()); + $order->setBaseAdjustmentNegative( + $order->getBaseAdjustmentNegative() + $creditmemo->getBaseAdjustmentNegative() + ); + + $order->setDiscountRefunded($order->getDiscountRefunded() + $creditmemo->getDiscountAmount()); + $order->setBaseDiscountRefunded($order->getBaseDiscountRefunded() + $creditmemo->getBaseDiscountAmount()); + + if ($online) { + $order->setTotalOnlineRefunded($order->getTotalOnlineRefunded() + $creditmemo->getGrandTotal()); + $order->setBaseTotalOnlineRefunded( + $order->getBaseTotalOnlineRefunded() + $creditmemo->getBaseGrandTotal() + ); + } else { + $order->setTotalOfflineRefunded($order->getTotalOfflineRefunded() + $creditmemo->getGrandTotal()); + $order->setBaseTotalOfflineRefunded( + $order->getBaseTotalOfflineRefunded() + $creditmemo->getBaseGrandTotal() + ); + } + + $order->setBaseTotalInvoicedCost( + $order->getBaseTotalInvoicedCost() - $creditmemo->getBaseCost() + ); + + if ($online) { + $order->getPayment()->refund($creditmemo); + } + + $this->eventManager->dispatch('sales_order_creditmemo_refund', ['creditmemo' => $creditmemo]); + } + + return $order; + } +} diff --git a/app/code/Magento/Sales/Model/Order/PaymentAdapter.php b/app/code/Magento/Sales/Model/Order/PaymentAdapter.php index 84eb0fa07553c..d176ce0566bb3 100644 --- a/app/code/Magento/Sales/Model/Order/PaymentAdapter.php +++ b/app/code/Magento/Sales/Model/Order/PaymentAdapter.php @@ -12,20 +12,40 @@ */ class PaymentAdapter implements PaymentAdapterInterface { + /** + * @var \Magento\Sales\Model\Order\Creditmemo\RefundOperation + */ + private $refundOperation; + /** * @var \Magento\Sales\Model\Order\Invoice\PayOperation */ private $payOperation; /** + * PaymentAdapter constructor. + * @param \Magento\Sales\Model\Order\Creditmemo\RefundOperation $refundOperation * @param \Magento\Sales\Model\Order\Invoice\PayOperation $payOperation */ public function __construct( + \Magento\Sales\Model\Order\Creditmemo\RefundOperation $refundOperation, \Magento\Sales\Model\Order\Invoice\PayOperation $payOperation ) { + $this->refundOperation = $refundOperation; $this->payOperation = $payOperation; } + /** + * {@inheritdoc} + */ + public function refund( + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\OrderInterface $order, + $isOnline = false + ) { + return $this->refundOperation->execute($creditmemo, $order, $isOnline); + } + /** * {@inheritdoc} */ diff --git a/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php b/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php index 0e4b193169da8..3636bc2592f3b 100644 --- a/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php +++ b/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php @@ -23,4 +23,16 @@ interface PaymentAdapterInterface * @return OrderInterface */ public function pay(OrderInterface $order, InvoiceInterface $invoice, $capture); + + /** + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param bool $isOnline + * @return \Magento\Sales\Api\Data\OrderInterface + */ + public function refund( + \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, + \Magento\Sales\Api\Data\OrderInterface $order, + $isOnline = false + ); } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Relation/Refund.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Relation/Refund.php index 6c38259432780..82df0aa0bebdf 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Relation/Refund.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Relation/Refund.php @@ -10,6 +10,7 @@ /** * Class Relation + * @deprecated */ class Refund implements RelationInterface { diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php index 54bb5a746e51f..4889ccd3750d5 100644 --- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php +++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php @@ -8,6 +8,7 @@ /** * Class CreditmemoService + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CreditmemoService implements \Magento\Sales\Api\CreditmemoManagementInterface { @@ -46,6 +47,26 @@ class CreditmemoService implements \Magento\Sales\Api\CreditmemoManagementInterf */ protected $eventManager; + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var \Magento\Sales\Model\Order\PaymentAdapterInterface + */ + private $paymentAdapter; + + /** + * @var \Magento\Sales\Api\OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var \Magento\Sales\Api\InvoiceRepositoryInterface + */ + private $invoiceRepository; + /** * @param \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository * @param \Magento\Sales\Api\CreditmemoCommentRepositoryInterface $creditmemoCommentRepository @@ -130,6 +151,7 @@ public function notify($id) * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo * @param bool $offlineRequested * @return \Magento\Sales\Api\Data\CreditmemoInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function refund( \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, @@ -138,19 +160,31 @@ public function refund( $this->validateForRefund($creditmemo); $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED); - foreach ($creditmemo->getAllItems() as $item) { - $item->setCreditMemo($creditmemo); - if ($item->getQty() > 0) { - $item->register(); - } else { - $item->isDeleted(true); + $connection = $this->getResource()->getConnection('sales'); + $connection->beginTransaction(); + try { + $order = $this->getPaymentAdapter()->refund( + $creditmemo, + $creditmemo->getOrder(), + !$offlineRequested + ); + $this->getOrderRepository()->save($order); + $invoice = $creditmemo->getInvoice(); + if ($invoice && !$offlineRequested) { + $invoice->setIsUsedForRefund(true); + $invoice->setBaseTotalRefunded( + $invoice->getBaseTotalRefunded() + $creditmemo->getBaseGrandTotal() + ); + $creditmemo->setInvoiceId($invoice->getId()); + $this->getInvoiceRepository()->save($creditmemo->getInvoice()); } + $this->creditmemoRepository->save($creditmemo); + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw new \Magento\Framework\Exception\LocalizedException($e->getMessage()); } - $creditmemo->setDoTransaction(!$offlineRequested); - - $this->eventManager->dispatch('sales_order_creditmemo_refund', ['creditmemo' => $creditmemo]); - $this->creditmemoRepository->save($creditmemo); return $creditmemo; } @@ -183,4 +217,60 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface } return true; } + + /** + * @return \Magento\Sales\Model\Order\PaymentAdapterInterface + * + * @deprecated + */ + private function getPaymentAdapter() + { + if ($this->paymentAdapter === null) { + $this->paymentAdapter = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Sales\Model\Order\PaymentAdapterInterface::class); + } + return $this->paymentAdapter; + } + + /** + * @return \Magento\Framework\App\ResourceConnection|mixed + * + * @deprecated + */ + private function getResource() + { + if ($this->resource === null) { + $this->resource = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\App\ResourceConnection::class); + } + return $this->resource; + } + + /** + * @return \Magento\Sales\Api\OrderRepositoryInterface + * + * @deprecated + */ + private function getOrderRepository() + { + if ($this->orderRepository === null) { + $this->orderRepository = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Sales\Api\OrderRepositoryInterface::class); + } + return $this->orderRepository; + } + + /** + * @return \Magento\Sales\Api\InvoiceRepositoryInterface + * + * @deprecated + */ + private function getInvoiceRepository() + { + if ($this->invoiceRepository === null) { + $this->invoiceRepository = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Sales\Api\InvoiceRepositoryInterface::class); + } + return $this->invoiceRepository; + } } 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 new file mode 100644 index 0000000000000..93caeba8643f1 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/RefundOperationTest.php @@ -0,0 +1,398 @@ +orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getBaseCost']) + ->getMockForAbstractClass(); + + $this->paymentMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + ->disableOriginalConstructor() + ->setMethods(['refund']) + ->getMockForAbstractClass(); + + $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + ->disableOriginalConstructor() + ->setMethods(['round']) + ->getMockForAbstractClass(); + + $contextMock = $this->getMockBuilder(\Magento\Framework\Model\Context::class) + ->disableOriginalConstructor() + ->setMethods(['getEventDispatcher']) + ->getMock(); + + $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $contextMock->expects($this->once()) + ->method('getEventDispatcher') + ->willReturn($this->eventManagerMock); + + $this->subject = new \Magento\Sales\Model\Order\Creditmemo\RefundOperation( + $contextMock, + $this->priceCurrencyMock + ); + } + + /** + * @param string $state + * @dataProvider executeNotRefundedCreditmemoDataProvider + */ + public function testExecuteNotRefundedCreditmemo($state) + { + $this->creditmemoMock->expects($this->once()) + ->method('getState') + ->willReturn($state); + $this->orderMock->expects($this->never()) + ->method('getEntityId'); + $this->assertEquals( + $this->orderMock, + $this->subject->execute( + $this->creditmemoMock, + $this->orderMock + ) + ); + } + + /** + * Data provider for testExecuteNotRefundedCreditmemo + * @return array + */ + public function executeNotRefundedCreditmemoDataProvider() + { + return [ + [Creditmemo::STATE_OPEN], + [Creditmemo::STATE_CANCELED], + ]; + } + + public function testExecuteWithWrongOrder() + { + $creditmemoOrderId = 1; + $orderId = 2; + $this->creditmemoMock->expects($this->once()) + ->method('getState') + ->willReturn(Creditmemo::STATE_REFUNDED); + $this->creditmemoMock->expects($this->once()) + ->method('getOrderId') + ->willReturn($creditmemoOrderId); + $this->orderMock->expects($this->once()) + ->method('getEntityId') + ->willReturn($orderId); + $this->orderMock->expects($this->never()) + ->method('setTotalRefunded'); + $this->assertEquals( + $this->orderMock, + $this->subject->execute($this->creditmemoMock, $this->orderMock) + ); + } + + /** + * @param array $amounts + * @dataProvider baseAmountsDataProvider + */ + public function testExecuteOffline($amounts) + { + $orderId = 1; + $this->creditmemoMock->expects($this->once()) + ->method('getState') + ->willReturn(Creditmemo::STATE_REFUNDED); + $this->creditmemoMock->expects($this->once()) + ->method('getOrderId') + ->willReturn($orderId); + $this->orderMock->expects($this->once()) + ->method('getEntityId') + ->willReturn($orderId); + + $this->registerItems(); + + $this->priceCurrencyMock->expects($this->any()) + ->method('round') + ->willReturnArgument(0); + + $this->setBaseAmounts($amounts); + $this->orderMock->expects($this->once()) + ->method('setTotalOfflineRefunded') + ->with(2); + $this->orderMock->expects($this->once()) + ->method('getTotalOfflineRefunded') + ->willReturn(0); + $this->orderMock->expects($this->once()) + ->method('setBaseTotalOfflineRefunded') + ->with(1); + $this->orderMock->expects($this->once()) + ->method('getBaseTotalOfflineRefunded') + ->willReturn(0); + $this->orderMock->expects($this->never()) + ->method('setTotalOnlineRefunded'); + + $this->orderMock->expects($this->never()) + ->method('getPayment'); + + $this->eventManagerMock->expects($this->once()) + ->method('dispatch') + ->with( + 'sales_order_creditmemo_refund', + ['creditmemo' => $this->creditmemoMock] + ); + + $this->assertEquals( + $this->orderMock, + $this->subject->execute($this->creditmemoMock, $this->orderMock, false) + ); + } + + /** + * @param array $amounts + * @dataProvider baseAmountsDataProvider + */ + public function testExecuteOnline($amounts) + { + $orderId = 1; + $this->creditmemoMock->expects($this->once()) + ->method('getState') + ->willReturn(Creditmemo::STATE_REFUNDED); + $this->creditmemoMock->expects($this->once()) + ->method('getOrderId') + ->willReturn($orderId); + $this->orderMock->expects($this->once()) + ->method('getEntityId') + ->willReturn($orderId); + + $this->registerItems(); + + $this->priceCurrencyMock->expects($this->any()) + ->method('round') + ->willReturnArgument(0); + + $this->setBaseAmounts($amounts); + $this->orderMock->expects($this->once()) + ->method('setTotalOnlineRefunded') + ->with(2); + $this->orderMock->expects($this->once()) + ->method('getTotalOnlineRefunded') + ->willReturn(0); + $this->orderMock->expects($this->once()) + ->method('setBaseTotalOnlineRefunded') + ->with(1); + $this->orderMock->expects($this->once()) + ->method('getBaseTotalOnlineRefunded') + ->willReturn(0); + $this->orderMock->expects($this->never()) + ->method('setTotalOfflineRefunded'); + + $this->orderMock->expects($this->once()) + ->method('getPayment') + ->willReturn($this->paymentMock); + $this->paymentMock->expects($this->once()) + ->method('refund') + ->with($this->creditmemoMock); + + $this->assertEquals( + $this->orderMock, + $this->subject->execute($this->creditmemoMock, $this->orderMock, true) + ); + } + + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function baseAmountsDataProvider() + { + return [ + [[ + 'setBaseTotalRefunded' => [ + 'result' => 2, + 'order' => ['method' => 'getBaseTotalRefunded', 'amount' => 1], + 'creditmemo' => ['method' => 'getBaseGrandTotal', 'amount' => 1] + ], + 'setTotalRefunded' => [ + 'result' => 4, + 'order' => ['method' => 'getTotalRefunded', 'amount' => 2], + 'creditmemo' => ['method' => 'getGrandTotal', 'amount' => 2] + ], + 'setBaseSubtotalRefunded' => [ + 'result' => 6, + 'order' => ['method' => 'getBaseSubtotalRefunded', 'amount' => 3], + 'creditmemo' => ['method' => 'getBaseSubtotal', 'amount' => 3] + ], + 'setSubtotalRefunded' => [ + 'result' => 6, + 'order' => ['method' => 'getSubtotalRefunded', 'amount' => 3], + 'creditmemo' => ['method' => 'getSubtotal', 'amount' => 3] + ], + 'setBaseTaxRefunded' => [ + 'result' => 8, + 'order' => ['method' => 'getBaseTaxRefunded', 'amount' => 4], + 'creditmemo' => ['method' => 'getBaseTaxAmount', 'amount' => 4] + ], + 'setTaxRefunded' => [ + 'result' => 10, + 'order' => ['method' => 'getTaxRefunded', 'amount' => 5], + 'creditmemo' => ['method' => 'getTaxAmount', 'amount' => 5] + ], + 'setBaseDiscountTaxCompensationRefunded' => [ + 'result' => 12, + 'order' => ['method' => 'getBaseDiscountTaxCompensationRefunded', 'amount' => 6], + 'creditmemo' => ['method' => 'getBaseDiscountTaxCompensationAmount', 'amount' => 6] + ], + 'setDiscountTaxCompensationRefunded' => [ + 'result' => 14, + 'order' => ['method' => 'getDiscountTaxCompensationRefunded', 'amount' => 7], + 'creditmemo' => ['method' => 'getDiscountTaxCompensationAmount', 'amount' => 7] + ], + 'setBaseShippingRefunded' => [ + 'result' => 16, + 'order' => ['method' => 'getBaseShippingRefunded', 'amount' => 8], + 'creditmemo' => ['method' => 'getBaseShippingAmount', 'amount' => 8] + ], + 'setShippingRefunded' => [ + 'result' => 18, + 'order' => ['method' => 'getShippingRefunded', 'amount' => 9], + 'creditmemo' => ['method' => 'getShippingAmount', 'amount' => 9] + ], + 'setBaseShippingTaxRefunded' => [ + 'result' => 20, + 'order' => ['method' => 'getBaseShippingTaxRefunded', 'amount' => 10], + 'creditmemo' => ['method' => 'getBaseShippingTaxAmount', 'amount' => 10] + ], + 'setShippingTaxRefunded' => [ + 'result' => 22, + 'order' => ['method' => 'getShippingTaxRefunded', 'amount' => 11], + 'creditmemo' => ['method' => 'getShippingTaxAmount', 'amount' => 11] + ], + 'setAdjustmentPositive' => [ + 'result' => 24, + 'order' => ['method' => 'getAdjustmentPositive', 'amount' => 12], + 'creditmemo' => ['method' => 'getAdjustmentPositive', 'amount' => 12] + ], + 'setBaseAdjustmentPositive' => [ + 'result' => 26, + 'order' => ['method' => 'getBaseAdjustmentPositive', 'amount' => 13], + 'creditmemo' => ['method' => 'getBaseAdjustmentPositive', 'amount' => 13] + ], + 'setAdjustmentNegative' => [ + 'result' => 28, + 'order' => ['method' => 'getAdjustmentNegative', 'amount' => 14], + 'creditmemo' => ['method' => 'getAdjustmentNegative', 'amount' => 14] + ], + 'setBaseAdjustmentNegative' => [ + 'result' => 30, + 'order' => ['method' => 'getBaseAdjustmentNegative', 'amount' => 15], + 'creditmemo' => ['method' => 'getBaseAdjustmentNegative', 'amount' => 15] + ], + 'setDiscountRefunded' => [ + 'result' => 32, + 'order' => ['method' => 'getDiscountRefunded', 'amount' => 16], + 'creditmemo' => ['method' => 'getDiscountAmount', 'amount' => 16] + ], + 'setBaseDiscountRefunded' => [ + 'result' => 34, + 'order' => ['method' => 'getBaseDiscountRefunded', 'amount' => 17], + 'creditmemo' => ['method' => 'getBaseDiscountAmount', 'amount' => 17] + ], + 'setBaseTotalInvoicedCost' => [ + 'result' => 7, + 'order' => ['method' => 'getBaseTotalInvoicedCost', 'amount' => 18], + 'creditmemo' => ['method' => 'getBaseCost', 'amount' => 11] + ], + ]], + ]; + } + + private function setBaseAmounts($amounts) + { + foreach ($amounts as $amountName => $summands) { + $this->orderMock->expects($this->once()) + ->method($amountName) + ->with($summands['result']); + $this->orderMock->expects($this->once()) + ->method($summands['order']['method']) + ->willReturn($summands['order']['amount']); + $this->creditmemoMock->expects($this->any()) + ->method($summands['creditmemo']['method']) + ->willReturn($summands['creditmemo']['amount']); + } + } + + private function registerItems() + { + $item1 = $this->getCreditmemoItemMock(); + $item1->expects($this->once())->method('isDeleted')->willReturn(true); + $item1->expects($this->never())->method('setCreditMemo'); + + $item2 = $this->getCreditmemoItemMock(); + $item2->expects($this->at(0))->method('isDeleted')->willReturn(false); + $item2->expects($this->once())->method('setCreditMemo')->with($this->creditmemoMock); + $item2->expects($this->once())->method('getQty')->willReturn(0); + $item2->expects($this->at(3))->method('isDeleted')->with(true); + $item2->expects($this->never())->method('register'); + + $item3 = $this->getCreditmemoItemMock(); + $item3->expects($this->once())->method('isDeleted')->willReturn(false); + $item3->expects($this->once())->method('setCreditMemo')->with($this->creditmemoMock); + $item3->expects($this->once())->method('getQty')->willReturn(1); + $item3->expects($this->once())->method('register'); + + $this->creditmemoMock->expects($this->any()) + ->method('getItems') + ->willReturn([$item1, $item2, $item3]); + } + + private function getCreditmemoItemMock() + { + return $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoItemInterface::class) + ->disableOriginalConstructor() + ->setMethods(['isDeleted', 'setCreditMemo', 'getQty', 'register']) + ->getMockForAbstractClass(); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php index 2da2f4bba3f1a..8d4daec7bf8d5 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php @@ -16,10 +16,20 @@ class PaymentAdapterTest extends \PHPUnit_Framework_TestCase private $subject; /** - * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Sales\Api\Data\OrderInterface|\PHPUnit_Framework_MockObject_MockObject */ private $orderMock; + /** + * @var \Magento\Sales\Api\Data\CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + /** + * @var \Magento\Sales\Model\Order\Creditmemo\RefundOperation|\PHPUnit_Framework_MockObject_MockObject + */ + private $refundOperationMock; + /** * @var \Magento\Sales\Api\Data\InvoiceInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -36,6 +46,14 @@ protected function setUp() ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->refundOperationMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Creditmemo\RefundOperation::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -45,10 +63,24 @@ protected function setUp() ->getMock(); $this->subject = new \Magento\Sales\Model\Order\PaymentAdapter( + $this->refundOperationMock, $this->payOperationMock ); } + public function testRefund() + { + $isOnline = true; + $this->refundOperationMock->expects($this->once()) + ->method('execute') + ->with($this->creditmemoMock, $this->orderMock, $isOnline) + ->willReturn($this->orderMock); + $this->assertEquals( + $this->orderMock, + $this->subject->refund($this->creditmemoMock, $this->orderMock, $isOnline) + ); + } + public function testPay() { $isOnline = true; diff --git a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php index daf71daf4a569..73aa1a34c29b0 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php @@ -5,15 +5,11 @@ */ namespace Magento\Sales\Test\Unit\Model\Service; -use Magento\Framework\Pricing\PriceCurrencyInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Sales\Api\Data\CreditmemoInterface; use Magento\Sales\Model\Order; -use Magento\Sales\Model\Order\Creditmemo; -use Magento\Sales\Model\Order\Creditmemo\Item; /** * Class CreditmemoServiceTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CreditmemoServiceTest extends \PHPUnit_Framework_TestCase { @@ -43,7 +39,7 @@ class CreditmemoServiceTest extends \PHPUnit_Framework_TestCase protected $creditmemoNotifierMock; /** - * @var PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject */ private $priceCurrencyMock; @@ -52,13 +48,16 @@ class CreditmemoServiceTest extends \PHPUnit_Framework_TestCase */ protected $creditmemoService; + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + /** * SetUp */ protected function setUp() { - $objectManager = new ObjectManagerHelper($this); - $this->creditmemoRepositoryMock = $this->getMockForAbstractClass( \Magento\Sales\Api\CreditmemoRepositoryInterface::class, ['get'], @@ -92,9 +91,11 @@ protected function setUp() '', false ); - $this->priceCurrencyMock = $this->getMockBuilder(PriceCurrencyInterface::class)->getMockForAbstractClass(); + $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + ->getMockForAbstractClass(); + $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->creditmemoService = $objectManager->getObject( + $this->creditmemoService = $this->objectManagerHelper->getObject( \Magento\Sales\Model\Service\CreditmemoService::class, [ 'creditmemoRepository' => $this->creditmemoRepositoryMock, @@ -198,21 +199,71 @@ public function testNotify() public function testRefund() { - $creditMemoMock = $this->getMockBuilder(Creditmemo::class) - ->setMethods(['getId', 'getOrder', 'getBaseGrandTotal', 'getAllItems', 'setDoTransaction']) + $creditMemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) + ->setMethods(['getId', 'getOrder', 'getInvoice']) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); $creditMemoMock->expects($this->once())->method('getId')->willReturn(null); $orderMock = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); + $creditMemoMock->expects($this->atLeastOnce())->method('getOrder')->willReturn($orderMock); - $itemMock = $this->getMockBuilder( - Item::class - )->disableOriginalConstructor()->getMock(); - $creditMemoMock->expects($this->once())->method('getAllItems')->willReturn([$itemMock]); - $itemMock->expects($this->once()) -> method('setCreditMemo')->with($creditMemoMock); - $itemMock->expects($this->once()) -> method('getQty')->willReturn(1); - $itemMock->expects($this->once()) -> method('register'); - $creditMemoMock->expects($this->once())->method('setDoTransaction')->with(false); + $orderMock->expects($this->once())->method('getBaseTotalRefunded')->willReturn(0); + $orderMock->expects($this->once())->method('getBaseTotalPaid')->willReturn(10); + $creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn(10); + + $this->priceCurrencyMock->expects($this->any()) + ->method('round') + ->willReturnArgument(0); + + // Set payment adapter dependency + $paymentAdapterMock = $this->getMockBuilder(\Magento\Sales\Model\Order\PaymentAdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->creditmemoService, + 'paymentAdapter', + $paymentAdapterMock + ); + + // Set resource dependency + $resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->creditmemoService, + 'resource', + $resourceMock + ); + + // Set order repository dependency + $orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->creditmemoService, + 'orderRepository', + $orderRepositoryMock + ); + + $adapterMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $resourceMock->expects($this->once())->method('getConnection')->with('sales')->willReturn($adapterMock); + $adapterMock->expects($this->once())->method('beginTransaction'); + $paymentAdapterMock->expects($this->once()) + ->method('refund') + ->with($creditMemoMock, $orderMock, false) + ->willReturn($orderMock); + $orderRepositoryMock->expects($this->once()) + ->method('save') + ->with($orderMock); + $creditMemoMock->expects($this->once()) + ->method('getInvoice') + ->willReturn(null); + $adapterMock->expects($this->once())->method('commit'); + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('save'); + $this->assertSame($creditMemoMock, $this->creditmemoService->refund($creditMemoMock, true)); } @@ -225,8 +276,8 @@ public function testRefundExpectsMoneyAvailableToReturn() $baseGrandTotal = 10; $baseTotalRefunded = 9; $baseTotalPaid = 10; - $creditMemoMock = $this->getMockBuilder(CreditmemoInterface::class) - ->setMethods(['getId', 'getOrder', 'getBaseGrandTotal', 'formatBasePrice']) + $creditMemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) + ->setMethods(['getId', 'getOrder', 'formatBasePrice']) ->getMockForAbstractClass(); $creditMemoMock->expects($this->once())->method('getId')->willReturn(null); $orderMock = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); @@ -254,7 +305,7 @@ public function testRefundExpectsMoneyAvailableToReturn() */ public function testRefundDoNotExpectsId() { - $creditMemoMock = $this->getMockBuilder(CreditmemoInterface::class) + $creditMemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) ->setMethods(['getId']) ->getMockForAbstractClass(); $creditMemoMock->expects($this->once())->method('getId')->willReturn(444); diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index bd5fcf871d11b..383650c8688fc 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -430,7 +430,6 @@ Magento\Sales\Model\ResourceModel\Order\Creditmemo\Relation - Magento\Sales\Model\ResourceModel\Order\Creditmemo\Relation\Refund