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');