diff --git a/.github/workflows/end-2-end-test.yml b/.github/workflows/end-2-end-test.yml index 09d8806bb19..d9a1c9f8c30 100644 --- a/.github/workflows/end-2-end-test.yml +++ b/.github/workflows/end-2-end-test.yml @@ -73,9 +73,9 @@ jobs: - name: Start the Magento container run: | openssl req -x509 -newkey rsa:4096 -keyout .github/workflows/templates/nginx-proxy/magento.test.key -out .github/workflows/templates/nginx-proxy/magento.test.crt -days 365 -nodes -subj "/CN=magento.test" && \ - docker-compose -f .github/workflows/templates/docker-compose.yml up -d + docker compose -f .github/workflows/templates/docker-compose.yml up -d # Get the URL from ngrok - docker-compose -f .github/workflows/templates/docker-compose.yml logs ngrok + docker compose -f .github/workflows/templates/docker-compose.yml logs ngrok MAGENTO_URL=$(docker exec magento-project-community-edition /bin/bash -c "curl -s ngrok:4040/api/tunnels |jq -r \".tunnels[0].public_url\"") echo "magento_url=$MAGENTO_URL" >> $GITHUB_ENV @@ -118,9 +118,9 @@ jobs: - name: Dump docker-compose logs if: always() run: | - docker-compose -f .github/workflows/templates/docker-compose.yml logs magento > magento.log && \ - docker-compose -f .github/workflows/templates/docker-compose.yml logs ngrok > ngrok.log && \ - docker-compose -f .github/workflows/templates/docker-compose.yml logs e2e > e2e.log + docker compose -f .github/workflows/templates/docker-compose.yml logs magento > magento.log && \ + docker compose -f .github/workflows/templates/docker-compose.yml logs ngrok > ngrok.log && \ + docker compose -f .github/workflows/templates/docker-compose.yml logs e2e > e2e.log - name: Upload artifacts uses: actions/upload-artifact@v3 diff --git a/Controller/ApplePay/PlaceOrder.php b/Controller/ApplePay/PlaceOrder.php index 6fa59109534..835705e62a9 100644 --- a/Controller/ApplePay/PlaceOrder.php +++ b/Controller/ApplePay/PlaceOrder.php @@ -86,13 +86,16 @@ public function execute() $cart->setCustomerEmail($this->getRequest()->getParam('shippingAddress')['emailAddress']); - $shippingAddress->setShippingMethod( - str_replace( - '__SPLIT__', - '_', - $this->getRequest()->getParam('shippingMethod')['identifier'] - ) - ); + // Orders with digital products can't have a shipping method + if ($this->getRequest()->getParam('shippingMethod')) { + $shippingAddress->setShippingMethod( + str_replace( + '__SPLIT__', + '_', + $this->getRequest()->getParam('shippingMethod')['identifier'] + ) + ); + } $cart->setPaymentMethod('mollie_methods_applepay'); $cart->setCustomerIsGuest(true); diff --git a/Model/Client/Orders/Processors/SuccessfulPayment.php b/Model/Client/Orders/Processors/SuccessfulPayment.php index c6f9a16d329..e4108bdf72a 100644 --- a/Model/Client/Orders/Processors/SuccessfulPayment.php +++ b/Model/Client/Orders/Processors/SuccessfulPayment.php @@ -104,12 +104,12 @@ public function __construct( } /** - * @param OrderInterface|\Magento\Sales\Model\Order $order + * @param OrderInterface|MagentoOrder $order * @param MollieOrder $mollieOrder * @param string $type * @param ProcessTransactionResponse $response - * @throws \Magento\Framework\Exception\LocalizedException * @return ProcessTransactionResponse|null + * @throws \Magento\Framework\Exception\LocalizedException */ public function process(OrderInterface $order, Order $mollieOrder, string $type, ProcessTransactionResponse $response): ?ProcessTransactionResponse { @@ -173,7 +173,9 @@ private function handleWebhookCall(OrderInterface $order, MollieOrder $mollieOrd $payment->setTransactionId($paymentId); $payment->setCurrencyCode($order->getBaseCurrencyCode()); - if ($order->getState() != \Magento\Sales\Model\Order::STATE_PROCESSING && $mollieOrder->isPaid()) { + if (!in_array($order->getState(), [MagentoOrder::STATE_PROCESSING, MagentoOrder::STATE_COMPLETE]) && + $mollieOrder->isPaid() + ) { $payment->setIsTransactionClosed(true); $payment->registerCaptureNotification($order->getBaseGrandTotal(), true); } @@ -230,7 +232,7 @@ public function checkCheckoutSession( } /** - * @param OrderInterface|\Magento\Sales\Model\Order $order + * @param OrderInterface|MagentoOrder $order */ protected function sendOrderEmails(OrderInterface $order): void { diff --git a/Model/Client/Payments.php b/Model/Client/Payments.php index 1070a0d5a52..e3dade8f7c4 100644 --- a/Model/Client/Payments.php +++ b/Model/Client/Payments.php @@ -406,7 +406,9 @@ public function processTransaction(Order $order, $mollieApi, $type = 'webhook', ); } - $payment->registerCaptureNotification($order->getBaseGrandTotal(), true); + if (!in_array($order->getState(), [Order::STATE_PROCESSING, Order::STATE_COMPLETE])) { + $payment->registerCaptureNotification($order->getBaseGrandTotal(), true); + } } $order->setState(Order::STATE_PROCESSING); diff --git a/Service/Magento/PaymentLinkRedirect.php b/Service/Magento/PaymentLinkRedirect.php index dfa0d553619..1be8f7e05aa 100644 --- a/Service/Magento/PaymentLinkRedirect.php +++ b/Service/Magento/PaymentLinkRedirect.php @@ -75,7 +75,7 @@ public function execute(string $orderId): PaymentLinkRedirectResult ]); } - if ($this->isPaymentLinkExpired->execute($order) || !$order->canReorder()) { + if ($this->isPaymentLinkExpired->execute($order)) { return $this->paymentLinkRedirectResultFactory->create([ 'redirectUrl' => null, 'isExpired' => true, diff --git a/Service/Mollie/GetMollieStatusResult.php b/Service/Mollie/GetMollieStatusResult.php index 6daabc13a0d..cd421db4c62 100644 --- a/Service/Mollie/GetMollieStatusResult.php +++ b/Service/Mollie/GetMollieStatusResult.php @@ -50,6 +50,7 @@ public function shouldRedirectToSuccessPage(): bool 'pending', 'paid', 'authorized', + 'shipping', // When having free or virtual products orders might go into shipping status real quick 'completed', // Completed is mainly to support digital products ]); } diff --git a/Service/Mollie/Order/IsPaymentLinkExpired.php b/Service/Mollie/Order/IsPaymentLinkExpired.php index befb412cf1f..e7e443ac070 100644 --- a/Service/Mollie/Order/IsPaymentLinkExpired.php +++ b/Service/Mollie/Order/IsPaymentLinkExpired.php @@ -40,8 +40,8 @@ public function __construct( public function execute(OrderInterface $order): bool { - $methodCode = $this->methodCode->execute($order); - $this->methodCode->getExpiresAtMethod(); + $this->methodCode->execute($order); + $methodCode = $this->methodCode->getExpiresAtMethod(); if (!$this->expires->availableForMethod($methodCode, $order->getStoreId())) { return $this->checkWithDefaultDate($order); } @@ -51,11 +51,15 @@ public function execute(OrderInterface $order): bool return $expiresAt < $order->getCreatedAt(); } + /** + * Default for when no expiry date is set on the chosen method. + */ private function checkWithDefaultDate(OrderInterface $order): bool { - $date = $this->timezone->scopeDate($order->getStoreId()); - $date = $date->add(new \DateInterval('P28D')); + $now = $this->timezone->scopeDate($order->getStoreId()); + $orderDate = $this->timezone->scopeDate($order->getStoreId(), new \DateTime($order->getCreatedAt())); + $diff = $now->diff($orderDate); - return $date->format('Y-m-d H:i:s') < $order->getCreatedAt(); + return $diff->days >= 28; } } diff --git a/Service/Order/MethodCode.php b/Service/Order/MethodCode.php index 30fc7a6f617..f46330225a5 100644 --- a/Service/Order/MethodCode.php +++ b/Service/Order/MethodCode.php @@ -51,6 +51,7 @@ private function paymentLinkMethod(OrderInterface $order): string } if (!is_array($additionalInformation['limited_methods']) || count($additionalInformation['limited_methods']) !== 1) { + $this->expiresAtMethod = 'paymentlink'; return ''; } diff --git a/Test/End-2-end/cypress/e2e/magento/checkout.cy.js b/Test/End-2-end/cypress/e2e/magento/checkout.cy.js index 1c14679dd03..3e17cd95c82 100644 --- a/Test/End-2-end/cypress/e2e/magento/checkout.cy.js +++ b/Test/End-2-end/cypress/e2e/magento/checkout.cy.js @@ -28,7 +28,7 @@ describe('Checkout usage', () => { const availableMethods = Cypress.env('mollie_available_methods'); [ - 'alma', + // 'alma', Disabled, not available in NL 'bancomatpay', 'bancontact', 'banktransfer', diff --git a/Test/Integration/Service/Magento/PaymentLinkRedirectTest.php b/Test/Integration/Service/Magento/PaymentLinkRedirectTest.php new file mode 100644 index 00000000000..860221e9510 --- /dev/null +++ b/Test/Integration/Service/Magento/PaymentLinkRedirectTest.php @@ -0,0 +1,76 @@ +expectException(\Magento\Framework\Exception\NotFoundException::class); + + $encryptor = $this->objectManager->get(EncryptorInterface::class); + $orderId = base64_encode($encryptor->encrypt('random string')); + + /** @var PaymentLinkRedirect $instance */ + $instance = $this->objectManager->create(PaymentLinkRedirect::class); + + $instance->execute($orderId); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + * + * @return void + */ + public function testDoesNotRedirectWhenOrderAlreadyPaid(): void + { + $order = $this->loadOrder('100000001'); + $order->setState(Order::STATE_PROCESSING); + + $encryptor = $this->objectManager->get(EncryptorInterface::class); + $orderId = base64_encode($encryptor->encrypt($order->getEntityId())); + + /** @var PaymentLinkRedirect $instance */ + $instance = $this->objectManager->create(PaymentLinkRedirect::class); + $result = $instance->execute($orderId); + + $this->assertTrue($result->isAlreadyPaid()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + * + * @return void + */ + public function testDoesNotRedirectWhenExpired(): void + { + $orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + + $order = $this->loadOrder('100000001'); + $order->setState(Order::STATE_PENDING_PAYMENT); + $order->setCreatedAt(date('Y-m-d H:i:s', strtotime('-31 days'))); + $orderRepository->save($order); + + $encryptor = $this->objectManager->get(EncryptorInterface::class); + $orderId = base64_encode($encryptor->encrypt($order->getEntityId())); + + /** @var PaymentLinkRedirect $instance */ + $instance = $this->objectManager->create(PaymentLinkRedirect::class); + $result = $instance->execute($orderId); + + $this->assertTrue($result->isExpired()); + } +} diff --git a/Test/Integration/Service/Mollie/Order/IsPaymentLinkExpiredTest.php b/Test/Integration/Service/Mollie/Order/IsPaymentLinkExpiredTest.php index 4efeb9b5522..07509bc17f0 100644 --- a/Test/Integration/Service/Mollie/Order/IsPaymentLinkExpiredTest.php +++ b/Test/Integration/Service/Mollie/Order/IsPaymentLinkExpiredTest.php @@ -25,7 +25,7 @@ public function testIsValidTheDayBeforeTheDefaultExpire(): void $date = new \DateTimeImmutable(); $date = $date->add(new \DateInterval('P28D'))->setTime(0, 0, 0); - $order->setcreatedAt($date->format('Y-m-d H:i:s')); + $order->setCreatedAt($date->format('Y-m-d H:i:s')); $instance = $this->objectManager->create(IsPaymentLinkExpired::class); @@ -42,7 +42,7 @@ public function testIsInvalidTheDayAfterTheDefaultExpire(): void $date = new \DateTimeImmutable(); $date = $date->add(new \DateInterval('P29D'))->setTime(23, 59, 59); - $order->setcreatedAt($date->format('Y-m-d H:i:s')); + $order->setCreatedAt($date->format('Y-m-d H:i:s')); $instance = $this->objectManager->create(IsPaymentLinkExpired::class); @@ -61,7 +61,7 @@ public function testIsValidWhenAvailableForMethodIsSetTheDayBefore(): void $date = new \DateTimeImmutable(); $date = $date->add(new \DateInterval('P9D'))->setTime(0, 0, 0); - $order->setcreatedAt($date->format('Y-m-d H:i:s')); + $order->setCreatedAt($date->format('Y-m-d H:i:s')); $order->getPayment()->setAdditionalInformation(['limited_methods' => ['ideal']]); @@ -82,7 +82,7 @@ public function testIsValidWhenAvailableForMethodIsSetTheDayAfter(): void $date = new \DateTimeImmutable(); $date = $date->add(new \DateInterval('P11D'))->setTime(23, 59, 59); - $order->setcreatedAt($date->format('Y-m-d H:i:s')); + $order->setCreatedAt($date->format('Y-m-d H:i:s')); $order->getPayment()->setAdditionalInformation(['limited_methods' => ['ideal']]); @@ -90,4 +90,29 @@ public function testIsValidWhenAvailableForMethodIsSetTheDayAfter(): void $this->assertTrue($instance->execute($order)); } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + * @magentoConfigFixture default_store payment/mollie_methods_paymentlink/days_before_expire 10 + */ + public function testUsesPaymentlinkForExpiryWhenNoLimitedMethodsAreSet(): void + { + $order = $this->loadOrder('100000001'); + $order->getPayment()->setMethod(Paymentlink::CODE); + $order->getPayment()->setAdditionalInformation(['limited_methods' => null]); + + $instance = $this->objectManager->create(IsPaymentLinkExpired::class); + + $date = new \DateTimeImmutable(); + $date = $date->add(new \DateInterval('P9D'))->setTime(23, 59, 59); + $order->setCreatedAt($date->format('Y-m-d H:i:s')); + + $this->assertFalse($instance->execute($order)); + + $date = new \DateTimeImmutable(); + $date = $date->add(new \DateInterval('P11D'))->setTime(23, 59, 59); + $order->setCreatedAt($date->format('Y-m-d H:i:s')); + + $this->assertTrue($instance->execute($order)); + } } diff --git a/composer.json b/composer.json index 998992b96dd..15a186b6f26 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "mollie/magento2", "description": "Mollie Payment Module for Magento 2", - "version": "2.40.0", + "version": "2.40.1", "keywords": [ "mollie", "payment", diff --git a/etc/config.xml b/etc/config.xml index acd17bef5d6..bfe2ca62d18 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -8,7 +8,7 @@ - v2.40.0 + v2.40.1 0 0 test diff --git a/view/adminhtml/templates/form/mollie_paymentlink_javascript.phtml b/view/adminhtml/templates/form/mollie_paymentlink_javascript.phtml index f99f16fa7ed..7751f2e42d5 100644 --- a/view/adminhtml/templates/form/mollie_paymentlink_javascript.phtml +++ b/view/adminhtml/templates/form/mollie_paymentlink_javascript.phtml @@ -3,30 +3,34 @@ * Copyright Magmodules.eu. All rights reserved. * See COPYING.txt for license details. */ -?> - +'; + +// @phpstan-ignore-next-line +if (isset($secureRenderer)) { + echo $secureRenderer->renderTag('script', [], $scriptString, false); + return; +} + +echo ''; diff --git a/view/adminhtml/templates/order/shipment/create/payment_hold_warning.phtml b/view/adminhtml/templates/order/shipment/create/payment_hold_warning.phtml index 273c7804f62..37485d101a4 100644 --- a/view/adminhtml/templates/order/shipment/create/payment_hold_warning.phtml +++ b/view/adminhtml/templates/order/shipment/create/payment_hold_warning.phtml @@ -1,19 +1,27 @@ +use Magento\Framework\View\Helper\SecureHtmlRenderer; + +/** @var SecureHtmlRenderer $secureRenderer */ +?>
Please note: You are creating a partial shipment, but it's only possible to capture the payment once. Please double-check you are shipping the correct items.
- +'; + +// @phpstan-ignore-next-line +if (isset($secureRenderer)) { + echo $secureRenderer->renderTag('script', [], $scriptString, false); + return; +} + +echo ''; diff --git a/view/adminhtml/templates/system/config/button/apikey.phtml b/view/adminhtml/templates/system/config/button/apikey.phtml index 7a1667f6bba..b4d9290b8f5 100644 --- a/view/adminhtml/templates/system/config/button/apikey.phtml +++ b/view/adminhtml/templates/system/config/button/apikey.phtml @@ -1,49 +1,64 @@ - -getButtonHtml() ?> +'; + +// @phpstan-ignore-next-line +if (isset($secureRenderer)) { + echo $secureRenderer->renderTag('script', [], $scriptString, false); +} + +// @phpstan-ignore-next-line +if (!isset($secureRenderer)) { + echo ''; +} + +echo $block->getButtonHtml(); diff --git a/view/adminhtml/templates/system/config/button/compatibility.phtml b/view/adminhtml/templates/system/config/button/compatibility.phtml index 6d714a1b9dd..6339e380d13 100644 --- a/view/adminhtml/templates/system/config/button/compatibility.phtml +++ b/view/adminhtml/templates/system/config/button/compatibility.phtml @@ -1,50 +1,65 @@ - -getButtonHtml() ?> \ No newline at end of file +'; + +// @phpstan-ignore-next-line +if (isset($secureRenderer)) { + echo $secureRenderer->renderTag('script', [], $scriptString, false); +} + +// @phpstan-ignore-next-line +if (!isset($secureRenderer)) { + echo ''; +} + +echo $block->getButtonHtml();