From b5219f38a782a965b56c7471342a78002712b680 Mon Sep 17 00:00:00 2001 From: Michiel Gerritsen Date: Thu, 23 Jun 2022 08:58:02 +0200 Subject: [PATCH 1/5] Feature: Support for subscriptions under configurables --- .../SetSubscriptionDataOnBuyRequest.php | 81 +++++++++++++++++++ etc/events.xml | 3 + 2 files changed, 84 insertions(+) create mode 100644 Observer/SalesQuoteItemSetProduct/SetSubscriptionDataOnBuyRequest.php diff --git a/Observer/SalesQuoteItemSetProduct/SetSubscriptionDataOnBuyRequest.php b/Observer/SalesQuoteItemSetProduct/SetSubscriptionDataOnBuyRequest.php new file mode 100644 index 00000000000..c3f360d4fdd --- /dev/null +++ b/Observer/SalesQuoteItemSetProduct/SetSubscriptionDataOnBuyRequest.php @@ -0,0 +1,81 @@ +serializer = $serializer; + } + + public function execute(Observer $observer) + { + /** @var ProductInterface $product */ + $product = $observer->getData('product'); + if (!$product->getData('mollie_subscription_product')) { + return; + } + + $table = $product->getData('mollie_subscription_table'); + if (!$table) { + return; + } + + $buyRequest = $this->getBuyRequest($observer->getData('quote_item')); + $value = $this->serializer->unserialize($buyRequest->getValue()); + if (isset($value['mollie_metadata'])) { + return; + } + + $data = $this->serializer->unserialize($table); + $default = $this->getDefault($data); + + $value['mollie_metadata'] = [ + 'purchase' => 'subscription', + 'recurring_metadata' => [ + 'option_id' => $default['identifier'], + ], + ]; + + $buyRequest->setValue($this->serializer->serialize($value)); + } + + private function getDefault(array $data): array + { + foreach ($data as $row) { + if (isset($row['isDefault']) && $row['isDefault']) { + return $row; + } + } + + return array_shift($data); + } + + private function getBuyRequest(CartItemInterface $item): OptionInterface + { + /** @var OptionInterface[] $options */ + $options = $item->getOptions(); + foreach ($options as $option) { + if ($option->getCode() == 'info_buyRequest') { + return $option; + } + } + + throw new NotFoundException(__('No info_buyRequest option found')); + } +} diff --git a/etc/events.xml b/etc/events.xml index 87bf1783d1e..5a411409ce0 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -42,4 +42,7 @@ + + + From 9bcd5b390609dc24fd579ff50f3aaa62141c4010 Mon Sep 17 00:00:00 2001 From: Michiel Gerritsen Date: Thu, 23 Jun 2022 11:07:23 +0200 Subject: [PATCH 2/5] Bugfix: PHP8.1 does not allow null --- Helper/General.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/General.php b/Helper/General.php index f7830db08ad..3d549924854 100755 --- a/Helper/General.php +++ b/Helper/General.php @@ -605,7 +605,7 @@ public function formatCurrencyValue($value, $currency) $decimalPrecision = 0; } - return number_format($value, $decimalPrecision, '.', ''); + return number_format($value ?? 0.0, $decimalPrecision, '.', ''); } /** From 0825dd49bd649ff14390abf408afbf9ed47ed581 Mon Sep 17 00:00:00 2001 From: Michiel Gerritsen Date: Thu, 23 Jun 2022 11:08:01 +0200 Subject: [PATCH 3/5] Bugfix: Check that the invoice is not null --- Model/Client/Payments/Processors/SuccessfulPayment.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Model/Client/Payments/Processors/SuccessfulPayment.php b/Model/Client/Payments/Processors/SuccessfulPayment.php index 5f5e7c06616..a5c8a89ab60 100644 --- a/Model/Client/Payments/Processors/SuccessfulPayment.php +++ b/Model/Client/Payments/Processors/SuccessfulPayment.php @@ -127,12 +127,14 @@ public function process( $this->handlePayment($magentoOrder, $molliePayment); } - /** @var Order\Invoice $invoice */ + /** @var Order\Invoice|null $invoice */ $invoice = $payment->getCreatedInvoice(); $sendInvoice = $this->mollieHelper->sendInvoice($magentoOrder->getStoreId()); $this->sendOrderConfirmationEmail($magentoOrder); - $this->sendInvoiceEmail($invoice, $sendInvoice, $magentoOrder); + if ($invoice) { + $this->sendInvoiceEmail($invoice, $sendInvoice, $magentoOrder); + } return $this->processTransactionResponseFactory->create([ 'success' => true, From 88ae8451c1de4ccdd892d408dbda27b8000865bf Mon Sep 17 00:00:00 2001 From: Michiel Gerritsen Date: Mon, 27 Jun 2022 13:54:31 +0200 Subject: [PATCH 4/5] Feature: Allow to cancel the order when in pending payment state --- Observer/OrderCancelAfter.php | 17 ++- .../Observer/TestOrderCancelAfter.php | 105 ++++++++++++++++++ 2 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 Test/Integration/Observer/TestOrderCancelAfter.php diff --git a/Observer/OrderCancelAfter.php b/Observer/OrderCancelAfter.php index dbb11b7559c..c28290930a9 100644 --- a/Observer/OrderCancelAfter.php +++ b/Observer/OrderCancelAfter.php @@ -8,6 +8,8 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Event\Observer; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Message\ManagerInterface; use Mollie\Payment\Model\Mollie as MollieModel; use Mollie\Payment\Helper\General as MollieHelper; @@ -28,6 +30,11 @@ class OrderCancelAfter implements ObserverInterface */ private $mollieHelper; + /** + * @var ManagerInterface + */ + private $messageManager; + /** * OrderCancelAfter constructor. * @@ -36,10 +43,12 @@ class OrderCancelAfter implements ObserverInterface */ public function __construct( MollieModel $mollieModel, - MollieHelper $mollieHelper + MollieHelper $mollieHelper, + ManagerInterface $messageManager ) { $this->mollieModel = $mollieModel; $this->mollieHelper = $mollieHelper; + $this->messageManager = $messageManager; } /** @@ -55,12 +64,14 @@ public function execute(Observer $observer) /** * When manually marking an order as paid we don't want to communicate to Mollie as it will throw an exception. */ - if ($order->getReordered()) { + if ($order->getReordered() || !$this->mollieHelper->isPaidUsingMollieOrdersApi($order)) { return; } - if ($this->mollieHelper->isPaidUsingMollieOrdersApi($order)) { + try { $this->mollieModel->cancelOrder($order); + } catch (LocalizedException $localizedException) { + $this->messageManager->addErrorMessage($localizedException->getMessage()); } } } diff --git a/Test/Integration/Observer/TestOrderCancelAfter.php b/Test/Integration/Observer/TestOrderCancelAfter.php new file mode 100644 index 00000000000..652d8eb03e2 --- /dev/null +++ b/Test/Integration/Observer/TestOrderCancelAfter.php @@ -0,0 +1,105 @@ +createMock(Mollie::class); + $modelMock->expects($this->never())->method('cancelOrder'); + + /** @var OrderCancelAfter $instance */ + $instance = $this->objectManager->create(OrderCancelAfter::class, [ + 'mollieModel' => $modelMock, + ]); + + $order = $this->objectManager->create(OrderInterface::class); + $order->setReordered(true); + + $instance->execute($this->makeObserver($order)); + } + + public function testDoesNothingWhenNotPaidUsingOrdersApi(): void + { + $modelMock = $this->createMock(Mollie::class); + $modelMock->expects($this->never())->method('cancelOrder'); + + $helperMock = $this->createMock(\Mollie\Payment\Helper\General::class); + $helperMock->method('isPaidUsingMollieOrdersApi')->willReturn(false); + + /** @var OrderCancelAfter $instance */ + $instance = $this->objectManager->create(OrderCancelAfter::class, [ + 'mollieHelper' => $helperMock, + 'mollieModel' => $modelMock, + ]); + + $order = $this->objectManager->create(OrderInterface::class); + + $instance->execute($this->makeObserver($order)); + } + + public function testCancelsTheOrder(): void + { + $modelMock = $this->createMock(Mollie::class); + $modelMock->expects($this->once())->method('cancelOrder'); + + $helperMock = $this->createMock(\Mollie\Payment\Helper\General::class); + $helperMock->method('isPaidUsingMollieOrdersApi')->willReturn(true); + + /** @var OrderCancelAfter $instance */ + $instance = $this->objectManager->create(OrderCancelAfter::class, [ + 'mollieHelper' => $helperMock, + 'mollieModel' => $modelMock, + ]); + + $order = $this->objectManager->create(OrderInterface::class); + + $instance->execute($this->makeObserver($order)); + } + + public function testConvertsExceptionToErrorMessage(): void + { + $message = 'Error executing API call (422: Unprocessable Entity): The order cannot be canceled due to an open payment. Please wait until the payment is in a finalized state.. Documentation: https://docs.mollie.com/reference/v2/orders-api/cancel-order'; + $exception = new LocalizedException(__($message)); + $modelMock = $this->createMock(Mollie::class); + $modelMock->method('cancelOrder')->willThrowException($exception); + + $helperMock = $this->createMock(\Mollie\Payment\Helper\General::class); + $helperMock->method('isPaidUsingMollieOrdersApi')->willReturn(true); + + /** @var OrderCancelAfter $instance */ + $instance = $this->objectManager->create(OrderCancelAfter::class, [ + 'mollieHelper' => $helperMock, + 'mollieModel' => $modelMock, + ]); + + $order = $this->objectManager->create(OrderInterface::class); + + $instance->execute($this->makeObserver($order)); + + /** @var \\Magento\Framework\Message\ManagerInterface $manager */ + $manager = $this->objectManager->get(\Magento\Framework\Message\ManagerInterface::class); + + $messages = $manager->getMessages(); + $this->assertCount(1, $messages->getErrors()); + $this->assertEquals($message, $messages->getErrors()[0]->getText()); + } + + private function makeObserver(OrderInterface $order): Observer + { + return new Observer([ + 'event' => new Event([ + 'order' => $order, + ]), + ]); + } +} From b99ceb9b71ae365ec9aaf63f42d23634a3afa54a Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 27 Jun 2022 20:19:01 +0200 Subject: [PATCH 5/5] Version bump --- composer.json | 2 +- etc/config.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index cd6302aea22..e539280f523 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "mollie/magento2", "description": "Mollie Payment Module for Magento 2", - "version": "2.12.0", + "version": "2.13.0", "keywords": [ "mollie", "payment", diff --git a/etc/config.xml b/etc/config.xml index 1bde992980d..010d552643e 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -3,7 +3,7 @@ - v2.12.0 + v2.13.0 0 0 test