diff --git a/.github/workflows/end-2-end-test.yml b/.github/workflows/end-2-end-test.yml index 43455ac0f36..14ff4a960e1 100644 --- a/.github/workflows/end-2-end-test.yml +++ b/.github/workflows/end-2-end-test.yml @@ -76,10 +76,12 @@ jobs: 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 + # Note the `mollie-pwa.html` file, as it is copied to the pub folder. This is so that it can be accessed by Cypress. - name: Upload the code into the docker container run: | sed -i '/version/d' ./composer.json && \ docker cp $(pwd) magento-project-community-edition:/data/extensions/ && \ + docker cp $(pwd)/Test/End-2-end/cypress/fixtures/mollie-pwa.html magento-project-community-edition:/data/pub/opt/ && \ docker exec magento-project-community-edition ./install-composer-package mollie/magento2:@dev - name: Activate the extension diff --git a/.github/workflows/templates/e2e/Dockerfile b/.github/workflows/templates/e2e/Dockerfile index 2136acaf2c3..b9bf86b6530 100644 --- a/.github/workflows/templates/e2e/Dockerfile +++ b/.github/workflows/templates/e2e/Dockerfile @@ -2,6 +2,7 @@ FROM cypress/included:12.1.0 WORKDIR /e2e +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN npm i @cypress/webpack-preprocessor cypress-mollie cypress-testrail --save-dev CMD tail -f /dev/null diff --git a/Config.php b/Config.php index 9fcb87b2724..0a2bf109633 100644 --- a/Config.php +++ b/Config.php @@ -58,6 +58,7 @@ class Config const PAYMENT_CREDITCARD_USE_COMPONENTS = 'payment/mollie_methods_creditcard/use_components'; const PAYMENT_CREDITCARD_ENABLE_CUSTOMERS_API = 'payment/mollie_methods_creditcard/enable_customers_api'; const PAYMENT_BANKTRANSFER_STATUS_PENDING = 'payment/mollie_methods_banktransfer/order_status_pending'; + const PAYMENT_METHOD_ISSUER_LIST_TYPE = 'payment/mollie_methods_%s/issuer_list_type'; const PAYMENT_METHOD_PAYMENT_ACTIVE = 'payment/mollie_methods_%s/active'; const PAYMENT_METHOD_PAYMENT_DESCRIPTION = 'payment/mollie_methods_%s/payment_description'; const PAYMENT_METHOD_PAYMENT_SURCHARGE_FIXED_AMOUNT = 'payment/mollie_methods_%s/payment_surcharge_fixed_amount'; @@ -73,6 +74,7 @@ class Config const PAYMENT_VOUCHER_CUSTOM_ATTRIBUTE = 'payment/mollie_methods_voucher/custom_attribute'; const CURRENCY_OPTIONS_DEFAULT = 'currency/options/default'; + /** * @var ScopeConfigInterface */ @@ -726,6 +728,20 @@ public function encryptPaymentDetails($storeId = null): bool return $this->isSetFlag(static::GENERAL_ENCRYPT_PAYMENT_DETAILS, $storeId); } + /** + * @param string $method + * @param null|int|string $storeId + * + * @return string + */ + public function getIssuerListType(string $method, $storeId = null): string + { + return $this->getPath( + $this->addMethodToPath(static::PAYMENT_METHOD_ISSUER_LIST_TYPE, $method), + $storeId + ) ?? 'none'; + } + /** * @param $method * @return string diff --git a/Controller/ApplePay/ShippingMethods.php b/Controller/ApplePay/ShippingMethods.php index 6d4377f3dfe..f65b04b9da7 100644 --- a/Controller/ApplePay/ShippingMethods.php +++ b/Controller/ApplePay/ShippingMethods.php @@ -11,13 +11,10 @@ use Magento\Framework\App\Action\Context; use Magento\Framework\Controller\ResultFactory; use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Quote\Api\Data\AddressInterfaceFactory; use Magento\Quote\Api\Data\CartInterface; -use Magento\Quote\Api\Data\PaymentInterface; -use Magento\Quote\Api\Data\PaymentInterfaceFactory; use Magento\Quote\Api\GuestCartRepositoryInterface; -use Magento\Quote\Api\PaymentMethodManagementInterface; use Magento\Quote\Api\ShippingMethodManagementInterface; +use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Total as AddressTotal; class ShippingMethods extends Action @@ -33,47 +30,25 @@ class ShippingMethods extends Action private $guestCartRepository; /** - * @var AddressInterfaceFactory - */ - private $addressFactory; - - /** - * @var PaymentMethodManagementInterface - */ - private $paymentMethodManagement; - - /** - * @var PaymentInterfaceFactory + * @var ShippingMethodManagementInterface */ - private $paymentInterfaceFactory; + private $shippingMethodManagement; /** * @var CheckoutSession */ private $checkoutSession; - /** - * @var ShippingMethodManagementInterface - */ - private $shippingMethodManagement; - public function __construct( Context $context, CartRepositoryInterface $cartRepository, - GuestCartRepositoryInterface $guestCartRepository, ShippingMethodManagementInterface $shippingMethodManagement, - AddressInterfaceFactory $addressFactory, - PaymentMethodManagementInterface $paymentMethodManagement, - PaymentInterfaceFactory $paymentInterfaceFactory, - CheckoutSession $checkoutSession + CheckoutSession $checkoutSession, + GuestCartRepositoryInterface $guestCartRepository ) { parent::__construct($context); - - $this->guestCartRepository = $guestCartRepository; $this->shippingMethodManagement = $shippingMethodManagement; - $this->addressFactory = $addressFactory; - $this->paymentMethodManagement = $paymentMethodManagement; - $this->paymentInterfaceFactory = $paymentInterfaceFactory; + $this->guestCartRepository = $guestCartRepository; $this->cartRepository = $cartRepository; $this->checkoutSession = $checkoutSession; } @@ -82,28 +57,25 @@ public function execute() { $cart = $this->getCart(); - $address = $this->addressFactory->create(); + /** + * @var Address $address + */ + $address = $cart->getShippingAddress(); + $address->setData(null); $address->setCountryId($this->getRequest()->getParam('countryCode')); $address->setPostcode($this->getRequest()->getParam('postalCode')); - $cart->setShippingAddress($address); - - $cart->collectTotals(); - $this->cartRepository->save($cart); - if ($this->getRequest()->getParam('shippingMethod')) { - $this->addShippingMethod($cart, $this->getRequest()->getParam('shippingMethod')['identifier']); + $address->setCollectShippingRates(true); + $address->setShippingMethod($this->getRequest()->getParam('shippingMethod')['identifier']); } - $methods = $this->shippingMethodManagement->getList($cart->getId()); - $this->setDefaultShippingMethod($cart, $methods); - - /** @var PaymentInterface $payment */ - $payment = $this->paymentInterfaceFactory->create(); - $payment->setMethod('mollie_methods_applepay'); - $this->paymentMethodManagement->set($cart->getId(), $payment); - $cart = $this->cartRepository->get($cart->getId()); + $cart->setPaymentMethod('mollie_methods_applepay'); + $cart->getPayment()->importData(['method' => 'mollie_methods_applepay']); + $this->cartRepository->save($cart); + $cart->collectTotals(); + $methods = $this->shippingMethodManagement->getList($cart->getId()); $response = $this->resultFactory->create(ResultFactory::TYPE_JSON); return $response->setData([ @@ -118,6 +90,7 @@ public function execute() 'totals' => array_map(function (AddressTotal $total) { return [ 'type' => 'final', + 'code' => $total->getCode(), 'label' => $total->getData('title'), 'amount' => number_format($total->getData('value'), 2, '.', ''), ]; @@ -125,36 +98,6 @@ public function execute() ]); } - /** - * @param CartInterface $cart - * @param \Magento\Quote\Api\Data\ShippingMethodInterface[] $methods - */ - private function setDefaultShippingMethod(CartInterface $cart, array $methods) - { - if ($cart->getShippingAddress()->getShippingMethod()) { - return; - } - - $method = array_shift($methods); - if (!$method) { - return; - } - - $this->addShippingMethod($cart, $method->getCarrierCode() . '_' . $method->getMethodCode()); - $this->cartRepository->save($cart); - } - - private function addShippingMethod(CartInterface $cart, string $identifier) - { - $address = $cart->getShippingAddress(); - - $address->setShippingMethod($identifier); - $address->setCollectShippingRates(true); - $address->save(); - - $address->collectShippingRates(); - } - /** * @throws \Magento\Framework\Exception\NoSuchEntityException * @return CartInterface diff --git a/Helper/General.php b/Helper/General.php index 06945c89d3b..36683d2556e 100755 --- a/Helper/General.php +++ b/Helper/General.php @@ -380,6 +380,8 @@ public function useImage($storeId = null) } /** + * @deprecated See \Mollie\Payment\Config::getIssuerListType instead + * * @param string $method * * @return mixed @@ -387,7 +389,7 @@ public function useImage($storeId = null) public function getIssuerListType(string $method): string { $methodXpath = str_replace('%method%', $method, self::XPATH_ISSUER_LIST_TYPE); - return $this->getStoreConfig($methodXpath); + return $this->getStoreConfig($methodXpath) ?? 'none'; } /** diff --git a/Model/MollieConfigProvider.php b/Model/MollieConfigProvider.php index bb9797195e6..3efe02cc6ad 100644 --- a/Model/MollieConfigProvider.php +++ b/Model/MollieConfigProvider.php @@ -263,7 +263,7 @@ public function getActiveMethods(MollieApiClient $mollieApi, CartInterface $cart */ private function getIssuers(MollieApiClient $mollieApi, string $code, array $config): array { - $issuerListType = $this->mollieHelper->getIssuerListType($code); + $issuerListType = $this->config->getIssuerListType($code, $this->storeManager->getStore()->getId()); $config['payment']['issuersListType'][$code] = $issuerListType; $config['payment']['issuers'][$code] = $this->getIssuers->execute($mollieApi, $code, $issuerListType); diff --git a/Model/PaymentFee/Quote/Address/Total/PaymentFeeTax.php b/Model/PaymentFee/Quote/Address/Total/PaymentFeeTax.php index 1e6f0d8afcb..b4772d7947c 100644 --- a/Model/PaymentFee/Quote/Address/Total/PaymentFeeTax.php +++ b/Model/PaymentFee/Quote/Address/Total/PaymentFeeTax.php @@ -6,25 +6,30 @@ namespace Mollie\Payment\Model\PaymentFee\Quote\Address\Total; +use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement; +use Magento\Customer\Api\Data\AddressInterfaceFactory as CustomerAddressFactory; +use Magento\Customer\Api\Data\RegionInterfaceFactory as CustomerAddressRegionFactory; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Quote\Api\Data\ShippingAssignmentInterface; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address\Total; use Magento\Quote\Model\Quote\Address\Total\AbstractTotal; +use Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory; +use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterfaceFactory; +use Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory; +use Magento\Tax\Api\Data\TaxClassKeyInterface; +use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory; +use Magento\Tax\Api\TaxCalculationInterface; +use Magento\Tax\Helper\Data as TaxHelper; +use Magento\Tax\Model\Config as TaxConfig; use Magento\Tax\Model\Sales\Total\Quote\CommonTaxCollector; use Mollie\Payment\Config; use Mollie\Payment\Exceptions\UnknownPaymentFeeType; -use Mollie\Payment\Service\Config\PaymentFee as PaymentFeeConfig; use Mollie\Payment\Service\PaymentFee\Calculate; use Mollie\Payment\Service\PaymentFee\Result; -class PaymentFeeTax extends AbstractTotal +class PaymentFeeTax extends CommonTaxCollector { - /** - * @var PaymentFeeConfig - */ - private $paymentFeeConfig; - /** * @var PriceCurrencyInterface */ @@ -41,14 +46,55 @@ class PaymentFeeTax extends AbstractTotal private $config; public function __construct( - PaymentFeeConfig $paymentFeeConfig, - PriceCurrencyInterface $priceCurrency, + TaxConfig $taxConfig, + TaxCalculationInterface $taxCalculationService, + QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory, + QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory, + TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory, + CustomerAddressFactory $customerAddressFactory, + CustomerAddressRegionFactory $customerAddressRegionFactory, Calculate $calculate, - Config $config + PriceCurrencyInterface $priceCurrency, + Config $config, + TaxHelper $taxHelper = null, + QuoteDetailsItemExtensionInterfaceFactory $quoteDetailsItemExtensionInterfaceFactory = null, + ?CustomerAccountManagement $customerAccountManagement = null ) { - $this->paymentFeeConfig = $paymentFeeConfig; - $this->priceCurrency = $priceCurrency; + $parent = new \ReflectionClass(parent::class); + $parentConstructor = $parent->getConstructor(); + + // The parent call fails when running setup:di:compile in 2.4.3 and lower due to an extra parameter. + if ($parentConstructor->getNumberOfParameters() == 9) { + // @phpstan-ignore-next-line + parent::__construct( + $taxConfig, + $taxCalculationService, + $quoteDetailsDataObjectFactory, + $quoteDetailsItemDataObjectFactory, + $taxClassKeyDataObjectFactory, + $customerAddressFactory, + $customerAddressRegionFactory, + $taxHelper, + $quoteDetailsItemExtensionInterfaceFactory + ); + } else { + // @phpstan-ignore-next-line + parent::__construct( + $taxConfig, + $taxCalculationService, + $quoteDetailsDataObjectFactory, + $quoteDetailsItemDataObjectFactory, + $taxClassKeyDataObjectFactory, + $customerAddressFactory, + $customerAddressRegionFactory, + $taxHelper, + $quoteDetailsItemExtensionInterfaceFactory, + $customerAccountManagement + ); + } + $this->calculate = $calculate; + $this->priceCurrency = $priceCurrency; $this->config = $config; } @@ -74,10 +120,26 @@ public function collect(Quote $quote, ShippingAssignmentInterface $shippingAssig $this->addAssociatedTaxable($shippingAssignment, $result, $quote); + $feeDataObject = $this->quoteDetailsItemDataObjectFactory->create() + ->setType('mollie_payment_fee') + ->setCode('mollie_payment_fee') + ->setQuantity(1); + + $feeDataObject->setUnitPrice($result->getRoundedAmount()); + $feeDataObject->setTaxClassKey( + $this->taxClassKeyDataObjectFactory->create() + ->setType(TaxClassKeyInterface::TYPE_ID) + ->setValue(4) + ); + $feeDataObject->setIsTaxIncluded(true); + + $quoteDetails = $this->prepareQuoteDetails($shippingAssignment, [$feeDataObject]); + + $this->taxCalculationService->calculateTax($quoteDetails, $quote->getStoreId()); + parent::collect($quote, $shippingAssignment, $total); $extensionAttributes = $quote->getExtensionAttributes(); - if (!$extensionAttributes) { return $this; } diff --git a/Plugin/Tax/Helper/DataPlugin.php b/Plugin/Tax/Helper/DataPlugin.php new file mode 100644 index 00000000000..58b90e589d1 --- /dev/null +++ b/Plugin/Tax/Helper/DataPlugin.php @@ -0,0 +1,104 @@ +orderTaxManagement = $orderTaxManagement; + } + + public function afterGetCalculatedTaxes(object $callable, array $result, $source): array + { + if (!$source instanceof InvoiceInterface && + !$source instanceof CreditmemoInterface + ) { + return $result; + } + + $order = $source->getOrder(); + $orderTaxDetails = $this->orderTaxManagement->getOrderTaxDetails($order->getId()); + + $items = array_filter($orderTaxDetails->getItems(), function (Item $item) { + return $item->getType() == 'mollie_payment_fee_tax'; + }); + + if (count($items) === 0) { + return $result; + } + + foreach ($items as $item) { + $result = $this->aggregateTaxes($result, $item, 1); + } + + return $result; + } + + /** + * Copied from \Magento\Tax\Helper\Data::_aggregateTaxes + * + * @param $taxClassAmount + * @param OrderTaxDetailsItemInterface $itemTaxDetail + * @param $ratio + * @return array + */ + private function aggregateTaxes($taxClassAmount, OrderTaxDetailsItemInterface $itemTaxDetail, $ratio) + { + $itemAppliedTaxes = $itemTaxDetail->getAppliedTaxes(); + foreach ($itemAppliedTaxes as $itemAppliedTax) { + $taxAmount = $itemAppliedTax->getAmount() * $ratio; + $baseTaxAmount = $itemAppliedTax->getBaseAmount() * $ratio; + + if (0 == $taxAmount && 0 == $baseTaxAmount) { + continue; + } + $taxCode = $this->getKeyByName($taxClassAmount, $itemAppliedTax->getCode()); + if (!isset($taxClassAmount[$taxCode])) { + $taxClassAmount[$taxCode]['title'] = $itemAppliedTax->getTitle(); + $taxClassAmount[$taxCode]['percent'] = $itemAppliedTax->getPercent(); + $taxClassAmount[$taxCode]['tax_amount'] = $taxAmount; + $taxClassAmount[$taxCode]['base_tax_amount'] = $baseTaxAmount; + } else { + $taxClassAmount[$taxCode]['tax_amount'] += $taxAmount; + $taxClassAmount[$taxCode]['base_tax_amount'] += $baseTaxAmount; + } + } + + return $taxClassAmount; + } + + /** + * @param array $taxClassAmount + * @param string $name + * @return string|int + */ + private function getKeyByName(array $taxClassAmount, string $name) + { + foreach ($taxClassAmount as $key => $tax) { + if ($tax['title'] === $name) { + return $key; + } + } + + return $name; + } +} diff --git a/Service/Mollie/GetIssuers.php b/Service/Mollie/GetIssuers.php index 25a3f0986dc..d9259c5ff71 100644 --- a/Service/Mollie/GetIssuers.php +++ b/Service/Mollie/GetIssuers.php @@ -10,6 +10,7 @@ use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Locale\Resolver; use Mollie\Api\MollieApiClient; +use Mollie\Payment\Config; use Mollie\Payment\Helper\General; use Mollie\Payment\Model\Mollie as MollieModel; @@ -36,24 +37,23 @@ class GetIssuers * @var Resolver */ private $resolver; - /** - * @var General + * @var Config */ - private $general; + private $config; public function __construct( CacheInterface $cache, SerializerInterface $serializer, MollieModel $mollieModel, Resolver $resolver, - General $general + Config $config ) { $this->cache = $cache; $this->serializer = $serializer; $this->mollieModel = $mollieModel; $this->resolver = $resolver; - $this->general = $general; + $this->config = $config; } /** @@ -106,7 +106,7 @@ public function getForGraphql($storeId, string $method): ?array $issuers = $this->execute( $mollieApi, $method, - $this->general->getIssuerListType($method) + $this->config->getIssuerListType($method) ); if (!$issuers) { diff --git a/Test/End-2-end/cypress/e2e/magento/backend/refunds.cy.js b/Test/End-2-end/cypress/e2e/magento/backend/refunds.cy.js new file mode 100644 index 00000000000..a44b2e4c6bb --- /dev/null +++ b/Test/End-2-end/cypress/e2e/magento/backend/refunds.cy.js @@ -0,0 +1,28 @@ +/* + * Copyright Magmodules.eu. All rights reserved. + * See COPYING.txt for license details. + */ + +import InvoiceOverviewPage from "Pages/backend/InvoiceOverviewPage"; +import InvoicePage from "Pages/backend/InvoicePage"; +import CreditMemoPage from "Pages/backend/CreditMemoPage"; +import PlaceOrderComposite from "CompositeActions/PlaceOrderComposite"; + +const invoiceOverviewPage = new InvoiceOverviewPage(); +const invoicePage = new InvoicePage(); +const creditMemoPage = new CreditMemoPage(); +const placeOrderComposite = new PlaceOrderComposite(); + +describe('Check that refunds behave as excepted', () => { + it('Can do a refund on an iDeal order', () => { + placeOrderComposite.placeOrder(); + + cy.get('@order-id').then(orderId => { + invoiceOverviewPage.openByOrderId(orderId); + }); + + invoicePage.creditMemo(); + + creditMemoPage.refund(); + }); +}); diff --git a/Test/End-2-end/cypress/e2e/magento/graphql/place-order.cy.js b/Test/End-2-end/cypress/e2e/magento/graphql/place-order.cy.js new file mode 100644 index 00000000000..b4251d1c255 --- /dev/null +++ b/Test/End-2-end/cypress/e2e/magento/graphql/place-order.cy.js @@ -0,0 +1,50 @@ +/* + * Copyright Magmodules.eu. All rights reserved. + * See COPYING.txt for license details. + */ + +import Cookies from "Services/Cookies"; +import MollieHostedPaymentPage from "Pages/mollie/MollieHostedPaymentPage"; +import CheckoutSuccessPage from "Pages/frontend/CheckoutSuccessPage"; +import OrdersPage from "Pages/backend/OrdersPage"; + +const cookies = new Cookies(); +const mollieHostedPaymentPage = new MollieHostedPaymentPage(); +const checkoutSuccessPage = new CheckoutSuccessPage(); +const ordersPage = new OrdersPage(); + +describe('Check that the headless endpoints work as expected', () => { + it('C1835263: Validate that an order can be placed through GraphQL ', () => { + cy.visit('opt/mollie-pwa.html'); + + cy.get('[data-key="start-checkout-process"]').click(); + + cy.get('[data-key="mollie_methods_ideal"]').click(); + + cy.get('[data-key="mollie_methods_ideal-issuer"]').first().click(); + + cy.get('[data-key="place-order-action"]').click(); + + cy.get('[data-key="increment-id"]').then((element) => { + cy.wrap(element.text()).as('increment-id'); + }); + + cookies.disableSameSiteCookieRestrictions(); + + cy.get('[data-key="redirect-url"]').then((element) => { + cy.visit(element.attr('href')); + }); + + mollieHostedPaymentPage.selectStatus('paid'); + + checkoutSuccessPage.assertThatOrderSuccessPageIsShown(); + + cy.backendLogin(false); + + cy.get('@increment-id').then((incrementId) => { + ordersPage.openByIncrementId(incrementId); + }); + + ordersPage.assertOrderStatusIs('Processing'); + }); +}) diff --git a/Test/End-2-end/cypress/fixtures/mollie-pwa.html b/Test/End-2-end/cypress/fixtures/mollie-pwa.html new file mode 100644 index 00000000000..fcea4e2c5e7 --- /dev/null +++ b/Test/End-2-end/cypress/fixtures/mollie-pwa.html @@ -0,0 +1,834 @@ + + + + +
+
+ + +
+
+
+
Profile ID (optional)
+
+
+ +
+
+ Mode + (Must match payment/mollie_general/type) +
+
+
+ +
+
+ +
+
CurrentStep
+
{{ currentStep }}
+
+ +
+
CartId
+
{{ cartId }}
+
+ +
+ +
+
+
+ + +
+
+
+
+ + +
+
+

Order done!

+
+

Order ID: {{ orderId }}

+

1. Please open this url and select a payment status (or not):

+ {{ redirectUrl }}
+ +

2. When you have opened the url, click here to get the payment status:

+ + +
+ PaymentStatus: {{ paymentStatus }}
+ redirect_to_cart: {{ redirectToCart ? 'true' : 'false' }}
+ redirect_to_success_page: {{ redirectToSuccessPage ? 'true' : 'false' }}
+
+
+
+
+
+
+ +
+
+ + {{ method.title }} + + +
    +
  • + +
  • +
+
+ +
+
+
Card Number
+
+
+
+ +
+
Card Holder
+
+
+
+ +
+
Expiry Date
+
+
+
+ +
+
CVV
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + Continue!? +
+
+
+ + + diff --git a/Test/End-2-end/cypress/support/actions/composite/PlaceOrderComposite.js b/Test/End-2-end/cypress/support/actions/composite/PlaceOrderComposite.js new file mode 100644 index 00000000000..70cea2577e4 --- /dev/null +++ b/Test/End-2-end/cypress/support/actions/composite/PlaceOrderComposite.js @@ -0,0 +1,28 @@ +/* + * Copyright Magmodules.eu. All rights reserved. + * See COPYING.txt for license details. + */ + +import VisitCheckoutPaymentCompositeAction from "CompositeActions/VisitCheckoutPaymentCompositeAction"; +import CheckoutPaymentPage from "Pages/frontend/CheckoutPaymentPage"; +import MollieHostedPaymentPage from "Pages/mollie/MollieHostedPaymentPage"; +import CheckoutSuccessPage from "Pages/frontend/CheckoutSuccessPage"; + +const visitCheckoutPaymentCompositeAction = new VisitCheckoutPaymentCompositeAction(); +const checkoutPaymentsPage = new CheckoutPaymentPage(); +const mollieHostedPaymentPage = new MollieHostedPaymentPage(); +const checkoutSuccessPage = new CheckoutSuccessPage(); + +export default class PlaceOrderComposite { + placeOrder() { + visitCheckoutPaymentCompositeAction.visit(); + + checkoutPaymentsPage.selectPaymentMethod('iDeal'); + checkoutPaymentsPage.selectFirstAvailableIssuer(); + checkoutPaymentsPage.placeOrder(); + + mollieHostedPaymentPage.selectStatus('paid'); + + checkoutSuccessPage.assertThatOrderSuccessPageIsShown(); + } +} diff --git a/Test/End-2-end/cypress/support/pages/backend/CreditMemoPage.js b/Test/End-2-end/cypress/support/pages/backend/CreditMemoPage.js new file mode 100644 index 00000000000..329345f664c --- /dev/null +++ b/Test/End-2-end/cypress/support/pages/backend/CreditMemoPage.js @@ -0,0 +1,16 @@ +/* + * Copyright Magmodules.eu. All rights reserved. + * See COPYING.txt for license details. + */ + +export default class CreditMemoPage { + refund() { + cy.get('.primary.refund').click(); + + cy.url().should('include', 'admin/sales/order/view/order_id/'); + + cy.contains('You created the credit memo.'); + + cy.get('.note-list-comment').contains('We refunded').contains('Transaction ID:'); + } +} diff --git a/Test/End-2-end/cypress/support/pages/backend/InvoiceOverviewPage.js b/Test/End-2-end/cypress/support/pages/backend/InvoiceOverviewPage.js new file mode 100644 index 00000000000..a62a676bc4c --- /dev/null +++ b/Test/End-2-end/cypress/support/pages/backend/InvoiceOverviewPage.js @@ -0,0 +1,47 @@ +/* + * Copyright Magmodules.eu. All rights reserved. + * See COPYING.txt for license details. + */ + +import MagentoRestApi from "Services/MagentoRestApi"; + +const magentoRestApi = new MagentoRestApi(); + +export default class InvoiceOverviewPage { + openByOrderId(orderId) { + const invoices = magentoRestApi.getInvoicesByOrderId(orderId).then(response => { + const entityId = response.items[0].entity_id; + cy.wrap(entityId).as('invoiceId'); + const incrementId = response.items[0].increment_id; + cy.wrap(incrementId).as('invoiceIncrementId'); + }); + + cy.backendLogin(); + + cy.get('[data-ui-id="menu-magento-sales-sales-invoice"] a').click({force: true}); + + cy.url().should('include', 'admin/sales/invoice/index'); + + cy.get('[data-action="grid-filter-expand"]').click(); + + cy.get('.action-clear').click({force: true}); + + cy.get('@invoiceIncrementId').then(invoiceIncrementId => { + cy.get('.admin__form-field-label') + .contains('Invoice') + .parents('.admin__form-field') + .find('input') + .clear() + .type(invoiceIncrementId); + + cy.get('.admin__data-grid-filters-wrap .action-secondary').click(); + + cy.get('.data-grid-cell-content') + .contains(invoiceIncrementId) + .first() + .parents('tr') + .find('a') + .click(); + }); + } +} diff --git a/Test/End-2-end/cypress/support/pages/backend/InvoicePage.js b/Test/End-2-end/cypress/support/pages/backend/InvoicePage.js index fd7e1ac8f81..154836b389a 100644 --- a/Test/End-2-end/cypress/support/pages/backend/InvoicePage.js +++ b/Test/End-2-end/cypress/support/pages/backend/InvoicePage.js @@ -17,4 +17,15 @@ export default class InvoicePage { cy.url().should('include', '/admin/sales/order/view/order_id/'); } + + creditMemo() { + // Last element on the page so javascript has time to load + cy.get('.magento-version').should('be.visible'); + cy.wait(500); + + cy.get('.credit-memo') + .should('be.visible') + .should('not.be.disabled') + .click(); + } } diff --git a/Test/End-2-end/cypress/support/pages/backend/OrdersPage.js b/Test/End-2-end/cypress/support/pages/backend/OrdersPage.js index 56b3b537b33..41f445ff7c6 100644 --- a/Test/End-2-end/cypress/support/pages/backend/OrdersPage.js +++ b/Test/End-2-end/cypress/support/pages/backend/OrdersPage.js @@ -15,6 +15,19 @@ export default class OrdersPage { cy.visit('/admin/sales/order/view/order_id/' + id); } + openByIncrementId(incrementId) { + cy.visit('/admin/sales/order/'); + + cy.get('.data-grid-cell-content') + .contains(incrementId) + .parents('tr') + .find('a.action-menu-item') + .contains('View') + .then((element) => { + cy.visit(element.attr('href')); + }); + } + callFetchStatus() { cy.get('.fetch-mollie-payment-status').click(); diff --git a/Test/Integration/Model/PaymentFee/Quote/Address/Total/PaymentFeeTaxTest.php b/Test/Integration/Model/PaymentFee/Quote/Address/Total/PaymentFeeTaxTest.php index 57bf27b752b..116ec18d9ad 100644 --- a/Test/Integration/Model/PaymentFee/Quote/Address/Total/PaymentFeeTaxTest.php +++ b/Test/Integration/Model/PaymentFee/Quote/Address/Total/PaymentFeeTaxTest.php @@ -103,6 +103,7 @@ private function getShippingAssignment() { /** @var AddressInterface $address */ $address = $this->objectManager->create(AddressInterface::class); + $address->setQuote($this->getQuote()); /** @var ShippingInterface $shipping */ $shipping = $this->objectManager->create(ShippingInterface::class); diff --git a/composer.json b/composer.json index 086128c0ee2..057388c2ca7 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "mollie/magento2", "description": "Mollie Payment Module for Magento 2", - "version": "2.32.2", + "version": "2.32.3", "keywords": [ "mollie", "payment", diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index 6bb0a891f96..43c49dd54e7 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -49,4 +49,8 @@ + + + + diff --git a/etc/config.xml b/etc/config.xml index 753a4a59a11..80db50cddd7 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -3,7 +3,7 @@ - v2.32.2 + v2.32.3 0 0 test diff --git a/etc/di.xml b/etc/di.xml index cbaa6870e46..4db948de342 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -338,7 +338,7 @@ - ^/mollie/checkout/webhook.* + ^/mollie/.* diff --git a/i18n/de_DE.csv b/i18n/de_DE.csv index 33c354f4b48..ffc04215f39 100644 --- a/i18n/de_DE.csv +++ b/i18n/de_DE.csv @@ -198,7 +198,7 @@ "We recommend using another 'pending' status as the default Magento pending status can automatically cancel the order before the payment expiry time is reached.
By default the status ""Pending Payment"" is not visible for customers, therefore we advise you to create a new status for this, which should also be visible to the customer, informing them they still have to complete their payment.","Wir empfehlen, einen anderen als den standardmäßigen ausstehenden Status von Magento zu verwenden, da die Zahlung bei diesem vor Ablauf der Zahlungsfrist automatisch storniert werden kann.
Standardmäßig ist der Status „Ausstehende Zahlung“ für den Kunden nicht sichtbar. Wir empfehlen Ihnen daher, einen neuen Status zu erstellen, der auch für den Kunden sichtbar ist und ihm mitteilt, dass er die Zahlung noch vervollständigen muss." "Due Days","Zahlungstermine" "Belfius","Belfius" -"Credit Card","Kreditkarte" +"Credit Card","Kredit-/Debitkarte" "Use Mollie Components","Mollie-Komponenten verwenden" "Enable Single Click Payments","Ein-Klick-Zahlungen aktivieren" "SEPA Direct Debit","SEPA-Lastschrift" diff --git a/i18n/en_US.csv b/i18n/en_US.csv index 9423b35c7bc..bf91ace9770 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -198,7 +198,7 @@ "We recommend using another 'pending' status as the default Magento pending status can automatically cancel the order before the payment expiry time is reached.
By default the status ""Pending Payment"" is not visible for customers, therefore we advise you to create a new status for this, which should also be visible to the customer, informing them they still have to complete their payment.","We recommend using another 'pending' status as the default Magento pending status can automatically cancel the order before the payment expiry time is reached.
By default the status ""Pending Payment"" is not visible for customers, therefore we advise you to create a new status for this, which should also be visible to the customer, informing them they still have to complete their payment." "Due Days","Due Days" "Belfius","Belfius" -"Credit Card","Credit Card" +"Credit Card","Credit/Debit Card" "Use Mollie Components","Use Mollie Components" "Enable Single Click Payments","Enable Single Click Payments" "SEPA Direct Debit","SEPA Direct Debit" diff --git a/i18n/es_ES.csv b/i18n/es_ES.csv index b23eb98c316..b47786c5312 100644 --- a/i18n/es_ES.csv +++ b/i18n/es_ES.csv @@ -198,7 +198,7 @@ "We recommend using another 'pending' status as the default Magento pending status can automatically cancel the order before the payment expiry time is reached.
By default the status ""Pending Payment"" is not visible for customers, therefore we advise you to create a new status for this, which should also be visible to the customer, informing them they still have to complete their payment.","Recomendamos utilizar otro estado 'pendiente', ya que el estado pendiente por defecto de Magento puede cancelar automáticamente el pedido antes de que se llegue a la fecha de vencimiento del pago.
Por defecto, el estado «Pago pendiente» no es visible para los clientes, por lo que le aconsejamos que cree un nuevo estado para esto, que también debería ser visible para el cliente, que le informe de que todavía tiene que completar su pago." "Due Days","Días de vencimiento" "Belfius","Belfius" -"Credit Card","Tarjeta de crédito" +"Credit Card","Tarjeta de crédito/débito" "Use Mollie Components","Usar componentes de Mollie" "Enable Single Click Payments","Habilitar pagos con un solo clic" "SEPA Direct Debit","Adeudo directo SEPA" diff --git a/i18n/fr_FR.csv b/i18n/fr_FR.csv index 72501816b60..4a68eba30eb 100644 --- a/i18n/fr_FR.csv +++ b/i18n/fr_FR.csv @@ -198,7 +198,7 @@ "We recommend using another 'pending' status as the default Magento pending status can automatically cancel the order before the payment expiry time is reached.
By default the status ""Pending Payment"" is not visible for customers, therefore we advise you to create a new status for this, which should also be visible to the customer, informing them they still have to complete their payment.","Nous vous recommandons d'utiliser un autre statut «en attente», car le statut en attente Magento par défaut peut annuler automatiquement la commande avant même l'expiration du délai de paiement.
Comme le statut «Paiement en attente» n'est pas visible par défaut par les clients, nous vous recommandons de créer un nouveau statut qui sera visible par la clientèle et l'informera qu'elle doit finaliser son paiement." "Due Days","Jours d'échéance" "Belfius","Belfius" -"Credit Card","Carte de crédit" +"Credit Card","Carte de crédit/débit" "Use Mollie Components","Utilisez les composants Mollie" "Enable Single Click Payments","Activer les paiements Single Click" "SEPA Direct Debit","SEPA débit direct" diff --git a/i18n/nl_NL.csv b/i18n/nl_NL.csv index 80607968e49..81ccf595230 100644 --- a/i18n/nl_NL.csv +++ b/i18n/nl_NL.csv @@ -198,7 +198,7 @@ "We recommend using another 'pending' status as the default Magento pending status can automatically cancel the order before the payment expiry time is reached.
By default the status ""Pending Payment"" is not visible for customers, therefore we advise you to create a new status for this, which should also be visible to the customer, informing them they still have to complete their payment.","Wij raden u aan om een andere status voor 'openstaand' te gebruiken, omdat de standaard Magento-status 'openstaand' de bestelling automatisch kan annuleren, voordat de vervaltermijn is afgelopen.
Standaard is de status 'openstaande betaling' niet zichtbaar voor klanten, daarom adviseren wij u om hiervoor een nieuwe status aan te maken, die ook zichtbaar is voor de klant, om hem te laten weten dat hij zijn betaling nog dient te voltooien." "Due Days","Dagen achterstallig" "Belfius","Belfius" -"Credit Card","Creditcard" +"Credit Card","Krediet-/Debetkaart" "Use Mollie Components","Gebruik Mollie Components" "Enable Single Click Payments","Schakel betaling met één klik in" "SEPA Direct Debit","SEPA Direct Debit" diff --git a/view/frontend/web/js/view/payment/method-renderer/applepay-direct.js b/view/frontend/web/js/view/payment/method-renderer/applepay-direct.js index eb920e76e24..05f29cf8713 100644 --- a/view/frontend/web/js/view/payment/method-renderer/applepay-direct.js +++ b/view/frontend/web/js/view/payment/method-renderer/applepay-direct.js @@ -2,6 +2,7 @@ define( [ 'jquery', 'uiRegistry', + 'ko', 'Mollie_Payment/js/view/payment/method-renderer/default', 'Magento_Checkout/js/model/totals', 'mage/url', @@ -11,6 +12,7 @@ define( function ( $, uiRegistry, + ko, Component, totals, url, @@ -28,6 +30,7 @@ define( defaults: { template: 'Mollie_Payment/payment/applepay-direct', isIosc: uiRegistry.has("checkout.iosc.ajax"), + applePayPaymentToken: ko.observable('') }, initObservable: function () { diff --git a/view/frontend/web/js/view/product/apple-pay-button.js b/view/frontend/web/js/view/product/apple-pay-button.js index f965801e7e1..1ae2ac23045 100644 --- a/view/frontend/web/js/view/product/apple-pay-button.js +++ b/view/frontend/web/js/view/product/apple-pay-button.js @@ -223,7 +223,9 @@ define([ getLineItems: function () { let totals = [...this.quoteTotals]; - totals.pop(); + + // Delete the item that has code == grand_total + totals.splice(totals.findIndex(total => total.code === 'grand_total'), 1); return totals; }, @@ -231,7 +233,7 @@ define([ getTotal: function () { let totals = [...this.quoteTotals]; - var total = totals.pop(); + const total = totals.find(total => total.code === 'grand_total'); total.label = this.storeName;