From 2b9e39a254cbfdeee02c595964c536972d9dd30b Mon Sep 17 00:00:00 2001 From: Michiel Gerritsen Date: Thu, 18 Jan 2024 13:58:57 +0100 Subject: [PATCH] Bugfix: Call startTransaction when the customer clicks the paymentLink link --- Block/Info/Base.php | 50 +++++++--- Config.php | 18 ++++ Controller/Checkout/PaymentLink.php | 99 +++++++++++++++++++ Helper/General.php | 1 + .../StartTransactionForPaymentLinkOrders.php | 43 -------- Test/End-2-end/cypress.config.js | 5 + .../e2e/magento/backend/paymentlink.cy.js | 59 +++++------ .../support/pages/backend/OrdersCreatePage.js | 13 ++- .../Controller/Checkout/PaymentLinkTest.php | 75 ++++++++++++++ ...artTransactionForPaymentLinkOrdersTest.php | 61 ------------ etc/adminhtml/di.xml | 6 ++ etc/events.xml | 1 - i18n/de_DE.csv | 1 + i18n/en_US.csv | 1 + i18n/es_ES.csv | 1 + i18n/fr_FR.csv | 1 + i18n/nl_NL.csv | 1 + .../templates/info/mollie_paymentlink.phtml | 6 +- .../templates/info/mollie_paymentlink.phtml | 7 +- 19 files changed, 291 insertions(+), 158 deletions(-) create mode 100644 Controller/Checkout/PaymentLink.php delete mode 100644 Observer/SalesModelServiceQuoteSubmitSuccess/StartTransactionForPaymentLinkOrders.php create mode 100644 Test/Integration/Controller/Checkout/PaymentLinkTest.php delete mode 100644 Test/Integration/Observer/CheckoutSubmitAllAfter/StartTransactionForPaymentLinkOrdersTest.php diff --git a/Block/Info/Base.php b/Block/Info/Base.php index 51f0184b4db..b2a00ff1b34 100644 --- a/Block/Info/Base.php +++ b/Block/Info/Base.php @@ -6,12 +6,15 @@ namespace Mollie\Payment\Block\Info; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Registry; +use Magento\Framework\UrlInterface; use Magento\Payment\Block\Info; use Magento\Framework\View\Element\Template\Context; use Magento\Framework\Stdlib\DateTime; use Magento\Sales\Api\Data\OrderInterface; +use Mollie\Payment\Config; use Mollie\Payment\Helper\General as MollieHelper; use Mollie\Payment\Model\Methods\Billie; use Mollie\Payment\Model\Methods\Klarna; @@ -21,7 +24,6 @@ class Base extends Info { - /** * @var string */ @@ -42,26 +44,36 @@ class Base extends Info * @var PriceCurrencyInterface */ private $price; - /** - * Base constructor. - * - * @param Context $context - * @param MollieHelper $mollieHelper - * @param Registry $registry - * @param PriceCurrencyInterface $price + * @var EncryptorInterface + */ + private $encryptor; + /** + * @var Config */ + private $config; + /** + * @var UrlInterface + */ + private $urlBuilder; + public function __construct( Context $context, + Config $config, MollieHelper $mollieHelper, Registry $registry, - PriceCurrencyInterface $price + PriceCurrencyInterface $price, + EncryptorInterface $encryptor, + UrlInterface $urlBuilder ) { parent::__construct($context); $this->mollieHelper = $mollieHelper; $this->timezone = $context->getLocaleDate(); $this->registry = $registry; $this->price = $price; + $this->encryptor = $encryptor; + $this->config = $config; + $this->urlBuilder = $urlBuilder; } public function getCheckoutType(): ?string @@ -87,16 +99,24 @@ public function getExpiresAt(): ?string return null; } - /** - * @param mixed $storeId - */ public function getPaymentLink($storeId = null): ?string { - if ($checkoutUrl = $this->getCheckoutUrl()) { - return $this->mollieHelper->getPaymentLinkMessage($checkoutUrl, $storeId); + if (!$this->config->addPaymentLinkMessage($storeId)) { + return null; } - return null; + return str_replace( + '%link%', + $this->getPaymentLinkUrl(), + $this->config->paymentLinkMessage($storeId) + ); + } + + public function getPaymentLinkUrl(): string + { + return $this->urlBuilder->getUrl('mollie/checkout/paymentlink', [ + 'order' => base64_encode($this->encryptor->encrypt($this->getInfo()->getParentId())), + ]); } public function getCheckoutUrl(): ?string diff --git a/Config.php b/Config.php index 0a2bf109633..a9f35f4d4c7 100644 --- a/Config.php +++ b/Config.php @@ -69,6 +69,8 @@ class Config const PAYMENT_METHOD_PAYMENT_TITLE = 'payment/mollie_methods_%s/title'; const PAYMENT_PAYMENTLINK_ALLOW_MARK_AS_PAID = 'payment/mollie_methods_paymentlink/allow_mark_as_paid'; const PAYMENT_PAYMENTLINK_NEW_STATUS = 'payment/mollie_methods_paymentlink/order_status_new'; + const PAYMENT_PAYMENTLINK_ADD_MESSAGE = 'payment/mollie_methods_paymentlink/add_message'; + const PAYMENT_PAYMENTLINK_MESSAGE = 'payment/mollie_methods_paymentlink/message'; const PAYMENT_POINTOFSALE_ALLOWED_CUSTOMER_GROUPS = 'payment/mollie_methods_pointofsale/allowed_customer_groups'; const PAYMENT_VOUCHER_CATEGORY = 'payment/mollie_methods_voucher/category'; const PAYMENT_VOUCHER_CUSTOM_ATTRIBUTE = 'payment/mollie_methods_voucher/custom_attribute'; @@ -495,6 +497,22 @@ public function statusNewPaymentLink($storeId = null) ); } + public function addPaymentLinkMessage($storeId = null): string + { + return (string)$this->getPath( + static::PAYMENT_PAYMENTLINK_ADD_MESSAGE, + $storeId + ); + } + + public function paymentLinkMessage($storeId = null): string + { + return (string)$this->getPath( + static::PAYMENT_PAYMENTLINK_MESSAGE, + $storeId + ); + } + /** * @param string $method * @param int|null $storeId diff --git a/Controller/Checkout/PaymentLink.php b/Controller/Checkout/PaymentLink.php new file mode 100644 index 00000000000..0db0b5b7144 --- /dev/null +++ b/Controller/Checkout/PaymentLink.php @@ -0,0 +1,99 @@ +request = $request; + $this->encryptor = $encryptor; + $this->resultFactory = $resultFactory; + $this->messageManager = $messageManager; + $this->orderRepository = $orderRepository; + $this->mollie = $mollie; + } + + public function execute() + { + $orderKey = $this->request->getParam('order'); + if (!$orderKey) { + return $this->returnStatusCode(400); + } + + $id = $this->encryptor->decrypt(base64_decode($orderKey)); + + if (empty($id)) { + return $this->returnStatusCode(404); + } + + try { + $order = $this->orderRepository->get($id); + } catch (NoSuchEntityException $exception) { + return $this->returnStatusCode(404); + } + + if (in_array($order->getState(), [Order::STATE_PROCESSING, Order::STATE_COMPLETE])) { + $this->messageManager->addSuccessMessage(__('Your order has already been paid.')); + + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setUrl('/'); + } + + $url = $this->mollie->startTransaction($order); + + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setUrl($url); + } + + public function returnStatusCode(int $code): ResultInterface + { + return $this->resultFactory->create(ResultFactory::TYPE_RAW)->setHttpResponseCode($code); + } +} diff --git a/Helper/General.php b/Helper/General.php index faf42696d71..2db4aa606c1 100755 --- a/Helper/General.php +++ b/Helper/General.php @@ -478,6 +478,7 @@ public function sendInvoice($storeId = 0) * @param int|null $storeId * * @return mixed + * @deprecated since 2.34.0 */ public function getPaymentLinkMessage($checkoutUrl, $storeId = null) { diff --git a/Observer/SalesModelServiceQuoteSubmitSuccess/StartTransactionForPaymentLinkOrders.php b/Observer/SalesModelServiceQuoteSubmitSuccess/StartTransactionForPaymentLinkOrders.php deleted file mode 100644 index bdcdc1678ff..00000000000 --- a/Observer/SalesModelServiceQuoteSubmitSuccess/StartTransactionForPaymentLinkOrders.php +++ /dev/null @@ -1,43 +0,0 @@ -mollie = $mollie; - } - - public function execute(Observer $observer) - { - if (!$observer->hasData('order')) { - return; - } - - /** @var OrderInterface $order */ - $order = $observer->getData('order'); - - if ($order->getPayment()->getData('method') != Paymentlink::CODE) { - return; - } - - $this->mollie->startTransaction($order); - } -} diff --git a/Test/End-2-end/cypress.config.js b/Test/End-2-end/cypress.config.js index 59ed0e4e1b8..aced106db09 100644 --- a/Test/End-2-end/cypress.config.js +++ b/Test/End-2-end/cypress.config.js @@ -17,6 +17,11 @@ module.exports = defineConfig({ require('./cypress/plugins/index.js')(on, config); require('./cypress/plugins/disable-successful-videos.js')(on, config); + // If we're running in CI, we need to set the CI env variable + if (process.env.CI) { + config.env.CI = true + } + // Retrieve available method await new Promise((resolve, reject) => { var https = require('follow-redirects').https; diff --git a/Test/End-2-end/cypress/e2e/magento/backend/paymentlink.cy.js b/Test/End-2-end/cypress/e2e/magento/backend/paymentlink.cy.js index 02d78ec919e..0904ec7fc4b 100644 --- a/Test/End-2-end/cypress/e2e/magento/backend/paymentlink.cy.js +++ b/Test/End-2-end/cypress/e2e/magento/backend/paymentlink.cy.js @@ -11,45 +11,46 @@ const ordersCreatePage = new OrdersCreatePage(); const cookies = new Cookies(); describe('Placing orders from the backend', () => { - // Skipped for now as it keeps failing on CI for unknown reasons. - it.skip('C895380: Validate that the ecommerce admin can submit an order in the backend and mark as "Paid" ', () => { - cy.backendLogin(); + // This fails in CI, but works locally. Not sure why. + if (!Cypress.env('CI')) { + it('C895380: Validate that the ecommerce admin can submit an order in the backend and mark as "Paid" ', () => { + cy.backendLogin(); - ordersCreatePage.createNewOrderFor('Veronica Costello'); + ordersCreatePage.createNewOrderFor('Veronica Costello'); - ordersCreatePage.addFirstSimpleProduct(); + ordersCreatePage.addFirstSimpleProduct(); - ordersCreatePage.selectShippingMethod('Fixed'); + ordersCreatePage.selectShippingMethod('Fixed'); - // 2.3.7 needs a double click to select the payment method, not sure why. - cy.get('[for="p_method_mollie_methods_paymentlink"]').click().click(); + // 2.3.7 needs a double click to select the payment method, not sure why. + cy.get('[for="p_method_mollie_methods_paymentlink"]').click().click(); - cy.get('#mollie_methods_paymentlink_methods').select([ - 'banktransfer', - 'creditcard', - 'ideal', - ]); + cy.get('#mollie_methods_paymentlink_methods').select([ + 'banktransfer', + 'creditcard', + 'ideal', + ]); - cookies.disableSameSiteCookieRestrictions(); + cookies.disableSameSiteCookieRestrictions(); - ordersCreatePage.submitOrder(); + ordersCreatePage.submitOrder(); - cy.get('.mollie-checkout-url .mollie-copy-url') - .invoke('attr', 'data-url') - .then(href => { - cy.visit(href); - }); + cy.get('.mollie-checkout-url .mollie-copy-url') + .invoke('attr', 'data-url') + .then(href => { + cy.visit(href); + }); - mollieHostedPaymentPage.selectPaymentMethod('iDEAL'); - mollieHostedPaymentPage.selectFirstIssuer(); - mollieHostedPaymentPage.selectStatus('paid'); + mollieHostedPaymentPage.selectPaymentMethod('Overboeking'); + mollieHostedPaymentPage.selectStatus('paid'); - checkoutSuccessPage.assertThatOrderSuccessPageIsShown(); + checkoutSuccessPage.assertThatOrderSuccessPageIsShown(); - cy.get('@order-id').then((orderId) => { - ordersPage.openOrderById(orderId); - }); + cy.get('@order-id').then((orderId) => { + ordersPage.openOrderById(orderId); + }); - ordersPage.assertOrderStatusIs('Processing'); - }); + ordersPage.assertOrderStatusIs('Processing'); + }); + } }); diff --git a/Test/End-2-end/cypress/support/pages/backend/OrdersCreatePage.js b/Test/End-2-end/cypress/support/pages/backend/OrdersCreatePage.js index 8c7d1ceafbe..984cd4664a1 100644 --- a/Test/End-2-end/cypress/support/pages/backend/OrdersCreatePage.js +++ b/Test/End-2-end/cypress/support/pages/backend/OrdersCreatePage.js @@ -6,12 +6,23 @@ export default class OrdersCreatePage { cy.contains('Create New Order').click(); - cy.contains(customerName).click(); + cy.get('#sales_order_create_customer_grid_table').contains(customerName).should('be.visible').click(); cy.wait('@header-block'); cy.get('.loader').should('not.exist'); + cy.get('.page-wrapper').then(element => { + cy.log(element.find('#order-store-selector')); + + if (element.find('#order-store-selector').is(':visible')) { + cy.get('.tree-store-scope') + .contains('Default Store View') + .should('be.visible') + .click(); + } + }) + cy.contains('Address Information').should('be.visible'); } diff --git a/Test/Integration/Controller/Checkout/PaymentLinkTest.php b/Test/Integration/Controller/Checkout/PaymentLinkTest.php new file mode 100644 index 00000000000..db11814da69 --- /dev/null +++ b/Test/Integration/Controller/Checkout/PaymentLinkTest.php @@ -0,0 +1,75 @@ +dispatch('mollie/checkout/paymentLink'); + + $this->assertSame(400, $this->getResponse()->getHttpResponseCode()); + } + + public function testThrowsErrorWhenDecodingIsEmpty(): void + { + $this->dispatch('mollie/checkout/paymentLink/order/999'); + + $this->assertSame(404, $this->getResponse()->getHttpResponseCode()); + } + + public function testThrowsErrorWhenOrderIsInvalid(): void + { + // OTk5 = an order id (999) but encrypted + $this->dispatch('mollie/checkout/paymentLink/order/OTk5'); + + $this->assertSame(404, $this->getResponse()->getHttpResponseCode()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + * @return void + */ + public function testRedirectsToMollieWhenTheInputIsValid(): void + { + $mollieMock = $this->createMock(Mollie::class); + $mollieMock->method('startTransaction')->willReturn('https://www.example.com'); + $this->_objectManager->addSharedInstance($mollieMock, Mollie::class); + + $order = $this->_objectManager->create(Order::class)->loadByIncrementId('100000001'); + $key = $this->_objectManager->get(EncryptorInterface::class)->encrypt($order->getId()); + + $this->dispatch('mollie/checkout/paymentLink/order/' . base64_encode($key)); + + $this->assertSame(302, $this->getResponse()->getHttpResponseCode()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + * @return void + */ + public function testRedirectsToTheHomepageWhenAlreadyPaid(): void + { + $order = $this->_objectManager->create(Order::class)->loadByIncrementId('100000001'); + $order->setState(Order::STATE_PROCESSING); + + $key = $this->_objectManager->get(EncryptorInterface::class)->encrypt($order->getId()); + + $this->dispatch('mollie/checkout/paymentLink/order/' . base64_encode($key)); + + $response = $this->getResponse(); + $this->assertSame(302, $response->getHttpResponseCode()); + $this->assertSame('/', $response->getHeader('Location')->getUri()); + } +} diff --git a/Test/Integration/Observer/CheckoutSubmitAllAfter/StartTransactionForPaymentLinkOrdersTest.php b/Test/Integration/Observer/CheckoutSubmitAllAfter/StartTransactionForPaymentLinkOrdersTest.php deleted file mode 100644 index 78fff6a7045..00000000000 --- a/Test/Integration/Observer/CheckoutSubmitAllAfter/StartTransactionForPaymentLinkOrdersTest.php +++ /dev/null @@ -1,61 +0,0 @@ -createMock(Mollie::class); - $mollieMock->expects($this->never())->method('startTransaction'); - - $order = $this->loadOrderById('100000001'); - $payment = $order->getPayment(); - $payment->setMethod('mollie_methods_ideal'); - - $observer = $this->objectManager->create(Observer::class); - $observer->setData('order', $order); - - /** @var StartTransactionForPaymentLinkOrders $instance */ - $instance = $this->objectManager->create(StartTransactionForPaymentLinkOrders::class, [ - 'mollie' => $mollieMock, - ]); - - $instance->execute($observer); - } - - /** - * @magentoDataFixture Magento/Sales/_files/order.php - */ - public function testStartTransactionForPaymentLinkOrders() - { - $mollieMock = $this->createMock(Mollie::class); - $mollieMock->expects($this->once())->method('startTransaction'); - - $order = $this->loadOrderById('100000001'); - $payment = $order->getPayment(); - $payment->setMethod('mollie_methods_paymentlink'); - - $observer = $this->objectManager->create(Observer::class); - $observer->setData('order', $order); - - /** @var StartTransactionForPaymentLinkOrders $instance */ - $instance = $this->objectManager->create(StartTransactionForPaymentLinkOrders::class, [ - 'mollie' => $mollieMock, - ]); - - $instance->execute($observer); - } -} diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index 43c49dd54e7..904793d0a33 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -53,4 +53,10 @@ + + + + Magento\Framework\Url + + diff --git a/etc/events.xml b/etc/events.xml index 9ceb643617f..1b45d8f4f24 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -28,7 +28,6 @@ - diff --git a/i18n/de_DE.csv b/i18n/de_DE.csv index ffc04215f39..50bb89aa8bd 100644 --- a/i18n/de_DE.csv +++ b/i18n/de_DE.csv @@ -392,3 +392,4 @@ ending,Ende "Encrypt payment details","Zahlungsdetails verschlüsseln" "Send an e-mail to customers with a failed or unfinished payment to give them a second chance on finishing the payment through the PaymentLink and revive their order.
You can either send these payment reminders manually or activate the e-mail fully automated.","Senden Sie eine E-Mail an Kunden mit einer fehlgeschlagenen oder unvollständigen Zahlung, um ihnen eine zweite Chance zu geben, die Zahlung über den PaymentLink abzuschließen und ihre Bestellung wiederzubeleben.
Sie können diese Zahlungserinnerungen entweder manuellsenden oder die E-Mail vollständig automatisiert aktivieren." "Payment Method To Use For Second Change Payments","Zahlungsmethode für Zahlungen bei zweiter Änderung verwenden" +"Your order has already been paid.","Ihre Bestellung wurde bereits bezahlt." diff --git a/i18n/en_US.csv b/i18n/en_US.csv index bf91ace9770..7e585d121e3 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -369,3 +369,4 @@ ending,ending "Encrypt payment details","Encrypt payment details" "Send an e-mail to customers with a failed or unfinished payment to give them a second chance on finishing the payment through the PaymentLink and revive their order.
You can either send these payment reminders manually or activate the e-mail fully automated.","Send an e-mail to customers with a failed or unfinished payment to give them a second chance on finishing the payment through the PaymentLink and revive their order.
You can either send these payment reminders manually or activate the e-mail fully automated." "Payment Method To Use For Second Change Payments","Payment Method To Use For Second Change Payments" +"Your order has already been paid.","Your order has already been paid." diff --git a/i18n/es_ES.csv b/i18n/es_ES.csv index b47786c5312..6748c810298 100644 --- a/i18n/es_ES.csv +++ b/i18n/es_ES.csv @@ -392,3 +392,4 @@ ending,finalizando "Encrypt payment details","Cifrar detalles de pago" "Send an e-mail to customers with a failed or unfinished payment to give them a second chance on finishing the payment through the PaymentLink and revive their order.
You can either send these payment reminders manually or activate the e-mail fully automated.","Envíe un correo electrónico a los clientes con un pago fallido o inconcluso para darles una segunda oportunidad de finalizar el pago a través del enlace de pago y revivir su pedido.
Puede enviar estos recordatorios de pago manualmente o activar el correo electrónico completamente automatizado." "Payment Method To Use For Second Change Payments","Método de pago para usar en pagos de segunda oportunidad" +"Your order has already been paid.","Su pedido ya ha sido pagado." diff --git a/i18n/fr_FR.csv b/i18n/fr_FR.csv index 4a68eba30eb..3b3df5b3159 100644 --- a/i18n/fr_FR.csv +++ b/i18n/fr_FR.csv @@ -392,3 +392,4 @@ ending,fin "Encrypt payment details","Chiffrer les détails du paiement" "Send an e-mail to customers with a failed or unfinished payment to give them a second chance on finishing the payment through the PaymentLink and revive their order.
You can either send these payment reminders manually or activate the e-mail fully automated.","Envoyez un e-mail aux clients ayant échoué ou n'ayant pas terminé le paiement pour leur donner une seconde chance de finaliser le paiement via le PaymentLink et de relancer leur commande.
Vous pouvez envoyer ces rappels de paiement manuellement ou activer l'e-mail entièrement automatisé." "Payment Method To Use For Second Change Payments","Méthode de paiement à utiliser pour les paiements de seconde chance" +"Your order has already been paid.","Votre commande a déjà été payée." diff --git a/i18n/nl_NL.csv b/i18n/nl_NL.csv index 81ccf595230..6a841376758 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -394,3 +394,4 @@ ending,eindigend "Encrypt payment details","Betaalgegevens versleutelen" "Send an e-mail to customers with a failed or unfinished payment to give them a second chance on finishing the payment through the PaymentLink and revive their order.
You can either send these payment reminders manually or activate the e-mail fully automated.","Stuur een e-mail naar klanten met een mislukte of onvoltooide betaling om hen een tweede kans te geven de betaling te voltooien via de PaymentLink en hun bestelling te herstellen.
U kunt deze betalingsherinneringen handmatig verzenden of de e-mail volledig geautomatiseerd activeren." "Payment Method To Use For Second Change Payments","Betaalmethode te gebruiken voor tweede kans betalingen" +"Your order has already been paid.","Uw bestelling is al betaald." diff --git a/view/adminhtml/templates/info/mollie_paymentlink.phtml b/view/adminhtml/templates/info/mollie_paymentlink.phtml index 25567ab3375..1b89946138b 100644 --- a/view/adminhtml/templates/info/mollie_paymentlink.phtml +++ b/view/adminhtml/templates/info/mollie_paymentlink.phtml @@ -23,12 +23,12 @@ $status = $block->getPaymentStatus(); getCheckoutType()); ?> - getCheckoutUrl() && $status == 'created'): ?> + - getCheckoutUrl(); ?> - + here to pay', $block->getPaymentLinkUrl()); ?> + diff --git a/view/frontend/templates/info/mollie_paymentlink.phtml b/view/frontend/templates/info/mollie_paymentlink.phtml index aabaf34b560..3ee59cdfe51 100644 --- a/view/frontend/templates/info/mollie_paymentlink.phtml +++ b/view/frontend/templates/info/mollie_paymentlink.phtml @@ -17,12 +17,9 @@ $title = $block->escapeHtml($block->getMethod()->getTitle()); - getPaymentStatus(), ['created', 'open']) && - $paymentUrl = $block->getPaymentLink($block->getMethod()->getStore()) - ): ?> + getPaymentLink()): ?>
- +