diff --git a/.github/workflows/templates/e2e/Dockerfile b/.github/workflows/templates/e2e/Dockerfile index b9bf86b6530..ac3be1a6c1f 100644 --- a/.github/workflows/templates/e2e/Dockerfile +++ b/.github/workflows/templates/e2e/Dockerfile @@ -1,4 +1,4 @@ -FROM cypress/included:12.1.0 +FROM cypress/included:13.6.2 WORKDIR /e2e 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 26316a10ede..dbd9ded1967 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 28aa5cb145c..0ab4c850847 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; @@ -27,7 +32,7 @@ module.exports = defineConfig({ const query = ` query { - molliePaymentMethods(input:{amount:100, currency:null}) { + molliePaymentMethods(input:{amount:50, currency:"EUR"}) { methods { code image 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 807893a5d71..4659350dcea 100644 --- a/i18n/de_DE.csv +++ b/i18n/de_DE.csv @@ -393,3 +393,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 3b7e3d1a42d..3da26903d4d 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -370,3 +370,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 b0105ab956b..f6e00ae3a0a 100644 --- a/i18n/es_ES.csv +++ b/i18n/es_ES.csv @@ -393,3 +393,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 cb5c5da4edc..494000d05f3 100644 --- a/i18n/fr_FR.csv +++ b/i18n/fr_FR.csv @@ -393,3 +393,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 9baea213dba..55b1f8368c4 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -395,3 +395,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()): ?>
- +