From 32b0be3f47ae801c8bf9cf80a0c8b83fe6b7ddad Mon Sep 17 00:00:00 2001 From: Vitalij Mik Date: Mon, 15 Jan 2024 13:46:33 +0100 Subject: [PATCH] MOL-1280: Fix retry route and add cypress tests (#683) * MOL-1280: Fix retry route and add cypress tests * MOL-1280: Update cypress ID * MOL-1280: cleanup cypress test --------- Co-authored-by: Vitalij Mik --- .../Payment/MollieFailureControllerBase.php | 44 +++- src/Facade/MolliePaymentFinalize.php | 3 +- .../config/compatibility/controller.xml | 2 + .../config/compatibility/controller_6.5.xml | 2 + src/Service/TransactionService.php | 1 + .../subscriptions/subscription.cy.js | 212 ++++++++++-------- 6 files changed, 167 insertions(+), 97 deletions(-) diff --git a/src/Controller/Storefront/Payment/MollieFailureControllerBase.php b/src/Controller/Storefront/Payment/MollieFailureControllerBase.php index d9a9d804c..1aa7d03e8 100644 --- a/src/Controller/Storefront/Payment/MollieFailureControllerBase.php +++ b/src/Controller/Storefront/Payment/MollieFailureControllerBase.php @@ -12,17 +12,22 @@ use Kiener\MolliePayments\Exception\MissingOrderInTransactionException; use Kiener\MolliePayments\Exception\MollieOrderCouldNotBeFetchedException; use Kiener\MolliePayments\Factory\MollieApiFactory; +use Kiener\MolliePayments\Service\CustomerService; use Kiener\MolliePayments\Service\Mollie\MolliePaymentStatus; use Kiener\MolliePayments\Service\Mollie\OrderStatusConverter; use Kiener\MolliePayments\Service\MollieApi\Order as MollieServiceOrder; use Kiener\MolliePayments\Service\Order\OrderStateService; +use Kiener\MolliePayments\Service\SettingsService; use Kiener\MolliePayments\Service\TransactionService; use Kiener\MolliePayments\Service\Transition\TransactionTransitionServiceInterface; use Kiener\MolliePayments\Struct\Order\OrderAttributes; +use Kiener\MolliePayments\Struct\OrderLineItemEntity\OrderLineItemEntityAttributes; use Mollie\Api\Exceptions\ApiException; use Mollie\Api\Resources\Order; use Psr\Log\LoggerInterface; use Shopware\Core\Checkout\Cart\Exception\OrderNotFoundException; +use Shopware\Core\Checkout\Order\Aggregate\OrderCustomer\OrderCustomerEntity; +use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection; use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; use Shopware\Core\Checkout\Order\OrderEntity; use Shopware\Core\Checkout\Order\OrderStates; @@ -87,6 +92,16 @@ class MollieFailureControllerBase extends StorefrontController */ private $orderStatusConverter; + /** + * @var SettingsService + */ + private $settingsService; + + /** + * @var CustomerService + */ + private $customerService; + /** * @param RouterInterface $router * @param CompatibilityGatewayInterface $compatibilityGateway @@ -99,7 +114,7 @@ class MollieFailureControllerBase extends StorefrontController * @param MollieServiceOrder $mollieOrderService * @param OrderStatusConverter $orderStatusConverter */ - public function __construct(RouterInterface $router, CompatibilityGatewayInterface $compatibilityGateway, MollieApiFactory $apiFactory, OrderStateService $orderStateService, TransactionService $transactionService, LoggerInterface $logger, TransactionTransitionServiceInterface $transactionTransitionService, FlowBuilderFactoryInterface $flowBuilderFactory, MollieServiceOrder $mollieOrderService, OrderStatusConverter $orderStatusConverter) + public function __construct(RouterInterface $router, CompatibilityGatewayInterface $compatibilityGateway, MollieApiFactory $apiFactory, OrderStateService $orderStateService, TransactionService $transactionService, LoggerInterface $logger, TransactionTransitionServiceInterface $transactionTransitionService, FlowBuilderFactoryInterface $flowBuilderFactory, MollieServiceOrder $mollieOrderService, OrderStatusConverter $orderStatusConverter, SettingsService $settingsService, CustomerService $customerService) { $this->router = $router; $this->compatibilityGateway = $compatibilityGateway; @@ -112,6 +127,8 @@ public function __construct(RouterInterface $router, CompatibilityGatewayInterfa $this->orderStatusConverter = $orderStatusConverter; $this->eventDispatcher = $flowBuilderFactory->createDispatcher(); + $this->settingsService = $settingsService; + $this->customerService = $customerService; } /** @@ -240,7 +257,30 @@ public function retry(SalesChannelContext $context, string $transactionId): Redi # if its a failed status, then we have to create a new payment # otherwise no payment would exist, and we are not able to redirect to the payment screen if (MolliePaymentStatus::isFailedStatus('', $paymentStatus)) { - $mollieOrder->createPayment([]); + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + $paymentData = []; + + if ($settings->isSubscriptionsEnabled()) { + /** @var OrderLineItemCollection $lineItems */ + $lineItems = $order->getLineItems(); + /** @var OrderCustomerEntity $customer */ + $customer = $order->getOrderCustomer(); + /** @var string $customerId */ + $customerId = $customer->getCustomerId(); + + # mollie customer ID is required for recurring payments, see https://docs.mollie.com/reference/v2/orders-api/create-order-payment + $mollieCustomerId = $this->customerService->getMollieCustomerId($customerId, $context->getSalesChannelId(), $context->getContext()); + + foreach ($lineItems as $lineItem) { + $attributes = new OrderLineItemEntityAttributes($lineItem); + if ($attributes->isSubscriptionProduct()) { + $paymentData['sequenceType'] = 'first'; + $paymentData['customerId'] = $mollieCustomerId; + break; + } + } + } + $mollieOrder->createPayment($paymentData); } $redirectUrl = (string)$orderAttributes->getMolliePaymentUrl(); diff --git a/src/Facade/MolliePaymentFinalize.php b/src/Facade/MolliePaymentFinalize.php index 772d5fab7..208198740 100644 --- a/src/Facade/MolliePaymentFinalize.php +++ b/src/Facade/MolliePaymentFinalize.php @@ -205,7 +205,8 @@ public function finalize(AsyncPaymentTransactionStruct $transactionStruct, Sales if ($this->settingsService->getMollieCypressMode() && $orderAttributes->isTypeSubscription()) { if ($mollieOrder->payments() !== null && count($mollieOrder->payments()) > 0) { $paymentDetails = new MolliePaymentDetails(); - $mandateId = $paymentDetails->getMandateId($mollieOrder->payments()[0]); + $lasMolliePayment = count($mollieOrder->payments()) -1; + $mandateId = $paymentDetails->getMandateId($mollieOrder->payments()[$lasMolliePayment]); $this->subscriptionManager->confirmSubscription($order, $mandateId, $salesChannelContext->getContext()); } } diff --git a/src/Resources/config/compatibility/controller.xml b/src/Resources/config/compatibility/controller.xml index 6d89afe28..4925d940d 100644 --- a/src/Resources/config/compatibility/controller.xml +++ b/src/Resources/config/compatibility/controller.xml @@ -105,6 +105,8 @@ + + diff --git a/src/Resources/config/compatibility/controller_6.5.xml b/src/Resources/config/compatibility/controller_6.5.xml index 0d524d653..3d59f398e 100644 --- a/src/Resources/config/compatibility/controller_6.5.xml +++ b/src/Resources/config/compatibility/controller_6.5.xml @@ -104,6 +104,8 @@ + + diff --git a/src/Service/TransactionService.php b/src/Service/TransactionService.php index 56951789d..cf6ce1800 100644 --- a/src/Service/TransactionService.php +++ b/src/Service/TransactionService.php @@ -45,6 +45,7 @@ public function getTransactionById($transactionId, $versionId = null, Context $c } $transactionCriteria->addAssociation('order.currency'); + $transactionCriteria->addAssociation('order.lineItems'); /** @var OrderTransactionCollection $transactions */ $transactions = $this->orderTransactionRepository->search( diff --git a/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js b/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js index 1ffb862f2..3baa86497 100644 --- a/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js @@ -75,112 +75,31 @@ describe('Subscription', () => { }); describe('Storefront + Administration', function () { + it('C2339889: Purchase subscription after failed payment and verify data in Administration', () =>{ + purchaseSubscriptionAndGoToPayment(); - it('C4066: Purchase subscription and verify data in Administration', () => { - - configAction.setupPlugin(true, false, false, true); - configAction.updateProducts('', true, 3, 'weeks'); - - dummyUserScenario.execute(); - cy.visit('/'); - topMenu.clickOnSecondCategory(); - listing.clickOnFirstProduct(); - - // we have to see the subscription indicator - // and the add to basket button should show that we can subscribe - cy.contains('Subscription product'); - cy.contains('.btn', 'Subscribe'); - // we also want to see the translated interval - cy.contains('Every 3 weeks'); - - pdp.addToCart(2); - - // ------------------------------------------------------------------------------------------------------ - - // verify our warning information in our offcanvas - cy.contains('Not all payments methods are available when ordering subscription products'); - - checkout.goToCheckoutInOffCanvas(); - - // ------------------------------------------------------------------------------------------------------ - - // verify our warning information on the cart page - cy.contains('Not all payments methods are available when ordering subscription products'); - // we also want to see the translated interval - cy.contains('Every 3 weeks'); - - // now open our payment methods and verify - // that some of them are not available - // this is a check to at least see that it does something - // we also verify that we see all available methods (just to also check if mollie is even configured correctly). - if (shopware.isVersionGreaterEqual(6.4)) { - paymentAction.showAllPaymentMethods(); - } else { - paymentAction.openPaymentsModal(); - } - - assertAvailablePaymentMethods(); + molliePayment.selectFailed(); - if (shopware.isVersionLower(6.4)) { - paymentAction.closePaymentsModal(); - } + cy.url().should('include', '/payment/failed'); + cy.get('.container-main .btn-primary').click(); + cy.url().should('include','/checkout/select-method'); + cy.get('.grid-button-creditcard[value="creditcard"]').click(); - paymentAction.switchPaymentMethod('Card'); - - shopware.prepareDomainChange(); - checkout.placeOrderOnConfirm(); mollieSandbox.initSandboxCookie(); mollieCreditCardForm.enterValidCard(); mollieCreditCardForm.submitForm(); molliePayment.selectPaid(); - cy.url().should('include', '/checkout/finish'); - cy.contains('Thank you for your order'); - - - // ------------------------------------------------------------------------------------------------------ - - adminLogin.login(); - adminOrders.openOrders(); - adminOrders.openLastOrder(); - - // our latest order must have a subscription "badge" - repoOrdersDetails.getSubscriptionBadge().should('exist'); - - // ------------------------------------------------------------------------------------------------------ - - // verify that we have found a new subscription entry - // attention, this will not be 100% accurate if we have a persisting server - // or multiple subscription tests, but for now it has to work - adminSubscriptions.openSubscriptions(); - adminSubscriptions.openSubscription(0); - - // ------------------------------------------------------------------------------------------------------ + assertValidSubscriptionInAdmin(); + }) - repoAdminSubscriptonDetails.getMollieCustomerIdField().should('be.visible'); + it('C4066: Purchase subscription and verify data in Administration', () => { + purchaseSubscriptionAndGoToPayment(); - vueJs.textField(repoAdminSubscriptonDetails.getMollieCustomerIdField()).containsValue('cst_'); - vueJs.textField(repoAdminSubscriptonDetails.getCreatedAtField()).notEmptyValue(); + molliePayment.selectPaid(); - vueJs.textField(repoAdminSubscriptonDetails.getStatusField()).equalsValue('Active'); - vueJs.textField(repoAdminSubscriptonDetails.getCanceledAtField()).emptyValue(); - vueJs.textField(repoAdminSubscriptonDetails.getMollieSubscriptionIdField()).containsValue('sub_'); - vueJs.textField(repoAdminSubscriptonDetails.getMandateField()).containsValue('mdt_'); - vueJs.textField(repoAdminSubscriptonDetails.getNextPaymentAtField()).notEmptyValue(); - vueJs.textField(repoAdminSubscriptonDetails.getLastRemindedAtField()).emptyValue(); - - // just do a contains, because card-titles are just different - // across shopware versions, and in the end, we just need to make sure we see this exact string - cy.contains("History (2)"); - - // oldest history entry - cy.contains(repoAdminSubscriptonDetails.getHistoryStatusToSelector(1), 'pending', {matchCase: false}); - cy.contains(repoAdminSubscriptonDetails.getHistoryCommentSelector(1), 'created'); - // latest history entry - cy.contains(repoAdminSubscriptonDetails.getHistoryStatusFromSelector(0), 'pending', {matchCase: false}); - cy.contains(repoAdminSubscriptonDetails.getHistoryStatusToSelector(0), 'active', {matchCase: false}); - cy.contains(repoAdminSubscriptonDetails.getHistoryCommentSelector(0), 'confirmed'); + assertValidSubscriptionInAdmin(); }) }); @@ -413,6 +332,111 @@ describe('Subscription', () => { }) +function purchaseSubscriptionAndGoToPayment(){ + configAction.setupPlugin(true, false, false, true); + configAction.updateProducts('', true, 3, 'weeks'); + + dummyUserScenario.execute(); + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + + // we have to see the subscription indicator + // and the add to basket button should show that we can subscribe + cy.contains('Subscription product'); + cy.contains('.btn', 'Subscribe'); + // we also want to see the translated interval + cy.contains('Every 3 weeks'); + + pdp.addToCart(2); + + // ------------------------------------------------------------------------------------------------------ + + // verify our warning information in our offcanvas + cy.contains('Not all payments methods are available when ordering subscription products'); + + checkout.goToCheckoutInOffCanvas(); + + // ------------------------------------------------------------------------------------------------------ + + // verify our warning information on the cart page + cy.contains('Not all payments methods are available when ordering subscription products'); + // we also want to see the translated interval + cy.contains('Every 3 weeks'); + + // now open our payment methods and verify + // that some of them are not available + // this is a check to at least see that it does something + // we also verify that we see all available methods (just to also check if mollie is even configured correctly). + if (shopware.isVersionGreaterEqual(6.4)) { + paymentAction.showAllPaymentMethods(); + } else { + paymentAction.openPaymentsModal(); + } + + assertAvailablePaymentMethods(); + + if (shopware.isVersionLower(6.4)) { + paymentAction.closePaymentsModal(); + } + + paymentAction.switchPaymentMethod('Card'); + + shopware.prepareDomainChange(); + checkout.placeOrderOnConfirm(); + + mollieSandbox.initSandboxCookie(); + mollieCreditCardForm.enterValidCard(); + mollieCreditCardForm.submitForm(); +} + +function assertValidSubscriptionInAdmin(){ + cy.url().should('include', '/checkout/finish'); + cy.contains('Thank you for your order'); + // ------------------------------------------------------------------------------------------------------ + + adminLogin.login(); + adminOrders.openOrders(); + adminOrders.openLastOrder(); + + // our latest order must have a subscription "badge" + repoOrdersDetails.getSubscriptionBadge().should('exist'); + + // ------------------------------------------------------------------------------------------------------ + + // verify that we have found a new subscription entry + // attention, this will not be 100% accurate if we have a persisting server + // or multiple subscription tests, but for now it has to work + adminSubscriptions.openSubscriptions(); + adminSubscriptions.openSubscription(0); + + // ------------------------------------------------------------------------------------------------------ + + repoAdminSubscriptonDetails.getMollieCustomerIdField().should('be.visible'); + + vueJs.textField(repoAdminSubscriptonDetails.getMollieCustomerIdField()).containsValue('cst_'); + vueJs.textField(repoAdminSubscriptonDetails.getCreatedAtField()).notEmptyValue(); + + vueJs.textField(repoAdminSubscriptonDetails.getStatusField()).equalsValue('Active'); + vueJs.textField(repoAdminSubscriptonDetails.getCanceledAtField()).emptyValue(); + vueJs.textField(repoAdminSubscriptonDetails.getMollieSubscriptionIdField()).containsValue('sub_'); + vueJs.textField(repoAdminSubscriptonDetails.getMandateField()).containsValue('mdt_'); + vueJs.textField(repoAdminSubscriptonDetails.getNextPaymentAtField()).notEmptyValue(); + vueJs.textField(repoAdminSubscriptonDetails.getLastRemindedAtField()).emptyValue(); + + // just do a contains, because card-titles are just different + // across shopware versions, and in the end, we just need to make sure we see this exact string + cy.contains("History (2)"); + + // oldest history entry + cy.contains(repoAdminSubscriptonDetails.getHistoryStatusToSelector(1), 'pending', {matchCase: false}); + cy.contains(repoAdminSubscriptonDetails.getHistoryCommentSelector(1), 'created'); + // latest history entry + cy.contains(repoAdminSubscriptonDetails.getHistoryStatusFromSelector(0), 'pending', {matchCase: false}); + cy.contains(repoAdminSubscriptonDetails.getHistoryStatusToSelector(0), 'active', {matchCase: false}); + cy.contains(repoAdminSubscriptonDetails.getHistoryCommentSelector(0), 'confirmed'); +} + function assertAvailablePaymentMethods() { cy.contains('Pay later').should('not.exist'); cy.contains('paysafecard').should('not.exist');