diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 2299e2e6..e4969105 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -15,7 +15,8 @@ jobs: timeout-minutes: 20 strategy: fail-fast: false - if: ${{ github.actor != 'renovate[bot]' || github.actor != 'lgtm-com[bot]' }} + # if: ${{ github.actor != 'renovate[bot]' || github.actor != 'lgtm-com[bot]' }} + if: false # Prevent bots from initiating E2E pipeline steps: - name: Clone Code diff --git a/.github/workflows/templates/docker-compose.playwright.yml b/.github/workflows/templates/docker-compose.playwright.yml index 77d4afe6..258d5aa4 100644 --- a/.github/workflows/templates/docker-compose.playwright.yml +++ b/.github/workflows/templates/docker-compose.playwright.yml @@ -2,7 +2,7 @@ version: '3' services: playwright: - image: mcr.microsoft.com/playwright:v1.48.0-focal + image: mcr.microsoft.com/playwright:v1.48.2-focal networks: - localnetwork shm_size: 1gb diff --git a/README.md b/README.md index 567ec239..3bae9bcb 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ The plugin integrates card component (Secured Fields) using Adyen Checkout for a - Blik - Billie - Clearpay - - Dotpay - Electronic Payment Service (EPS) - Gift cards - GiroPay @@ -52,6 +51,7 @@ The plugin integrates card component (Secured Fields) using Adyen Checkout for a - Klarna Pay Later - Klarna Pay Now - Klarna Pay Over Time + - Klarna Debit Risk - MB Way - MobilePay - Multibanco @@ -61,13 +61,14 @@ The plugin integrates card component (Secured Fields) using Adyen Checkout for a - PaySafeCard - RatePay, RatePay Direct Debit - SEPA Direct Debit - - Sofort - Swish - Trustly - Twint - Vipps - WeChat Pay - Open Banking / Pay by Bank + - Online Banking Finland + - Online Banking Poland ## API Library This module is using the Adyen APIs Library for PHP for all (API) connections to Adyen. diff --git a/composer.json b/composer.json index b8f7caac..7ed387ea 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ } ], "description": "Official Shopware 6 Plugin to connect to Payment Service Provider Adyen", - "version": "4.1.3", + "version": "4.2.0", "type": "shopware-platform-plugin", "license": "MIT", "require": { diff --git a/src/AdyenPaymentShopware6.php b/src/AdyenPaymentShopware6.php index 548e792b..c11c9344 100644 --- a/src/AdyenPaymentShopware6.php +++ b/src/AdyenPaymentShopware6.php @@ -28,6 +28,8 @@ use Adyen\Shopware\Entity\Notification\NotificationEntityDefinition; use Adyen\Shopware\Entity\PaymentResponse\PaymentResponseEntityDefinition; use Adyen\Shopware\Entity\PaymentStateData\PaymentStateDataEntityDefinition; +use Adyen\Shopware\Handlers\KlarnaDebitRiskPaymentMethodHandler; +use Adyen\Shopware\PaymentMethods\KlarnaDebitRiskPaymentMethod; use Adyen\Shopware\Service\ConfigurationService; use Shopware\Core\Checkout\Payment\PaymentMethodEntity; use Shopware\Core\Framework\Plugin; @@ -47,6 +49,8 @@ class AdyenPaymentShopware6 extends Plugin { + public const SOFORT = 'Adyen\Shopware\Handlers\SofortPaymentMethodHandler'; + public function install(InstallContext $installContext): void { foreach (PaymentMethods\PaymentMethods::PAYMENT_METHODS as $paymentMethod) { @@ -134,6 +138,19 @@ public function update(UpdateContext $updateContext): void if (\version_compare($currentVersion, '4.1.0', '<')) { $this->updateTo410($updateContext); } + + if (\version_compare($currentVersion, '4.2.0', '<')) { + $this->updateTo420($updateContext); + } + } + + public function postUpdate(UpdateContext $updateContext): void + { + $currentVersion = $updateContext->getCurrentPluginVersion(); + if (\version_compare($currentVersion, '4.2.0', '<')) { + $handler = $this->container->get("Adyen\Shopware\Service\FetchLogosService"); + $handler->getHandler()->run(); + } } private function addPaymentMethod(PaymentMethods\PaymentMethodInterface $paymentMethod, Context $context): void @@ -144,6 +161,31 @@ private function addPaymentMethod(PaymentMethods\PaymentMethodInterface $payment $pluginIdProvider = $this->container->get(PluginIdProvider::class); $pluginId = $pluginIdProvider->getPluginIdByBaseClass(get_class($this), $context); + /** @var EntityRepository $paymentRepository */ + $paymentRepository = $this->container->get('payment_method.repository'); + + // Rename if Klarna Debit Risk doesnt exist from previous installations + if ($paymentMethod->getPaymentHandler() === KlarnaDebitRiskPaymentMethodHandler::class + && $paymentMethodId === null) { + $sofortMethodId = $this->getPaymentMethodId(self::SOFORT); + + if ($sofortMethodId) { + // update Sofort to Klarna Debit Risk + $method = new PaymentMethods\KlarnaDebitRiskPaymentMethod(); + + $paymentMethodData = [ + 'id' => $sofortMethodId, + 'handlerIdentifier' => $method->getPaymentHandler(), + 'name' => $method->getName(), + 'description' => $method->getDescription(), + ]; + + $paymentRepository->update([$paymentMethodData], $context); + + return; + } + } + // Payment method exists already, set the pluginId if ($paymentMethodId) { $this->setPluginId($paymentMethodId, $pluginId, $context); @@ -158,8 +200,6 @@ private function addPaymentMethod(PaymentMethods\PaymentMethodInterface $payment 'afterOrderEnabled' => true ]; - /** @var EntityRepository $paymentRepository */ - $paymentRepository = $this->container->get('payment_method.repository'); $paymentRepository->create([$paymentData], $context); } @@ -481,6 +521,69 @@ private function updateTo410(UpdateContext $updateContext): void ); } + private function updateTo420(UpdateContext $updateContext): void + { + // Version 4.2.0 introduces Online Banking Finland and Online Banking Poland + $paymentMethods = [ + new PaymentMethods\OnlineBankingFinlandPaymentMethod(), + new PaymentMethods\OnlineBankingPolandPaymentMethod(), + ]; + + foreach ($paymentMethods as $method) { + $this->addPaymentMethod( + $method, + $updateContext->getContext() + ); + + $this->setPaymentMethodIsActive( + true, + $updateContext->getContext(), + $method + ); + } + + // Version 4.2.0 removes Dotpay + $paymentMethodHandler = 'Adyen\Shopware\Handlers\DotpayPaymentMethodHandler'; + $this->deactivateAndRemovePaymentMethod($updateContext, $paymentMethodHandler); + + // Version 4.2.0 replaces Sofort with Klarna Debit Risk + $paymentRepository = $this->container->get('payment_method.repository'); + $paymentMethodId = $this->getPaymentMethodId(self::SOFORT); + $klarnaDebitRisktMethodId = $this->getPaymentMethodId( + 'Adyen\Shopware\Handlers\KlarnaDebitRiskPaymentMethodHandler' + ); + + // If Sofort does not exist, return + if (!$paymentMethodId) { + return; + } + + if ($klarnaDebitRisktMethodId !== null) { + // Klarna Debit Risk exists, deactivate Sofort and skip renaming + $this->deactivateAndRemovePaymentMethod($updateContext, self::SOFORT); + // activate Klarna Debit Risk + $this->setPaymentMethodIsActive( + true, + $updateContext->getContext(), + new PaymentMethods\KlarnaDebitRiskPaymentMethod() + ); + + return; + } + + // Update Sofort to Klarna Debit Risk + $method = new PaymentMethods\KlarnaDebitRiskPaymentMethod(); + + $paymentMethodData = [ + 'id' => $paymentMethodId, + 'handlerIdentifier' => $method->getPaymentHandler(), + 'name' => $method->getName(), + 'description' => $method->getDescription(), + ]; + + $paymentRepository->update([$paymentMethodData], $updateContext->getContext()); + } + /** * @param UpdateContext $updateContext * @param string $paymentMethodHandler diff --git a/src/Core/Checkout/Order/Aggregate/OrderTransaction/OrderTransactionExtension.php b/src/Core/Checkout/Order/Aggregate/OrderTransaction/OrderTransactionExtension.php index cf0dceb4..d7d52fcf 100644 --- a/src/Core/Checkout/Order/Aggregate/OrderTransaction/OrderTransactionExtension.php +++ b/src/Core/Checkout/Order/Aggregate/OrderTransaction/OrderTransactionExtension.php @@ -23,7 +23,10 @@ namespace Adyen\Shopware\Core\Checkout\Order\Aggregate\OrderTransaction; +use Adyen\Shopware\Entity\AdyenPayment\AdyenPaymentEntityDefinition; +use Adyen\Shopware\Entity\PaymentCapture\PaymentCaptureEntityDefinition; use Adyen\Shopware\Entity\PaymentResponse\PaymentResponseEntityDefinition; +use Adyen\Shopware\Entity\Refund\RefundEntityDefinition; use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition; use Shopware\Core\Framework\Api\Context\SalesChannelApiSource; use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension; @@ -49,14 +52,47 @@ public function extendFields(FieldCollection $collection): void 'order_transaction_id' ); + $refundField = new OneToManyAssociationField( + 'adyenRefund', + RefundEntityDefinition::class, + 'order_transaction_id' + ); + + $captureField = new OneToManyAssociationField( + 'adyenCapture', + PaymentCaptureEntityDefinition::class, + 'order_transaction_id' + ); + + $paymentField = new OneToManyAssociationField( + 'adyenPayment', + AdyenPaymentEntityDefinition::class, + 'order_transaction_id' + ); + // Ensure the data is not available via the Store API in older Shopware versions. if (!class_exists(ApiAware::class) && class_exists(Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ReadProtected::class)) { $field->addFlags( new Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ReadProtected(SalesChannelApiSource::class) ); + + $refundField->addFlags( + new Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ReadProtected(SalesChannelApiSource::class) + ); + + $captureField->addFlags( + new Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ReadProtected(SalesChannelApiSource::class) + ); + + $paymentField->addFlags( + new Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ReadProtected(SalesChannelApiSource::class) + ); } $collection->add($field); + $collection->add($refundField); + $collection->add($captureField); + $collection->add($paymentField); } } diff --git a/src/Entity/AdyenPayment/AdyenPaymentEntity.php b/src/Entity/AdyenPayment/AdyenPaymentEntity.php index d9407f80..d66e3f56 100644 --- a/src/Entity/AdyenPayment/AdyenPaymentEntity.php +++ b/src/Entity/AdyenPayment/AdyenPaymentEntity.php @@ -24,6 +24,7 @@ namespace Adyen\Shopware\Entity\AdyenPayment; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; use Shopware\Core\Framework\DataAbstractionLayer\Entity; use Shopware\Core\Framework\DataAbstractionLayer\EntityIdTrait; @@ -96,6 +97,11 @@ class AdyenPaymentEntity extends Entity */ protected $updatedAt; + /** + * @var OrderTransactionEntity + */ + protected OrderTransactionEntity $orderTransaction; + /** * @return string */ @@ -171,7 +177,7 @@ public function getOrderTransactionId(): string /** * @param int $orderTransactionId */ - public function setEventCode(int $orderTransactionId): void + public function setOrderTransactionId(int $orderTransactionId): void { $this->orderTransactionId = $orderTransactionId; } @@ -279,4 +285,20 @@ public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; } + + /** + * @return OrderTransactionEntity + */ + public function getOrderTransaction(): OrderTransactionEntity + { + return $this->orderTransaction; + } + + /** + * @param OrderTransactionEntity $orderTransaction + */ + public function setOrderTransaction(OrderTransactionEntity $orderTransaction): void + { + $this->orderTransaction = $orderTransaction; + } } diff --git a/src/Entity/AdyenPayment/AdyenPaymentEntityDefinition.php b/src/Entity/AdyenPayment/AdyenPaymentEntityDefinition.php index cb90cc56..ec7b308d 100644 --- a/src/Entity/AdyenPayment/AdyenPaymentEntityDefinition.php +++ b/src/Entity/AdyenPayment/AdyenPaymentEntityDefinition.php @@ -26,7 +26,6 @@ use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition; use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; -use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required; use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField; @@ -34,7 +33,6 @@ use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField; use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField; use Shopware\Core\Framework\DataAbstractionLayer\Field\IntField; -use Shopware\Core\Framework\DataAbstractionLayer\Field\DateTimeField; use Shopware\Core\Framework\DataAbstractionLayer\Field\UpdatedAtField; use Shopware\Core\Framework\DataAbstractionLayer\Field\CreatedAtField; use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection; @@ -65,11 +63,7 @@ protected function defineFields(): FieldCollection { return new FieldCollection([ (new IdField('id', 'id'))->addFlags(new PrimaryKey(), new Required()), - (new FkField( - 'order_transaction_id', - 'orderTransactionId', - OrderTransactionDefinition::class - ))->addFlags(new Required()), + new IdField('order_transaction_id', 'orderTransactionId'), new StringField('pspreference', 'pspreference'), new StringField('original_reference', 'originalReference'), new StringField('merchant_reference', 'merchantReference'), diff --git a/src/Entity/PaymentCapture/PaymentCaptureEntityDefinition.php b/src/Entity/PaymentCapture/PaymentCaptureEntityDefinition.php index a17902dc..242ae295 100644 --- a/src/Entity/PaymentCapture/PaymentCaptureEntityDefinition.php +++ b/src/Entity/PaymentCapture/PaymentCaptureEntityDefinition.php @@ -26,7 +26,6 @@ use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition; use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; -use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required; use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField; @@ -58,11 +57,7 @@ protected function defineFields(): FieldCollection { return new FieldCollection([ (new IdField('id', 'id'))->addFlags(new PrimaryKey(), new Required()), - (new FkField( - 'order_transaction_id', - 'orderTransactionId', - OrderTransactionDefinition::class - ))->addFlags(new Required()), + new IdField('order_transaction_id', 'orderTransactionId'), new StringField('psp_reference', 'pspReference'), (new IntField('amount', 'amount'))->addFlags(new Required()), (new StringField('source', 'source'))->addFlags(new Required()), diff --git a/src/Entity/PaymentResponse/PaymentResponseEntity.php b/src/Entity/PaymentResponse/PaymentResponseEntity.php index a5dee447..49f10efc 100644 --- a/src/Entity/PaymentResponse/PaymentResponseEntity.php +++ b/src/Entity/PaymentResponse/PaymentResponseEntity.php @@ -46,16 +46,6 @@ class PaymentResponseEntity extends Entity */ protected $resultCode; - /** - * @var string - */ - protected $refusalReason; - - /** - * @var string - */ - protected $refusalReasonCode; - /** * @var string */ @@ -71,6 +61,7 @@ class PaymentResponseEntity extends Entity */ protected $pspreference; + /** * @return string */ @@ -119,38 +110,6 @@ public function setResultCode(string $resultCode): void $this->resultCode = $resultCode; } - /** - * @return string - */ - public function getRefusalReason(): ?string - { - return $this->refusalReason; - } - - /** - * @param string $refusalReason - */ - public function setRefusalReason(string $refusalReason): void - { - $this->refusalReason = $refusalReason; - } - - /** - * @return string - */ - public function getRefusalReasonCode(): ?string - { - return $this->refusalReasonCode; - } - - /** - * @param string $refusalReasonCode - */ - public function setRefusalReasonCode(string $refusalReasonCode): void - { - $this->refusalReasonCode = $refusalReasonCode; - } - /** * @return string */ @@ -182,4 +141,14 @@ public function setPspreference(?string $pspreference): void { $this->pspreference = $pspreference; } + + public function getCreatedAt(): ?\DateTimeInterface + { + return $this->createdAt; + } + + public function setCreatedAt(?\DateTimeInterface $createdAt): void + { + $this->createdAt = $createdAt; + } } diff --git a/src/Entity/PaymentResponse/PaymentResponseEntityDefinition.php b/src/Entity/PaymentResponse/PaymentResponseEntityDefinition.php index 55163ef6..a46b3ce7 100644 --- a/src/Entity/PaymentResponse/PaymentResponseEntityDefinition.php +++ b/src/Entity/PaymentResponse/PaymentResponseEntityDefinition.php @@ -26,7 +26,6 @@ use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition; use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; use Shopware\Core\Framework\DataAbstractionLayer\Field\CreatedAtField; -use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required; use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField; @@ -59,11 +58,7 @@ protected function defineFields(): FieldCollection { return new FieldCollection([ (new IdField('id', 'id'))->addFlags(new PrimaryKey(), new Required()), - (new FkField( - 'order_transaction_id', - 'orderTransactionId', - OrderTransactionDefinition::class - ))->addFlags(new Required()), + new IdField('order_transaction_id', 'orderTransactionId'), new StringField('result_code', 'resultCode'), new StringField('pspreference', 'pspreference'), new LongTextField('response', 'response'), diff --git a/src/Entity/Refund/RefundEntityDefinition.php b/src/Entity/Refund/RefundEntityDefinition.php index e76201ab..416e55dd 100644 --- a/src/Entity/Refund/RefundEntityDefinition.php +++ b/src/Entity/Refund/RefundEntityDefinition.php @@ -25,11 +25,7 @@ namespace Adyen\Shopware\Entity\Refund; use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition; -use Shopware\Core\Checkout\Order\OrderDefinition; use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; -use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField; -use Shopware\Core\Framework\DataAbstractionLayer\Field\CreatedAtField; -use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey; use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required; use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField; @@ -61,11 +57,7 @@ protected function defineFields(): FieldCollection { return new FieldCollection([ (new IdField('id', 'id'))->addFlags(new PrimaryKey(), new Required()), - (new FkField( - 'order_transaction_id', - 'orderTransactionId', - OrderTransactionDefinition::class - ))->addFlags(new Required()), + new IdField('order_transaction_id', 'orderTransactionId'), new StringField('psp_reference', 'pspReference'), (new IntField('amount', 'amount'))->addFlags(new Required()), (new StringField('source', 'source'))->addFlags(new Required()), diff --git a/src/Handlers/AbstractPaymentMethodHandler.php b/src/Handlers/AbstractPaymentMethodHandler.php index d8a98afd..90fa9205 100644 --- a/src/Handlers/AbstractPaymentMethodHandler.php +++ b/src/Handlers/AbstractPaymentMethodHandler.php @@ -65,6 +65,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; abstract class AbstractPaymentMethodHandler implements AsynchronousPaymentHandlerInterface @@ -331,9 +332,28 @@ public function pay( ); // Payment had no error, continue the process + + // If Bancontact mobile payment is used, redirect to proxy finalize transaction endpoint + if ($stateData['paymentMethod']['type'] === 'bcmc_mobile') { + return new RedirectResponse($this->getReturnUrl($transaction)); + } + return new RedirectResponse($transaction->getReturnUrl()); } + private function getReturnUrl(AsyncPaymentTransactionStruct $transaction): string + { + $query = parse_url($transaction->getReturnUrl(), PHP_URL_QUERY); + parse_str($query, $params); + $token = $params['_sw_payment_token'] ?? ''; + + return $this->symfonyRouter->generate( + 'payment.adyen.proxy-finalize-transaction', + ['_sw_payment_token' => $token, 'orderId' => $transaction->getOrder()->getId()], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } + /** * @param SalesChannelContext $salesChannelContext * @param $transaction @@ -586,7 +606,11 @@ protected function preparePaymentsRequest( $paymentRequest->setMerchantAccount( $this->configurationService->getMerchantAccount($salesChannelContext->getSalesChannel()->getId()) ); - $paymentRequest->setReturnUrl($transaction->getReturnUrl()); + if ($paymentMethodType === 'bcmc_mobile') { + $paymentRequest->setReturnUrl($this->getReturnUrl($transaction)); + } else { + $paymentRequest->setReturnUrl($transaction->getReturnUrl()); + } if (static::$isOpenInvoice) { $orderLines = $transaction->getOrder()->getLineItems(); diff --git a/src/Handlers/DotpayPaymentMethodHandler.php b/src/Handlers/KlarnaDebitRiskPaymentMethodHandler.php similarity index 87% rename from src/Handlers/DotpayPaymentMethodHandler.php rename to src/Handlers/KlarnaDebitRiskPaymentMethodHandler.php index cc9ae051..511173cd 100644 --- a/src/Handlers/DotpayPaymentMethodHandler.php +++ b/src/Handlers/KlarnaDebitRiskPaymentMethodHandler.php @@ -16,7 +16,7 @@ * * Adyen Payment Module * - * Copyright (c) 2021 Adyen B.V. + * Copyright (c) 2020 Adyen B.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. * @@ -25,10 +25,10 @@ namespace Adyen\Shopware\Handlers; -class DotpayPaymentMethodHandler extends AbstractPaymentMethodHandler +class KlarnaDebitRiskPaymentMethodHandler extends AbstractPaymentMethodHandler { public static function getPaymentMethodCode() { - return 'dotpay'; + return 'directEbanking'; } } diff --git a/src/Handlers/OnlineBankingFinlandPaymentMethodHandler.php b/src/Handlers/OnlineBankingFinlandPaymentMethodHandler.php new file mode 100644 index 00000000..2314e283 --- /dev/null +++ b/src/Handlers/OnlineBankingFinlandPaymentMethodHandler.php @@ -0,0 +1,34 @@ + + */ + +namespace Adyen\Shopware\Handlers; + +class OnlineBankingFinlandPaymentMethodHandler extends AbstractPaymentMethodHandler +{ + public static function getPaymentMethodCode() + { + return 'ebanking_FI'; + } +} diff --git a/src/Handlers/OnlineBankingPolandPaymentMethodHandler.php b/src/Handlers/OnlineBankingPolandPaymentMethodHandler.php new file mode 100644 index 00000000..23ab8ae3 --- /dev/null +++ b/src/Handlers/OnlineBankingPolandPaymentMethodHandler.php @@ -0,0 +1,34 @@ + + */ + +namespace Adyen\Shopware\Handlers; + +class OnlineBankingPolandPaymentMethodHandler extends AbstractPaymentMethodHandler +{ + public static function getPaymentMethodCode() + { + return 'onlineBanking_PL'; + } +} diff --git a/src/Handlers/PaymentResponseHandlerResult.php b/src/Handlers/PaymentResponseHandlerResult.php index 89414305..2bf33869 100644 --- a/src/Handlers/PaymentResponseHandlerResult.php +++ b/src/Handlers/PaymentResponseHandlerResult.php @@ -25,8 +25,6 @@ public function createFromPaymentResponse(PaymentResponseEntity $paymentResponse { // Set result code $this->setResultCode($paymentResponse->getResultCode()); - $this->setRefusalReason($paymentResponse->getRefusalReason()); - $this->setRefusalReasonCode($paymentResponse->getRefusalReasonCode()); $response = $paymentResponse->getResponse(); diff --git a/src/PaymentMethods/DotpayPaymentMethod.php b/src/PaymentMethods/KlarnaDebitRiskPaymentMethod.php old mode 100755 new mode 100644 similarity index 83% rename from src/PaymentMethods/DotpayPaymentMethod.php rename to src/PaymentMethods/KlarnaDebitRiskPaymentMethod.php index 9c57add9..306a1a8c --- a/src/PaymentMethods/DotpayPaymentMethod.php +++ b/src/PaymentMethods/KlarnaDebitRiskPaymentMethod.php @@ -15,7 +15,7 @@ * * Adyen Payment Module * - * Copyright (c) 2021 Adyen B.V. + * Copyright (c) 2020 Adyen B.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. * @@ -24,9 +24,9 @@ namespace Adyen\Shopware\PaymentMethods; -use Adyen\Shopware\Handlers\DotpayPaymentMethodHandler; +use Adyen\Shopware\Handlers\KlarnaDebitRiskPaymentMethodHandler; -class DotpayPaymentMethod implements PaymentMethodInterface +class KlarnaDebitRiskPaymentMethod implements PaymentMethodInterface { /** * {@inheritDoc} @@ -35,7 +35,7 @@ class DotpayPaymentMethod implements PaymentMethodInterface */ public function getName(): string { - return 'Dotpay'; + return 'Klarna Debit Risk'; } /** @@ -45,7 +45,7 @@ public function getName(): string */ public function getDescription(): string { - return 'Online banking payments'; + return 'Direct debit payments'; } /** @@ -55,7 +55,7 @@ public function getDescription(): string */ public function getPaymentHandler(): string { - return DotpayPaymentMethodHandler::class; + return KlarnaDebitRiskPaymentMethodHandler::class; } /** @@ -65,7 +65,7 @@ public function getPaymentHandler(): string */ public function getGatewayCode(): string { - return 'ADYEN_DOTPAY'; + return 'ADYEN_KLARNA_DEBIT_RISK'; } /** @@ -85,7 +85,7 @@ public function getTemplate(): ?string */ public function getLogo(): string { - return 'dotpay.png'; + return 'directEbanking.png'; } /** diff --git a/src/PaymentMethods/OnlineBankingFinlandPaymentMethod.php b/src/PaymentMethods/OnlineBankingFinlandPaymentMethod.php new file mode 100644 index 00000000..f9fa06f6 --- /dev/null +++ b/src/PaymentMethods/OnlineBankingFinlandPaymentMethod.php @@ -0,0 +1,100 @@ + + */ + +namespace Adyen\Shopware\PaymentMethods; + +use Adyen\Shopware\Handlers\OnlineBankingFinlandPaymentMethodHandler; + +class OnlineBankingFinlandPaymentMethod implements PaymentMethodInterface +{ + /** + * {@inheritDoc} + * + * @return string + */ + public function getName(): string + { + return 'Online Banking Finland'; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getDescription(): string + { + return 'Online Banking Finland payment method'; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getPaymentHandler(): string + { + return OnlineBankingFinlandPaymentMethodHandler::class; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getGatewayCode(): string + { + return 'ADYEN_EBANKING_FI'; + } + + /** + * {@inheritDoc} + * + * @return string|null + */ + public function getTemplate(): ?string + { + return null; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getLogo(): string + { + return 'ebanking_FI.png'; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getType(): string + { + return 'redirect'; + } +} diff --git a/src/PaymentMethods/OnlineBankingPolandPaymentMethod.php b/src/PaymentMethods/OnlineBankingPolandPaymentMethod.php new file mode 100644 index 00000000..e369b656 --- /dev/null +++ b/src/PaymentMethods/OnlineBankingPolandPaymentMethod.php @@ -0,0 +1,100 @@ + + */ + +namespace Adyen\Shopware\PaymentMethods; + +use Adyen\Shopware\Handlers\OnlineBankingPolandPaymentMethodHandler; + +class OnlineBankingPolandPaymentMethod implements PaymentMethodInterface +{ + /** + * {@inheritDoc} + * + * @return string + */ + public function getName(): string + { + return 'Online Banking Poland'; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getDescription(): string + { + return 'Online Banking Poland payment method'; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getPaymentHandler(): string + { + return OnlineBankingPolandPaymentMethodHandler::class; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getGatewayCode(): string + { + return 'ADYEN_ONLINEBANKING_PL'; + } + + /** + * {@inheritDoc} + * + * @return string|null + */ + public function getTemplate(): ?string + { + return null; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getLogo(): string + { + return 'onlineBanking_PL.png'; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getType(): string + { + return 'redirect'; + } +} diff --git a/src/PaymentMethods/PaymentMethods.php b/src/PaymentMethods/PaymentMethods.php index 25ac411e..ce3c396c 100644 --- a/src/PaymentMethods/PaymentMethods.php +++ b/src/PaymentMethods/PaymentMethods.php @@ -35,13 +35,12 @@ class PaymentMethods RatepayPaymentMethod::class, RatepayDirectdebitPaymentMethod::class, SepaPaymentMethod::class, - SofortPaymentMethod::class, + KlarnaDebitRiskPaymentMethod::class, PaypalPaymentMethod::class, OneClickPaymentMethod::class, GiroPayPaymentMethod::class, ApplePayPaymentMethod::class, GooglePayPaymentMethod::class, - DotpayPaymentMethod::class, BancontactCardPaymentMethod::class, BancontactMobilePaymentMethod::class, AmazonPayPaymentMethod::class, @@ -70,6 +69,8 @@ class PaymentMethods VippsPaymentMethod::class, MobilePayPaymentMethod::class, OpenBankingPaymentMethod::class, - BilliePaymentMethod::class + BilliePaymentMethod::class, + OnlineBankingFinlandPaymentMethod::class, + OnlineBankingPolandPaymentMethod::class ]; } diff --git a/src/Resources/app/storefront/src/configuration/adyen.js b/src/Resources/app/storefront/src/configuration/adyen.js index 73db72c0..3d19505a 100644 --- a/src/Resources/app/storefront/src/configuration/adyen.js +++ b/src/Resources/app/storefront/src/configuration/adyen.js @@ -22,7 +22,7 @@ export default { updatablePaymentMethods: [ - 'scheme', 'ideal', 'sepadirectdebit', 'oneclick', 'dotpay', 'bcmc', 'bcmc_mobile', 'blik', 'klarna_b2b', 'eps', 'facilypay_3x', + 'scheme', 'ideal', 'sepadirectdebit', 'oneclick', 'bcmc', 'bcmc_mobile', 'blik', 'klarna_b2b', 'eps', 'facilypay_3x', 'facilypay_4x', 'facilypay_6x', 'facilypay_10x', 'facilypay_12x', 'afterpay_default', 'ratepay', 'ratepay_directdebit', 'giftcard', 'paybright', 'affirm', 'multibanco', 'mbway', 'vipps', 'mobilepay', 'wechatpayQR', 'wechatpayWeb', 'paybybank' @@ -121,13 +121,12 @@ export default { 'ratepay': 'handler_adyen_ratepaypaymentmethodhandler', 'ratepay_directdebit': 'handler_adyen_ratepaydirectdebitpaymentmethodhandler', 'sepadirectdebit': 'handler_adyen_sepapaymentmethodhandler', - 'sofort': 'handler_adyen_sofortpaymentmethodhandler', + 'directEbanking': 'handler_adyen_klarnadebitriskpaymentmethodhandler', 'paypal': 'handler_adyen_paypalpaymentmethodhandler', 'oneclick': 'handler_adyen_oneclickpaymentmethodhandler', 'giropay': 'handler_adyen_giropaypaymentmethodhandler', 'applepay': 'handler_adyen_applepaypaymentmethodhandler', 'googlepay': 'handler_adyen_googlepaypaymentmethodhandler', - 'dotpay': 'handler_adyen_dotpaypaymentmethodhandler', 'bcmc': 'handler_adyen_bancontactcardpaymentmethodhandler', 'bcmc_mobile': 'handler_adyen_bancontactmobilepaymentmethodhandler', 'amazonpay': 'handler_adyen_amazonpaypaymentmethodhandler', @@ -156,6 +155,8 @@ export default { 'affirm': 'handler_adyen_affirmpaymentmethodhandler', 'paybright': 'handler_adyen_paybrightpaymentmethodhandler', 'paybybank': 'handler_adyen_openbankingpaymentmethodhandler', - 'klarna_b2b': 'handler_adyen_billiepaymentmethodhandler' + 'klarna_b2b': 'handler_adyen_billiepaymentmethodhandler', + 'ebanking_FI': 'handler_adyen_onlinebankingfinlandpaymentmethodhandler', + 'onlineBanking_PL': 'handler_adyen_onlinebankingpolandpaymentmethodhandler' } } diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 6dd9ffac..5d981b6a 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -97,6 +97,10 @@ + + + + diff --git a/src/Resources/config/services/controllers.xml b/src/Resources/config/services/controllers.xml index 26f88290..90afe56f 100644 --- a/src/Resources/config/services/controllers.xml +++ b/src/Resources/config/services/controllers.xml @@ -32,6 +32,7 @@ id="Shopware\Core\Checkout\Payment\SalesChannel\HandlePaymentMethodRoute"/> + diff --git a/src/Resources/config/services/payment-handlers.xml b/src/Resources/config/services/payment-handlers.xml index 40c3c178..ad606930 100644 --- a/src/Resources/config/services/payment-handlers.xml +++ b/src/Resources/config/services/payment-handlers.xml @@ -44,7 +44,7 @@ parent="Adyen\Shopware\Handlers\AbstractPaymentMethodHandler"> - @@ -68,10 +68,6 @@ parent="Adyen\Shopware\Handlers\AbstractPaymentMethodHandler"> - - - @@ -244,5 +240,13 @@ parent="Adyen\Shopware\Handlers\AbstractPaymentMethodHandler"> + + + + + + diff --git a/src/Resources/config/services/utils.xml b/src/Resources/config/services/utils.xml index 6b2e6c9b..a26550dd 100644 --- a/src/Resources/config/services/utils.xml +++ b/src/Resources/config/services/utils.xml @@ -17,6 +17,9 @@ + + + diff --git a/src/Resources/snippet/de_DE/messages.de-DE.json b/src/Resources/snippet/de_DE/messages.de-DE.json index 4b108601..b5253ad8 100644 --- a/src/Resources/snippet/de_DE/messages.de-DE.json +++ b/src/Resources/snippet/de_DE/messages.de-DE.json @@ -10,6 +10,7 @@ "remainingBalance": "Verbleibendes guthaben der geschenkkarte", "remainingAmount": "Restbetrag", "discount": "Geschenkkarten-Rabatt" - } + }, + "unsuccessful_adyen_transaction": "Payment with Bancontact mobile was unsuccessful. Please change the payment method or try again." } } diff --git a/src/Resources/snippet/en_GB/messages.en-GB.json b/src/Resources/snippet/en_GB/messages.en-GB.json index ae41ceaa..da75f65f 100644 --- a/src/Resources/snippet/en_GB/messages.en-GB.json +++ b/src/Resources/snippet/en_GB/messages.en-GB.json @@ -11,6 +11,7 @@ "deductedAmount": "Deducted Amount", "remainingAmount": "Remaining amount", "discount": "Giftcard discount" - } + }, + "unsuccessful_adyen_transaction": "Payment with Bancontact mobile was unsuccessful. Please change the payment method or try again." } } diff --git a/src/Resources/views/storefront/page/checkout/cart/index.html.twig b/src/Resources/views/storefront/page/checkout/cart/index.html.twig index 1e4d21e8..b5e3bb83 100644 --- a/src/Resources/views/storefront/page/checkout/cart/index.html.twig +++ b/src/Resources/views/storefront/page/checkout/cart/index.html.twig @@ -7,3 +7,27 @@ {% sw_include '@AdyenPaymentShopware6/storefront/component/adyencheckout.html.twig' %} {% sw_include '@AdyenPaymentShopware6/storefront/component/checkout/cart/giftcards.html.twig' %} {% endblock %} + +{% block page_checkout_container %} + {% if page.cart.lineItems.count is same as(0) %} + {{ parent() }} + {% if page.extensions['errorCodes'].errorCode == 'UNSUCCESSFUL_ADYEN_TRANSACTION'%} + {% sw_include '@Storefront/storefront/utilities/alert.html.twig' with { + type: 'danger', + content: 'adyen.unsuccessful_adyen_transaction' | trans + } %} + {% endif %} + {% else %} + {{ parent() }} + {% endif %} +{% endblock %} + +{% block page_checkout_cart_header %} + {{ parent() }} + {% if page.extensions['errorCodes'].errorCode == 'UNSUCCESSFUL_ADYEN_TRANSACTION'%} + {% sw_include '@Storefront/storefront/utilities/alert.html.twig' with { + type: 'danger', + content: 'adyen.unsuccessful_adyen_transaction' | trans + } %} + {% endif %} +{% endblock %} diff --git a/src/ScheduledTask/ProcessNotificationsHandler.php b/src/ScheduledTask/ProcessNotificationsHandler.php index 51cc374a..d623fb74 100644 --- a/src/ScheduledTask/ProcessNotificationsHandler.php +++ b/src/ScheduledTask/ProcessNotificationsHandler.php @@ -171,6 +171,10 @@ public function run(): void /** @var NotificationEntity $notification */ $logContext = ['eventCode' => $notification->getEventCode()]; + if (is_null($notification->getMerchantReference())) { + continue; + } + /* * Before processing any notification, factory should be created first. * It checks the supported EventCode to use related class in the factory. diff --git a/src/ScheduledTask/Webhook/RefundWebhookHandler.php b/src/ScheduledTask/Webhook/RefundWebhookHandler.php index 530dd1b9..7c846fff 100644 --- a/src/ScheduledTask/Webhook/RefundWebhookHandler.php +++ b/src/ScheduledTask/Webhook/RefundWebhookHandler.php @@ -127,6 +127,18 @@ private function handleSuccessfulNotification( $notificationEntity->getOriginalReference() ); + if ($adyenPayment === null) { + $this->logger->warning( + 'Adyen payment entity not found for the given notification.', + [ + 'originalReference' => $notificationEntity->getOriginalReference(), + 'notificationVars' => $notificationEntity->getVars() + ] + ); + + return; + } + $this->adyenPaymentService->updateTotalRefundedAmount( $adyenPayment, (int) $notificationEntity->getAmountValue() diff --git a/src/Service/FetchLogosService.php b/src/Service/FetchLogosService.php new file mode 100644 index 00000000..0714f5eb --- /dev/null +++ b/src/Service/FetchLogosService.php @@ -0,0 +1,62 @@ +handler = $handler; + } + + /** + * Get the FetchPaymentMethodLogosHandler. + * + * @return FetchPaymentMethodLogosHandler The handler responsible for executing the logo fetching logic. + */ + public function getHandler(): FetchPaymentMethodLogosHandler + { + return $this->handler; + } +} diff --git a/src/Storefront/Controller/FrontendProxyController.php b/src/Storefront/Controller/FrontendProxyController.php index c017885d..e08b1ecf 100644 --- a/src/Storefront/Controller/FrontendProxyController.php +++ b/src/Storefront/Controller/FrontendProxyController.php @@ -27,6 +27,8 @@ use Adyen\Shopware\Controller\StoreApi\Donate\DonateController; use Adyen\Shopware\Controller\StoreApi\OrderApi\OrderApiController; use Adyen\Shopware\Controller\StoreApi\Payment\PaymentController; +use Adyen\Shopware\Handlers\PaymentResponseHandler; +use Adyen\Shopware\Util\ShopwarePaymentTokenValidator; use Error; use Shopware\Core\Checkout\Cart\Exception\InvalidCartException; use Shopware\Core\Checkout\Cart\SalesChannel\AbstractCartOrderRoute; @@ -39,10 +41,12 @@ use Shopware\Core\System\SalesChannel\SalesChannel\AbstractContextSwitchRoute; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Storefront\Controller\StorefrontController; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; #[Route(defaults: ['_routeScope' => ['storefront']])] class FrontendProxyController extends StorefrontController @@ -67,6 +71,11 @@ class FrontendProxyController extends StorefrontController */ private AbstractHandlePaymentMethodRoute $handlePaymentMethodRoute; + /** + * @var RouterInterface + */ + private RouterInterface $router; + /** * @var PaymentController */ @@ -82,31 +91,42 @@ class FrontendProxyController extends StorefrontController */ private DonateController $donateController; + /** + * @var ShopwarePaymentTokenValidator + */ + private ShopwarePaymentTokenValidator $paymentTokenValidator; + /** * @param AbstractCartOrderRoute $cartOrderRoute * @param AbstractHandlePaymentMethodRoute $handlePaymentMethodRoute * @param AbstractContextSwitchRoute $contextSwitchRoute * @param CartService $cartService + * @param RouterInterface $router * @param PaymentController $paymentController * @param OrderApiController $orderApiController * @param DonateController $donateController + * @param ShopwarePaymentTokenValidator $paymentTokenValidator */ - public function __construct( - AbstractCartOrderRoute $cartOrderRoute, - AbstractHandlePaymentMethodRoute $handlePaymentMethodRoute, - AbstractContextSwitchRoute $contextSwitchRoute, - CartService $cartService, - PaymentController $paymentController, - OrderApiController $orderApiController, - DonateController $donateController - ) { + public function __construct(//NOSONAR + AbstractCartOrderRoute $cartOrderRoute,//NOSONAR + AbstractHandlePaymentMethodRoute $handlePaymentMethodRoute,//NOSONAR + AbstractContextSwitchRoute $contextSwitchRoute,//NOSONAR + CartService $cartService,//NOSONAR + RouterInterface $router,//NOSONAR + PaymentController $paymentController,//NOSONAR + OrderApiController $orderApiController,//NOSONAR + DonateController $donateController,//NOSONAR + ShopwarePaymentTokenValidator $paymentTokenValidator//NOSONAR + ) {//NOSONAR $this->cartOrderRoute = $cartOrderRoute; $this->cartService = $cartService; $this->handlePaymentMethodRoute = $handlePaymentMethodRoute; $this->contextSwitchRoute = $contextSwitchRoute; + $this->router = $router; $this->paymentController = $paymentController; $this->orderApiController = $orderApiController; $this->donateController = $donateController; + $this->paymentTokenValidator = $paymentTokenValidator; } /** @@ -167,6 +187,58 @@ public function handlePayment(Request $request, SalesChannelContext $salesChanne return new JsonResponse($routeResponse->getObject()); } + #[Route( + '/adyen/proxy-finalize-transaction', + name: 'payment.adyen.proxy-finalize-transaction', + defaults: ['XmlHttpRequest' => true, 'csrf_protected' => false], + methods: ['GET'] + )] + public function finalizeTransaction(Request $request, SalesChannelContext $salesChannelContext): RedirectResponse + { + $paymentToken = $request->get('_sw_payment_token'); + $redirectResult = $request->get('redirectResult'); + + if ($this->paymentTokenValidator->validateToken($paymentToken) && !$redirectResult) { + return new RedirectResponse( + $this->router->generate( + 'payment.finalize.transaction', + ['_sw_payment_token' => $paymentToken], + UrlGeneratorInterface::ABSOLUTE_URL + ) + ); + } + + $orderId = $request->get('orderId') ?? ''; + $stateData = ['details' => ['redirectResult' => $redirectResult]]; + $request->request->add(['stateData' => json_encode($stateData, JSON_THROW_ON_ERROR)]); + $request->request->add(['orderId' => $orderId]); + $response = $this->paymentController->postPaymentDetails($request, $salesChannelContext); + $resultCode = json_decode( + $response->getContent(), + false, + 512, + JSON_THROW_ON_ERROR + )->resultCode ?? ''; + + if ($resultCode === PaymentResponseHandler::AUTHORISED) { + return new RedirectResponse( + $this->router->generate( + 'frontend.checkout.finish.page', + ['orderId' => $orderId], + UrlGeneratorInterface::ABSOLUTE_URL + ) + ); + } + + return new RedirectResponse( + $this->router->generate( + 'frontend.checkout.cart.page', + ['errorCode' => 'UNSUCCESSFUL_ADYEN_TRANSACTION'], + UrlGeneratorInterface::ABSOLUTE_URL + ) + ); + } + /** * @deprecated This method is deprecated and will be removed in future versions. */ diff --git a/src/Subscriber/PaymentSubscriber.php b/src/Subscriber/PaymentSubscriber.php index 8b04500f..875d509d 100644 --- a/src/Subscriber/PaymentSubscriber.php +++ b/src/Subscriber/PaymentSubscriber.php @@ -215,6 +215,19 @@ public function onShoppingCartLoaded(PageLoadedEvent $event) { /** @var CheckoutCartPage|OffcanvasCartPage $page */ $page = $event->getPage(); + $errorCodes = []; + if ($event->getRequest()->get('errorCode') + && $event->getRequest()->get('errorCode') === 'UNSUCCESSFUL_ADYEN_TRANSACTION' + ) { + $errorCodes['errorCode'] = 'UNSUCCESSFUL_ADYEN_TRANSACTION'; + $page->addExtension( + 'errorCodes', + new ArrayEntity( + $errorCodes + ) + ); + } + if ($page->getCart()->getLineItems()->count() === 0) { return; } diff --git a/src/Util/ShopwarePaymentTokenValidator.php b/src/Util/ShopwarePaymentTokenValidator.php new file mode 100644 index 00000000..3b613a4c --- /dev/null +++ b/src/Util/ShopwarePaymentTokenValidator.php @@ -0,0 +1,44 @@ +tokenFactory = $tokenFactory; + } + + /** + * Validates if the Shopware payment token is still valid. + * + * @param string|null $paymentToken + * + * @return bool + */ + public function validateToken(?string $paymentToken): bool + { + try { + $token = $this->tokenFactory->parseToken($paymentToken); + + if ($token->isExpired()) { + return false; + } + + return true; + } catch (PaymentException) { + return false; + } + } +}