From c101e5c4725567d0d767f79da0083d463796416f Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Mon, 25 May 2026 14:32:46 +0500 Subject: [PATCH 01/12] Support checkout session --- CHANGELOG.md | 4 + composer.json | 2 +- src/Service/PaymentService.php | 132 ++++++++++++++++++++++++++++++++- src/WexoAltaPay.php | 2 +- 4 files changed, 136 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c071a..50228d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## [2.1.0] +### Added +- Add support for MarketPay payment methods. + ## [2.0.10] ### Fixed - Fix: Payment not captured when order status is changed to done for digital products. diff --git a/composer.json b/composer.json index ca22d4b..6181673 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "AltaPay plugin for Shopware 6", "type": "shopware-platform-plugin", "license": "MIT", - "version": "2.0.10", + "version": "2.1.0", "authors": [ { "name": "AltaPay A/S", diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index a6bf19a..2c30ade 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -192,6 +192,42 @@ public function pay( } + $terminals = $this->getActiveTerminals($salesChannelContext); + if (($key = array_search($terminal, $terminals)) !== false) { + unset($terminals[$key]); + } + array_unshift($terminals, $terminal); + + $altapayCartToken = $order->getCustomFieldsValue(WexoAltaPay::ALTAPAY_CART_TOKEN); + $localSessionId = 'session-' . $altapayCartToken . '-' . $order->getOrderNumber(); + $altapaySessionId = null; + + try { + $session = $this->requestStack->getSession(); + $altapaySessionId = $session->get('altapay_checkout_session_id'); + } catch (\Exception $e) { + $session = null; + } + + if ($altapaySessionId !== $localSessionId) { + $altapaySessionId = $this->checkoutSession( + $localSessionId, + $terminals, + $order->getSalesChannelId(), + $order->getOrderNumber(), + $order->getAmountTotal(), + $order->getCurrency()->getIsoCode(), + $terminal + ); + + if ($session && $altapaySessionId) { + try { + $session->set('altapay_checkout_session_id', $altapaySessionId); + } catch (\Exception $e) { + } + } + } + $paymentRequestType = ($paymentMethod->getTranslated()['customFields'][self::ALTAPAY_AUTO_CAPTURE_CUSTOM_FIELD] ?? null) ? 'paymentAndCapture' : 'payment'; try { $altaPayResponse = $this->createPaymentRequest( @@ -199,7 +235,8 @@ public function pay( $transaction->getReturnUrl(), $salesChannelContext, $terminal, - $paymentRequestType + $paymentRequestType, + $altapaySessionId ); } catch (GuzzleException $e) { throw PaymentException::asyncProcessInterrupted( @@ -218,6 +255,91 @@ public function pay( 'X-Dynamic-JavaScript-Url' => (string)$altaPayResponse->DynamicJavascriptUrl ]); } + /** + * Get active terminals for the current sales channel, with fallback to global terminal if sales channel specific terminal is not set. + * + * @param SalesChannelContext $context + * + * @return array + */ + public function getActiveTerminals(SalesChannelContext $context): array + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('handlerIdentifier', PaymentService::class)); + $criteria->addFilter(new EqualsFilter('active', true)); + + $paymentMethods = $this->container->get('payment_method.repository') + ->search($criteria, $context->getContext()) + ->getEntities(); + + $terminals = []; + foreach ($paymentMethods as $paymentMethod) { + $terminal = $paymentMethod->getTranslated()['customFields'][self::ALTAPAY_TERMINAL_ID_CUSTOM_FIELD] ?? null; + $salesChannelTerminal = $paymentMethod->getTranslated()['customFields'][self::ALTAPAY_SALES_CHANNEL_TERMINAL_ID] ?? null; + + if (!empty($salesChannelTerminal)) { + $field = 'WexoAltaPay.config.' . $salesChannelTerminal; + $salesChannelTerminalValue = $this->systemConfigService->get($field, $context->getSalesChannelId()); + + if (!empty($salesChannelTerminalValue)) { + $terminal = $salesChannelTerminalValue; + } + } + + if (!empty($terminal)) { + $terminals[] = $terminal; + } + } + + return array_unique($terminals); + } + + /** + * Creates or retrieves an AltaPay checkout session. + * + * @throws GuzzleException + * @param string $sessionId + * @param array $terminals + * @param string $salesChannelId + * @param string $shopOrderId + * @param float $amount + * @param string $currency + * @param string $terminal + * + * @return string|null + */ + public function checkoutSession( + string $sessionId, + array $terminals, + string $salesChannelId, + string $shopOrderId, + float $amount, + string $currency, + string $terminal + ): ?string { + try { + $response = $this->getAltaPayClient($salesChannelId)->request('POST', 'checkoutSession', [ + 'form_params' => [ + 'session_id' => $sessionId, + 'terminals' => $terminals, + 'shop_orderid' => $shopOrderId, + 'amount' => $amount, + 'currency' => $currency, + 'terminal' => $terminal, + ] + ]); + + $xml = new SimpleXMLElement($response->getBody()->getContents()); + + if ($xml->Body->Session->Id) { + return (string)$xml->Body->Session->Id; + } + } catch (\Exception $e) { + $this->loggr->error('AltaPay CheckoutSession error: ' . $e->getMessage()); + } + + return null; + } /** * This method will be called after redirect from the external payment provider. @@ -506,7 +628,8 @@ public function createPaymentRequest( string $returnUrl, SalesChannelContext $context, string $terminal, - string $paymentRequestType + string $paymentRequestType, + string $sessionId = null ): SimpleXMLElement { $orderLines = []; foreach ($order->getLineItems() as $lineItem) { @@ -675,7 +798,12 @@ public function createPaymentRequest( ] ]; + if ($sessionId) { + $formParams['session_id'] = $sessionId; + } + $checkoutStyle = $this->systemConfigService->get('WexoAltaPay.config.checkoutStyle', $salesChannelId); + if (!empty($checkoutStyle)) { $formParams['form_template'] = $checkoutStyle; } diff --git a/src/WexoAltaPay.php b/src/WexoAltaPay.php index 90acbaa..97bef51 100644 --- a/src/WexoAltaPay.php +++ b/src/WexoAltaPay.php @@ -21,7 +21,7 @@ class WexoAltaPay extends Plugin public const ALTAPAY_FIELD_SET_NAME = "wexoAltaPay"; public const ALTAPAY_PAYMENT_METHOD_FIELD_SET_NAME = "wexoAltaPayPaymentMethod"; public const ALTAPAY_CART_TOKEN = "wexoAltaPayCartToken"; - public const ALTAPAY_PLUGIN_VERSION = '2.0.10'; + public const ALTAPAY_PLUGIN_VERSION = '2.1.0'; public const ALTAPAY_PLUGIN_NAME = 'WexoAltaPay'; public function update(UpdateContext $updateContext): void From 752b7a6885e6b8652d08e4941533287ebbc572fa Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Mon, 25 May 2026 15:30:49 +0500 Subject: [PATCH 02/12] Add SonarQube configuration to ignore specific PHP rule violations --- sonar-project.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sonar-project.properties b/sonar-project.properties index 6b906d5..e721a1a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,3 +2,7 @@ sonar.projectKey=AltaPay_plugin-shopware_6ff55bfb-f13e-4cba-a54c-9f0804511bbf sonar.projectBaseDir=src sonar.coverage.exclusions=** sonar.cpd.exclusions=** +# Ignore Sonar rule: Property names should match ^[a-z][a-zA-Z0-9]*$ +sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria.e1.ruleKey=php:S116 +sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.php \ No newline at end of file From 9def0d59aa886b29dc21beea7e532684284234c7 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Mon, 25 May 2026 15:51:13 +0500 Subject: [PATCH 03/12] Refactor PaymentService to improve session handling and update documentation Co-authored-by: Copilot --- sonar-project.properties | 6 +----- src/Service/PaymentService.php | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index e721a1a..7f8526a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,8 +1,4 @@ sonar.projectKey=AltaPay_plugin-shopware_6ff55bfb-f13e-4cba-a54c-9f0804511bbf sonar.projectBaseDir=src sonar.coverage.exclusions=** -sonar.cpd.exclusions=** -# Ignore Sonar rule: Property names should match ^[a-z][a-zA-Z0-9]*$ -sonar.issue.ignore.multicriteria=e1 -sonar.issue.ignore.multicriteria.e1.ruleKey=php:S116 -sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.php \ No newline at end of file +sonar.cpd.exclusions=** \ No newline at end of file diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index 2c30ade..1dcdb1c 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -201,7 +201,7 @@ public function pay( $altapayCartToken = $order->getCustomFieldsValue(WexoAltaPay::ALTAPAY_CART_TOKEN); $localSessionId = 'session-' . $altapayCartToken . '-' . $order->getOrderNumber(); $altapaySessionId = null; - + try { $session = $this->requestStack->getSession(); $altapaySessionId = $session->get('altapay_checkout_session_id'); @@ -220,10 +220,13 @@ public function pay( $terminal ); - if ($session && $altapaySessionId) { + if ($session && $altapaySessionId) { try { $session->set('altapay_checkout_session_id', $altapaySessionId); } catch (\Exception $e) { + $this->logger->warning('Unable to store AltaPay checkout session id in session', [ + 'exception' => $e, + ]); } } } @@ -257,8 +260,8 @@ public function pay( } /** * Get active terminals for the current sales channel, with fallback to global terminal if sales channel specific terminal is not set. - * - * @param SalesChannelContext $context + * + * @param SalesChannelContext $context * * @return array */ @@ -296,17 +299,17 @@ public function getActiveTerminals(SalesChannelContext $context): array /** * Creates or retrieves an AltaPay checkout session. - * + * * @throws GuzzleException - * @param string $sessionId - * @param array $terminals - * @param string $salesChannelId - * @param string $shopOrderId + * @param string $sessionId + * @param array $terminals + * @param string $salesChannelId + * @param string $shopOrderId * @param float $amount * @param string $currency * @param string $terminal * - * @return string|null + * @return string|null */ public function checkoutSession( string $sessionId, @@ -482,7 +485,7 @@ public function transactionCallback( } break; } - + if ($stateMachineState->getTechnicalName() !== OrderTransactionStates::STATE_OPEN) { break; } From 81b9866f40a76935289df47e876fa7b7a42965c1 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Tue, 2 Jun 2026 18:52:35 +0500 Subject: [PATCH 04/12] Support optional Gateway URL for AltaPay integration --- src/Resources/config/config.xml | 12 ++++++++++- src/Service/PaymentService.php | 38 +++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index 70710da..02e8526 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -28,13 +28,23 @@ shopName - true Enter the shop name that appears at the beginning of your AltaPay URL. For example: demoshop (from https://demoshop.altapaysecure.com) + + gatewayUrl + + + Optional. Use this field only if your gateway URL does not match the + standard AltaPay pattern. Enter the base URL provided by the gateway. + This field is used ONLY when "AltaPay ShopName" is empty. + When "AltaPay ShopName" is filled, this field is ignored and the + existing behavior is preserved. + + username true diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index 1dcdb1c..9ae3d40 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -338,7 +338,11 @@ public function checkoutSession( return (string)$xml->Body->Session->Id; } } catch (\Exception $e) { - $this->loggr->error('AltaPay CheckoutSession error: ' . $e->getMessage()); + $this->logger->error('AltaPay CheckoutSession error: ' . $e->getMessage(), [ + 'exception' => $e, + 'shopOrderId' => $shopOrderId, + 'sessionId' => $sessionId, + ]); } return null; @@ -586,12 +590,23 @@ public function transactionCallback( public function getAltaPayClient(string $salesChannelId): Client { - $paymentEnvironment = $this->systemConfigService->get('WexoAltaPay.config.paymentEnvironment', $salesChannelId); - $shopName = $this->systemConfigService->get('WexoAltaPay.config.shopName', $salesChannelId); + $paymentEnvironment = (string) $this->systemConfigService->get('WexoAltaPay.config.paymentEnvironment', $salesChannelId); + $shopName = (string) $this->systemConfigService->get('WexoAltaPay.config.shopName', $salesChannelId); + $gatewayUrl = (string) $this->systemConfigService->get('WexoAltaPay.config.gatewayUrl', $salesChannelId); $username = $this->systemConfigService->get('WexoAltaPay.config.username', $salesChannelId); $password = $this->systemConfigService->get('WexoAltaPay.config.password', $salesChannelId); + + // Default behavior (preserved for all existing customers): expand $PLACEHOLDER$ with shopName. + // Override behavior (new): if shopName is empty and a Gateway URL is configured, use it. + // The merchant/API/ path is appended automatically so the admin only needs to enter the host. + if (trim($shopName) === '' && trim($gatewayUrl) !== '') { + $baseUri = rtrim(trim($gatewayUrl), '/') . '/merchant/API/'; + } else { + $baseUri = str_replace('$PLACEHOLDER$', $shopName, $paymentEnvironment); + } + return new Client([ - 'base_uri' => str_replace('$PLACEHOLDER$', $shopName, $paymentEnvironment), + 'base_uri' => $baseUri, 'auth' => [ $username, $password @@ -601,12 +616,15 @@ public function getAltaPayClient(string $salesChannelId): Client /** * Escape hatch that can be overridden for custom line items. + * + * The $gatewayItemId is a short alphanumeric id (e.g. "i1") generated per request + * to satisfy the gateway's ITN field constraints (Alphanumeric+, 1-20 chars). */ - public function getUnknownLineItemFormat(OrderEntity $order, OrderLineItemEntity $lineItem): array + public function getUnknownLineItemFormat(OrderEntity $order, OrderLineItemEntity $lineItem, ?string $gatewayItemId = null): array { return [ 'description' => $lineItem->getLabel(), - 'itemId' => $lineItem->getId(), + 'itemId' => $gatewayItemId ?? $lineItem->getId(), 'quantity' => $lineItem->getQuantity(), 'unitPrice' => $lineItem->getPrice()?->getUnitPrice() ?? 0.0, 'taxAmount' => $lineItem->getPrice()?->getCalculatedTaxes()->getAmount() ?? 0.0, @@ -635,6 +653,7 @@ public function createPaymentRequest( string $sessionId = null ): SimpleXMLElement { $orderLines = []; + $itemIdCounter = 0; foreach ($order->getLineItems() as $lineItem) { $unitTaxRate = $lineItem->getPrice()?->getCalculatedTaxes()->getAmount() / $lineItem->getQuantity(); @@ -645,6 +664,7 @@ public function createPaymentRequest( } $taxAmount = $lineItem->getPrice()?->getCalculatedTaxes()->getAmount() ?? 0.0; + $gatewayItemId = 'item-' . (++$itemIdCounter); $orderLines[] = match ($lineItem->getType()) { LineItem::PRODUCT_LINE_ITEM_TYPE, @@ -654,7 +674,7 @@ public function createPaymentRequest( LineItem::DISCOUNT_LINE_ITEM, LineItem::PROMOTION_LINE_ITEM_TYPE => [ 'description' => $lineItem->getLabel(), - 'itemId' => $lineItem->getId(), + 'itemId' => $gatewayItemId, 'quantity' => $lineItem->getQuantity(), 'unitPrice' => $unitPrice, 'taxAmount' => $taxAmount, @@ -668,7 +688,7 @@ public function createPaymentRequest( LineItem::PROMOTION_LINE_ITEM_TYPE => 'handling', } ], - default => $this->getUnknownLineItemFormat($order, $lineItem) + default => $this->getUnknownLineItemFormat($order, $lineItem, $gatewayItemId) }; } foreach ($order->getDeliveries() as $delivery) { @@ -683,7 +703,7 @@ public function createPaymentRequest( $orderLines[] = [ 'description' => $delivery->getShippingMethod()?->getDescription() ?? 'Shipping', - 'itemId' => $delivery->getId(), + 'itemId' => 'shipping', 'quantity' => 1, 'unitPrice' => $netUnitPrice, 'taxAmount' => $taxAmount, From 5ba5f788d5e62e08b4ebe083487007a63b1574db Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Wed, 3 Jun 2026 10:20:34 +0000 Subject: [PATCH 05/12] Enhance Gateway URL configuration and improve session handling in PaymentService --- src/Resources/config/config.xml | 8 ++------ src/Service/PaymentService.php | 30 ++++++++++++++++-------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index 02e8526..0662f89 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -36,13 +36,9 @@ gatewayUrl - + - Optional. Use this field only if your gateway URL does not match the - standard AltaPay pattern. Enter the base URL provided by the gateway. - This field is used ONLY when "AltaPay ShopName" is empty. - When "AltaPay ShopName" is filled, this field is ignored and the - existing behavior is preserved. + Example: https://testgateway.altapaysecure.com or https://onl.preprod.mpg.market-pay.com diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index 9ae3d40..44caea9 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -39,6 +39,7 @@ use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory; use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService; use Shopware\Core\Checkout\Cart\Order\OrderConverter; +use Shopware\Core\Framework\Util\Hasher; use Shopware\Core\Framework\Uuid\Uuid; use Shopware\Core\Checkout\Cart\Order\RecalculationService; use Symfony\Contracts\Translation\TranslatorInterface; @@ -198,20 +199,23 @@ public function pay( } array_unshift($terminals, $terminal); - $altapayCartToken = $order->getCustomFieldsValue(WexoAltaPay::ALTAPAY_CART_TOKEN); - $localSessionId = 'session-' . $altapayCartToken . '-' . $order->getOrderNumber(); + $sessionKey = 'altapay_checkout_session_id_' . $order->getId(); $altapaySessionId = null; try { $session = $this->requestStack->getSession(); - $altapaySessionId = $session->get('altapay_checkout_session_id'); + $altapaySessionId = $session->get($sessionKey); } catch (\Exception $e) { $session = null; } - - if ($altapaySessionId !== $localSessionId) { + if (empty($altapaySessionId)) { + $sessionIdentifier = Hasher::hash([ + 'cartToken' => $order->getCustomFieldsValue(WexoAltaPay::ALTAPAY_CART_TOKEN), + 'orderId' => $order->getId(), + 'orderNumber' => $order->getOrderNumber() + ]); $altapaySessionId = $this->checkoutSession( - $localSessionId, + $sessionIdentifier, $terminals, $order->getSalesChannelId(), $order->getOrderNumber(), @@ -222,11 +226,9 @@ public function pay( if ($session && $altapaySessionId) { try { - $session->set('altapay_checkout_session_id', $altapaySessionId); + $session->set($sessionKey, $altapaySessionId); } catch (\Exception $e) { - $this->logger->warning('Unable to store AltaPay checkout session id in session', [ - 'exception' => $e, - ]); + // Optional: Failure to store in session should not interrupt the payment flow. } } } @@ -596,10 +598,10 @@ public function getAltaPayClient(string $salesChannelId): Client $username = $this->systemConfigService->get('WexoAltaPay.config.username', $salesChannelId); $password = $this->systemConfigService->get('WexoAltaPay.config.password', $salesChannelId); - // Default behavior (preserved for all existing customers): expand $PLACEHOLDER$ with shopName. - // Override behavior (new): if shopName is empty and a Gateway URL is configured, use it. - // The merchant/API/ path is appended automatically so the admin only needs to enter the host. - if (trim($shopName) === '' && trim($gatewayUrl) !== '') { + // Default behavior: expand $PLACEHOLDER$ with shopName. + // Override behavior: if a Gateway URL is configured, it overrides the shopName. + // The merchant/API/ path is appended automatically. + if (!empty($gatewayUrl)) { $baseUri = rtrim(trim($gatewayUrl), '/') . '/merchant/API/'; } else { $baseUri = str_replace('$PLACEHOLDER$', $shopName, $paymentEnvironment); From 474d17804fc3a1f994bbd95439f954c98c157769 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Wed, 3 Jun 2026 10:56:10 +0000 Subject: [PATCH 06/12] Update Gateway URL instructions for improved clarity and flexibility --- wiki.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki.md b/wiki.md index 3e22c28..d6c491d 100644 --- a/wiki.md +++ b/wiki.md @@ -59,7 +59,7 @@ be provided by AltaPay. ![shopware_setup_altapay_credentials](Docs/Configuration/shopware_setup_altapay_credentials.png) - > **Note:** The **AltaPay ShopName** should match the subdomain at the beginning of your AltaPay URL. For example, if your payment URL is `https://demoshop.altapaysecure.com`, then the AltaPay ShopName is `demoshop`. + > **Note:** We recommend entering the complete URL in the **Gateway URL** field (e.g., `https://testgateway.altapaysecure.com` or `https://onl.preprod.mpg.market-pay.com`). If provided, this will override the ShopName. If you prefer to use the **AltaPay ShopName** field, enter only the subdomain at the beginning of your AltaPay URL (e.g., `demoshop` from `https://demoshop.altapaysecure.com`). 3. Enable the **Change the order status to In-Progress after a successful payment** setting if you want to update the order status to **In Progress** automatically after a successful payment. From f656bb671d31faddc44528cb88b524f2666e41ef Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Wed, 3 Jun 2026 10:58:38 +0000 Subject: [PATCH 07/12] Fix newline issue in sonar-project.properties for proper formatting --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 7f8526a..6b906d5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,4 @@ sonar.projectKey=AltaPay_plugin-shopware_6ff55bfb-f13e-4cba-a54c-9f0804511bbf sonar.projectBaseDir=src sonar.coverage.exclusions=** -sonar.cpd.exclusions=** \ No newline at end of file +sonar.cpd.exclusions=** From 07a37528249d09fcd6046213935469f0c46588d8 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Wed, 3 Jun 2026 11:11:47 +0000 Subject: [PATCH 08/12] Refactor shipping item ID generation in PaymentService for uniqueness --- src/Service/PaymentService.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index 44caea9..9a79207 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -619,8 +619,6 @@ public function getAltaPayClient(string $salesChannelId): Client /** * Escape hatch that can be overridden for custom line items. * - * The $gatewayItemId is a short alphanumeric id (e.g. "i1") generated per request - * to satisfy the gateway's ITN field constraints (Alphanumeric+, 1-20 chars). */ public function getUnknownLineItemFormat(OrderEntity $order, OrderLineItemEntity $lineItem, ?string $gatewayItemId = null): array { @@ -693,6 +691,7 @@ public function createPaymentRequest( default => $this->getUnknownLineItemFormat($order, $lineItem, $gatewayItemId) }; } + $shippingIdCounter = 0; foreach ($order->getDeliveries() as $delivery) { $netUnitPrice = round($delivery->getShippingCosts()->getUnitPrice() - $delivery->getShippingCosts()->getCalculatedTaxes()->getAmount(), 2); @@ -705,7 +704,7 @@ public function createPaymentRequest( $orderLines[] = [ 'description' => $delivery->getShippingMethod()?->getDescription() ?? 'Shipping', - 'itemId' => 'shipping', + 'itemId' => 'shipping-' . (++$shippingIdCounter), 'quantity' => 1, 'unitPrice' => $netUnitPrice, 'taxAmount' => $taxAmount, From ced4195d1e40d6c2b7aa6a7c9da8985779732a49 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Wed, 3 Jun 2026 11:14:05 +0000 Subject: [PATCH 09/12] Remove unnecessary comments in getUnknownLineItemFormat and streamline checkoutStyle handling --- src/Service/PaymentService.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index 9a79207..378de85 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -618,7 +618,6 @@ public function getAltaPayClient(string $salesChannelId): Client /** * Escape hatch that can be overridden for custom line items. - * */ public function getUnknownLineItemFormat(OrderEntity $order, OrderLineItemEntity $lineItem, ?string $gatewayItemId = null): array { @@ -827,7 +826,6 @@ public function createPaymentRequest( } $checkoutStyle = $this->systemConfigService->get('WexoAltaPay.config.checkoutStyle', $salesChannelId); - if (!empty($checkoutStyle)) { $formParams['form_template'] = $checkoutStyle; } From ea67a2737c5923e710b4057151c2779bfc05c29d Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Thu, 4 Jun 2026 17:28:13 +0500 Subject: [PATCH 10/12] Add SystemConfigSubscriber to validate AltaPay gateway URL and ensure HTTPS usage Co-authored-by: Copilot --- src/Exception/AltaPayConfigException.php | 20 ++++++++ src/Resources/config/services.xml | 5 ++ src/Service/PaymentService.php | 9 +++- src/Subscriber/SystemConfigSubscriber.php | 56 +++++++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/Exception/AltaPayConfigException.php create mode 100644 src/Subscriber/SystemConfigSubscriber.php diff --git a/src/Exception/AltaPayConfigException.php b/src/Exception/AltaPayConfigException.php new file mode 100644 index 0000000..c53a2a1 --- /dev/null +++ b/src/Exception/AltaPayConfigException.php @@ -0,0 +1,20 @@ + + + + + + diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index 378de85..fad19e1 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -602,7 +602,14 @@ public function getAltaPayClient(string $salesChannelId): Client // Override behavior: if a Gateway URL is configured, it overrides the shopName. // The merchant/API/ path is appended automatically. if (!empty($gatewayUrl)) { - $baseUri = rtrim(trim($gatewayUrl), '/') . '/merchant/API/'; + $gatewayUrl = trim($gatewayUrl); + if (!str_starts_with(strtolower($gatewayUrl), 'https://')) { + $this->logger->error('AltaPay gateway URL is not HTTPS. Request would be sent unencrypted.', [ + 'gatewayUrl' => $gatewayUrl, + 'salesChannelId' => $salesChannelId, + ]); + } + $baseUri = rtrim($gatewayUrl, '/') . '/merchant/API/'; } else { $baseUri = str_replace('$PLACEHOLDER$', $shopName, $paymentEnvironment); } diff --git a/src/Subscriber/SystemConfigSubscriber.php b/src/Subscriber/SystemConfigSubscriber.php new file mode 100644 index 0000000..f015b46 --- /dev/null +++ b/src/Subscriber/SystemConfigSubscriber.php @@ -0,0 +1,56 @@ + 'onBeforeSystemConfigChanged', + ]; + } + + public function onBeforeSystemConfigChanged(BeforeSystemConfigChangedEvent $event): void + { + if ($event->getKey() !== self::GATEWAY_URL_KEY) { + return; + } + + $value = $event->getValue(); + + if ($value === null || $value === '') { + return; + } + + if (!is_string($value)) { + return; + } + + $trimmed = trim($value); + if ($trimmed === '') { + return; + } + + if (!str_starts_with(strtolower($trimmed), 'https://')) { + $this->logger->error('Rejected AltaPay gateway URL because it is not HTTPS.', [ + 'gatewayUrl' => $trimmed, + 'salesChannelId' => $event->getSalesChannelId(), + ]); + + throw AltaPayConfigException::gatewayUrlNotHttps(); + } + } +} From 361ffe6c25e5727c68ed5cddcd9134b232f58e48 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Thu, 4 Jun 2026 17:38:43 +0500 Subject: [PATCH 11/12] Simplify value validation in SystemConfigSubscriber by removing redundant checks Co-authored-by: Copilot --- src/Subscriber/SystemConfigSubscriber.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Subscriber/SystemConfigSubscriber.php b/src/Subscriber/SystemConfigSubscriber.php index f015b46..1d291b1 100644 --- a/src/Subscriber/SystemConfigSubscriber.php +++ b/src/Subscriber/SystemConfigSubscriber.php @@ -30,16 +30,8 @@ public function onBeforeSystemConfigChanged(BeforeSystemConfigChangedEvent $even } $value = $event->getValue(); + $trimmed = is_string($value) ? trim($value) : ''; - if ($value === null || $value === '') { - return; - } - - if (!is_string($value)) { - return; - } - - $trimmed = trim($value); if ($trimmed === '') { return; } From cf3184f726c4322712b272878203d3a8a6223ae4 Mon Sep 17 00:00:00 2001 From: Bushra Asif Date: Thu, 4 Jun 2026 21:11:21 +0500 Subject: [PATCH 12/12] Remove AltaPayConfigException and update PaymentService to enforce HTTPS for gateway URLs Co-authored-by: Copilot --- src/Exception/AltaPayConfigException.php | 20 -------------------- src/Resources/config/services.xml | 5 ----- src/Service/PaymentService.php | 9 ++++----- 3 files changed, 4 insertions(+), 30 deletions(-) delete mode 100644 src/Exception/AltaPayConfigException.php diff --git a/src/Exception/AltaPayConfigException.php b/src/Exception/AltaPayConfigException.php deleted file mode 100644 index c53a2a1..0000000 --- a/src/Exception/AltaPayConfigException.php +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index fad19e1..09e1b7f 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -603,11 +603,10 @@ public function getAltaPayClient(string $salesChannelId): Client // The merchant/API/ path is appended automatically. if (!empty($gatewayUrl)) { $gatewayUrl = trim($gatewayUrl); - if (!str_starts_with(strtolower($gatewayUrl), 'https://')) { - $this->logger->error('AltaPay gateway URL is not HTTPS. Request would be sent unencrypted.', [ - 'gatewayUrl' => $gatewayUrl, - 'salesChannelId' => $salesChannelId, - ]); + if (preg_match('#^http://#i', $gatewayUrl)) { + $gatewayUrl = 'https://' . substr($gatewayUrl, 7); + } elseif (!preg_match('#^https://#i', $gatewayUrl)) { + $gatewayUrl = 'https://' . ltrim($gatewayUrl, '/'); } $baseUri = rtrim($gatewayUrl, '/') . '/merchant/API/'; } else {