diff --git a/CHANGELOG.md b/CHANGELOG.md index 117d2e7..e2f7281 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.0.9] +### Added +- Support for sales channel–specific terminals for payment methods. + ## [2.0.8] ### Fixed - Payment status was shown as Failed even though the order was placed successfully. diff --git a/Docs/Configuration/shopware_configure_altapay_terminal_detail.png b/Docs/Configuration/shopware_configure_altapay_terminal_detail.png index e967688..f49261a 100644 Binary files a/Docs/Configuration/shopware_configure_altapay_terminal_detail.png and b/Docs/Configuration/shopware_configure_altapay_terminal_detail.png differ diff --git a/Docs/Configuration/shopware_setup_altapay_credentials.png b/Docs/Configuration/shopware_setup_altapay_credentials.png index d6211ee..a098e53 100644 Binary files a/Docs/Configuration/shopware_setup_altapay_credentials.png and b/Docs/Configuration/shopware_setup_altapay_credentials.png differ diff --git a/composer.json b/composer.json index 1adda35..8ba8815 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.8", + "version": "2.0.9", "authors": [ { "name": "AltaPay A/S", diff --git a/src/Controller/ApiController.php b/src/Controller/ApiController.php index 9e23c1f..c4ccf4b 100644 --- a/src/Controller/ApiController.php +++ b/src/Controller/ApiController.php @@ -29,11 +29,11 @@ public function __construct( public function getPayments(Request $request): JsonApiResponse|Response { $orderId = $request->get('orderId'); + /** @var ?OrderEntity $order */ $order = $this->orderRepository->search(new Criteria([$orderId]), Context::createDefaultContext())->first(); if (!$order) { return new Response(status: 400); } - /** @var $order OrderEntity */ $response = $this->paymentService->getTransaction($order, $order->getSalesChannelId()); $responseAsXml = new \SimpleXMLElement($response->getBody()->getContents()); return new JsonApiResponse(json_encode($responseAsXml)); @@ -45,10 +45,7 @@ public function capture(Request $request): JsonApiResponse|Response $orderId = $request->get('orderId'); $captureAmount = $request->get('captureAmount'); $context = Context::createDefaultContext(); - $criteria = new Criteria([$orderId]); - $criteria->addAssociation('transactions') - ->addSorting(new FieldSorting('transactions.createdAt', FieldSorting::DESCENDING)); - $order = $this->orderRepository->search($criteria, $context)->first(); + $order = $this->getOrder($orderId, $context); if (!$order) { return new JsonApiResponse(['success' => false, 'message' => 'Order not found.'], 400); } @@ -69,7 +66,6 @@ public function capture(Request $request): JsonApiResponse|Response return new JsonApiResponse(['success' => false, 'message' => 'The capture amount exceeds the remaining amount.'], 400); } - /** @var $order OrderEntity */ $response = $this->paymentService->captureReservation($order, $order->getSalesChannelId(), null, $captureAmount); $responseAsXml = new \SimpleXMLElement($response->getBody()->getContents()); if ((string)$responseAsXml->Body?->Result === "Success") { @@ -96,10 +92,7 @@ public function refund(Request $request): JsonApiResponse|Response $orderId = $request->get('orderId'); $refundAmount = $request->get('refundAmount'); $context = Context::createDefaultContext(); - $criteria = new Criteria([$orderId]); - $criteria->addAssociation('transactions') - ->addSorting(new FieldSorting('transactions.createdAt', FieldSorting::DESCENDING)); - $order = $this->orderRepository->search($criteria, $context)->first(); + $order = $this->getOrder($orderId, $context); if (!$order) { return new JsonApiResponse(['success' => false, 'message' => 'Order not found.'], 400); } @@ -120,7 +113,6 @@ public function refund(Request $request): JsonApiResponse|Response return new JsonApiResponse(['success' => false, 'message' => 'The order has already been fully refunded.'], 400); } - /** @var $order OrderEntity */ $response = $this->paymentService->refundCapturedReservation( $order->getCustomFields()[PaymentService::ALTAPAY_TRANSACTION_ID_CUSTOM_FIELD], $order->getSalesChannelId(), @@ -149,14 +141,11 @@ public function cancel(Request $request): JsonApiResponse|Response { $orderId = $request->get('orderId'); $context = Context::createDefaultContext(); - $criteria = new Criteria([$orderId]); - $criteria->addAssociation('transactions') - ->addSorting(new FieldSorting('transactions.createdAt', FieldSorting::DESCENDING)); - $order = $this->orderRepository->search($criteria, $context)->first(); + $order = $this->getOrder($orderId, $context); if (!$order) { return new Response(status: 400); } - /** @var $order OrderEntity */ + $response = $this->paymentService->releaseReservation( $order->getCustomFields()[PaymentService::ALTAPAY_TRANSACTION_ID_CUSTOM_FIELD], $order->getSalesChannelId() @@ -164,10 +153,22 @@ public function cancel(Request $request): JsonApiResponse|Response $responseAsXml = new \SimpleXMLElement($response->getBody()->getContents()); if ((string)$responseAsXml->Body?->Result === "Success") { $this->orderTransactionStateHandler->cancel( - $order->getTransactions()->first()->getId(), // todo get right transaction + $order->getTransactions()?->first()->getId(), $context ); } return new JsonApiResponse(json_encode($responseAsXml)); } + private function getOrder(string $orderId, ?Context $context = null): ?OrderEntity + { + if ($context === null) { + $context = Context::createDefaultContext(); + } + $criteria = new Criteria([$orderId]); + $criteria->addAssociation('transactions') + ->addSorting(new FieldSorting('transactions.createdAt', FieldSorting::DESCENDING)); + + /** @var ?OrderEntity */ + return $this->orderRepository->search($criteria, $context)->first(); + } } diff --git a/src/Core/Checkout/Cart/SalesChannel/CartOrderRouteDecorator.php b/src/Core/Checkout/Cart/SalesChannel/CartOrderRouteDecorator.php index 279e714..8811a07 100644 --- a/src/Core/Checkout/Cart/SalesChannel/CartOrderRouteDecorator.php +++ b/src/Core/Checkout/Cart/SalesChannel/CartOrderRouteDecorator.php @@ -52,7 +52,7 @@ protected function restoreCart(Cart $cart, OrderEntity $orderEntity, SalesChanne ->addAssociation('paymentMethod'); - /** @var OrderTransactionEntity $orderTransaction */ + /** @var OrderTransactionEntity|null $orderTransaction */ $orderTransaction = $this->orderTransactionRepository->search($criteria, $context->getContext())->first(); if ($orderTransaction) { diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index 01064a6..70710da 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -92,4 +92,47 @@ form_checkout_div + + Terminal setup + + terminal1 + + + + terminal2 + + + + terminal3 + + + + terminal4 + + + + terminal5 + + + + terminal6 + + + + terminal7 + + + + terminal8 + + + + terminal9 + + + + terminal10 + + + diff --git a/src/Service/PaymentService.php b/src/Service/PaymentService.php index c465129..936228c 100644 --- a/src/Service/PaymentService.php +++ b/src/Service/PaymentService.php @@ -58,6 +58,7 @@ class PaymentService extends AbstractPaymentHandler public const ALTAPAY_TRANSACTION_PAYMENT_NATURE_CUSTOM_FIELD = "wexoAltapayTransactionPaymentNature"; public const ALTAPAY_IP_ADDRESS_SET = ["185.206.120.0/24", "2a10:a200::/29", '185.203.232.129', '185.203.233.129']; //NOSONAR public const ALTAPAY_ORDER_STATUS = "altaPayOrderStatus"; + public const ALTAPAY_SALES_CHANNEL_TERMINAL_ID = "altapaySalesChannelTerminalId"; public function __construct( protected readonly SystemConfigService $systemConfigService, @@ -179,6 +180,18 @@ public function pay( $paymentMethod = $salesChannelContext->getPaymentMethod(); $terminal = $paymentMethod->getTranslated()['customFields'][self::ALTAPAY_TERMINAL_ID_CUSTOM_FIELD]; + $salesChannelTerminal = $paymentMethod->getTranslated()['customFields'][self::ALTAPAY_SALES_CHANNEL_TERMINAL_ID]; + + if (!empty($salesChannelTerminal)) { + $field = 'WexoAltaPay.config.' . $salesChannelTerminal; + $salesChannelTerminalValue = $this->systemConfigService->get($field, $order->getSalesChannelId()); + + if (!empty($salesChannelTerminalValue)) { + $terminal = $salesChannelTerminalValue; + } + + } + $paymentRequestType = ($paymentMethod->getTranslated()['customFields'][self::ALTAPAY_AUTO_CAPTURE_CUSTOM_FIELD] ?? null) ? 'paymentAndCapture' : 'payment'; try { $altaPayResponse = $this->createPaymentRequest( diff --git a/src/Service/Setup/CustomFieldSetupService.php b/src/Service/Setup/CustomFieldSetupService.php index 6f471be..e9762e6 100644 --- a/src/Service/Setup/CustomFieldSetupService.php +++ b/src/Service/Setup/CustomFieldSetupService.php @@ -186,6 +186,60 @@ private function createPaymentMethodCustomField(Context $context): void context: $context ); + $terminals = []; + $languages = ['de-DE', 'en-GB', 'da-DK']; + + // Add default empty option + $terminals[] = [ + 'label' => [ + 'de-DE' => 'Wählen Sie ein Terminal aus', + 'en-GB' => 'Select a terminal', + 'da-DK' => 'Vælg en terminal', + ], + 'value' => '', + ]; + + for ($i = 1; $i <= 10; $i++) { + $label = []; + + foreach ($languages as $lang) { + $label[$lang] = 'terminal ' . $i; + } + + $terminals[] = [ + 'label' => $label, + 'value' => 'terminal' . $i, + ]; + } + + $this->addCustomField( + name: PaymentService::ALTAPAY_SALES_CHANNEL_TERMINAL_ID, + type: CustomFieldTypes::SELECT, + config: [ + 'customFieldPosition' => 4, + 'componentName' => 'sw-single-select', + 'customFieldType' => 'select', + 'label' => [ + 'de-DE' => 'Sales Channel - AltaPay Terminal-ID', + 'en-GB' => 'Sales Channel - AltaPay Terminal ID', + 'da-DK' => 'Sales Channel - AltaPay Terminal-ID', + ], + 'placeholder' => [ + 'de-DE' => 'Wählen Sie ein Terminal aus...', + 'en-GB' => 'Select a terminal...', + 'da-DK' => 'Vælg en terminal...', + ], + 'helpText' => [ + 'de-DE' => 'Wählen Sie die Terminal-Konfiguration für den Vertriebskanal aus. Falls leer gelassen, wird die Standard-Terminal-ID aus der Zahlungsmethode verwendet.', + 'en-GB' => 'Select the terminal configuration for the sales channel. If left empty, the default terminal ID from the payment method will be used.', + 'da-DK' => 'Vælg terminal-konfigurationen for salgskanalen. Hvis den efterlades tom, vil standard terminal-ID fra betalingsmetoden blive brugt.', + ], + 'options' => $terminals, + ], + customFieldSetId: $fieldSetId, + context: $context + ); + } private function addCustomField( diff --git a/src/Subscriber/PaymentMethodSubscriber.php b/src/Subscriber/PaymentMethodSubscriber.php index 6747df9..7414055 100644 --- a/src/Subscriber/PaymentMethodSubscriber.php +++ b/src/Subscriber/PaymentMethodSubscriber.php @@ -4,6 +4,7 @@ use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\DefaultPayment; use Shopware\Core\Checkout\Payment\PaymentEvents; +use Shopware\Core\Checkout\Payment\PaymentMethodCollection; use Shopware\Core\Checkout\Payment\PaymentMethodEntity; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent; @@ -16,6 +17,9 @@ class PaymentMethodSubscriber implements EventSubscriberInterface { + /** + * @param EntityRepository $paymentMethodRepository + */ public function __construct( protected EntityRepository $paymentMethodRepository, protected PluginIdProvider $pluginIdProvider diff --git a/src/WexoAltaPay.php b/src/WexoAltaPay.php index 51615b6..e7cf9d5 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.8'; + public const ALTAPAY_PLUGIN_VERSION = '2.0.9'; public const ALTAPAY_PLUGIN_NAME = 'WexoAltaPay'; public function update(UpdateContext $updateContext): void diff --git a/wiki.md b/wiki.md index e5f11e9..85d2f29 100644 --- a/wiki.md +++ b/wiki.md @@ -81,6 +81,7 @@ be provided by AltaPay. ![Checkout v2](Docs/Configuration/checkout_v2.png) +7. Enter the terminal names from your AltaPay account that you want to use for specific sales channels in the **Terminal Setup** section. Link these terminals from the Payment Method page. ## Add Payment Methods @@ -107,19 +108,21 @@ be provided by AltaPay. 8. Once saved, a new section, **Custom fields**, will appear with the options **AltaPay Terminal ID**(where you must enter the terminal name from AltaPay), **Auto Capture**, and **Surcharge**. ![shopware_configure_altapay_terminal_detail](Docs/Configuration/shopware_configure_altapay_terminal_detail.png) -9. Click the **Save** button again. +9. Select a sales channel-specific terminal configuration. If left empty, the default terminal ID (from the field above) will be used. -10. Now click on your desired shop from the **Sales Channels** menu on the left. +10. Click the **Save** button again. + +11. Now click on your desired shop from the **Sales Channels** menu on the left. -11. In the **General** tab, scroll down to the **Payment and shipping** section & search by name for the payment method you just created. +12. In the **General** tab, scroll down to the **Payment and shipping** section & search by name for the payment method you just created. ![shopware_show_payment_method_on_checkout.png](Docs/Configuration/shopware_show_payment_method_on_checkout.png) -12. Choose the payment method and click Save button in the top-right corner. +13. Choose the payment method and click Save button in the top-right corner. ![shopware_verify_payment_method.png](Docs/Configuration/shopware_verify_payment_method.png) -13. Once the payment methods are configured, you will be ready to process transactions through AltaPay. +14. Once the payment methods are configured, you will be ready to process transactions through AltaPay. ![shopware_checkout_page.png](Docs/Configuration/shopware_checkout_page.png)