diff --git a/.github/workflows/codesniffer.yml b/.github/workflows/codesniffer.yml index 539bd37..7c70cf3 100644 --- a/.github/workflows/codesniffer.yml +++ b/.github/workflows/codesniffer.yml @@ -5,7 +5,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Run codesniffer run: diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 4f93f34..9338240 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -10,39 +10,23 @@ jobs: MAGENTO_VERSION: 2.3.7-p3 - PHP_VERSION: php74-fpm MAGENTO_VERSION: 2.4.0 - - PHP_VERSION: php81-fpm - MAGENTO_VERSION: 2.4.5 + - PHP_VERSION: php83-fpm + MAGENTO_VERSION: 2.4.7 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - # Public repository - - uses: actions/checkout@v2 - if: "!contains(github.repository, 'magmodules')" - with: - repository: mollie/magento2 - path: mollie-base - ref: master - - # Internal repository - - uses: actions/checkout@v2 - if: "contains(github.repository, 'magmodules')" - with: - repository: magmodules/mollie-magento2 - path: mollie-base - ssh-key: ${{ secrets.MAGMODULES_MOLLIE_MAGENTO2_KEY }} + - uses: actions/checkout@v4 - name: Start Docker run: docker run --detach --name magento-project-community-edition michielgerritsen/magento-project-community-edition:${{ matrix.PHP_VERSION }}-magento${{ matrix.MAGENTO_VERSION }} - name: Remove version from composer.json - run: sed -i '/version/d' ./composer.json && sed -i '/version/d' ./mollie-base/composer.json + run: sed -i '/version/d' ./composer.json - name: Upload the code into the docker container - run: docker cp $(pwd)/mollie-base/ magento-project-community-edition:/data/extensions/mollie-magento2/ && rm -rf mollie-base/ && docker cp $(pwd)/. magento-project-community-edition:/data/extensions/mollie-magento2-multishipping/ + run: docker cp $(pwd)/. magento-project-community-edition:/data/extensions/mollie-magento2-multishipping/ - name: Install Mollie and Mollie Multishipping extensions - run: docker exec magento-project-community-edition composer require mollie/magento2:"dev-master as 2.99.99" mollie/magento2-multishipping:@dev -vvv + run: docker exec magento-project-community-edition ./install-composer-package mollie/magento2 mollie/magento2-multishipping:@dev - name: Enable modules run: docker exec magento-project-community-edition bash -c "php bin/magento module:enable Mollie_Payment Mollie_Multishipping && php bin/magento setup:upgrade" diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 4d22f57..c8fca87 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -2,30 +2,22 @@ name: Lint PHP files on: [push, pull_request] jobs: - php-71: + php-73: runs-on: ubuntu-latest steps: - - uses: StephaneBour/actions-php-lint@7.1 - with: - dir: './' + - uses: prestashop/github-action-php-lint/7.3@v2.1 - php-72: + php-74: runs-on: ubuntu-latest steps: - - uses: StephaneBour/actions-php-lint@7.2 - with: - dir: './' + - uses: prestashop/github-action-php-lint/7.4@v2.1 - php-73: + php-81: runs-on: ubuntu-latest steps: - - uses: StephaneBour/actions-php-lint@7.3 - with: - dir: './' + - uses: prestashop/github-action-php-lint/8.1@v2.1 - php-74: + php-82: runs-on: ubuntu-latest steps: - - uses: StephaneBour/actions-php-lint@7.4 - with: - dir: './' + - uses: prestashop/github-action-php-lint/8.2@v2.1 diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 42cf7fd..37b5745 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -10,39 +10,23 @@ jobs: MAGENTO_VERSION: 2.3.7-p3 - PHP_VERSION: php74-fpm MAGENTO_VERSION: 2.4.0 - - PHP_VERSION: php81-fpm - MAGENTO_VERSION: 2.4.5 + - PHP_VERSION: php83-fpm + MAGENTO_VERSION: 2.4.7 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - # Public repository - - uses: actions/checkout@v2 - if: "!contains(github.repository, 'magmodules')" - with: - repository: mollie/magento2 - path: mollie-base - ref: master - - # Internal repository - - uses: actions/checkout@v2 - if: "contains(github.repository, 'magmodules')" - with: - repository: magmodules/mollie-magento2 - path: mollie-base - ssh-key: ${{ secrets.MAGMODULES_MOLLIE_MAGENTO2_KEY }} + - uses: actions/checkout@v4 - name: Start Docker run: docker run --detach --name magento-project-community-edition michielgerritsen/magento-project-community-edition:${{ matrix.PHP_VERSION }}-magento${{ matrix.MAGENTO_VERSION }} - name: Remove version from composer.json - run: sed -i '/version/d' ./composer.json && sed -i '/version/d' ./mollie-base/composer.json + run: sed -i '/version/d' ./composer.json - name: Upload the code into the docker container - run: docker cp $(pwd)/mollie-base/ magento-project-community-edition:/data/extensions/mollie-magento2/ && rm -rf mollie-base/ && docker cp $(pwd)/. magento-project-community-edition:/data/extensions/mollie-magento2-multishipping/ + run: docker cp $(pwd)/. magento-project-community-edition:/data/extensions/mollie-magento2-multishipping/ - name: Install Mollie and Mollie Multishipping extensions - run: docker exec magento-project-community-edition composer require mollie/magento2:"dev-master as 2.99.99" mollie/magento2-multishipping:@dev + run: docker exec magento-project-community-edition ./install-composer-package mollie/magento2 mollie/magento2-multishipping:@dev - name: Run PHPStan run: docker exec magento-project-community-edition /bin/bash -c "./vendor/bin/phpstan analyse -c /data/extensions/mollie-magento2-multishipping/phpstan.neon /data/extensions/mollie-magento2-multishipping" diff --git a/.github/workflows/setup-di-compile.yml b/.github/workflows/setup-di-compile.yml index 4b95ded..f2c90cf 100644 --- a/.github/workflows/setup-di-compile.yml +++ b/.github/workflows/setup-di-compile.yml @@ -10,39 +10,23 @@ jobs: MAGENTO_VERSION: 2.3.7-p3 - PHP_VERSION: php74-fpm MAGENTO_VERSION: 2.4.0 - - PHP_VERSION: php81-fpm - MAGENTO_VERSION: 2.4.5 + - PHP_VERSION: php83-fpm + MAGENTO_VERSION: 2.4.7 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - # Public repository - - uses: actions/checkout@v2 - if: "!contains(github.repository, 'magmodules')" - with: - repository: mollie/magento2 - path: mollie-base - ref: master - - # Internal repository - - uses: actions/checkout@v2 - if: "contains(github.repository, 'magmodules')" - with: - repository: magmodules/mollie-magento2 - path: mollie-base - ssh-key: ${{ secrets.MAGMODULES_MOLLIE_MAGENTO2_KEY }} + - uses: actions/checkout@v4 - name: Start Docker run: docker run --detach --name magento-project-community-edition michielgerritsen/magento-project-community-edition:${{ matrix.PHP_VERSION }}-magento${{ matrix.MAGENTO_VERSION }} - name: Remove version from composer.json - run: sed -i '/version/d' ./composer.json && sed -i '/version/d' ./mollie-base/composer.json + run: sed -i '/version/d' ./composer.json - name: Upload the code into the docker container - run: docker cp $(pwd)/mollie-base/ magento-project-community-edition:/data/extensions/mollie-magento2/ && rm -rf mollie-base/ && docker cp $(pwd)/. magento-project-community-edition:/data/extensions/mollie-magento2-multishipping/ + run: docker cp $(pwd)/. magento-project-community-edition:/data/extensions/mollie-magento2-multishipping/ - name: Install Mollie and Mollie Multishipping extensions - run: docker exec magento-project-community-edition composer require mollie/magento2:"dev-master as 2.99.99" mollie/magento2-multishipping:@dev + run: docker exec magento-project-community-edition ./install-composer-package mollie/magento2 mollie/magento2-multishipping:@dev - name: Run setup:di:compile run: docker exec magento-project-community-edition ./retry "php bin/magento setup:di:compile" diff --git a/PlaceOrder.php b/PlaceOrder.php index b2edf4d..ae7f2d9 100644 --- a/PlaceOrder.php +++ b/PlaceOrder.php @@ -16,6 +16,7 @@ use Mollie\Payment\Model\Client\Payments; use Mollie\Payment\Model\Mollie; use Mollie\Payment\Service\Order\BuildTransaction; +use Mollie\Payment\Service\Order\MethodCode; use Mollie\Payment\Service\Order\Transaction; use Mollie\Payment\Service\PaymentToken\PaymentTokenForOrder; @@ -75,6 +76,10 @@ class PlaceOrder implements PlaceOrderInterface * @var PaymentTokenForOrder */ private $paymentTokenForOrder; + /** + * @var MethodCode + */ + private $methodCode; public function __construct( OrderManagementInterface $orderManagement, @@ -86,7 +91,8 @@ public function __construct( BuildTransaction $buildTransaction, CheckoutUrl $checkoutUrl, TransactionDescription $transactionDescription, - PaymentTokenForOrder $paymentTokenForOrder + PaymentTokenForOrder $paymentTokenForOrder, + MethodCode $methodCode ) { $this->orderManagement = $orderManagement; $this->mollieModel = $mollieModel; @@ -98,6 +104,7 @@ public function __construct( $this->checkoutUrl = $checkoutUrl; $this->transactionDescription = $transactionDescription; $this->paymentTokenForOrder = $paymentTokenForOrder; + $this->methodCode = $methodCode; } /** @@ -107,13 +114,24 @@ public function __construct( public function place(array $orderList): array { try { + $mollieOrders = []; foreach ($orderList as $order) { $this->orderManagement->place($order); + if (substr($order->getPayment()->getMethod(), 0, 6) === 'mollie') { + // Only process Mollie orders; some orders _could_ have been paid with 'free' method. + $mollieOrders[] = $order; + } } - $firstOrder = reset($orderList); + if (count($mollieOrders) === 0) { + // This situation should not happen, as the quote would then have 'free' payment method. + // This class will then never be called. But to be sure... + return $this->errorList; + } + + $firstOrder = reset($mollieOrders); $storeId = $firstOrder->getStoreId(); - $paymentData = $this->buildPaymentData($orderList, $storeId); + $paymentData = $this->buildPaymentData($mollieOrders, $storeId); $paymentData = $this->mollieHelper->validatePaymentData($paymentData); $this->mollieHelper->addTolog('request', $paymentData); @@ -133,7 +151,7 @@ public function place(array $orderList): array return $errorList; } - foreach ($orderList as $order) { + foreach ($mollieOrders as $order) { try { $this->molliePaymentsApi->processResponse($order, $paymentResponse); } catch (\Exception $exception) { @@ -171,21 +189,21 @@ private function getTotalAmount(array $orderList) private function buildPaymentData(array $orderList, $storeId): array { $firstOrder = reset($orderList); - $paymentToken = $this->paymentTokenForOrder->execute($firstOrder); - $method = $this->mollieHelper->getMethodCode($firstOrder); + $paymentTokens = $this->getPaymentTokens($orderList); + $method = $this->methodCode->execute($firstOrder); $orderIds = array_map(function (OrderInterface $order) { return $order->getEntityId(); }, $orderList); $paymentData = [ 'amount' => $this->getTotalAmount($orderList), 'description' => $this->transactionDescription->forMultishippingTransaction($storeId), 'billingAddress' => $this->molliePaymentsApi->getAddressLine($firstOrder->getBillingAddress()), - 'redirectUrl' => $this->multishippingTransaction->getRedirectUrl($orderList, $paymentToken), + 'redirectUrl' => $this->multishippingTransaction->getRedirectUrl($orderList, $paymentTokens), 'webhookUrl' => $this->transaction->getWebhookUrl($orderList), 'method' => $method, 'metadata' => [ 'order_ids' => implode(', ', $orderIds), 'store_id' => $storeId, - 'payment_token' => $paymentToken + 'payment_token' => implode(', ', $paymentTokens), ], 'locale' => $this->mollieHelper->getLocaleCode($storeId, Payments::CHECKOUT_TYPE), ]; @@ -201,4 +219,14 @@ private function buildPaymentData(array $orderList, $storeId): array return $this->buildTransaction->execute($firstOrder, Payments::CHECKOUT_TYPE, $paymentData); } + + public function getPaymentTokens(array $orders): array + { + $output = []; + foreach ($orders as $order) { + $output[] = $this->paymentTokenForOrder->execute($order); + } + + return $output; + } } diff --git a/Service/Order/MultishippingTransaction.php b/Service/Order/MultishippingTransaction.php index 1d04c50..e25d375 100644 --- a/Service/Order/MultishippingTransaction.php +++ b/Service/Order/MultishippingTransaction.php @@ -34,11 +34,11 @@ public function __construct( /** * @param OrderInterface[] $orders - * @param string $paymentToken - * @throws \Exception + * @param array $paymentTokens * @return string + *@throws \Exception */ - public function getRedirectUrl(array $orders, string $paymentToken): string + public function getRedirectUrl(array $orders, array $paymentTokens): string { if (!$orders) { throw new \Exception('The provided order array is empty'); @@ -50,7 +50,7 @@ public function getRedirectUrl(array $orders, string $paymentToken): string $orderIds = array_map( function (OrderInterface $order) { return $order->getId(); }, $orders); $parameters = http_build_query([ 'order_ids' => $orderIds, - 'payment_token' => $paymentToken, + 'payment_tokens' => $paymentTokens, 'utm_nooverride' => 1, ]); diff --git a/Test/Integration/Configuration/DiXmlTest.php b/Test/Integration/Configuration/DiXmlTest.php new file mode 100644 index 0000000..e4917de --- /dev/null +++ b/Test/Integration/Configuration/DiXmlTest.php @@ -0,0 +1,35 @@ +objectManager->get(PlaceOrderPool::class); + + foreach ($this->objectManager->get(PaymentMethods::class)->getCodes() as $code) { + $this->assertNotNull( + $instance->get($code), + sprintf('An instance should be returned for "%s"', $code) + ); + } + } +} diff --git a/Test/Integration/Configuration/PaymentXmlTest.php b/Test/Integration/Configuration/PaymentXmlTest.php new file mode 100644 index 0000000..9fec513 --- /dev/null +++ b/Test/Integration/Configuration/PaymentXmlTest.php @@ -0,0 +1,56 @@ +objectManager->get(Reader::class); + + $methods = $reader->read()['methods']; + + $allMethods = array_filter( + $this->objectManager->get(PaymentMethods::class)->getCodes(), + function (string $code) { + return !in_array( + $code, + [ + 'mollie_methods_in3', + 'mollie_methods_klarna', + 'mollie_methods_klarnapaylater', + 'mollie_methods_klarnapaynow', + 'mollie_methods_klarnasliceit', + 'mollie_methods_pointofsale', + ] + ); + } + ); + + + foreach ($allMethods as $code) { + $this->assertArrayHasKey($code, $methods); + + $method = $methods[$code]; + $this->assertArrayHasKey('allow_multiple_address', $method); + $this->assertEquals( + '1', + $method['allow_multiple_address'], + sprintf('allow_multiple_address for method %s should be enabled', $code) + ); + } + } +} diff --git a/Test/Integration/Controller/Checkout/ProcessTest.php b/Test/Integration/Controller/Checkout/ProcessTest.php index 58b8638..d75b1d9 100644 --- a/Test/Integration/Controller/Checkout/ProcessTest.php +++ b/Test/Integration/Controller/Checkout/ProcessTest.php @@ -10,20 +10,71 @@ use Magento\Framework\Message\MessageInterface; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\AbstractController; use Mollie\Payment\Model\Mollie; +use Mollie\Payment\Service\Mollie\GetMollieStatusResult; +use Mollie\Payment\Service\Mollie\ProcessTransaction; +use Mollie\Payment\Service\Mollie\ValidateProcessRequest; class ProcessTest extends AbstractController { + /** + * @magentoDataFixture Magento/Sales/_files/order_list.php + * @return void + */ public function testRedirectsToSuccessPage() { - $mollieModel = $this->createMock(Mollie::class); - $mollieModel->method('processTransaction')->willReturn(['success' => true]); + $order1 = $this->loadOrderById('100000002'); + $order1Id = $order1->getEntityId(); + $order2 = $this->loadOrderById('100000003'); + $order2Id = $order2->getEntityId(); - $this->_objectManager->addSharedInstance($mollieModel, Mollie::class); + $this->_objectManager->addSharedInstance(new class extends ProcessTransaction { + public function __construct() {} - $this->dispatch('mollie/checkout/process?order_ids[]=123&order_ids[]=456'); + public function execute(int $orderId, ?string $transactionId, string $type = 'webhook'): GetMollieStatusResult + { + return ObjectManager::getInstance()->create(GetMollieStatusResult::class, [ + 'status' => 'paid', + 'method' => 'ideal', + ]); + } + }, ProcessTransaction::class); + + $this->_objectManager->addSharedInstance(new class($order1Id, $order2Id) extends ValidateProcessRequest { + + private $order1Id; + private $order2Id; + + public function __construct($order1Id, $order2Id) { + $this->order1Id = $order1Id; + $this->order2Id = $order2Id; + } + + public function execute(): array + { + return [$this->order1Id => 'abc', $this->order2Id => 'def']; + } + }, ValidateProcessRequest::class); + + $this->dispatch('mollie/checkout/process?order_ids[]=' . $order1Id . '&order_ids[]=' . $order2Id . '&payment_tokens[]=abc&payment_tokens[]=def'); $this->assertRedirect($this->stringContains('multishipping/checkout/success?utm_nooverride=1')); } + + /** + * @param $orderId + * @return \Magento\Sales\Model\Order + */ + private function loadOrderById($orderId) + { + $repository = ObjectManager::getInstance()->get(OrderRepositoryInterface::class); + $builder = ObjectManager::getInstance()->create(SearchCriteriaBuilder::class); + $searchCriteria = $builder->addFilter('increment_id', $orderId, 'eq')->create(); + + $orderList = $repository->getList($searchCriteria)->getItems(); + + return array_shift($orderList); + } } diff --git a/Test/Integration/Service/Order/TransactionTest.php b/Test/Integration/Service/Order/TransactionTest.php index 3195626..1c309a3 100644 --- a/Test/Integration/Service/Order/TransactionTest.php +++ b/Test/Integration/Service/Order/TransactionTest.php @@ -1,5 +1,5 @@ objectManager->create(MultishippingTransaction::class); - $result = $instance->getRedirectUrl($orders, 'PAYMENT_TOKEN_TEST'); + $result = $instance->getRedirectUrl($orders, ['PAYMENT_TOKEN_TEST']); $this->assertStringContainsString('order_ids[0]=777', urldecode($result)); $this->assertStringContainsString('order_ids[1]=888', urldecode($result)); $this->assertStringContainsString('order_ids[2]=999', urldecode($result)); - $this->assertStringContainsString('payment_token=PAYMENT_TOKEN_TEST', urldecode($result)); + $this->assertStringContainsString('payment_tokens[0]=PAYMENT_TOKEN_TEST', urldecode($result)); $this->assertStringContainsString('utm_nooverride=1', urldecode($result)); } @@ -40,6 +40,6 @@ public function testThrowsAnExceptionWhenTheOrderListIsEmpty() /** @var MultishippingTransaction $instance */ $instance = $this->objectManager->create(MultishippingTransaction::class); - $instance->getRedirectUrl($orders, 'PAYMENT_TOKEN_TEST'); + $instance->getRedirectUrl($orders, ['PAYMENT_TOKEN_TEST']); } } diff --git a/composer.json b/composer.json index f232399..8c37e01 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ ], "require": { "magento/framework": ">=102.0.0", - "mollie/magento2": ">=2.22.0" + "mollie/magento2": ">=2.34.0" }, "type": "magento2-module", "license": [ diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 7c3721d..57193d6 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -7,11 +7,14 @@ + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder @@ -20,14 +23,18 @@ Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder + Mollie\Multishipping\PlaceOrder Mollie\Multishipping\PlaceOrder diff --git a/etc/payment.xml b/etc/payment.xml index f55a076..cf4076a 100644 --- a/etc/payment.xml +++ b/etc/payment.xml @@ -5,9 +5,15 @@ --> + + 1 + 1 + + 1 + 1 @@ -17,6 +23,12 @@ 1 + + 1 + + + 1 + 1 @@ -53,6 +65,9 @@ 1 + + 1 + 1