diff --git a/.github/actions/run-e2e/action.yml b/.github/actions/run-e2e/action.yml index 2d33ab682..974d3fee0 100644 --- a/.github/actions/run-e2e/action.yml +++ b/.github/actions/run-e2e/action.yml @@ -97,6 +97,7 @@ runs: docker exec shop bash -c 'sed -i "s/APP_ENV=dev/APP_ENV=prod/g" /var/www/html/.env' || true; # we have to enable cypress mode in our shop, this helps us to create subscriptions without webhooks docker exec shop bash -c "echo "MOLLIE_CYPRESS_MODE=1" >> /var/www/html/.env" || true; + docker exec shop bash -c "echo "MOLLIE_PAYPAL_EXPRESS_BETA=1" >> /var/www/html/.env" || true; # -------------------------------------------------------------------------------------------------------------------------------------- diff --git a/.github/workflows/ci_pipe.yml b/.github/workflows/ci_pipe.yml index f4bcdbeb0..1d0d117f1 100644 --- a/.github/workflows/ci_pipe.yml +++ b/.github/workflows/ci_pipe.yml @@ -373,7 +373,7 @@ jobs: MOLLIE_APIKEY_TEST: ${{ secrets.MOLLIE_APIKEY_TEST }} # ------------------------------------------- RUN_CYPRESS: true - TESTRAIL: true + TESTRAIL: false TESTRAIL_DOMAIN: ${{ secrets.TESTRAIL_DOMAIN }} TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} TESTRAIL_PASSWORD: ${{ secrets.TESTRAIL_PASSWORD }} diff --git a/.github/workflows/nightly_pipe.yml b/.github/workflows/nightly_pipe.yml index 24b7cab5c..1f000f4c2 100644 --- a/.github/workflows/nightly_pipe.yml +++ b/.github/workflows/nightly_pipe.yml @@ -417,7 +417,7 @@ jobs: MOLLIE_APIKEY_TEST: ${{ secrets.MOLLIE_APIKEY_TEST }} # ------------------------------------------- RUN_CYPRESS: true - TESTRAIL: true + TESTRAIL: false TESTRAIL_DOMAIN: ${{ secrets.TESTRAIL_DOMAIN }} TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} TESTRAIL_PASSWORD: ${{ secrets.TESTRAIL_PASSWORD }} diff --git a/makefile b/makefile index 855ffd852..e2debaeff 100644 --- a/makefile +++ b/makefile @@ -12,11 +12,18 @@ NODE_VERSION:=$(shell node -v) help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + @printf "\033[33mInstallation:%-30s\033[0m %s\n" + @grep -E '^[a-zA-Z_-]+:.*?##1 .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?##1 "}; {printf "\033[33m - %-30s\033[0m %s\n", $$1, $$2}' + @echo "---------------------------------------------------------------------------------------------------------" + @printf "\033[36mDevelopment:%-30s\033[0m %s\n" + @grep -E '^[a-zA-Z_-]+:.*?##2 .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?##2 "}; {printf "\033[36m - %-30s\033[0m %s\n", $$1, $$2}' + @echo "---------------------------------------------------------------------------------------------------------" + @printf "\033[35mDevOps:%-30s\033[0m %s\n" + @grep -E '^[a-zA-Z_-]+:.*?##3 .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?##3 "}; {printf "\033[35m - %-30s\033[0m %s\n", $$1, $$2}' # ------------------------------------------------------------------------------------------------------------ -prod: ## Installs all production dependencies +prod: ##1 Installs all production dependencies # do not switch to production composer PROD, otherwise it would # also install shopware in here -> we just need it for the release composer.json file # so just switch to our dev dependency variant @@ -27,7 +34,7 @@ prod: ## Installs all production dependencies cd src/Resources/app/administration && npm install --omit=dev cd src/Resources/app/storefront && npm install --omit=dev -dev: ## Installs all dev dependencies +dev: ##1 Installs all dev dependencies php switch-composer.php dev @composer validate # we have to run update in dev mode, because dev dependencies are not compatible with newer php version. should be updated when support for 6.4 is dropped @@ -36,10 +43,10 @@ dev: ## Installs all dev dependencies cd src/Resources/app/storefront && npm install curl -1sLf 'https://dl.cloudsmith.io/public/friendsofshopware/stable/setup.deb.sh' | sudo -E bash && sudo apt install -y --allow-downgrades shopware-cli=0.3.18 -install: ## [deprecated] Installs all production dependencies. Please use "make prod" now. +install: ##1 [deprecated] Installs all production dependencies. Please use "make prod" now. @make prod -B -clean: ## Cleans all dependencies and files +clean: ##1 Cleans all dependencies and files rm -rf vendor/* # ------------------------------------------------------ rm -rf .reports | true @@ -52,7 +59,7 @@ clean: ## Cleans all dependencies and files rm -rf ./src/Resources/public/administration rm -rf ./src/Resources/public/mollie-payments.js -build: ## Installs the plugin, and builds the artifacts using the Shopware build commands. +build: ##3 Installs the plugin, and builds the artifacts using the Shopware build commands. # ----------------------------------------------------- # CUSTOM WEBPACK php switch-composer.php dev @@ -68,83 +75,93 @@ build: ## Installs the plugin, and builds the artifacts using the Shopware build cd ../../.. && php bin/console --no-debug assets:install cd ../../.. && php bin/console --no-debug cache:clear -fixtures: ## Installs all available testing fixtures of the Mollie plugin +fixtures: ##3 Installs all available testing fixtures of the Mollie plugin cd ../../.. && php bin/console --no-debug cache:clear cd ../../.. && php bin/console --no-debug fixture:load:group mollie # ------------------------------------------------------------------------------------------------------------ -phpcheck: ## Starts the PHP syntax checks +phpcheck: ##2 Starts the PHP syntax checks @find . -name '*.php' -not -path "./vendor/*" -not -path "./tests/*" | xargs -n 1 -P4 php -l -phpmin: ## Starts the PHP compatibility checks +phpmin: ##2 Starts the PHP compatibility checks @php vendor/bin/phpcs -p --standard=PHPCompatibility --extensions=php --runtime-set testVersion 7.4 ./src -csfix: ## Starts the PHP CS Fixer +csfix: ##2 Starts the PHP CS Fixer @PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer fix --config=./.php_cs.php --dry-run -stan: ## Starts the PHPStan Analyser +stan: ##2 Starts the PHPStan Analyser @php vendor/bin/phpstan analyse -c ./.phpstan.neon -phpunit: ## Starts all PHPUnit Tests +phpunit: ##2 Starts all PHPUnit Tests @XDEBUG_MODE=coverage php vendor/bin/phpunit --configuration=phpunit.xml --coverage-html ./.reports/phpunit/coverage -infection: ## Starts all Infection/Mutation tests +infection: ##2 Starts all Infection/Mutation tests @XDEBUG_MODE=coverage php vendor/bin/infection --configuration=./.infection.json --log-verbosity=all --debug -insights: ## Starts the PHPInsights Analyser +insights: ##2 Starts the PHPInsights Analyser @php vendor/bin/phpinsights analyse --no-interaction -jest: ## Starts all Jest tests +jest: ##2 Starts all Jest tests cd ./src/Resources/app/administration && ./node_modules/.bin/jest --config=.jest.config.js --coverage cd ./src/Resources/app/storefront && ./node_modules/.bin/jest --config=.jest.config.js --coverage -stryker: ## Starts the Stryker Jest Mutation Tests +stryker: ##2 Starts the Stryker Jest Mutation Tests cd ./src/Resources/app/administration && ./node_modules/.bin/stryker run .stryker.conf.json @# Storefront has no tests at the moment @# cd ./src/Resources/app/storefront && ./node_modules/.bin/stryker run .stryker.conf.json -eslint: ## Starts the ESLinter +eslint: ##2 Starts the ESLinter +ifndef mode cd ./src/Resources/app/administration && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src cd ./src/Resources/app/storefront && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src +endif +ifeq ($(mode), no-dry-run) + cd ./src/Resources/app/administration && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src --fix + cd ./src/Resources/app/storefront && ./node_modules/.bin/eslint --config ./.eslintrc.json ./src --fix +endif + + + -stylelint: ## Starts the Stylelinter + +stylelint: ##2 Starts the Stylelinter cd ./src/Resources/app/administration && ./node_modules/.bin/stylelint --allow-empty-input ./src/**/*.scss cd ./src/Resources/app/storefront && ./node_modules/.bin/stylelint --allow-empty-input ./src/**/*.scss -configcheck: ## Tests and verifies the plugin configuration file +configcheck: ##2 Tests and verifies the plugin configuration file cd ./tests/Custom && php verify-plugin-config.php # ------------------------------------------------------------------------------------------------------------ -snippetcheck: ## Tests and verifies all plugin snippets +snippetcheck: ##2 Tests and verifies all plugin snippets php vendor/bin/phpunuhi validate --configuration=./.phpunuhi.xml --report-format=junit --report-output=./.phpunuhi/junit.xml -snippetexport: ## Exports all snippets +snippetexport: ##2 Exports all snippets php vendor/bin/phpunuhi export --configuration=./.phpunuhi.xml --dir=./.phpunuhi -snippetimport: ## Imports the provided snippet set [set=xyz file=xz.csv] +snippetimport: ##2 Imports the provided snippet set [set=xyz file=xz.csv] php vendor/bin/phpunuhi import --configuration=./.phpunuhi.xml --set=$(set) --file=$(file) --intent=1 # ------------------------------------------------------------------------------------------------------------ -pr: ## Prepares everything for a Pull Request +pr: ##2 Prepares everything for a Pull Request @PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer fix --config=./.php_cs.php @make phpcheck -B @make phpmin -B @make stan -B @make phpunit -B - @make infection -B @make jest -B - @make stryker -B - @make eslint -B + @make eslint mode=no-dry-run -B @make stylelint -B @make configcheck -B @make snippetcheck -B + @make stryker -B + @make infection -B # ------------------------------------------------------------------------------------------------- -release: ## Builds a PROD version and creates a ZIP file in plugins/.build. +release: ##3 Builds a PROD version and creates a ZIP file in plugins/.build. ifneq (,$(findstring v12,$(NODE_VERSION))) $(warning Attention, reqruires Node v14 or higher to build a release!) @exit 1 diff --git a/src/Components/ApplePayDirect/ApplePayDirect.php b/src/Components/ApplePayDirect/ApplePayDirect.php index 764639abc..e703de143 100644 --- a/src/Components/ApplePayDirect/ApplePayDirect.php +++ b/src/Components/ApplePayDirect/ApplePayDirect.php @@ -22,6 +22,7 @@ use Kiener\MolliePayments\Service\OrderService; use Kiener\MolliePayments\Service\SettingsService; use Kiener\MolliePayments\Service\ShopService; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Mollie\Api\Exceptions\ApiException; use Shopware\Core\Checkout\Cart\Cart; use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection; @@ -251,7 +252,7 @@ public function addProduct(string $productId, int $quantity, SalesChannelContext { # if we already have a backup cart, then do NOT backup again. # because this could backup our temp. apple pay cart - if (!$this->cartBackupService->isBackupExisting($context)) { + if (! $this->cartBackupService->isBackupExisting($context)) { $this->cartBackupService->backupCart($context); } @@ -348,7 +349,7 @@ public function restoreCart(SalesChannelContext $context): void * @throws \Exception * @return SalesChannelContext */ - public function prepareCustomer(string $firstname, string $lastname, string $email, string $street, string $zipcode, string $city, string $countryCode, string $phone, string $paymentToken, int $acceptedDataProtection, SalesChannelContext $context): SalesChannelContext + public function prepareCustomer(string $firstname, string $lastname, string $email, string $street, string $zipcode, string $city, string $countryCode, string $phone, string $paymentToken, ?int $acceptedDataProtection, SalesChannelContext $context): SalesChannelContext { if (empty($paymentToken)) { throw new \Exception('PaymentToken not found!'); @@ -359,28 +360,24 @@ public function prepareCustomer(string $firstname, string $lastname, string $ema # if we are not logged in, # then we have to create a new guest customer for our express order - if (!$this->customerService->isCustomerLoggedIn($context)) { - $customer = $this->customerService->createApplePayDirectCustomerIfNotExists( - $firstname, - $lastname, - $email, - $phone, - $street, - $zipcode, - $city, - $countryCode, - $acceptedDataProtection, - $context + if (! $this->customerService->isCustomerLoggedIn($context)) { + $address = new AddressStruct($firstname, $lastname, $email, $street, '', $zipcode, $city, $countryCode, $phone); + + $customer = $this->customerService->createGuestAccount( + $address, + $applePayID, + $context, + $acceptedDataProtection ); - if (!$customer instanceof CustomerEntity) { + if (! $customer instanceof CustomerEntity) { throw new \Exception('Error when creating customer!'); } # now start the login of our customer. # Our SalesChannelContext will be correctly updated after our # forward to the finish-payment page. - $this->customerService->customerLogin($customer, $context); + $this->customerService->loginCustomer($customer, $context); } # also (always) update our payment method to use Apple Pay for our cart @@ -455,7 +452,7 @@ public function createPayment(OrderEntity $order, string $shopwareReturnUrl, str $transactions = $order->getTransactions(); $transaction = $transactions->last(); - if (!$transaction instanceof OrderTransactionEntity) { + if (! $transaction instanceof OrderTransactionEntity) { throw new \Exception('Created Apple Pay Direct order has not OrderTransaction!'); } diff --git a/src/Components/PaypalExpress/PayPalExpress.php b/src/Components/PaypalExpress/PayPalExpress.php new file mode 100644 index 000000000..05d2f5744 --- /dev/null +++ b/src/Components/PaypalExpress/PayPalExpress.php @@ -0,0 +1,205 @@ +repoPaymentMethods = $repoPaymentMethods; + $this->mollieApiFactory = $mollieApiFactory; + $this->priceBuilder = $priceBuilder; + $this->urlBuilder = $urlBuilder; + $this->customerService = $customerService; + $this->cartService = $cartService; + } + + + /** + * @param SalesChannelContext $context + * @return bool + */ + public function isPaypalExpressEnabled(SalesChannelContext $context): bool + { + try { + $methodID = $this->getActivePaypalExpressID($context); + + return (! empty($methodID)); + } catch (\Exception $ex) { + return false; + } + } + + /** + * @param SalesChannelContext $context + * @throws \Exception + * @return string + */ + public function getActivePaypalExpressID(SalesChannelContext $context): string + { + return $this->repoPaymentMethods->getActivePaypalExpressID($context->getContext()); + } + + + /** + * @param Cart $cart + * @param SalesChannelContext $context + * @throws \Mollie\Api\Exceptions\ApiException + * @return Session + */ + public function startSession(Cart $cart, SalesChannelContext $context): Session + { + $mollie = $this->mollieApiFactory->getLiveClient($context->getSalesChannelId()); + + $params = [ + 'method' => 'paypal', + 'methodDetails' => [ + 'checkoutFlow' => 'express', + ], + 'amount' => $this->priceBuilder->build( + $cart->getPrice()->getTotalPrice(), + $context->getCurrency()->getIsoCode() + ), + 'redirectUrl' => $this->urlBuilder->buildPaypalExpressRedirectUrl(), + 'cancelUrl' => $this->urlBuilder->buildPaypalExpressCancelUrl(), + ]; + + return $mollie->sessions->create($params); + } + + public function loadSession(string $sessionId, SalesChannelContext $context): Session + { + $mollie = $this->mollieApiFactory->getLiveClient($context->getSalesChannelId()); + /** + * if we load the session from mollie api, we dont get the shipping address at first time. usually it takes several seconds until the data from paypal is transfered to mollie + * so we try to load the session at least 5 times with increased waiting time. + */ + for ($i = 0; $i < self::SESSION_MAX_RETRY; $i++) { + $sleepTimer = self::SESSION_BASE_TIMEOUT * ($i+1); + usleep($sleepTimer); + $session = $mollie->sessions->get($sessionId); + if ($session->methodDetails !== null && $session->methodDetails->shippingAddress !== null) { + break; + } + } + + return $session; + } + + + + /** + * @param AddressStruct $shippingAddress + * @param SalesChannelContext $context + * @param null|AddressStruct $billingAddress + * @throws \Exception + * @return SalesChannelContext + */ + public function prepareCustomer(AddressStruct $shippingAddress, SalesChannelContext $context, ?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): SalesChannelContext + { + $updateShippingAddress = true; + $paypalExpressId = $this->getActivePaypalExpressID($context); + + $customer = $context->getCustomer(); + + # if we are not logged in, + # then we have to create a new guest customer for our express order + # check here for instance because of phpstan + if ($customer === null) { + + # find existing customer by email + $customer = $this->customerService->findCustomerByEmail($shippingAddress->getEmail(), $context); + + + if ($customer === null) { + $updateShippingAddress = false; + $customer = $this->customerService->createGuestAccount( + $shippingAddress, + $paypalExpressId, + $context, + $acceptedDataProtection, + $billingAddress + ); + } + + + if (! $customer instanceof CustomerEntity) { + throw new \Exception('Error when creating customer!'); + } + + # now start the login of our customer. + # Our SalesChannelContext will be correctly updated after our + # forward to the finish-payment page. + $this->customerService->loginCustomer($customer, $context); + } + + # if we have an existing customer, we want reuse his shipping address instead of creating new one + if ($updateShippingAddress) { + $this->customerService->reuseOrCreateAddresses($customer, $shippingAddress, $context->getContext(), $billingAddress); + } + + + # also (always) update our payment method to use Apple Pay for our cart + return $this->cartService->updatePaymentMethod($context, $paypalExpressId); + } +} diff --git a/src/Components/ShipmentManager/ShipmentManager.php b/src/Components/ShipmentManager/ShipmentManager.php index 50964c074..f1660c913 100644 --- a/src/Components/ShipmentManager/ShipmentManager.php +++ b/src/Components/ShipmentManager/ShipmentManager.php @@ -348,7 +348,7 @@ private function findMatchingLineItems(OrderEntity $order, string $itemIdentifie // Check itemIdentifier against the mollie order_line_id custom field $customFields = $lineItem->getCustomFields() ?? []; - $mollieOrderLineId = $customFields[CustomFieldsInterface::MOLLIE_KEY]['order_line_id'] ?? null; + $mollieOrderLineId = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_LINE_KEY] ?? null; if (!is_null($mollieOrderLineId) && $mollieOrderLineId === $itemIdentifier) { return true; } diff --git a/src/Controller/Api/Order/OrderControllerBase.php b/src/Controller/Api/Order/OrderControllerBase.php index e16a7d1cc..7ae33ffc0 100644 --- a/src/Controller/Api/Order/OrderControllerBase.php +++ b/src/Controller/Api/Order/OrderControllerBase.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Controller\Api\Order; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Service\MollieApi\Order; use Kiener\MolliePayments\Service\OrderService; use Shopware\Core\Framework\Context; @@ -56,7 +57,7 @@ private function paymentUrlResponse(string $orderId, Context $context): JsonResp $customFields = $order->getCustomFields(); - $mollieOrderId = ($customFields !== null && isset($customFields['mollie_payments']['order_id'])) ? $customFields['mollie_payments']['order_id'] : null; + $mollieOrderId = ($customFields !== null && isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY])) ? $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY] : null; if (is_null($mollieOrderId)) { return $this->json([], 404); diff --git a/src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php b/src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php new file mode 100644 index 000000000..3a5209c1c --- /dev/null +++ b/src/Controller/Storefront/PaypalExpress/PaypalExpressControllerBase.php @@ -0,0 +1,247 @@ +paypalExpress = $paypalExpress; + $this->cartService = $cartService; + $this->router = $router; + $this->logger = $logger; + $this->settingsService = $settingsService; + } + + /** + * @param Request $request + * @param SalesChannelContext $context + * @throws ApiException + * @return Response + */ + public function startCheckout(Request $request, SalesChannelContext $context): Response + { + $redirectUrl = $this->getCheckoutCartPage($this->router); + + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + + if ($settings->isPaypalExpressEnabled() === false) { + $this->logger->error('Paypal Express is disabled'); + return new RedirectResponse($redirectUrl); + } + + $cart = $this->cartService->getCalculatedMainCart($context); + + + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + $oldSessionId = null; + + if ($cartExtension instanceof ArrayStruct) { + $oldSessionId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY] ?? null; + } + + if ($oldSessionId !== null) { + $session = $this->paypalExpress->loadSession($oldSessionId, $context); + } else { + $session = $this->paypalExpress->startSession($cart, $context); + + $cartExtension = [ + CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY => $session->id + ]; + + if ($settings->isRequireDataProtectionCheckbox()) { + $cartExtension[CustomFieldsInterface::ACCEPTED_DATA_PROTECTION] = (bool)$request->get(CustomFieldsInterface::ACCEPTED_DATA_PROTECTION, false); + } + + $cart->addExtension(CustomFieldsInterface::MOLLIE_KEY, new ArrayStruct($cartExtension)); + + $this->cartService->persistCart($cart, $context); + } + + $sessionRedirect = $session->getRedirectUrl(); + if ($sessionRedirect !== null) { + $redirectUrl = $sessionRedirect; + } + + return new RedirectResponse($redirectUrl); + } + + /** + * @param SalesChannelContext $context + * @return Response + */ + public function finishCheckout(SalesChannelContext $context): Response + { + $returnUrl = $this->getCheckoutCartPage($this->router); + + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + + if ($settings->isPaypalExpressEnabled() === false) { + $this->logger->error('Paypal Express is disabled'); + return new RedirectResponse($returnUrl); + } + + $cart = $this->cartService->getCalculatedMainCart($context); + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + + $payPalExpressSessionId = null; + $acceptedDataProtection = null; + + if ($cartExtension instanceof ArrayStruct) { + $payPalExpressSessionId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY] ?? null; + if ($settings->isRequireDataProtectionCheckbox()) { + $acceptedDataProtection = $cartExtension[CustomFieldsInterface::ACCEPTED_DATA_PROTECTION] ?? false; + } + } + + + + + + if ($payPalExpressSessionId === null) { + $this->logger->error('Failed to finish checkout, session not exists'); + + return new RedirectResponse($returnUrl); + } + + try { + $payPalExpressSession = $this->paypalExpress->loadSession($payPalExpressSessionId, $context); + } catch (\Throwable $e) { + $this->logger->critical('Failed to load session from mollie', [ + 'message' => $e->getMessage(), + 'sessionId' => $payPalExpressSessionId + ]); + return new RedirectResponse($returnUrl); + } + + + $methodDetails = $payPalExpressSession->methodDetails; + + + if ($methodDetails->shippingAddress === null) { + $this->logger->error('Failed to finish checkout, got methodDetails without shipping address', [ + 'sessionId' => $payPalExpressSession->id, + 'status' => $payPalExpressSession->status + ]); + + return new RedirectResponse($returnUrl); + } + if ($methodDetails->billingAddress === null) { + $this->logger->error('Failed to finish checkout, got methodDetails without billing address', [ + 'sessionId' => $payPalExpressSession->id, + 'status' => $payPalExpressSession->status + ]); + + return new RedirectResponse($returnUrl); + } + + $billingAddress = null; + + $shippingAddress = $methodDetails->shippingAddress; + $shippingAddress->phone = ''; + if ($methodDetails->billingAddress->streetAdditional !== null) { + $shippingAddress->streetAdditional = $methodDetails->billingAddress->streetAdditional; + } + if ($methodDetails->billingAddress->phone !== null) { + $shippingAddress->phone = $methodDetails->billingAddress->phone; + } + if ($methodDetails->billingAddress->email !== null) { + $shippingAddress->email = $methodDetails->billingAddress->email; + } + if ($methodDetails->billingAddress->streetAndNumber !== null) { + try { + $billingAddress = AddressStruct::createFromApiResponse($methodDetails->billingAddress); + } catch (\Throwable $e) { + $this->logger->error('Failed to create billing address', [ + 'message' => $e->getMessage(), + 'shippingAddress' => $shippingAddress, + 'billingAddress' => $methodDetails->billingAddress + ]); + return new RedirectResponse($returnUrl); + } + } + + try { + $shippingAddress = AddressStruct::createFromApiResponse($shippingAddress); + } catch (\Throwable $e) { + $this->logger->error('Failed to create shipping address', [ + 'message' => $e->getMessage(), + 'shippingAddress' => $shippingAddress, + 'billingAddress' => $methodDetails->billingAddress + ]); + return new RedirectResponse($returnUrl); + } + + + try { + # we have to update the cart extension before a new user is created and logged in, otherwise the extension is not saved + $cartExtension = new ArrayStruct([ + CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID => $payPalExpressSession->authenticationId + ]); + $cart->addExtension(CustomFieldsInterface::MOLLIE_KEY, $cartExtension); + + $this->cartService->updateCart($cart); + + $this->cartService->persistCart($cart, $context); + + + # create new account or find existing and login + $this->paypalExpress->prepareCustomer($shippingAddress, $context, $acceptedDataProtection, $billingAddress); + } catch (\Throwable $e) { + $this->logger->error('Failed to create customer or cart', [ + 'message' => $e->getMessage(), + ]); + return new RedirectResponse($returnUrl); + } + + + $returnUrl = $this->getCheckoutConfirmPage($this->router); + return new RedirectResponse($returnUrl); + } +} diff --git a/src/Facade/MolliePaymentDoPay.php b/src/Facade/MolliePaymentDoPay.php index 3547ee2df..f356a0240 100644 --- a/src/Facade/MolliePaymentDoPay.php +++ b/src/Facade/MolliePaymentDoPay.php @@ -9,10 +9,11 @@ use Kiener\MolliePayments\Exception\MollieOrderExpiredException; use Kiener\MolliePayments\Exception\PaymentUrlException; use Kiener\MolliePayments\Handler\Method\CreditCardPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Kiener\MolliePayments\Handler\Method\PosPayment; use Kiener\MolliePayments\Handler\PaymentHandler; use Kiener\MolliePayments\Service\CustomerService; -use Kiener\MolliePayments\Service\CustomFieldService; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Service\Mollie\MolliePaymentStatus; use Kiener\MolliePayments\Service\MollieApi\Builder\MollieOrderBuilder; use Kiener\MolliePayments\Service\MollieApi\Order; @@ -276,6 +277,11 @@ public function startMolliePayment(string $paymentMethod, AsyncPaymentTransactio if ($paymentHandler instanceof CreditCardPayment) { $checkoutURL = $transactionStruct->getReturnUrl(); } + + # paypal express does not have a redirect since we were already on paypal site before + if ($paymentHandler instanceof PayPalExpressPayment) { + $checkoutURL = $transactionStruct->getReturnUrl(); + } } return new MolliePaymentPrepareData((string)$checkoutURL, (string)$molliePaymentData->getId()); @@ -301,8 +307,8 @@ public function createCustomerAtMollie(OrderEntity $order, SalesChannelContext $ $customFields = $customer->getCustomFields(); - if (isset($customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS])) { - $mollieData = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS]; + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY])) { + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; $oneClickShouldSaveCard = (isset($mollieData[CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL])) ? (bool)$mollieData[CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] : false; $oneClickIsReused = (isset($mollieData[CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID])) ? (bool)$mollieData[CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID] : false; diff --git a/src/Handler/Method/CreditCardPayment.php b/src/Handler/Method/CreditCardPayment.php index a2fe5f852..4bcb6109b 100644 --- a/src/Handler/Method/CreditCardPayment.php +++ b/src/Handler/Method/CreditCardPayment.php @@ -4,7 +4,7 @@ use Kiener\MolliePayments\Handler\PaymentHandler; use Kiener\MolliePayments\Service\CustomerService; -use Kiener\MolliePayments\Service\CustomFieldService; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Mollie\Api\Types\PaymentMethod; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -49,13 +49,13 @@ public function __construct( public function processPaymentMethodSpecificParameters(array $orderData, OrderEntity $orderEntity, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array { $customFields = $customer->getCustomFields() ?? []; - $cardToken = $customFields['mollie_payments']['credit_card_token'] ?? ''; + $cardToken = $customFields[CustomFieldsInterface::MOLLIE_KEY]['credit_card_token'] ?? ''; if (!empty($cardToken)) { $orderData['payment']['cardToken'] = $cardToken; $this->customerService->setCardToken($customer, '', $salesChannelContext); - $isSaveCardToken = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] ?? false; + $isSaveCardToken = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomerService::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] ?? false; # change payment sequenceType to first if this is a single-click payment if ($this->enableSingleClickPayment && $isSaveCardToken) { $orderData['payment']['sequenceType'] = PaymentHandler::PAYMENT_SEQUENCE_TYPE_FIRST; @@ -70,7 +70,7 @@ public function processPaymentMethodSpecificParameters(array $orderData, OrderEn return $orderData; } - $mandateId = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID] ?? ''; + $mandateId = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomerService::CUSTOM_FIELDS_KEY_MANDATE_ID] ?? ''; if (empty($mandateId)) { return $orderData; } diff --git a/src/Handler/Method/PayPalExpressPayment.php b/src/Handler/Method/PayPalExpressPayment.php new file mode 100644 index 000000000..a114fffa7 --- /dev/null +++ b/src/Handler/Method/PayPalExpressPayment.php @@ -0,0 +1,41 @@ + $orderData + * @param OrderEntity $orderEntity + * @param SalesChannelContext $salesChannelContext + * @param CustomerEntity $customer + * @return array + */ + public function processPaymentMethodSpecificParameters(array $orderData, OrderEntity $orderEntity, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array + { + $orderData['authenticationId'] = $orderEntity->getCustomFields()[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? null; + return $orderData; + } +} diff --git a/src/Handler/Method/PosPayment.php b/src/Handler/Method/PosPayment.php index 1340d9be9..dea7a20e4 100644 --- a/src/Handler/Method/PosPayment.php +++ b/src/Handler/Method/PosPayment.php @@ -4,7 +4,7 @@ use Kiener\MolliePayments\Handler\PaymentHandler; use Kiener\MolliePayments\Service\CustomerService; -use Kiener\MolliePayments\Service\CustomFieldService; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Mollie\Api\Types\PaymentMethod; use Shopware\Core\Checkout\Customer\CustomerEntity; use Shopware\Core\Checkout\Order\OrderEntity; @@ -37,7 +37,7 @@ public function processPaymentMethodSpecificParameters(array $orderData, OrderEn { $customFields = $customer->getCustomFields() ?? []; - $this->selectedTerminalId = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][CustomerService::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] ?? ''; + $this->selectedTerminalId = $customFields[CustomFieldsInterface::MOLLIE_KEY][CustomerService::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] ?? ''; return $orderData; } diff --git a/src/Migration/Migration1725347559MollieTags.php b/src/Migration/Migration1725347559MollieTags.php index 28cabbe4f..275ba01d7 100644 --- a/src/Migration/Migration1725347559MollieTags.php +++ b/src/Migration/Migration1725347559MollieTags.php @@ -53,7 +53,7 @@ private function createTag( } $query = <<customerAddressRepository = $customerAddressRepository; } + + public function upsert(array $data, Context $context): EntityWrittenContainerEvent + { + return $this->customerAddressRepository->upsert($data, $context); + } + + public function create(array $data, Context $context): EntityWrittenContainerEvent + { + return $this->customerAddressRepository->create($data, $context); + } + + public function search(Criteria $criteria, Context $context): EntitySearchResult + { + return $this->customerAddressRepository->search($criteria, $context); + } + + public function update(array $data, Context $context): EntityWrittenContainerEvent + { + return $this->customerAddressRepository->update($data, $context); + } + /** * @param array $ids * @param Context $context diff --git a/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php b/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php index d7ce9c077..d3380ea4f 100644 --- a/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php +++ b/src/Repository/CustomerAddress/CustomerAddressRepositoryInterface.php @@ -5,9 +5,39 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult; interface CustomerAddressRepositoryInterface { + /** + * @param array $data + * @param Context $context + * @return EntityWrittenContainerEvent + */ + public function upsert(array $data, Context $context): EntityWrittenContainerEvent; + + /** + * @param array $data + * @param Context $context + * @return EntityWrittenContainerEvent + */ + public function create(array $data, Context $context): EntityWrittenContainerEvent; + + /** + * @param Criteria $criteria + * @param Context $context + * @return EntitySearchResult + */ + public function search(Criteria $criteria, Context $context): EntitySearchResult; + + /** + * @param array $data + * @param Context $context + * @return EntityWrittenContainerEvent + */ + public function update(array $data, Context $context): EntityWrittenContainerEvent; + /** * @param array $ids * @param Context $context diff --git a/src/Repository/PaymentMethod/PaymentMethodRepository.php b/src/Repository/PaymentMethod/PaymentMethodRepository.php index 4eed03162..a6a313dea 100644 --- a/src/Repository/PaymentMethod/PaymentMethodRepository.php +++ b/src/Repository/PaymentMethod/PaymentMethodRepository.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Repository\PaymentMethod; use Kiener\MolliePayments\Handler\Method\ApplePayPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; @@ -77,4 +78,25 @@ public function getActiveApplePayID(Context $context): string return (string)$paymentMethods[0]; } + + /** + * @param Context $context + * @throws \Exception + * @return string + */ + public function getActivePaypalExpressID(Context $context): string + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('handlerIdentifier', PayPalExpressPayment::class)); + $criteria->addFilter(new EqualsFilter('active', true)); + + /** @var array $paymentMethods */ + $paymentMethods = $this->repoPaymentMethods->searchIds($criteria, $context)->getIds(); + + if (count($paymentMethods) <= 0) { + throw new \Exception('Payment Method PayPal Express not found in system'); + } + + return (string)$paymentMethods[0]; + } } diff --git a/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js b/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js index 2d77a129e..413006563 100644 --- a/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js +++ b/src/Resources/app/administration/src/module/mollie-payments/extension/sw-order/view/sw-order-detail-general/index.js @@ -228,12 +228,12 @@ Component.override('sw-order-detail-general', { * */ copyPaymentUrlToClipboard() { - let fallback = async function(e) { + const fallback = async function(e) { await navigator.clipboard.writeText(e) }; // eslint-disable-next-line no-undef - let clipboard = typeof Shopware.Utils.dom.copyToClipboard === 'function' ? Shopware.Utils.dom.copyToClipboard : fallback; + const clipboard = typeof Shopware.Utils.dom.copyToClipboard === 'function' ? Shopware.Utils.dom.copyToClipboard : fallback; // eslint-disable-next-line no-undef clipboard(this.molliePaymentUrl); this.molliePaymentUrlCopied = true; diff --git a/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js b/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js index f9942cb00..b8064f136 100644 --- a/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/core/creditcard-mandate.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; /** * This plugin manage the credit card mandate of the customer diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js index 53f974b58..d0666e18c 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-direct.plugin.js @@ -1,229 +1,81 @@ -import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; -import ApplePaySessionFactory from "../services/ApplePaySessionFactory"; +import Plugin from '../Plugin'; +import ApplePaySessionFactory from '../services/ApplePaySessionFactory'; +import ExpressButtonsRepository from '../repository/ExpressButtonsRepository'; +import BuyElementRepository from '../repository/BuyElementRepository'; +import {MOLLIE_BIND_EXPRESS_EVENTS} from './mollie-express-actions.plugin'; export default class MollieApplePayDirect extends Plugin { - /** - * - * @type {number} - */ - APPLE_PAY_VERSION = 3; - /** * */ init() { - const me = this; - me.client = new HttpClient(); - - // register our off-canvas listener - // we need to re-init all apple pay button - // once the offcanvas is loaded (lazy) into the DOM - - const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get("instances"); + const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get('instances'); if (pluginOffCanvasInstances.length > 0) { - const pluginOffCanvas = pluginOffCanvasInstances[0]; - pluginOffCanvas.$emitter.subscribe('offCanvasOpened', me.initCurrentPage.bind(me)); - } - - - const submitForm = document.querySelector('#productDetailPageBuyProductForm'); - - if (submitForm !== null) { - this.checkSubmitButton(submitForm); - submitForm.addEventListener('change', (event) => { - this.checkSubmitButton(event.target.closest('form#productDetailPageBuyProductForm')); - this.initCurrentPage(); + pluginOffCanvasInstances.forEach((pluginOffCanvas) => { + pluginOffCanvas.$emitter.subscribe('offCanvasOpened', this.bindEvents.bind(this)); }); } - // now update our current page - this.initCurrentPage(); - + this.bindEvents(); } - - /** - * - */ - initCurrentPage() { - - const me = this; - - // we might have wrapping containers - // that also need to be hidden -> they might have different margins or other things - const applePayContainers = document.querySelectorAll('.js-apple-pay-container'); - // of course, also grab our real buttons - const applePayButtons = document.querySelectorAll('.js-apple-pay'); - + bindEvents() { if (!window.ApplePaySession || !window.ApplePaySession.canMakePayments()) { - // hide our wrapping Apple Pay containers - // to avoid any wrong margins being displayed - - if (applePayContainers) { - applePayContainers.forEach(function (container) { - container.style.display = 'none'; - container.classList.add('d-none'); - }); - } return; } + const expressButtonsRepository = new ExpressButtonsRepository(); + const expressButtons = expressButtonsRepository.findAll('.js-apple-pay'); + const applePayContainers = document.querySelectorAll('.js-apple-pay-container'); - - if (applePayButtons.length <= 0) { + if (expressButtons.length === 0 && applePayContainers.length === 0) { return; } - // we start by fetching the shop url from the data attribute. - // we need this as prefix for our ajax calls, so that we always - // call the correct sales channel and its controllers. - - const shopUrl = me.getShopUrl(applePayButtons[0]); - - - // verify if apple pay is even allowed - // in our current sales channel - me.client.get( - shopUrl + '/mollie/apple-pay/available', - data => { - if (data.available === undefined || data.available === false) { - return; - } - - applePayContainers.forEach(function (container) { - container.classList.remove('d-none'); - }); - - applePayButtons.forEach(function (button) { - - if (button.hasAttribute('disabled')) { - button.classList.add('d-none'); - button.removeEventListener('click', me.onButtonClick); - return; - } - // Remove display none - button.classList.remove('d-none'); - // remove previous handlers (just in case) - button.removeEventListener('click', me.onButtonClick); - // add click event handlers - button.addEventListener('click', me.onButtonClick); - }); - } - ); - } + document.dispatchEvent(new CustomEvent(MOLLIE_BIND_EXPRESS_EVENTS, {detail: expressButtons})); - checkSubmitButton(form) { - const buyButton = form.querySelector('.btn-buy'); + applePayContainers.forEach((container) => { + container.classList.remove('d-none'); + }) - if (buyButton === null) { - return; - } + expressButtons.forEach((button) => { + button.classList.remove('d-none'); + button.addEventListener('click', this.onExpressCheckout) + }); + } - const applePayButton = form.querySelector('.js-apple-pay'); + onExpressCheckout(event) { + const clickedButton = event.target; - if (applePayButton === null) { + if (!clickedButton.classList.contains('processed')) { return; } - if (applePayButton.hasAttribute('disabled')) { - applePayButton.removeAttribute('disabled'); - } - if (buyButton.hasAttribute('disabled')) { - applePayButton.setAttribute('disabled', 'disabled'); - } - - } - - /** - * - * @param event - */ - onButtonClick(event) { - - event.preventDefault(); - const button = event.target; - const form = button.parentNode; - // get sales channel base URL - // so that our shop slug is correctly - let shopSlug = button.getAttribute('data-shop-url'); + const buyElementRepository = new BuyElementRepository(); + const buyElement = buyElementRepository.find(clickedButton); - // remove trailing slash if existing - if (shopSlug.substr(-1) === '/') { - shopSlug = shopSlug.substr(0, shopSlug.length - 1); - } - - const countryCode = form.querySelector('input[name="countryCode"]').value; - const currency = form.querySelector('input[name="currency"]').value; - const mode = form.querySelector('input[name="mode"]').value; - const withPhone = parseInt(form.querySelector('input[name="withPhone"]').value); - const dataProtection = form.querySelector('input[name="acceptedDataProtection"]'); - form.classList.remove('was-validated'); + const countryCode = buyElement.querySelector('input[name="countryCode"]').value; + const currency = buyElement.querySelector('input[name="currency"]').value; + const mode = buyElement.querySelector('input[name="mode"]').value; + const withPhone = parseInt(buyElement.querySelector('input[name="withPhone"]').value); + const dataProtection = buyElement.querySelector('input[name="acceptedDataProtection"]'); + const isProductMode = mode === 'productMode'; - if (dataProtection !== null) { - const dataProtectionValue = dataProtection.checked ? 1: 0; - form.classList.add('was-validated'); + let shopSlug = clickedButton.getAttribute('data-shop-url'); - dataProtection.classList.remove('is-invalid'); - if (dataProtectionValue === 0) { - dataProtection.classList.add('is-invalid'); - return; - } + if (shopSlug.slice(-1) === '/') { + shopSlug = shopSlug.slice(0, -1); } - // this helps us to figure out if we are in - // "product" mode to purchase a single product, or in "cart" mode - // to just purchase the current cart with Apple Pay Direct. - const isProductMode = (mode === 'productMode'); - - if (isProductMode) { - - let productForm = document.querySelector('#productDetailPageBuyProductForm'); - if (productForm === null) { - productForm = button.closest('.product-box').querySelector('form'); - } - - - const formData = new FormData(productForm); - formData.delete("redirectTo"); - formData.append("isExpressCheckout", "1"); - - - fetch(productForm.action, { - method: productForm.method, - body: formData, - }); - - - } const applePaySessionFactory = new ApplePaySessionFactory(); const session = applePaySessionFactory.create(isProductMode, countryCode, currency, withPhone, shopSlug, dataProtection); session.begin(); } - - /** - * - * @param button - * @returns string - */ - getShopUrl(button) { - // get sales channel base URL - // so that our shop slug is correctly - let shopSlug = button.getAttribute('data-shop-url'); - - // remove trailing slash if existing - if (shopSlug.substr(-1) === '/') { - shopSlug = shopSlug.substr(0, shopSlug.length - 1); - } - - return shopSlug; - } - } diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js index 4b0a4cbd3..ca6674e1c 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/apple-pay-payment-method.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; export default class MollieApplePayPaymentMethod extends Plugin { diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js index eee0d5b46..4d0d5a005 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/bancomat-plugin.js @@ -1,4 +1,4 @@ -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; export default class MollieBancomatPlugin extends Plugin { diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js index 0903a86c0..10cd4e9fb 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/creditcard-mandate-manage.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient'; -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; /** * This plugin manage the credit card mandate of customer diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js new file mode 100644 index 000000000..a835a63b6 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/mollie-express-actions.plugin.js @@ -0,0 +1,114 @@ +import Plugin from '../Plugin'; +import {PrivacyNoteElement} from '../repository/PrivacyNoteElement'; +import BuyButtonRepository from '../repository/BuyButtonRepository'; +import ExpressAddToCart from '../services/ExpressAddToCart'; + +export const MOLLIE_BIND_EXPRESS_EVENTS = 'BindExpressEvents'; + +export class MollieExpressActions extends Plugin { + + + init() { + + const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get('instances'); + if (pluginOffCanvasInstances.length > 0) { + pluginOffCanvasInstances.forEach((pluginOffCanvas) => { + pluginOffCanvas.$emitter.subscribe('offCanvasOpened', this.bindEvents.bind(this)); + }); + } + + this.bindEvents(); + + } + + bindEvents() { + + const privacyNote = new PrivacyNoteElement(); + privacyNote.observeButtons(); + + document.addEventListener(MOLLIE_BIND_EXPRESS_EVENTS, (event) => { + const expressButtons = event.detail; + + if (expressButtons.length === 0) { + return; + } + + + + const buyButtonRepository = new BuyButtonRepository(); + + expressButtons.forEach((button) => { + + + button.classList.remove('d-none'); + button.addEventListener('click', this.onButtonClick) + + const buyButton = buyButtonRepository.find(button); + if (!(buyButton instanceof HTMLButtonElement)) { + return; + } + + if (buyButton.hasAttribute('disabled')) { + button.classList.add('d-none'); + button.removeEventListener('click', this.onButtonClick) + } + + const buyButtonForm = buyButton.closest('form'); + if (!(buyButtonForm instanceof HTMLFormElement)) { + return; + } + + buyButtonForm.addEventListener('change', () => { + + button.classList.remove('d-none'); + button.addEventListener('click', this.onButtonClick) + + if (buyButton.hasAttribute('disabled')) { + + button.classList.add('d-none'); + button.removeEventListener('click', this.onButtonClick) + } + + }) + + }); + + }); + } + + onButtonClick(event) { + + let target = event.target; + if (!(target instanceof HTMLButtonElement)) { + target = target.closest('button'); + } + + if (target.classList.contains('processed')) { + return; + } + + + + const privacyNote = new PrivacyNoteElement(); + + const privacyNoteElement = privacyNote.find(target); + + if (privacyNoteElement instanceof HTMLDivElement) { + privacyNoteElement.classList.add('was-validated'); + const isValid = privacyNote.validate(privacyNoteElement); + if (isValid === false) { + return; + } + } + + + const expressAddToCart = new ExpressAddToCart(); + + expressAddToCart.addItemToCart(target); + + target.classList.add('processed'); + const mollieEvent = new event.constructor(event.type, event); + target.dispatchEvent(mollieEvent); + target.classList.remove('processed'); + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js new file mode 100644 index 000000000..a0c4533a8 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/paypal-express.plugin.js @@ -0,0 +1,65 @@ +import Plugin from '@shopware-storefront-sdk/plugin-system/plugin.class'; +import ExpressButtonsRepository from '../repository/ExpressButtonsRepository'; +import {PrivacyNoteElement} from '../repository/PrivacyNoteElement'; +import {MOLLIE_BIND_EXPRESS_EVENTS} from './mollie-express-actions.plugin'; + +export default class PayPalExpressPlugin extends Plugin { + + init() { + const pluginOffCanvasInstances = window.PluginManager.getPluginList().OffCanvasCart.get('instances'); + if (pluginOffCanvasInstances.length > 0) { + pluginOffCanvasInstances.forEach((pluginOffCanvas) => { + pluginOffCanvas.$emitter.subscribe('offCanvasOpened', this.bindEvents.bind(this)); + }); + } + + this.bindEvents(); + + } + + bindEvents() { + const expressButtonsRepository = new ExpressButtonsRepository(); + + const expressButtons = expressButtonsRepository.findAll('.mollie-paypal-button'); + + if (expressButtons.length === 0) { + return; + } + + document.dispatchEvent(new CustomEvent(MOLLIE_BIND_EXPRESS_EVENTS, {detail: expressButtons})); + + expressButtons.forEach((button) => { + button.addEventListener('click', this.onExpressCheckout) + }); + + } + + onExpressCheckout(event) { + + const clickedButton = event.target; + if (!clickedButton.classList.contains('processed')) { + return; + } + + + const submitUrl = clickedButton.getAttribute('data-form-action'); + + const form = document.createElement('form'); + form.setAttribute('action', submitUrl); + form.setAttribute('method', 'POST'); + + const privacyNoteElement = new PrivacyNoteElement(); + const privacyNote = privacyNoteElement.find(clickedButton); + if (privacyNote instanceof HTMLDivElement) { + const checkbox = privacyNoteElement.getCheckbox(privacyNote); + const checkboxValue = checkbox.checked ? 'on' : ''; + form.setAttribute('acceptedDataProtection', checkboxValue); + } + + document.body.insertAdjacentElement('beforeend', form); + + form.submit(); + } + + +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js b/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js index 610f1dd7c..fb7015452 100644 --- a/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js +++ b/src/Resources/app/storefront/src/mollie-payments/plugins/pos-terminal.plugin.js @@ -1,5 +1,5 @@ import HttpClient from '../services/HttpClient' -import Plugin from "../Plugin"; +import Plugin from '../Plugin'; export default class MolliePosTerminalPlugin extends Plugin { diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js b/src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js new file mode 100644 index 000000000..1f903611c --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/BuyButtonRepository.js @@ -0,0 +1,17 @@ +import BuyElementRepository from './BuyElementRepository'; + +export default class BuyButtonRepository { + constructor() { + this.buyElementRepository = new BuyElementRepository(); + } + + find(button) { + const buyElementContainer = this.buyElementRepository.find(button); + if (buyElementContainer === null) { + return null; + } + return buyElementContainer.querySelector('.btn-buy'); + } + + +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js b/src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js new file mode 100644 index 000000000..ddb4ca1d0 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/BuyElementRepository.js @@ -0,0 +1,21 @@ +export default class BuyElementRepository { + find(target) { + let buyElementContainer = target.closest('.product-action'); + + if (buyElementContainer === null) { + buyElementContainer = target.closest('.product-detail-form-container'); + } + if(buyElementContainer === null){ + buyElementContainer = target.closest('.offcanvas-cart-actions'); + } + + if(buyElementContainer === null){ + buyElementContainer = target.closest('.checkout-aside-container'); + } + if(buyElementContainer === null){ + buyElementContainer = target.closest('.checkout-main'); + } + + return buyElementContainer; + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js b/src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js new file mode 100644 index 000000000..3550335bd --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/ExpressButtonsRepository.js @@ -0,0 +1,16 @@ +export default class ExpressButtonsRepository { + + constructor(target = null) { + this.target = target; + if(this.target === null){ + this.target = document; + } + } + findAll(additionalSelector = null) { + let selector = '.mollie-express-button'; + if(additionalSelector !== null){ + selector += additionalSelector; + } + return this.target.querySelectorAll(selector); + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js b/src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js new file mode 100644 index 000000000..1c30fc24f --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/repository/PrivacyNoteElement.js @@ -0,0 +1,86 @@ +import BuyElementRepository from './BuyElementRepository'; +import ExpressButtonsRepository from './ExpressButtonsRepository'; + +export const TOGGLE_PRIVACY_NOTE_EVENT = 'TogglePrivacyNote'; + +export class PrivacyNoteElement { + constructor() { + this.buyElementRepository = new BuyElementRepository(); + } + + find(button) { + const buyElementContainer = this.buyElementRepository.find(button); + if (buyElementContainer === null) { + return null; + } + return buyElementContainer.querySelector('.mollie-privacy-note'); + } + + getCheckbox(privacyNote) { + return privacyNote.querySelector('input[name="acceptedDataProtection"]'); + } + + validate(privacyNote) { + + const dataProtection = this.getCheckbox(privacyNote); + + const dataProtectionValue = dataProtection.checked ? 1 : 0; + dataProtection.classList.remove('is-invalid'); + + if (dataProtectionValue === 0) { + dataProtection.classList.add('is-invalid'); + return false; + } + return true; + } + + observeButtons() { + + const privacyNotes = document.querySelectorAll('.mollie-privacy-note:not(.observed)'); + const buyElementRepository = new BuyElementRepository(); + + privacyNotes.forEach((privacyNote) => { + + privacyNote.classList.add('observed'); + + const buyElement = buyElementRepository.find(privacyNote); + const expressButtonsRepository = new ExpressButtonsRepository(buyElement); + const expressButtons = expressButtonsRepository.findAll(':not(.d-none)'); + + + if(expressButtons.length === 0){ + privacyNote.classList.add('d-none'); + } + + + expressButtons.forEach((expressButton) => { + + const observer = new MutationObserver((mutations) => { + let visibleExpressButtons = expressButtons.length; + privacyNote.classList.remove('d-none'); + + mutations.forEach((mutation) => { + + if(mutation.target.classList.contains('d-none')){ + visibleExpressButtons--; + } + + }); + + + if(visibleExpressButtons <= 0){ + privacyNote.classList.add('d-none'); + } + + }); + + observer.observe(expressButton, {attributes: true, attributeFilter: ['class']}); + }) + + + }) + + + } + +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js b/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js index 54974e0c3..3fa9a1958 100644 --- a/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js +++ b/src/Resources/app/storefront/src/mollie-payments/services/ApplePaySessionFactory.js @@ -1,4 +1,4 @@ -import HttpClient from "./HttpClient"; +import HttpClient from './HttpClient'; export default class ApplePaySessionFactory { /** @@ -19,7 +19,7 @@ export default class ApplePaySessionFactory { * @param shopSlug * @param withPhone * @param dataProtection - * @returns {ApplePaySession} + * @returns ApplePaySession */ create(isProductMode, country, currency, withPhone, shopSlug, dataProtection) { diff --git a/src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js b/src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js new file mode 100644 index 000000000..64e0188a5 --- /dev/null +++ b/src/Resources/app/storefront/src/mollie-payments/services/ExpressAddToCart.js @@ -0,0 +1,25 @@ +import BuyButtonRepository from '../repository/BuyButtonRepository'; + +export default class ExpressAddToCart { + addItemToCart(button) { + const buyButtonRepository = new BuyButtonRepository(); + const buyButton = buyButtonRepository.find(button); + if (!(buyButton instanceof HTMLButtonElement)) { + return; + + } + const buyButtonForm = buyButton.closest('form'); + if (!(buyButtonForm instanceof HTMLFormElement)) { + return; + } + + const formData = new FormData(buyButtonForm); + formData.delete('redirectTo'); + formData.append('isExpressCheckout', '1'); + + fetch(buyButtonForm.action, { + method: buyButtonForm.method, + body: formData, + }).finally(() =>{}); + } +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/register.js b/src/Resources/app/storefront/src/register.js index bfd76dc6a..fd23720dd 100644 --- a/src/Resources/app/storefront/src/register.js +++ b/src/Resources/app/storefront/src/register.js @@ -4,10 +4,12 @@ import MollieApplePayDirect from './mollie-payments/plugins/apple-pay-direct.plu import MollieApplePayPaymentMethod from './mollie-payments/plugins/apple-pay-payment-method.plugin'; import MollieCreditCardMandateManage from './mollie-payments/plugins/creditcard-mandate-manage.plugin'; import MolliePosTerminalPlugin from './mollie-payments/plugins/pos-terminal.plugin'; +import PayPalExpressPlugin from './mollie-payments/plugins/paypal-express.plugin'; import MollieBancomatPlugin from './mollie-payments/plugins/bancomat-plugin'; +import {MollieExpressActions} from './mollie-payments/plugins/mollie-express-actions.plugin'; -export default class MolliRegistration { +export default class MollieRegistration { /** * @@ -19,9 +21,13 @@ export default class MolliRegistration { // global plugins // ----------------------------------------------------------------------------- // hide apple pay direct buttons across the whole shop, if not available + pluginManager.register('MollieExpressActions', MollieExpressActions); pluginManager.register('MollieApplePayDirect', MollieApplePayDirect); + // fix quantity select on PDP Page + pluginManager.register('PayPalExpressPlugin',PayPalExpressPlugin); + // hiding the standard Apple Pay method in the checkout and account area // ----------------------------------------------------------------------------- pluginManager.register('MollieApplePayPaymentMethod', MollieApplePayPaymentMethod, '[data-mollie-template-applepay-account]'); diff --git a/src/Resources/app/storefront/src/scss/base.scss b/src/Resources/app/storefront/src/scss/base.scss index ffe1075c6..a01e8887a 100644 --- a/src/Resources/app/storefront/src/scss/base.scss +++ b/src/Resources/app/storefront/src/scss/base.scss @@ -4,4 +4,5 @@ @import "./account/payment-selection"; @import "./account/subscriptions"; @import "./component/credit-card-mandate"; +@import "./component/paypal-express-button"; @import "./display"; diff --git a/src/Resources/app/storefront/src/scss/component/paypal-express-button.scss b/src/Resources/app/storefront/src/scss/component/paypal-express-button.scss new file mode 100644 index 000000000..c741377ea --- /dev/null +++ b/src/Resources/app/storefront/src/scss/component/paypal-express-button.scss @@ -0,0 +1,73 @@ +.mollie-paypal-button { + display: flex; + align-items: center; + justify-content: center; + + font-size: 16px; + font-weight: bold; + text-decoration: none; + text-align: center; + cursor: pointer; + transition: background-color 0.3s ease; + width: 100%; + padding: 7px; +} + +.paypal-button-pill { + border-radius: 30px; +} + +.paypal-button-rect { + border-radius: 0; +} + +.paypal-theme-gold { + color: #000000; + background-color: #FFC439FF; + border: 1px solid #fcc85b; +} + +.paypal-theme-gold:hover { + background-color: #fcc85b; +} + +.paypal-theme-blue { + color: #ffffff; + background-color: #009CDEFF; + border: 1px solid #09adf3; +} + +.paypal-theme-blue:hover { + background-color: #09adf3; +} + +.paypal-theme-silver { + color: #000000; + background-color: #EEEEEEFF; + border: 1px solid #f6f6f6; +} + +.paypal-theme-silver:hover { + background-color: #f6f6f6; +} + +.paypal-theme-white { + color: #000000; + background-color: #ffffff; + border: 1px solid #f3f3f3; +} + +.paypal-theme-white:hover { + background-color: #f3f3f3; +} + +.paypal-theme-black { + color: #ffffff; + background-color: #000000; + border: 1px solid #555555; +} + +.paypal-theme-black:hover { + background-color: #333333; +} + diff --git a/src/Resources/app/storefront/src/scss/display.scss b/src/Resources/app/storefront/src/scss/display.scss index 390a283e2..e7a7a056d 100644 --- a/src/Resources/app/storefront/src/scss/display.scss +++ b/src/Resources/app/storefront/src/scss/display.scss @@ -1,4 +1,5 @@ /* stylelint-disable selector-id-pattern, declaration-no-important */ +.mollie-express-button.d-none, .js-apple-pay .d-none, .js-apple-pay-container .d-none, .mollie-pos-terminals .d-none, @@ -7,5 +8,7 @@ #mollieCreditCardMandateDeleteSuccess .d-none, .mollie-ideal-issuer .d-none { display: none !important; + margin: 0 !important; + padding: 0 !important; } /* stylelint-enable selector-id-pattern, declaration-no-important */ diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index 5a18e5823..98f9f9c39 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -77,15 +77,61 @@ Aktiviert das Standardverhalten von Shopware für fehlerhafte Zahlungen. Wenn nicht aktiv, kümmert sich das Mollie Plugin um einen erneuten Versuch der Zahlung und leitet den Käufer auf die externe Mollie Zahlungsauswahl. Gebruik de standaard Shopware instelling voor mislukte betalingen. Indien uitgeschakeld, zal Mollie automatisch een manier aanbieden om de betaling opnieuw te proberen door de user om te leiden naar de Mollie betalingspagina. + + createCustomersAtMollie + + + + false + Automatically have customers being created inside your Mollie Dashboard to see all payments of a specific customer. + Erstellt automatisch Kunden im Mollie Dashboard. Dadurch hat man einen zusätzlichen Überblick über alle Zahlungen dieses Kunden innerhalb von Mollie. + Automatisch klanten laten aanmaken in het Mollie Dashboard om alle betalingen van een specifieke klant te zien. + + + useMolliePaymentMethodLimits + false + + + + Automatically hides payment methods in the checkout based on the availability rules for payment methods. Only active payment methods from your mollie dashboard will be shown. If the payment method has a cart limit, currency restriction or billing address restriction, it will be hidden during checkout. + Blendet automatisch Zahlungsart im Checkout basierend auf Verfügbarkeitsregeln von Mollie. Es werden nur die aktiven Zahlungsarten aus dem Mollie Dashboard angezeigt. Wenn die Zahlungsart eine Einschränkung auf den Warenkorbwert, Währung oder Rechnungsadresse hat, wird diese auch ausgeblendet. + Automatische betalingsmethode wordt verborgen tijdens het afrekenen op basis van beschikbaarheidsregels van Mollie. Alleen actieve betalingsmethoden uit het Mollie-dashboard worden weergegeven. Als de betalingsmethode beperkingen heeft op de winkelwagenwaarde, valuta of factuuradres, wordt deze ook verborgen. + + + formatOrderNumber + + + + If set, this format will be used before the order number in the Mollie dashboard. This will also be passed on to PayPal as invoice number. + Wenn gesetzt, wird dieses Format vor der Bestellnummer im Mollie-Dashboard verwendet. Dieser Wert wird auch an PayPal als Rechnungsnummber übergeben + Indien ingesteld, wordt dit formaat vóór het bestelnummer in het Mollie-dashboard gebruikt. Dit wordt ook als factuurnummer doorgegeven aan PayPal. + + + molliePluginConfigSectionPaymentsFormat + + + molliePluginConfigSectionPayments + + + + Credit card + Kreditkarte + Credit card enableCreditCardComponents true - Show credit card input fields directly within your Shopware shop. If disabled, Mollie will show the forms on the Mollie payment page. - Zeigt Kreditkartenfelder direkt im Shopware shop an. Wenn nicht aktiviert, dann zeigt Mollie diese Felder direkt auf der externen Mollie Zahlungsseite. - Toon credit card invoervelden direct in jouw Shopware shop. Indien uitgeschakeld, zal Mollie de formulieren tonen op de Mollie betaalpagina. + Show credit card input fields directly within your Shopware shop. If disabled, Mollie will show + the forms on the Mollie payment page. + + Zeigt Kreditkartenfelder direkt im Shopware shop an. Wenn nicht aktiviert, dann zeigt + Mollie diese Felder direkt auf der externen Mollie Zahlungsseite. + + Toon credit card invoervelden direct in jouw Shopware shop. Indien uitgeschakeld, zal + Mollie de formulieren tonen op de Mollie betaalpagina. + oneClickPaymentsEnabled @@ -93,9 +139,15 @@ false - Customers can choose to save their credit card data for repeating orders. Sensitive data will only be stored on the Mollie servers and not in Shopware. - Kunden haben die Möglichkeit, ihre Kreditkartendaten für erneute Bestellungen zu speichern. Sensible Daten werden nur auf den Mollie-Servern und nicht in Shopware gespeichert. - Klanten kunnen ervoor kiezen hun creditcardgegevens op te slaan voor toekomstige bestellingen. Gevoelige gegevens worden alleen opgeslagen op de servers van Mollie en niet in Shopware. + Customers can choose to save their credit card data for repeating orders. Sensitive data will only + be stored on the Mollie servers and not in Shopware. + + Kunden haben die Möglichkeit, ihre Kreditkartendaten für erneute Bestellungen zu + speichern. Sensible Daten werden nur auf den Mollie-Servern und nicht in Shopware gespeichert. + + Klanten kunnen ervoor kiezen hun creditcardgegevens op te slaan voor toekomstige + bestellingen. Gevoelige gegevens worden alleen opgeslagen op de servers van Mollie en niet in Shopware. + oneClickPaymentsCompactView @@ -103,10 +155,122 @@ false - If enabled, the stored credit cards will not be displayed as visual cards on the checkout page. - Wenn aktiviert, werden die gespeicherten Kreditkarten nicht als visuelle Karten auf der Checkout-Seite angezeigt. - Indien ingeschakeld, worden de opgeslagen creditcards niet weergegeven als visuele kaarten op de afrekenpagina. + If enabled, the stored credit cards will not be displayed as visual cards on the checkout page. + + Wenn aktiviert, werden die gespeicherten Kreditkarten nicht als visuelle Karten auf + der Checkout-Seite angezeigt. + + Indien ingeschakeld, worden de opgeslagen creditcards niet weergegeven als visuele + kaarten op de afrekenpagina. + + + + + + Apple Pay + Apple Pay + Apple Pay enableApplePayDirect @@ -173,43 +337,9 @@ en een alternatieve domein voor uw Apple Pay Direct integratie wilt opgeven. - - - createCustomersAtMollie - - - - false - Automatically have customers being created inside your Mollie Dashboard to see all payments of a specific customer. - Erstellt automatisch Kunden im Mollie Dashboard. Dadurch hat man einen zusätzlichen Überblick über alle Zahlungen dieses Kunden innerhalb von Mollie. - Automatisch klanten laten aanmaken in het Mollie Dashboard om alle betalingen van een specifieke klant te zien. - - - useMolliePaymentMethodLimits - false - - - - Automatically hides payment methods in the checkout based on the availability rules for payment methods. Only active payment methods from your mollie dashboard will be shown. If the payment method has a cart limit, currency restriction or billing address restriction, it will be hidden during checkout. - Blendet automatisch Zahlungsart im Checkout basierend auf Verfügbarkeitsregeln von Mollie. Es werden nur die aktiven Zahlungsarten aus dem Mollie Dashboard angezeigt. Wenn die Zahlungsart eine Einschränkung auf den Warenkorbwert, Währung oder Rechnungsadresse hat, wird diese auch ausgeblendet. - Automatische betalingsmethode wordt verborgen tijdens het afrekenen op basis van beschikbaarheidsregels van Mollie. Alleen actieve betalingsmethoden uit het Mollie-dashboard worden weergegeven. Als de betalingsmethode beperkingen heeft op de winkelwagenwaarde, valuta of factuuradres, wordt deze ook verborgen. - - - formatOrderNumber - - - - If set, this format will be used before the order number in the Mollie dashboard. This will also be passed on to PayPal as invoice number. - Wenn gesetzt, wird dieses Format vor der Bestellnummer im Mollie-Dashboard verwendet. Dieser Wert wird auch an PayPal als Rechnungsnummber übergeben - Indien ingesteld, wordt dit formaat vóór het bestelnummer in het Mollie-dashboard gebruikt. Dit wordt ook als factuurnummer doorgegeven aan PayPal. - - - molliePluginConfigSectionPaymentsFormat - - - molliePluginConfigSectionPayments - + + Order Management Auftragsverwaltung diff --git a/src/Resources/config/routes/storefront/paypal_express.xml b/src/Resources/config/routes/storefront/paypal_express.xml new file mode 100644 index 000000000..415e18085 --- /dev/null +++ b/src/Resources/config/routes/storefront/paypal_express.xml @@ -0,0 +1,21 @@ + + + + + Kiener\MolliePayments\Controller\Storefront\PaypalExpress\PaypalExpressControllerBase::startCheckout + storefront + true + + + + + Kiener\MolliePayments\Controller\Storefront\PaypalExpress\PaypalExpressControllerBase::finishCheckout + storefront + true + + + + diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 6c27fb0e3..4ad3856d2 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -69,6 +69,7 @@ + @@ -101,15 +102,25 @@ + + + %env(default::MOLLIE_PAYPAL_EXPRESS_BETA)% + %env(default::MOLLIE_PAYPAL_EXPRESS_BUTTON_STYLE)% + %env(default::MOLLIE_PAYPAL_EXPRESS_BUTTON_SHAPE)% + %env(default::MOLLIE_PAYPAL_EXPRESS_BUTTON_RESTRICTIONS)% + + + + %env(default::MOLLIE_SHOP_DOMAIN)% %env(default::MOLLIE_DEV_MODE)% %env(default::MOLLIE_CYPRESS_MODE)% @@ -224,6 +235,10 @@ %kernel.shopware_version% + + + + diff --git a/src/Resources/config/services/components.xml b/src/Resources/config/services/components.xml index 568cd9a1b..fc5db6262 100644 --- a/src/Resources/config/services/components.xml +++ b/src/Resources/config/services/components.xml @@ -44,6 +44,15 @@ + + + + + + + + + diff --git a/src/Resources/config/services/controller.xml b/src/Resources/config/services/controller.xml index 5d6123c75..f305c0d04 100644 --- a/src/Resources/config/services/controller.xml +++ b/src/Resources/config/services/controller.xml @@ -216,6 +216,19 @@ + + + + + + + + + + + + + diff --git a/src/Resources/config/services/handlers.xml b/src/Resources/config/services/handlers.xml index ca9cd19a4..fbf911565 100644 --- a/src/Resources/config/services/handlers.xml +++ b/src/Resources/config/services/handlers.xml @@ -128,6 +128,13 @@ + + + + + + + diff --git a/src/Resources/config/services/payment.xml b/src/Resources/config/services/payment.xml index 7233d5e38..24c841615 100644 --- a/src/Resources/config/services/payment.xml +++ b/src/Resources/config/services/payment.xml @@ -56,5 +56,16 @@ + + + + + + + + + + + diff --git a/src/Resources/config/services/repositories.xml b/src/Resources/config/services/repositories.xml index 0edfc3c9a..3ec784071 100644 --- a/src/Resources/config/services/repositories.xml +++ b/src/Resources/config/services/repositories.xml @@ -35,6 +35,11 @@ + + + + + diff --git a/src/Resources/config/services/subscriber.xml b/src/Resources/config/services/subscriber.xml index 641937bc9..60475b33c 100644 --- a/src/Resources/config/services/subscriber.xml +++ b/src/Resources/config/services/subscriber.xml @@ -53,6 +53,12 @@ + + + + + + diff --git a/src/Resources/public/static/ppe-black.png b/src/Resources/public/static/ppe-black.png new file mode 100644 index 000000000..9297e8922 Binary files /dev/null and b/src/Resources/public/static/ppe-black.png differ diff --git a/src/Resources/public/static/ppe-primary.png b/src/Resources/public/static/ppe-primary.png new file mode 100644 index 000000000..780c84f6c Binary files /dev/null and b/src/Resources/public/static/ppe-primary.png differ diff --git a/src/Resources/public/static/ppe-white.png b/src/Resources/public/static/ppe-white.png new file mode 100644 index 000000000..62e847fe0 Binary files /dev/null and b/src/Resources/public/static/ppe-white.png differ diff --git a/src/Resources/views/mollie/component/apple-pay-direct-button.twig b/src/Resources/views/mollie/component/apple-pay-direct-button.twig index 8b54cdaa7..e0e0f4e3f 100644 --- a/src/Resources/views/mollie/component/apple-pay-direct-button.twig +++ b/src/Resources/views/mollie/component/apple-pay-direct-button.twig @@ -33,12 +33,7 @@ {% block mollie_apple_pay_direct_button %} - - {% if mollie_express_required_data_protection %} - {% sw_include '@Storefront/storefront/component/privacy-notice.html.twig' %} - {% endif %} - - + +{% endblock %} \ No newline at end of file diff --git a/src/Resources/views/mollie/head.html.twig b/src/Resources/views/mollie/head.html.twig index 28d382a6c..1f9ecd873 100644 --- a/src/Resources/views/mollie/head.html.twig +++ b/src/Resources/views/mollie/head.html.twig @@ -6,7 +6,8 @@ 'frontend.checkout.cart.page' : 'cart', 'frontend.navigation.page' : 'plp', 'frontend.account.edit-order.page' : '', - 'frontend.detail.page' : 'pdp' + 'frontend.detail.page' : 'pdp', + 'frontend.checkout.register.page' : 'register' } %} {% set currentRoute = app.request.attributes.get('_route') %} @@ -17,12 +18,17 @@ {% set includeJsInHeader = true %} {% endif %} + {% set restrictions = mollie_applepaydirect_restrictions | merge(mollie_paypalexpress_restrictions) %} + {# requirement check for apple pay direct #} - {% if mollie_applepaydirect_enabled == true or mollie_applepay_enabled == true %} - {% if currentRoute in onlyShowHere|keys and onlyShowHere[currentRoute] not in mollie_applepaydirect_restrictions %} + {% if mollie_applepaydirect_enabled == true or mollie_applepay_enabled == true or mollie_paypalexpress_enabled == true %} + + {% if currentRoute in onlyShowHere|keys and onlyShowHere[currentRoute] not in restrictions %} {% set includeJsInHeader = true %} {% endif %} - {% if 'offcanvas' not in mollie_applepaydirect_restrictions %} + + + {% if 'offcanvas' not in mollie_applepaydirect_restrictions or 'offcanvas' not in mollie_paypalexpress_restrictions %} {% set includeJsInHeader = true %} {% endif %} {% endif %} diff --git a/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig b/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig index 9b66aa5a1..fcb32f45e 100644 --- a/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig +++ b/src/Resources/views/storefront/component/buy-widget/buy-widget-form.html.twig @@ -1,10 +1,12 @@ {% sw_extends '@Storefront/storefront/component/buy-widget/buy-widget-form.html.twig' %} -{% block buy_widget_buy_button %} +{% block buy_widget_buy_container %} {% if mollie_subscriptions_enabled and product.translated.customFields.mollie_payments_product_subscription_enabled %}
-
@@ -16,31 +18,51 @@ {% block buy_widget_buy_form_inner %} {{ parent() }} - {% block page_product_detail_buy_container_apple_direct %} - {# this is for Shopware < 6.4 #} - {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} - {# this is for Shopware >= 6.4 #} - {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} - - {% set productPrice = 0 %} - - {% if product.calculatedPrices|length == 1 %} - {% set productPrice = product.calculatedPrices.first.unitPrice %} - {% else %} - {% set productPrice = product.calculatedPrice.unitPrice %} - {% if listPrice.percentage > 0 %} - {% set productPrice = listPrice.price %} - {% endif %} + + + {# this is for Shopware < 6.4 #} + {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} + {# this is for Shopware >= 6.4 #} + {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} + + {% set productPrice = 0 %} + + {% if product.calculatedPrices|length == 1 %} + {% set productPrice = product.calculatedPrices.first.unitPrice %} + {% else %} + {% set productPrice = product.calculatedPrice.unitPrice %} + {% if listPrice.percentage > 0 %} + {% set productPrice = listPrice.price %} {% endif %} + {% endif %} - {% if mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} - {% block page_product_detail_buy_container_apple_direct_component %} -
+ {% set applePayVisible = mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('pdp' not in mollie_paypalexpress_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} + + + + {% block page_product_detail_buy_container_apple_direct %} + + {% if applePayVisible %} + {% block page_product_detail_buy_container_apple_direct_component %} +
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' with {cols: 'col-8'} %}
+ {% endblock %} + {% endif %} + {% endblock %} + {% block page_product_detail_buy_container_paypal_express %} + {% if paypalExpressVisible %} + {% block page_product_detail_buy_container_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' with {cols: 'col-8'} %} +
{% endblock %} {% endif %} {% endblock %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig b/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig index 61882e92b..26d87a73d 100644 --- a/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig +++ b/src/Resources/views/storefront/component/checkout/offcanvas-cart.html.twig @@ -4,13 +4,30 @@ {% block component_offcanvas_cart_actions_checkout %} {{ parent() }} {% if page.cart.lineItems|length > 0 %} - {% if mollie_applepaydirect_enabled and ('offcanvas' not in mollie_applepaydirect_restrictions) %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('offcanvas' not in mollie_applepaydirect_restrictions) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('offcanvas' not in mollie_paypalexpress_restrictions) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} + + + {% if applePayVisible %} {% block component_offcanvas_cart_actions_checkout_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' %}
{% endblock %} {% endif %} + + {% if paypalExpressVisible %} + {% block component_offcanvas_cart_actions_checkout_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' %} +
+ {% endblock %} + {% endif %} + {% endif %} {% endblock %} diff --git a/src/Resources/views/storefront/component/product/card/action.html.twig b/src/Resources/views/storefront/component/product/card/action.html.twig index 4605626e4..c31334fd7 100644 --- a/src/Resources/views/storefront/component/product/card/action.html.twig +++ b/src/Resources/views/storefront/component/product/card/action.html.twig @@ -5,33 +5,47 @@ {{ parent() }} - {% block component_product_box_action_buy_apple_direct %} - - {% set productPrice = 0 %} + {% set productPrice = 0 %} - {% if product.calculatedPrices|length == 1 %} - {% set productPrice = product.calculatedPrices.first.unitPrice %} - {% else %} - {% set productPrice = product.calculatedPrice.unitPrice %} - {% if listPrice.percentage > 0 %} - {% set productPrice = listPrice.price %} - {% endif %} + {% if product.calculatedPrices|length == 1 %} + {% set productPrice = product.calculatedPrices.first.unitPrice %} + {% else %} + {% set productPrice = product.calculatedPrice.unitPrice %} + {% if listPrice.percentage > 0 %} + {% set productPrice = listPrice.price %} {% endif %} + {% endif %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('plp' not in mollie_applepaydirect_restrictions) and productPrice > 0 %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('plp' not in mollie_paypalexpress_restrictions) and productPrice > 0 %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} - {% if mollie_applepaydirect_enabled and ('plp' not in mollie_applepaydirect_restrictions) and productPrice > 0 %} + {% block component_product_box_action_buy_apple_direct %} + + {% if applePayVisible %} {% block component_product_box_action_buy_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' %}
{% endblock %} {% endif %} + {% endblock %} + {% block component_product_box_action_buy_paypal_express %} + {% if paypalExpressVisible %} + {% block component_product_box_action_buy_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' %} +
+ {% endblock %} + {% endif %} {% endblock %} {% endblock %} - {% block page_product_detail_product_buy_button %} diff --git a/src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig b/src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig new file mode 100644 index 000000000..80142ac9f --- /dev/null +++ b/src/Resources/views/storefront/csrf/components/paypal-express-button-csrf.twig @@ -0,0 +1 @@ +{{ sw_csrf('frontend.mollie.paypal-express.start') }} diff --git a/src/Resources/views/storefront/page/checkout/address/index.html.twig b/src/Resources/views/storefront/page/checkout/address/index.html.twig new file mode 100644 index 000000000..3ab87e2ec --- /dev/null +++ b/src/Resources/views/storefront/page/checkout/address/index.html.twig @@ -0,0 +1,20 @@ +{% sw_extends '@Storefront/storefront/page/checkout/address/index.html.twig' %} + +{% block page_checkout_address_login_toggle %} + + {{ parent() }} + + + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('register' not in mollie_paypalexpress_restrictions) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:paypalExpressVisible} %} + + {% if paypalExpressVisible %} + {% block page_checkout_address_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' with {cols: 'col-5'} %} +
+ {% endblock %} + {% endif %} +{% endblock %} 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 536dffa58..35883d56e 100644 --- a/src/Resources/views/storefront/page/checkout/cart/index.html.twig +++ b/src/Resources/views/storefront/page/checkout/cart/index.html.twig @@ -21,12 +21,28 @@ {% block page_checkout_aside_actions %} {{ parent() }} - {% if mollie_applepaydirect_enabled and ('cart' not in mollie_applepaydirect_restrictions) %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('cart' not in mollie_applepaydirect_restrictions) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('cart' not in mollie_paypalexpress_restrictions) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} + + + {% if applePayVisible %} {% block page_checkout_aside_actions_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' %}
{% endblock %} {% endif %} + + {% if paypalExpressVisible %} + {% block page_checkout_aside_actions_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' %} +
+ {% endblock %} + {% endif %} {% endblock %} diff --git a/src/Resources/views/storefront/page/checkout/confirm/index.html.twig b/src/Resources/views/storefront/page/checkout/confirm/index.html.twig index 556efe76f..e5bbc8775 100644 --- a/src/Resources/views/storefront/page/checkout/confirm/index.html.twig +++ b/src/Resources/views/storefront/page/checkout/confirm/index.html.twig @@ -3,13 +3,15 @@ {% block page_checkout_main_content %} {% if mollie_applepay_enabled %} -
+
{% endif %} + {% if page.enable_credit_card_components == true %} {% endif %} diff --git a/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig b/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig index 14dc36b18..5993c5c5f 100755 --- a/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig +++ b/src/Resources/views/storefront/page/product-detail/buy-widget-form.html.twig @@ -2,34 +2,51 @@ {% block page_product_detail_buy_form_inner %} {{ parent() }} - {% block page_product_detail_buy_container_apple_direct %} - {# this is for Shopware < 6.4 #} - {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} - {# this is for Shopware >= 6.4 #} - {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} - {% set productPrice = 0 %} + {# this is for Shopware < 6.4 #} + {% set buyableLegacy = (not page.product.isCloseout or (page.product.availableStock >= page.product.minPurchase)) and page.product.childCount <= 0 %} + {# this is for Shopware >= 6.4 #} + {% set buyable = product.available and product.childCount <= 0 and product.calculatedMaxPurchase > 0 %} + + {% set productPrice = 0 %} - {% if product.calculatedPrices|length == 1 %} - {% set productPrice = product.calculatedPrices.first.unitPrice %} - {% else %} - {% set productPrice = product.calculatedPrice.unitPrice %} + {% if product.calculatedPrices|length == 1 %} + {% set productPrice = product.calculatedPrices.first.unitPrice %} + {% else %} + {% set productPrice = product.calculatedPrice.unitPrice %} - {% if listPrice.percentage > 0 %} - {% set productPrice = listPrice.price %} - {% endif %} + {% if listPrice.percentage > 0 %} + {% set productPrice = listPrice.price %} {% endif %} + {% endif %} + + {% set applePayVisible = mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set paypalExpressVisible = mollie_paypalexpress_enabled and ('pdp' not in mollie_paypalexpress_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% set noticeVisible = applePayVisible or paypalExpressVisible %} + + {% sw_include '@MolliePayments/mollie/component/express-privacy-notice.html.twig' with {visible:noticeVisible} %} - {% if mollie_applepaydirect_enabled and ('pdp' not in mollie_applepaydirect_restrictions) and ((buyableLegacy) or (buyable and productPrice) > 0) %} + {% block page_product_detail_buy_container_apple_direct %} + {% if applePayVisible %} {% block page_product_detail_buy_container_apple_direct_component %} -
+
{% include '@MolliePayments/mollie/component/apple-pay-direct-button.twig' with {cols: 'col-8'} %}
{% endblock %} {% endif %} {% endblock %} + {% block page_product_detail_buy_container_paypal_express %} + {% if paypalExpressVisible %} + {% block page_product_detail_buy_container_mollie_paypal_express_component %} +
+ {% include '@MolliePayments/mollie/component/paypal-express-button.twig' with {cols: 'col-8'} %} +
+ {% endblock %} + {% endif %} + {% endblock %} + {% endblock %} @@ -38,7 +55,9 @@ {% block page_product_detail_buy_button %} {% if mollie_subscriptions_enabled and page.product.translated.customFields.mollie_payments_product_subscription_enabled %} - {% else %} diff --git a/src/Service/Cart/CartBackupService.php b/src/Service/Cart/CartBackupService.php index 282bf4294..4fd04951d 100644 --- a/src/Service/Cart/CartBackupService.php +++ b/src/Service/Cart/CartBackupService.php @@ -12,7 +12,7 @@ class CartBackupService /** * */ - private const BACKUP_TOKEN = 'mollie_backup'; + private const BACKUP_TOKEN = 'mollie_backup_%s'; /** * @var CartService @@ -35,11 +35,14 @@ public function __construct(CartService $cartService) */ public function isBackupExisting(SalesChannelContext $context): bool { - $backupCart = $this->cartService->getCart(self::BACKUP_TOKEN, $context); - + $backupCart = $this->cartService->getCart($this->getToken($context), $context); return ($backupCart->getLineItems()->count() > 0); } + private function getToken(SalesChannelContext $context):string + { + return sprintf(self::BACKUP_TOKEN, $context->getToken()); + } /** * @param SalesChannelContext $context */ @@ -47,11 +50,8 @@ public function backupCart(SalesChannelContext $context): void { $originalCart = $this->cartService->getCart($context->getToken(), $context); - # additional language shops do not have a name, so make sure it has a string cast - $salesChannelName = (string)$context->getSalesChannel()->getName(); - # create new cart with our backup token - $newCart = $this->cartService->createNew(self::BACKUP_TOKEN); + $newCart = $this->cartService->createNew($this->getToken($context)); # assign our items to the backup # this is the only thing we really need to backup at this stage. @@ -89,7 +89,7 @@ public function restoreCart(SalesChannelContext $context): Cart */ public function clearBackup(SalesChannelContext $context): void { - $backupCart = $this->cartService->getCart(self::BACKUP_TOKEN, $context); + $backupCart = $this->cartService->getCart($this->getToken($context), $context); # removing does not really work # but we can set the item count to 0, which means "not existing" for us diff --git a/src/Service/Cart/Voucher/VoucherCartCollector.php b/src/Service/Cart/Voucher/VoucherCartCollector.php index af4ea6222..5a0f0b35a 100644 --- a/src/Service/Cart/Voucher/VoucherCartCollector.php +++ b/src/Service/Cart/Voucher/VoucherCartCollector.php @@ -4,6 +4,7 @@ use Kiener\MolliePayments\Handler\Method\VoucherPayment; use Kiener\MolliePayments\Repository\PaymentMethod\PaymentMethodRepositoryInterface; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Struct\LineItem\LineItemAttributes; use Kiener\MolliePayments\Struct\Voucher\VoucherType; use Shopware\Core\Checkout\Cart\Cart; @@ -88,7 +89,7 @@ public function collect(CartDataCollection $data, Cart $original, SalesChannelCo $attributes->setVoucherType($voucherType); $customFields = $item->getPayload()['customFields']; - $customFields['mollie_payments'] = $attributes->toArray(); + $customFields[CustomFieldsInterface::MOLLIE_KEY] = $attributes->toArray(); $item->setPayloadValue('customFields', $customFields); } diff --git a/src/Service/CartService.php b/src/Service/CartService.php index 853da875a..32e76d5c0 100644 --- a/src/Service/CartService.php +++ b/src/Service/CartService.php @@ -96,6 +96,7 @@ public function updateCart(Cart $cart): void $this->swCartService->setCart($cart); } + /** * @param Cart $cart * @return float @@ -186,4 +187,9 @@ public function clearFakeAddressIfExists(SalesChannelContext $context): void $this->shippingAddressFaker->deleteFakeShippingAddress($customer, $context->getContext()); } + + public function persistCart(Cart $cart, SalesChannelContext $context):Cart + { + return $this->swCartService->recalculate($cart, $context); + } } diff --git a/src/Service/CartServiceInterface.php b/src/Service/CartServiceInterface.php index 2c49d0844..a83e42373 100644 --- a/src/Service/CartServiceInterface.php +++ b/src/Service/CartServiceInterface.php @@ -52,4 +52,11 @@ public function updateShippingMethod(SalesChannelContext $context, string $shipp * @return SalesChannelContext */ public function updatePaymentMethod(SalesChannelContext $context, string $paymentMethodID): SalesChannelContext; + + /** + * @param Cart $cart + * @param SalesChannelContext $context + * @return Cart + */ + public function persistCart(Cart $cart, SalesChannelContext $context):Cart; } diff --git a/src/Service/CustomFieldsInterface.php b/src/Service/CustomFieldsInterface.php index dc5126ced..a068deecb 100644 --- a/src/Service/CustomFieldsInterface.php +++ b/src/Service/CustomFieldsInterface.php @@ -23,4 +23,14 @@ interface CustomFieldsInterface * */ public const DELIVERY_SHIPPED = 'is_shipped'; + + public const PAYMENT_KEY = 'payment_id'; + + public const THIRD_PARTY_PAYMENT_KEY = 'third_party_payment_id'; + + public const PAYPAL_EXPRESS_SESSION_ID_KEY = 'mollie_ppe_session_id'; + + public const PAYPAL_EXPRESS_AUTHENTICATE_ID = 'mollie_ppe_auth_id'; + + public const ACCEPTED_DATA_PROTECTION = 'acceptedDataProtection'; } diff --git a/src/Service/CustomerService.php b/src/Service/CustomerService.php index 1d954b371..313e3b353 100644 --- a/src/Service/CustomerService.php +++ b/src/Service/CustomerService.php @@ -8,11 +8,14 @@ use Kiener\MolliePayments\Exception\CustomerCouldNotBeFoundException; use Kiener\MolliePayments\Repository\Country\CountryRepositoryInterface; use Kiener\MolliePayments\Repository\Customer\CustomerRepositoryInterface; +use Kiener\MolliePayments\Repository\CustomerAddress\CustomerAddressRepositoryInterface; use Kiener\MolliePayments\Repository\Salutation\SalutationRepositoryInterface; use Kiener\MolliePayments\Service\MollieApi\Customer; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Kiener\MolliePayments\Struct\CustomerStruct; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressDefinition; use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity; use Shopware\Core\Checkout\Customer\CustomerEntity; use Shopware\Core\Checkout\Customer\Event\CustomerBeforeLoginEvent; @@ -22,8 +25,11 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; +use Shopware\Core\Framework\Uuid\Uuid; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException; use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister; @@ -38,6 +44,7 @@ class CustomerService implements CustomerServiceInterface public const CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL = 'shouldSaveCardDetail'; public const CUSTOM_FIELDS_KEY_PREFERRED_IDEAL_ISSUER = 'preferred_ideal_issuer'; public const CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL = 'preferred_pos_terminal'; + public const CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID = 'ppe_address_id'; /** * @var CountryRepositoryInterface @@ -82,6 +89,9 @@ class CustomerService implements CustomerServiceInterface */ private $container; + /** @var CustomerAddressRepositoryInterface */ + private $customerAddressRepository; + /** * @param CountryRepositoryInterface $countryRepository @@ -96,17 +106,18 @@ class CustomerService implements CustomerServiceInterface * @param ConfigService $configService */ public function __construct( - CountryRepositoryInterface $countryRepository, - CustomerRepositoryInterface $customerRepository, - Customer $customerApiService, - EventDispatcherInterface $eventDispatcher, - LoggerInterface $logger, - SalesChannelContextPersister $salesChannelContextPersister, - SalutationRepositoryInterface $salutationRepository, - SettingsService $settingsService, - string $shopwareVersion, - ConfigService $configService, - ContainerInterface $container //we have to inject the container, because in SW 6.4.20.2 we have circular injection for the register route + CountryRepositoryInterface $countryRepository, + CustomerRepositoryInterface $customerRepository, + CustomerAddressRepositoryInterface $customerAddressRepository, + Customer $customerApiService, + EventDispatcherInterface $eventDispatcher, + LoggerInterface $logger, + SalesChannelContextPersister $salesChannelContextPersister, + SalutationRepositoryInterface $salutationRepository, + SettingsService $settingsService, + string $shopwareVersion, + ConfigService $configService, + ContainerInterface $container //we have to inject the container, because in SW 6.4.20.2 we have circular injection for the register route ) { $this->countryRepository = $countryRepository; $this->customerRepository = $customerRepository; @@ -118,6 +129,7 @@ public function __construct( $this->settingsService = $settingsService; $this->shopwareVersion = $shopwareVersion; $this->configService = $configService; + $this->customerAddressRepository = $customerAddressRepository; $this->container = $container; } @@ -129,7 +141,7 @@ public function __construct( * * @return null|string */ - public function customerLogin(CustomerEntity $customer, SalesChannelContext $context): ?string + public function loginCustomer(CustomerEntity $customer, SalesChannelContext $context): ?string { /** @var null|string $newToken */ $newToken = null; @@ -217,9 +229,9 @@ public function setCardToken(CustomerEntity $customer, string $cardToken, SalesC } // Store the card token in the custom fields - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_CREDIT_CARD_TOKEN] = $cardToken; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_CREDIT_CARD_TOKEN] = $cardToken; // Store shouldSaveCardDetail in the custom fields - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] = $shouldSaveCardDetail; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_SHOULD_SAVE_CARD_DETAIL] = $shouldSaveCardDetail; $this->logger->debug("Setting Credit Card Token", [ 'customerId' => $customer->getId(), @@ -248,7 +260,7 @@ public function setMandateId(CustomerEntity $customer, string $mandateId, Contex $customFields = $customer->getCustomFields() ?? []; // Store the mandate id in the custom fields - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_MANDATE_ID] = $mandateId; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_MANDATE_ID] = $mandateId; $this->logger->debug("Setting Credit Card Mandate Id", [ 'customerId' => $customer->getId(), @@ -292,7 +304,7 @@ public function setPosTerminal(CustomerEntity $customer, string $terminalId, Con $customFields = []; } - $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS][self::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] = $terminalId; + $customFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_PREFERRED_POS_TERMINAL] = $terminalId; return $this->customerRepository->update([[ 'id' => $customer->getId(), @@ -346,8 +358,8 @@ public function getCustomer(string $customerId, Context $context): ?CustomerEnti $customer = null; try { - $criteria = new Criteria(); - $criteria->addFilter(new EqualsFilter('id', $customerId)); + $criteria = new Criteria([$customerId]); + $criteria->addAssociations([ 'defaultShippingAddress.country', 'defaultBillingAddress.country', @@ -385,7 +397,7 @@ public function getCustomerStruct(string $customerId, Context $context): Custome if (isset($customFields[self::CUSTOM_FIELDS_KEY_MOLLIE_CUSTOMER_ID])) { $struct->setLegacyCustomerId($customFields[self::CUSTOM_FIELDS_KEY_MOLLIE_CUSTOMER_ID]); } - $molliePaymentsCustomFields = $customFields[CustomFieldService::CUSTOM_FIELDS_KEY_MOLLIE_PAYMENTS] ?? []; + $molliePaymentsCustomFields = $customFields[CustomFieldsInterface::MOLLIE_KEY] ?? []; if (! is_array($molliePaymentsCustomFields)) { $this->logger->warning('Customer customFields for MolliePayments are invalid. Array is expected', [ 'currentCustomFields' => $molliePaymentsCustomFields @@ -436,23 +448,17 @@ public function getAddressArray($address, CustomerEntity $customer): array * @param SalesChannelContext $context * @return null|CustomerEntity */ - public function createApplePayDirectCustomerIfNotExists(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2, int $acceptedDataProtection, SalesChannelContext $context): ?CustomerEntity + public function createApplePayDirectCustomer(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2, SalesChannelContext $context): ?CustomerEntity { $countryId = $this->getCountryId($countryISO2, $context->getContext()); $salutationId = $this->getSalutationId($context->getContext()); - $customer = $this->findCustomerByEmail($email, $context); - if ($customer instanceof CustomerEntity) { - return $customer; - } - $data = new RequestDataBag(); $data->set('salutationId', $salutationId); $data->set('guest', true); $data->set('firstName', $firstname); $data->set('lastName', $lastname); $data->set('email', $email); - $data->set('acceptedDataProtection', $acceptedDataProtection); $billingAddress = new RequestDataBag(); $billingAddress->set('street', $street); @@ -471,9 +477,9 @@ public function createApplePayDirectCustomerIfNotExists(string $firstname, strin $errors = []; /** we have to store the errors in an array because getErrors returns a generator */ foreach ($e->getErrors() as $error) { - $errors[] = $error; + $errors[]=$error; } - $this->logger->error($e->getMessage(), ['errors' => $errors]); + $this->logger->error($e->getMessage(), ['errors'=>$errors]); return null; } } @@ -597,7 +603,8 @@ public function createMollieCustomer(string $customerId, string $salesChannelId, ); } - private function findCustomerByEmail(string $email, SalesChannelContext $context): ?CustomerEntity + + public function findCustomerByEmail(string $email, SalesChannelContext $context): ?CustomerEntity { $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('email', $email)); @@ -630,4 +637,202 @@ private function findCustomerByEmail(string $email, SalesChannelContext $context } return $foundCustomer; } + + public function reuseOrCreateAddresses(CustomerEntity $customer, AddressStruct $shippingAddress, Context $context, ?AddressStruct $billingAddress = null): EntityWrittenContainerEvent + { + $mollieAddressIds = [$shippingAddress->getMollieAddressId()]; + if ($billingAddress !== null) { + $mollieAddressIds[] = $billingAddress->getMollieAddressId(); + } + $criteria = new Criteria(); + $criteria->addFilter(new AndFilter([ + new EqualsFilter('customerId', $customer->getId()), + new EqualsAnyFilter('customFields.' . CustomFieldsInterface::MOLLIE_KEY . '.' . self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID, $mollieAddressIds) + ])); + + $customerAddressSearchResult = $this->customerAddressRepository->search($criteria, $context); + + // if we dont find any address for customer we create new once + if ($customerAddressSearchResult->getTotal() === 0) { + $shippingAddressId = Uuid::randomHex(); + $billingAddressId = $shippingAddressId; + + $addresses = [ + $this->createShopwareAddressArray($shippingAddressId, $customer->getId(), $customer->getSalutationId(), $shippingAddress, $context) + ]; + if ($billingAddress !== null) { + $billingAddressId = Uuid::randomHex(); + $addresses[] = $this->createShopwareAddressArray($billingAddressId, $customer->getId(), $customer->getSalutationId(), $billingAddress, $context); + } + + $customer = [ + 'id' => $customer->getId(), + 'defaultBillingAddressId' => $shippingAddressId, + 'defaultShippingAddressId' => $billingAddressId, + 'addresses' => $addresses + ]; + + return $this->customerRepository->upsert([$customer], $context); + } + + + $defaultShippingAddressId = null; + $defaultBillingAddressId = null; + + + /** @var CustomerAddressEntity $customerAddress */ + foreach ($customerAddressSearchResult->getElements() as $customerAddress) { + $addressCustomFields = $customerAddress->getCustomFields(); + + if ($addressCustomFields === null) { + continue; + } + + // skip addresses without custom fields, those are configured by the customer in backend + $mollieAddressId = $addressCustomFields[CustomFieldsInterface::MOLLIE_KEY][self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID] ?? null; + if ($mollieAddressId === null) { + continue; + } + // try to find default shipping and billing address and store them for later + if ($mollieAddressId === $shippingAddress->getMollieAddressId()) { + $defaultShippingAddressId = $customerAddress->getId(); + } + + if ($billingAddress !== null && $mollieAddressId === $billingAddress->getMollieAddressId()) { + $defaultBillingAddressId = $customerAddress->getId(); + } + } + + //customer have addresses, might be from old PPE orders, might be from shopware, lets find them and select them + $addresses = []; + + // we havent found a default adress, create a new one + if ($defaultShippingAddressId === null) { + $defaultShippingAddressId = Uuid::randomHex(); + $addresses[] = $this->createShopwareAddressArray($defaultShippingAddressId, $customer->getId(), $customer->getSalutationId(), $shippingAddress, $context); + } + + //we have a billing address but we didnt found them in saved addresses, create new one + if ($billingAddress !== null && $defaultBillingAddressId === null) { + $defaultBillingAddressId = Uuid::randomHex(); + $addresses[] = $this->createShopwareAddressArray($defaultBillingAddressId, $customer->getId(), $customer->getSalutationId(), $billingAddress, $context); + } + + //we dont have a billing adress, we use the shipping adress as billing + if ($billingAddress === null && $defaultBillingAddressId === null) { + $defaultBillingAddressId = $defaultShippingAddressId; + } + $customer = [ + 'id' => $customer->getId(), + 'defaultBillingAddressId' => $defaultBillingAddressId, + 'defaultShippingAddressId' => $defaultBillingAddressId, + ]; + + if (count($addresses) > 0) { + $customer['addresses'] = $addresses; + } + return $this->customerRepository->upsert([$customer], $context); + } + + public function createGuestAccount(AddressStruct $shippingAddress, string $paymentMethodId, SalesChannelContext $context, ?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): ?CustomerEntity + { + $countryId = $this->getCountryId($shippingAddress->getCountryCode(), $context->getContext()); + $salutationId = $this->getSalutationId($context->getContext()); + + $data = new RequestDataBag(); + $data->set('salutationId', $salutationId); + $data->set('guest', true); + $data->set('firstName', $shippingAddress->getFirstName()); + $data->set('lastName', $shippingAddress->getLastName()); + $data->set('email', $shippingAddress->getEmail()); + + $settings = $this->settingsService->getSettings($context->getSalesChannelId()); + if ($settings->isRequireDataProtectionCheckbox()) { + $data->set('acceptedDataProtection', (bool)$acceptedDataProtection); + } + + + + $shippingAddressData = new RequestDataBag(); + $shippingAddressData->set('firstName', $shippingAddress->getFirstName()); + $shippingAddressData->set('lastName', $shippingAddress->getLastName()); + $shippingAddressData->set('street', $shippingAddress->getStreet()); + $shippingAddressData->set('additionalAddressLine1', $shippingAddress->getStreetAdditional()); + $shippingAddressData->set('zipcode', $shippingAddress->getZipCode()); + $shippingAddressData->set('city', $shippingAddress->getCity()); + $shippingAddressData->set('countryId', $countryId); + $customFields = new RequestDataBag(); + $customFields->set(CustomerAddressDefinition::ENTITY_NAME, [ + CustomFieldsInterface::MOLLIE_KEY => [self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID => $shippingAddress->getMollieAddressId()] + ]); + $shippingAddressData->set('customFields', $customFields); + $data->set('shippingAddress', $shippingAddressData); + $data->set('billingAddress', $shippingAddressData); + + if ($billingAddress !== null) { + $countryId = $this->getCountryId($billingAddress->getCountryCode(), $context->getContext()); + + $billingAddressData = new RequestDataBag(); + $billingAddressData->set('street', $billingAddress->getStreet()); + $billingAddressData->set('additionalAddressLine1', $billingAddress->getStreetAdditional()); + $billingAddressData->set('zipcode', $billingAddress->getZipCode()); + $billingAddressData->set('city', $billingAddress->getCity()); + $billingAddressData->set('countryId', $countryId); + $customFields = new RequestDataBag(); + $customFields->set(CustomerAddressDefinition::ENTITY_NAME, [ + CustomFieldsInterface::MOLLIE_KEY => [self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID => $billingAddress->getMollieAddressId()] + ]); + $billingAddressData->set('customFields', $customFields); + + $data->set('billingAddress', $shippingAddressData); + } + + try { + $abstractRegisterRoute = $this->container->get(RegisterRoute::class); + $response = $abstractRegisterRoute->register($data, $context, false); + return $response->getCustomer(); + } catch (ConstraintViolationException $e) { + $errors = []; + /** we have to store the errors in an array because getErrors returns a generator */ + foreach ($e->getErrors() as $error) { + $errors[] = $error; + } + $this->logger->error($e->getMessage(), ['errors' => $errors]); + return null; + } + } + + + /** + * @param string $addressId + * @param string $customerId + * @param null|string $salutationId + * @param AddressStruct $address + * @param Context $context + * @return array + */ + private function createShopwareAddressArray(string $addressId, string $customerId, ?string $salutationId, AddressStruct $address, Context $context): array + { + $addressArray = [ + 'id' => $addressId, + 'customerId' => $customerId, + 'countryId' => $this->getCountryId($address->getCountryCode(), $context), + 'firstName' => $address->getFirstName(), + 'lastName' => $address->getLastName(), + 'street' => $address->getStreet(), + 'additionalAddressLine1' => $address->getStreetAdditional(), + 'zipcode' => $address->getZipCode(), + 'city' => $address->getCity(), + 'phoneNumber' => '', + 'customFields' => [ + CustomFieldsInterface::MOLLIE_KEY => [ + self::CUSTOM_FIELDS_KEY_PAYPAL_EXPRESS_ADDRESS_ID => $address->getMollieAddressId() + ] + ] + ]; + if ($salutationId !== null) { + $addressArray['salutationId'] = $salutationId; + } + return $addressArray; + } } diff --git a/src/Service/CustomerServiceInterface.php b/src/Service/CustomerServiceInterface.php index d450a9112..b131baa24 100644 --- a/src/Service/CustomerServiceInterface.php +++ b/src/Service/CustomerServiceInterface.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Service; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Kiener\MolliePayments\Struct\CustomerStruct; use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity; use Shopware\Core\Checkout\Customer\CustomerEntity; @@ -13,9 +14,12 @@ interface CustomerServiceInterface { - public function customerLogin(CustomerEntity $customer, SalesChannelContext $context): ?string; + public function loginCustomer(CustomerEntity $customer, SalesChannelContext $context): ?string; + public function isCustomerLoggedIn(SalesChannelContext $context): bool; + public function setCardToken(CustomerEntity $customer, string $cardToken, SalesChannelContext $context, bool $shouldSaveCardDetail = false): EntityWrittenContainerEvent; + public function setMandateId(CustomerEntity $customer, string $cardToken, Context $context): EntityWrittenContainerEvent; /** @@ -26,8 +30,11 @@ public function setMandateId(CustomerEntity $customer, string $cardToken, Contex */ public function saveCustomerCustomFields(string $customerID, array $customFields, Context $context): EntityWrittenContainerEvent; public function getMollieCustomerId(string $customerId, string $salesChannelId, Context $context): string; + public function setMollieCustomerId(string $customerId, string $mollieCustomerId, string $profileId, bool $testMode, Context $context): void; + public function getCustomer(string $customerId, Context $context): ?CustomerEntity; + public function getCustomerStruct(string $customerId, Context $context): CustomerStruct; /** @@ -36,8 +43,12 @@ public function getCustomerStruct(string $customerId, Context $context): Custome * @return array */ public function getAddressArray($address, CustomerEntity $customer): array; - public function createApplePayDirectCustomerIfNotExists(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2, int $acceptedDataProtection, SalesChannelContext $context): ?CustomerEntity; + + public function createGuestAccount(AddressStruct $shippingAddress, string $paymentMethodId, SalesChannelContext $context, ?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): ?CustomerEntity; + public function getCountryId(string $countryCode, Context $context): ?string; + public function getSalutationId(Context $context): ?string; + public function createMollieCustomer(string $customerId, string $salesChannelId, Context $context): void; } diff --git a/src/Service/Order/UpdateOrderLineItems.php b/src/Service/Order/UpdateOrderLineItems.php index 2ce73f5c6..fab2aba21 100644 --- a/src/Service/Order/UpdateOrderLineItems.php +++ b/src/Service/Order/UpdateOrderLineItems.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Service\Order; use Kiener\MolliePayments\Repository\OrderLineItem\OrderLineItemRepositoryInterface; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Mollie\Api\Resources\Order; use Mollie\Api\Resources\OrderLine; use Mollie\Api\Types\OrderLineType; @@ -57,7 +58,7 @@ public function updateOrderLineItems(array $orderLines, OrderLineItemCollection $updateLines[] = [ 'id' => $shopwareLine->getId(), 'customFields' => [ - 'mollie_payments' => $originalCustomFields + CustomFieldsInterface::MOLLIE_KEY => $originalCustomFields ], ]; } diff --git a/src/Service/PayPalExpressConfig.php b/src/Service/PayPalExpressConfig.php new file mode 100644 index 000000000..c675c44fa --- /dev/null +++ b/src/Service/PayPalExpressConfig.php @@ -0,0 +1,50 @@ +enabled = $enabled ?? 0; + $this->buttonStyle = $buttonStyle ?? 1; + $this->buttonShape = $buttonShape ?? 1; + $restrictions = $restrictions ?? ''; + + if (strlen($restrictions) > 0) { + $this->restrictions = explode(' ', $restrictions); + } + } + + public function isEnabled(): bool + { + return $this->enabled === 1; + } + + /** + * @param array $structData + * @return array + */ + public function assign(array $structData): array + { + $structData['paypalExpressEnabled'] = $this->isEnabled(); + $structData['paypalExpressButtonStyle'] = $structData['paypalExpressButtonStyle'] ?? $this->buttonStyle; + $structData['paypalExpressButtonShape'] = $structData['paypalExpressButtonShape'] ?? $this->buttonShape; + $structData['paypalExpressRestrictions'] = array_unique(array_merge($structData['paypalExpressRestrictions'] ?? [], $this->restrictions)); + return $structData; + } +} diff --git a/src/Service/Payment/Remover/PayPalExpressPaymentRemover.php b/src/Service/Payment/Remover/PayPalExpressPaymentRemover.php new file mode 100644 index 000000000..c6f0f8988 --- /dev/null +++ b/src/Service/Payment/Remover/PayPalExpressPaymentRemover.php @@ -0,0 +1,59 @@ +isAllowedRoute()) { + return $originalData; + } + $showPPEOnly = false; + + if ($this->isOrderRoute()) { + $order = $this->getOrder($context->getContext()); + $showPPEOnly = (bool)($order->getCustomFields()[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? false); + } + if ($this->isCartRoute()) { + $cart = $this->getCart($context); + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + if ($cartExtension instanceof ArrayStruct) { + $ppeSessionId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_SESSION_ID_KEY] ?? ''; + $ppeAuthId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? ''; + $showPPEOnly = mb_strlen($ppeSessionId) > 0 || mb_strlen($ppeAuthId); + } + } + + foreach ($originalData->getPaymentMethods() as $key => $paymentMethod) { + $attributes = new PaymentMethodAttributes($paymentMethod); + $isPayPalExpress = $attributes->getMollieIdentifier() === PayPalExpressPayment::PAYMENT_METHOD_NAME; + + if ($showPPEOnly === true && $isPayPalExpress === false) { + $originalData->getPaymentMethods()->remove($key); + continue; + } + + if ($showPPEOnly === false && $isPayPalExpress === true) { + $originalData->getPaymentMethods()->remove($key); + } + } + + + return $originalData; + } +} diff --git a/src/Service/PaymentMethodService.php b/src/Service/PaymentMethodService.php index 7dcc15078..47de70700 100644 --- a/src/Service/PaymentMethodService.php +++ b/src/Service/PaymentMethodService.php @@ -14,6 +14,7 @@ use Kiener\MolliePayments\Handler\Method\CreditCardPayment; use Kiener\MolliePayments\Handler\Method\EpsPayment; use Kiener\MolliePayments\Handler\Method\GiftCardPayment; +use Kiener\MolliePayments\Handler\Method\GiroPayPayment; use Kiener\MolliePayments\Handler\Method\iDealPayment; use Kiener\MolliePayments\Handler\Method\In3Payment; use Kiener\MolliePayments\Handler\Method\IngHomePayPayment; @@ -24,6 +25,7 @@ use Kiener\MolliePayments\Handler\Method\KlarnaSliceItPayment; use Kiener\MolliePayments\Handler\Method\MyBankPayment; use Kiener\MolliePayments\Handler\Method\PayconiqPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Kiener\MolliePayments\Handler\Method\PayPalPayment; use Kiener\MolliePayments\Handler\Method\PaySafeCardPayment; use Kiener\MolliePayments\Handler\Method\PosPayment; @@ -89,6 +91,7 @@ class PaymentMethodService * @var VersionCompare */ private $versionCompare; + private PayPalExpressConfig $payPalExpressConfig; /** @@ -99,7 +102,7 @@ class PaymentMethodService * @param PluginIdProvider $pluginIdProvider * @param HttpClientInterface $httpClient */ - public function __construct(string $shopwareVersion, MediaService $mediaService, MediaRepositoryInterface $mediaRepository, PaymentMethodRepositoryInterface $paymentRepository, PluginIdProvider $pluginIdProvider, HttpClientInterface $httpClient) + public function __construct(string $shopwareVersion, MediaService $mediaService, MediaRepositoryInterface $mediaRepository, PaymentMethodRepositoryInterface $paymentRepository, PluginIdProvider $pluginIdProvider, HttpClientInterface $httpClient, PayPalExpressConfig $payPalExpressConfig) { $this->mediaService = $mediaService; $this->mediaRepository = $mediaRepository; @@ -108,6 +111,7 @@ public function __construct(string $shopwareVersion, MediaService $mediaService, $this->httpClient = $httpClient; $this->versionCompare = new VersionCompare($shopwareVersion); + $this->payPalExpressConfig = $payPalExpressConfig; } @@ -120,7 +124,14 @@ public function installAndActivatePaymentMethods(Context $context): void # we still need the min the database # but always disable them :) $this->disablePaymentMethod(IngHomePayPayment::class, $context); - + $this->disablePaymentMethod(GiroPayPayment::class, $context); + $this->disablePaymentMethod(KlarnaPayLaterPayment::class, $context); + $this->disablePaymentMethod(KlarnaPayNowPayment::class, $context); + $this->disablePaymentMethod(KlarnaSliceItPayment::class, $context); + $this->disablePaymentMethod(SofortPayment::class, $context); + if (! $this->payPalExpressConfig->isEnabled()) { + $this->disablePaymentMethod(PayPalExpressPayment::class, $context); + } // Get installable payment methods $installablePaymentMethods = $this->getInstallablePaymentMethods(); @@ -200,7 +211,8 @@ public function addPaymentMethods(array $paymentMethods, Context $context): void if ($this->versionCompare->gte('6.5.7.0')) { # we do a string cast here, since getTechnicalName will be not nullable in the future - $technicalName = (string)$existingPaymentMethod->getTechnicalName(); /** @phpstan-ignore-line */ + /** @phpstan-ignore-next-line */ + $technicalName = (string)$existingPaymentMethod->getTechnicalName(); } } else { # let's create a full parameter list of everything @@ -213,8 +225,8 @@ public function addPaymentMethods(array $paymentMethods, Context $context): void 'description' => '', 'mediaId' => $mediaId, 'afterOrderEnabled' => true, - 'translations'=>[ - Defaults::LANGUAGE_SYSTEM=>[ + 'translations' => [ + Defaults::LANGUAGE_SYSTEM => [ 'name' => $paymentMethod['description'] ] ] @@ -222,7 +234,7 @@ public function addPaymentMethods(array $paymentMethods, Context $context): void } if (mb_strlen($technicalName) === 0) { - $technicalName = self::TECHNICAL_NAME_PREFIX . $paymentMethod['name']; + $technicalName = self::TECHNICAL_NAME_PREFIX . $paymentMethod['name']; } # custom field name is required to be specific, because we use it in the template to display components @@ -257,13 +269,13 @@ public function getInstalledPaymentMethodHandlers(array $installableHandlers, Co $paymentMethods = $this->paymentRepository->search($paymentCriteria, $context); - if (!$paymentMethods->count()) { + if (! $paymentMethods->count()) { return $installableHandlers; } /** @var PaymentMethodEntity $paymentMethod */ foreach ($paymentMethods->getEntities() as $paymentMethod) { - if (!in_array($paymentMethod->getHandlerIdentifier(), $installableHandlers, true)) { + if (! in_array($paymentMethod->getHandlerIdentifier(), $installableHandlers, true)) { continue; } @@ -282,10 +294,10 @@ public function getInstalledPaymentMethodHandlers(array $installableHandlers, Co */ public function activatePaymentMethods(array $paymentMethods, array $installedHandlers, Context $context): void { - if (!empty($paymentMethods)) { + if (! empty($paymentMethods)) { foreach ($paymentMethods as $paymentMethod) { if ( - !isset($paymentMethod['handler']) || + ! isset($paymentMethod['handler']) || in_array($paymentMethod['handler'], $installedHandlers, true) ) { continue; @@ -421,7 +433,7 @@ private function getPaymentMethod($handlerIdentifier, Context $context): ?Paymen */ public function getPaymentHandlers(): array { - return [ + $paymentHandlers = [ ApplePayPayment::class, BanContactPayment::class, BankTransferPayment::class, @@ -432,14 +444,14 @@ public function getPaymentHandlers(): array GiftCardPayment::class, iDealPayment::class, KbcPayment::class, - KlarnaPayLaterPayment::class, - KlarnaPayNowPayment::class, - KlarnaSliceItPayment::class, + // KlarnaPayLaterPayment::class, + // KlarnaPayNowPayment::class, + // KlarnaSliceItPayment::class, KlarnaOnePayment::class, PayPalPayment::class, PaySafeCardPayment::class, Przelewy24Payment::class, - SofortPayment::class, + // SofortPayment::class, VoucherPayment::class, In3Payment::class, PosPayment::class, @@ -455,6 +467,12 @@ public function getPaymentHandlers(): array // IngHomePayPayment::class, // not allowed anymore // DirectDebitPayment::class, // only allowed when updating subsriptions, aka => not allowed anymore ]; + + if ($this->payPalExpressConfig->isEnabled()) { + $paymentHandlers[] = PayPalExpressPayment::class; + } + + return $paymentHandlers; } /** @@ -467,8 +485,14 @@ public function getPaymentHandlers(): array */ private function getMediaId(array $paymentMethod, Context $context): ?string { + $name = $paymentMethod['name']; + + if ($name === PayPalExpressPayment::PAYMENT_METHOD_NAME) { + $name = PayPalPayment::PAYMENT_METHOD_NAME; + } + /** @var string $fileName */ - $fileName = $paymentMethod['name'] . '-icon'; + $fileName = $name . '-icon'; $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('fileName', $fileName)); @@ -517,7 +541,7 @@ public function isPaidApplePayTransaction(OrderTransactionEntity $transaction, O $paymentMethodId = $transaction->getPaymentMethodId(); $paymentMethod = $transaction->getPaymentMethod(); - if (!$paymentMethod instanceof PaymentMethodEntity) { + if (! $paymentMethod instanceof PaymentMethodEntity) { $criteria = new Criteria([$paymentMethodId]); $paymentMethod = $this->paymentRepository->search($criteria, Context::createDefaultContext())->first(); } diff --git a/src/Service/Refund/CompositionMigrationService.php b/src/Service/Refund/CompositionMigrationService.php index 5706cc2b8..82f176bb0 100644 --- a/src/Service/Refund/CompositionMigrationService.php +++ b/src/Service/Refund/CompositionMigrationService.php @@ -8,6 +8,7 @@ use Kiener\MolliePayments\Components\RefundManager\DAL\Refund\RefundEntity; use Kiener\MolliePayments\Components\RefundManager\DAL\RefundItem\RefundItemCollection; use Kiener\MolliePayments\Components\RefundManager\DAL\RefundItem\RefundItemEntity; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Service\MollieApi\Fixer\RoundingDifferenceFixer; use Mollie\Api\Resources\Refund; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection; @@ -159,10 +160,10 @@ private function filterByMollieId(OrderLineItemCollection $lineItems, string $mo if ($customFields === null) { continue; } - if (!isset($customFields['mollie_payments']['order_line_id'])) { + if (!isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_LINE_KEY])) { continue; } - if ($customFields['mollie_payments']['order_line_id'] === $mollieLineId) { + if ($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_LINE_KEY] === $mollieLineId) { $foundItem = $lineItem; break; } diff --git a/src/Service/Router/RoutingBuilder.php b/src/Service/Router/RoutingBuilder.php index 010ed56f1..d30ab7e36 100644 --- a/src/Service/Router/RoutingBuilder.php +++ b/src/Service/Router/RoutingBuilder.php @@ -192,6 +192,27 @@ public function buildSubscriptionPaymentUpdatedReturnUrl(string $subscriptionId) return $this->applyCustomDomain((string)$webhookUrl); } + /** + * @return string + */ + public function buildPaypalExpressRedirectUrl(): string + { + $confirmPage = $this->router->generate('frontend.mollie.paypal-express.finish', [], $this->router::ABSOLUTE_URL); + + return $confirmPage; + } + + /** + * @return string + */ + public function buildPaypalExpressCancelUrl(): string + { + $confirmPage = $this->router->generate('frontend.checkout.confirm.page', [], $this->router::ABSOLUTE_URL); + + return $confirmPage; + } + + /** * @param string $url * @return string diff --git a/src/Service/SettingsService.php b/src/Service/SettingsService.php index 424b645af..99c1c826e 100644 --- a/src/Service/SettingsService.php +++ b/src/Service/SettingsService.php @@ -18,7 +18,7 @@ class SettingsService implements PluginSettingsServiceInterface private const PHONE_NUMBER_FIELD = 'showPhoneNumberField'; - private const REQUIRE_DATA_PROTECTION ='requireDataProtectionCheckbox'; + private const REQUIRE_DATA_PROTECTION = 'requireDataProtectionCheckbox'; private const PAYMENT_FINALIZE_TRANSACTION_TIME = 'paymentFinalizeTransactionTime'; const LIVE_API_KEY = 'liveApiKey'; @@ -51,7 +51,12 @@ class SettingsService implements PluginSettingsServiceInterface * @var string */ private $envCypressMode; + private PayPalExpressConfig $payPalExpressConfig; + /** + * @var array + */ + private array $cachedStructs = []; /** * @param SystemConfigService $systemConfigService @@ -60,7 +65,7 @@ class SettingsService implements PluginSettingsServiceInterface * @param ?string $envDevMode * @param ?string $envCypressMode */ - public function __construct(SystemConfigService $systemConfigService, SalesChannelRepositoryInterface $repoSalesChannels, ?string $envShopDomain, ?string $envDevMode, ?string $envCypressMode) + public function __construct(SystemConfigService $systemConfigService, SalesChannelRepositoryInterface $repoSalesChannels, PayPalExpressConfig $payPalExpressConfig, ?string $envShopDomain, ?string $envDevMode, ?string $envCypressMode) { $this->systemConfigService = $systemConfigService; $this->repoSalesChannels = $repoSalesChannels; @@ -68,6 +73,7 @@ public function __construct(SystemConfigService $systemConfigService, SalesChann $this->envShopDomain = (string)$envShopDomain; $this->envDevMode = (string)$envDevMode; $this->envCypressMode = (string)$envCypressMode; + $this->payPalExpressConfig = $payPalExpressConfig; } /** @@ -78,11 +84,16 @@ public function __construct(SystemConfigService $systemConfigService, SalesChann */ public function getSettings(?string $salesChannelId = null): MollieSettingStruct { + $cacheKey = $salesChannelId ?? 'all'; + + if (isset($this->cachedStructs[$cacheKey])) { + return $this->cachedStructs[$cacheKey]; + } $structData = []; /** @var array $systemConfigData */ $systemConfigData = $this->systemConfigService->get(self::SYSTEM_CONFIG_DOMAIN, $salesChannelId); - if (count($systemConfigData) !== 0) { + if (is_array($systemConfigData) && count($systemConfigData) > 0) { foreach ($systemConfigData as $key => $value) { if (stripos($key, self::SYSTEM_CONFIG_DOMAIN) !== false) { $structData[substr($key, strlen(self::SYSTEM_CONFIG_DOMAIN))] = $value; @@ -94,18 +105,31 @@ public function getSettings(?string $salesChannelId = null): MollieSettingStruct /** @var array $coreSettings */ $coreSettings = $this->systemConfigService->get(self::SYSTEM_CORE_LOGIN_REGISTRATION_CONFIG_DOMAIN, $salesChannelId); + if (is_array($coreSettings) && count($coreSettings) > 0) { + $structData[self::PHONE_NUMBER_FIELD_REQUIRED] = $coreSettings[self::PHONE_NUMBER_FIELD_REQUIRED] ?? false; + $structData[self::PHONE_NUMBER_FIELD] = $coreSettings[self::PHONE_NUMBER_FIELD] ?? false; + $structData[self::REQUIRE_DATA_PROTECTION] = $coreSettings[self::REQUIRE_DATA_PROTECTION] ?? false; + } - $structData[self::PHONE_NUMBER_FIELD_REQUIRED] = $coreSettings[self::PHONE_NUMBER_FIELD_REQUIRED] ?? false; - - $structData[self::PHONE_NUMBER_FIELD] = $coreSettings[self::PHONE_NUMBER_FIELD] ?? false; - - $structData[self::REQUIRE_DATA_PROTECTION] = $coreSettings[self::REQUIRE_DATA_PROTECTION] ?? false; /** @var array $cartSettings */ $cartSettings = $this->systemConfigService->get(self::SYSTEM_CORE_CART_CONFIG_DOMAIN, $salesChannelId); - $structData[self::PAYMENT_FINALIZE_TRANSACTION_TIME] = $cartSettings[self::PAYMENT_FINALIZE_TRANSACTION_TIME] ?? 1800; + if (is_array($cartSettings) && count($cartSettings) > 0) { + $structData[self::PAYMENT_FINALIZE_TRANSACTION_TIME] = $cartSettings[self::PAYMENT_FINALIZE_TRANSACTION_TIME] ?? 1800; + } + + + /** + * TODO: remove this when we move to config + */ + if ($this->payPalExpressConfig->isEnabled()) { + $structData = $this->payPalExpressConfig->assign($structData); + } + + + $this->cachedStructs[$cacheKey] = (new MollieSettingStruct())->assign($structData); - return (new MollieSettingStruct())->assign($structData); + return $this->cachedStructs[$cacheKey]; } /** diff --git a/src/Service/UpdateOrderTransactionCustomFields.php b/src/Service/UpdateOrderTransactionCustomFields.php index 761372fd5..236721882 100644 --- a/src/Service/UpdateOrderTransactionCustomFields.php +++ b/src/Service/UpdateOrderTransactionCustomFields.php @@ -33,7 +33,7 @@ public function updateOrderTransaction(string $shopwareOrderTransactionId, Order $data = [ 'id' => $shopwareOrderTransactionId, 'customFields' => [ - 'mollie_payments' => $struct->toArray(), + CustomFieldsInterface::MOLLIE_KEY => $struct->toArray(), ] ]; diff --git a/src/Setting/MollieSettingStruct.php b/src/Setting/MollieSettingStruct.php index 2d4d44d04..c84d99cdd 100644 --- a/src/Setting/MollieSettingStruct.php +++ b/src/Setting/MollieSettingStruct.php @@ -290,6 +290,26 @@ class MollieSettingStruct extends Struct */ protected $automaticOrderExpire = false; + /** + * @var bool + */ + protected bool $paypalExpressEnabled = false; + + /** + * @var int + */ + protected $paypalExpressButtonStyle = 1; + + /** + * @var int + */ + protected $paypalExpressButtonShape = 1; + + /** + * @var array + */ + protected $paypalExpressRestrictions = []; + /** * @return string */ @@ -1088,4 +1108,51 @@ public function setAutomaticOrderExpire(bool $automaticOrderExpire): void { $this->automaticOrderExpire = $automaticOrderExpire; } + + public function isPaypalExpressEnabled(): bool + { + return $this->paypalExpressEnabled; + } + + public function setPaypalExpressEnabled(bool $paypalExpressEnabled): void + { + $this->paypalExpressEnabled = $paypalExpressEnabled; + } + + public function getPaypalExpressButtonStyle(): int + { + return $this->paypalExpressButtonStyle; + } + + public function setPaypalExpressButtonStyle(int $paypalExpressButtonStyle): void + { + $this->paypalExpressButtonStyle = $paypalExpressButtonStyle; + } + + public function getPaypalExpressButtonShape(): int + { + return $this->paypalExpressButtonShape; + } + + public function setPaypalExpressButtonShape(int $paypalExpressButtonShape): void + { + $this->paypalExpressButtonShape = $paypalExpressButtonShape; + } + + /** + * @return string[] + */ + public function getPaypalExpressRestrictions(): array + { + return $this->paypalExpressRestrictions; + } + + /** + * @param array $paypalExpressRestrictions + * @return void + */ + public function setPaypalExpressRestrictions(array $paypalExpressRestrictions): void + { + $this->paypalExpressRestrictions = $paypalExpressRestrictions; + } } diff --git a/src/Struct/Address/AddressStruct.php b/src/Struct/Address/AddressStruct.php new file mode 100644 index 000000000..15e983a61 --- /dev/null +++ b/src/Struct/Address/AddressStruct.php @@ -0,0 +1,145 @@ +firstName = $firstName; + $this->lastName = $lastName; + $this->street = $street; + $this->streetAdditional = $streetAdditional; + $this->zipCode = $zipCode; + $this->city = $city; + $this->countryCode = $countryCode; + $this->email = $email; + $this->phone = $phone; + } + + /** + * @param \stdClass $address + * @return self + */ + public static function createFromApiResponse(\stdClass $address) + { + $streetAdditional = ''; + if (property_exists($address, 'streetAdditional')) { + $streetAdditional = $address->streetAdditional; + } + if (property_exists($address, 'familyName')) { + $nameParts = explode(' ', $address->familyName); + $address->familyName = array_pop($nameParts); + $address->givenName = implode(' ', $nameParts); + } + + return new AddressStruct($address->givenName, $address->familyName, $address->email, $address->streetAndNumber, $streetAdditional, $address->postalCode, $address->city, $address->country, (string)$address->phone); + } + + public function getFirstName(): string + { + return $this->firstName; + } + + public function getLastName(): string + { + return $this->lastName; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getStreet(): string + { + return $this->street; + } + + public function getStreetAdditional(): string + { + return $this->streetAdditional; + } + + public function getZipCode(): string + { + return $this->zipCode; + } + + public function getCity(): string + { + return $this->city; + } + + public function getCountryCode(): string + { + return $this->countryCode; + } + + public function getPhone(): string + { + return $this->phone; + } + + + public function getMollieAddressId(): string + { + return md5(implode('-', [$this->firstName, $this->lastName, $this->email, $this->street, $this->streetAdditional, $this->zipCode, $this->city, $this->countryCode])); + } +} diff --git a/src/Struct/CustomerStruct.php b/src/Struct/CustomerStruct.php index 5837b918c..d7c84495a 100644 --- a/src/Struct/CustomerStruct.php +++ b/src/Struct/CustomerStruct.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Shopware\Core\Framework\Struct\Struct; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; @@ -143,7 +144,7 @@ public function toCustomFieldsArray(): array $fullCustomField = [ - 'mollie_payments' => $mollieData + CustomFieldsInterface::MOLLIE_KEY => $mollieData ]; # now either reset our old customer ID diff --git a/src/Struct/LineItem/LineItemAttributes.php b/src/Struct/LineItem/LineItemAttributes.php index efb4d1521..06968ef24 100644 --- a/src/Struct/LineItem/LineItemAttributes.php +++ b/src/Struct/LineItem/LineItemAttributes.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct\LineItem; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Shopware\Core\Checkout\Cart\LineItem\LineItem; class LineItemAttributes @@ -232,9 +233,9 @@ private function getCustomFieldValue(LineItem $lineItem, string $keyName): strin # and load, but we migrate to the new one # check if we have customFields if ($foundValue === '') { - if ($customFields !== null && array_key_exists('mollie_payments', $customFields)) { + if ($customFields !== null && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } diff --git a/src/Struct/Order/OrderAttributes.php b/src/Struct/Order/OrderAttributes.php index eca8bad18..dbec6cfb1 100644 --- a/src/Struct/Order/OrderAttributes.php +++ b/src/Struct/Order/OrderAttributes.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct\Order; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Struct\OrderLineItemEntity\OrderLineItemEntityAttributes; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection; use Shopware\Core\Checkout\Order\OrderEntity; @@ -104,6 +105,11 @@ class OrderAttributes */ private $bankBic; + /** + * @var string + */ + private $payPalExpressAuthenticateId; + /** * @var string */ @@ -134,6 +140,7 @@ public function __construct(OrderEntity $order) $this->bankBic = $this->getCustomFieldValue($order, 'bankBic'); $this->timezone = $this->getCustomFieldValue($order, 'timezone'); $this->bancomatPayPhoneNumber = $this->getCustomFieldValue($order, 'bancomatPayPhoneNumber'); + $this->payPalExpressAuthenticateId = $this->getCustomFieldValue($order, CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID); } /** @@ -450,6 +457,18 @@ public function setBankTransferDetails(?stdClass $details) } } + public function getPayPalExpressAuthenticateId(): string + { + return $this->payPalExpressAuthenticateId; + } + + public function setPayPalExpressAuthenticateId(string $payPalExpressAuthenticateId): void + { + $this->payPalExpressAuthenticateId = $payPalExpressAuthenticateId; + } + + + /** * @return string */ @@ -555,8 +574,12 @@ public function toArray(): array if ($this->bancomatPayPhoneNumber !== '') { $mollieData['bancomatPayPhoneNumber'] = $this->bancomatPayPhoneNumber; } + if ((string)$this->payPalExpressAuthenticateId !== '') { + $mollieData[CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] = $this->payPalExpressAuthenticateId; + } + return [ - 'mollie_payments' => $mollieData, + CustomFieldsInterface::MOLLIE_KEY => $mollieData, ]; } @@ -602,9 +625,9 @@ private function getCustomFieldValue(OrderEntity $order, string $keyName): strin $customFields = $order->getCustomFields(); # check if we have a mollie entry - if ($customFields !== null && array_key_exists('mollie_payments', $customFields)) { + if ($customFields !== null && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } diff --git a/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php b/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php index 669edbb7f..2975ea838 100644 --- a/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php +++ b/src/Struct/OrderLineItemEntity/OrderLineItemEntityAttributes.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Struct\OrderLineItemEntity; +use Kiener\MolliePayments\Service\CustomFieldsInterface; use Kiener\MolliePayments\Struct\Voucher\VoucherType; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity; @@ -205,9 +206,9 @@ private function getCustomFieldValue(OrderLineItemEntity $lineItem, string $keyN # old structure # check if we have a mollie entry - if ($foundValue === '' && array_key_exists('mollie_payments', $customFields)) { + if ($foundValue === '' && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } @@ -230,9 +231,9 @@ private function getCustomFieldValue(OrderLineItemEntity $lineItem, string $keyN # old structure # check if we have a mollie entry - if ($foundValue === '' && array_key_exists('mollie_payments', $customFields)) { + if ($foundValue === '' && array_key_exists(CustomFieldsInterface::MOLLIE_KEY, $customFields)) { # load the mollie entry - $mollieData = $customFields['mollie_payments']; + $mollieData = $customFields[CustomFieldsInterface::MOLLIE_KEY]; # assign our value if we have it $foundValue = (array_key_exists($keyName, $mollieData)) ? (string)$mollieData[$keyName] : ''; } diff --git a/src/Struct/OrderTransaction/OrderTransactionAttributes.php b/src/Struct/OrderTransaction/OrderTransactionAttributes.php index 1e10617e9..ad1909462 100644 --- a/src/Struct/OrderTransaction/OrderTransactionAttributes.php +++ b/src/Struct/OrderTransaction/OrderTransactionAttributes.php @@ -2,6 +2,8 @@ namespace Kiener\MolliePayments\Struct\OrderTransaction; +use Kiener\MolliePayments\Service\CustomFieldsInterface; + class OrderTransactionAttributes { /** @@ -88,9 +90,9 @@ public function toArray(): array } return [ - 'order_id' => $this->mollieOrderId, - 'payment_id' => $this->molliePaymentId, - 'third_party_payment_id' => $this->thirdPartyPaymentId, + CustomFieldsInterface::ORDER_KEY => $this->mollieOrderId, + CustomFieldsInterface::PAYMENT_KEY => $this->molliePaymentId, + CustomFieldsInterface::THIRD_PARTY_PAYMENT_KEY => $this->thirdPartyPaymentId, ]; } @@ -99,20 +101,20 @@ public function toArray(): array */ private function setCustomFields(array $customFields): void { - if (!isset($customFields['mollie_payments'])) { + if (!isset($customFields[CustomFieldsInterface::MOLLIE_KEY])) { return; } - if (isset($customFields['mollie_payments']['order_id'])) { - $this->setMollieOrderId((string)$customFields['mollie_payments']['order_id']); + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY])) { + $this->setMollieOrderId((string)$customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::ORDER_KEY]); } - if (isset($customFields['mollie_payments']['payment_id'])) { - $this->setMolliePaymentId((string)$customFields['mollie_payments']['payment_id']); + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYMENT_KEY])) { + $this->setMolliePaymentId((string)$customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYMENT_KEY]); } - if (isset($customFields['mollie_payments']['third_party_payment_id'])) { - $this->setThirdPartyPaymentId((string)$customFields['mollie_payments']['third_party_payment_id']); + if (isset($customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::THIRD_PARTY_PAYMENT_KEY])) { + $this->setThirdPartyPaymentId((string)$customFields[CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::THIRD_PARTY_PAYMENT_KEY]); } } } diff --git a/src/Subscriber/CartConvertedSubscriber.php b/src/Subscriber/CartConvertedSubscriber.php new file mode 100644 index 000000000..0e3013d49 --- /dev/null +++ b/src/Subscriber/CartConvertedSubscriber.php @@ -0,0 +1,36 @@ + 'savePayPalExpressData' + ]; + } + + public function savePayPalExpressData(CartConvertedEvent $event): void + { + $cart = $event->getCart(); + $cartExtension = $cart->getExtension(CustomFieldsInterface::MOLLIE_KEY); + if (! ($cartExtension instanceof ArrayStruct)) { + return; + } + $paypalExpressAuthenticateId = $cartExtension[CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] ?? null; + if ($paypalExpressAuthenticateId === null) { + return; + } + + $convertedCart = $event->getConvertedCart(); + $convertedCart['customFields'][CustomFieldsInterface::MOLLIE_KEY][CustomFieldsInterface::PAYPAL_EXPRESS_AUTHENTICATE_ID] = $paypalExpressAuthenticateId; + $event->setConvertedCart($convertedCart); + } +} diff --git a/src/Subscriber/PaypalExpressSubscriber.php b/src/Subscriber/PaypalExpressSubscriber.php new file mode 100644 index 000000000..6004a4d49 --- /dev/null +++ b/src/Subscriber/PaypalExpressSubscriber.php @@ -0,0 +1,66 @@ +settingsService = $settingsService; + $this->paypal = $paypal; + } + + + /** + * @inheritDoc + */ + public static function getSubscribedEvents() + { + return [ + StorefrontRenderEvent::class => 'onStorefrontRender', + ]; + } + + /** + * @param StorefrontRenderEvent $event + * @throws \Exception + * @return void + */ + public function onStorefrontRender(StorefrontRenderEvent $event): void + { + $settings = $this->settingsService->getSettings($event->getSalesChannelContext()->getSalesChannel()->getId()); + + $paymentEnabled = $this->paypal->isPaypalExpressEnabled($event->getSalesChannelContext()) && $settings->isPaypalExpressEnabled(); + + $event->setParameter('mollie_paypalexpress_enabled', $paymentEnabled); + + $style = $settings->getPaypalExpressButtonStyle(); + $shape = $settings->getPaypalExpressButtonShape(); + + $restrictions = $settings->getPaypalExpressRestrictions(); + + $event->setParameter('mollie_paypalexpress_style', $style); + $event->setParameter('mollie_paypalexpress_shape', $shape); + $event->setParameter('mollie_paypalexpress_restrictions', $restrictions); + } +} diff --git a/src/Traits/Storefront/RedirectTrait.php b/src/Traits/Storefront/RedirectTrait.php index 3d0a487be..47b72d62a 100644 --- a/src/Traits/Storefront/RedirectTrait.php +++ b/src/Traits/Storefront/RedirectTrait.php @@ -50,4 +50,9 @@ public function getEditOrderPage(string $orderId, RouterInterface $router): stri $router::ABSOLUTE_URL ); } + + public function getCheckoutCartPage(RouterInterface $router):string + { + return $router->generate('frontend.checkout.cart.page', [], $router::ABSOLUTE_URL); + } } diff --git a/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js b/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js index 36a61e6c1..c02e41fd9 100644 --- a/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/cancel/cancel-item.cy.js @@ -43,7 +43,7 @@ context("Cancel Authorized items", () => { context(devices.getDescription(device), () => { it('C3259233: Cancel items from order', () => { - createOrderAndOpenAdmin('Pay now'); + createOrderAndOpenAdmin('Klarna'); orderDetailsRepository.getLineItemActionsButton(1).should('be.visible').click({force: true}); diff --git a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js index 4eeee1da2..308060f4a 100644 --- a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-states.cy.js @@ -88,7 +88,7 @@ context("Order Status Mapping Tests", () => { it('C4024: Test Status Authorized', () => { scenarioDummyBasket.execute(); - paymentAction.switchPaymentMethod('Pay later'); + paymentAction.switchPaymentMethod('Klarna'); shopware.prepareDomainChange(); checkout.placeOrderOnConfirm(); diff --git a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js index cf640d97f..52f2ac686 100644 --- a/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/checkout/checkout-success.cy.js @@ -45,9 +45,7 @@ const payments = [ {caseId: 'C4101', key: 'credit-card', name: 'Card', sanity: false}, {caseId: 'C4111', key: 'paypal', name: 'PayPal', sanity: true}, {caseId: 'C466903', key: 'billie', name: 'Billie', sanity: false}, - {caseId: 'C4114', key: 'klarnapaynow', name: 'Pay now', sanity: false}, - {caseId: 'C4115', key: 'klarnapaylater', name: 'Pay later', sanity: false}, - {caseId: 'C4117', key: 'klarnasliceit', name: 'Slice it', sanity: false}, + {caseId: '', key: 'klarna', name: 'Klarna', sanity: false}, {caseId: 'C4118', key: 'ideal', name: 'iDEAL', sanity: false}, {caseId: 'C4120', key: 'eps', name: 'eps', sanity: false}, {caseId: 'C4123', key: 'mistercash', name: 'Bancontact', sanity: false}, @@ -126,11 +124,7 @@ context("Checkout Tests", () => { mollieSandbox.initSandboxCookie(); - if (payment.key === 'klarnapaylater' || payment.key === 'klarnapaynow' || payment.key === 'klarnasliceit') { - - molliePayment.selectAuthorized(); - - } else if (payment.key === 'billie') { + if (payment.key === 'billie' || payment.key === 'klarna') { molliePayment.selectAuthorized(); diff --git a/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js b/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js index c97249dde..69ee003ff 100644 --- a/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/payment-methods/creditcard.cy.js @@ -265,7 +265,7 @@ describe('Status Tests', () => { devices.setDevice(devices.getFirstDevice()); // turn off credit card components // to speed up a few things - configAction.setupPlugin(false, false, false, false); + configAction.setupPlugin(false, false, false, false,[]); }) @@ -306,7 +306,7 @@ describe('Administration Tests', () => { before(function () { devices.setDevice(devices.getFirstDevice()); - configAction.setupPlugin(false, false, false, false); + configAction.setupPlugin(false, false, false, false,[]); }) beforeEach(() => { diff --git a/tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js b/tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js new file mode 100644 index 000000000..6e2ea8634 --- /dev/null +++ b/tests/Cypress/cypress/e2e/storefront/payment-methods/paypal-express.cy.js @@ -0,0 +1,190 @@ +import TopMenuAction from "Actions/storefront/navigation/TopMenuAction"; +import ListingAction from "Actions/storefront/products/ListingAction"; +import Devices from "Services/utils/Devices"; +import ShopConfigurationAction from "Actions/admin/ShopConfigurationAction"; +import PDPRepository from "Repositories/storefront/products/PDPRepository"; +import PDPAction from "Actions/storefront/products/PDPAction"; +import OffCanvasRepository from "Repositories/storefront/checkout/OffCanvasRepository"; +import CheckoutAction from "Actions/storefront/checkout/CheckoutAction"; +import CartRepository from "Repositories/storefront/checkout/CartRepository"; +import ListingRepository from "Repositories/storefront/products/ListingRepository"; +import RegisterRepository from "Repositories/storefront/checkout/RegisterRepository"; + + +const devices = new Devices(); +const configAction = new ShopConfigurationAction(); +const topMenu = new TopMenuAction(); +const listing = new ListingAction(); +const pdp = new PDPAction(); +const checkout = new CheckoutAction(); + +const repoPDP = new PDPRepository(); +const repoListing = new ListingRepository(); +const repoOffcanvas = new OffCanvasRepository(); +const repoCart = new CartRepository(); +const registerRepo = new RegisterRepository(); + + +describe('Paypal Express - UI Tests', () => { + + + before(function () { + devices.setDevice(devices.getFirstDevice()); + }) + + beforeEach(function () { + devices.setDevice(devices.getFirstDevice()); + }) + + describe('PDP', () => { + + it('Paypal Express button is visible @core', () => { + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + + const button = repoPDP.getPayPalExpressButton(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['pdp']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + + repoPDP.getPayPalExpressButton().should('not.exist'); + }) + + }) + + describe('Listing', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + + const button = repoListing.getPayPalExpressButton().first(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['plp']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + + repoListing.getPayPalExpressButton().should('not.exist'); + }) + }) + + + describe('Offcanvas', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + const button = repoOffcanvas.getPayPalExpressButton(); + button.should('be.visible'); + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['offcanvas']) + + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + repoOffcanvas.getPayPalExpressButton().should('not.exist'); + }) + + }) + + describe('Cart', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCartInOffCanvas(); + + const button = repoCart.getPayPalExpressButton(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['cart']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCartInOffCanvas(); + + repoCart.getPayPalExpressButton().should('not.exist'); + }) + + }) + + describe('Register Page', () => { + + it('Paypal Express button is visible @core', () => { + + configAction.setupPlugin(false,false,false,false,[]); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCheckoutInOffCanvas(); + + const button = registerRepo.getPayPalExpressButton(); + button.should('be.visible'); + + }) + + it('Paypal Express button is hidden because of restriction @core', () => { + + configAction.setupPlugin(false,false,false,false,['register']); + + cy.visit('/'); + topMenu.clickOnSecondCategory(); + listing.clickOnFirstProduct(); + pdp.addToCart(1); + + checkout.goToCheckoutInOffCanvas(); + + registerRepo.getPayPalExpressButton().should('not.exist'); + }) + + }) + +}); \ No newline at end of file diff --git a/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js b/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js index a7e30d72e..9a760ea5d 100644 --- a/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/shipment/shipment.cy.js @@ -266,7 +266,7 @@ function createOrderAndOpenAdmin(itemCount, itemQty) { const scenarioDummyBasket = new DummyBasketScenario(itemQty, itemCount); scenarioDummyBasket.execute(); - paymentAction.switchPaymentMethod('Pay later'); + paymentAction.switchPaymentMethod('Klarna'); shopware.prepareDomainChange(); checkout.placeOrderOnConfirm(); diff --git a/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js b/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js index e9da18259..0d8534331 100644 --- a/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js +++ b/tests/Cypress/cypress/e2e/storefront/subscriptions/subscription.cy.js @@ -225,7 +225,7 @@ describe('Subscription', () => { it('C4067: Subscription Indicator on PDP can be turned ON @core', () => { configAction.updateProducts('', true, 3, 'weeks'); - configAction.setupPlugin(true, false, false, true); + configAction.setupPlugin(true, false, false, true,[]); cy.wait(2000); cy.visit('/'); @@ -241,7 +241,7 @@ describe('Subscription', () => { it('C4068: Subscription Indicator on PDP can be turned OFF @core', () => { configAction.updateProducts('', true, 3, 'weeks'); - configAction.setupPlugin(true, false, false, false); + configAction.setupPlugin(true, false, false, false,[]); cy.wait(2000); cy.visit('/'); @@ -333,7 +333,7 @@ describe('Subscription', () => { function purchaseSubscriptionAndGoToPayment(){ - configAction.setupPlugin(true, false, false, true); + configAction.setupPlugin(true, false, false, true,[]); configAction.updateProducts('', true, 3, 'weeks'); dummyUserScenario.execute(); @@ -451,7 +451,7 @@ function assertAvailablePaymentMethods() { } function prepareSubscriptionAndOpenAdminDetails() { - configAction.setupPlugin(true, false, false, true); + configAction.setupPlugin(true, false, false, true,[]); configAction.updateProducts('', true, 3, 'weeks'); const dummyScenario = new DummyBasketScenario(1) diff --git a/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js b/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js index f3231b82a..134381037 100644 --- a/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js +++ b/tests/Cypress/cypress/support/actions/admin/ShopConfigurationAction.js @@ -18,6 +18,7 @@ export default class ShopConfigurationAction { * @param mollieFailureMode * @param creditCardComponents * @param applePayDirect + * @param paypalExpressRestrictions */ setupShop(mollieFailureMode, creditCardComponents, applePayDirect) { @@ -27,7 +28,7 @@ export default class ShopConfigurationAction { this.prepareShippingMethods(); - this.setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, false); + this.setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, false, []); this._clearCache(); } @@ -39,8 +40,9 @@ export default class ShopConfigurationAction { * @param creditCardComponents * @param applePayDirect * @param subscriptionIndicator + * @param paypalExpressRestrictions */ - setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator) { + setupPlugin(mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator, paypalExpressRestrictions) { // assign all payment methods to // all available sales channels @@ -52,7 +54,7 @@ export default class ShopConfigurationAction { channels.forEach(channel => { this._configureSalesChannel(channel.id); - this._configureMolliePlugin(channel.id, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator); + this._configureMolliePlugin(channel.id, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator, paypalExpressRestrictions); }); }); } @@ -148,9 +150,10 @@ export default class ShopConfigurationAction { * @param creditCardComponents * @param applePayDirect * @param subscriptionIndicator + * @param paypalExpressRestrictions * @private */ - _configureMolliePlugin(channelId, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator) { + _configureMolliePlugin(channelId, mollieFailureMode, creditCardComponents, applePayDirect, subscriptionIndicator, paypalExpressRestrictions) { const data = {}; const config = { @@ -173,6 +176,8 @@ export default class ShopConfigurationAction { "MolliePayments.config.subscriptionsShowIndicator": subscriptionIndicator, "MolliePayments.config.subscriptionsAllowPauseResume": true, "MolliePayments.config.subscriptionsAllowSkip": true, + // --------------------------------------------------------------- + "MolliePayments.config.paypalExpressRestrictions": paypalExpressRestrictions }; data[null] = config; // also add for "All Sales Channels" otherwise things in admin wouldnt work diff --git a/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js b/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js index 491bf2a2e..c70c14a61 100644 --- a/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js +++ b/tests/Cypress/cypress/support/actions/storefront/products/PDPAction.js @@ -2,19 +2,21 @@ import PDPRepository from 'Repositories/storefront/products/PDPRepository'; import Shopware from "Services/shopware/Shopware"; const shopware = new Shopware(); - +const repo = new PDPRepository(); export default class PDPAction { - /** * * @param quantity */ addToCart(quantity) { - const repo = new PDPRepository(); + this.setQuantity(quantity); + repo.getAddToCartButton().click(); + } + setQuantity(quantity){ if (shopware.isVersionGreaterEqual('6.5')) { const repetitions = quantity - 1; // its already 1 initially @@ -26,9 +28,5 @@ export default class PDPAction { } else { repo.getQuantityDropdown().select(quantity + ""); } - - - repo.getAddToCartButton().click(); } - } diff --git a/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js b/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js index 97b01098c..858a93052 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/checkout/CartRepository.js @@ -8,4 +8,11 @@ export default class CartRepository { return cy.get('.mollie-apple-pay-direct-cart > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-cart button[name="paypal-express"]'); + } } diff --git a/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js b/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js index d5f93b694..ceaf9abb5 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/checkout/OffCanvasRepository.js @@ -24,4 +24,11 @@ export default class OffCanvasRepository { return cy.get('.mollie-apple-pay-direct-offcanvas > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-offcanvas button[name="paypal-express"]'); + } } diff --git a/tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js b/tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js new file mode 100644 index 000000000..33ee9707a --- /dev/null +++ b/tests/Cypress/cypress/support/repositories/storefront/checkout/RegisterRepository.js @@ -0,0 +1,11 @@ +export default class RegisterRepository { + + + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-register button[name="paypal-express"]'); + } +} \ No newline at end of file diff --git a/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js b/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js index f033d40c5..60c6e05a5 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/products/ListingRepository.js @@ -25,4 +25,11 @@ export default class ListingRepository { return cy.get('.mollie-apple-pay-direct-listing > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-plp button[name="paypal-express"]') + } } diff --git a/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js b/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js index 4ceecdb18..8601f74a0 100644 --- a/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js +++ b/tests/Cypress/cypress/support/repositories/storefront/products/PDPRepository.js @@ -44,4 +44,13 @@ export default class PDPRepository { return cy.get('.mollie-apple-pay-direct-pdp > div > .js-apple-pay'); } + /** + * + * @returns {Cypress.Chainable>} + */ + getPayPalExpressButton(){ + return cy.get('.mollie-paypal-express-pdp button[name="paypal-express"]'); + } + + } diff --git a/tests/PHPUnit/Fakes/FakeCartService.php b/tests/PHPUnit/Fakes/FakeCartService.php index 88abf000c..662e1dad8 100644 --- a/tests/PHPUnit/Fakes/FakeCartService.php +++ b/tests/PHPUnit/Fakes/FakeCartService.php @@ -73,4 +73,10 @@ public function updatePaymentMethod(SalesChannelContext $context, string $paymen { // TODO: Implement updatePaymentMethod() method. } + + public function persistCart(Cart $cart, SalesChannelContext $context): Cart + { + // TODO: Implement persistCart() method. + } + } diff --git a/tests/PHPUnit/Fakes/FakeCustomerService.php b/tests/PHPUnit/Fakes/FakeCustomerService.php index a4232a72f..f17d01b6f 100644 --- a/tests/PHPUnit/Fakes/FakeCustomerService.php +++ b/tests/PHPUnit/Fakes/FakeCustomerService.php @@ -5,6 +5,7 @@ use Exception; use Kiener\MolliePayments\Service\CustomerServiceInterface; +use Kiener\MolliePayments\Struct\Address\AddressStruct; use Kiener\MolliePayments\Struct\CustomerStruct; use Kiener\MolliePayments\Struct\Mandate\MandateCollection; use Shopware\Core\Checkout\Customer\CustomerEntity; @@ -28,7 +29,7 @@ public function __construct(bool $throwException = false) $this->throwException = $throwException; } - public function customerLogin(CustomerEntity $customer, SalesChannelContext $context): ?string + public function loginCustomer(CustomerEntity $customer, SalesChannelContext $context): ?string { return null; } @@ -83,8 +84,7 @@ public function getAddressArray($address, CustomerEntity $customer): array return []; } - public function createApplePayDirectCustomerIfNotExists(string $firstname, string $lastname, string $email, string $phone, string $street, string $zipCode, string $city, string $countryISO2,int $acceptedDataProtection, SalesChannelContext $context): ?CustomerEntity - { + public function createGuestAccount(AddressStruct $shippingAddress, string $paymentMethodId, SalesChannelContext $context,?int $acceptedDataProtection, ?AddressStruct $billingAddress = null): ?CustomerEntity{ return null; } diff --git a/tests/PHPUnit/Service/CustomerServiceTest.php b/tests/PHPUnit/Service/CustomerServiceTest.php index 5f62b13a9..d9a304c77 100644 --- a/tests/PHPUnit/Service/CustomerServiceTest.php +++ b/tests/PHPUnit/Service/CustomerServiceTest.php @@ -4,6 +4,7 @@ use Kiener\MolliePayments\Repository\Country\CountryRepository; use Kiener\MolliePayments\Repository\Customer\CustomerRepositoryInterface; +use Kiener\MolliePayments\Repository\CustomerAddress\CustomerAddressRepositoryInterface; use Kiener\MolliePayments\Repository\Salutation\SalutationRepository; use Kiener\MolliePayments\Service\ConfigService; use Kiener\MolliePayments\Service\CustomerService; @@ -42,11 +43,13 @@ class CustomerServiceTest extends TestCase public function setUp(): void { $this->customerRepository = new FakeCustomerRepository(new CustomerDefinition()); + $this->settingsService = $this->createMock(SettingsService::class); $this->customerService = new CustomerService( $this->createMock(CountryRepository::class), $this->customerRepository, + $this->createMock(CustomerAddressRepositoryInterface::class), $this->createMock(Customer::class), $this->createMock(EventDispatcherInterface::class), new NullLogger(), diff --git a/tests/PHPUnit/Service/PaymentMethodServiceTest.php b/tests/PHPUnit/Service/PaymentMethodServiceTest.php index 2f8a32973..3d32a2720 100644 --- a/tests/PHPUnit/Service/PaymentMethodServiceTest.php +++ b/tests/PHPUnit/Service/PaymentMethodServiceTest.php @@ -22,6 +22,7 @@ use Kiener\MolliePayments\Handler\Method\KlarnaPayNowPayment; use Kiener\MolliePayments\Handler\Method\KlarnaSliceItPayment; use Kiener\MolliePayments\Handler\Method\MyBankPayment; +use Kiener\MolliePayments\Handler\Method\PayPalExpressPayment; use Kiener\MolliePayments\Handler\Method\PayconiqPayment; use Kiener\MolliePayments\Handler\Method\PayPalPayment; use Kiener\MolliePayments\Handler\Method\PaySafeCardPayment; @@ -36,6 +37,7 @@ use Kiener\MolliePayments\Repository\Media\MediaRepositoryInterface; use Kiener\MolliePayments\Repository\PaymentMethod\PaymentMethodRepositoryInterface; use Kiener\MolliePayments\Service\PaymentMethodService; +use Kiener\MolliePayments\Service\PayPalExpressConfig; use MolliePayments\Tests\Fakes\FakeHttpClient; use MolliePayments\Tests\Fakes\Repositories\FakeMediaRepository; use MolliePayments\Tests\Fakes\Repositories\FakePaymentMethodRepository; @@ -90,7 +92,8 @@ protected function setUp(): void $this->mediaRepository, $this->paymentMethodRepository, $this->createMock(PluginIdProvider::class), - new FakeHttpClient() + new FakeHttpClient(), + new PayPalExpressConfig(1), ); } @@ -124,14 +127,10 @@ public function testSupportedMethods(): void GiftCardPayment::class, iDealPayment::class, KbcPayment::class, - KlarnaPayLaterPayment::class, - KlarnaPayNowPayment::class, - KlarnaSliceItPayment::class, KlarnaOnePayment::class, PayPalPayment::class, PaySafeCardPayment::class, Przelewy24Payment::class, - SofortPayment::class, VoucherPayment::class, In3Payment::class, PosPayment::class, @@ -144,6 +143,7 @@ public function testSupportedMethods(): void PayconiqPayment::class, RivertyPayment::class, SatispayPayment::class, + PayPalExpressPayment::class, ]; $handlers = $this->paymentMethodService->getPaymentHandlers(); diff --git a/vendor_manual/makefile b/vendor_manual/makefile index 4e1d8209e..880e6a673 100644 --- a/vendor_manual/makefile +++ b/vendor_manual/makefile @@ -1,5 +1,6 @@ -MOLLIE_PHP_VERSION:=2.61.0 +#MOLLIE_PHP_VERSION:=v2.61.0 +MOLLIE_PHP_VERSION:=feature/add-sessions help: @@ -9,7 +10,7 @@ help: install: ## Installs all production dependencies rm -rf mollie - git clone -b v$(MOLLIE_PHP_VERSION) https://github.com/mollie/mollie-api-php.git mollie/mollie-api-php + git clone -b $(MOLLIE_PHP_VERSION) https://github.com/mollie/mollie-api-php.git mollie/mollie-api-php rm -rf mollie/mollie-api-php/.git rm -rf mollie/mollie-api-php/.github rm -rf mollie/mollie-api-php/.gitattributes diff --git a/vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php b/vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php deleted file mode 100644 index f79cca01d..000000000 --- a/vendor_manual/mollie/mollie-api-php/.php-cs-fixer.dist.php +++ /dev/null @@ -1,37 +0,0 @@ -in([ - __DIR__ . '/src', - __DIR__ . '/examples', - __DIR__ . '/tests', - ]) - ->name('*.php') - ->notPath('bootstrap/*') - ->notPath('storage/*') - ->notPath('vendor') - ->ignoreDotFiles(true) - ->ignoreVCS(true); - -return (new PhpCsFixer\Config()) - ->setRules([ - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => ['sort_algorithm' => 'alpha'], - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => true, - 'trailing_comma_in_multiline' => true, - 'phpdoc_scalar' => true, - 'unary_operator_spaces' => true, - 'binary_operator_spaces' => true, - 'blank_line_before_statement' => [ - 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], - ], - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_var_without_name' => true, - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - 'keep_multiple_spaces_after_comma' => true, - ], - 'single_trait_insert_per_statement' => true, - ]) - ->setFinder($finder); diff --git a/vendor_manual/mollie/mollie-api-php/README.md b/vendor_manual/mollie/mollie-api-php/README.md index 8f9cb7d71..faa1275e3 100644 --- a/vendor_manual/mollie/mollie-api-php/README.md +++ b/vendor_manual/mollie/mollie-api-php/README.md @@ -18,7 +18,7 @@ To use the Mollie API client, the following things are required: + Get yourself a free [Mollie account](https://www.mollie.com/signup). No sign up costs. + Now you're ready to use the Mollie API client in test mode. + Follow [a few steps](https://www.mollie.com/dashboard/?modal=onboarding) to enable payment methods in live mode, and let us handle the rest. -+ PHP >= 7.0 ++ PHP >= 7.2 + Up-to-date OpenSSL (or other SSL/TLS toolkit) For leveraging [Mollie Connect](https://docs.mollie.com/oauth/overview) (advanced use cases only), we recommend also installing our [OAuth2 client](https://github.com/mollie/oauth2-mollie-php). @@ -32,7 +32,7 @@ The easiest way to install the Mollie API client is by using [Composer](http://g composer require mollie/mollie-api-php ``` -To work with the most recent API version, ensure that you are using a version of this API client that is equal to or greater than 2.0.0. If you prefer to continue using the v1 API, make sure your client version is below 2.0.0. For guidance on transitioning from v1 to v2, please refer to the [migration notes](https://docs.mollie.com/migrating-v1-to-v2). +To work with the most recent API version, ensure that you are using a version of this API client that is equal to or greater than 2.0.0. If you prefer to continue using the v1 API, make sure your client version is below 2.0.0. For guidance on transitioning from v1 to v2, please refer to the [migration notes](https://docs.mollie.com/docs/migrating-from-v1-to-v2). ### Manual Installation ### If you're not familiar with using composer we've added a ZIP file to the releases containing the API client and all the packages normally installed by composer. diff --git a/vendor_manual/mollie/mollie-api-php/composer.json b/vendor_manual/mollie/mollie-api-php/composer.json index 47040d24b..04def5417 100644 --- a/vendor_manual/mollie/mollie-api-php/composer.json +++ b/vendor_manual/mollie/mollie-api-php/composer.json @@ -57,7 +57,7 @@ "eloquent/liberator": "^2.0||^3.0", "friendsofphp/php-cs-fixer": "^3.0", "guzzlehttp/guzzle": "^6.3 || ^7.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^8.5 || ^9.5" }, "suggest": { diff --git a/vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon b/vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon deleted file mode 100644 index 56cabd08a..000000000 --- a/vendor_manual/mollie/mollie-api-php/phpstan-baseline.neon +++ /dev/null @@ -1,5 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Variable \\$mollie might not be defined\\.$#" - path: examples/* diff --git a/vendor_manual/mollie/mollie-api-php/phpstan.neon b/vendor_manual/mollie/mollie-api-php/phpstan.neon deleted file mode 100644 index ecd56da76..000000000 --- a/vendor_manual/mollie/mollie-api-php/phpstan.neon +++ /dev/null @@ -1,17 +0,0 @@ -parameters: - level: 5 - paths: - - %currentWorkingDirectory%/src - - %currentWorkingDirectory%/tests - - %currentWorkingDirectory%/examples - excludePaths: - - %currentWorkingDirectory%/vendor - ignoreErrors: - - '#Access to an undefined property Eloquent\\Liberator\\LiberatorProxyInterface::\$request#' - - '#Access to protected property Mollie\\Api\\MollieApiClient::\$apiKey#' - - '#Access to protected property Mollie\\Api\\MollieApiClient::\$httpClient#' - - '#Call to an undefined method Mollie\\Api\\HttpAdapter\\MollieHttpAdapterInterface::enableDebugging\(\)#' - - '#Call to an undefined method Mollie\\Api\\HttpAdapter\\MollieHttpAdapterInterface::disableDebugging\(\)#' - treatPhpDocTypesAsCertain: false -includes: - - phpstan-baseline.neon diff --git a/vendor_manual/mollie/mollie-api-php/phpunit.xml b/vendor_manual/mollie/mollie-api-php/phpunit.xml deleted file mode 100644 index eb7a8f95d..000000000 --- a/vendor_manual/mollie/mollie-api-php/phpunit.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - src/ - - - - - - - - tests/ - - diff --git a/vendor_manual/mollie/mollie-api-php/scoper.inc.php b/vendor_manual/mollie/mollie-api-php/scoper.inc.php deleted file mode 100644 index 88317a713..000000000 --- a/vendor_manual/mollie/mollie-api-php/scoper.inc.php +++ /dev/null @@ -1,13 +0,0 @@ - [], // Finder[] - 'patchers' => [], // callable[] - 'whitelist' => [ - 'Mollie\\Api\\*', - ], -]; \ No newline at end of file diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php index fe87e9787..7c34c0a2d 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/EndpointAbstract.php @@ -131,7 +131,7 @@ protected function rest_read($id, array $filters) } /** - * Sends a DELETE request to a single Molle API object. + * Sends a DELETE request to a single Mollie API object. * * @param string $id * @param array $body diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php index d6dee89f3..7e77c167a 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MandateEndpoint.php @@ -122,8 +122,8 @@ public function iteratorFor(Customer $customer, ?string $from = null, ?int $limi /** * @param string $customerId - * @param null $from - * @param null $limit + * @param string|null $from + * @param int|null $limit * @param array $parameters * * @return \Mollie\Api\Resources\MandateCollection diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php new file mode 100644 index 000000000..2cb661b91 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/MethodIssuerEndpoint.php @@ -0,0 +1,85 @@ +profileId = $profileId; + $this->methodId = $methodId; + $this->issuerId = $issuerId; + + $response = $this->rest_create([], []); + + $this->resetResourceIds(); + + return $response; + } + + public function disable(string $profileId, string $methodId, string $issuerId) + { + $this->profileId = $profileId; + $this->methodId = $methodId; + + return $this->rest_delete($issuerId); + } + + protected function resetResourceIds() + { + $this->profileId = null; + $this->methodId = null; + $this->issuerId = null; + } + + /** + * @return string + * @throws ApiException + */ + public function getResourcePath() + { + if (! $this->profileId) { + throw new ApiException("No profileId provided."); + } + + if (! $this->methodId) { + throw new ApiException("No methodId provided."); + } + + $path = "profiles/{$this->profileId}/methods/{$this->methodId}/issuers"; + + if ($this->issuerId) { + $path .= "/$this->issuerId"; + } + + return $path; + } + + /** + * Get the object that is used by this API endpoint. Every API endpoint uses one type of object. + * + * @return Issuer + */ + protected function getResourceObject() + { + return new Issuer($this->client); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php index 52ba56958..c8cb9a364 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OnboardingEndpoint.php @@ -40,13 +40,13 @@ public function get() } /** + * @deprecated 2023-05-01 For an alternative, see https://docs.mollie.com/reference/create-client-link . * Submit data that will be prefilled in the merchant’s onboarding. * Please note that the data you submit will only be processed when the onboarding status is needs-data. * * Information that the merchant has entered in their dashboard will not be overwritten. * - * Will throw a ApiException if the resource cannot be found. - * + * Will throw an ApiException if the resource cannot be found. * @throws ApiException */ public function submit(array $parameters = []) diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php index d50c9e3b4..ac4edf3dc 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/OrderRefundEndpoint.php @@ -66,4 +66,28 @@ public function createForId($orderId, array $data, array $filters = []) return parent::rest_create($data, $filters); } + + /** + * @param $orderId + * @param array $parameters + * @return RefundCollection + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function pageForId($orderId, array $parameters = []) + { + $this->parentId = $orderId; + + return parent::rest_list(null, null, $parameters); + } + + /** + * @param \Mollie\Api\Resources\Order $order + * @param array $parameters + * @return \Mollie\Api\Resources\RefundCollection + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function pageFor(Order $order, array $parameters = []) + { + return $this->pageForId($order->id, $parameters); + } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php index f27d09ddf..9a19e0dcb 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkEndpoint.php @@ -4,7 +4,6 @@ use Mollie\Api\Exceptions\ApiException; use Mollie\Api\Resources\LazyCollection; -use Mollie\Api\Resources\Payment; use Mollie\Api\Resources\PaymentLink; use Mollie\Api\Resources\PaymentLinkCollection; @@ -17,6 +16,40 @@ class PaymentLinkEndpoint extends CollectionEndpointAbstract */ public const RESOURCE_ID_PREFIX = 'pl_'; + /** + * Update a Payment Link. + * + * @param string $paymentLinkId + * @param array $data + * @return PaymentLink + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function update(string $paymentLinkId, array $data) + { + if (empty($paymentLinkId) || strpos($paymentLinkId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid payment ID: '{$paymentLinkId}'. A Payment Link ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + return $this->rest_update($paymentLinkId, $data); + } + + /** + * Delete a Payment Link. + * + * @param string $paymentLinkId + * @param array $data + * @return void + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function delete(string $paymentLinkId, array $data = []) + { + if (empty($paymentLinkId) || strpos($paymentLinkId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid payment ID: '{$paymentLinkId}'. A Payment Link ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + $this->rest_delete($paymentLinkId, $data); + } + /** * @return PaymentLink */ diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php new file mode 100644 index 000000000..d118b3666 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentLinkPaymentEndpoint.php @@ -0,0 +1,93 @@ +client, $count, $_links); + } + + /** + * @inheritDoc + */ + protected function getResourceObject() + { + return new Payment($this->client); + } + + public function pageForId(string $paymentLinkId, string $from = null, int $limit = null, array $filters = []) + { + $this->parentId = $paymentLinkId; + + return $this->rest_list($from, $limit, $filters); + } + + public function pageFor(PaymentLink $paymentLink, string $from = null, int $limit = null, array $filters = []) + { + return $this->pageForId($paymentLink->id, $from, $limit, $filters); + } + + /** + * Create an iterator for iterating over payments associated with the provided Payment Link id, retrieved from Mollie. + * + * @param string $paymentLinkId + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit + * @param array $parameters + * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). + * + * @return LazyCollection + */ + public function iteratorForId( + string $paymentLinkId, + ?string $from = null, + ?int $limit = null, + array $parameters = [], + bool $iterateBackwards = false + ): LazyCollection { + $this->parentId = $paymentLinkId; + + return $this->rest_iterator($from, $limit, $parameters, $iterateBackwards); + } + + /** + * Create an iterator for iterating over payments associated with the provided Payment Link object, retrieved from Mollie. + * + * @param PaymentLink $paymentLink + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit + * @param array $parameters + * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). + * + * @return LazyCollection + */ + public function iteratorFor( + PaymentLink $paymentLink, + ?string $from = null, + ?int $limit = null, + array $parameters = [], + bool $iterateBackwards = false + ): LazyCollection { + return $this->iteratorForId( + $paymentLink->id, + $from, + $limit, + $parameters, + $iterateBackwards + ); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php index 68524ec8c..6110cdb57 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/PaymentRefundEndpoint.php @@ -78,8 +78,8 @@ public function listFor(Payment $payment, array $parameters = []) * Create an iterator for iterating over refunds for the given payment, retrieved from Mollie. * * @param Payment $payment - * @param string $from The first resource ID you want to include in your list. - * @param int $limit + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit * @param array $parameters * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). * @@ -108,8 +108,8 @@ public function listForId($paymentId, array $parameters = []) * Create an iterator for iterating over refunds for the given payment id, retrieved from Mollie. * * @param string $paymentId - * @param string $from The first resource ID you want to include in your list. - * @param int $limit + * @param string|null $from The first resource ID you want to include in your list. + * @param int|null $limit * @param array $parameters * @param bool $iterateBackwards Set to true for reverse order iteration (default is false). * diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php new file mode 100644 index 000000000..8f2d72ee8 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SessionEndpoint.php @@ -0,0 +1,139 @@ +client); + } + + /** + * Get the collection object that is used by this API endpoint. Every API + * endpoint uses one type of collection object. + * + * @param int $count + * @param \stdClass $_links + * + * @return SessionCollection + */ + protected function getResourceCollectionObject($count, $_links) + { + return new SessionCollection($this->client, $count, $_links); + } + + /** + * Creates a session in Mollie. + * + * @param array $data An array containing details on the session. + * @param array $filters + * + * @return Session + * @throws ApiException + */ + public function create(array $data = [], array $filters = []) + { + return $this->rest_create($data, $filters); + } + + /** + * Update a specific Session resource + * + * Will throw a ApiException if the resource id is invalid or the resource cannot be found. + * + * @param string $resourceId + * + * @param array $data + * @return Session + * @throws ApiException + */ + public function update($resourceId, array $data = []) + { + if (empty($resourceId) || strpos($resourceId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid session ID: '{$resourceId}'. A session ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + return parent::rest_update($resourceId, $data); + } + + /** + * Retrieve a single session from Mollie. + * + * Will throw a ApiException if the resource id is invalid or the resource cannot + * be found. + * + * @param array $parameters + * @return Session + * @throws ApiException + */ + public function get($resourceId, array $parameters = []) + { + if (empty($resourceId) || strpos($resourceId, self::RESOURCE_ID_PREFIX) !== 0) { + throw new ApiException("Invalid session ID: '{$resourceId}'. A session ID should start with '" . self::RESOURCE_ID_PREFIX . "'."); + } + + return parent::rest_read($resourceId, $parameters); + } + + /** + * Cancel the given Session. + * + * @param string $resourceId + * @param array $parameters + * @return Session + * @throws ApiException + */ + public function cancel($resourceId, $parameters = []) + { + return $this->rest_delete($resourceId, $parameters); + } + + /** + * Retrieves a collection of Sessions from Mollie. + * + * @param string $from The first resource ID you want to include in your list. + * @param int $limit + * @param array $parameters + * + * @return SessionCollection + * @throws ApiException + */ + public function page(?string $from = null, ?int $limit = null, array $parameters = []) + { + return $this->rest_list($from, $limit, $parameters); + } + + /** + * Create an iterator for iterating over sessions retrieved from Mollie. + * + * @param string $from The first resource ID you want to include in your list. + * @param int $limit + * @param array $parameters + * @param bool $iterateBackwards Set to true for reverse resource iteration (default is false). + * + * @return LazyCollection + */ + public function iterator(?string $from = null, ?int $limit = null, array $parameters = [], bool $iterateBackwards = false): LazyCollection + { + return $this->rest_iterator($from, $limit, $parameters, $iterateBackwards); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php new file mode 100644 index 000000000..b65e0682a --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Endpoints/SubscriptionPaymentEndpoint.php @@ -0,0 +1,79 @@ +customerId = $customerId; + $this->subscriptionId = $subscriptionId; + + return $this->rest_list($from, $limit, $parameters); + } + + /** + * Get the object that is used by this API endpoint. Every API endpoint uses one type of object. + * + * @return Payment + */ + protected function getResourceObject() + { + return new Payment($this->client); + } + + /** + * Get the collection object that is used by this API endpoint. Every API endpoint uses one type of collection object. + * + * @param int $count + * @param \stdClass $_links + * + * @return PaymentCollection + */ + protected function getResourceCollectionObject($count, $_links) + { + return new PaymentCollection($this->client, $count, $_links); + } + + public function getResourcePath() + { + if (is_null($this->customerId)) { + throw new ApiException('No customerId provided.'); + } + + if (is_null($this->subscriptionId)) { + throw new ApiException('No subscriptionId provided.'); + } + + return "customers/{$this->customerId}/subscriptions/{$this->subscriptionId}/payments"; + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php b/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php index a7a1a35e7..2b62ea320 100644 --- a/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php +++ b/vendor_manual/mollie/mollie-api-php/src/MollieApiClient.php @@ -13,6 +13,7 @@ use Mollie\Api\Endpoints\InvoiceEndpoint; use Mollie\Api\Endpoints\MandateEndpoint; use Mollie\Api\Endpoints\MethodEndpoint; +use Mollie\Api\Endpoints\MethodIssuerEndpoint; use Mollie\Api\Endpoints\OnboardingEndpoint; use Mollie\Api\Endpoints\OrderEndpoint; use Mollie\Api\Endpoints\OrderLineEndpoint; @@ -24,12 +25,14 @@ use Mollie\Api\Endpoints\PaymentChargebackEndpoint; use Mollie\Api\Endpoints\PaymentEndpoint; use Mollie\Api\Endpoints\PaymentLinkEndpoint; +use Mollie\Api\Endpoints\PaymentLinkPaymentEndpoint; use Mollie\Api\Endpoints\PaymentRefundEndpoint; use Mollie\Api\Endpoints\PaymentRouteEndpoint; use Mollie\Api\Endpoints\PermissionEndpoint; use Mollie\Api\Endpoints\ProfileEndpoint; use Mollie\Api\Endpoints\ProfileMethodEndpoint; use Mollie\Api\Endpoints\RefundEndpoint; +use Mollie\Api\Endpoints\SessionEndpoint; use Mollie\Api\Endpoints\SettlementCaptureEndpoint; use Mollie\Api\Endpoints\SettlementChargebackEndpoint; use Mollie\Api\Endpoints\SettlementPaymentEndpoint; @@ -37,6 +40,7 @@ use Mollie\Api\Endpoints\SettlementsEndpoint; use Mollie\Api\Endpoints\ShipmentEndpoint; use Mollie\Api\Endpoints\SubscriptionEndpoint; +use Mollie\Api\Endpoints\SubscriptionPaymentEndpoint; use Mollie\Api\Endpoints\TerminalEndpoint; use Mollie\Api\Endpoints\WalletEndpoint; use Mollie\Api\Exceptions\ApiException; @@ -50,7 +54,7 @@ class MollieApiClient /** * Version of our client. */ - public const CLIENT_VERSION = "2.65.0"; + public const CLIENT_VERSION = "2.73.0"; /** * Endpoint of the remote API. @@ -99,6 +103,11 @@ class MollieApiClient */ public $profileMethods; + /** + * @var \Mollie\Api\Endpoints\MethodIssuerEndpoint + */ + public $methodIssuers; + /** * RESTful Customers resource. * @@ -155,6 +164,13 @@ class MollieApiClient */ public $subscriptions; + /** + * RESTful Subscription Payments resource. + * + * @var SubscriptionPaymentEndpoint + */ + public $subscriptionPayments; + /** * RESTful Mandate resource. * @@ -291,6 +307,13 @@ class MollieApiClient */ public $orderRefunds; + /** + * RESTful Payment Link Payment resource. + * + * @var PaymentLinkPaymentEndpoint + */ + public $paymentLinkPayments; + /** * Manages Payment Links requests * @@ -319,6 +342,27 @@ class MollieApiClient */ public $wallets; + /** + * RESTful Client resource. + * + * @var ClientEndpoint + */ + public $clients; + + /** + * RESTful Client resource. + * + * @var ClientLinkEndpoint + */ + public $clientLinks; + + /** + * RESTful Session resource. + * + * @var SessionEndpoint + */ + public $sessions; + /** * @var string */ @@ -349,20 +393,6 @@ class MollieApiClient */ protected $versionStrings = []; - /** - * RESTful Client resource. - * - * @var ClientEndpoint - */ - public $clients; - - /** - * RESTful Client resource. - * - * @var ClientLinkEndpoint - */ - public $clientLinks; - /** * @param \GuzzleHttp\ClientInterface|\Mollie\Api\HttpAdapter\MollieHttpAdapterInterface|null $httpClient * @param \Mollie\Api\HttpAdapter\MollieHttpAdapterPickerInterface|null $httpAdapterPicker, @@ -384,43 +414,47 @@ public function __construct($httpClient = null, $httpAdapterPicker = null, $idem public function initializeEndpoints() { - $this->payments = new PaymentEndpoint($this); - $this->methods = new MethodEndpoint($this); - $this->profileMethods = new ProfileMethodEndpoint($this); - $this->customers = new CustomerEndpoint($this); - $this->settlements = new SettlementsEndpoint($this); - $this->settlementCaptures = new SettlementCaptureEndpoint($this); - $this->settlementChargebacks = new SettlementChargebackEndpoint($this); - $this->settlementPayments = new SettlementPaymentEndpoint($this); - $this->settlementRefunds = new SettlementRefundEndpoint($this); - $this->subscriptions = new SubscriptionEndpoint($this); - $this->customerPayments = new CustomerPaymentsEndpoint($this); - $this->mandates = new MandateEndpoint($this); - $this->balances = new BalanceEndpoint($this); - $this->balanceTransactions = new BalanceTransactionEndpoint($this); $this->balanceReports = new BalanceReportEndpoint($this); + $this->balanceTransactions = new BalanceTransactionEndpoint($this); + $this->balances = new BalanceEndpoint($this); + $this->chargebacks = new ChargebackEndpoint($this); + $this->clientLinks = new ClientLinkEndpoint($this); + $this->clients = new ClientEndpoint($this); + $this->customerPayments = new CustomerPaymentsEndpoint($this); + $this->customers = new CustomerEndpoint($this); $this->invoices = new InvoiceEndpoint($this); - $this->permissions = new PermissionEndpoint($this); - $this->profiles = new ProfileEndpoint($this); + $this->mandates = new MandateEndpoint($this); + $this->methods = new MethodEndpoint($this); + $this->methodIssuers = new MethodIssuerEndpoint($this); $this->onboarding = new OnboardingEndpoint($this); - $this->organizations = new OrganizationEndpoint($this); - $this->orders = new OrderEndpoint($this); $this->orderLines = new OrderLineEndpoint($this); $this->orderPayments = new OrderPaymentEndpoint($this); $this->orderRefunds = new OrderRefundEndpoint($this); - $this->shipments = new ShipmentEndpoint($this); - $this->refunds = new RefundEndpoint($this); - $this->paymentRefunds = new PaymentRefundEndpoint($this); + $this->orders = new OrderEndpoint($this); + $this->organizationPartners = new OrganizationPartnerEndpoint($this); + $this->organizations = new OrganizationEndpoint($this); $this->paymentCaptures = new PaymentCaptureEndpoint($this); - $this->paymentRoutes = new PaymentRouteEndpoint($this); - $this->chargebacks = new ChargebackEndpoint($this); $this->paymentChargebacks = new PaymentChargebackEndpoint($this); - $this->wallets = new WalletEndpoint($this); + $this->paymentLinkPayments = new PaymentLinkPaymentEndpoint($this); $this->paymentLinks = new PaymentLinkEndpoint($this); + $this->paymentRefunds = new PaymentRefundEndpoint($this); + $this->paymentRoutes = new PaymentRouteEndpoint($this); + $this->payments = new PaymentEndpoint($this); + $this->permissions = new PermissionEndpoint($this); + $this->profileMethods = new ProfileMethodEndpoint($this); + $this->profiles = new ProfileEndpoint($this); + $this->refunds = new RefundEndpoint($this); + $this->settlementCaptures = new SettlementCaptureEndpoint($this); + $this->settlementChargebacks = new SettlementChargebackEndpoint($this); + $this->settlementPayments = new SettlementPaymentEndpoint($this); + $this->settlementRefunds = new SettlementRefundEndpoint($this); + $this->settlements = new SettlementsEndpoint($this); + $this->sessions = new SessionEndpoint($this); + $this->shipments = new ShipmentEndpoint($this); + $this->subscriptionPayments = new SubscriptionPaymentEndpoint($this); + $this->subscriptions = new SubscriptionEndpoint($this); $this->terminals = new TerminalEndpoint($this); - $this->organizationPartners = new OrganizationPartnerEndpoint($this); - $this->clients = new ClientEndpoint($this); - $this->clientLinks = new ClientLinkEndpoint($this); + $this->wallets = new WalletEndpoint($this); } protected function initializeVersionStrings() diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php index 2fc7c4e5e..89cd86209 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Capture.php @@ -24,6 +24,13 @@ class Capture extends BaseResource */ public $mode; + /** + * Status of the capture. + * + * @var string + */ + public $status; + /** * Amount object containing the value and currency * @@ -59,6 +66,15 @@ class Capture extends BaseResource */ public $settlementId; + /** + * Provide any data you like, for example a string or a JSON object. The data will be saved alongside the capture. + * Whenever you fetch the capture, the metadata will be included. + * You can use up to approximately 1kB on this field. + * + * @var \stdClass|mixed|null + */ + public $metadata; + /** * @var string */ diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php index 98fc7443d..bc44daaf2 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Customer.php @@ -6,6 +6,8 @@ class Customer extends BaseResource { + use HasPresetOptions; + /** * Id of the customer. * @@ -221,30 +223,4 @@ public function hasValidMandateForMethod($method) return false; } - - /** - * When accessed by oAuth we want to pass the testmode by default - * - * @return array - */ - private function getPresetOptions() - { - $options = []; - if ($this->client->usesOAuth()) { - $options["testmode"] = $this->mode === "test" ? true : false; - } - - return $options; - } - - /** - * Apply the preset options. - * - * @param array $options - * @return array - */ - private function withPresetOptions(array $options) - { - return array_merge($this->getPresetOptions(), $options); - } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php b/vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php new file mode 100644 index 000000000..8a0e24e21 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/HasPresetOptions.php @@ -0,0 +1,38 @@ +client->usesOAuth()) { + $options["testmode"] = $this->mode === "test" ? true : false; + } + + return $options; + } + + /** + * Apply the preset options. + * + * @param array $options + * @return array + */ + protected function withPresetOptions(array $options) + { + return array_merge($this->getPresetOptions(), $options); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php index 349a2c3df..f579c2aae 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Order.php @@ -3,11 +3,12 @@ namespace Mollie\Api\Resources; use Mollie\Api\Exceptions\ApiException; -use Mollie\Api\MollieApiClient; use Mollie\Api\Types\OrderStatus; class Order extends BaseResource { + use HasPresetOptions; + /** * Id of the order. * @@ -81,7 +82,7 @@ class Order extends BaseResource public $orderNumber; /** - * The person and the address the order is billed to. + * The person and the address the order is shipped to. * * @var \stdClass */ @@ -474,18 +475,7 @@ public function refundAll(array $data = []) */ public function refunds() { - if (! isset($this->_links->refunds->href)) { - return new RefundCollection($this->client, 0, null); - } - - $result = $this->client->performHttpCallToFullUrl(MollieApiClient::HTTP_GET, $this->_links->refunds->href); - - return ResourceFactory::createCursorResourceCollection( - $this->client, - $result->_embedded->refunds, - Refund::class, - $result->_links - ); + return $this->client->orderRefunds->pageFor($this); } /** @@ -541,30 +531,4 @@ public function payments() Payment::class ); } - - /** - * When accessed by oAuth we want to pass the testmode by default - * - * @return array - */ - private function getPresetOptions() - { - $options = []; - if ($this->client->usesOAuth()) { - $options["testmode"] = $this->mode === "test" ? true : false; - } - - return $options; - } - - /** - * Apply the preset options. - * - * @param array $options - * @return array - */ - private function withPresetOptions(array $options) - { - return array_merge($this->getPresetOptions(), $options); - } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php index e56861f19..e912341eb 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Payment.php @@ -9,6 +9,8 @@ class Payment extends BaseResource { + use HasPresetOptions; + /** * Id of the payment (on the Mollie platform). * @@ -144,6 +146,7 @@ class Payment extends BaseResource * * @example "user@mollie.com" * @var string|null + * @deprecated 2024-06-01 The billingEmail field is deprecated. Use the "billingAddress" field instead. */ public $billingEmail; @@ -207,6 +210,27 @@ class Payment extends BaseResource */ public $orderId; + /** + * The lines contain the actual items the customer bought. + * + * @var array|object[]|null + */ + public $lines; + + /** + * The person and the address the order is billed to. + * + * @var \stdClass|null + */ + public $billingAddress; + + /** + * The person and the address the order is shipped to. + * + * @var \stdClass|null + */ + public $shippingAddress; + /** * The settlement ID this payment belongs to. * @@ -729,37 +753,14 @@ public function update() "dueDate" => $this->dueDate, ]; - $result = $this->client->payments->update($this->id, $body); + $result = $this->client->payments->update( + $this->id, + $this->withPresetOptions($body) + ); return ResourceFactory::createFromApiResult($result, new Payment($this->client)); } - /** - * When accessed by oAuth we want to pass the testmode by default - * - * @return array - */ - private function getPresetOptions() - { - $options = []; - if ($this->client->usesOAuth()) { - $options["testmode"] = $this->mode === "test" ? true : false; - } - - return $options; - } - - /** - * Apply the preset options. - * - * @param array $options - * @return array - */ - private function withPresetOptions(array $options) - { - return array_merge($this->getPresetOptions(), $options); - } - /** * The total amount that is already captured for this payment. Only available * when this payment supports captures. diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php b/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php index cd64ed7d9..bef2c9e30 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/PaymentLink.php @@ -124,4 +124,81 @@ public function getCheckoutUrl() return $this->_links->paymentLink->href; } + + /** + * Persist the current local Payment Link state to the Mollie API. + * + * @return mixed|\Mollie\Api\Resources\BaseResource + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function update() + { + $body = $this->withPresetOptions([ + 'description' => $this->description, + 'archived' => $this->archived, + ]); + + $result = $this->client->paymentLinks->update($this->id, $body); + + return ResourceFactory::createFromApiResult($result, new PaymentLink($this->client)); + } + + /** + * Archive this Payment Link. + * + * @return \Mollie\Api\Resources\PaymentLink + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function archive() + { + $data = $this->withPresetOptions([ + 'archived' => true, + ]); + + return $this->client->paymentLinks->update($this->id, $data); + } + + /** + * Retrieve a paginated list of payments associated with this payment link. + * + * @param string|null $from + * @param int|null $limit + * @param array $filters + * @return mixed|\Mollie\Api\Resources\BaseCollection + */ + public function payments(string $from = null, int $limit = null, array $filters = []) + { + return $this->client->paymentLinkPayments->pageFor( + $this, + $from, + $limit, + $this->withPresetOptions($filters) + ); + } + + /** + * When accessed by oAuth we want to pass the testmode by default + * + * @return array + */ + private function getPresetOptions() + { + $options = []; + if ($this->client->usesOAuth()) { + $options["testmode"] = $this->mode === "test"; + } + + return $options; + } + + /** + * Apply the preset options. + * + * @param array $options + * @return array + */ + private function withPresetOptions(array $options) + { + return array_merge($this->getPresetOptions(), $options); + } } diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/Session.php b/vendor_manual/mollie/mollie-api-php/src/Resources/Session.php new file mode 100644 index 000000000..842559b73 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/Session.php @@ -0,0 +1,178 @@ +status === SessionStatus::STATUS_CREATED; + } + + public function isReadyForProcessing() + { + return $this->status === SessionStatus::STATUS_READY_FOR_PROCESSING; + } + + public function isCompleted() + { + return $this->status === SessionStatus::STATUS_COMPLETED; + } + + public function hasFailed() + { + return $this->status === SessionStatus::STATUS_FAILED; + } + + /** + * Saves the session's updatable properties. + * + * @return \Mollie\Api\Resources\Session + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function update() + { + $body = [ + 'billingAddress' => $this->billingAddress, + 'shippingAddress' => $this->shippingAddress, + ]; + + $result = $this->client->sessions->update($this->id, $this->withPresetOptions($body)); + + return ResourceFactory::createFromApiResult($result, new Session($this->client)); + } + + /** + * Cancels this session. + * + * @return Session + * @throws \Mollie\Api\Exceptions\ApiException + */ + public function cancel() + { + return $this->client->sessions->cancel($this->id, $this->getPresetOptions()); + } + + /** + * @return string|null + */ + public function getRedirectUrl() + { + if (empty($this->_links->redirect)) { + return null; + } + + return $this->_links->redirect->href; + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php b/vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php new file mode 100644 index 000000000..b1414c36b --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Resources/SessionCollection.php @@ -0,0 +1,22 @@ +client); + } +} diff --git a/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php b/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php index 512804288..3dba80fa2 100644 --- a/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php +++ b/vendor_manual/mollie/mollie-api-php/src/Types/PaymentMethod.php @@ -14,6 +14,11 @@ class PaymentMethod */ public const APPLEPAY = "applepay"; + /** + * @link https://www.mollie.com/en/payments/bacs + */ + public const BACS = "bacs"; + /** * @link https://www.mollie.com/en/payments/bancomatpay */ @@ -70,10 +75,16 @@ class PaymentMethod public const GIFTCARD = "giftcard"; /** + * @deprecated * @link https://www.mollie.com/en/payments/giropay */ public const GIROPAY = "giropay"; + /** + * @link https://www.mollie.com/en/payments/in3 + */ + public const IN3 = "in3"; + /** * @link https://www.mollie.com/en/payments/ideal */ @@ -116,6 +127,11 @@ class PaymentMethod */ public const MYBANK = "mybank"; + /** + * @link https://www.mollie.com/en/payments/payconiq + */ + public const PAYCONIQ = "payconiq"; + /** * @link https://www.mollie.com/en/payments/paypal */ @@ -127,9 +143,9 @@ class PaymentMethod public const PAYSAFECARD = "paysafecard"; /** - * @link https://www.mollie.com/en/payments/przelewy24 + * @link https://www.mollie.com/en/payments/pay-by-bank */ - public const PRZELEWY24 = 'przelewy24'; + public const PAYBYBANK = "paybybank"; /** * @deprecated @@ -137,20 +153,35 @@ class PaymentMethod */ public const PODIUMCADEAUKAART = "podiumcadeaukaart"; + /** + * @link https://docs.mollie.com/point-of-sale/overview + */ + public const POINT_OF_SALE = "pointofsale"; + + /** + * @link https://www.mollie.com/en/payments/przelewy24 + */ + public const PRZELEWY24 = 'przelewy24'; + + /** + * @link https://www.mollie.com/en/payments/satispay + */ + public const SATISPAY = "satispay"; + /** * @link https://www.mollie.com/en/payments/sofort */ public const SOFORT = "sofort"; /** - * @link https://www.mollie.com/en/payments/in3 + * @link https://www.mollie.com/en/payments/riverty */ - public const IN3 = "in3"; + public const RIVERTY = "riverty"; /** - * @link https://docs.mollie.com/point-of-sale/overview + * @link https://www.mollie.com/en/payments/trustly */ - public const POINT_OF_SALE = "pointofsale"; + public const TRUSTLY = "trustly"; /** * @link https://www.mollie.com/en/payments/twint diff --git a/vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php b/vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php new file mode 100644 index 000000000..8d19b7798 --- /dev/null +++ b/vendor_manual/mollie/mollie-api-php/src/Types/SessionStatus.php @@ -0,0 +1,26 @@ +