From 770052a6371bd1109d73f45436af0d80cd03283e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 2 Aug 2018 18:33:44 +0200 Subject: [PATCH 1/3] First checkin, add new column to sales_order table --- Model/PayoneConfig.php | 2 +- Setup/UpgradeData.php | 8 +++++ Setup/UpgradeSchema.php | 79 +++++++++++++++++++++++++++-------------- etc/module.xml | 2 +- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/Model/PayoneConfig.php b/Model/PayoneConfig.php index 421f513e..d5b38827 100644 --- a/Model/PayoneConfig.php +++ b/Model/PayoneConfig.php @@ -32,7 +32,7 @@ abstract class PayoneConfig { /* Module version */ - const MODULE_VERSION = '2.3.1'; + const MODULE_VERSION = '2.3.3'; /* Authorization request types */ const REQUEST_TYPE_PREAUTHORIZATION = 'preauthorization'; diff --git a/Setup/UpgradeData.php b/Setup/UpgradeData.php index 73d0085d..07f0f30b 100644 --- a/Setup/UpgradeData.php +++ b/Setup/UpgradeData.php @@ -177,6 +177,14 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface ['type' => 'varchar', 'length' => 64, 'default' => ''] ); } + if (!$setup->getConnection()->tableColumnExists($setup->getTable('sales_order'), 'payone_cancel_substitute_increment_id')) { + $salesInstaller = $this->salesSetupFactory->create(['resourceName' => 'sales_setup', 'setup' => $setup]); + $salesInstaller->addAttribute( + 'order', + 'payone_cancel_substitute_increment_id', + ['type' => 'varchar', 'length' => 64, 'default' => ''] + ); + } $serializedRows = $this->getSerializedConfigRows($setup); if (!empty($serializedRows) && version_compare($this->shopHelper->getMagentoVersion(), '2.2.0', '>=')) { diff --git a/Setup/UpgradeSchema.php b/Setup/UpgradeSchema.php index f4c0577e..389089f9 100644 --- a/Setup/UpgradeSchema.php +++ b/Setup/UpgradeSchema.php @@ -40,13 +40,13 @@ class UpgradeSchema extends BaseSchema implements UpgradeSchemaInterface { /** - * Upgrade method + * Add new columns * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context * @return void */ - public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + protected function addNewColumns(SchemaSetupInterface $setup, ModuleContextInterface $context) { if (version_compare($context->getVersion(), '1.3.0', '<')) {// pre update version is lower than 1.3.0 $this->addTable($setup, \Payone\Core\Setup\Tables\CheckedAddresses::getData()); @@ -74,9 +74,31 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con ] ); } + } + + /** + * Add new tables + * + * @param SchemaSetupInterface $setup + * @param ModuleContextInterface $context + * @return void + */ + protected function addNewTables(SchemaSetupInterface $setup, ModuleContextInterface $context) + { if (!$setup->getConnection()->isTableExists($setup->getTable(PaymentBan::TABLE_PAYMENT_BAN))) { $this->addTable($setup, PaymentBan::getData()); } + } + + /** + * Modify already existing columns + * + * @param SchemaSetupInterface $setup + * @param ModuleContextInterface $context + * @return void + */ + protected function modifyColumns(SchemaSetupInterface $setup, ModuleContextInterface $context) + { if (version_compare($context->getVersion(), '2.3.0', '<=')) { $setup->getConnection()->modifyColumn( $setup->getTable('payone_protocol_api'), @@ -91,36 +113,41 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con 'portalid', ['type' => Table::TYPE_INTEGER, 'default' => '0'] ); } + } - /* - * add index to payone_protocol_api::txid to speed up transaction status calls - */ + /** + * Add indexes to speed up certain calls + * + * @param SchemaSetupInterface $setup + * @param ModuleContextInterface $context + * @return void + */ + protected function addIndexes(SchemaSetupInterface $setup, ModuleContextInterface $context) + { if (version_compare($context->getVersion(), '2.3.1', '<=')) { - $connection = $setup->getConnection(); - $protocolApiTable = $connection->getTableName(Api::TABLE_PROTOCOL_API); - $indexField = 'txid'; - $connection->addIndex( - $protocolApiTable, - $connection->getIndexName($protocolApiTable, $indexField), - $indexField - ); + $protocolApiTable = $connection->getTableName(Api::TABLE_PROTOCOL_API); + $connection->addIndex($protocolApiTable, $connection->getIndexName($protocolApiTable, 'txid'), 'txid'); $transactionStatusTable = $connection->getTableName(Transactionstatus::TABLE_PROTOCOL_TRANSACTIONSTATUS); - $indexFieldTxid = 'txid'; - $indexFieldCustomerid = 'customerid'; - - $connection->addIndex( - $transactionStatusTable, - $connection->getIndexName($transactionStatusTable, $indexFieldTxid), - $indexFieldTxid - ); - $connection->addIndex( - $transactionStatusTable, - $connection->getIndexName($transactionStatusTable, $indexFieldCustomerid), - $indexFieldCustomerid - ); + $connection->addIndex($transactionStatusTable, $connection->getIndexName($transactionStatusTable, 'txid'), 'txid'); + $connection->addIndex($transactionStatusTable, $connection->getIndexName($transactionStatusTable, 'customerid'), 'customerid'); } } + + /** + * Upgrade method + * + * @param SchemaSetupInterface $setup + * @param ModuleContextInterface $context + * @return void + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $this->addNewColumns($setup, $context); + $this->addNewTables($setup, $context); + $this->modifyColumns($setup, $context); + $this->addIndexes($setup, $context); + } } diff --git a/etc/module.xml b/etc/module.xml index df94b130..2d60e5e8 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -25,7 +25,7 @@ */ --> - + From 1a743aa8ef4eaa62c656a0e7262a28e6c74ce2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 16 Aug 2018 13:47:44 +0200 Subject: [PATCH 2/3] MAG2-77 - Solution for back-button canceled orders --- Controller/Onepage/Cancel.php | 13 +- Controller/Onepage/PlaceOrder.php | 8 ++ Controller/Onepage/Returned.php | 113 +++++++++++++++++- Controller/Paypal/Express.php | 1 + Helper/Database.php | 35 ++++++ Model/Api/Invoice.php | 2 +- .../Handler/Cancellation.php | 48 +++++--- Model/Methods/BaseMethod.php | 4 +- Observer/CancelOrder.php | 64 ++++++++++ Test/Unit/Controller/Onepage/CancelTest.php | 3 +- .../Controller/Onepage/PlaceOrderTest.php | 27 ++++- Test/Unit/Controller/Onepage/ReturnedTest.php | 70 ++++++++++- Test/Unit/Helper/DatabaseTest.php | 36 +++++- .../Handler/CancellationTest.php} | 94 ++++++++------- Test/Unit/Observer/CancelOrderTest.php | 69 +++++++++++ etc/events.xml | 5 +- 16 files changed, 506 insertions(+), 86 deletions(-) rename Observer/PredispatchCheckoutIndex.php => Model/Handler/Cancellation.php (67%) create mode 100644 Observer/CancelOrder.php rename Test/Unit/{Observer/PredispatchCheckoutIndexTest.php => Model/Handler/CancellationTest.php} (51%) create mode 100644 Test/Unit/Observer/CancelOrderTest.php diff --git a/Controller/Onepage/Cancel.php b/Controller/Onepage/Cancel.php index baa7cad9..392e251e 100644 --- a/Controller/Onepage/Cancel.php +++ b/Controller/Onepage/Cancel.php @@ -99,21 +99,12 @@ public function execute() $orderId = $this->checkoutSession->getLastOrderId(); $order = $orderId ? $this->orderFactory->create()->load($orderId) : false; if ($order) { - $sQuoteId = $order->getQuoteId(); - $order->cancel()->save(); + $this->checkoutSession->restoreQuote(); $this->checkoutSession ->unsLastQuoteId() ->unsLastSuccessQuoteId() - ->unsLastOrderId() - ->unsLastRealOrderId(); - - // Use the old quote/basket again - $this->checkoutSession->setQuoteId($sQuoteId); - $this->checkoutSession->setLoadInactive(); - $oQuote = $this->checkoutSession->getQuote(); - $oQuote->setIsActive(1)->setReservedOrderId(null)->save(); - $this->checkoutSession->replaceQuote($oQuote); + ->unsLastOrderId(); } } catch (LocalizedException $e) { $this->messageManager->addExceptionMessage($e, $e->getMessage()); diff --git a/Controller/Onepage/PlaceOrder.php b/Controller/Onepage/PlaceOrder.php index d3cb6e78..059342f8 100644 --- a/Controller/Onepage/PlaceOrder.php +++ b/Controller/Onepage/PlaceOrder.php @@ -104,6 +104,14 @@ public function execute() protected function placeOrder() { $oQuote = $this->checkoutSession->getQuote(); + + if ($oQuote->getSubtotal() != $this->checkoutSession->getPayoneGenericpaymentSubtotal()) { + // The basket was changed - abort current checkout + $this->messageManager->addErrorMessage('An error occured during the Checkout.'); + $this->_redirect('checkout/cart'); + return; + } + $oQuote->getBillingAddress()->setShouldIgnoreValidation(true); if (!$oQuote->getIsVirtual()) { $oQuote->getShippingAddress()->setShouldIgnoreValidation(true); diff --git a/Controller/Onepage/Returned.php b/Controller/Onepage/Returned.php index eacf0e7d..5f90d629 100644 --- a/Controller/Onepage/Returned.php +++ b/Controller/Onepage/Returned.php @@ -26,6 +26,8 @@ namespace Payone\Core\Controller\Onepage; +use Magento\Sales\Model\Order; + /** * Controller for handling return from payment provider */ @@ -38,18 +40,118 @@ class Returned extends \Magento\Framework\App\Action\Action */ protected $checkoutSession; + /** + * Quote management object + * + * @var \Magento\Quote\Model\QuoteManagement + */ + protected $quoteManagement; + + /** + * Order repository + * + * @var \Magento\Sales\Api\OrderRepositoryInterface + */ + protected $orderRepository; + + /** + * Order repository + * + * @var \Magento\Quote\Api\CartRepositoryInterface + */ + protected $quoteRepository; + + /** + * PAYONE database helper + * + * @var \Payone\Core\Helper\Database + */ + protected $databaseHelper; + /** * Constructor * - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Framework\App\Action\Context $context + * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Quote\Model\QuoteManagement $quoteManagement + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository + * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository + * @param \Payone\Core\Helper\Database $databaseHelper */ public function __construct( \Magento\Framework\App\Action\Context $context, - \Magento\Checkout\Model\Session $checkoutSession + \Magento\Checkout\Model\Session $checkoutSession, + \Magento\Quote\Model\QuoteManagement $quoteManagement, + \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, + \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, + \Payone\Core\Helper\Database $databaseHelper ) { parent::__construct($context); $this->checkoutSession = $checkoutSession; + $this->quoteManagement = $quoteManagement; + $this->orderRepository = $orderRepository; + $this->databaseHelper = $databaseHelper; + $this->quoteRepository = $quoteRepository; + } + + /** + * Order was canceled before because of multi-tab browsing or back-button cancelling + * Create a clean order for the payment + * + * @param Order $canceledOrder + * @return void + */ + protected function createSubstituteOrder(Order $canceledOrder) + { + $this->checkoutSession->setPayoneCreatingSubstituteOrder(true); + + $oOldQuote = $this->quoteRepository->get($canceledOrder->getQuoteId()); + $oOldQuote->setIsActive(true); + $oOldQuote->setReservedOrderId(null); + $oOldQuote->save(); + + $orderId = $this->quoteManagement->placeOrder($oOldQuote->getId()); + $newOrder = $this->orderRepository->get($orderId); + + $oldData = $canceledOrder->getData(); + foreach ($oldData as $sKey => $sValue) { + if (stripos($sKey, 'payone') !== false) { + $newOrder->setData($sKey, $sValue); + } + } + + $newOrder->setPayoneCancelSubstituteIncrementId($canceledOrder->getIncrementId()); + $newOrder->save(); + + $this->checkoutSession->setLastOrderId($newOrder->getId()); + $this->checkoutSession->setLastRealOrderId($newOrder->getIncrementId()); + $this->checkoutSession->getQuote()->setIsActive(false)->save(); + + $this->databaseHelper->relabelTransaction($canceledOrder->getId(), $newOrder->getId(), $newOrder->getPayment()->getId()); + $this->databaseHelper->relabelApiProtocol($canceledOrder->getIncrementId(), $newOrder->getIncrementId()); + $this->databaseHelper->relabelOrderPayment($canceledOrder->getIncrementId(), $newOrder->getId()); + + $this->checkoutSession->unsPayoneCreatingSubstituteOrder(); + } + + /** + * Get canceled order. + * Return order if found. + * Return false if not found or not canceled + * + * @return bool|Order + */ + protected function getCanceledOrder() + { + $order = $this->checkoutSession->getLastRealOrder(); + if (!$order->getId() && !empty($this->checkoutSession->getPayoneCanceledOrder())) { + $order->loadByIncrementId($this->checkoutSession->getPayoneCanceledOrder()); + } + $this->checkoutSession->unsPayoneCanceledOrder(); + if ($order->getStatus() == Order::STATE_CANCELED) { + return $order; + } + return false; } /** @@ -62,6 +164,11 @@ public function execute() { $this->checkoutSession->unsPayoneCustomerIsRedirected(); + $canceledOrder = $this->getCanceledOrder(); + if ($canceledOrder !== false) { + $this->createSubstituteOrder($canceledOrder); + } + $this->_redirect($this->_url->getUrl('checkout/onepage/success')); } } diff --git a/Controller/Paypal/Express.php b/Controller/Paypal/Express.php index 70063d88..a381e24f 100644 --- a/Controller/Paypal/Express.php +++ b/Controller/Paypal/Express.php @@ -158,6 +158,7 @@ public function execute() $oQuote->save(); $this->checkoutSession->setPayoneWorkorderId($aResponse['workorderid']); + $this->checkoutSession->setPayoneGenericpaymentSubtotal($oQuote->getSubtotal()); $this->_redirect($aResponse['redirecturl']); } return; diff --git a/Helper/Database.php b/Helper/Database.php index 26e2a3bd..88d38b43 100644 --- a/Helper/Database.php +++ b/Helper/Database.php @@ -244,4 +244,39 @@ public function getOldAddressStatus(AddressInterface $oAddress, $blIsCreditratin } return $this->getDb()->fetchOne($oSelect, $aParams); } + + public function relabelTransaction($sOldOrderId, $sNewOrderId, $sNewPaymentId) + { + $table = $this->databaseResource->getTableName('sales_payment_transaction'); + $data = [ + 'order_id' => $sNewOrderId, + 'payment_id' => $sNewPaymentId + ]; + $where = ['order_id = ?' => $sOldOrderId]; + return $this->getDb()->update($table, $data, $where); + } + + public function relabelApiProtocol($sOldIncrementId, $sNewIncrementId) + { + $table = $this->databaseResource->getTableName('payone_protocol_api'); + $data = ['order_id' => $sNewIncrementId]; + $where = ['order_id = ?' => $sOldIncrementId]; + return $this->getDb()->update($table, $data, $where); + } + + public function relabelOrderPayment($sOldIncrementId, $sNewOrderId) + { + $oSelect = $this->getDb() + ->select() + ->from(['a' => $this->databaseResource->getTableName('sales_order_payment')], ['last_trans_id']) + ->joinInner(['b' => $this->databaseResource->getTableName('sales_order')], 'a.parent_id = b.entity_id') + ->where("b.increment_id = :incrementId") + ->limit(1); + $sLastTransId = $this->getDb()->fetchOne($oSelect, ['incrementId' => $sOldIncrementId]); + + $table = $this->databaseResource->getTableName('sales_order_payment'); + $data = ['last_trans_id' => $sLastTransId]; + $where = ['parent_id = ?' => $sNewOrderId]; + return $this->getDb()->update($table, $data, $where); + } } diff --git a/Model/Api/Invoice.php b/Model/Api/Invoice.php index 7c45f507..16fea29c 100644 --- a/Model/Api/Invoice.php +++ b/Model/Api/Invoice.php @@ -216,7 +216,7 @@ protected function addDiscountItem(Order $oOrder, $aPositions, $blDebit) if ($this->toolkitHelper->getConfigParam('currency') == 'display') { $dTransmitDiscount = $oOrder->getDiscountAmount(); } - if ($dTransmitDiscount != 0 && $oOrder->getCouponCode() && ($aPositions === false || ($blDebit === false || array_key_exists('oxvoucherdiscount', $aPositions) !== false))) { + if ($dTransmitDiscount != 0 && ($aPositions === false || ($blDebit === false || array_key_exists('oxvoucherdiscount', $aPositions) !== false))) { $dDiscount = $this->toolkitHelper->formatNumber($dTransmitDiscount); // format discount if ($aPositions === false) {// full invoice? // The calculations broken down to single items of Magento2 are unprecise and the Payone API will send an error if diff --git a/Observer/PredispatchCheckoutIndex.php b/Model/Handler/Cancellation.php similarity index 67% rename from Observer/PredispatchCheckoutIndex.php rename to Model/Handler/Cancellation.php index 101170e2..bfc1f364 100644 --- a/Observer/PredispatchCheckoutIndex.php +++ b/Model/Handler/Cancellation.php @@ -19,24 +19,20 @@ * @category Payone * @package Payone_Magento2_Plugin * @author FATCHIP GmbH - * @copyright 2003 - 2016 Payone GmbH + * @copyright 2003 - 2018 Payone GmbH * @license GNU Lesser General Public License * @link http://www.payone.de */ -namespace Payone\Core\Observer; +namespace Payone\Core\Model\Handler; -use Magento\Framework\Event\ObserverInterface; -use Magento\Framework\Event\Observer; use Magento\Checkout\Model\Session; +use Magento\Sales\Model\Order; use Magento\Sales\Model\OrderFactory; use Magento\Framework\Exception\LocalizedException; +use \Magento\Quote\Api\CartRepositoryInterface as QuoteRepo; -/** - * Event class to prevent the basket from getting lost with redirect payment types - * when the customer uses the browser back-button - */ -class PredispatchCheckoutIndex implements ObserverInterface +class Cancellation { /** * Checkout session @@ -52,38 +48,58 @@ class PredispatchCheckoutIndex implements ObserverInterface */ protected $orderFactory; + /** + * Order repository + * + * @var QuoteRepo + */ + protected $quoteRepository; + /** * Constructor * * @param Session $checkoutSession * @param OrderFactory $orderFactory + * @param QuoteRepo $quoteRepository */ - public function __construct(Session $checkoutSession, OrderFactory $orderFactory) + public function __construct(Session $checkoutSession, OrderFactory $orderFactory, QuoteRepo $quoteRepository) { $this->checkoutSession = $checkoutSession; $this->orderFactory = $orderFactory; + $this->quoteRepository = $quoteRepository; } /** - * @param Observer $observer - * @return $this + * @return void */ - public function execute(Observer $observer) + public function handle() { if ($this->checkoutSession->getPayoneCustomerIsRedirected()) { try { $orderId = $this->checkoutSession->getLastOrderId(); $order = $orderId ? $this->orderFactory->create()->load($orderId) : false; if ($order) { - $order->cancel()->save(); + $order->cancel(); + $order->addStatusHistoryComment(__('The Payone transaction has been canceled.'), Order::STATE_CANCELED); + $order->save(); - $this->checkoutSession->restoreQuote(); + $oCurrentQuote = $this->checkoutSession->getQuote(); + + $quoteId = $this->checkoutSession->getLastQuoteId(); + $oOldQuote = $this->quoteRepository->get($quoteId); + if ($oOldQuote && $oOldQuote->getId()) { + $oCurrentQuote->merge($oOldQuote); + $oCurrentQuote->collectTotals(); + $oCurrentQuote->save(); + } $this->checkoutSession ->unsLastQuoteId() ->unsLastSuccessQuoteId() ->unsLastOrderId() ->unsLastRealOrderId(); + + $this->checkoutSession->setPayoneCanceledOrder($order->getIncrementId()); } } catch (LocalizedException $e) { // catch and continue - do something when needed @@ -95,4 +111,4 @@ public function execute(Observer $observer) $this->checkoutSession->setIsPayoneRedirectCancellation(true); } } -} +} \ No newline at end of file diff --git a/Model/Methods/BaseMethod.php b/Model/Methods/BaseMethod.php index 20b5f2b1..19d6588a 100644 --- a/Model/Methods/BaseMethod.php +++ b/Model/Methods/BaseMethod.php @@ -279,7 +279,9 @@ public function getConfigPaymentAction() public function authorize(InfoInterface $payment, $amount) { $oReturn = parent::authorize($payment, $amount); // execute Magento parent authorization - $this->sendPayoneAuthorization($payment, $amount); // send auth request to PAYONE + if (!$this->checkoutSession->getPayoneCreatingSubstituteOrder()) { + $this->sendPayoneAuthorization($payment, $amount); // send auth request to PAYONE + } return $oReturn; // return magento parent auth value } diff --git a/Observer/CancelOrder.php b/Observer/CancelOrder.php new file mode 100644 index 00000000..0d7c8998 --- /dev/null +++ b/Observer/CancelOrder.php @@ -0,0 +1,64 @@ +. + * + * PHP version 5 + * + * @category Payone + * @package Payone_Magento2_Plugin + * @author FATCHIP GmbH + * @copyright 2003 - 2016 Payone GmbH + * @license GNU Lesser General Public License + * @link http://www.payone.de + */ + +namespace Payone\Core\Observer; + +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Event\Observer; +use Payone\Core\Model\Handler\Cancellation; + +/** + * Event class to prevent the basket from getting lost with redirect payment types + * when the customer uses the browser back-button + */ +class CancelOrder implements ObserverInterface +{ + /** + * Checkout session + * + * @var Cancellation + */ + protected $cancellation; + + /** + * Constructor + * + * @param Cancellation $cancellation + */ + public function __construct(Cancellation $cancellation) + { + $this->cancellation = $cancellation; + } + + /** + * @param Observer $observer + * @return $this + */ + public function execute(Observer $observer) + { + $this->cancellation->handle(); + } +} diff --git a/Test/Unit/Controller/Onepage/CancelTest.php b/Test/Unit/Controller/Onepage/CancelTest.php index ee394146..3dfaa067 100644 --- a/Test/Unit/Controller/Onepage/CancelTest.php +++ b/Test/Unit/Controller/Onepage/CancelTest.php @@ -107,7 +107,8 @@ protected function setUp() 'getPayoneRedirectedPaymentMethod', 'setPayoneCanceledPaymentMethod', 'setPayoneIsError', - 'unsPayoneWorkorderId' + 'unsPayoneWorkorderId', + 'restoreQuote' ]) ->getMock(); $this->checkoutSession->method('getLastOrderId')->willReturn('12345'); diff --git a/Test/Unit/Controller/Onepage/PlaceOrderTest.php b/Test/Unit/Controller/Onepage/PlaceOrderTest.php index 4d37c04a..0c50c280 100644 --- a/Test/Unit/Controller/Onepage/PlaceOrderTest.php +++ b/Test/Unit/Controller/Onepage/PlaceOrderTest.php @@ -103,16 +103,28 @@ protected function setUp() $address = $this->getMockBuilder(Address::class)->disableOriginalConstructor()->getMock(); - $quote = $this->getMockBuilder(Quote::class)->disableOriginalConstructor()->getMock(); + $quote = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getBillingAddress', + 'getShippingAddress', + 'getIsVirtual', + 'getId', + 'setIsActive', + 'getSubtotal', + 'save' + ]) + ->getMock(); $quote->method('getBillingAddress')->willReturn($address); $quote->method('getShippingAddress')->willReturn($address); $quote->method('getIsVirtual')->willReturn(false); $quote->method('getId')->willReturn('12345'); $quote->method('setIsActive')->willReturn($quote); + $quote->method('getSubtotal')->willReturn(100); $this->checkoutSession = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() - ->setMethods(['getQuote', 'setLastQuoteId', 'setLastSuccessQuoteId', 'unsPayoneWorkorderId']) + ->setMethods(['getQuote', 'setLastQuoteId', 'setLastSuccessQuoteId', 'unsPayoneWorkorderId', 'getPayoneGenericpaymentSubtotal']) ->getMock(); $this->checkoutSession->method('getQuote')->willReturn($quote); $this->checkoutSession->method('setLastQuoteId')->willReturn($this->checkoutSession); @@ -133,6 +145,7 @@ protected function setUp() public function testExecute() { + $this->checkoutSession->method('getPayoneGenericpaymentSubtotal')->willReturn(100); $this->request->method('getBeforeForwardInfo')->willReturn(false); $result = $this->classToTest->execute(); $this->assertNull($result); @@ -147,6 +160,8 @@ public function testExecuteValidation() public function testExecuteException() { + $this->checkoutSession->method('getPayoneGenericpaymentSubtotal')->willReturn(100); + $exception = new \Exception(); $this->cartManagement->method('placeOrder')->willThrowException($exception); @@ -154,4 +169,12 @@ public function testExecuteException() $result = $this->classToTest->execute(); $this->assertNull($result); } + + public function testExecuteSubtotalMismatch() + { + $this->checkoutSession->method('getPayoneGenericpaymentSubtotal')->willReturn(110); + $this->request->method('getBeforeForwardInfo')->willReturn(false); + $result = $this->classToTest->execute(); + $this->assertNull($result); + } } diff --git a/Test/Unit/Controller/Onepage/ReturnedTest.php b/Test/Unit/Controller/Onepage/ReturnedTest.php index 3b48b871..3b83dde7 100644 --- a/Test/Unit/Controller/Onepage/ReturnedTest.php +++ b/Test/Unit/Controller/Onepage/ReturnedTest.php @@ -26,19 +26,21 @@ namespace Payone\Core\Test\Unit\Controller\Onepage; +use Magento\Quote\Model\Quote; use Payone\Core\Controller\Onepage\Returned as ClassToTest; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Sales\Model\Order as OrderCore; use Magento\Checkout\Model\Session; use Magento\Framework\App\Action\Context; -use Magento\Customer\Model\Session as CustomerSession; use Magento\Store\App\Response\Redirect as RedirectResponse; use Magento\Framework\App\Console\Response; use Magento\Framework\App\ResponseInterface; use Magento\Framework\UrlInterface; use Payone\Core\Test\Unit\BaseTestCase; use Payone\Core\Test\Unit\PayoneObjectManager; - +use Magento\Quote\Model\QuoteRepository; +use Magento\Sales\Model\OrderRepository; +use Magento\Sales\Model\Order\Payment; class ReturnedTest extends BaseTestCase { @@ -52,6 +54,11 @@ class ReturnedTest extends BaseTestCase */ private $objectManager; + /** + * @var Session|\PHPUnit_Framework_MockObject_MockObject + */ + private $checkoutSession; + protected function setUp() { $this->objectManager = $this->getObjectManager(); @@ -70,16 +77,71 @@ protected function setUp() $context->method('getResponse')->willReturn($response); $context->method('getUrl')->willReturn($url); - $checkoutSession = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock(); + $this->checkoutSession = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getLastRealOrder', + 'setLastRealOrderId', + 'getPayoneCanceledOrder', + 'unsPayoneCanceledOrder', + 'unsPayoneCustomerIsRedirected', + 'setPayoneCreatingSubstituteOrder', + 'unsPayoneCreatingSubstituteOrder', + 'setLastOrderId', + 'getQuote' + ]) + ->getMock(); + + $quote = $this->getMockBuilder(Quote::class)->disableOriginalConstructor()->getMock(); + + $quoteRepository = $this->getMockBuilder(QuoteRepository::class)->disableOriginalConstructor()->getMock(); + $quoteRepository->method('get')->willReturn($quote); + + $payment = $this->getMockBuilder(Payment::class)->disableOriginalConstructor()->getMock(); + $payment->method('getId')->willReturn(123); + + $order = $this->getMockBuilder(OrderCore::class)->disableOriginalConstructor()->getMock(); + $order->method('getPayment')->willReturn($payment); + + $orderRepository = $this->getMockBuilder(OrderRepository::class)->disableOriginalConstructor()->getMock(); + $orderRepository->method('get')->willReturn($order); $this->classToTest = $this->objectManager->getObject(ClassToTest::class, [ 'context' => $context, - 'checkoutSession' => $checkoutSession + 'checkoutSession' => $this->checkoutSession, + 'quoteRepository' => $quoteRepository, + 'orderRepository' => $orderRepository ]); } public function testExecute() { + $order = $this->getMockBuilder(OrderCore::class)->disableOriginalConstructor()->getMock(); + $order->method('getId')->willReturn(null); + $order->method('getStatus')->willReturn(OrderCore::STATE_COMPLETE); + + $this->checkoutSession->method('getLastRealOrder')->willReturn($order); + $this->checkoutSession->method('getPayoneCanceledOrder')->willReturn(null); + + $result = $this->classToTest->execute(); + $this->assertNull($result); + } + + public function testExecuteSubstitute() + { + $order = $this->getMockBuilder(OrderCore::class)->disableOriginalConstructor()->getMock(); + $order->method('getId')->willReturn(null); + $order->method('getStatus')->willReturn(OrderCore::STATE_CANCELED); + $order->method('getQuoteId')->willReturn('123'); + $order->method('getData')->willReturn(['payone_txid' => '12345']); + + $quote = $this->getMockBuilder(Quote::class)->disableOriginalConstructor()->getMock(); + $quote->method('setIsActive')->willReturn($quote); + + $this->checkoutSession->method('getLastRealOrder')->willReturn($order); + $this->checkoutSession->method('getPayoneCanceledOrder')->willReturn(123); + $this->checkoutSession->method('getQuote')->willReturn($quote); + $result = $this->classToTest->execute(); $this->assertNull($result); } diff --git a/Test/Unit/Helper/DatabaseTest.php b/Test/Unit/Helper/DatabaseTest.php index b09b98d8..1fc9148a 100644 --- a/Test/Unit/Helper/DatabaseTest.php +++ b/Test/Unit/Helper/DatabaseTest.php @@ -80,13 +80,14 @@ protected function setUp() $this->connection = $this->getMockBuilder(Select::class) ->disableOriginalConstructor() - ->setMethods(['fetchOne', 'fetchAll', 'select', 'from', 'where', 'limit', 'order']) + ->setMethods(['fetchOne', 'fetchAll', 'select', 'from', 'where', 'limit', 'order', 'update', 'joinInner']) ->getMock(); $this->connection->method('select')->willReturn($this->connection); $this->connection->method('from')->willReturn($this->connection); $this->connection->method('where')->willReturn($this->connection); $this->connection->method('limit')->willReturn($this->connection); $this->connection->method('order')->willReturn($this->connection); + $this->connection->method('joinInner')->willReturn($this->connection); $this->databaseResource = $this->getMockBuilder(ResourceConnection::class)->disableOriginalConstructor()->getMock(); $this->databaseResource->method('getConnection')->willReturn($this->connection); @@ -217,4 +218,37 @@ public function testGetOldAddressStatus() $result = $this->database->getOldAddressStatus($address, false); $this->assertEquals($expected, $result); } + + public function testRelabelTransaction() + { + $expected = '1'; + + $this->databaseResource->method('getTableName')->willReturn('sales_payment_transaction'); + $this->connection->method('update')->willReturn($expected); + + $result = $this->database->relabelTransaction('1', '2', '3'); + $this->assertEquals($expected, $result); + } + + public function testRelabelApiProtocol() + { + $expected = '1'; + + $this->databaseResource->method('getTableName')->willReturn('payone_protocol_api'); + $this->connection->method('update')->willReturn($expected); + + $result = $this->database->relabelApiProtocol('1', '2'); + $this->assertEquals($expected, $result); + } + + public function testRelabelOrderPayment() + { + $expected = '1'; + + $this->databaseResource->method('getTableName')->willReturn('sales_order_payment'); + $this->connection->method('update')->willReturn($expected); + + $result = $this->database->relabelOrderPayment('1', '2'); + $this->assertEquals($expected, $result); + } } diff --git a/Test/Unit/Observer/PredispatchCheckoutIndexTest.php b/Test/Unit/Model/Handler/CancellationTest.php similarity index 51% rename from Test/Unit/Observer/PredispatchCheckoutIndexTest.php rename to Test/Unit/Model/Handler/CancellationTest.php index eeeb0291..3c4ae49d 100644 --- a/Test/Unit/Observer/PredispatchCheckoutIndexTest.php +++ b/Test/Unit/Model/Handler/CancellationTest.php @@ -24,19 +24,20 @@ * @link http://www.payone.de */ -namespace Payone\Core\Test\Unit\Observer; +namespace Payone\Core\Test\Unit\Model\Handler; -use Payone\Core\Observer\PredispatchCheckoutIndex as ClassToTest; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Framework\Event\Observer; -use Magento\Checkout\Model\Session; -use Magento\Sales\Model\OrderFactory; +use Magento\Quote\Model\Quote; use Magento\Sales\Model\Order; -use Magento\Framework\Exception\NoSuchEntityException; +use Payone\Core\Model\Handler\Cancellation as ClassToTest; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Payone\Core\Test\Unit\BaseTestCase; use Payone\Core\Test\Unit\PayoneObjectManager; +use Magento\Checkout\Model\Session; +use Magento\Sales\Model\OrderFactory; +use Magento\Quote\Model\QuoteRepository; +use Magento\Framework\Exception\LocalizedException; -class PredispatchCheckoutIndexTest extends BaseTestCase +class CancellationTest extends BaseTestCase { /** * @var ClassToTest @@ -49,77 +50,80 @@ class PredispatchCheckoutIndexTest extends BaseTestCase private $objectManager; /** - * @var Session|\PHPUnit_Framework_MockObject_MockObject + * @var Order|\PHPUnit_Framework_MockObject_MockObject */ - private $checkoutSession; + private $order; protected function setUp() { $this->objectManager = $this->getObjectManager(); - $this->checkoutSession = $this->getMockBuilder(Session::class) + $quote = $this->getMockBuilder(Quote::class)->disableOriginalConstructor()->getMock(); + $quote->method('getId')->willReturn(123); + + $checkoutSession = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() ->setMethods([ 'getPayoneCustomerIsRedirected', + 'unsPayoneCustomerIsRedirected', + 'getQuote', 'getLastOrderId', - 'restoreQuote', + 'unsLastOrderId', + 'getLastQuoteId', 'unsLastQuoteId', 'unsLastSuccessQuoteId', - 'unsLastOrderId', 'unsLastRealOrderId', - 'unsPayoneCustomerIsRedirected', - 'setIsPayoneRedirectCancellation' + 'setPayoneCanceledOrder', + 'setIsPayoneRedirectCancellation', ]) ->getMock(); - $this->checkoutSession->method('getPayoneCustomerIsRedirected')->willReturn(true); - $this->checkoutSession->method('getLastOrderId')->willReturn('123'); - $this->checkoutSession->method('unsLastQuoteId')->willReturn($this->checkoutSession); - $this->checkoutSession->method('unsLastSuccessQuoteId')->willReturn($this->checkoutSession); - $this->checkoutSession->method('unsLastOrderId')->willReturn($this->checkoutSession); + $checkoutSession->method('getPayoneCustomerIsRedirected')->willReturn(true); + $checkoutSession->method('getLastOrderId')->willReturn(123); + $checkoutSession->method('getQuote')->willReturn($quote); + $checkoutSession->method('getLastQuoteId')->willReturn(123); + $checkoutSession->method('unsLastQuoteId')->willReturn($checkoutSession); + $checkoutSession->method('unsLastSuccessQuoteId')->willReturn($checkoutSession); + $checkoutSession->method('unsLastOrderId')->willReturn($checkoutSession); + $checkoutSession->method('unsLastRealOrderId')->willReturn($checkoutSession); - $order = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); - $order->method('load')->willReturn($order); - $order->method('cancel')->willReturn($order); + $this->order = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); + $this->order->method('load')->willReturn($this->order); + $this->order->method('getIncrementId')->willReturn(123); - $orderFactory = $this->getMockBuilder(OrderFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $orderFactory->method('create')->willReturn($order); + $orderFactory = $this->getMockBuilder(OrderFactory::class)->disableOriginalConstructor()->getMock(); + $orderFactory->method('create')->willReturn($this->order); + + $quoteRepository = $this->getMockBuilder(QuoteRepository::class)->disableOriginalConstructor()->getMock(); + $quoteRepository->method('get')->willReturn($quote); $this->classToTest = $this->objectManager->getObject(ClassToTest::class, [ - 'checkoutSession' => $this->checkoutSession, + 'checkoutSession' => $checkoutSession, 'orderFactory' => $orderFactory, + 'quoteRepository' => $quoteRepository ]); } - public function testExecute() + public function testHandle() { - $observer = $this->getMockBuilder(Observer::class)->disableOriginalConstructor()->getMock(); - - $result = $this->classToTest->execute($observer); + $result = $this->classToTest->handle(); $this->assertNull($result); } - public function testExecuteLocalizedException() + public function testExecuteException() { - $exception = $this->objectManager->getObject(NoSuchEntityException::class); - $this->checkoutSession->expects($this->once())->method('restoreQuote')->willThrowException($exception); - - $observer = $this->getMockBuilder(Observer::class)->disableOriginalConstructor()->getMock(); + $exception = new \Exception(); + $this->order->method('cancel')->willThrowException($exception); - $result = $this->classToTest->execute($observer); + $result = $this->classToTest->handle(); $this->assertNull($result); } - public function testExecuteException() + public function testExecuteLocalizedException() { - $exception = $this->objectManager->getObject(\Exception::class); - $this->checkoutSession->expects($this->once())->method('restoreQuote')->willThrowException($exception); - - $observer = $this->getMockBuilder(Observer::class)->disableOriginalConstructor()->getMock(); + $exception = new LocalizedException(__('An error occured')); + $this->order->method('cancel')->willThrowException($exception); - $result = $this->classToTest->execute($observer); + $result = $this->classToTest->handle(); $this->assertNull($result); } } diff --git a/Test/Unit/Observer/CancelOrderTest.php b/Test/Unit/Observer/CancelOrderTest.php new file mode 100644 index 00000000..322bec64 --- /dev/null +++ b/Test/Unit/Observer/CancelOrderTest.php @@ -0,0 +1,69 @@ +. + * + * PHP version 5 + * + * @category Payone + * @package Payone_Magento2_Plugin + * @author FATCHIP GmbH + * @copyright 2003 - 2017 Payone GmbH + * @license GNU Lesser General Public License + * @link http://www.payone.de + */ + +namespace Payone\Core\Test\Unit\Observer; + +use Payone\Core\Model\Handler\Cancellation; +use Payone\Core\Observer\CancelOrder as ClassToTest; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Event\Observer; +use Payone\Core\Test\Unit\BaseTestCase; +use Payone\Core\Test\Unit\PayoneObjectManager; + + +class CancelOrderTest extends BaseTestCase +{ + /** + * @var ClassToTest + */ + private $classToTest; + + /** + * @var ObjectManager|PayoneObjectManager + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = $this->getObjectManager(); + + $cancellation = $this->getMockBuilder(Cancellation::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->classToTest = $this->objectManager->getObject(ClassToTest::class, [ + 'cancellation' => $cancellation + ]); + } + + public function testExecute() + { + $observer = $this->getMockBuilder(Observer::class)->disableOriginalConstructor()->getMock(); + + $result = $this->classToTest->execute($observer); + $this->assertNull($result); + } +} diff --git a/etc/events.xml b/etc/events.xml index cb9476bf..1751e9fb 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -35,7 +35,10 @@ - + + + + From 6c39e672ed56d23d74cee38d2ebd710aaf900310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 14 Nov 2018 13:00:35 +0100 Subject: [PATCH 3/3] MAG2-77 - Fixed problem with the TransactionStatus changing the canceled order before the substitute can be created --- Controller/Onepage/Returned.php | 57 ++++++- Controller/Transactionstatus/Index.php | 97 ++++-------- Helper/Database.php | 39 +++++ Model/Handler/TransactionStatus.php | 148 ++++++++++++++++++ Model/PayoneConfig.php | 2 +- Model/ResourceModel/TransactionStatus.php | 47 ++++-- Setup/UpgradeSchema.php | 13 ++ Test/Unit/Controller/Onepage/ReturnedTest.php | 18 ++- .../Transactionstatus/IndexTest.php | 35 ++++- Test/Unit/Helper/DatabaseTest.php | 13 ++ .../Model/Handler/TransactionStatusTest.php | 73 +++++++++ .../ResourceModel/TransactionStatusTest.php | 17 +- etc/module.xml | 2 +- 13 files changed, 454 insertions(+), 107 deletions(-) create mode 100644 Model/Handler/TransactionStatus.php create mode 100644 Test/Unit/Model/Handler/TransactionStatusTest.php diff --git a/Controller/Onepage/Returned.php b/Controller/Onepage/Returned.php index 5f90d629..2f7a8597 100644 --- a/Controller/Onepage/Returned.php +++ b/Controller/Onepage/Returned.php @@ -68,15 +68,31 @@ class Returned extends \Magento\Framework\App\Action\Action */ protected $databaseHelper; + /** + * TransactionStatus factory + * + * @var \Payone\Core\Model\Entities\TransactionStatusFactory + */ + protected $statusFactory; + + /** + * TransactionStatus handler + * + * @var \Payone\Core\Model\Handler\TransactionStatus + */ + protected $transactionStatusHandler; + /** * Constructor * - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Checkout\Model\Session $checkoutSession - * @param \Magento\Quote\Model\QuoteManagement $quoteManagement - * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository - * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param \Payone\Core\Helper\Database $databaseHelper + * @param \Magento\Framework\App\Action\Context $context + * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Quote\Model\QuoteManagement $quoteManagement + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository + * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository + * @param \Payone\Core\Helper\Database $databaseHelper + * @param \Payone\Core\Model\Entities\TransactionStatusFactory $statusFactory + * @param \Payone\Core\Model\Handler\TransactionStatus $transactionStatusHandler */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -84,7 +100,9 @@ public function __construct( \Magento\Quote\Model\QuoteManagement $quoteManagement, \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, - \Payone\Core\Helper\Database $databaseHelper + \Payone\Core\Helper\Database $databaseHelper, + \Payone\Core\Model\Entities\TransactionStatusFactory $statusFactory, + \Payone\Core\Model\Handler\TransactionStatus $transactionStatusHandler ) { parent::__construct($context); $this->checkoutSession = $checkoutSession; @@ -92,6 +110,8 @@ public function __construct( $this->orderRepository = $orderRepository; $this->databaseHelper = $databaseHelper; $this->quoteRepository = $quoteRepository; + $this->statusFactory = $statusFactory; + $this->transactionStatusHandler = $transactionStatusHandler; } /** @@ -131,9 +151,32 @@ protected function createSubstituteOrder(Order $canceledOrder) $this->databaseHelper->relabelApiProtocol($canceledOrder->getIncrementId(), $newOrder->getIncrementId()); $this->databaseHelper->relabelOrderPayment($canceledOrder->getIncrementId(), $newOrder->getId()); + $this->handleTransactionStatus($canceledOrder, $newOrder); + $this->checkoutSession->unsPayoneCreatingSubstituteOrder(); } + /** + * Handle stored TransactionStatus + * + * @param Order $canceledOrder + * @param Order $newOrder + * @return void + */ + protected function handleTransactionStatus(Order $canceledOrder, Order $newOrder) + { + $aTransactionStatusIds = $this->databaseHelper->getNotHandledTransactionsByOrderId($canceledOrder->getIncrementId()); + foreach ($aTransactionStatusIds as $aRow) { + $oTransactionStatus = $this->statusFactory->create(); + $oTransactionStatus->load($aRow['id']); + + $this->transactionStatusHandler->handle($newOrder, $oTransactionStatus->getRawStatusArray()); + + $oTransactionStatus->setHasBeenHandled(true); + $oTransactionStatus->save(); + } + } + /** * Get canceled order. * Return order if found. diff --git a/Controller/Transactionstatus/Index.php b/Controller/Transactionstatus/Index.php index a6fef269..e6d74eba 100644 --- a/Controller/Transactionstatus/Index.php +++ b/Controller/Transactionstatus/Index.php @@ -33,13 +33,6 @@ */ class Index extends \Magento\Framework\App\Action\Action { - /** - * Contect object - * - * @var \Magento\Framework\App\Action\Context - */ - protected $context; - /** * TransactionStatus model * @@ -69,18 +62,11 @@ class Index extends \Magento\Framework\App\Action\Action protected $orderHelper; /** - * PAYONE TransactionStatus Mapping - * - * @var \Payone\Core\Model\TransactionStatus\Mapping - */ - protected $statusMapping; - - /** - * PAYONE TransactionStatus Forwarding + * TransactionStatus handler * - * @var \Payone\Core\Model\TransactionStatus\Forwarding + * @var \Payone\Core\Model\Handler\TransactionStatus */ - protected $statusForwarding; + protected $transactionStatusHandler; /** * Result factory for file-download @@ -89,13 +75,6 @@ class Index extends \Magento\Framework\App\Action\Action */ protected $resultRawFactory; - /** - * Event manager object - * - * @var \Magento\Framework\Event\ManagerInterface - */ - protected $eventManager; - /** * Constructor * @@ -104,8 +83,7 @@ class Index extends \Magento\Framework\App\Action\Action * @param \Payone\Core\Helper\Toolkit $toolkitHelper * @param \Payone\Core\Helper\Environment $environmentHelper * @param \Payone\Core\Helper\Order $orderHelper - * @param \Payone\Core\Model\TransactionStatus\Mapping $statusMapping - * @param \Payone\Core\Model\TransactionStatus\Forwarding $statusForwarding + * @param \Payone\Core\Model\Handler\TransactionStatus $transactionStatusHandler, * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory */ public function __construct( @@ -114,20 +92,16 @@ public function __construct( \Payone\Core\Helper\Toolkit $toolkitHelper, \Payone\Core\Helper\Environment $environmentHelper, \Payone\Core\Helper\Order $orderHelper, - \Payone\Core\Model\TransactionStatus\Mapping $statusMapping, - \Payone\Core\Model\TransactionStatus\Forwarding $statusForwarding, + \Payone\Core\Model\Handler\TransactionStatus $transactionStatusHandler, \Magento\Framework\Controller\Result\RawFactory $resultRawFactory ) { parent::__construct($context); - $this->context = $context; $this->transactionStatus = $transactionStatus; $this->toolkitHelper = $toolkitHelper; $this->environmentHelper = $environmentHelper; $this->orderHelper = $orderHelper; - $this->statusMapping = $statusMapping; - $this->statusForwarding = $statusForwarding; + $this->transactionStatusHandler = $transactionStatusHandler; $this->resultRawFactory = $resultRawFactory; - $this->eventManager = $context->getEventManager(); } /** @@ -138,7 +112,7 @@ public function __construct( */ protected function getParam($sKey) { - return $this->context->getRequest()->getParam($sKey, ''); + return $this->getRequest()->getParam($sKey, ''); } /** @@ -148,31 +122,20 @@ protected function getParam($sKey) */ protected function getPostArray() { - return $this->context->getRequest()->getPost()->toArray(); + return $this->getRequest()->getPostValue(); } /** * Write the TransactionStatus to the database * * @param Order $oOrder + * @param array $aRequest + * @param bool $blWillBeHandled * @return void */ - protected function log(Order $oOrder = null) + protected function logTransactionStatus(Order $oOrder = null, $aRequest, $blWillBeHandled) { - $this->transactionStatus->addTransactionLogEntry($this->context, $oOrder); - } - - /** - * Order processing - * - * @param Order $oOrder - * @return void - */ - protected function handleOrder(Order $oOrder) - { - $sAction = $this->getParam('txaction'); - $oOrder->setPayoneTransactionStatus($sAction); - $oOrder->save(); + $this->transactionStatus->addTransactionLogEntry($aRequest, $oOrder, $blWillBeHandled); } /** @@ -184,27 +147,23 @@ protected function handleTransactionStatus() { if (!$this->environmentHelper->isRemoteIpValid()) { return 'Access denied'; + } elseif (!$this->toolkitHelper->isKeyValid($this->getParam('key'))) { + return 'Key wrong or missing!'; } - if ($this->toolkitHelper->isKeyValid($this->getParam('key'))) { - $oOrder = $this->orderHelper->getOrderByTxid($this->getParam('txid')); - $this->log($oOrder); - if ($oOrder) { - $this->handleOrder($oOrder); - $this->statusMapping->handleMapping($oOrder, $this->getParam('txaction')); - } - $this->statusForwarding->handleForwardings($this->getPostArray()); - - $aParams = [ - 'order' => $oOrder, - 'transactionstatus' => $this->getPostArray(), - ]; - - $this->eventManager->dispatch('payone_core_transactionstatus_all', $aParams); - $this->eventManager->dispatch('payone_core_transactionstatus_'.$this->getParam('txaction'), $aParams); - - return 'TSOK'; + + $blWillBeHandled = true; + $oOrder = $this->orderHelper->getOrderByTxid($this->getParam('txid')); + if ($this->getParam('txaction') == 'appointed' && $oOrder->getStatus() == 'canceled') { + $blWillBeHandled = false; // order was already canceled, status will be handled in substitute order mechanism, if order is finished } - return 'Key wrong or missing!'; + + $this->logTransactionStatus($oOrder, $this->getPostArray(), $blWillBeHandled); + + if ($blWillBeHandled === true) { + $this->transactionStatusHandler->handle($oOrder, $this->getPostArray()); + } + + return 'TSOK'; } /** @@ -215,8 +174,10 @@ protected function handleTransactionStatus() public function execute() { $sOutput = $this->handleTransactionStatus(); + $oResultRaw = $this->resultRawFactory->create(); $oResultRaw->setContents($sOutput); + return $oResultRaw; } } diff --git a/Helper/Database.php b/Helper/Database.php index 88d38b43..6054fedb 100644 --- a/Helper/Database.php +++ b/Helper/Database.php @@ -245,6 +245,14 @@ public function getOldAddressStatus(AddressInterface $oAddress, $blIsCreditratin return $this->getDb()->fetchOne($oSelect, $aParams); } + /** + * Relabel sales payment transaction to substitute order + * + * @param string $sOldOrderId + * @param string $sNewOrderId + * @param string $sNewPaymentId + * @return int + */ public function relabelTransaction($sOldOrderId, $sNewOrderId, $sNewPaymentId) { $table = $this->databaseResource->getTableName('sales_payment_transaction'); @@ -256,6 +264,13 @@ public function relabelTransaction($sOldOrderId, $sNewOrderId, $sNewPaymentId) return $this->getDb()->update($table, $data, $where); } + /** + * Relabel payone protocol api to substitute order + * + * @param string $sOldIncrementId + * @param string $sNewIncrementId + * @return int + */ public function relabelApiProtocol($sOldIncrementId, $sNewIncrementId) { $table = $this->databaseResource->getTableName('payone_protocol_api'); @@ -264,6 +279,13 @@ public function relabelApiProtocol($sOldIncrementId, $sNewIncrementId) return $this->getDb()->update($table, $data, $where); } + /** + * Relabel sales order payment to substitute order + * + * @param string $sOldIncrementId + * @param string $sNewOrderId + * @return int + */ public function relabelOrderPayment($sOldIncrementId, $sNewOrderId) { $oSelect = $this->getDb() @@ -279,4 +301,21 @@ public function relabelOrderPayment($sOldIncrementId, $sNewOrderId) $where = ['parent_id = ?' => $sNewOrderId]; return $this->getDb()->update($table, $data, $where); } + + /** + * Return not handled transactions by order id + * + * @param string $sOrderId + * @return array + */ + public function getNotHandledTransactionsByOrderId($sOrderId) + { + $oSelect = $this->getDb() + ->select() + ->from($this->databaseResource->getTableName('payone_protocol_transactionstatus'), ['id']) + ->where("order_id = :orderId") + ->where("has_been_handled = 0") + ->order('id ASC'); + return $this->getDb()->fetchAll($oSelect, ['orderId' => $sOrderId]); + } } diff --git a/Model/Handler/TransactionStatus.php b/Model/Handler/TransactionStatus.php new file mode 100644 index 00000000..c976ac03 --- /dev/null +++ b/Model/Handler/TransactionStatus.php @@ -0,0 +1,148 @@ +. + * + * PHP version 5 + * + * @category Payone + * @package Payone_Magento2_Plugin + * @author FATCHIP GmbH + * @copyright 2003 - 2018 Payone GmbH + * @license GNU Lesser General Public License + * @link http://www.payone.de + */ + +namespace Payone\Core\Model\Handler; + +use Magento\Checkout\Model\Session; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\OrderFactory; +use Magento\Framework\Exception\LocalizedException; +use \Magento\Quote\Api\CartRepositoryInterface as QuoteRepo; + +class TransactionStatus +{ + /** + * TransactionStatus array + * + * @var array + */ + protected $status = []; + + /** + * PAYONE TransactionStatus Mapping + * + * @var \Payone\Core\Model\TransactionStatus\Mapping + */ + protected $statusMapping; + + /** + * PAYONE TransactionStatus Forwarding + * + * @var \Payone\Core\Model\TransactionStatus\Forwarding + */ + protected $statusForwarding; + + /** + * Magento event manager + * + * @var \Magento\Framework\Event\ManagerInterface + */ + protected $eventManager; + + /** + * Constructor + * + * @param \Payone\Core\Model\TransactionStatus\Mapping $statusMapping + * @param \Payone\Core\Model\TransactionStatus\Forwarding $statusForwarding + * @param \Magento\Framework\Event\ManagerInterface $eventManager + */ + public function __construct( + \Payone\Core\Model\TransactionStatus\Mapping $statusMapping, + \Payone\Core\Model\TransactionStatus\Forwarding $statusForwarding, + \Magento\Framework\Event\ManagerInterface $eventManager + ) { + $this->statusMapping = $statusMapping; + $this->statusForwarding = $statusForwarding; + $this->eventManager = $eventManager; + } + + /** + * Set status array + * + * @param array $aStatus + * @return void + */ + protected function setStatus($aStatus) + { + if (is_array($aStatus)) { + $this->status = $aStatus; + } + } + + /** + * Return status array + * + * @return array + */ + protected function getStatus() + { + return $this->status; + } + + /** + * Return certain key from status array + * + * @param string $sKey + * @return string|null + */ + protected function getParam($sKey) + { + if (isset($this->status[$sKey])) { + return $this->status[$sKey]; + } + return null; + } + + /** + * Handle TransactionStatus + * + * @param Order $oOrder + * @param array $aStatus + * @return void + */ + public function handle(Order $oOrder, $aStatus) + { + $this->setStatus($aStatus); + + $sAction = $this->getParam('txaction'); + + if ($oOrder) { + $oOrder->setPayoneTransactionStatus($sAction); + $oOrder->save(); + + $this->statusMapping->handleMapping($oOrder, $sAction); + } + $this->statusForwarding->handleForwardings($this->getStatus()); + + $aParams = [ + 'order' => $oOrder, + 'transactionstatus' => $this->getStatus(), + ]; + + $this->eventManager->dispatch('payone_core_transactionstatus_all', $aParams); + $this->eventManager->dispatch('payone_core_transactionstatus_'.$sAction, $aParams); + } +} \ No newline at end of file diff --git a/Model/PayoneConfig.php b/Model/PayoneConfig.php index d5b38827..7f08a7af 100644 --- a/Model/PayoneConfig.php +++ b/Model/PayoneConfig.php @@ -32,7 +32,7 @@ abstract class PayoneConfig { /* Module version */ - const MODULE_VERSION = '2.3.3'; + const MODULE_VERSION = '2.4.2'; /* Authorization request types */ const REQUEST_TYPE_PREAUTHORIZATION = 'preauthorization'; diff --git a/Model/ResourceModel/TransactionStatus.php b/Model/ResourceModel/TransactionStatus.php index 030d5ded..f8ad7168 100644 --- a/Model/ResourceModel/TransactionStatus.php +++ b/Model/ResourceModel/TransactionStatus.php @@ -36,11 +36,11 @@ class TransactionStatus extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { /** - * Context object + * TransactionStatus POST request * - * @var \Magento\Framework\App\Action\Context + * @var array */ - protected $oContext = null; + protected $request = []; /** * Store manager object @@ -85,6 +85,29 @@ protected function _construct() $this->_init('payone_protocol_transactionstatus', 'id'); } + /** + * Set request property + * + * @param array $request + * @return void + */ + public function setRequest($request) + { + if (is_array($request)) { + $this->request = $request; + } + } + + /** + * Get request property + * + * @return array + */ + public function getRequest() + { + return $this->request; + } + /** * Get request-parameter value or a given default if not set * @@ -94,8 +117,8 @@ protected function _construct() */ public function getParam($sKey, $sDefault = '') { - if ($this->oContext) { - $sParam = $this->oContext->getRequest()->getParam($sKey, $sDefault); + if (isset($this->request[$sKey])) { + $sParam = $this->request[$sKey]; if (!$this->toolkitHelper->isUTF8($sParam)) { $sParam = utf8_encode($sParam); } @@ -107,15 +130,16 @@ public function getParam($sKey, $sDefault = '') /** * Write TransactionStatus entry to database * - * @param Context $oContext - * @param Order $oOrder + * @param array $aRequest + * @param Order $oOrder + * @param bool $blHasBeenHandled * @return $this */ - public function addTransactionLogEntry(Context $oContext, Order $oOrder = null) + public function addTransactionLogEntry($aRequest, Order $oOrder = null, $blHasBeenHandled = true) { - $this->oContext = $oContext; - $aRequest = $oContext->getRequest()->getPostValue(); - $sRawStatus = serialize($aRequest); + $this->setRequest($aRequest); + + $sRawStatus = serialize($this->getRequest()); if (!$this->toolkitHelper->isUTF8($sRawStatus)) { $sRawStatus = utf8_encode($sRawStatus); // needed for serializing the array } @@ -177,6 +201,7 @@ public function addTransactionLogEntry(Context $oContext, Order $oOrder = null) 'clearing_reference' => $this->getParam('clearing_reference'), 'clearing_instructionnote' => $this->getParam('clearing_instructionnote'), 'raw_status' => $sRawStatus, + 'has_been_handled' => $blHasBeenHandled ] ); return $this; diff --git a/Setup/UpgradeSchema.php b/Setup/UpgradeSchema.php index 389089f9..f3a5ba8e 100644 --- a/Setup/UpgradeSchema.php +++ b/Setup/UpgradeSchema.php @@ -74,6 +74,19 @@ protected function addNewColumns(SchemaSetupInterface $setup, ModuleContextInter ] ); } + if (!$setup->getConnection()->tableColumnExists($setup->getTable(Transactionstatus::TABLE_PROTOCOL_TRANSACTIONSTATUS), 'has_been_handled')) { + $setup->getConnection()->addColumn( + $setup->getTable(Transactionstatus::TABLE_PROTOCOL_TRANSACTIONSTATUS), + 'has_been_handled', + [ + 'type' => Table::TYPE_SMALLINT, + 'length' => null, + 'nullable' => false, + 'default' => 1, + 'comment' => 'Has the status been handled already' + ] + ); + } } /** diff --git a/Test/Unit/Controller/Onepage/ReturnedTest.php b/Test/Unit/Controller/Onepage/ReturnedTest.php index 3b83dde7..882c9305 100644 --- a/Test/Unit/Controller/Onepage/ReturnedTest.php +++ b/Test/Unit/Controller/Onepage/ReturnedTest.php @@ -41,6 +41,9 @@ use Magento\Quote\Model\QuoteRepository; use Magento\Sales\Model\OrderRepository; use Magento\Sales\Model\Order\Payment; +use Payone\Core\Helper\Database; +use Payone\Core\Model\Entities\TransactionStatusFactory; +use Payone\Core\Model\Entities\TransactionStatus; class ReturnedTest extends BaseTestCase { @@ -106,11 +109,24 @@ protected function setUp() $orderRepository = $this->getMockBuilder(OrderRepository::class)->disableOriginalConstructor()->getMock(); $orderRepository->method('get')->willReturn($order); + $databaseHelper = $this->getMockBuilder(Database::class)->disableOriginalConstructor()->getMock(); + $databaseHelper->method('getNotHandledTransactionsByOrderId')->willReturn([['id' => 5]]); + + $status = $this->getMockBuilder(TransactionStatus::class)->disableOriginalConstructor()->getMock(); + + $statusFactory = $this->getMockBuilder(TransactionStatusFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $statusFactory->method('create')->willReturn($status); + $this->classToTest = $this->objectManager->getObject(ClassToTest::class, [ 'context' => $context, 'checkoutSession' => $this->checkoutSession, 'quoteRepository' => $quoteRepository, - 'orderRepository' => $orderRepository + 'orderRepository' => $orderRepository, + 'databaseHelper' => $databaseHelper, + 'statusFactory' => $statusFactory ]); } diff --git a/Test/Unit/Controller/Transactionstatus/IndexTest.php b/Test/Unit/Controller/Transactionstatus/IndexTest.php index 53b0d158..535a69c5 100644 --- a/Test/Unit/Controller/Transactionstatus/IndexTest.php +++ b/Test/Unit/Controller/Transactionstatus/IndexTest.php @@ -62,6 +62,16 @@ class IndexTest extends BaseTestCase */ private $environmentHelper; + /** + * @var OrderCore|\PHPUnit_Framework_MockObject_MockObject + */ + private $order; + + /** + * @var Http + */ + private $request; + protected function setUp() { $this->objectManager = $this->getObjectManager(); @@ -69,25 +79,24 @@ protected function setUp() $post = $this->getMockBuilder(self::class)->disableOriginalConstructor()->setMethods(['toArray'])->getMock(); $post->method('toArray')->willReturn(['test' => 'array']); - $request = $this->getMockBuilder(Http::class) + $this->request = $this->getMockBuilder(Http::class) ->disableOriginalConstructor() ->setMethods(['getParam', 'getPost']) ->getMock(); - $request->method('getParam')->willReturn('Value'); - $request->method('getPost')->willReturn($post); + $this->request->method('getPost')->willReturn($post); $eventManater = $this->getMockBuilder(ManagerInterface::class)->disableOriginalConstructor()->getMock(); $context = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); - $context->method('getRequest')->willReturn($request); + $context->method('getRequest')->willReturn($this->request); $context->method('getEventManager')->willReturn($eventManater); $this->toolkitHelper = $this->getMockBuilder(Toolkit::class)->disableOriginalConstructor()->getMock(); $this->environmentHelper = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); - $order = $this->getMockBuilder(OrderCore::class)->disableOriginalConstructor()->getMock(); + $this->order = $this->getMockBuilder(OrderCore::class)->disableOriginalConstructor()->getMock(); $orderHelper = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); - $orderHelper->method('getOrderByTxid')->willReturn($order); + $orderHelper->method('getOrderByTxid')->willReturn($this->order); $rawResponse = $this->getMockBuilder(Raw::class)->disableOriginalConstructor()->getMock(); $resultRawFactory = $this->getMockBuilder(RawFactory::class) @@ -107,6 +116,7 @@ protected function setUp() public function testExecuteIpInvalid() { + $this->request->method('getParam')->willReturn('Value'); $this->environmentHelper->method('isRemoteIpValid')->willReturn(false); $result = $this->classToTest->execute(); @@ -115,6 +125,7 @@ public function testExecuteIpInvalid() public function testExecuteKeyInvalid() { + $this->request->method('getParam')->willReturn('Value'); $this->environmentHelper->method('isRemoteIpValid')->willReturn(true); $this->toolkitHelper->method('isKeyValid')->willReturn(false); @@ -122,8 +133,20 @@ public function testExecuteKeyInvalid() $this->assertInstanceOf(Raw::class, $result); } + public function testExecuteCanceled() + { + $this->request->method('getParam')->willReturn('appointed'); + $this->order->method('getStatus')->willReturn('canceled'); + $this->environmentHelper->method('isRemoteIpValid')->willReturn(true); + $this->toolkitHelper->method('isKeyValid')->willReturn(true); + + $result = $this->classToTest->execute(); + $this->assertInstanceOf(Raw::class, $result); + } + public function testExecute() { + $this->request->method('getParam')->willReturn('Value'); $this->environmentHelper->method('isRemoteIpValid')->willReturn(true); $this->toolkitHelper->method('isKeyValid')->willReturn(true); diff --git a/Test/Unit/Helper/DatabaseTest.php b/Test/Unit/Helper/DatabaseTest.php index 1fc9148a..1f3b94d1 100644 --- a/Test/Unit/Helper/DatabaseTest.php +++ b/Test/Unit/Helper/DatabaseTest.php @@ -251,4 +251,17 @@ public function testRelabelOrderPayment() $result = $this->database->relabelOrderPayment('1', '2'); $this->assertEquals($expected, $result); } + + public function testGetNotHandledTransactionsByOrderId() + { + $expected = [ + ['id' => '5'], + ]; + + $this->databaseResource->method('getTableName')->willReturn('payone_protocol_transactionstatus'); + $this->connection->method('fetchAll')->willReturn($expected); + + $result = $this->database->getNotHandledTransactionsByOrderId(5); + $this->assertEquals($expected, $result); + } } diff --git a/Test/Unit/Model/Handler/TransactionStatusTest.php b/Test/Unit/Model/Handler/TransactionStatusTest.php new file mode 100644 index 00000000..d0a4a1bf --- /dev/null +++ b/Test/Unit/Model/Handler/TransactionStatusTest.php @@ -0,0 +1,73 @@ +. + * + * PHP version 5 + * + * @category Payone + * @package Payone_Magento2_Plugin + * @author FATCHIP GmbH + * @copyright 2003 - 2018 Payone GmbH + * @license GNU Lesser General Public License + * @link http://www.payone.de + */ + +namespace Payone\Core\Test\Unit\Model\Handler; + +use Magento\Sales\Model\Order; +use Payone\Core\Model\Handler\TransactionStatus as ClassToTest; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Payone\Core\Test\Unit\BaseTestCase; +use Payone\Core\Test\Unit\PayoneObjectManager; + +class TransactionStatusTest extends BaseTestCase +{ + /** + * @var ClassToTest + */ + private $classToTest; + + /** + * @var ObjectManager|PayoneObjectManager + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = $this->getObjectManager(); + + $this->classToTest = $this->objectManager->getObject(ClassToTest::class); + } + + public function testHandle() + { + $order = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); + + $status = ['txaction' => 'appointed']; + + $result = $this->classToTest->handle($order, $status); + $this->assertNull($result); + } + + public function testHandleGetParamNull() + { + $order = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); + + $status = []; + + $result = $this->classToTest->handle($order, $status); + $this->assertNull($result); + } +} diff --git a/Test/Unit/Model/ResourceModel/TransactionStatusTest.php b/Test/Unit/Model/ResourceModel/TransactionStatusTest.php index e7e08390..0113e1cc 100644 --- a/Test/Unit/Model/ResourceModel/TransactionStatusTest.php +++ b/Test/Unit/Model/ResourceModel/TransactionStatusTest.php @@ -99,19 +99,12 @@ protected function setUp() public function testAddTransactionLogEntry() { - $post = 15; + $request = [ + 'txaction' => 'appointed', + 'txtime' => time(), + ]; - $request = $this->getMockBuilder(Http::class) - ->disableOriginalConstructor() - ->setMethods(['getPostValue', 'getParam']) - ->getMock(); - $request->method('getPostValue')->willReturn($post); - $request->method('getParam')->willReturn(15); - - $context = $this->getMockBuilder(ActionContext::class)->disableOriginalConstructor()->getMock(); - $context->method('getRequest')->willReturn($request); - - $result = $this->classToTest->addTransactionLogEntry($context); + $result = $this->classToTest->addTransactionLogEntry($request); $this->assertInstanceOf(ClassToTest::class, $result); } diff --git a/etc/module.xml b/etc/module.xml index 2d60e5e8..646ba7cb 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -25,7 +25,7 @@ */ --> - +