diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce5b0a9..e787ec1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# 7.3.4
+- Shopware 6.7.10.0 compatible
+- Fix for cart being lost when changing payment methods
+- Fixed for incorrectly reference function (getState())
+- Fix cache not being cleaned after payment errors
+- Fix missing shipping costs
+- Minor fix for Customer Id null inconsistency
+
# 7.3.3
- Shopware 6.7.9.0 compatible
- Fix for only showing 25 sales channels in selector
diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md
index 47e3d4d..6fc9485 100644
--- a/CHANGELOG_de-DE.md
+++ b/CHANGELOG_de-DE.md
@@ -1,3 +1,11 @@
+# 7.3.4
+- Kompatibel mit Shopware 6.7.10.0
+- Problem behoben, bei dem der Warenkorb beim Ändern der Zahlungsmethode verloren ging
+- Fehlerhafte Referenzierung der Funktion getState() behoben
+- Problem behoben, bei dem der Cache nach Zahlungsfehlern nicht geleert wurde
+- Fehlende Versandkosten behoben
+- Kleinere Korrektur eines Fehlers mit der Kunden-ID (null)
+
# 7.3.3
- Kompatibel mit Shopware 6.7.9.0
- Problem behoben, dass im Selektor nur 25 Vertriebskanäle angezeigt wurden
diff --git a/README.md b/README.md
index cdc6a8c..cec96e4 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,10 @@ Please note that this plugin is for versions 6.5, 6.6 or 6.7. For the 6.4 plugin
## Documentation
-- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.3/docs/en/documentation.html)
-- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.3/docs/de/documentation.html)
-- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.3/docs/fr/documentation.html)
-- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.3/docs/it/documentation.html)
+- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.4/docs/en/documentation.html)
+- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.4/docs/de/documentation.html)
+- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.4/docs/fr/documentation.html)
+- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.4/docs/it/documentation.html)
## Installation
diff --git a/composer.json b/composer.json
index 1a0d5ab..25dfff9 100644
--- a/composer.json
+++ b/composer.json
@@ -59,5 +59,5 @@
"vrpayment/sdk": "^4.0.0"
},
"type": "shopware-platform-plugin",
- "version": "7.3.3"
+ "version": "7.3.4"
}
diff --git a/docs/de/documentation.html b/docs/de/documentation.html
index 778f8fa..f97b6c1 100644
--- a/docs/de/documentation.html
+++ b/docs/de/documentation.html
@@ -23,7 +23,7 @@
-
+
Source
diff --git a/docs/en/documentation.html b/docs/en/documentation.html
index 4d7b37d..a2b2c4f 100644
--- a/docs/en/documentation.html
+++ b/docs/en/documentation.html
@@ -23,7 +23,7 @@
-
+
Source
diff --git a/docs/fr/documentation.html b/docs/fr/documentation.html
index 6f62905..a5a3bb2 100644
--- a/docs/fr/documentation.html
+++ b/docs/fr/documentation.html
@@ -23,7 +23,7 @@
-
+
Source
diff --git a/docs/it/documentation.html b/docs/it/documentation.html
index 4d44c60..8a38301 100644
--- a/docs/it/documentation.html
+++ b/docs/it/documentation.html
@@ -23,7 +23,7 @@
-
+
Source
diff --git a/src/Core/Api/Transaction/Service/TransactionService.php b/src/Core/Api/Transaction/Service/TransactionService.php
index 836f69a..6937ae7 100644
--- a/src/Core/Api/Transaction/Service/TransactionService.php
+++ b/src/Core/Api/Transaction/Service/TransactionService.php
@@ -32,6 +32,7 @@ use VRPayment\Sdk\Model\{
LineItemAttributeCreate,
LineItemCreate,
LineItemType,
+ TaxCreate,
TokenizationMode,
Transaction,
TransactionCreate,
@@ -606,9 +607,17 @@ class TransactionService
if ($customer === null) {
throw new \Exception('Customer is required to create a transaction');
}
- $lineItems = $this->extractLineItems($event);
+ $lineItems = $this->extractLineItems(
+ $event,
+ $salesChannelContext,
+ );
- $customerId = "";
+ /*
+ * For guest checkouts, the customer ID is set to null rather than an empty string.
+ * This ensures consistency with TransactionPayload which also uses null for guests,
+ * preventing the Portal from treating the difference as an update/change in customer details.
+ */
+ $customerId = null;
if ($customer->getGuest() === false) {
$customerId = $customer->getCustomerNumber();
}
@@ -690,13 +699,16 @@ class TransactionService
}
/**
- * Extracts line items from the given source (Event or Cart).
+ * Extracts line items from the given source (Event or Cart) and appends shipping costs.
*
* @param mixed $source
+ * @param SalesChannelContext|null $salesChannelContext
* @return array
*/
- public function extractLineItems($source): array
- {
+ public function extractLineItems(
+ $source,
+ ?SalesChannelContext $salesChannelContext = null,
+ ): array {
$lineItems = [];
if ($source) {
if ($source instanceof CheckoutConfirmPageLoadedEvent) {
@@ -721,6 +733,38 @@ class TransactionService
$lineItems[] = $this->createTempLineItem($cartLineItem);
}
}
+
+ // Extract and append shipping costs as a line item if applicable.
+ $shippingCosts = null;
+ $taxStatus = 'gross';
+
+ if ($source instanceof CheckoutConfirmPageLoadedEvent) {
+ $cart = $source->getPage()->getCart();
+ $shippingCosts = $cart->getDeliveries()->getShippingCosts();
+ if ($salesChannelContext !== null) {
+ $taxStatus = $salesChannelContext->getTaxState();
+ }
+ } elseif ($source instanceof AccountEditOrderPageLoadedEvent) {
+ $order = $source->getPage()->getOrder();
+ $shippingCosts = $order->getShippingCosts();
+ $taxStatus = $order->getTaxStatus();
+ } elseif ($source instanceof \Shopware\Core\Checkout\Cart\Cart) {
+ $shippingCosts = $source->getDeliveries()->getShippingCosts();
+ if ($salesChannelContext !== null) {
+ $taxStatus = $salesChannelContext->getTaxState();
+ }
+ }
+
+ if ($shippingCosts !== null) {
+ $shippingLineItem = $this->extractShippingLineItem(
+ $shippingCosts,
+ $taxStatus,
+ $salesChannelContext,
+ );
+ if ($shippingLineItem !== null) {
+ $lineItems[] = $shippingLineItem;
+ }
+ }
}
return $lineItems;
}
@@ -862,6 +906,7 @@ class TransactionService
$address->setOrganizationName($addressEntity->getCompany());
$address->setPhoneNumber($addressEntity->getPhoneNumber());
$address->setCountry($addressEntity->getCountry()->getIso());
+ $address->setCity($addressEntity->getCity() ?: '');
$postalState = $addressEntity?->getCountryState()?->getName()
?: $addressEntity?->getCountryState()?->getShortCode()
@@ -967,7 +1012,7 @@ class TransactionService
*
* @param SalesChannelContext $salesChannelContext
*/
- private function clearTransactionIdFromContext(SalesChannelContext $salesChannelContext): void
+ public function clearTransactionIdFromContext(SalesChannelContext $salesChannelContext): void
{
// Clear from cache key.
$cacheKey = $this->getPendingTransactionCacheKey($salesChannelContext);
@@ -1028,4 +1073,76 @@ class TransactionService
return $lineItem;
}
+
+ /**
+ * Extracts shipping line item from cart/order shipping costs.
+ *
+ * @param mixed $shippingCosts
+ * @param string $taxStatus
+ * @param SalesChannelContext|null $salesChannelContext
+ * @return LineItemCreate|null
+ */
+ private function extractShippingLineItem(
+ $shippingCosts,
+ string $taxStatus,
+ ?SalesChannelContext $salesChannelContext = null,
+ ): ?LineItemCreate {
+ // When shipping costs are extracted from a Cart, they are returned as a PriceCollection
+ // containing multiple CalculatedPrice items. We must sum them to get a single aggregated price.
+ if ($shippingCosts instanceof \Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection) {
+ $shippingCosts = $shippingCosts->sum();
+ }
+
+ if (!$shippingCosts instanceof \Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice) {
+ return null;
+ }
+
+ if ($shippingCosts->getTotalPrice() <= 0) {
+ return null;
+ }
+
+ $amount = $shippingCosts->getTotalPrice();
+ if ($taxStatus === 'net') {
+ $amount += $shippingCosts->getCalculatedTaxes()->getAmount();
+ }
+ $roundedAmount = $this->round($amount);
+
+ $shippingMethodName = $salesChannelContext?->getShippingMethod()?->getName();
+ $translator = $this->container->has('translator') ? $this->container->get('translator') : null;
+ $fallbackName = $translator ? $translator->trans('vrpayment.payload.shipping.name') : 'Shipping';
+ $shippingName = $shippingMethodName ?? $fallbackName;
+
+ $shippingLineItem = new LineItemCreate();
+ $shippingLineItem->setAmountIncludingTax($roundedAmount)
+ ->setName($this->fixLength($shippingName . ' ' . ($translator ? $translator->trans('vrpayment.payload.shipping.lineItem') : 'Shipping'), 150))
+ ->setQuantity($shippingCosts->getQuantity() ?? 1)
+ ->setSku($this->fixLength($shippingName . '-Shipping', 200))
+ ->setType(LineItemType::SHIPPING)
+ ->setUniqueId($this->fixLength($shippingName . '-Shipping', 200));
+
+ if ($taxStatus !== 'tax-free') {
+ $taxes = [];
+ foreach ($shippingCosts->getCalculatedTaxes() as $calculatedTax) {
+ $tax = (new TaxCreate())
+ ->setRate($calculatedTax->getTaxRate())
+ ->setTitle($this->fixLength($shippingName . ' : ' . $calculatedTax->getTaxRate(), 40));
+ $taxes[] = $tax;
+ }
+ $shippingLineItem->setTaxes($taxes);
+ }
+
+ return $shippingLineItem;
+ }
+
+ /**
+ * Fix string length to specific length.
+ *
+ * @param string $string
+ * @param int $maxLength
+ * @return string
+ */
+ private function fixLength(string $string, int $maxLength): string
+ {
+ return \mb_substr($string, 0, $maxLength, 'UTF-8');
+ }
}
diff --git a/src/Core/Api/WebHooks/Controller/WebHookController.php b/src/Core/Api/WebHooks/Controller/WebHookController.php
index 2f2636b..118f4bd 100644
--- a/src/Core/Api/WebHooks/Controller/WebHookController.php
+++ b/src/Core/Api/WebHooks/Controller/WebHookController.php
@@ -700,16 +700,6 @@ class WebHookController extends AbstractController {
private function unholdAndCancelDelivery(string $orderId, Context $context): void
{
$order = $this->getOrderEntity($orderId, $context);
- try {
- $this->orderService->orderStateTransition(
- $order->getId(),
- StateMachineTransitionActions::ACTION_CANCEL,
- new ParameterBag(),
- $context
- );
- } catch (\Exception $exception) {
- $this->logger->info($exception->getMessage(), $exception->getTrace());
- }
try {
/**
diff --git a/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php b/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php
index e77e898..3c29cd2 100644
--- a/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php
+++ b/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php
@@ -378,16 +378,6 @@ abstract class WebHookStrategyBase implements WebHookStrategyInterface {
protected function unholdAndCancelDelivery(string $orderId, Context $context): void
{
$order = $this->getOrderEntity($orderId, $context);
- try {
- $this->orderService->orderStateTransition(
- $order->getId(),
- StateMachineTransitionActions::ACTION_CANCEL,
- new ParameterBag(),
- $context
- );
- } catch (\Exception $exception) {
- $this->logger->info($exception->getMessage(), $exception->getTrace());
- }
try {
diff --git a/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php b/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php
index 4057afa..953c406 100644
--- a/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php
+++ b/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php
@@ -151,10 +151,18 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
}
return new RedirectResponse($redirectUrl);
} catch (\Throwable $e) {
- $request->getSession()->remove('transactionId');
+ // Clear the transaction ID from the cache or session context depending on whether the SalesChannelContext was initialized
+ // to prevent subsequent checkout attempts from reusing a failed/invalid transaction ID.
+ if (isset($salesChannelContext)) {
+ $this->pluginTransactionService->clearTransactionIdFromContext($salesChannelContext);
+ } else {
+ $request->getSession()->remove('transactionId');
+ }
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
$this->logger->critical($errorMessage);
- if ($orderTransaction->getState()?->getTechnicalName() === OrderTransactionStates::STATE_CANCELLED) {
+ // If the transaction has already been marked as cancelled (e.g. via webhook or concurrent request),
+ // throw an interrupted exception to signal that payment processing cannot be finalized normally.
+ if ($orderTransaction->getStateMachineState()?->getTechnicalName() === OrderTransactionStates::STATE_CANCELLED) {
throw PaymentException::asyncFinalizeInterrupted($orderTransaction->getOrder()->getId(), $errorMessage);
}
throw PaymentException::customerCanceled($transaction->getOrderTransactionId(), $errorMessage);
@@ -196,12 +204,27 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
$transactionEntity->getSalesChannelId()
);
- if (in_array($vRPaymentTransaction->getState(), [TransactionState::FAILED])) {
+ if (in_array($vRPaymentTransaction->getState(), [TransactionState::FAILED, TransactionState::DECLINE, TransactionState::VOIDED,])) {
$errorMessage = strtr('Customer canceled payment for :orderId on SalesChannel :salesChannelName', [
':orderId' => $orderTransaction->getOrder()->getId(),
':salesChannelName' => $transactionEntity->getSalesChannelId(),
]);
- $request->getSession()->remove('transactionId');
+
+ // Retrieve the sales channel context parameters to clear the transaction ID from cache or session context,
+ // ensuring that a failed transaction is not reused when the customer retries payment.
+ $token = $this->getContextToken($request);
+ if ($token) {
+ $parameters = new SalesChannelContextServiceParameters(
+ $transactionEntity->getSalesChannelId(),
+ $token,
+ originalContext: $context,
+ );
+ $salesChannelContext = $this->salesChannelContextService->get($parameters);
+ $this->pluginTransactionService->clearTransactionIdFromContext($salesChannelContext);
+ } else {
+ $request->getSession()->remove('transactionId');
+ }
+
$this->logger->info($errorMessage);
throw PaymentException::customerCanceled($orderTransactionId, $errorMessage);
}
diff --git a/src/Core/Checkout/Service/TransactionManagementService.php b/src/Core/Checkout/Service/TransactionManagementService.php
index a2bad81..95dc9ea 100644
--- a/src/Core/Checkout/Service/TransactionManagementService.php
+++ b/src/Core/Checkout/Service/TransactionManagementService.php
@@ -112,7 +112,10 @@ class TransactionManagementService
$addressHash = $customer ? md5(json_encode((array) $customer)) : null;
$currency = (string)$salesChannelContext->getCurrency()->getIsoCode();
- $lineItems = $this->transactionService->extractLineItems($event);
+ $lineItems = $this->transactionService->extractLineItems(
+ $event,
+ $salesChannelContext,
+ );
$lineItemHash = !empty($lineItems) ? md5(json_encode($lineItems)) : $oldLineItemHash;
$needsUpdate = ($oldAddressHash !== $addressHash)
diff --git a/src/Core/Checkout/StoreApi/Route/CartRecoveryRoute.php b/src/Core/Checkout/StoreApi/Route/CartRecoveryRoute.php
index 98f3039..7831b93 100644
--- a/src/Core/Checkout/StoreApi/Route/CartRecoveryRoute.php
+++ b/src/Core/Checkout/StoreApi/Route/CartRecoveryRoute.php
@@ -9,6 +9,7 @@ use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
+use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
use VRPaymentPayment\Core\Checkout\Service\CartRecoveryService;
#[Package('checkout')]
@@ -26,11 +27,21 @@ class CartRecoveryRoute
private CartRecoveryService $cartRecoveryService;
/**
- * @param CartRecoveryService $cartRecoveryService
+ * @var TransactionService
+ * Service to clear and manage transactions.
*/
- public function __construct(CartRecoveryService $cartRecoveryService)
- {
+ private TransactionService $transactionService;
+
+ /**
+ * @param CartRecoveryService $cartRecoveryService
+ * @param TransactionService $transactionService
+ */
+ public function __construct(
+ CartRecoveryService $cartRecoveryService,
+ TransactionService $transactionService,
+ ) {
$this->cartRecoveryService = $cartRecoveryService;
+ $this->transactionService = $transactionService;
}
/**
@@ -57,6 +68,10 @@ class CartRecoveryRoute
return new JsonResponse(['error' => 'Sales channel mismatch'], 403);
}
+ // Clear the transaction ID from cache and session to prevent the subsequent
+ // checkout attempt from reusing a stale/failed transaction.
+ $this->transactionService->clearTransactionIdFromContext($context);
+
// Perform the cart reconstruction.
$cart = $this->cartRecoveryService->recreateCartFromOrder($order, $context);
diff --git a/src/Core/Storefront/Checkout/Controller/CheckoutController.php b/src/Core/Storefront/Checkout/Controller/CheckoutController.php
index 366e0da..a084627 100644
--- a/src/Core/Storefront/Checkout/Controller/CheckoutController.php
+++ b/src/Core/Storefront/Checkout/Controller/CheckoutController.php
@@ -167,6 +167,9 @@ class CheckoutController extends StorefrontController
if ($this->logger) {
$this->logger->error($e->getMessage());
}
+ // Clear the transaction ID from the cache/session context on error to ensure
+ // subsequent payment attempts will create/retrieve a clean transaction.
+ $this->transactionService->clearTransactionIdFromContext($salesChannelContext);
$this->addFlash('danger', $this->trans('vrpayment.paymentMethod.notAvailable'));
return $this->redirectToRoute('frontend.home.page');
}
@@ -219,6 +222,10 @@ class CheckoutController extends StorefrontController
$this->addFlash('danger', $transaction->getUserFailureMessage());
}
}
+ // Clear the transaction ID from cache and session to prevent the subsequent
+ // checkout attempt from reusing a stale/failed transaction.
+ $this->transactionService->clearTransactionIdFromContext($salesChannelContext);
+
$this->cartRecoveryService->recreateCartFromOrder($order, $salesChannelContext);
} catch (\Exception $exception) {
$this->addFlash('danger', $this->trans('error.addToCartError'));
diff --git a/src/Core/Util/Analytics/Analytics.php b/src/Core/Util/Analytics/Analytics.php
index 42ab496..b85978b 100644
--- a/src/Core/Util/Analytics/Analytics.php
+++ b/src/Core/Util/Analytics/Analytics.php
@@ -26,7 +26,7 @@ class Analytics {
self::SHOP_SYSTEM => 'shopware',
self::SHOP_SYSTEM_VERSION => '6',
self::SHOP_SYSTEM_AND_VERSION => 'shopware-6',
- self::PLUGIN_SYSTEM_VERSION => '7.3.3',
+ self::PLUGIN_SYSTEM_VERSION => '7.3.4',
];
}
diff --git a/src/Resources/config/services/core/store_api.xml b/src/Resources/config/services/core/store_api.xml
index f804de0..4edd3c1 100644
--- a/src/Resources/config/services/core/store_api.xml
+++ b/src/Resources/config/services/core/store_api.xml
@@ -18,6 +18,7 @@
+