From 32db09110805687da4efcaade080c7a818a1b255 Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 15:53:32 +0100 Subject: [PATCH 01/11] POC - Added feature flag --- src/Resources/app/administration/src/main.ts | 15 ++++++++++++++- src/Resources/config/packages/feature.yaml | 8 ++++++++ src/Resources/config/services/setting.xml | 8 ++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Resources/config/packages/feature.yaml diff --git a/src/Resources/app/administration/src/main.ts b/src/Resources/app/administration/src/main.ts index fcd80df91..43bdb06c2 100644 --- a/src/Resources/app/administration/src/main.ts +++ b/src/Resources/app/administration/src/main.ts @@ -6,7 +6,6 @@ import './mixin/swag-paypal-pos-catch-error.mixin'; import './mixin/swag-paypal-pos-log-label.mixin'; import './module/extension'; -import './module/swag-paypal'; import './module/swag-paypal-disputes'; import './module/swag-paypal-payment'; import './module/swag-paypal-pos'; @@ -15,6 +14,20 @@ import './init/api-service.init'; import './init/translation.init'; import './init/svg-icons.init'; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const bootPromise = window.Shopware ? Shopware.Plugin.addBootPromise() : () => {}; + +(async () => { + if (Shopware.Feature.isActive('PAYPAL_SETTINGS_TWEAKS')) { + } else { + await import('./module/swag-paypal'); + } + + // @ts-expect-error - bootPromise has a wrong doc type + bootPromise(); +})(); + ui.module.payment.overviewCard.add({ positionId: 'swag-paypal-overview-card-before', component: 'swag-paypal-overview-card', diff --git a/src/Resources/config/packages/feature.yaml b/src/Resources/config/packages/feature.yaml new file mode 100644 index 000000000..e6d9edac3 --- /dev/null +++ b/src/Resources/config/packages/feature.yaml @@ -0,0 +1,8 @@ +shopware: + feature: + flags: + - name: PAYPAL_SETTINGS_TWEAKS + default: true + major: false + toggleable: true + description: 'PayPal Admin rewrite. Contains a lot of small improvements, but could cause problem with plugins extending PayPal.' diff --git a/src/Resources/config/services/setting.xml b/src/Resources/config/services/setting.xml index c295c56b1..75b008652 100644 --- a/src/Resources/config/services/setting.xml +++ b/src/Resources/config/services/setting.xml @@ -8,6 +8,8 @@ + + @@ -37,5 +39,11 @@ + + + + + + From ceb00403d011fb6dfa041309f966b95df2f966fa Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 15:57:17 +0100 Subject: [PATCH 02/11] POC - Added settings save route --- src/Resources/config/services/webhook.xml | 2 + src/Setting/Service/SettingsSaver.php | 131 ++++++++++++++++++ .../Service/SettingsSaverInterface.php | 17 +++ src/Setting/Settings.php | 17 +++ src/Setting/SettingsController.php | 33 +++++ .../Struct/SettingsInformationStruct.php | 91 ++++++++++++ .../Registration/WebhookSubscriber.php | 60 ++++++-- .../WebhookSystemConfigController.php | 12 ++ .../WebhookSystemConfigHelper.php | 2 +- tests/Setting/SettingsControllerTest.php | 40 ++++-- .../Registration/WebhookSubscriberTest.php | 10 +- .../WebhookSystemConfigControllerTest.php | 2 + 12 files changed, 388 insertions(+), 29 deletions(-) create mode 100644 src/Setting/Service/SettingsSaver.php create mode 100644 src/Setting/Service/SettingsSaverInterface.php create mode 100644 src/Setting/Struct/SettingsInformationStruct.php diff --git a/src/Resources/config/services/webhook.xml b/src/Resources/config/services/webhook.xml index 583b87d25..555e4c864 100644 --- a/src/Resources/config/services/webhook.xml +++ b/src/Resources/config/services/webhook.xml @@ -123,6 +123,8 @@ + + diff --git a/src/Setting/Service/SettingsSaver.php b/src/Setting/Service/SettingsSaver.php new file mode 100644 index 000000000..5582487d5 --- /dev/null +++ b/src/Setting/Service/SettingsSaver.php @@ -0,0 +1,131 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Swag\PayPal\Setting\Service; + +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\System\SystemConfig\SystemConfigService; +use Swag\PayPal\Setting\Settings; +use Swag\PayPal\Setting\Struct\SettingsInformationStruct; +use Swag\PayPal\Webhook\Registration\WebhookSystemConfigHelper; + +#[Package('checkout')] +class SettingsSaver implements SettingsSaverInterface +{ + /** + * @internal + */ + public function __construct( + private readonly SystemConfigService $systemConfigService, + private readonly ApiCredentialService $apiCredentialService, + private readonly WebhookSystemConfigHelper $webhookSystemConfigHelper, + ) { + } + + /** + * @param array $settings + */ + public function save(array $settings, ?string $salesChannelId = null): SettingsInformationStruct + { + $information = new SettingsInformationStruct(); + + $old = $this->getCredentials($salesChannelId); + + $information->setLiveCredentialsChanged( + $this->credentialsChanged($settings, $old, Settings::LIVE_CREDENTIAL_KEYS), + ); + $information->setSandboxCredentialsChanged( + $this->credentialsChanged($settings, $old, Settings::SANDBOX_CREDENTIAL_KEYS), + ); + + if ($information->getLiveCredentialsChanged()) { + $valid = $this->testCredentials( + $settings[Settings::CLIENT_ID], + $settings[Settings::CLIENT_SECRET], + $settings[Settings::MERCHANT_PAYER_ID], + false, + ); + + $information->setLiveCredentialsValid($valid); + } + + if ($information->getSandboxCredentialsChanged()) { + $valid = $this->testCredentials( + $settings[Settings::CLIENT_ID_SANDBOX], + $settings[Settings::CLIENT_SECRET_SANDBOX], + $settings[Settings::MERCHANT_PAYER_ID_SANDBOX], + true, + ); + + $information->setSandboxCredentialsValid($valid); + } + + if ($information->getLiveCredentialsValid() || $information->getSandboxCredentialsValid()) { + $webhookErrors = $this->webhookSystemConfigHelper->checkWebhookBefore([$salesChannelId => $settings]); + } + + $this->systemConfigService->setMultiple($settings, $salesChannelId); + + if ($information->getLiveCredentialsValid() || $information->getSandboxCredentialsValid()) { + $webhookErrors = \array_merge( + $webhookErrors ?? [], + $this->webhookSystemConfigHelper->checkWebhookAfter([$salesChannelId]), + ); + } + + $information->setWebhookErrors(\array_map(static fn (\Throwable $e) => $e->getMessage(), $webhookErrors ?? [])); + + return $information; + } + + protected function testCredentials(string $clientId, string $clientSecret, string $merchantId, bool $sandbox): bool + { + try { + return $this->apiCredentialService->testApiCredentials($clientId, $clientSecret, $sandbox, $merchantId); + } catch (\Exception $e) { + return false; + } + } + + protected function getCredentials(?string $salesChannelId): array + { + return \array_combine( + Settings::CREDENTIAL_KEYS, + \array_map( + fn (string $key) => $this->systemConfigService->get($key, $salesChannelId), + Settings::CREDENTIAL_KEYS, + ), + ); + } + + /** + * @param array $new + * @param array $old + * @param list $keys + */ + protected function credentialsChanged(array $new, array $old, array $keys): bool + { + $new = $this->filterSettings($new, $keys); + $old = $this->filterSettings($old, $keys); + + return \count(\array_diff($new, $old)) > 0; + } + + /** + * @param array $kvs + * @param list $keys + * + * @return array + */ + protected function filterSettings(array $kvs, array $keys): array + { + return \array_combine($keys, \array_map( + static fn (string $key) => $kvs[$key] ?? null, + $keys, + )); + } +} diff --git a/src/Setting/Service/SettingsSaverInterface.php b/src/Setting/Service/SettingsSaverInterface.php new file mode 100644 index 000000000..0872f7681 --- /dev/null +++ b/src/Setting/Service/SettingsSaverInterface.php @@ -0,0 +1,17 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Swag\PayPal\Setting\Service; + +use Shopware\Core\Framework\Log\Package; +use Swag\PayPal\Setting\Struct\SettingsInformationStruct; + +#[Package('checkout')] +interface SettingsSaverInterface +{ + public function save(array $settings, ?string $salesChannelId = null): SettingsInformationStruct; +} diff --git a/src/Setting/Settings.php b/src/Setting/Settings.php index 398178244..450c2cdf3 100644 --- a/src/Setting/Settings.php +++ b/src/Setting/Settings.php @@ -134,6 +134,23 @@ final class Settings self::MERCHANT_LOCATION_OTHER, ]; + public const LIVE_CREDENTIAL_KEYS = [ + Settings::CLIENT_ID, + Settings::CLIENT_SECRET, + Settings::MERCHANT_PAYER_ID, + ]; + + public const SANDBOX_CREDENTIAL_KEYS = [ + Settings::CLIENT_ID_SANDBOX, + Settings::CLIENT_SECRET_SANDBOX, + Settings::MERCHANT_PAYER_ID_SANDBOX, + ]; + + public const CREDENTIAL_KEYS = [ + ...self::LIVE_CREDENTIAL_KEYS, + ...self::SANDBOX_CREDENTIAL_KEYS, + ]; + private function __construct() { } diff --git a/src/Setting/SettingsController.php b/src/Setting/SettingsController.php index 9168c7a23..7169689fd 100644 --- a/src/Setting/SettingsController.php +++ b/src/Setting/SettingsController.php @@ -12,9 +12,12 @@ use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Routing\RoutingException; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\System\SystemConfig\Validation\SystemConfigValidator; use Swag\PayPal\Setting\Service\ApiCredentialServiceInterface; use Swag\PayPal\Setting\Service\MerchantIntegrationsService; +use Swag\PayPal\Setting\Service\SettingsSaverInterface; use Swag\PayPal\Setting\Struct\MerchantInformationStruct; +use Swag\PayPal\Setting\Struct\SettingsInformationStruct; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -31,6 +34,8 @@ class SettingsController extends AbstractController public function __construct( private readonly ApiCredentialServiceInterface $apiCredentialService, private readonly MerchantIntegrationsService $merchantIntegrationsService, + private readonly SystemConfigValidator $systemConfigValidator, + private readonly SettingsSaverInterface $settingsSaver, ) { } @@ -153,4 +158,32 @@ public function getMerchantInformation(Request $request, Context $context): Json return new JsonResponse($response); } + + #[OA\Get( + path: '/api/_action/paypal/save-settings', + operationId: 'saveSettings', + tags: ['Admin Api', 'PayPal'], + responses: [new OA\Response( + response: Response::HTTP_OK, + description: 'Returns information about the saved settings', + content: new OA\JsonContent(type: 'object', additionalProperties: new OA\AdditionalProperties(ref: SettingsInformationStruct::class)) + )] + )] + #[Route(path: '/api/_action/paypal/save-settings', name: 'api.action.paypal.settings.save', methods: ['POST'], defaults: ['_acl' => ['swag_paypal.editor', 'system_config:update', 'system_config:create', 'system_config:delete']])] + public function saveSettings(RequestDataBag $data, Context $context): JsonResponse + { + $this->systemConfigValidator->validate($data->all(), $context); + + $information = []; + + /** + * @var string $salesChannel + * @var array $kvs + */ + foreach ($data->all() as $salesChannel => $kvs) { + $information[$salesChannel] = $this->settingsSaver->save($kvs, $salesChannel === 'null' ? null : $salesChannel); + } + + return new JsonResponse($information); + } } diff --git a/src/Setting/Struct/SettingsInformationStruct.php b/src/Setting/Struct/SettingsInformationStruct.php new file mode 100644 index 000000000..e7df3fb5d --- /dev/null +++ b/src/Setting/Struct/SettingsInformationStruct.php @@ -0,0 +1,91 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Swag\PayPal\Setting\Struct; + +use OpenApi\Attributes as OA; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Struct\Struct; + +#[OA\Schema(schema: 'swag_paypal_setting_settings_information')] +#[Package('checkout')] +class SettingsInformationStruct extends Struct +{ + #[OA\Property(type: 'boolean')] + protected bool $sandboxCredentialsChanged = false; + + #[OA\Property(type: 'boolean', nullable: true)] + protected ?bool $sandboxCredentialsValid = null; + + #[OA\Property(type: 'boolean')] + protected bool $liveCredentialsChanged = false; + + #[OA\Property(type: 'boolean', nullable: true)] + protected ?bool $liveCredentialsValid = null; + + /** + * @var array + */ + #[OA\Property(type: 'array', items: new OA\Items(type: 'string'))] + protected array $webhookErrors = []; + + public function getSandboxCredentialsChanged(): bool + { + return $this->sandboxCredentialsChanged; + } + + public function setSandboxCredentialsChanged(bool $sandboxCredentialsChanged): void + { + $this->sandboxCredentialsChanged = $sandboxCredentialsChanged; + } + + public function getSandboxCredentialsValid(): ?bool + { + return $this->sandboxCredentialsValid; + } + + public function setSandboxCredentialsValid(?bool $sandboxCredentialsValid): void + { + $this->sandboxCredentialsValid = $sandboxCredentialsValid; + } + + public function getLiveCredentialsChanged(): bool + { + return $this->liveCredentialsChanged; + } + + public function setLiveCredentialsChanged(bool $liveCredentialsChanged): void + { + $this->liveCredentialsChanged = $liveCredentialsChanged; + } + + public function getLiveCredentialsValid(): ?bool + { + return $this->liveCredentialsValid; + } + + public function setLiveCredentialsValid(?bool $liveCredentialsValid): void + { + $this->liveCredentialsValid = $liveCredentialsValid; + } + + /** + * @return array + */ + public function getWebhookErrors(): array + { + return $this->webhookErrors; + } + + /** + * @param array $webhookErrors + */ + public function setWebhookErrors(array $webhookErrors): void + { + $this->webhookErrors = $webhookErrors; + } +} diff --git a/src/Webhook/Registration/WebhookSubscriber.php b/src/Webhook/Registration/WebhookSubscriber.php index c40fa17ea..560bfe836 100644 --- a/src/Webhook/Registration/WebhookSubscriber.php +++ b/src/Webhook/Registration/WebhookSubscriber.php @@ -9,12 +9,17 @@ use Psr\Log\LoggerInterface; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent; +use Shopware\Core\Framework\Feature; use Shopware\Core\Framework\Log\Package; use Shopware\Core\System\SalesChannel\SalesChannelEvents; +use Shopware\Core\System\SystemConfig\Event\BeforeSystemConfigMultipleChangedEvent; +use Shopware\Core\System\SystemConfig\Event\SystemConfigMultipleChangedEvent; use Shopware\Core\System\SystemConfig\SystemConfigService; +use Swag\PayPal\Setting\Service\SettingsSaver; use Swag\PayPal\Setting\Settings; use Swag\PayPal\Webhook\WebhookServiceInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; /** * @internal @@ -22,26 +27,21 @@ #[Package('checkout')] class WebhookSubscriber implements EventSubscriberInterface { - private LoggerInterface $logger; - - private SystemConfigService $systemConfigService; - - private WebhookServiceInterface $webhookService; - public function __construct( - LoggerInterface $logger, - SystemConfigService $systemConfigService, - WebhookServiceInterface $webhookService, + private readonly LoggerInterface $logger, + private readonly SystemConfigService $systemConfigService, + private readonly WebhookServiceInterface $webhookService, + private readonly WebhookSystemConfigHelper $webhookSystemConfigHelper, + private readonly RequestStack $requestStack, ) { - $this->logger = $logger; - $this->systemConfigService = $systemConfigService; - $this->webhookService = $webhookService; } public static function getSubscribedEvents(): array { return [ SalesChannelEvents::SALES_CHANNEL_DELETED => 'removeSalesChannelWebhookConfiguration', + BeforeSystemConfigMultipleChangedEvent::class => 'checkWebhookBefore', + SystemConfigMultipleChangedEvent::class => 'checkWebhookAfter', ]; } @@ -60,4 +60,40 @@ public function removeSalesChannelWebhookConfiguration(EntityDeletedEvent $event } } } + + /** + * system-config should be written by {@see SettingsSaver} only, which checks the webhook on its own. + * Just in case new/changed credentials will be saved via the normal system config save route. + */ + public function checkWebhookBefore(BeforeSystemConfigMultipleChangedEvent $event): void + { + $routeName = (string) $this->requestStack->getMainRequest()?->attributes->getString('_route'); + + if (!\str_contains($routeName, 'api.action.core.save.system-config')) { + return; + } + + if (Feature::isActive('PAYPAL_SETTINGS_TWEAKS')) { + /** @var array> $config */ + $config = $event->getConfig(); + $this->webhookSystemConfigHelper->checkWebhookBefore($config); + } + } + + /** + * system-config should be written by {@see SettingsSaver} only, which checks the webhook on its own. + * Just in case new/changed credentials will be saved via the normal system config save route. + */ + public function checkWebhookAfter(SystemConfigMultipleChangedEvent $event): void + { + $routeName = (string) $this->requestStack->getMainRequest()?->attributes->getString('_route'); + + if (!\str_contains($routeName, 'api.action.core.save.system-config')) { + return; + } + + if (Feature::isActive('PAYPAL_SETTINGS_TWEAKS')) { + $this->webhookSystemConfigHelper->checkWebhookAfter(\array_keys($event->getConfig())); + } + } } diff --git a/src/Webhook/Registration/WebhookSystemConfigController.php b/src/Webhook/Registration/WebhookSystemConfigController.php index fa25e9ecd..65f60ef48 100644 --- a/src/Webhook/Registration/WebhookSystemConfigController.php +++ b/src/Webhook/Registration/WebhookSystemConfigController.php @@ -8,6 +8,7 @@ namespace Swag\PayPal\Webhook\Registration; use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\Feature; use Shopware\Core\Framework\Log\Package; use Shopware\Core\System\SystemConfig\Api\SystemConfigController; use Shopware\Core\System\SystemConfig\Service\ConfigurationService; @@ -17,6 +18,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Attribute\Route; +/** + * @deprecated tag:v10.0.0 - Will be removed. Use {@see SettingsController::saveSettings} instead + */ #[Package('checkout')] #[Route(defaults: ['_routeScope' => ['api']])] class WebhookSystemConfigController extends SystemConfigController @@ -40,6 +44,10 @@ public function __construct( public function saveConfiguration(Request $request): JsonResponse { + if (Feature::isActive('PAYPAL_SETTINGS_TWEAKS')) { + return parent::saveConfiguration($request); + } + $salesChannelId = $request->query->get('salesChannelId'); if (!\is_string($salesChannelId) || $salesChannelId === '') { $salesChannelId = 'null'; @@ -63,6 +71,10 @@ public function saveConfiguration(Request $request): JsonResponse public function batchSaveConfiguration(Request $request, Context $context): JsonResponse { + if (Feature::isActive('PAYPAL_SETTINGS_TWEAKS')) { + return parent::batchSaveConfiguration($request, $context); + } + /** @var array> $data */ $data = $request->request->all(); $errors = $this->webhookSystemConfigHelper->checkWebhookBefore($data); diff --git a/src/Webhook/Registration/WebhookSystemConfigHelper.php b/src/Webhook/Registration/WebhookSystemConfigHelper.php index 08a38d8e7..7f9ccaac8 100644 --- a/src/Webhook/Registration/WebhookSystemConfigHelper.php +++ b/src/Webhook/Registration/WebhookSystemConfigHelper.php @@ -93,7 +93,7 @@ public function checkWebhookBefore(array $newData): array } /** - * @param string[] $salesChannelIds + * @param array $salesChannelIds * * @return \Throwable[] */ diff --git a/tests/Setting/SettingsControllerTest.php b/tests/Setting/SettingsControllerTest.php index 7b66cbe5b..31b1d4298 100644 --- a/tests/Setting/SettingsControllerTest.php +++ b/tests/Setting/SettingsControllerTest.php @@ -11,6 +11,7 @@ use Psr\Log\NullLogger; use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour; +use Shopware\Core\System\SystemConfig\Validation\SystemConfigValidator; use Swag\PayPal\RestApi\Exception\PayPalApiException; use Swag\PayPal\RestApi\V1\Resource\CredentialsResource; use Swag\PayPal\RestApi\V1\Resource\MerchantIntegrationsResource; @@ -19,6 +20,7 @@ use Swag\PayPal\Setting\Service\ApiCredentialService; use Swag\PayPal\Setting\Service\CredentialsUtil; use Swag\PayPal\Setting\Service\MerchantIntegrationsService; +use Swag\PayPal\Setting\Service\SettingsSaver; use Swag\PayPal\Setting\Service\SettingsValidationService; use Swag\PayPal\Setting\SettingsController; use Swag\PayPal\Test\Helper\ConstantsForTesting; @@ -28,6 +30,7 @@ use Swag\PayPal\Test\Mock\PayPal\Client\PayPalClientFactoryMock; use Swag\PayPal\Test\Mock\PayPal\Client\TokenClientFactoryMock; use Swag\PayPal\Util\Lifecycle\Method\PaymentMethodDataRegistry; +use Swag\PayPal\Webhook\Registration\WebhookSystemConfigHelper; use Symfony\Component\HttpFoundation\Request; /** @@ -79,28 +82,35 @@ private function createApiValidationController(): SettingsController { $logger = new NullLogger(); $systemConfigService = $this->createDefaultSystemConfig(); - - return new SettingsController( - new ApiCredentialService( - new CredentialsResource( - new TokenClientFactoryMock($logger), - new CredentialsClientFactoryMock($logger), - new TokenValidator() - ), + $apiCredentialsService = new ApiCredentialService( + new CredentialsResource( new TokenClientFactoryMock($logger), - new TokenValidator(), - new CredentialProvider( - new SettingsValidationService($systemConfigService, $logger), - $systemConfigService, - new CredentialsUtil($systemConfigService) - ), - $logger, + new CredentialsClientFactoryMock($logger), + new TokenValidator() ), + new TokenClientFactoryMock($logger), + new TokenValidator(), + new CredentialProvider( + new SettingsValidationService($systemConfigService, $logger), + $systemConfigService, + new CredentialsUtil($systemConfigService) + ), + $logger, + ); + + return new SettingsController( + $apiCredentialsService, new MerchantIntegrationsService( new MerchantIntegrationsResource(new PayPalClientFactoryMock($logger)), new CredentialsUtil($systemConfigService), $this->getContainer()->get(PaymentMethodDataRegistry::class), new PayPalClientFactoryMock($logger) + ), + $this->getContainer()->get(SystemConfigValidator::class), + new SettingsSaver( + $systemConfigService, + $apiCredentialsService, + $this->createMock(WebhookSystemConfigHelper::class), ) ); } diff --git a/tests/Webhook/Registration/WebhookSubscriberTest.php b/tests/Webhook/Registration/WebhookSubscriberTest.php index 67ed81d5c..5f8898b70 100644 --- a/tests/Webhook/Registration/WebhookSubscriberTest.php +++ b/tests/Webhook/Registration/WebhookSubscriberTest.php @@ -16,6 +16,8 @@ use Shopware\Core\Framework\Log\Package; use Shopware\Core\System\SalesChannel\SalesChannelDefinition; use Shopware\Core\System\SalesChannel\SalesChannelEvents; +use Shopware\Core\System\SystemConfig\Event\BeforeSystemConfigMultipleChangedEvent; +use Shopware\Core\System\SystemConfig\Event\SystemConfigMultipleChangedEvent; use Shopware\Core\System\SystemConfig\SystemConfigService; use Shopware\Core\Test\TestDefaults; use Swag\PayPal\RestApi\V1\Resource\WebhookResource; @@ -24,8 +26,10 @@ use Swag\PayPal\Test\Mock\PayPal\Client\PayPalClientFactoryMock; use Swag\PayPal\Test\Mock\Setting\Service\SystemConfigServiceMock; use Swag\PayPal\Webhook\Registration\WebhookSubscriber; +use Swag\PayPal\Webhook\Registration\WebhookSystemConfigHelper; use Swag\PayPal\Webhook\WebhookRegistry; use Swag\PayPal\Webhook\WebhookService; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Routing\RouterInterface; /** @@ -73,6 +77,8 @@ public function testSubscribedEvents(): void { static::assertEqualsCanonicalizing([ SalesChannelEvents::SALES_CHANNEL_DELETED => 'removeSalesChannelWebhookConfiguration', + BeforeSystemConfigMultipleChangedEvent::class => 'checkWebhookBefore', + SystemConfigMultipleChangedEvent::class => 'checkWebhookAfter', ], WebhookSubscriber::getSubscribedEvents()); } @@ -98,7 +104,9 @@ private function createWebhookSubscriber(array $configuration): WebhookSubscribe return new WebhookSubscriber( new NullLogger(), $this->systemConfigService, - $webhookService + $webhookService, + $this->createMock(WebhookSystemConfigHelper::class), + new RequestStack(), ); } diff --git a/tests/Webhook/Registration/WebhookSystemConfigControllerTest.php b/tests/Webhook/Registration/WebhookSystemConfigControllerTest.php index cbbc21d96..cca2dffd1 100644 --- a/tests/Webhook/Registration/WebhookSystemConfigControllerTest.php +++ b/tests/Webhook/Registration/WebhookSystemConfigControllerTest.php @@ -16,6 +16,7 @@ use Shopware\Core\System\SystemConfig\Service\ConfigurationService; use Shopware\Core\System\SystemConfig\SystemConfigService; use Shopware\Core\System\SystemConfig\Validation\SystemConfigValidator; +use Shopware\Core\Test\Annotation\DisabledFeatures; use Shopware\Core\Test\TestDefaults; use Swag\PayPal\Setting\Service\SettingsValidationService; use Swag\PayPal\Setting\Settings; @@ -28,6 +29,7 @@ /** * @internal */ +#[DisabledFeatures(features: ['PAYPAL_SETTINGS_TWEAKS'])] #[Package('checkout')] class WebhookSystemConfigControllerTest extends TestCase { From d0d585034a098312555a5a1f060cc6b770a15394 Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 15:58:09 +0100 Subject: [PATCH 03/11] POC - Added credentials test route --- src/Setting/Service/ApiCredentialService.php | 1 + src/Setting/SettingsController.php | 59 +++++++++++++++++++ .../Struct/MerchantInformationStruct.php | 2 +- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Setting/Service/ApiCredentialService.php b/src/Setting/Service/ApiCredentialService.php index 1a7d53cab..9d954fa90 100644 --- a/src/Setting/Service/ApiCredentialService.php +++ b/src/Setting/Service/ApiCredentialService.php @@ -43,6 +43,7 @@ public function __construct( * @deprecated tag:v10.0.0 - parameter $merchantPayerId will be added * * @throws PayPalInvalidApiCredentialsException + * @throws PayPalApiException */ public function testApiCredentials(string $clientId, string $clientSecret, bool $sandboxActive /* , ?string $merchantPayerId */): bool { diff --git a/src/Setting/SettingsController.php b/src/Setting/SettingsController.php index 7169689fd..64927d39e 100644 --- a/src/Setting/SettingsController.php +++ b/src/Setting/SettingsController.php @@ -8,11 +8,13 @@ namespace Swag\PayPal\Setting; use OpenApi\Attributes as OA; +use Shopware\Core\Framework\Api\EventListener\ErrorResponseFactory; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Routing\RoutingException; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; use Shopware\Core\System\SystemConfig\Validation\SystemConfigValidator; +use Swag\PayPal\RestApi\Exception\PayPalApiException; use Swag\PayPal\Setting\Service\ApiCredentialServiceInterface; use Swag\PayPal\Setting\Service\MerchantIntegrationsService; use Swag\PayPal\Setting\Service\SettingsSaverInterface; @@ -101,6 +103,63 @@ public function validateApiCredentials(Request $request): JsonResponse return new JsonResponse(['credentialsValid' => $credentialsValid]); } + #[OA\Get( + path: '/api/_action/paypal/test-api-credentials', + operationId: 'testApiCredentials', + tags: ['Admin Api', 'PayPal'], + responses: [new OA\Response( + response: Response::HTTP_OK, + description: 'Returns if the provided API credentials are valid', + content: new OA\JsonContent( + required: ['valid', 'errors'], + properties: [ + new OA\Property( + property: 'valid', + type: 'boolean', + ), + new OA\Property( + property: 'errors', + type: 'array', + items: new OA\Items(ref: '#/components/schemas/error'), + ), + ] + ) + )] + )] + #[Route(path: '/api/_action/paypal/test-api-credentials', name: 'api.action.paypal.test-api-credentials', methods: ['POST'], defaults: ['_acl' => ['swag_paypal.viewer']])] + public function testApiCredentials(RequestDataBag $data): JsonResponse + { + $clientId = $data->getString('clientId'); + if (!$clientId) { + throw RoutingException::invalidRequestParameter('clientId'); + } + + $clientSecret = $data->getString('clientSecret'); + if (!$clientSecret) { + throw RoutingException::invalidRequestParameter('clientSecret'); + } + + $merchantPayerId = $data->get('merchantPayerId'); + if ($merchantPayerId !== null && !\is_string($merchantPayerId)) { + throw RoutingException::invalidRequestParameter('merchantPayerId'); + } + + $sandboxActive = $data->getBoolean('sandboxActive'); + + try { + /* @phpstan-ignore-next-line method will have additional method */ + $valid = $this->apiCredentialService->testApiCredentials($clientId, $clientSecret, $sandboxActive, $merchantPayerId); + } catch (PayPalApiException $error) { + $valid = false; + $errors = (new ErrorResponseFactory())->getErrorsFromException($error); + } + + return new JsonResponse([ + 'valid' => $valid, + 'errors' => $errors ?? [], + ]); + } + #[OA\Post( path: '/api/_action/paypal/get-api-credentials', operationId: 'getApiCredentials', diff --git a/src/Setting/Struct/MerchantInformationStruct.php b/src/Setting/Struct/MerchantInformationStruct.php index 263dad610..358a88788 100644 --- a/src/Setting/Struct/MerchantInformationStruct.php +++ b/src/Setting/Struct/MerchantInformationStruct.php @@ -16,7 +16,7 @@ #[Package('checkout')] class MerchantInformationStruct extends Struct { - #[OA\Property(ref: MerchantIntegrations::class)] + #[OA\Property(ref: MerchantIntegrations::class, nullable: true)] protected ?MerchantIntegrations $merchantIntegrations; /** From 0f0a6536e865904ee7e11f561c4fd06ba6fdc3a5 Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:00:43 +0100 Subject: [PATCH 04/11] POC - Added admin settings service --- .../swag-paypal-api-credentials.service.ts | 3 ++ .../api/swag-paypal-settings.service.ts | 52 +++++++++++++++++++ .../app/administration/src/global.types.ts | 3 ++ .../src/init/api-service.init.ts | 5 ++ .../app/administration/src/types/openapi.d.ts | 40 +++++++++++++- .../administration/src/types/system-config.ts | 18 +++++-- 6 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 src/Resources/app/administration/src/core/service/api/swag-paypal-settings.service.ts diff --git a/src/Resources/app/administration/src/core/service/api/swag-paypal-api-credentials.service.ts b/src/Resources/app/administration/src/core/service/api/swag-paypal-api-credentials.service.ts index 4fb2e5a8f..c68c09b3e 100644 --- a/src/Resources/app/administration/src/core/service/api/swag-paypal-api-credentials.service.ts +++ b/src/Resources/app/administration/src/core/service/api/swag-paypal-api-credentials.service.ts @@ -4,6 +4,9 @@ import type * as PayPal from 'src/types'; const ApiService = Shopware.Classes.ApiService; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `SwagPayPalSettingsService` + */ class SwagPayPalApiCredentialsService extends ApiService { constructor(httpClient: AxiosInstance, loginService: LoginService, apiEndpoint = 'paypal') { super(httpClient, loginService, apiEndpoint); diff --git a/src/Resources/app/administration/src/core/service/api/swag-paypal-settings.service.ts b/src/Resources/app/administration/src/core/service/api/swag-paypal-settings.service.ts new file mode 100644 index 000000000..615bf70f3 --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/swag-paypal-settings.service.ts @@ -0,0 +1,52 @@ +import type { LoginService } from 'src/core/service/login.service'; +import type { AxiosInstance } from 'axios'; +import type * as PayPal from 'src/types'; + +const ApiService = Shopware.Classes.ApiService; + +export default class SwagPayPalSettingsService extends ApiService { + constructor(httpClient: AxiosInstance, loginService: LoginService, apiEndpoint = 'paypal') { + super(httpClient, loginService, apiEndpoint); + } + + save(allConfigs: Record) { + return this.httpClient.post>( + `_action/${this.getApiBasePath()}/save-settings`, + allConfigs, + { headers: this.getBasicHeaders() }, + ).then(ApiService.handleResponse.bind(this)); + } + + testApiCredentials(clientId?: string, clientSecret?: string, merchantPayerId?: string, sandboxActive?: boolean) { + return this.httpClient.post>( + `_action/${this.getApiBasePath()}/test-api-credentials`, + { clientId, clientSecret, sandboxActive, merchantPayerId }, + { headers: this.getBasicHeaders() }, + ).then(ApiService.handleResponse.bind(this)); + } + + getApiCredentials( + authCode: string, + sharedId: string, + nonce: string, + sandboxActive: boolean, + params: object = {}, + additionalHeaders: object = {}, + ) { + return this.httpClient.post>( + `_action/${this.getApiBasePath()}/get-api-credentials`, + { authCode, sharedId, nonce, sandboxActive }, + { params, headers: this.getBasicHeaders(additionalHeaders) }, + ).then(ApiService.handleResponse.bind(this)); + } + + getMerchantInformation(salesChannelId: string | null = null) { + return this.httpClient.get>( + `_action/${this.getApiBasePath()}/merchant-information`, + { + params: { salesChannelId }, + headers: this.getBasicHeaders(), + }, + ).then(ApiService.handleResponse.bind(this)); + } +} diff --git a/src/Resources/app/administration/src/global.types.ts b/src/Resources/app/administration/src/global.types.ts index 0794a8b53..85965f590 100644 --- a/src/Resources/app/administration/src/global.types.ts +++ b/src/Resources/app/administration/src/global.types.ts @@ -15,6 +15,7 @@ import type SwagPayPalPosSettingApiService from './core/service/api/swag-paypal- import type SwagPayPalPosWebhookRegisterService from './core/service/api/swag-paypal-pos-webhook-register.service'; import type SwagPayPalPosApiService from './core/service/api/swag-paypal-pos.api.service'; import type SwagPayPalWebhookService from './core/service/api/swag-paypal-webhook.service'; +import type SwagPayPalSettingsService from './core/service/api/swag-paypal-settings.service'; declare global { type TEntity = Entity; @@ -40,6 +41,8 @@ declare global { SwagPayPalOrderService: SwagPayPalOrderService; SwagPaypalPaymentMethodService: SwagPaypalPaymentMethodService; SwagPayPalDisputeApiService: SwagPayPalDisputeApiService; + SwagPayPalSettingsService: SwagPayPalSettingsService; + } } } diff --git a/src/Resources/app/administration/src/init/api-service.init.ts b/src/Resources/app/administration/src/init/api-service.init.ts index 046f40529..59bcfdcc6 100644 --- a/src/Resources/app/administration/src/init/api-service.init.ts +++ b/src/Resources/app/administration/src/init/api-service.init.ts @@ -7,6 +7,7 @@ import SwagPayPalPaymentService from '../core/service/api/swag-paypal-payment.se import SwagPayPalOrderService from '../core/service/api/swag-paypal-order.service'; import SwagPaypalPaymentMethodService from '../core/service/api/swag-paypal-payment-method.service'; import SwagPayPalDisputeApiService from '../core/service/api/swag-paypal-dispute.api.service'; +import SwagPayPalSettingsService from '../core/service/api/swag-paypal-settings.service'; const { Application } = Shopware; @@ -56,3 +57,7 @@ Application.addServiceProvider( 'SwagPayPalDisputeApiService', (container) => new SwagPayPalDisputeApiService(initContainer.httpClient, container.loginService), ); +Application.addServiceProvider( + 'SwagPayPalSettingsService', + (container) => new SwagPayPalSettingsService(initContainer.httpClient, container.loginService), +); diff --git a/src/Resources/app/administration/src/types/openapi.d.ts b/src/Resources/app/administration/src/types/openapi.d.ts index 6c6bd485c..fc12f50cf 100644 --- a/src/Resources/app/administration/src/types/openapi.d.ts +++ b/src/Resources/app/administration/src/types/openapi.d.ts @@ -136,12 +136,18 @@ export interface paths { "/api/_action/paypal/validate-api-credentials": { get: operations["validateApiCredentials"]; }; + "/api/_action/paypal/test-api-credentials": { + get: operations["testApiCredentials"]; + }; "/api/_action/paypal/get-api-credentials": { post: operations["getApiCredentials"]; }; "/api/_action/paypal/merchant-information": { get: operations["getMerchantInformation"]; }; + "/api/_action/paypal/save-settings": { + get: operations["saveSettings"]; + }; "/.well-known/apple-developer-merchantid-domain-association": { /** @description Return the Apple Pay developer association */ get: operations["applePayDomainAssociation"]; @@ -1431,12 +1437,19 @@ export interface components { order_id: string; }; swag_paypal_setting_merchant_information: { - merchantIntegrations: components["schemas"]["swag_paypal_v1_merchant_integrations"]; + merchantIntegrations: components["schemas"]["swag_paypal_v1_merchant_integrations"] | null; /** @description string> key: paymentMethodId, value: capability (see AbstractMethodData) */ capabilities: { [key: string]: string; }; }; + swag_paypal_setting_settings_information: { + sandboxCredentialsChanged: boolean; + sandboxCredentialsValid: boolean | null; + liveCredentialsChanged: boolean; + liveCredentialsValid: boolean | null; + webhookErrors: string[]; + }; }; responses: never; parameters: never; @@ -2150,6 +2163,19 @@ export interface operations { }; }; }; + testApiCredentials: { + responses: { + /** @description Returns if the provided API credentials are valid */ + 200: { + content: { + "application/json": { + valid: boolean; + errors: components["schemas"]["error"][]; + }; + }; + }; + }; + }; getApiCredentials: { requestBody?: { content: { @@ -2188,6 +2214,18 @@ export interface operations { }; }; }; + saveSettings: { + responses: { + /** @description Returns information about the saved settings */ + 200: { + content: { + "application/json": { + [key: string]: components["schemas"]["swag_paypal_setting_settings_information"]; + }; + }; + }; + }; + }; /** @description Return the Apple Pay developer association */ applePayDomainAssociation: { responses: { diff --git a/src/Resources/app/administration/src/types/system-config.ts b/src/Resources/app/administration/src/types/system-config.ts index 4d05837d1..a7b08d22a 100644 --- a/src/Resources/app/administration/src/types/system-config.ts +++ b/src/Resources/app/administration/src/types/system-config.ts @@ -1,8 +1,16 @@ -export const LANDING_PAGES = ['LOGIN', 'BILLING', 'NO_PREFERENCE'] as const; -export const BUTTON_COLORS = ['gold', 'blue', 'black', 'silver', 'white'] as const; -export const BUTTON_SHAPES = ['rect', 'pill', 'sharp'] as const; -export const INTENTS = ['CAPTURE', 'AUTHORIZE'] as const; +import type { SYSTEM_CONFIG, LANDING_PAGES, BUTTON_COLORS, BUTTON_SHAPES, INTENTS } from 'src/constant/swag-paypal-settings.constant'; +/** + * @deprecated tag:v10.0.0 - Will be moved to constant/swag-paypal-settings.constant.ts. + */ +export { + LANDING_PAGES, + BUTTON_COLORS, + BUTTON_SHAPES, + INTENTS, +}; + +// @todo - Keys should be from SYSTEM_CONFIG export declare type SystemConfig = { 'SwagPayPal.settings.clientId'?: string; 'SwagPayPal.settings.clientSecret'?: string; @@ -69,7 +77,7 @@ export declare type SystemConfig = { /** * @private */ -export const SystemConfigDefinition: Record = { +export const SystemConfigDefinition: Record = { 'SwagPayPal.settings.clientId': 'string', 'SwagPayPal.settings.clientSecret': 'string', 'SwagPayPal.settings.clientIdSandbox': 'string', From 49c201eb6d3996aa36de7c3489903897119f2e9b Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:09:16 +0100 Subject: [PATCH 05/11] POC - Reworked error notification creation --- .../administration/src/app/snippet/de-DE.json | 10 ++++++++++ .../administration/src/app/snippet/en-GB.json | 10 ++++++++++ .../src/mixin/swag-paypal-notification.mixin.ts | 17 +++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/Resources/app/administration/src/app/snippet/de-DE.json create mode 100644 src/Resources/app/administration/src/app/snippet/en-GB.json diff --git a/src/Resources/app/administration/src/app/snippet/de-DE.json b/src/Resources/app/administration/src/app/snippet/de-DE.json new file mode 100644 index 000000000..068b86949 --- /dev/null +++ b/src/Resources/app/administration/src/app/snippet/de-DE.json @@ -0,0 +1,10 @@ +{ + "swag-paypal": { + "errors": { + "UNKNOWN": "Ein unbekannter Fehler ist aufgetreten", + "SWAG_PAYPAL__API_INVALID_CREDENTIALS": "Die API-Zugangsdaten sind ungültig", + "SWAG_PAYPAL__API_NOT_AVAILABLE": "Die PayPal-Services sind derzeit nicht verfügbar. Bitte versuchen Sie es später errneut.", + "SWAG_PAYPAL__INVALID_API_CREDENTIALS": "Die API-Zugangsdaten sind ungültig" + } + } +} diff --git a/src/Resources/app/administration/src/app/snippet/en-GB.json b/src/Resources/app/administration/src/app/snippet/en-GB.json new file mode 100644 index 000000000..510dea2e0 --- /dev/null +++ b/src/Resources/app/administration/src/app/snippet/en-GB.json @@ -0,0 +1,10 @@ +{ + "swag-paypal": { + "errors": { + "UNKNOWN": "An unknown error occurred", + "SWAG_PAYPAL__API_INVALID_CREDENTIALS": "The API credentials are invalid", + "SWAG_PAYPAL__API_NOT_AVAILABLE": "PayPal services are currently unavailable. Please try again later.", + "SWAG_PAYPAL__INVALID_API_CREDENTIALS": "The API credentials are invalid" + } + } +} diff --git a/src/Resources/app/administration/src/mixin/swag-paypal-notification.mixin.ts b/src/Resources/app/administration/src/mixin/swag-paypal-notification.mixin.ts index 631fba430..8248c8bde 100644 --- a/src/Resources/app/administration/src/mixin/swag-paypal-notification.mixin.ts +++ b/src/Resources/app/administration/src/mixin/swag-paypal-notification.mixin.ts @@ -26,6 +26,8 @@ export default Shopware.Mixin.register('swag-paypal-notification', Shopware.Comp methods: { /** + * @deprecated tag:v10.0.0 - Will be removed, use `createMessageFromError` instead + * * Handles a service error and creates a notification for each error. * If the errorResponse is undefined, a generic error notification will be created. * If the errorResponse is not a ShopwareHttpError, the errorResponse will be used as message. @@ -68,5 +70,20 @@ export default Shopware.Mixin.register('swag-paypal-notification', Shopware.Comp this.createNotificationError({ message: messages[i], title }); } }, + + createMessageFromError(httpError: PayPal.ServiceError): string { + return (httpError?.response?.data?.errors ?? []) + .map((error) => { + const message = typeof error.meta?.parameters?.message === 'string' + ? error.meta.parameters.message + : error.detail; + + const snippet = `swag-paypal.errors.${error.code}`; + const translation = this.$t(snippet, { message }); + + return snippet !== translation ? translation : message; + }) + .join('
'); + }, }, })); From fc31e3655f5e6f86ec91088305702ccf3a31289e Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:04:05 +0100 Subject: [PATCH 06/11] POC - Added admin settings stores, components and mixins --- .../component/swag-paypal-setting/index.ts | 155 ++++++++++++++++++ .../swag-paypal-setting.html.twig | 53 ++++++ .../app/administration/src/app/index.ts | 14 ++ .../administration/src/app/snippet/de-DE.json | 95 +++++++++++ .../administration/src/app/snippet/en-GB.json | 95 +++++++++++ .../swag-paypal-merchant-information.store.ts | 75 +++++++++ .../app/store/swag-paypal-settings.store.ts | 63 +++++++ .../constant/swag-paypal-settings.constant.ts | 155 ++++++++++++++++++ .../app/administration/src/global.types.ts | 10 ++ src/Resources/app/administration/src/main.ts | 3 + .../swag-paypal-merchant-information.mixin.ts | 56 +++++++ .../src/mixin/swag-paypal-settings.mixin.ts | 92 +++++++++++ 12 files changed, 866 insertions(+) create mode 100644 src/Resources/app/administration/src/app/component/swag-paypal-setting/index.ts create mode 100644 src/Resources/app/administration/src/app/component/swag-paypal-setting/swag-paypal-setting.html.twig create mode 100644 src/Resources/app/administration/src/app/index.ts create mode 100644 src/Resources/app/administration/src/app/store/swag-paypal-merchant-information.store.ts create mode 100644 src/Resources/app/administration/src/app/store/swag-paypal-settings.store.ts create mode 100644 src/Resources/app/administration/src/constant/swag-paypal-settings.constant.ts create mode 100644 src/Resources/app/administration/src/mixin/swag-paypal-merchant-information.mixin.ts create mode 100644 src/Resources/app/administration/src/mixin/swag-paypal-settings.mixin.ts diff --git a/src/Resources/app/administration/src/app/component/swag-paypal-setting/index.ts b/src/Resources/app/administration/src/app/component/swag-paypal-setting/index.ts new file mode 100644 index 000000000..b611f8652 --- /dev/null +++ b/src/Resources/app/administration/src/app/component/swag-paypal-setting/index.ts @@ -0,0 +1,155 @@ +import type * as PayPal from 'src/types'; +import { SystemConfigDefinition } from '../../../types/system-config'; +import template from './swag-paypal-setting.html.twig'; +import { type SYSTEM_CONFIG, SYSTEM_CONFIGS } from '../../../constant/swag-paypal-settings.constant'; + +const { string, object } = Shopware.Utils; + +/** + * @private - The component has a stable public API (props), but expect that implementation details may change. + */ +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: ['acl'], + + emits: ['update:value'], + + props: { + path: { + required: true, + type: String as PropType, + validation: (value: SYSTEM_CONFIG) => { + return SYSTEM_CONFIGS.includes(value); + }, + }, + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + value() { + return this.settingsStore.getActual(this.path); + }, + + inheritedValue() { + return this.settingsStore.salesChannel + ? this.settingsStore.getRoot(this.path) + : undefined; + }, + + hasParent() { + return !!this.settingsStore.salesChannel; + }, + + pathDomainless() { + return this.path.replace('SwagPayPal.settings.', ''); + }, + + disabled() { + return !this.acl.can('swag_paypal.editor') || this.settingsStore.isLoading || this.formAttrs.disabled; + }, + + type() { + return SystemConfigDefinition[this.path]; + }, + + customInheritationCheckFunction() { + return (value: unknown) => value === null || value === undefined; + + // switch (this.type) { + // case 'array': return (value: unknown) => !Array.isArray(value); + // case 'boolean': return (value: unknown) => typeof value !== 'boolean'; + // case 'string': return (value: unknown) => typeof value !== 'string'; + // default: throw new Error(`Unhandled or undefined type "${this.type}" for system-config path "${this.path}"`); + // } + }, + + label() { + return this.tif(`swag-paypal-setting.label.${this.pathDomainless}`); + }, + + helpText() { + return this.tif( + `swag-paypal-setting.helpText.${this.pathDomainless}.${this.settingsStore.getActual(this.path)}`, + `swag-paypal-setting.helpText.${this.pathDomainless}`, + ); + }, + + hintText() { + return this.tif( + `swag-paypal-setting.hintText.${this.pathDomainless}.${this.settingsStore.getActual(this.path)}`, + `swag-paypal-setting.hintText.${this.pathDomainless}`, + ); + }, + + attrs() { + // normalize attribute keys to camelCase + const entries = Object.entries(this.$attrs).map(([key, value]) => [string.camelCase(key), value]); + const attrs = Object.fromEntries(entries) as Record; + + if (!attrs.hasOwnProperty('label') && this.label) { + attrs.label = this.label; + } + + if (!attrs.hasOwnProperty('helpText') && this.helpText) { + attrs.helpText = this.helpText; + } + + if (!attrs.hasOwnProperty('hintText') && this.hintText) { + attrs.hintText = this.hintText; + } + + return attrs; + }, + + wrapperAttrs() { + const pick = ['disabled']; + + if (this.type !== 'boolean') { + pick.push('label', 'helpText', 'required'); + } + + return object.pick(this.attrs, pick); + }, + + formAttrs() { + const pick = [ + 'class', + 'disabled', + 'error', + 'labelProperty', + 'options', + 'valueProperty', + 'bordered', + ]; + + if (this.type === 'boolean') { + pick.push('label', 'helpText', 'required'); + } + + return object.pick(this.attrs, pick); + }, + }, + + methods: { + /** + * Translate if found, otherwise return null + */ + tif(...keys: string[]): string | null { + // $te will also report partial matches as found + const key = keys.find((k) => this.$te(k) && this.$t(k) !== k); + + return key ? this.$t(key) : null; + }, + + setValue(value: PayPal.SystemConfig[keyof PayPal.SystemConfig]) { + this.settingsStore.set(this.path, value); + this.$emit('update:value', value ?? undefined); + }, + }, +}); diff --git a/src/Resources/app/administration/src/app/component/swag-paypal-setting/swag-paypal-setting.html.twig b/src/Resources/app/administration/src/app/component/swag-paypal-setting/swag-paypal-setting.html.twig new file mode 100644 index 000000000..8c2e0094f --- /dev/null +++ b/src/Resources/app/administration/src/app/component/swag-paypal-setting/swag-paypal-setting.html.twig @@ -0,0 +1,53 @@ + + + diff --git a/src/Resources/app/administration/src/app/index.ts b/src/Resources/app/administration/src/app/index.ts new file mode 100644 index 000000000..bba7fe5f3 --- /dev/null +++ b/src/Resources/app/administration/src/app/index.ts @@ -0,0 +1,14 @@ +import './store/swag-paypal-merchant-information.store'; +import './store/swag-paypal-settings.store'; +Shopware.Component.register('swag-paypal-setting', () => import('./component/swag-paypal-setting')); + +// synchronise salesChannel of stores +Shopware.Vue.watch( + () => Shopware.Store.get('swagPayPalSettings').salesChannel, + (salesChannel) => { Shopware.Store.get('swagPayPalMerchantInformation').salesChannel = salesChannel; }, +); + +Shopware.Vue.watch( + () => Shopware.Store.get('swagPayPalMerchantInformation').salesChannel, + (salesChannel) => { Shopware.Store.get('swagPayPalSettings').salesChannel = salesChannel; }, +); diff --git a/src/Resources/app/administration/src/app/snippet/de-DE.json b/src/Resources/app/administration/src/app/snippet/de-DE.json index 068b86949..1b6cbbafc 100644 --- a/src/Resources/app/administration/src/app/snippet/de-DE.json +++ b/src/Resources/app/administration/src/app/snippet/de-DE.json @@ -5,6 +5,101 @@ "SWAG_PAYPAL__API_INVALID_CREDENTIALS": "Die API-Zugangsdaten sind ungültig", "SWAG_PAYPAL__API_NOT_AVAILABLE": "Die PayPal-Services sind derzeit nicht verfügbar. Bitte versuchen Sie es später errneut.", "SWAG_PAYPAL__INVALID_API_CREDENTIALS": "Die API-Zugangsdaten sind ungültig" + }, + "notifications": { + "save": { + "title": "PayPal Einstellungen", + "webhookMessage": "Der Webhook konnte nicht gespeichert werden:
{message}", + "errorMessage": "Speicherung fehlgeschlagen:
{message}" + } + } + }, + "swag-paypal-setting": { + "label": { + "clientId": "Client-ID", + "clientSecret": "Client-Secret", + "merchantPayerId": "PayPal-Händler-ID", + "clientIdSandbox": "Sandbox-Client-ID", + "clientSecretSandbox": "Sandbox-Client-Secret", + "merchantPayerIdSandbox": "Sandbox-PayPal-Händler-ID", + "sandbox": "Sandbox aktivieren", + "intent": "Zahlungsabschluss", + "submitCart": "Warenkorb übertragen", + "brandName": "Eigener Markenname auf der PayPal-Seite", + "landingPage": "PayPal-Landingpage", + "sendOrderNumber": "Bestellnummer übertragen", + "orderNumberPrefix": "Bestellnummer-Präfix", + "orderNumberSuffix": "Bestellnummer-Suffix", + "excludedProductIds": "Ausgeschlossene Produkte", + "excludedProductStreamIds": "Ausgeschlossene dynamische Produktgruppen", + "ecsDetailEnabled": "'Direkt zu PayPal' auf Detailseite", + "ecsCartEnabled": "'Direkt zu PayPal' im Warenkorb", + "ecsOffCanvasEnabled": "'Direkt zu PayPal' im Off-Canvas Warenkorb", + "ecsLoginEnabled": "'Direkt zu PayPal' auf Loginseite", + "ecsListingEnabled": "'Direkt zu PayPal' auf Listingseiten", + "ecsButtonColor": "Buttonfarbe", + "ecsButtonShape": "Buttonform", + "ecsSubmitCart": "Warenkorb übertragen", + "ecsButtonLanguageIso": "Buttonsprache", + "ecsShowPayLater": "'Später Bezahlen' neben dem 'PayPal Checkout'-Button anzeigen", + "installmentBannerDetailPageEnabled": "'Später Bezahlen'-Banner auf Detailseite", + "installmentBannerCartEnabled": "'Später Bezahlen'-Banner im Warenkorb", + "installmentBannerOffCanvasCartEnabled": "'Später Bezahlen'-Banner im Off-Canvas-Warenkorb", + "installmentBannerLoginPageEnabled": "'Später Bezahlen'-Banner auf Loginseite", + "installmentBannerFooterEnabled": "'Später Bezahlen'-Banner im Footer", + "acdcForce3DS": "Zahlungen aus Nicht-3DS-Ländern blockieren", + "puiCustomerServiceInstructions": "Kundenservice-Anweisungen für Rechnungskauf", + "spbCheckoutEnabled": "Smart Payment Buttons aktivieren", + "spbButtonLanguageIso": "Buttonsprache", + "spbAlternativePaymentMethodsEnabled": "Aktiviert die alternativen Zahlungsarten der Smart Payment Buttons.", + "spbShowPayLater": "'Später Bezahlen'-Button neben PayPal-Button anzeigen", + "spbButtonColor": "Buttonfarbe", + "spbButtonShape": "Buttonform", + "vaultingEnabledWallet": "Vaulting für PayPal-Zahlungen aktivieren", + "vaultingEnabledACDC": "Vaulting für Kredit- und Debitkarten-Zahlungen verwenden", + "vaultingEnabledVenmo": "Vaulting für Venmo-Zahlungen verwenden", + "crossBorderMessagingEnabled" : "Länderübergreifende Lokalisierung der \"Später bezahlen\"-Nachricht aktivieren", + "crossBorderBuyerCountry" : "Lokalisierung" + }, + "helpText": { + "clientId": "Die Client-ID der REST-API, die das Plugin dazu verwendet, sich mit der PayPal-API zu authentifizieren.", + "clientSecret": "Das Client-Secret der REST-API, das das Plugin dazu verwendet, sich mit der PayPal-API zu authentifizieren.", + "merchantPayerId": "Die PayPal-Händler-ID, die dem PayPal-Konto zugeordnet ist (siehe Geschäftsangaben in den PayPal Kontoeinstellungen).", + "clientIdSandbox": "Die Client-ID der REST-API, die das Plugin im Testfall dazu verwendet, sich mit der PayPal-API zu authentifizieren.", + "clientSecretSandbox": "Das Client-Secret der REST-API, das das Plugin im Testfall dazu verwendet, sich mit der PayPal-API zu authentifizieren.", + "merchantPayerIdSandbox": "Die PayPal-Händler-ID, die dem Sandbox-PayPal-Konto zugeordnet ist (siehe Geschäftsangaben in den PayPal Kontoeinstellungen).", + "sandbox": "Aktiviere diese Option, um die Integration zu testen.", + "submitCart": "Wenn diese Option aktiv ist, werden beim Checkout die Warenkorbdaten an PayPal übertragen.", + "brandName": "Dieser Text wird als Markenname auf der PayPal-Zahlungsseite angezeigt.", + "sendOrderNumber": "Wenn diese Option aktiv ist, wird beim Checkout die Bestellnummer an PayPal als Rechnungsnummer übertragen.", + "orderNumberPrefix": "Dieser Text wird vor die ursprüngliche Bestellnummer gehängt (z.B. MeinShop_SW20001). Das hilft dabei der Identifizierung des Shops, in dem die Zahlung ausgeführt wurde. Du findest diese als Rechnungsnummer in Deinem PayPal-Dashboard.", + "orderNumberSuffix": "Dieser Text wird an die ursprüngliche Bestellnummer gehängt (z.B. SW20001_MeinShop). Das hilft dabei der Identifizierung des Shops, in dem die Zahlung ausgeführt wurde. Du findest diese als Rechnungsnummer in Deinem PayPal-Dashboard.", + "excludedProductIds": "Hier ausgewählte Produkte können nicht mit PayPal gekauft werden.", + "excludedProductStreamIds": "Hier ausgewählte dynamische Produktgruppen können nicht mit PayPal gekauft werden.", + "ecsDetailEnabled": "Wenn diese Option aktiv ist, wird der Express Checkout Button auf jeder Produktdetailseite angezeigt.", + "ecsCartEnabled": "Wenn diese Option aktiv ist, wird der Express Checkout Button auf der Warenkorbseite angezeigt.", + "ecsOffCanvasEnabled": "Wenn diese Option aktiv ist, wird der Express Checkout Button in dem Off-Canvas-Warenkorb angezeigt.", + "ecsLoginEnabled": "Wenn diese Option aktiv ist, wird der Express Checkout Button auf der Login- und Registrierungsseite angezeigt.", + "ecsListingEnabled": "Wenn diese Option aktiv ist, wird der Express Checkout Button auf Listingseiten angezeigt.", + "ecsSubmitCart": "Wenn diese Option aktiv ist, wird der Warenkorb bei Express-Bestellungen an PayPal übertragen.", + "ecsButtonLanguageIso": "Wenn nicht gesetzt, wird die Sprache des Verkaufskanals verwendet.", + "ecsShowPayLater": "Die Schaltfläche 'Später Bezahlen' wird auf denselben Seiten und im selben Design wie die Schaltfläche 'Direkt zu PayPal' angezeigt.", + "installmentBannerDetailPageEnabled": "Wenn diese Option aktiv ist, wird der 'Später Bezahlen'-Banner auf jeder Produktdetailseite angezeigt.", + "installmentBannerCartEnabled": "Wenn diese Option aktiv ist, wird der 'Später Bezahlen'-Banner im Warenkorb angezeigt.", + "installmentBannerOffCanvasCartEnabled": "Wenn diese Option aktiv ist, wird der 'Später Bezahlen'-Banner im Off-Canvas-Warenkorb angezeigt.", + "installmentBannerLoginPageEnabled": "Wenn diese Option aktiv ist, wird der 'Später Bezahlen'-Banner auf der Login- und Registrierungsseite angezeigt.", + "installmentBannerFooterEnabled": "Wenn diese Option aktiv ist, wird der 'Später Bezahlen'-Banner im Footer angezeigt.", + "acdcForce3DS": "PayPal prüft auf Basis der präsentierten Kredit- oder Debitkarte, ob 3DS (Starke Kunden-Authentifizierung) erforderlich ist. Durch das Setzen dieser Option werden Zahlungsversuche ohne 3DS-Check abgelehnt.", + "puiCustomerServiceInstructions": "Diese Anweisungen werden an PayPal & RatePay übermittelt und in Kunden-E-Mails verwendet.", + "spbButtonLanguageIso": "Wenn nicht gesetzt, wird die Sprache des Verkaufskanals verwendet.", + "spbAlternativePaymentMethodsEnabled": "Die alternativen Zahlungsarten sind Kredit- und Debitkarten und weitere." + }, + "hintText": { + "landingPage": { + "LOGIN": "Anmeldung: Auf der PayPal-Seite wird der Login als Landingpage angezeigt.", + "GUEST_CHECKOUT": "Gast-Checkout: Auf der PayPal-Seite werden direkt Zahlungsdaten des Kunden abgefragt, ohne dass dieser sich bei PayPal einloggen muss.", + "NO_PREFERENCE": "Keine Präferenz: PayPal entscheidet auf Grundlage früherer Interaktionen des Kunden mit PayPal welche Seite angezeigt wird." + } } } } diff --git a/src/Resources/app/administration/src/app/snippet/en-GB.json b/src/Resources/app/administration/src/app/snippet/en-GB.json index 510dea2e0..59f594b62 100644 --- a/src/Resources/app/administration/src/app/snippet/en-GB.json +++ b/src/Resources/app/administration/src/app/snippet/en-GB.json @@ -5,6 +5,101 @@ "SWAG_PAYPAL__API_INVALID_CREDENTIALS": "The API credentials are invalid", "SWAG_PAYPAL__API_NOT_AVAILABLE": "PayPal services are currently unavailable. Please try again later.", "SWAG_PAYPAL__INVALID_API_CREDENTIALS": "The API credentials are invalid" + }, + "notifications": { + "save": { + "title": "PayPal Settings", + "webhookMessage": "The webhook could not be saved:
{message}", + "errorMessage": "Saving failed:
{message}" + } + } + }, + "swag-paypal-setting": { + "label": { + "clientId": "Client ID", + "clientSecret": "Client secret", + "merchantPayerId": "PayPal Merchant ID", + "clientIdSandbox": "Sandbox client ID", + "clientSecretSandbox": "Sandbox client secret", + "merchantPayerIdSandbox": "Sandbox PayPal Merchant ID", + "sandbox": "Enable sandbox", + "intent": "Payment acquisition", + "submitCart": "Submit cart", + "brandName": "Your own brand name on PayPal page", + "landingPage": "PayPal landing page", + "sendOrderNumber": "Submit order number", + "orderNumberPrefix": "Order number prefix", + "orderNumberSuffix": "Order number suffix", + "excludedProductIds": "Excluded products", + "excludedProductStreamIds": "Excluded dynamic product groups", + "ecsDetailEnabled": "'PayPal Checkout' on detail page", + "ecsCartEnabled": "'PayPal Checkout' on cart", + "ecsOffCanvasEnabled": "'PayPal Checkout' on off-canvas cart", + "ecsLoginEnabled": "'PayPal Checkout' on login page", + "ecsListingEnabled": "'PayPal Checkout' on listing pages", + "ecsButtonColor": "Button color", + "ecsButtonShape": "Button shape", + "ecsSubmitCart": "Submit cart", + "ecsButtonLanguageIso": "Button locale", + "ecsShowPayLater": "Display 'Pay Later' button next to the 'PayPal Checkout' button", + "installmentBannerDetailPageEnabled": "'Pay Later' banner on detail page", + "installmentBannerCartEnabled": "'Pay Later' banner on cart", + "installmentBannerOffCanvasCartEnabled": "'Pay Later' banner on off-canvas cart", + "installmentBannerLoginPageEnabled": "'Pay Later' banner on login page", + "installmentBannerFooterEnabled": "'Pay Later' banner on footer", + "acdcForce3DS": "Block payments from non-3DS countries", + "puiCustomerServiceInstructions": "Customer service instructions for Pay upon invoice", + "spbCheckoutEnabled": "Enable Smart Payment Buttons", + "spbButtonLanguageIso": "Button locale", + "spbAlternativePaymentMethodsEnabled": "Enable the alternative payment methods for the Smart Payment Buttons.", + "spbShowPayLater": "Display 'Pay Later' button next to PayPal button", + "spbButtonColor": "Button color", + "spbButtonShape": "Button shape", + "vaultingEnabledWallet": "Enable Vaulting for PayPal payments", + "vaultingEnabledACDC": "Enable Vaulting for credit and debit cards", + "vaultingEnabledVenmo": "Enable Vaulting for Venmo payments", + "crossBorderMessagingEnabled" : "Enable cross-border localization of Pay Later message", + "crossBorderBuyerCountry" : "Localization" + }, + "helpText": { + "clientId": "The REST API client ID is used to authenticate this plugin with the PayPal API.", + "clientSecret": "The REST API client secret is used to authenticate this plugin with the PayPal API.", + "merchantPayerId": "The PayPal Merchant ID assigned to your PayPal account (see Business Information in PayPal account settings).", + "clientIdSandbox": "The REST API client ID is used while testing to authenticate this plugin with the PayPal API.", + "clientSecretSandbox": "The REST API client secret is used while testing to authenticate this plugin with the PayPal API.", + "merchantPayerIdSandbox": "The PayPal Merchant ID assigned to your PayPal sandbox account (see Business Information in PayPal account settings).", + "sandbox": "Enable, if you want to test the PayPal integration.", + "submitCart": "If this option is active, cart data will be submitted to PayPal at checkout.", + "brandName": "This text will be displayed as the brand name on the PayPal payment page.", + "sendOrderNumber": "If this option is active, the order number will be submitted to PayPal as invoice ID at checkout.", + "orderNumberPrefix": "This text is placed before the original order number (e.g MyShop_SW20001). This helps to identify the shop where the payment was made. You can find it as invoice ID in your PayPal dashboard.", + "orderNumberSuffix": "This text is placed after the original order number (e.g SW20001_MyShop). This helps to identify the shop where the payment was made. You can find it as invoice ID in your PayPal dashboard.", + "excludedProductIds": "Products selected here cannot be purchased with PayPal.", + "excludedProductStreamIds": "Products included in the dynamic product groups selected here cannot be purchased with PayPal.", + "ecsDetailEnabled": "If this option is active, the Express Checkout button will be shown on each product detail page.", + "ecsCartEnabled": "If this option is active, the Express Checkout button will be shown on the cart.", + "ecsOffCanvasEnabled": "If this option is active, the Express Checkout button will be shown on the off-canvas cart.", + "ecsLoginEnabled": "If this option is active, the Express Checkout button will be shown on the login and register page.", + "ecsListingEnabled": "If this option is active, the Express Checkout button will be shown on listing pages.", + "ecsSubmitCart": "If this option is active, the cart will be submitted to PayPal for Express orders.", + "ecsButtonLanguageIso": "If not set, the sales channel language will be used.", + "ecsShowPayLater": "The 'Pay Later' button will be displayed on the same pages and in the same design as the 'PayPal Checkout' button.", + "installmentBannerDetailPageEnabled": "If this option is active, the 'Pay Later' banner will be shown on each product detail page.", + "installmentBannerCartEnabled": "If this option is active, the 'Pay Later' banner will be shown on the cart.", + "installmentBannerOffCanvasCartEnabled": "If this option is active, the 'Pay Later' banner will be shown on the off-canvas cart.", + "installmentBannerLoginPageEnabled": "If this option is active, the 'Pay Later' banner will be shown on the login and register page.", + "installmentBannerFooterEnabled": "If this option is active, the 'Pay Later' banner will be shown on the footer.", + "acdcForce3DS": "PayPal checks whether 3DS (Strong Customer Authentication) is required based on the credit or debit card presented. Setting this option will reject payment attempts without a 3DS check.", + "puiCustomerServiceInstructions": "These instructions will be submitted to PayPal & RatePay and shown in emails for the customer.", + "spbButtonLanguageIso": "If not set, the sales channel language will be used.", + "spbAlternativePaymentMethodsEnabled": "Alternative payment methods are credit- and debit cards and more." + }, + "hintText": { + "landingPage": { + "LOGIN": "Login: The PayPal site displays a login screen as landing page.", + "GUEST_CHECKOUT": "Guest Checkout: The PayPal site displays a form for payment information, the customer does not need to log in.", + "NO_PREFERENCE": "No preference: PayPal decides which page is shown, depending on the previous interaction of the customer with PayPal." + } } } } diff --git a/src/Resources/app/administration/src/app/store/swag-paypal-merchant-information.store.ts b/src/Resources/app/administration/src/app/store/swag-paypal-merchant-information.store.ts new file mode 100644 index 000000000..096d019a5 --- /dev/null +++ b/src/Resources/app/administration/src/app/store/swag-paypal-merchant-information.store.ts @@ -0,0 +1,75 @@ +import type * as PayPal from 'src/types'; + +type State = { + salesChannel: string | null; + allMerchantInformations: Record>; +}; + +const MerchantInformationStub: PayPal.Setting<'merchant_information'> = { + merchantIntegrations: null, + capabilities: {}, +}; + +const store = Shopware.Store.register({ + id: 'swagPayPalMerchantInformation', + + state: (): State => ({ + salesChannel: null, + allMerchantInformations: {}, + }), + + actions: { + set(salesChannelId: string | null, merchantInformation: PayPal.Setting<'merchant_information'>) { + this.allMerchantInformations[String(salesChannelId)] = merchantInformation; + }, + + has(salesChannelId: string | null): boolean { + return this.allMerchantInformations.hasOwnProperty(String(salesChannelId)); + }, + + delete(salesChannelId: string | null) { + delete this.allMerchantInformations[String(salesChannelId)]; + }, + }, + + getters: { + isLoading(): boolean { + return !this.allMerchantInformations.hasOwnProperty(String(this.salesChannel)); + }, + + actual(): PayPal.Setting<'merchant_information'> { + return this.allMerchantInformations[String(this.salesChannel)] ?? MerchantInformationStub; + }, + + products(): PayPal.V1<'merchant_integrations'>['products'] { + return this.actual.merchantIntegrations?.products ?? []; + }, + + capabilities(): PayPal.Setting<'merchant_information'>['capabilities'] { + return this.actual.capabilities; + }, + + merchantCapabilities(): NonNullable['capabilities']> { + return this.actual.merchantIntegrations?.capabilities ?? []; + }, + + canVault(): boolean { + return this.merchantCapabilities.some( + (capability) => capability.name === 'PAYPAL_WALLET_VAULTING_ADVANCED' && capability.status === 'ACTIVE', + ); + }, + + canPPCP(): boolean { + return this.merchantCapabilities.some( + (capability) => capability.name === 'PAYPAL_CHECKOUT' && capability.status === 'ACTIVE', + ); + }, + + needsReonboarding(): boolean { + return Object.values(this.actual.capabilities).some((c) => c !== 'active' && c !== 'mybank'); + }, + }, +}); + +export type MerchantInformationStore = ReturnType; +export default store; diff --git a/src/Resources/app/administration/src/app/store/swag-paypal-settings.store.ts b/src/Resources/app/administration/src/app/store/swag-paypal-settings.store.ts new file mode 100644 index 000000000..2edfd7126 --- /dev/null +++ b/src/Resources/app/administration/src/app/store/swag-paypal-settings.store.ts @@ -0,0 +1,63 @@ +import type * as PayPal from 'src/types'; + +type State = { + salesChannel: string | null; + allConfigs: Readonly>; +}; + +const store = Shopware.Store.register({ + id: 'swagPayPalSettings', + + state: (): State => ({ + salesChannel: null, + allConfigs: {}, + }), + + actions: { + setConfig(salesChannelId: string | null, config: PayPal.SystemConfig) { + // @ts-expect-error - we are allowed to mutate the state + this.allConfigs[String(salesChannelId)] = config; + }, + + hasConfig(salesChannelId: string | null): boolean { + return this.allConfigs.hasOwnProperty(String(salesChannelId)); + }, + + set(key: K, value: PayPal.SystemConfig[K]) { + this.allConfigs[String(this.salesChannel)][key] = value ?? undefined; + }, + + get(key: K): PayPal.SystemConfig[K] { + return this.actual[key] ?? this.root[key] ?? undefined; + }, + + getRoot(key: K): PayPal.SystemConfig[K] { + return this.root[key] ?? undefined; + }, + + getActual(key: K): PayPal.SystemConfig[K] { + return this.actual[key] ?? undefined; + }, + }, + + getters: { + isLoading(): boolean { + return !this.allConfigs.hasOwnProperty(String(this.salesChannel)); + }, + + isSandbox(): boolean { + return this.actual['SwagPayPal.settings.sandbox'] ?? this.root['SwagPayPal.settings.sandbox'] ?? false; + }, + + root(): PayPal.SystemConfig { + return this.allConfigs.null ?? {}; + }, + + actual(): PayPal.SystemConfig { + return this.allConfigs[String(this.salesChannel)] ?? {}; + }, + }, +}); + +export type SettingsStore = ReturnType; +export default store; diff --git a/src/Resources/app/administration/src/constant/swag-paypal-settings.constant.ts b/src/Resources/app/administration/src/constant/swag-paypal-settings.constant.ts new file mode 100644 index 000000000..5d1e0502b --- /dev/null +++ b/src/Resources/app/administration/src/constant/swag-paypal-settings.constant.ts @@ -0,0 +1,155 @@ +export const LOCALES = [ + 'ar_EG', + 'cs_CZ', + 'da_DK', + 'de_DE', + 'el_GR', + 'en_AU', + 'en_GB', + 'en_IN', + 'en_US', + 'es_ES', + 'es_XC', + 'fi_FI', + 'fr_CA', + 'fr_FR', + 'fr_XC', + 'he_IL', + 'hu_HU', + 'id_ID', + 'it_IT', + 'ja_JP', + 'ko_KR', + 'nl_NL', + 'no_NO', + 'pl_PL', + 'pt_BR', + 'pt_PT', + 'ru_RU', + 'sk_SK', + 'sv_SE', + 'th_TH', + 'zh_CN', + 'zh_HK', + 'zh_TW', + 'zh_XC', +] as const; + +export type LOCALE = typeof LOCALES[number]; + +export const COUNTRY_OVERRIDES = [ + 'en-AU', + 'de-DE', + 'es-ES', + 'fr-FR', + 'en-GB', + 'it-IT', + 'en-US', +] as const; + +export type COUNTRY_OVERRIDE = typeof COUNTRY_OVERRIDES[number]; + +export const INTENTS = [ + 'CAPTURE', + 'AUTHORIZE', +] as const; + +export type INTENT = typeof INTENTS[number]; + +export const LANDING_PAGES = [ + 'LOGIN', + 'GUEST_CHECKOUT', + 'NO_PREFERENCE', +] as const; + +export type LANDING_PAGE = typeof LANDING_PAGES[number]; + +export const BUTTON_COLORS = [ + 'blue', + 'black', + 'gold', + 'silver', + 'white', +] as const; + +export type BUTTON_COLOR = typeof BUTTON_COLORS[number]; + +export const BUTTON_SHAPES = [ + 'sharp', + 'pill', + 'rect', +] as const; + +export type BUTTON_SHAPE = typeof BUTTON_SHAPES[number]; + +export const SYSTEM_CONFIGS = [ + 'SwagPayPal.settings.clientId', + 'SwagPayPal.settings.clientSecret', + 'SwagPayPal.settings.clientIdSandbox', + 'SwagPayPal.settings.clientSecretSandbox', + 'SwagPayPal.settings.merchantPayerId', + 'SwagPayPal.settings.merchantPayerIdSandbox', + 'SwagPayPal.settings.sandbox', + + 'SwagPayPal.settings.intent', + 'SwagPayPal.settings.submitCart', + 'SwagPayPal.settings.brandName', + 'SwagPayPal.settings.landingPage', + 'SwagPayPal.settings.sendOrderNumber', + 'SwagPayPal.settings.orderNumberPrefix', + 'SwagPayPal.settings.orderNumberSuffix', + 'SwagPayPal.settings.excludedProductIds', + 'SwagPayPal.settings.excludedProductStreamIds', + + 'SwagPayPal.settings.ecsDetailEnabled', + 'SwagPayPal.settings.ecsCartEnabled', + 'SwagPayPal.settings.ecsOffCanvasEnabled', + 'SwagPayPal.settings.ecsLoginEnabled', + 'SwagPayPal.settings.ecsListingEnabled', + 'SwagPayPal.settings.ecsButtonColor', + 'SwagPayPal.settings.ecsButtonShape', + 'SwagPayPal.settings.ecsButtonLanguageIso', + 'SwagPayPal.settings.ecsShowPayLater', + + 'SwagPayPal.settings.spbButtonColor', + 'SwagPayPal.settings.spbButtonShape', + 'SwagPayPal.settings.spbButtonLanguageIso', + 'SwagPayPal.settings.spbShowPayLater', + 'SwagPayPal.settings.spbCheckoutEnabled', + 'SwagPayPal.settings.spbAlternativePaymentMethodsEnabled', + + 'SwagPayPal.settings.acdcForce3DS', + + 'SwagPayPal.settings.puiCustomerServiceInstructions', + + 'SwagPayPal.settings.installmentBannerDetailPageEnabled', + 'SwagPayPal.settings.installmentBannerCartEnabled', + 'SwagPayPal.settings.installmentBannerOffCanvasCartEnabled', + 'SwagPayPal.settings.installmentBannerLoginPageEnabled', + 'SwagPayPal.settings.installmentBannerFooterEnabled', + + 'SwagPayPal.settings.vaultingEnabled', + 'SwagPayPal.settings.vaultingEnableAlways', + 'SwagPayPal.settings.vaultingEnabledWallet', + 'SwagPayPal.settings.vaultingEnabledACDC', + 'SwagPayPal.settings.vaultingEnabledVenmo', + + 'SwagPayPal.settings.crossBorderMessagingEnabled', + 'SwagPayPal.settings.crossBorderBuyerCountry', + + 'SwagPayPal.settings.webhookId', + 'SwagPayPal.settings.webhookExecuteToken', + + + /** + * @deprecated tag:v10.0.0 - Will be removed without replacement. + */ + 'SwagPayPal.settings.merchantLocation', + + /** + * @deprecated tag:v10.0.0 - Will be removed without replacement. + */ + 'SwagPayPal.settings.plusCheckoutEnabled', +] as const; + +export type SYSTEM_CONFIG = typeof SYSTEM_CONFIGS[number]; diff --git a/src/Resources/app/administration/src/global.types.ts b/src/Resources/app/administration/src/global.types.ts index 85965f590..cd27c96b6 100644 --- a/src/Resources/app/administration/src/global.types.ts +++ b/src/Resources/app/administration/src/global.types.ts @@ -6,6 +6,8 @@ import type SwagPaypalNotificationMixin from './mixin/swag-paypal-notification.m import type SwagPaypalCredentialsLoaderMixin from './mixin/swag-paypal-credentials-loader.mixin'; import type SwagPaypalPosCatchErrorMixin from './mixin/swag-paypal-pos-catch-error.mixin'; import type SwagPaypalPosLogLabelMixin from './mixin/swag-paypal-pos-log-label.mixin'; +import type SwagPaypalSettingsMixin from './mixin/swag-paypal-settings.mixin'; +import type SwagPaypalMerchantInformationMixin from './mixin/swag-paypal-merchant-information.mixin'; import type SwagPayPalApiCredentialsService from './core/service/api/swag-paypal-api-credentials.service'; import type SwagPayPalDisputeApiService from './core/service/api/swag-paypal-dispute.api.service'; import type SwagPayPalOrderService from './core/service/api/swag-paypal-order.service'; @@ -16,6 +18,8 @@ import type SwagPayPalPosWebhookRegisterService from './core/service/api/swag-pa import type SwagPayPalPosApiService from './core/service/api/swag-paypal-pos.api.service'; import type SwagPayPalWebhookService from './core/service/api/swag-paypal-webhook.service'; import type SwagPayPalSettingsService from './core/service/api/swag-paypal-settings.service'; +import type { MerchantInformationStore } from './app/store/swag-paypal-merchant-information.store'; +import type { SettingsStore } from './app/store/swag-paypal-settings.store'; declare global { type TEntity = Entity; @@ -29,6 +33,8 @@ declare global { 'swag-paypal-notification': typeof SwagPaypalNotificationMixin; 'swag-paypal-pos-catch-error': typeof SwagPaypalPosCatchErrorMixin; 'swag-paypal-pos-log-label': typeof SwagPaypalPosLogLabelMixin; + 'swag-paypal-settings': typeof SwagPaypalSettingsMixin; + 'swag-paypal-merchant-information': typeof SwagPaypalMerchantInformationMixin; } interface ServiceContainer { @@ -43,6 +49,10 @@ declare global { SwagPayPalDisputeApiService: SwagPayPalDisputeApiService; SwagPayPalSettingsService: SwagPayPalSettingsService; } + + interface PiniaRootState { + swagPayPalMerchantInformation: MerchantInformationStore; + swagPayPalSettings: SettingsStore; } } diff --git a/src/Resources/app/administration/src/main.ts b/src/Resources/app/administration/src/main.ts index 43bdb06c2..c4cc8be96 100644 --- a/src/Resources/app/administration/src/main.ts +++ b/src/Resources/app/administration/src/main.ts @@ -4,6 +4,8 @@ import './mixin/swag-paypal-credentials-loader.mixin'; import './mixin/swag-paypal-notification.mixin'; import './mixin/swag-paypal-pos-catch-error.mixin'; import './mixin/swag-paypal-pos-log-label.mixin'; +import './mixin/swag-paypal-settings.mixin'; +import './mixin/swag-paypal-merchant-information.mixin'; import './module/extension'; import './module/swag-paypal-disputes'; @@ -20,6 +22,7 @@ const bootPromise = window.Shopware ? Shopware.Plugin.addBootPromise() : () => { (async () => { if (Shopware.Feature.isActive('PAYPAL_SETTINGS_TWEAKS')) { + await import('./app'); } else { await import('./module/swag-paypal'); } diff --git a/src/Resources/app/administration/src/mixin/swag-paypal-merchant-information.mixin.ts b/src/Resources/app/administration/src/mixin/swag-paypal-merchant-information.mixin.ts new file mode 100644 index 000000000..ec5aee2a7 --- /dev/null +++ b/src/Resources/app/administration/src/mixin/swag-paypal-merchant-information.mixin.ts @@ -0,0 +1,56 @@ +import type * as PayPal from 'src/types'; + +export default Shopware.Mixin.register('swag-paypal-merchant-information', Shopware.Component.wrapComponentConfig({ + inject: [ + 'SwagPayPalApiCredentialsService', + ], + + mixins: [ + Shopware.Mixin.getByName('swag-paypal-notification'), + ], + + computed: { + merchantInformationStore() { + return Shopware.Store.get('swagPayPalMerchantInformation'); + }, + }, + + watch: { + 'merchantInformationStore.salesChannel': { + immediate: true, + handler(salesChannel: string | null) { + this.fetchMerchantInformation(salesChannel); + }, + }, + 'savingSettings'(savingSettings: string) { + if (savingSettings === 'success') { + this.fetchMerchantInformation(this.merchantInformationStore.salesChannel); + } + }, + }, + + beforeCreate() { + // computed properties aren't ready yet + Shopware.Store.get('swagPayPalMerchantInformation').$reset(); + }, + + methods: { + async fetchMerchantInformation(salesChannel: string | null) { + if (this.merchantInformationStore.has(salesChannel)) { + return; + } + + const merchantInformation = await this.SwagPayPalApiCredentialsService + .getMerchantInformation(salesChannel) + .catch((errorResponse: PayPal.ServiceError) => { + this.createNotificationFromError({ errorResponse }); + + return null; + }); + + if (merchantInformation) { + this.merchantInformationStore.set(salesChannel, merchantInformation); + } + }, + }, +})); diff --git a/src/Resources/app/administration/src/mixin/swag-paypal-settings.mixin.ts b/src/Resources/app/administration/src/mixin/swag-paypal-settings.mixin.ts new file mode 100644 index 000000000..14d67f46d --- /dev/null +++ b/src/Resources/app/administration/src/mixin/swag-paypal-settings.mixin.ts @@ -0,0 +1,92 @@ +import type * as PayPal from 'src/types'; + +export default Shopware.Mixin.register('swag-paypal-settings', Shopware.Component.wrapComponentConfig({ + inject: [ + 'systemConfigApiService', + 'SwagPayPalSettingsService', + ], + + mixins: [ + Shopware.Mixin.getByName('swag-paypal-notification'), + ], + + data(): { + savingSettings: 'none' | 'loading' | 'success'; + } { + return { + savingSettings: 'none', + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + }, + + watch: { + 'settingsStore.salesChannel': { + immediate: true, + handler(salesChannel: string | null) { + this.fetchSettings(salesChannel); + }, + }, + }, + + beforeCreate() { + // computed properties aren't ready yet + Shopware.Store.get('swagPayPalSettings').$reset(); + }, + + methods: { + async fetchSettings(salesChannel: string | null): Promise { + if (this.settingsStore.hasConfig(salesChannel)) { + return; + } + + const config = await this.systemConfigApiService.getValues('SwagPayPal.settings', salesChannel as null) as PayPal.SystemConfig; + + this.settingsStore.setConfig(salesChannel, config || {}); + }, + + async saveSettings(): Promise> | void> { + this.savingSettings = 'loading'; + + return this.SwagPayPalSettingsService.save(this.settingsStore.allConfigs) + .then((response) => { + Object.entries(response).forEach(([salesChannel, information]) => { + this.handleSettingsSaveInformation(salesChannel, information); + }); + + this.savingSettings = 'success'; + + setTimeout(() => { this.savingSettings = 'none'; }, 5000); + + return response; + }) + .catch((error: PayPal.ServiceError) => { + this.createNotificationError({ + title: this.$t('swag-paypal.notifications.save.title'), + message: this.$t('swag-paypal.notifications.save.errorMessage', { + message: this.createMessageFromError(error), + }), + }); + + this.savingSettings = 'none'; + }); + }, + + handleSettingsSaveInformation(salesChannel: string, information: PayPal.Setting<'settings_information'>) { + if (information.sandboxCredentialsChanged || information.liveCredentialsChanged) { + Shopware.Store.get('swagPayPalMerchantInformation').delete(salesChannel); + } + + information.webhookErrors.forEach((message) => { + this.createNotificationWarning({ + title: this.$t('swag-paypal.notifications.save.title'), + message: this.$t('swag-paypal.notifications.save.webhookMessage', { message }), + }); + }); + }, + }, +})); From 67af445b1c01b0ee5d7c0419e9df2c7bbf499d1b Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:06:46 +0100 Subject: [PATCH 07/11] POC - Added admin onboarding button --- .../swag-paypal-onboarding-button/index.ts | 255 ++++++++++++++++++ .../swag-paypal-onboarding-button.html.twig | 13 + .../swag-paypal-onboarding-button.scss | 21 ++ .../app/administration/src/app/index.ts | 2 + .../administration/src/app/snippet/de-DE.json | 14 + .../administration/src/app/snippet/en-GB.json | 14 + .../swag-paypal-credentials-loader.mixin.ts | 3 + .../administration/src/types/window-paypal.ts | 10 + 8 files changed, 332 insertions(+) create mode 100644 src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/index.ts create mode 100644 src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.html.twig create mode 100644 src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.scss diff --git a/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/index.ts b/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/index.ts new file mode 100644 index 000000000..c2c28e620 --- /dev/null +++ b/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/index.ts @@ -0,0 +1,255 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import template from './swag-paypal-onboarding-button.html.twig'; +import './swag-paypal-onboarding-button.scss'; + +/** + * @private - The component has a stable public API (props), but expect that implementation details may change. + */ +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'acl', + 'SwagPayPalApiCredentialsService', + ], + + emits: ['onboarded'], + + mixins: [ + Shopware.Mixin.getByName('notification'), + ], + + props: { + type: { + type: String as PropType<'live' | 'sandbox'>, + required: true, + }, + variant: { + type: String as PropType<'ghost' | 'link'>, + required: false, + default: 'ghost', + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + + data() { + return { + callbackId: Shopware.Utils.createId(), + + isLoading: false, + + scriptId: 'paypal-js', + scriptURL: 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js', + + live: { + partnerId: 'DYKPBPEAW5JNA', + partnerClientId: 'AR1aQ13lHxH1c6b3CDd8wSY6SWad2Lt5fv5WkNIZg-qChBoGNfHr2kT180otUmvE_xXtwkgahXUBBurW', + sellerNonce: `${Shopware.Utils.createId()}${Shopware.Utils.createId()}`, + }, + sandbox: { + partnerId: '45KXQA7PULGAG', + partnerClientId: 'AQ9g8qMYHpE8s028VCq_GO3Roy9pjeqGDjKTkR_sxzX0FtncBb3QUWbFtoQMtdpe2lG9NpnDT419dK8s', + sellerNonce: `${Shopware.Utils.createId()}${Shopware.Utils.createId()}`, + }, + commonRequestParams: { + channelId: 'partner', + product: 'ppcp', + secondaryProducts: 'advanced_vaulting,PAYMENT_METHODS', + capabilities: [ + 'APPLE_PAY', + 'GOOGLE_PAY', + 'PAY_UPON_INVOICE', + 'PAYPAL_WALLET_VAULTING_ADVANCED', + ].join(','), + integrationType: 'FO', + features: [ + 'PAYMENT', + 'REFUND', + 'READ_SELLER_DISPUTE', + 'UPDATE_SELLER_DISPUTE', + 'ADVANCED_TRANSACTIONS_SEARCH', + 'ACCESS_MERCHANT_INFORMATION', + 'TRACKING_SHIPMENT_READWRITE', + 'VAULT', + 'BILLING_AGREEMENT', + ].join(','), + displayMode: 'minibrowser', + partnerLogoUrl: 'https://assets.shopware.com/media/logos/shopware_logo_blue.svg', + }, + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + merchantInformationStore() { + return Shopware.Store.get('swagPayPalMerchantInformation'); + }, + + isSandbox() { + return this.type === 'sandbox'; + }, + + suffix() { + return this.isSandbox ? 'Sandbox' : ''; + }, + + returnUrl(): string { + return `${window.location.origin}${window.location.pathname}#${this.$route.path}`; + }, + + requestParams() { + return this.isSandbox ? this.sandbox : this.live; + }, + + onboardingUrl() { + const url = new URL('/bizsignup/partner/entry', this.isSandbox ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com'); + + url.search = (new URLSearchParams({ + ...this.commonRequestParams, + ...this.requestParams, + returnToPartnerUrl: this.returnUrl, + })).toString(); + + return url.href; + }, + + buttonTitle(): string { + if (!this.settingsStore.get(`SwagPayPal.settings.clientSecret${this.suffix}`)) { + return this.$t(`swag-paypal-onboarding-button.${this.type}.title`); + } + + if (!this.merchantInformationStore.canPPCP) { + return this.$t(`swag-paypal-onboarding-button.${this.type}.onboardingTitle`); + } + + if (this.merchantInformationStore.needsReonboarding) { + return this.$t(`swag-paypal-onboarding-button.${this.type}.restartOnboardingTitle`); + } + + return this.$t(`swag-paypal-onboarding-button.${this.type}.changeTitle`); + }, + + callbackName(): `onboardingCallback${string}` { + return `onboardingCallback${this.callbackId}`; + }, + + isDisabled() { + return !this.acl.can('swag_paypal.editor') || this.isLoading || this.disabled; + }, + + classes() { + return { + 'is--sandbox': this.isSandbox, + 'is--live': !this.isSandbox, + 'is--link': this.variant === 'link', + 'sw-button': this.variant === 'ghost', + 'sw-button--ghost': this.variant === 'ghost', + 'sw-button--disabled': this.isDisabled, + }; + }, + }, + + mounted() { + if (!this.acl.can('swag_paypal.editor')) { + return; + } + + window[this.callbackName] = (authCode, sharedId) => { + this.fetchCredentials(authCode, sharedId); + }; + + this.loadPayPalScript(); + }, + + beforeUnmount() { + delete window[this.callbackName]; + }, + + methods: { + createScriptElement(): HTMLScriptElement { + const payPalScript = document.createElement('script'); + payPalScript.id = this.scriptId; + payPalScript.type = 'text/javascript'; + payPalScript.src = this.scriptURL; + payPalScript.async = true; + + document.head.appendChild(payPalScript); + + return payPalScript; + }, + + loadPayPalScript() { + const el = document.getElementById(this.scriptId) ?? this.createScriptElement(); + + if (window.PAYPAL) { + window.PAYPAL.apps.Signup.setup(); + } else { + el.addEventListener('load', this.renderPayPalButton.bind(this), false); + } + }, + + renderPayPalButton() { + // The original render function inside the partner.js is overwritten here. + // The function gets overwritten again, as soon as PayPals signup.js is loaded. + // A loop is created and the render() function is executed until the real render() function is available. + // PayPal does originally nearly the same, but only once and not in a loop. + // If the signup.js is loaded to slow the button is not rendered. + window.PAYPAL!.apps.Signup.render = function proxyPPrender() { + if (window.PAYPAL!.apps.Signup.timeout) { + clearTimeout(window.PAYPAL!.apps.Signup.timeout); + } + + window.PAYPAL!.apps.Signup.timeout = setTimeout(window.PAYPAL!.apps.Signup.render, 300); + }; + + window.PAYPAL!.apps.Signup.render(); + }, + + async fetchCredentials(authCode: string, sharedId: string) { + if (this.isLoading) { + return; + } + + const response = await this.SwagPayPalApiCredentialsService.getApiCredentials( + authCode, + sharedId, + this.requestParams.sellerNonce, + this.isSandbox, + ).catch(() => { + this.createNotificationError({ + message: this.$t('swag-paypal.settingForm.credentials.button.messageFetchedError'), + // @ts-expect-error - wrongly typed as string + duration: 10000, + }); + + return {} as Record; + }); + + this.setConfig(response.client_id, response.client_secret, response.payer_id); + + this.isLoading = false; + }, + + setConfig(clientId?: string, clientSecret?: string, merchantPayerId?: string) { + this.settingsStore.set(`SwagPayPal.settings.clientId${this.suffix}`, clientId); + this.settingsStore.set(`SwagPayPal.settings.clientSecret${this.suffix}`, clientSecret); + this.settingsStore.set(`SwagPayPal.settings.merchantPayerId${this.suffix}`, merchantPayerId); + + // First time onboarding + if (!this.merchantInformationStore.canPPCP) { + this.settingsStore.set('SwagPayPal.settings.sandbox', this.isSandbox); + } + + this.$emit('onboarded'); + }, + }, +}); diff --git a/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.html.twig b/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.html.twig new file mode 100644 index 000000000..c8229e21f --- /dev/null +++ b/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.html.twig @@ -0,0 +1,13 @@ + + + {{ buttonTitle }} + + diff --git a/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.scss b/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.scss new file mode 100644 index 000000000..ffd0a2f92 --- /dev/null +++ b/src/Resources/app/administration/src/app/component/swag-paypal-onboarding-button/swag-paypal-onboarding-button.scss @@ -0,0 +1,21 @@ +@import "~scss/variables"; + +.swag-paypal-onboarding-button { + display: flex; + align-items: center; + gap: 12px; + + &.is--link { + font-weight: $font-weight-semi-bold; + font-size: $font-size-xs; + } + + // Multiple buttons side by side and visible + &:not([style*="none"]) + & { + margin-left: 12px; + } +} + +.isu-minibrowser-component { + z-index: 2000; // above all shopware components (like modals) +} diff --git a/src/Resources/app/administration/src/app/index.ts b/src/Resources/app/administration/src/app/index.ts index bba7fe5f3..21fb603b3 100644 --- a/src/Resources/app/administration/src/app/index.ts +++ b/src/Resources/app/administration/src/app/index.ts @@ -1,5 +1,7 @@ import './store/swag-paypal-merchant-information.store'; import './store/swag-paypal-settings.store'; + +Shopware.Component.register('swag-paypal-onboarding-button', () => import('./component/swag-paypal-onboarding-button')); Shopware.Component.register('swag-paypal-setting', () => import('./component/swag-paypal-setting')); // synchronise salesChannel of stores diff --git a/src/Resources/app/administration/src/app/snippet/de-DE.json b/src/Resources/app/administration/src/app/snippet/de-DE.json index 1b6cbbafc..bfeb3a110 100644 --- a/src/Resources/app/administration/src/app/snippet/de-DE.json +++ b/src/Resources/app/administration/src/app/snippet/de-DE.json @@ -14,6 +14,20 @@ } } }, + "swag-paypal-onboarding-button": { + "sandbox": { + "title": "PayPal-Sandbox-Konto verbinden", + "changeTitle": "Anderes Sandbox-Konto verbinden", + "onboardingTitle": "PayPal Checkout-Sandbox-Onboarding starten", + "restartOnboardingTitle": "PayPal Checkout-Sandbox-Onboarding wiederholen" + }, + "live": { + "title": "PayPal-Konto verbinden", + "changeTitle": "Anderes Konto verbinden", + "onboardingTitle": "PayPal Checkout-Onboarding starten", + "restartOnboardingTitle": "PayPal Checkout-Onboarding wiederholen" + } + }, "swag-paypal-setting": { "label": { "clientId": "Client-ID", diff --git a/src/Resources/app/administration/src/app/snippet/en-GB.json b/src/Resources/app/administration/src/app/snippet/en-GB.json index 59f594b62..2b4fc0678 100644 --- a/src/Resources/app/administration/src/app/snippet/en-GB.json +++ b/src/Resources/app/administration/src/app/snippet/en-GB.json @@ -14,6 +14,20 @@ } } }, + "swag-paypal-onboarding-button": { + "sandbox": { + "title": "Connect sandbox account", + "changeTitle": "Connect different sandbox account", + "onboardingTitle": "Start PayPal Checkout sandbox onboarding", + "restartOnboardingTitle": "Restart PayPal Checkout sandbox onboarding" + }, + "live": { + "title": "Connect PayPal account", + "changeTitle": "Connect different account", + "onboardingTitle": "Start PayPal Checkout onboarding", + "restartOnboardingTitle": "Restart PayPal Checkout onboarding" + } + }, "swag-paypal-setting": { "label": { "clientId": "Client ID", diff --git a/src/Resources/app/administration/src/mixin/swag-paypal-credentials-loader.mixin.ts b/src/Resources/app/administration/src/mixin/swag-paypal-credentials-loader.mixin.ts index edce8b91d..75afad4d7 100644 --- a/src/Resources/app/administration/src/mixin/swag-paypal-credentials-loader.mixin.ts +++ b/src/Resources/app/administration/src/mixin/swag-paypal-credentials-loader.mixin.ts @@ -1,5 +1,8 @@ const { debug } = Shopware.Utils; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-onboarding-button` + */ export default Shopware.Mixin.register('swag-paypal-credentials-loader', Shopware.Component.wrapComponentConfig({ inject: ['SwagPayPalApiCredentialsService'], diff --git a/src/Resources/app/administration/src/types/window-paypal.ts b/src/Resources/app/administration/src/types/window-paypal.ts index ec916c3c2..a713b8a45 100644 --- a/src/Resources/app/administration/src/types/window-paypal.ts +++ b/src/Resources/app/administration/src/types/window-paypal.ts @@ -10,6 +10,7 @@ export declare type AppSignup = { }; render: () => void; + setup: () => void; timeout?: NodeJS.Timeout; }; @@ -28,8 +29,17 @@ declare global { interface Window { PAYPAL?: PAYPAL; + /** + * @deprecated tag:v10.0.0 - Will be removed. + */ onboardingCallbackLive?: (authCode: string, sharedId: string) => void; + + /** + * @deprecated tag:v10.0.0 - Will be removed. + */ onboardingCallbackSandbox?: (authCode: string, sharedId: string) => void; + + [key: `onboardingCallback${string}`]: undefined | ((authCode: string, sharedId: string) => void); } } From 003eff6d6eb3b3d5cdd7cc109456e327920dff32 Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:07:57 +0100 Subject: [PATCH 08/11] POC - Moved acl config --- .../app/administration/src/app/acl/index.ts | 66 +++++++++++++++++++ .../app/administration/src/app/index.ts | 1 + .../src/module/swag-paypal/acl/index.ts | 6 ++ 3 files changed, 73 insertions(+) create mode 100644 src/Resources/app/administration/src/app/acl/index.ts diff --git a/src/Resources/app/administration/src/app/acl/index.ts b/src/Resources/app/administration/src/app/acl/index.ts new file mode 100644 index 000000000..9a7ba0fc0 --- /dev/null +++ b/src/Resources/app/administration/src/app/acl/index.ts @@ -0,0 +1,66 @@ +Shopware.Service('privileges').addPrivilegeMappingEntry({ + category: 'permissions', + parent: 'swag_paypal', + key: 'swag_paypal', + roles: { + viewer: { + privileges: [ + 'sales_channel:read', + 'sales_channel_payment_method:read', + 'system_config:read', + ], + dependencies: [], + }, + editor: { + privileges: [ + 'sales_channel:update', + 'sales_channel_payment_method:create', + 'sales_channel_payment_method:update', + 'system_config:update', + 'system_config:create', + 'system_config:delete', + ], + dependencies: [ + 'swag_paypal.viewer', + ], + }, + }, +}); + +Shopware.Service('privileges').addPrivilegeMappingEntry({ + category: 'permissions', + parent: null, + key: 'sales_channel', + roles: { + viewer: { + privileges: [ + 'swag_paypal_pos_sales_channel:read', + 'swag_paypal_pos_sales_channel_run:read', + 'swag_paypal_pos_sales_channel_run:update', + 'swag_paypal_pos_sales_channel_run:create', + 'swag_paypal_pos_sales_channel_run_log:read', + 'sales_channel_payment_method:read', + ], + }, + editor: { + privileges: [ + 'swag_paypal_pos_sales_channel:update', + 'swag_paypal_pos_sales_channel_run:delete', + 'payment_method:update', + ], + }, + creator: { + privileges: [ + 'swag_paypal_pos_sales_channel:create', + 'payment_method:create', + 'shipping_method:create', + 'delivery_time:create', + ], + }, + deleter: { + privileges: [ + 'swag_paypal_pos_sales_channel:delete', + ], + }, + }, +}); diff --git a/src/Resources/app/administration/src/app/index.ts b/src/Resources/app/administration/src/app/index.ts index 21fb603b3..4c7c83e1b 100644 --- a/src/Resources/app/administration/src/app/index.ts +++ b/src/Resources/app/administration/src/app/index.ts @@ -1,3 +1,4 @@ +import './acl'; import './store/swag-paypal-merchant-information.store'; import './store/swag-paypal-settings.store'; diff --git a/src/Resources/app/administration/src/module/swag-paypal/acl/index.ts b/src/Resources/app/administration/src/module/swag-paypal/acl/index.ts index 9a7ba0fc0..61024854a 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/acl/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/acl/index.ts @@ -1,3 +1,6 @@ +/** + * @deprecated tag:v10.0.0 - Will be moved to app directory + */ Shopware.Service('privileges').addPrivilegeMappingEntry({ category: 'permissions', parent: 'swag_paypal', @@ -27,6 +30,9 @@ Shopware.Service('privileges').addPrivilegeMappingEntry({ }, }); +/** + * @deprecated tag:v10.0.0 - Will be moved to app directory + */ Shopware.Service('privileges').addPrivilegeMappingEntry({ category: 'permissions', parent: null, From 78a2bfc6aa95cc1ae8eeb9972f325d788915da2e Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:16:29 +0100 Subject: [PATCH 09/11] POC - Reworked settings page --- src/Resources/app/administration/src/main.ts | 2 + .../swag-paypal-settings-icon/index.ts | 8 + .../swag-paypal-settings-icon.html.twig | 1 + .../swag-paypal-settings-icon.scss | 15 ++ .../index.ts | 61 +++++ ...ag-paypal-settings-locale-select.html.twig | 29 +++ .../swag-paypal-settings-locale-select.scss | 13 + .../index.ts | 93 +++++++ ...al-settings-sales-channel-switch.html.twig | 33 +++ .../swag-paypal-settings-webhook/index.ts | 119 +++++++++ .../swag-paypal-settings-webhook.html.twig | 35 +++ .../swag-paypal-settings-webhook.scss | 31 +++ .../src/module/swag-paypal-settings/index.ts | 67 +++++ .../page/swag-paypal-settings/index.ts | 23 ++ .../swag-paypal-settings.html.twig | 61 +++++ .../swag-paypal-settings.scss | 16 ++ .../swag-paypal-settings/snippet/de-DE.json | 114 +++++++++ .../swag-paypal-settings/snippet/en-GB.json | 114 +++++++++ .../swag-paypal-settings-advanced/index.ts | 27 +++ .../swag-paypal-settings-advanced.html.twig | 23 ++ .../swag-paypal-settings-advanced.scss | 23 ++ .../swag-paypal-settings-general/index.ts | 103 ++++++++ .../swag-paypal-settings-general.html.twig | 229 ++++++++++++++++++ .../swag-paypal-settings-general.scss | 28 +++ .../swag-paypal-settings-storefront/index.ts | 68 ++++++ .../swag-paypal-settings-storefront.html.twig | 98 ++++++++ .../components/swag-paypal-acdc/index.ts | 3 + .../components/swag-paypal-behavior/index.ts | 3 + .../swag-paypal-credentials/index.ts | 3 + .../swag-paypal-cross-border/index.ts | 3 + .../components/swag-paypal-express/index.ts | 4 + .../swag-paypal-inherit-wrapper/index.ts | 2 + .../swag-paypal-installment/index.ts | 3 + .../swag-paypal-locale-field/index.ts | 3 + .../index.ts | 3 + .../components/swag-paypal-pui/index.ts | 3 + .../swag-paypal-settings-hint/index.ts | 3 + .../swag-paypal-settings-icon/index.ts | 3 + .../components/swag-paypal-spb/index.ts | 3 + .../components/swag-paypal-vaulting/index.ts | 3 + .../components/swag-paypal-webhook/index.ts | 3 + .../src/module/swag-paypal/index.ts | 3 + .../swag-paypal/page/swag-paypal/index.ts | 3 + 43 files changed, 1485 insertions(+) create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/swag-paypal-settings-sales-channel-switch.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/snippet/de-DE.json create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/snippet/en-GB.json create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/swag-paypal-settings-storefront.html.twig diff --git a/src/Resources/app/administration/src/main.ts b/src/Resources/app/administration/src/main.ts index c4cc8be96..655527f84 100644 --- a/src/Resources/app/administration/src/main.ts +++ b/src/Resources/app/administration/src/main.ts @@ -23,6 +23,8 @@ const bootPromise = window.Shopware ? Shopware.Plugin.addBootPromise() : () => { (async () => { if (Shopware.Feature.isActive('PAYPAL_SETTINGS_TWEAKS')) { await import('./app'); + // @ts-expect-error - yes it's not a module + await import('./module/swag-paypal-settings'); } else { await import('./module/swag-paypal'); } diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/index.ts new file mode 100644 index 000000000..2038e8487 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/index.ts @@ -0,0 +1,8 @@ +import template from './swag-paypal-settings-icon.html.twig'; +import './swag-paypal-settings-icon.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.html.twig new file mode 100644 index 000000000..7d48f247a --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.html.twig @@ -0,0 +1 @@ + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.scss b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.scss new file mode 100644 index 000000000..311281b5a --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-icon/swag-paypal-settings-icon.scss @@ -0,0 +1,15 @@ +@import "~scss/mixins"; + +.swag-paypal-settings-icon { + @include size(24px); + + display: inline-block; + vertical-align: middle; + line-height: 0; + + > svg { + width: 100%; + height: 100%; + vertical-align: middle; + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/index.ts new file mode 100644 index 000000000..63b14efcc --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/index.ts @@ -0,0 +1,61 @@ +import template from './swag-paypal-settings-locale-select.html.twig'; +import './swag-paypal-settings-locale-select.scss'; +import { LOCALES, type LOCALE } from '../../../../constant/swag-paypal-settings.constant'; + +type LocaleOption = { + value: string | null; + dashed: string | null; + label: string; +}; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + props: { + value: { + type: String as PropType, + required: false, + default: null, + }, + }, + + computed: { + options(): LocaleOption[] { + const options = LOCALES.map((locale) => this.toOption(locale)); + + options.splice(0, 0, { + value: null, + dashed: null, + label: this.$t('swag-paypal-settings-locale-select.automatic'), + }); + + if (this.invalidError) { + options.splice(0, 0, this.toOption(this.value)); + } + + return options; + }, + + invalidError() { + if (this.value && !LOCALES.includes(this.value)) { + return { detail: this.$t('swag-paypal-settings-locale-select.invalid') }; + } + + return undefined; + }, + }, + + methods: { + toOption(locale: string): LocaleOption { + const localeDash = locale.replace('_', '-'); + + return { + value: locale, + dashed: localeDash, + label: this.$te(`locale.${localeDash}`) ? this.$t(`locale.${localeDash}`) : localeDash, + }; + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.html.twig new file mode 100644 index 000000000..47f9ce395 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.html.twig @@ -0,0 +1,29 @@ + + + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.scss b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.scss new file mode 100644 index 000000000..6f923c693 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-locale-select/swag-paypal-settings-locale-select.scss @@ -0,0 +1,13 @@ +@import "~scss/variables"; + +.swag-paypal-settings-locale-select { + &__locale_info { + .sw-highlight-text, .sw-select-result__result-item-text { + display: inline; + } + } + + &__locale { + color: $color-gray-600; + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/index.ts new file mode 100644 index 000000000..d39f56079 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/index.ts @@ -0,0 +1,93 @@ +import template from './swag-paypal-settings-sales-channel-switch.html.twig'; + +const { Defaults } = Shopware; +const { Criteria } = Shopware.Data; + +type SalesChannel = { + value: string | null; + label: string; +} + +/** + * @private + */ +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'acl', + 'repositoryFactory', + 'SwagPaypalPaymentMethodService', + ], + + data(): { + isLoading: boolean; + salesChannels: SalesChannel[]; + defaultPaymentMethods: 'none' | 'loading' | 'success'; + } { + return { + isLoading: true, + salesChannels: [{ + value: null, + label: this.$t('sw-sales-channel-switch.labelDefaultOption'), + }], + defaultPaymentMethods: 'none', + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + salesChannelRepository() { + return this.repositoryFactory.create('sales_channel'); + }, + + salesChannelCriteria(): TCriteria { + const criteria = new Criteria(1, 500); + + criteria.addFilter(Criteria.equalsAny('typeId', [ + Defaults.storefrontSalesChannelTypeId, + Defaults.apiSalesChannelTypeId, + ])); + + return criteria; + }, + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.fetchSalesChannels(); + }, + + async fetchSalesChannels() { + try { + const salesChannels = await this.salesChannelRepository.search(this.salesChannelCriteria, Shopware.Context.api); + + salesChannels.forEach((salesChannel) => { + this.salesChannels.push({ + value: salesChannel.id, + label: salesChannel.translated?.name || salesChannel.name, + }); + }); + } finally { + this.isLoading = false; + } + }, + + onSetPaymentMethodDefault() { + this.defaultPaymentMethods = 'loading'; + + this.SwagPaypalPaymentMethodService.setDefaultPaymentForSalesChannel(this.settingsStore.salesChannel) + .then(() => { this.defaultPaymentMethods = 'success'; }) + .catch(() => { this.defaultPaymentMethods = 'none'; }); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/swag-paypal-settings-sales-channel-switch.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/swag-paypal-settings-sales-channel-switch.html.twig new file mode 100644 index 000000000..d9a3dca76 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-sales-channel-switch/swag-paypal-settings-sales-channel-switch.html.twig @@ -0,0 +1,33 @@ + + + + + + + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/index.ts new file mode 100644 index 000000000..8ce47d538 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/index.ts @@ -0,0 +1,119 @@ +import type * as PayPal from 'src/types'; +import template from './swag-paypal-settings-webhook.html.twig'; +import './swag-paypal-settings-webhook.scss'; + +const STATUS_WEBHOOK_MISSING = 'missing'; +const STATUS_WEBHOOK_INVALID = 'invalid'; +const STATUS_WEBHOOK_VALID = 'valid'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'acl', + 'SwagPayPalWebhookService', + ], + + mixins: [ + Shopware.Mixin.getByName('swag-paypal-notification'), + ], + + data(): { + allWebhookStatus: Record; + status: 'none' | 'fetching' | 'refreshing'; + } { + return { + allWebhookStatus: {}, + status: 'none', + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + webhookStatus(): string | undefined { + return this.allWebhookStatus[String(this.settingsStore.salesChannel)]; + }, + + webhookStatusLabel() { + return this.$t(`swag-paypal-settings.webhook.status.${this.webhookStatus || 'unknown'}`); + }, + + webhookStatusVariant(): 'danger' | 'warning' | 'success' | 'neutral' { + switch (this.webhookStatus) { + case STATUS_WEBHOOK_MISSING: + return 'danger'; + + case STATUS_WEBHOOK_INVALID: + return 'warning'; + + case STATUS_WEBHOOK_VALID: + return 'success'; + + default: + return 'neutral'; + } + }, + + allowRefresh(): boolean { + return [STATUS_WEBHOOK_INVALID, STATUS_WEBHOOK_MISSING] + .includes(this.webhookStatus ?? ''); + }, + }, + + watch: { + 'settingsStore.salesChannel': { + immediate: true, + handler() { + this.fetchWebhookStatus(this.settingsStore.salesChannel); + }, + }, + }, + + methods: { + fetchWebhookStatus(salesChannelId: string | null) { + if (this.webhookStatus) { + return; + } + + this.status = 'fetching'; + + this.SwagPayPalWebhookService.status(salesChannelId) + .then((response) => { + this.allWebhookStatus[String(salesChannelId)] = response.result; + this.status = 'none'; + }) + .catch((errorResponse: PayPal.ServiceError) => { + this.createNotificationError({ + title: this.$t('swag-paypal.notifications.webhook.title'), + message: this.$t('swag-paypal.notifications.webhook.errorMessage', { + message: this.createMessageFromError(errorResponse), + }), + }); + }); + }, + + async onRefreshWebhook() { + this.status = 'refreshing'; + + await this.SwagPayPalWebhookService + .register(this.settingsStore.salesChannel) + .catch((errorResponse: PayPal.ServiceError) => { + this.createNotificationError({ + title: this.$t('swag-paypal.notifications.webhook.title'), + message: this.$t('swag-paypal.notifications.webhook.errorMessage', { + message: this.createMessageFromError(errorResponse), + }), + }); + }); + + this.status = 'none'; + + this.fetchWebhookStatus(this.settingsStore.salesChannel); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.html.twig new file mode 100644 index 000000000..26195801f --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.html.twig @@ -0,0 +1,35 @@ + + + + + {{ $t('swag-paypal-settings.webhook.info') }} + + + + {{ $t('swag-paypal-settings.webhook.buttonRefresh') }} + + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.scss b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.scss new file mode 100644 index 000000000..a38ad8a79 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/components/swag-paypal-settings-webhook/swag-paypal-settings-webhook.scss @@ -0,0 +1,31 @@ +@import "~scss/variables"; + +.swag-paypal-settings-webhook { + .sw-card__titles { + display: grid; + grid-template-columns: min-content min-content; + gap: 12px; + place-items: center; + } + + .sw-card__content { + display: grid; + gap: 16px; + place-items: end; + font-size: $font-size-xs; + line-height: $line-height-sm; + } + + &__label { + border: 0; + margin: 0; + + .sw-label__caption { + overflow: visible; + } + + .sw-color-badge { + margin: 0 0 0 2px; + } + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/index.ts new file mode 100644 index 000000000..67565d769 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/index.ts @@ -0,0 +1,67 @@ +Shopware.Component.register('swag-paypal-settings-icon', () => import('./components/swag-paypal-settings-icon')); +Shopware.Component.register('swag-paypal-settings-locale-select', () => import('./components/swag-paypal-settings-locale-select')); +Shopware.Component.register('swag-paypal-settings-sales-channel-switch', () => import('./components/swag-paypal-settings-sales-channel-switch')); +Shopware.Component.register('swag-paypal-settings-webhook', () => import('./components/swag-paypal-settings-webhook')); + +Shopware.Component.register('swag-paypal-settings-advanced', () => import('./view/swag-paypal-settings-advanced')); +Shopware.Component.register('swag-paypal-settings-general', () => import('./view/swag-paypal-settings-general')); +Shopware.Component.register('swag-paypal-settings-storefront', () => import('./view/swag-paypal-settings-storefront')); + +Shopware.Component.register('swag-paypal-settings', () => import('./page/swag-paypal-settings')); + +Shopware.Module.register('swag-paypal-settings', { + type: 'plugin', + name: 'SwagPayPalSettings', + title: 'swag-paypal-settings.module.title', + description: 'swag-paypal-settings.module.description', + version: '1.0.0', + targetVersion: '1.0.0', + color: '#9AA8B5', + icon: 'regular-cog', + + routes: { + index: { + component: 'swag-paypal-settings', + path: 'index', + redirect: { name: 'swag.paypal.settings.index.general' }, + meta: { + parentPath: 'sw.settings.index.plugins', + privilege: 'swag_paypal.viewer', + }, + children: { + general: { + path: 'general', + component: 'swag-paypal-settings-general', + meta: { + privilege: 'swag_paypal.viewer', + parentPath: 'sw.settings.index', + }, + }, + storefront: { + path: 'storefront', + component: 'swag-paypal-settings-storefront', + meta: { + privilege: 'swag_paypal.viewer', + parentPath: 'sw.settings.index', + }, + }, + advanced: { + path: 'advanced', + component: 'swag-paypal-settings-advanced', + meta: { + privilege: 'swag_paypal.viewer', + parentPath: 'sw.settings.index', + }, + }, + }, + }, + }, + + settingsItem: { + group: 'plugins', + to: 'swag.paypal.settings.index', + iconComponent: 'swag-paypal-settings-icon', + backgroundEnabled: true, + privilege: 'swag_paypal.viewer', + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/index.ts new file mode 100644 index 000000000..a1bfb7056 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/index.ts @@ -0,0 +1,23 @@ +import template from './swag-paypal-settings.html.twig'; +import './swag-paypal-settings.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'acl', + ], + + mixins: [ + Shopware.Mixin.getByName('swag-paypal-settings'), + Shopware.Mixin.getByName('swag-paypal-merchant-information'), + ], + + metaInfo() { + return { + title: this.$createTitle(), + }; + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.html.twig new file mode 100644 index 000000000..4ca7d6e79 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.html.twig @@ -0,0 +1,61 @@ + + + + + + + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.scss b/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.scss new file mode 100644 index 000000000..090c04d9e --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/page/swag-paypal-settings/swag-paypal-settings.scss @@ -0,0 +1,16 @@ +.swag-paypal-settings { + // Remove margin from first and last field in a card + .sw-card__content { + :nth-last-child(1 of :not(.sw-loader)) { + .sw-field--switch, .sw-field { + margin-bottom: 0; + } + } + + :first-child { + .sw-field--switch, .sw-field { + margin-top: 0; + } + } + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/snippet/de-DE.json b/src/Resources/app/administration/src/module/swag-paypal-settings/snippet/de-DE.json new file mode 100644 index 000000000..96d88eaf8 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/snippet/de-DE.json @@ -0,0 +1,114 @@ +{ + "swag-paypal": { + "notifications": { + "test": { + "title": "PayPal API-Zugangsdaten", + "errorMessage": "Die Validation ist fehlgeschlagen:
{message}", + "successMessage": "Die Zugangsdaten sind gültig." + }, + "webhook": { + "title": "PayPal Webhook", + "errorMessage": "Der Webhook konnte nicht aktualisiert werden:
{message}", + "successMessage": "Die Aktualisierung war erfolgreich." + } + } + }, + "swag-paypal-settings": { + "header": "PayPal", + "module": { + "title": "PayPal", + "description": "Einstellungen für PayPal" + }, + "tabs": { + "general": "Allgemein", + "storefront": "Storefront-Darstellung", + "advanced": "Fortgeschritten" + }, + "options": { + "intent": { + "CAPTURE": "Automatischer Zahlungseinzug (Intent CAPTURE)", + "AUTHORIZE": "Manueller Zahlungseinzug (Intent AUTHORIZE)" + }, + "landingPage": { + "LOGIN": "Anmeldung", + "GUEST_CHECKOUT": "Gast-Checkout", + "NO_PREFERENCE": "Keine Präferenz" + }, + "buttonColor": { + "blue": "Blau", + "black": "Schwarz", + "gold": "Gold (empfohlen)", + "silver": "Silber", + "white": "Weiß" + }, + "buttonShape": { + "sharp": "Rechteckig mit spitzen Ecken", + "pill": "Rund", + "rect": "Rechteckig mit abgerundeten Ecken" + } + }, + "credentialsLive": { + "title": "Live-API-Zugangsdaten", + "test": "Testen" + }, + "credentialsSandbox": { + "title": "Sandbox-API-Zugangsdaten", + "test": "Testen" + }, + "behavior": { + "title": "Verhalten" + }, + "vaulting": { + "title": "Vaulting", + "descriptionTitle": "Einmal-Paypal-Checkout", + "descriptionText": "PayPal-Kontos mit aktiviertem 'Vaulting' können kontinuierlich belastet werden, ohne dass Deine Kunden während der Transaktionen anwesend sein müssen. Auch weitere Transaktionen können durchgeführt werden, ohne dass sich Deine Kunden erneut bei PayPal authentifizieren müssen.", + "activeButtonLabel": "Vaulting freischalten" + }, + "acdc": { + "title": "Kredit- oder Debitkarte" + }, + "pui": { + "title": "Rechnungskauf" + }, + "express": { + "title": "Express Checkout Shortcut", + "subtitle": "Express Checkouts erhöhen die Konversionsrate in Deinem Shop und stellen für Dich als Händler kein finanzielles Risiko dar, daher sollten sie immer aktiv sein.", + "alertMessage": "Double Opt-In für Gast-Bestellungen ist in einem Deiner Verkaufskanäle aktiviert. Diese Funktion ist nicht mit dem PayPal Express Checkout Shortcut kompatibel. Kunden, die sich über den Express Checkout Shortcut anmelden, erhalten keine Double-Opt-In-Verifizierungs-E-Mails." + }, + "installment": { + "title": "'Später Bezahlen'-Banner", + "subtitle": "Banner erhöhen die Konversionsrate in Deinem Shop und stellen für Dich als Händler kein finanzielles Risiko dar, daher sollten sie immer aktiv sein." + }, + "spb": { + "title": "Smart Payment Buttons" + }, + "webhook": { + "title": "Webhook", + "info": "Der Webhook dient dazu, den Status einer Transaktion asynchron zu aktualisieren. Falsch registrierte Webhooks, z.B. auf einem anderen oder nicht öffentlich zugänglichen Server, können zu unbestätigten Transaktionen führen, unabhängig davon, ob die Transaktion von PayPal verarbeitet wurde.", + "buttonRefresh": "Webhook aktualisieren", + "refreshFailed": "Aktualisierung des Webhooks ist fehlgeschlagen", + "fetchFailed": "Prüfen des Webhooks ist fehlgeschlagen", + "status": { + "unknown": "Laden...", + "missing": "Fehlend", + "invalid": "Registriert, aber mit abweichender Domain", + "valid": "Gültig" + } + }, + "crossBorder": { + "title": "Später bezahlen - Länderübergreifende Nachrichten", + "warning": "Bitte wenden Sie sich an Ihren PayPal-Vertreter, um diese Funktion für Ihr Konto zu aktivieren.", + "info": "Mit länderübergreifende Nachrichten für \"Später bezahlen\" wird die \"Später bezahlen\"-Nachricht in der Sprache des Kunden anzeigt. Diese Funktion ist nur für bestimmte Länder verfügbar. {0}", + "buyerCountryAuto": "Automatische Bestimmung", + "buyerCountryOverride": "Lokalisierung" + } + }, + "swag-paypal-settings-sales-channel-switch": { + "description": "Klicken, um PayPal im ausgewählten Sales Channel zu aktivieren und als Standardzahlungsart zu setzen", + "label": "Setze PayPal als Standard" + }, + "swag-paypal-settings-locale-select": { + "invalid": "Die angegebene Sprache ist ungültig. Bitte wähle eine aus der Liste aus.", + "automatic": "Automatisch (empfohlen)" + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/snippet/en-GB.json b/src/Resources/app/administration/src/module/swag-paypal-settings/snippet/en-GB.json new file mode 100644 index 000000000..8701f32e1 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/snippet/en-GB.json @@ -0,0 +1,114 @@ +{ + "swag-paypal": { + "notifications": { + "test": { + "title": "PayPal API credentials", + "errorMessage": "The validation failed:
{message}", + "successMessage": "The credentials are valid." + }, + "webhook": { + "title": "PayPal Webhook", + "errorMessage": "Failed to refresh webhook:
{message}", + "successMessage": "The webhook has been refreshed." + } + } + }, + "swag-paypal-settings": { + "header": "PayPal", + "module": { + "title": "PayPal", + "description": "PayPal settings" + }, + "tabs": { + "general": "General", + "storefront": "Storefront presentation", + "advanced": "Advanced" + }, + "options": { + "intent": { + "CAPTURE": "Automatic payment collection (intent CAPTURE)", + "AUTHORIZE": "Manual payment collection (intent AUTHORIZE)" + }, + "landingPage": { + "LOGIN": "Login", + "GUEST_CHECKOUT": "Guest Checkout", + "NO_PREFERENCE": "No preference" + }, + "buttonShape": { + "sharp": "Rectangular with sharp corners", + "pill": "Round", + "rect": "Rectangular with rounded corners" + }, + "buttonColor": { + "blue": "Blue", + "black": "Black", + "gold": "Gold (recommended)", + "silver": "Silver", + "white": "White" + } + }, + "credentialsLive": { + "title": "Live API credentials", + "test": "Test" + }, + "credentialsSandbox": { + "title": "Sandbox API credentials", + "test": "Test" + }, + "behavior": { + "title": "Behaviour" + }, + "vaulting": { + "title": "Vaulting", + "descriptionTitle": "One-time Paypal checkout", + "descriptionText": "Vaulting a PayPal account will allow you to charge the account in the future without requiring your customer to be present during the transaction or re-authenticate with PayPal when they are present during the transaction.", + "activeButtonLabel": "Activate Vaulting" + }, + "acdc": { + "title": "Credit- or debit card" + }, + "pui": { + "title": "Pay upon invoice" + }, + "express": { + "title": "Express Checkout Shortcut", + "subtitle": "Express Checkouts increase the conversion rate in your shop and pose no financial risk to you as a merchant. It is recommended to keep them activated. ", + "alertMessage": "Double opt-in for guests is activated in one of your Sales Channels. This feature is not compatible with PayPal Express Shortcut buttons. Customers who sign up via the Express Checkout Shortcut buttons won't receive double opt-in verification emails." + }, + "installment": { + "title": "'Pay Later' banner", + "subtitle": "Banners increase the conversion rate in your shop and pose no financial risk to you as a merchant. It is recommended to keep them activated." + }, + "spb": { + "title": "Smart Payment Buttons" + }, + "webhook": { + "title": "Webhook", + "info": "The webhook is used for updating the status of a transaction asynchronously. Incorrectly registered webhooks, e.g. on a different or not publicly available server, may result in unconfirmed transactions, regardless whether the transaction has been processed by PayPal.", + "buttonRefresh": "Refresh webhook", + "refreshFailed": "Failed to refresh webhook", + "fetchFailed": "Failed to fetch webhook status", + "status": { + "unknown": "Loading...", + "missing": "Not registered", + "invalid": "Registered, but possible domain mismatch", + "valid": "Registered" + } + }, + "crossBorder": { + "title": "Pay Later cross-border messaging", + "warning": "Please contact your PayPal representative to enable this feature for your account.", + "info": "Pay Later messaging is a feature that allows you to display a Pay Later message in the language of the customer. This feature is only available for certain countries. {0}", + "buyerCountryAuto": "Automatic determination", + "buyerCountryOverride": "Localization" + } + }, + "swag-paypal-settings-sales-channel-switch": { + "description": "Click this button to enable PayPal and select it as default in the selected Sales Channel", + "label": "Set PayPal as default" + }, + "swag-paypal-settings-locale-select": { + "invalid": "The provided locale is invalid. Please choose one from the list.", + "automatic": "Automatic (recommended)" + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/index.ts new file mode 100644 index 000000000..3036c2b07 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/index.ts @@ -0,0 +1,27 @@ +import template from './swag-paypal-settings-advanced.html.twig'; +import './swag-paypal-settings-advanced.scss'; +import { COUNTRY_OVERRIDES } from '../../../../constant/swag-paypal-settings.constant'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + countryOverrideOptions() { + const options = COUNTRY_OVERRIDES.map((locale) => ({ + value: locale, + label: this.$t(`locale.${locale}`), + })).sort((a, b) => a.label.localeCompare(b.label)); + + return [{ + value: null, + label: this.$t('swag-paypal-settings.crossBorder.buyerCountryAuto'), + }, ...options]; + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.html.twig new file mode 100644 index 000000000..305c7afb1 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.html.twig @@ -0,0 +1,23 @@ + + + + + {{ $t('swag-paypal-settings.crossBorder.warning') }} + + + + {{ $t('swag-paypal-settings.crossBorder.info') }} + + + + + + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.scss b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.scss new file mode 100644 index 000000000..5283aac12 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-advanced/swag-paypal-settings-advanced.scss @@ -0,0 +1,23 @@ +@import "~scss/variables"; + +.swag-paypal-cross-border { + &__titles { + font-size: $font-size-m; + font-weight: $font-weight-semi-bold; + } + + &__warning-text { + width: 100%; + } + + .sw-card__content { + display: grid; + gap: 8px; + font-size: $font-size-xs; + line-height: $line-height-sm; + } + + .sw-field { + margin-bottom: 0; + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/index.ts new file mode 100644 index 000000000..6b936c0ca --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/index.ts @@ -0,0 +1,103 @@ +import type * as PayPal from 'src/types'; +import template from './swag-paypal-settings-general.html.twig'; +import './swag-paypal-settings-general.scss'; +import { INTENTS, LANDING_PAGES } from '../../../../constant/swag-paypal-settings.constant'; + +const { Criteria } = Shopware.Data; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'acl', + 'repositoryFactory', + 'SwagPayPalSettingsService', + ], + + mixins: [ + Shopware.Mixin.getByName('swag-paypal-notification'), + ], + + data(): { + testCredentials: 'none' | 'loading' | 'success'; + testCredentialsSandbox: 'none' | 'loading' | 'success'; + } { + return { + testCredentials: 'none', + testCredentialsSandbox: 'none', + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + merchantInformationStore() { + return Shopware.Store.get('swagPayPalMerchantInformation'); + }, + + intentOptions() { + return INTENTS.map((intent) => { + return { + value: intent, + label: this.$t(`swag-paypal-settings.options.intent.${intent}`), + }; + }); + }, + + landingPageOptions() { + return LANDING_PAGES.map((landingPage) => { + return { + value: landingPage, + label: this.$t(`swag-paypal-settings.options.landingPage.${landingPage}`), + }; + }); + }, + + productRepository() { + return this.repositoryFactory.create('product'); + }, + + productStreamRepository() { + return this.repositoryFactory.create('product_stream'); + }, + + excludedProductCriteria() { + return (new Criteria(1, 25)).addAssociation('options.group'); + }, + }, + + methods: { + async onTest(prefix: 'Sandbox' | '') { + this[`testCredentials${prefix}`] = 'loading'; + + const response = await this.SwagPayPalSettingsService.testApiCredentials( + this.settingsStore.get(`SwagPayPal.settings.clientId${prefix}`), + this.settingsStore.get(`SwagPayPal.settings.clientSecret${prefix}`), + this.settingsStore.get(`SwagPayPal.settings.merchantPayerId${prefix}`), + prefix === 'Sandbox', + ); + + if (response.valid) { + this.createNotificationSuccess({ + title: this.$t('swag-paypal.notifications.test.title'), + message: this.$t('swag-paypal.notifications.test.successMessage'), + }); + } else if (response.errors) { + response.errors.forEach((error: PayPal.ServiceError) => { + this.createNotificationError({ + title: this.$t('swag-paypal.notifications.test.title'), + message: this.$t('swag-paypal.notifications.test.errorMessage', { + message: this.createMessageFromError(error), + }), + }); + }); + } + + this[`testCredentials${prefix}`] = response.valid ? 'success' : 'none'; + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.html.twig new file mode 100644 index 000000000..5c9edb939 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.html.twig @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ $t('swag-paypal-settings.vaulting.descriptionTitle') }} +
+ +
+ {{ $t('swag-paypal-settings.vaulting.descriptionText') }} +
+ + +
+ + + + + + + + diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.scss b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.scss new file mode 100644 index 000000000..ae7ace6cd --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-general/swag-paypal-settings-general.scss @@ -0,0 +1,28 @@ +@import "~scss/variables"; + +.swag-paypal-settings { + &-credentials { + .sw-card__titles { + display: flex; + gap: 8px; + align-items: center; + } + + .sw-field--switch { + margin: 0; + } + } + + &-vaulting { + &__description-title { + color: $color-darkgray-200; + font-weight: $font-weight-bold; + font-size: $font-size-m; + margin-bottom: 16px; + } + + &__activate-button { + margin-top: 24px; + } + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/index.ts b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/index.ts new file mode 100644 index 000000000..d01dec5aa --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/index.ts @@ -0,0 +1,68 @@ +import template from './swag-paypal-settings-storefront.html.twig'; +import { BUTTON_COLORS, BUTTON_SHAPES } from '../../../../constant/swag-paypal-settings.constant'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'systemConfigApiService', + ], + + data() { + return { + doubleOptInConfig: false, + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + buttonColorOptions() { + return BUTTON_COLORS.map((color) => ({ + value: color, + label: this.$t(`swag-paypal-settings.options.buttonColor.${color}`), + })); + }, + + buttonShapeOptions() { + return BUTTON_SHAPES.map((shape) => ({ + value: shape, + label: this.$t(`swag-paypal-settings.options.buttonShape.${shape}`), + })); + }, + + sbpSettingsDisabled(): boolean { + return !this.settingsStore.salesChannel && !this.settingsStore.getActual('SwagPayPal.settings.spbCheckoutEnabled'); + }, + + ecsSettingsDisabled(): boolean { + return !this.settingsStore.salesChannel + && !this.settingsStore.getActual('SwagPayPal.settings.ecsDetailEnabled') + && !this.settingsStore.getActual('SwagPayPal.settings.ecsCartEnabled') + && !this.settingsStore.getActual('SwagPayPal.settings.ecsOffCanvasEnabled') + && !this.settingsStore.getActual('SwagPayPal.settings.ecsLoginEnabled') + && !this.settingsStore.getActual('SwagPayPal.settings.ecsListingEnabled'); + }, + }, + + watch: { + 'settingsStore.salesChannel': { + immediate: true, + handler() { + this.fetchDoubleOptIn(); + }, + }, + }, + + methods: { + async fetchDoubleOptIn() { + const config = await this.systemConfigApiService.getValues('core.loginRegistration.doubleOptInGuestOrder') as Record; + + this.doubleOptInConfig = !!config['core.loginRegistration.doubleOptInGuestOrder']; + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/swag-paypal-settings-storefront.html.twig b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/swag-paypal-settings-storefront.html.twig new file mode 100644 index 000000000..8fa8cbc81 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-settings/view/swag-paypal-settings-storefront/swag-paypal-settings-storefront.html.twig @@ -0,0 +1,98 @@ + + + {{ $t('swag-paypal-settings.express.alertMessage') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-acdc/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-acdc/index.ts index 605915a75..e0e9ab81f 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-acdc/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-acdc/index.ts @@ -1,6 +1,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-acdc.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-general` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-behavior/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-behavior/index.ts index 1143603cc..755eff648 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-behavior/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-behavior/index.ts @@ -4,6 +4,9 @@ import constants from '../../page/swag-paypal/swag-paypal-consts'; const { Criteria } = Shopware.Data; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-general` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-credentials/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-credentials/index.ts index 2a866da7d..6a6f06c21 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-credentials/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-credentials/index.ts @@ -1,6 +1,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-credentials.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-general` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-cross-border/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-cross-border/index.ts index 3ac4cd84a..848c77eca 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-cross-border/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-cross-border/index.ts @@ -2,6 +2,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-cross-border.html.twig'; import './swag-paypal-cross-border.scss'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-advanced` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-express/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-express/index.ts index 8b45f13cc..6f9a41377 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-express/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-express/index.ts @@ -2,6 +2,10 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-express.html.twig'; const { Criteria } = Shopware.Data; + +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-storefront` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-inherit-wrapper/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-inherit-wrapper/index.ts index d3065d2ac..c68e0d100 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-inherit-wrapper/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-inherit-wrapper/index.ts @@ -4,6 +4,8 @@ import template from './swag-paypal-inherit-wrapper.html.twig'; /** * @private + * + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-setting` */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-installment/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-installment/index.ts index 88ecd74c1..b48c09ecf 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-installment/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-installment/index.ts @@ -1,6 +1,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-installment.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-storefront` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-locale-field/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-locale-field/index.ts index e6fce1b07..12ba4f8a3 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-locale-field/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-locale-field/index.ts @@ -6,6 +6,9 @@ const { debounce } = Shopware.Utils; type ValueEvent = { target: { value?: string } }; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-locale-select` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-plugin-box-with-onboarding/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-plugin-box-with-onboarding/index.ts index 19b074218..660f0e15f 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-plugin-box-with-onboarding/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-plugin-box-with-onboarding/index.ts @@ -1,5 +1,8 @@ import template from './sw-plugin-box-with-onboarding.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be removed without replacement + */ Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-pui/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-pui/index.ts index 4f88ab016..a5881d034 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-pui/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-pui/index.ts @@ -1,6 +1,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-pui.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-general` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-hint/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-hint/index.ts index 0ab4aae1f..a10f8e770 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-hint/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-hint/index.ts @@ -1,6 +1,9 @@ import './swag-paypal-settings-hint.scss'; import template from './swag-paypal-settings-hint.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be removed without replacement + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-icon/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-icon/index.ts index 4cb44d09d..60adad637 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-icon/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-settings-icon/index.ts @@ -1,6 +1,9 @@ import template from './swag-paypal-settings-icon.html.twig'; import './swag-paypal-settings-icon.scss'; +/** + * @deprecated tag:v10.0.0 - Will be moved to `swag-paypal-settings` module + */ export default Shopware.Component.wrapComponentConfig({ template, }); diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-spb/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-spb/index.ts index cf4020f08..ecc2a5100 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-spb/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-spb/index.ts @@ -1,6 +1,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-spb.html.twig'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-storefront` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-vaulting/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-vaulting/index.ts index 9aa127319..606e5ec99 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-vaulting/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-vaulting/index.ts @@ -2,6 +2,9 @@ import type * as PayPal from 'src/types'; import template from './swag-paypal-vaulting.html.twig'; import './swag-paypal-vaulting.scss'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-general` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-webhook/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-webhook/index.ts index af8a02652..a8767541c 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-webhook/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-webhook/index.ts @@ -6,6 +6,9 @@ const STATUS_WEBHOOK_MISSING = 'missing'; const STATUS_WEBHOOK_INVALID = 'invalid'; const STATUS_WEBHOOK_VALID = 'valid'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings-webhook` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/index.ts b/src/Resources/app/administration/src/module/swag-paypal/index.ts index 816356a00..92b61eb6f 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/index.ts @@ -24,6 +24,9 @@ Shopware.Component.register('swag-paypal', () => import('./page/swag-paypal')); const { Module } = Shopware; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings` + */ Module.register('swag-paypal', { type: 'plugin', name: 'SwagPayPal', diff --git a/src/Resources/app/administration/src/module/swag-paypal/page/swag-paypal/index.ts b/src/Resources/app/administration/src/module/swag-paypal/page/swag-paypal/index.ts index 5a43b96e7..80eb9b10a 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/page/swag-paypal/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/page/swag-paypal/index.ts @@ -12,6 +12,9 @@ type ConfigComponent = { const { Defaults } = Shopware; const { Criteria } = Shopware.Data; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-settings` + */ export default Shopware.Component.wrapComponentConfig({ template, From 03a9bcc20c3968bc288d8d5b54fa7382bdde9894 Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 16:17:12 +0100 Subject: [PATCH 10/11] POC - Reworked payment method card --- src/Resources/app/administration/src/main.ts | 59 +++++----- .../src/module/extension/index.ts | 8 +- .../swag-paypal-overview-card/index.ts | 3 + .../sw-settings-payment-detail/index.ts | 3 + .../sw-settings-payment-list/index.ts | 3 + .../index.ts | 43 ++++++++ ...paypal-method-domain-association.html.twig | 15 +++ ...swag-paypal-method-domain-association.scss | 8 ++ .../index.ts | 55 ++++++++++ ...ypal-method-merchant-information.html.twig | 23 ++++ ...ag-paypal-method-merchant-information.scss | 24 ++++ .../swag-paypal-payment-method/index.ts | 103 ++++++++++++++++++ .../swag-paypal-payment-method.html.twig | 62 +++++++++++ .../swag-paypal-payment-method.scss | 63 +++++++++++ .../sw-settings-payment-detail/index.ts | 46 ++++++++ .../sw-settings-payment-detail.html.twig | 30 +++++ .../sw-settings-payment-detail.scss | 5 + .../src/module/swag-paypal-method/index.ts | 36 ++++++ .../swag-paypal-method/snippet/de-DE.json | 55 ++++++++++ .../swag-paypal-method/snippet/en-GB.json | 57 ++++++++++ .../view/swag-paypal-method-card/index.ts | 95 ++++++++++++++++ .../swag-paypal-method-card.html.twig | 88 +++++++++++++++ .../swag-paypal-method-card.scss | 35 ++++++ .../index.ts | 3 + .../swag-paypal-checkout-method/index.ts | 3 + .../components/swag-paypal-checkout/index.ts | 3 + .../index.ts | 3 + 27 files changed, 902 insertions(+), 29 deletions(-) create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.scss create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/snippet/de-DE.json create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/snippet/en-GB.json create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/index.ts create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.html.twig create mode 100644 src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.scss diff --git a/src/Resources/app/administration/src/main.ts b/src/Resources/app/administration/src/main.ts index 655527f84..90f4419cb 100644 --- a/src/Resources/app/administration/src/main.ts +++ b/src/Resources/app/administration/src/main.ts @@ -25,6 +25,8 @@ const bootPromise = window.Shopware ? Shopware.Plugin.addBootPromise() : () => { await import('./app'); // @ts-expect-error - yes it's not a module await import('./module/swag-paypal-settings'); + // @ts-expect-error - yes it's not a module + await import('./module/swag-paypal-method'); } else { await import('./module/swag-paypal'); } @@ -33,29 +35,34 @@ const bootPromise = window.Shopware ? Shopware.Plugin.addBootPromise() : () => { bootPromise(); })(); -ui.module.payment.overviewCard.add({ - positionId: 'swag-paypal-overview-card-before', - component: 'swag-paypal-overview-card', - paymentMethodHandlers: [ - 'handler_swag_trustlyapmhandler', - 'handler_swag_sofortapmhandler', - 'handler_swag_p24apmhandler', - 'handler_swag_oxxoapmhandler', - 'handler_swag_mybankapmhandler', - 'handler_swag_multibancoapmhandler', - 'handler_swag_idealapmhandler', - 'handler_swag_giropayapmhandler', - 'handler_swag_epsapmhandler', - 'handler_swag_blikapmhandler', - 'handler_swag_bancontactapmhandler', - 'handler_swag_sepahandler', - 'handler_swag_acdchandler', - 'handler_swag_puihandler', - 'handler_swag_paypalpaymenthandler', - 'handler_swag_pospayment', - 'handler_swag_venmohandler', - 'handler_swag_paylaterhandler', - 'handler_swag_applepayhandler', - 'handler_swag_googlepayhandler', - ], -}); +if (!Shopware.Feature.isActive('PAYPAL_SETTINGS_TWEAKS')) { + /** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-method` + */ + ui.module.payment.overviewCard.add({ + positionId: 'swag-paypal-overview-card-before', + component: 'swag-paypal-overview-card', + paymentMethodHandlers: [ + 'handler_swag_trustlyapmhandler', + 'handler_swag_sofortapmhandler', + 'handler_swag_p24apmhandler', + 'handler_swag_oxxoapmhandler', + 'handler_swag_mybankapmhandler', + 'handler_swag_multibancoapmhandler', + 'handler_swag_idealapmhandler', + 'handler_swag_giropayapmhandler', + 'handler_swag_epsapmhandler', + 'handler_swag_blikapmhandler', + 'handler_swag_bancontactapmhandler', + 'handler_swag_sepahandler', + 'handler_swag_acdchandler', + 'handler_swag_puihandler', + 'handler_swag_paypalpaymenthandler', + 'handler_swag_pospayment', + 'handler_swag_venmohandler', + 'handler_swag_paylaterhandler', + 'handler_swag_applepayhandler', + 'handler_swag_googlepayhandler', + ], + }); +} diff --git a/src/Resources/app/administration/src/module/extension/index.ts b/src/Resources/app/administration/src/module/extension/index.ts index 767461348..4f8d1c722 100644 --- a/src/Resources/app/administration/src/module/extension/index.ts +++ b/src/Resources/app/administration/src/module/extension/index.ts @@ -4,9 +4,11 @@ Shopware.Component.override('sw-sales-channel-modal-detail', () => import('./sw- Shopware.Component.override('sw-sales-channel-modal-grid', () => import('./sw-sales-channel-modal-grid')); -Shopware.Component.register('swag-paypal-overview-card', () => import('./sw-settings-payment/components/swag-paypal-overview-card')); -Shopware.Component.override('sw-settings-payment-detail', () => import('./sw-settings-payment/sw-settings-payment-detail')); -Shopware.Component.override('sw-settings-payment-list', () => import('./sw-settings-payment/sw-settings-payment-list')); +if (!Shopware.Feature.isActive('PAYPAL_SETTINGS_TWEAKS')) { + Shopware.Component.register('swag-paypal-overview-card', () => import('./sw-settings-payment/components/swag-paypal-overview-card')); + Shopware.Component.override('sw-settings-payment-detail', () => import('./sw-settings-payment/sw-settings-payment-detail')); + Shopware.Component.override('sw-settings-payment-list', () => import('./sw-settings-payment/sw-settings-payment-list')); +} Shopware.Component.override('sw-settings-shipping-detail', () => import('./sw-settings-shipping/sw-settings-shipping-detail')); diff --git a/src/Resources/app/administration/src/module/extension/sw-settings-payment/components/swag-paypal-overview-card/index.ts b/src/Resources/app/administration/src/module/extension/sw-settings-payment/components/swag-paypal-overview-card/index.ts index eb019e3a2..fb0149b27 100644 --- a/src/Resources/app/administration/src/module/extension/sw-settings-payment/components/swag-paypal-overview-card/index.ts +++ b/src/Resources/app/administration/src/module/extension/sw-settings-payment/components/swag-paypal-overview-card/index.ts @@ -5,6 +5,9 @@ type ConfigComponent = { save:() => Promise<{ payPalWebhookErrors?: string[] }>; }; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-method-card` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-detail/index.ts b/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-detail/index.ts index cb51571e2..91ff86dbf 100644 --- a/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-detail/index.ts +++ b/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-detail/index.ts @@ -2,6 +2,9 @@ import type * as PayPal from 'src/types'; import template from './sw-settings-payment-detail.html.twig'; import './sw-settings-payment-detail.scss'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by swag-paypal-settings/extension/sw-settings-payment-detail + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-list/index.ts b/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-list/index.ts index fa36aac85..63da0a0c1 100644 --- a/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-list/index.ts +++ b/src/Resources/app/administration/src/module/extension/sw-settings-payment/sw-settings-payment-list/index.ts @@ -2,6 +2,9 @@ import type * as PayPal from 'src/types'; import template from './sw-settings-payment-list.html.twig'; import './sw-settings-payment-list.scss'; +/** + * @deprecated tag:v10.0.0 - Will be replaced without replacement + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/index.ts b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/index.ts new file mode 100644 index 000000000..4dc393b1f --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/index.ts @@ -0,0 +1,43 @@ +import template from './swag-paypal-method-domain-association.html.twig'; +import './swag-paypal-method-domain-association.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + props: { + paymentMethod: { + type: Object as PropType>, + required: false, + default: null, + }, + }, + + data(): { + hidden: boolean; + } { + return { + hidden: localStorage.getItem('domain-association-hidden') === 'true', + }; + }, + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + domainAssociationLink() { + return this.settingsStore.get('SwagPayPal.settings.sandbox') + ? 'https://www.sandbox.paypal.com/uccservicing/apm/applepay' + : 'https://www.paypal.com/uccservicing/apm/applepay'; + }, + }, + + methods: { + onCloseAlert() { + this.hidden = true; + localStorage.setItem('domain-association-hidden', 'true'); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.html.twig b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.html.twig new file mode 100644 index 000000000..f6888cf84 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.html.twig @@ -0,0 +1,15 @@ + + {{ $t('swag-paypal-method.domainAssociation.title') }} + +
+ + + {{ $t('swag-paypal-method.domainAssociation.link') }} + +
diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.scss b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.scss new file mode 100644 index 000000000..7d0ddf986 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-domain-association/swag-paypal-method-domain-association.scss @@ -0,0 +1,8 @@ +@import "~scss/variables"; + +.swag-paypal-payment-domain-association { + grid-column: span 4; + width: 100%; + margin-bottom: 4px; + font-size: $font-size-xs; +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/index.ts b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/index.ts new file mode 100644 index 000000000..b31972566 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/index.ts @@ -0,0 +1,55 @@ +import template from './swag-paypal-method-merchant-information.html.twig'; +import './swag-paypal-method-merchant-information.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + emits: ['save'], + + computed: { + settingsStore() { + return Shopware.Store.get('swagPayPalSettings'); + }, + + merchantInformationStore() { + return Shopware.Store.get('swagPayPalMerchantInformation'); + }, + + showSandboxToggle() { + return !!this.settingsStore.get('SwagPayPal.settings.clientSecret') + || !!this.settingsStore.get('SwagPayPal.settings.clientSecretSandbox') + || !!this.settingsStore.salesChannel; + }, + + merchantEmail() { + return this.merchantInformationStore.actual?.merchantIntegrations?.primary_email + ?? this.merchantInformationStore.actual?.merchantIntegrations?.tracking_id; + }, + + sandboxToggleDisabled(): boolean { + if (this.settingsStore.salesChannel) { + return false; + } + + if (this.settingsStore.isSandbox) { + return !!this.settingsStore.get('SwagPayPal.settings.clientSecretSandbox') && !this.settingsStore.get('SwagPayPal.settings.clientSecret'); + } + + return !!this.settingsStore.get('SwagPayPal.settings.clientSecret') && !this.settingsStore.get('SwagPayPal.settings.clientSecretSandbox'); + }, + + tooltipSandbox() { + return this.settingsStore.isSandbox + ? this.$t('swag-paypal-method.sandbox.onlySandboxTooltip') + : this.$t('swag-paypal-method.sandbox.onlyLiveTooltip'); + }, + }, + + methods: { + onSandboxToggle() { + this.$emit('save'); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.html.twig b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.html.twig new file mode 100644 index 000000000..a0afa8b36 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.html.twig @@ -0,0 +1,23 @@ +
+ + + + + +
diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.scss b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.scss new file mode 100644 index 000000000..41a87e912 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-method-merchant-information/swag-paypal-method-merchant-information.scss @@ -0,0 +1,24 @@ +@import "~scss/variables"; + +.swag-paypal-merchant-information { + background-color: $color-gray-50; + border: 1px solid $color-gray-300; + border-radius: $border-radius-default; + padding: 32px; + + &__sandbox-toggle { + float: right; + margin-top: -15px; + } + + &__email { + line-height: 24px; + font-weight: $font-weight-semi-bold; + font-size: $font-size-xs; + margin-bottom: 8px; + } + + .sw-field--switch { + margin: 0; + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/index.ts b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/index.ts new file mode 100644 index 000000000..a39c8570b --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/index.ts @@ -0,0 +1,103 @@ +import template from './swag-paypal-payment-method.html.twig'; +import './swag-paypal-payment-method.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'acl', + 'repositoryFactory', + ], + + emits: ['update:active'], + + props: { + paymentMethod: { + type: Object as PropType>, + required: true, + }, + }, + + computed: { + merchantInformationStore() { + return Shopware.Store.get('swagPayPalMerchantInformation'); + }, + + onboardingStatus() { + return this.merchantInformationStore.capabilities[this.paymentMethod.id]; + }, + + identifier(): string { + return this.paymentMethod.formattedHandlerIdentifier?.split('_').pop() ?? ''; + }, + + isPayPalPui() { + return this.identifier === 'puihandler'; + }, + + paymentMethodToggleDisabled() { + // should be able to deactivate active payment method + if (this.paymentMethod.active) { + return false; + } + + return !this.showEditLink; + }, + + showEditLink() { + return ['active', 'limited', 'mybank'].includes(this.onboardingStatus); + }, + + statusBadgeVariant() { + switch (this.onboardingStatus) { + case 'active': + return 'success'; + case 'limited': + case 'mybank': + return 'danger'; + case 'inactive': + case 'ineligible': + return 'neutral'; + case 'pending': + return 'info'; + default: + return 'neutral'; + } + }, + + onboardingStatusText() { + return this.$t(`swag-paypal-method.onboardingStatusText.${this.onboardingStatus}`); + }, + + onboardingStatusTooltip() { + const snippetKey = `swag-paypal-method.onboardingStatusTooltip.${this.onboardingStatus}`; + + if (!this.$te(snippetKey)) { + return null; + } + + return this.$t(snippetKey); + }, + + availabilityToolTip() { + const snippetKey = `swag-paypal-method.availabilityToolTip.${this.identifier}`; + + if (!this.$te(snippetKey)) { + return null; + } + + return this.$t(snippetKey); + }, + }, + + methods: { + onUpdateActive(active: boolean) { + // update:value is emitted twice + if (this.paymentMethod.active !== active) { + this.$emit('update:active', active); + } + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.html.twig b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.html.twig new file mode 100644 index 000000000..bdff6f6ec --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.html.twig @@ -0,0 +1,62 @@ +
+ + +
+ {{ paymentMethod.translated.name }} + + ({{ $t('swag-paypal-method.ratePayLabel') }}) + +
+ + + +
+ + + + + + {{ onboardingStatusText }} + + + + {{ $t('swag-paypal-method.editDetail') }} + +
+ + + + +
diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.scss b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.scss new file mode 100644 index 000000000..21dbfb385 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/component/swag-paypal-payment-method/swag-paypal-payment-method.scss @@ -0,0 +1,63 @@ +@import "~scss/variables"; + +.swag-paypal-payment-method { + display: grid; + grid-template-columns: auto 1fr auto auto; + align-items: center; + border: 1px solid $color-gray-300; + border-radius: $border-radius-default; + gap: 16px 12px; + padding: 10px 16px; + font-size: $font-size-xs; + + &__name { + font-weight: $font-weight-semi-bold; + } + + &__icon { + width: 32px; + max-height: 24px; + } + + &__dynamic { + display: flex; + align-items: center; + justify-items: end; + gap: 12px; + + .sw-label { + margin: 0; + } + } + + &__status-label { + border: none; + font-weight: $font-weight-semi-bold; + + .sw-color-badge { + margin: 0; + } + + .sw-label__caption { + // sw-label will not center the text vertically + display: grid; + grid-template-columns: auto auto; + place-items: center; + height: 100%; + + gap: 4px; + + overflow-y: visible; // g's and p's are cut off + } + } + + & > .sw-skeleton-bar { + height: 24px; + margin: 0; + width: 150px; + } + + .sw-field--switch { + margin: 0; + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/index.ts b/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/index.ts new file mode 100644 index 000000000..b5165f4a8 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/index.ts @@ -0,0 +1,46 @@ +import type * as PayPal from 'src/types'; +import template from './sw-settings-payment-detail.html.twig'; +import './sw-settings-payment-detail.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + inject: [ + 'SwagPayPalApiCredentialsService', + ], + + data(): { + capabilities: PayPal.Setting<'merchant_information'>['capabilities']; + } { + return { + capabilities: {}, + }; + }, + + computed: { + needsOnboarding(): boolean { + // @ts-expect-error - paymentMethod is from extended component + if (!this.paymentMethod || !this.capabilities) { + return true; + } + + // @ts-expect-error - paymentMethod is from extended component + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return this.capabilities[this.paymentMethod.id] === 'inactive'; + }, + }, + + methods: { + createdComponent() { + this.$super('createdComponent'); + + this.fetchMerchantCapabilities(); + }, + + async fetchMerchantCapabilities() { + const merchantInformation = await this.SwagPayPalApiCredentialsService.getMerchantInformation(); + this.capabilities = merchantInformation.capabilities ?? {}; + }, + }, +}); + diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.html.twig b/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.html.twig new file mode 100644 index 000000000..d8fbf15d5 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.html.twig @@ -0,0 +1,30 @@ +{% block sw_settings_payment_detail_content_field_plugin %} + + {{ $t('sw-plugin-box.buttonPluginSettings') }} + + + +{% endblock %} + +{% block sw_settings_payment_detail_content_field_active %} + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.scss b/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.scss new file mode 100644 index 000000000..6058b9cd5 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/extension/sw-settings-payment/sw-settings-payment-detail/sw-settings-payment-detail.scss @@ -0,0 +1,5 @@ +@import "~scss/variables"; + +.swag-paypal-go-to-settings-link { + color: $color-shopware-brand-500; +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/index.ts b/src/Resources/app/administration/src/module/swag-paypal-method/index.ts new file mode 100644 index 000000000..96cbd09f0 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/index.ts @@ -0,0 +1,36 @@ +import { ui } from '@shopware-ag/meteor-admin-sdk'; + +Shopware.Component.register('swag-paypal-method-domain-association', () => import('./component/swag-paypal-method-domain-association')); +Shopware.Component.register('swag-paypal-method-merchant-information', () => import('./component/swag-paypal-method-merchant-information')); +Shopware.Component.register('swag-paypal-payment-method', () => import('./component/swag-paypal-payment-method')); + +Shopware.Component.register('swag-paypal-method-card', () => import('./view/swag-paypal-method-card')); + +Shopware.Component.override('sw-settings-payment-detail', () => import('./extension/sw-settings-payment/sw-settings-payment-detail')); + +ui.module.payment.overviewCard.add({ + positionId: 'swag-paypal-method-card-before', + component: 'swag-paypal-method-card', + paymentMethodHandlers: [ + 'handler_swag_trustlyapmhandler', + 'handler_swag_sofortapmhandler', + 'handler_swag_p24apmhandler', + 'handler_swag_oxxoapmhandler', + 'handler_swag_mybankapmhandler', + 'handler_swag_multibancoapmhandler', + 'handler_swag_idealapmhandler', + 'handler_swag_giropayapmhandler', + 'handler_swag_epsapmhandler', + 'handler_swag_blikapmhandler', + 'handler_swag_bancontactapmhandler', + 'handler_swag_sepahandler', + 'handler_swag_acdchandler', + 'handler_swag_puihandler', + 'handler_swag_paypalpaymenthandler', + 'handler_swag_pospayment', + 'handler_swag_venmohandler', + 'handler_swag_paylaterhandler', + 'handler_swag_applepayhandler', + 'handler_swag_googlepayhandler', + ], +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/snippet/de-DE.json b/src/Resources/app/administration/src/module/swag-paypal-method/snippet/de-DE.json new file mode 100644 index 000000000..a3330f506 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/snippet/de-DE.json @@ -0,0 +1,55 @@ +{ + "swag-paypal-method": { + "cardTitle": "PayPal Checkout", + "settingsLink": "Einstellungen", + "header": "Mehr Zahlungsmöglichkeiten in einer Lösung von PayPal", + "description": "PayPal Checkout ist die neue All-in-One-Lösung, mit der Sie leistungsstarke und flexible Funktionen zur Zahlungsabwicklung auf Marktplätzen und anderen Handelsplattformen anbieten können.", + "banner": "Mit PayPal Checkout Kannst Du Deinen Kunden die Bezahlung per Rechnung, Kreditkarte und viele andere lokale Zahlungsmethoden anbieten. PayPal Checkout unterstützt Dich mit der neuesten Technologie und bringt Dir höchste Flexibilität. Du behältst Deine bisherigen Zahlungsarten und die Gebühren bleiben gleich! Aktiviere PayPal Checkout und deaktiviere PayPal PLUS, um doppelte Zahlungsmethoden in Deinem Shop zu vermeiden. Mehr Informationen", + "sandbox": { + "onlySandboxTooltip": "Du bist bisher nur mit einem PayPal-Sandbox-Konto verbunden.", + "onlyLiveTooltip": "Du bist bisher nur mit einem PayPal-Live-Konto verbunden." + }, + "switch": { + "label": "Aktiv", + "active": "Zahlungsart \"{name}\" ist jetzt aktiv.", + "inactive": "Zahlungsart \"{name}\" ist jetzt inaktiv." + }, + "domainAssociation": { + "title": "Bitte vergewissere dich, dass die Domain korrekt eingestellt ist, damit Apple Pay funktioniert. Die Domain-Zuordnungsdatei ist bereits hinterlegt.", + "link": "Domain-Einstellungen öffnen" + }, + "paymentMethodText": "Zahlungsmöglichkeiten", + "appImageAlt": "PayPal", + "ratePayLabel": "von Ratepay", + "editDetail": "Details bearbeiten", + "availabilityToolTip": { + "bancontactapmhandler": "Bancontact ist für die folgenden Länder verfügbar: Belgien", + "blikapmhandler": "BLIK ist für die folgenden Länder verfügbar: Polen", + "boletobancarioapmhandler": "Boleto Bancário ist für die folgenden Länder verfügbar: Brasilien", + "epsapmhandler": "eps ist für die folgenden Länder verfügbar: Österreich", + "idealapmhandler": "iDEAL ist für die folgenden Länder verfügbar: Niederlande", + "multibancoapmhandler": "Multibanco ist für die folgenden Länder verfügbar: Portugal", + "mybankapmhandler": "MyBank ist für die folgenden Länder verfügbar: Italien", + "oxxoapmhandler": "OXXO ist für die folgenden Länder verfügbar: Mexiko", + "p24apmhandler": "Przelewy24 ist für die folgenden Länder verfügbar: Polen", + "paylaterhandler": "Später Bezahlen ist für die folgenden Länder verfügbar: Australien, Deutschland, Frankreich, Italien, Spanien, Vereinigte Staaten, Vereinigtes Königreich", + "puihandler": "Rechnungskauf ist für die folgenden Länder verfügbar: Deutschland", + "sepahandler": "SEPA Lastschrift ist für die folgenden Länder verfügbar: Deutschland", + "venmohandler": "Venmo ist für die folgenden Länder verfügbar: USA", + "trustlyapmhandler": "Trustly ist für die folgenden Länder verfügbar: Estland, Finnland, Niederlande, Schweden" + }, + "onboardingStatusText": { + "active": "Autorisiert", + "limited": "Limitiert", + "pending": "Authorisierung ausstehend", + "ineligible": "Gesperrt", + "inactive": "Onboarding benötigt", + "mybank": "Limitiert" + }, + "onboardingStatusTooltip": { + "ineligible": "PayPal hat uns informiert, dass diese Zahlungsmethode aktuell für Ihren Account nicht freigeschaltet ist.", + "limited": "PayPal hat uns informiert, dass Einschränkugen dieser Zahlungsart für Ihren Account bestehen.", + "mybank": "Händler, die MyBank nach Februar 2023 aktivieren, benötigen eine manuelle Genehmigung von PayPal. Wenden Sie sich an den PayPal-Support, um weitere Informationen zu erhalten." + } + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/snippet/en-GB.json b/src/Resources/app/administration/src/module/swag-paypal-method/snippet/en-GB.json new file mode 100644 index 000000000..58f4b06b2 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/snippet/en-GB.json @@ -0,0 +1,57 @@ +{ + "checkout": { + "cardTitle": "PayPal Checkout", + "settingsLink": "Settings", + "header": "More payment methods with one solution from PayPal", + "description": "PayPal Checkout is the new all-in-one solution that lets you offer powerful flexibile payment processing features on the marketplaces and other commerce platforms.", + "banner": "With PayPal Checkout, you can offer your customers payment by invoice, credit card and other local payment methods. PayPal Checkout supports you with the latest technology and brings you the highest flexibility. You keep your existing payment methods, and the fees remain the same! Activate PayPal Checkout and deactivate PayPal PLUS to avoid duplicate payment methods in your shop. More information", + "sandbox": { + "label": "Sandbox", + "onlySandboxTooltip": "You are only connected a sandbox PayPal account so far.", + "onlyLiveTooltip": "You are only connected a live PayPal account so far.", + "helpText": "Enable, if you want to test the PayPal integration." + }, + "switch": { + "label": "Active", + "active": "Payment method \"{name}\" is now active.", + "inactive": "Payment method \"{name}\" is now inactive." + }, + "domainAssociation": { + "title": "Please make sure your domain is set correctly for Apple Pay to work properly. The domain association file is already present.", + "link": "Open domains settings" + }, + "paymentMethodText": "Payment methods", + "appImageAlt": "PayPal", + "ratePayLabel": "by Ratepay", + "editDetail": "Edit details", + "availabilityToolTip": { + "bancontactapmhandler": "Bancontact is available for the following countries: Belgium", + "blikapmhandler": "BLIK is available for the following countries: Poland", + "boletobancarioapmhandler": "Boleto Bancário is available for the following countries: Brazil", + "epsapmhandler": "eps is available for the following countries: Austria", + "idealapmhandler": "iDEAL is available for the following countries: Netherlands", + "multibancoapmhandler": "Multibanco is available for the following countries: Portugal", + "mybankapmhandler": "MyBank is available for the following countries: Italy", + "oxxoapmhandler": "OXXO is available for the following countries: Mexico", + "p24apmhandler": "Przelewy24 is available for the following countries: Poland", + "paylaterhandler": "Pay Later is available for the following countries: Australia, France, Germany, Italy, Spain, United Kingdom, United States", + "puihandler": "Pay upon invoice is available for the following countries: Germany", + "sepahandler": "SEPA Lastschrift is available for the following countries: Germany", + "venmohandler": "Venmo is available for the following countries: United States", + "trustlyapmhandler": "Trustly is available for the following countries: Estonia, Finland, Netherlands, Sweden" + }, + "onboardingStatusText": { + "active": "Authorized", + "limited": "Limited", + "pending": "Authorization pending", + "ineligible": "Ineligible", + "inactive": "Onboarding needed", + "mybank": "Limited" + }, + "onboardingStatusTooltip": { + "ineligible": "PayPal informed us, that this payment method is currently ineligible for your account.", + "limited": "PayPal informed us, that this payment method has some limitations for your account.", + "mybank": "Merchants enabling MyBank after February 2023 will need manual approval by PayPal. Reach out to PayPal support for further information on this." + } + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/index.ts b/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/index.ts new file mode 100644 index 000000000..20969267d --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/index.ts @@ -0,0 +1,95 @@ +import template from './swag-paypal-method-card.html.twig'; +import './swag-paypal-method-card.scss'; + +const { Context } = Shopware; +const { Criteria } = Shopware.Data; + +export default Shopware.Component.wrapComponentConfig({ + template, + + compatConfig: Shopware.compatConfig, + + inject: [ + 'repositoryFactory', + ], + + mixins: [ + Shopware.Mixin.getByName('notification'), + Shopware.Mixin.getByName('swag-paypal-settings'), + Shopware.Mixin.getByName('swag-paypal-merchant-information'), + ], + + data(): { + isLoadingPaymentMethods: boolean; + paymentMethods: TEntity<'payment_method'>[]; + } { + return { + isLoadingPaymentMethods: true, + paymentMethods: [], + }; + }, + + computed: { + assetFilter() { + return Shopware.Filter.getByName('asset'); + }, + + paymentMethodRepository(): TRepository<'payment_method'> { + return this.repositoryFactory.create('payment_method'); + }, + + paymentMethodCriteria(): TCriteria { + return (new Criteria(1, 500)) + .addAssociation('media') + .addFilter(Criteria.equals('plugin.name', 'SwagPayPal')) + .addSorting(Criteria.sort('position', 'ASC')); + }, + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.fetchPaymentMethods(); + }, + + async fetchPaymentMethods() { + this.isLoadingPaymentMethods = true; + + const paymentMethods = await this.paymentMethodRepository.search(this.paymentMethodCriteria, Context.api) + .catch(() => ([])); + + this.paymentMethods = paymentMethods + .filter((pm) => { + if (pm.formattedHandlerIdentifier === 'handler_swag_pospayment') { + return false; + } + + return !([ + 'handler_swag_trustlyapmhandler', + 'handler_swag_giropayapmhandler', + 'handler_swag_sofortapmhandler', + ].includes(pm.formattedHandlerIdentifier ?? '') && !pm.active); + }); + + this.isLoadingPaymentMethods = false; + }, + + async onUpdateActive(paymentMethod: TEntity<'payment_method'>, active: boolean) { + paymentMethod.active = active; + + await this.paymentMethodRepository.save(paymentMethod, Context.api) + .then(() => { + this.createNotificationSuccess({ + message: this.$t( + `swag-paypal-method.switch.${paymentMethod.active ? 'active' : 'inactive'}`, + { name: paymentMethod.translated?.name || paymentMethod.name }, + ), + }); + }) + .catch(() => { paymentMethod.active = !active; }); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.html.twig b/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.html.twig new file mode 100644 index 000000000..626f3a326 --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.html.twig @@ -0,0 +1,88 @@ + + + + + +
+ + + + + + + + + + +

+ {{ $t('swag-paypal-method.paymentMethodText') }} +

+ + + +
+ +
+
+
diff --git a/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.scss b/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.scss new file mode 100644 index 000000000..11a20a7cd --- /dev/null +++ b/src/Resources/app/administration/src/module/swag-paypal-method/view/swag-paypal-method-card/swag-paypal-method-card.scss @@ -0,0 +1,35 @@ +@import "~scss/variables"; + +.swag-paypal-payment-card { + padding: 30px; + + &__header { + font-weight: $font-weight-bold; + font-size: $font-size-m; + line-height: 42px; + } + + &__description { + line-height: 25px; + } + + &__banner { + margin-top: 24px; + } + + &__payment-method-headline { + font-size: $font-size-m; + margin: 32px 0 8px; + } + + &__listing { + display: flex; + gap: 16px; + flex-direction: column; + } + + & > .sw-skeleton-bar { + height: 24px; + margin-bottom: 16px; + } +} diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-domain-association/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-domain-association/index.ts index 0a141f0ff..f7740baae 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-domain-association/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-domain-association/index.ts @@ -1,6 +1,9 @@ import template from './swag-paypal-checkout-domain-association.html.twig'; import './swag-paypal-checkout-domain-association.scss'; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-method-domain-association` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-method/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-method/index.ts index c7d4b84c6..604447f49 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-method/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout-method/index.ts @@ -3,6 +3,9 @@ import './swag-paypal-checkout-method.scss'; const { Context } = Shopware; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-payment-method` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout/index.ts index eeb36333c..958545e65 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-checkout/index.ts @@ -5,6 +5,9 @@ import './swag-paypal-checkout.scss'; const { Context } = Shopware; const { Criteria } = Shopware.Data; +/** + * @deprecated tag:v10.0.0 - Will be replaced by `swag-paypal-method-card` + */ export default Shopware.Component.wrapComponentConfig({ template, diff --git a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-created-component-helper/index.ts b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-created-component-helper/index.ts index 119e901b9..d75c5db8b 100644 --- a/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-created-component-helper/index.ts +++ b/src/Resources/app/administration/src/module/swag-paypal/components/swag-paypal-created-component-helper/index.ts @@ -7,6 +7,9 @@ import template from './swag-paypal-created-component-helper.html.twig'; * race conditions can occur here. */ +/** + * @deprecated tag:v10.0.0 - Will be removed without replacement + */ export default Shopware.Component.wrapComponentConfig({ template, From 2dcbdf68162e3cca21e8270d92370b3fd20ab957 Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Sun, 1 Dec 2024 18:18:48 +0100 Subject: [PATCH 11/11] POC - Reworked first-run-wizard extension --- .../src/module/extension/index.ts | 6 +- .../sw-first-run-wizard/snippets/de-DE.json | 2 + .../sw-first-run-wizard/snippets/en-GB.json | 2 + .../index.ts | 169 ++++++++++++++++++ ...st-run-wizard-paypal-credentials.html.twig | 122 +++++++++++++ ...w-first-run-wizard-paypal-credentials.scss | 31 ++++ .../index.ts | 169 +++++++----------- ...st-run-wizard-paypal-credentials.html.twig | 163 +++++++---------- ...w-first-run-wizard-paypal-credentials.scss | 34 ++-- 9 files changed, 478 insertions(+), 220 deletions(-) create mode 100644 src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/index.ts create mode 100644 src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.html.twig create mode 100644 src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.scss diff --git a/src/Resources/app/administration/src/module/extension/index.ts b/src/Resources/app/administration/src/module/extension/index.ts index 4f8d1c722..683fc247b 100644 --- a/src/Resources/app/administration/src/module/extension/index.ts +++ b/src/Resources/app/administration/src/module/extension/index.ts @@ -1,4 +1,8 @@ -Shopware.Component.override('sw-first-run-wizard-paypal-credentials', () => import('./sw-first-run-wizard/sw-first-run-wizard-paypal-credentials')); +if (Shopware.Feature.isActive('PAYPAL_SETTINGS_TWEAKS')) { + Shopware.Component.override('sw-first-run-wizard-paypal-credentials', () => import('./sw-first-run-wizard/sw-first-run-wizard-paypal-credentials')); +} else { + Shopware.Component.override('sw-first-run-wizard-paypal-credentials', () => import('./sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated')); +} Shopware.Component.override('sw-sales-channel-modal-detail', () => import('./sw-sales-channel-modal-detail')); diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/de-DE.json b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/de-DE.json index bc8128dd3..c8e9b6381 100644 --- a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/de-DE.json +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/de-DE.json @@ -1,6 +1,7 @@ { "swag-paypal-frw-credentials": { "buttonGetCredentials": "Hole API Zugangsdaten", + "buttonGetSandboxCredentials": "Hole Sandbox API Zugangsdaten", "textIntroPayPal": "Um PayPal zu nutzen müssen nur die API Zugangsdaten eingegeben werden.", "labelClientId": "Client-ID", "labelClientSecret": "Client-Secret", @@ -15,6 +16,7 @@ "messageFetchedError": " Bitte versuche es erneut oder nutze die erweiterten Einstellungen um die Zugangsdaten direkt einzugeben.", "textFetchedSuccessful": "Die Zugangsdaten wurden erfolgreich abgerufen.", "messageNoCredentials": "Es wurden keine Zugangsdaten hinterlegt.", + "messageInvalidCredentials": "Die Zugangsdaten sind ungültig.", "messageTestSuccess": "Die Zugangsdaten sind gültig." } } diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/en-GB.json b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/en-GB.json index dc3f91c36..8c0697dc2 100644 --- a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/en-GB.json +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/snippets/en-GB.json @@ -1,6 +1,7 @@ { "swag-paypal-frw-credentials": { "buttonGetCredentials": "Get API credentials", + "buttonGetSandboxCredentials": "Get sandbox API credentials", "textIntroPayPal": "To get PayPal up and running you only need to provide your PayPal API credentials.", "labelClientId": "Client ID", "labelClientSecret": "Client secret", @@ -15,6 +16,7 @@ "messageFetchedError": "Try again or use the advanced settings to provide your credentials.", "textFetchedSuccessful": "Credentials have been fetched.", "messageNoCredentials": "No credentials provided.", + "messageInvalidCredentials": "Credentials are invalid.", "messageTestSuccess": "Credentials are valid." } } diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/index.ts b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/index.ts new file mode 100644 index 000000000..c15319caa --- /dev/null +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/index.ts @@ -0,0 +1,169 @@ +import type * as PayPal from 'src/types'; +import template from './sw-first-run-wizard-paypal-credentials.html.twig'; +import './sw-first-run-wizard-paypal-credentials.scss'; + +export default Shopware.Component.wrapComponentConfig({ + template, + + inject: [ + 'systemConfigApiService', + 'SwagPaypalPaymentMethodService', + ], + + mixins: [ + Shopware.Mixin.getByName('swag-paypal-notification'), + Shopware.Mixin.getByName('swag-paypal-credentials-loader'), + ], + + data() { + return { + config: {} as PayPal.SystemConfig, + isLoading: false, + setDefault: false, + }; + }, + + computed: { + sandboxMode() { + return this.config['SwagPayPal.settings.sandbox'] || false; + }, + + onboardingUrl() { + return this.sandboxMode ? this.onboardingUrlSandbox : this.onboardingUrlLive; + }, + + onboardingCallback() { + return this.sandboxMode ? 'onboardingCallbackSandbox' : 'onboardingCallbackLive'; + }, + + buttonConfig() { + const prev = this.$super('buttonConfig') as { key: string; action: () => Promise }[]; + + return prev.map((button) => { + if (button.key === 'next') { + button.action = this.onClickNext.bind(this); + } + + return button; + }); + }, + + credentialsProvided() { + return (!this.sandboxMode && this.credentialsProvidedLive) + || (this.sandboxMode && this.credentialsProvidedSandbox); + }, + + credentialsProvidedLive() { + return !!this.config['SwagPayPal.settings.clientId'] + && !!this.config['SwagPayPal.settings.clientSecret']; + }, + + credentialsProvidedSandbox() { + return !!this.config['SwagPayPal.settings.clientIdSandbox'] + && !!this.config['SwagPayPal.settings.clientSecretSandbox']; + }, + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.$super('createdComponent'); + this.fetchPayPalConfig(); + }, + + onPayPalCredentialsLoadSuccess(clientId: string, clientSecret: string, merchantPayerId: string, sandbox: boolean) { + this.setConfig(clientId, clientSecret, merchantPayerId, sandbox); + }, + + onPayPalCredentialsLoadFailed(sandbox: boolean) { + this.setConfig('', '', '', sandbox); + this.createNotificationError({ + message: this.$tc('swag-paypal-frw-credentials.messageFetchedError'), + // @ts-expect-error - duration is not defined correctly + duration: 10000, + }); + }, + + setConfig(clientId: string, clientSecret: string, merchantPayerId: string, sandbox: boolean) { + const suffix = sandbox ? 'Sandbox' : ''; + this.$set(this.config, `SwagPayPal.settings.clientId${suffix}`, clientId); + this.$set(this.config, `SwagPayPal.settings.clientSecret${suffix}`, clientSecret); + this.$set(this.config, `SwagPayPal.settings.merchantPayerId${suffix}`, merchantPayerId); + }, + + async onClickNext(): Promise { + if (!this.credentialsProvided) { + this.createNotificationError({ + message: this.$tc('swag-paypal-frw-credentials.messageNoCredentials'), + }); + + return true; + } + + try { + // Do not test the credentials if they have been fetched from the PayPal api + if (!this.isGetCredentialsSuccessful) { + await this.testApiCredentials(); + } + + await this.saveConfig(); + + this.$emit('frw-redirect', 'sw.first.run.wizard.index.plugins'); + + return false; + } catch { + return true; + } + }, + + fetchPayPalConfig() { + this.isLoading = true; + return this.systemConfigApiService.getValues('SwagPayPal.settings', null) + .then((values: PayPal.SystemConfig) => { + this.config = values; + }) + .finally(() => { + this.isLoading = false; + }); + }, + + async saveConfig() { + this.isLoading = true; + await this.systemConfigApiService.saveValues(this.config, null); + + if (this.setDefault) { + await this.SwagPaypalPaymentMethodService.setDefaultPaymentForSalesChannel(); + } + + this.isLoading = false; + }, + + async testApiCredentials() { + this.isLoading = true; + + const sandbox = this.config['SwagPayPal.settings.sandbox'] ?? false; + const sandboxSetting = sandbox ? 'Sandbox' : ''; + const clientId = this.config[`SwagPayPal.settings.clientId${sandboxSetting}`]; + const clientSecret = this.config[`SwagPayPal.settings.clientSecret${sandboxSetting}`]; + + const response = await this.SwagPayPalApiCredentialsService + .validateApiCredentials(clientId, clientSecret, sandbox) + .catch((errorResponse: PayPal.ServiceError) => { + this.createNotificationFromError({ errorResponse, title: 'swag-paypal.settingForm.messageTestError' }); + + return { credentialsValid: false }; + }); + + this.isLoading = false; + + return response.credentialsValid ? Promise.resolve() : Promise.reject(); + }, + + onCredentialsChanged() { + this.isGetCredentialsSuccessful = null; + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.html.twig b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.html.twig new file mode 100644 index 000000000..f307ba9a9 --- /dev/null +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.html.twig @@ -0,0 +1,122 @@ +{% block sw_first_run_wizard_paypal_credentials %} +
+ + {% block sw_first_run_wizard_paypal_credentials_inner %} + + + {% block sw_first_run_wizard_paypal_credentials_intro %} +

+ {{ $tc('swag-paypal-frw-credentials.textIntroPayPal') }} +

+ {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_sandbox %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_button_container %} +
+ + {% block sw_first_run_wizard_paypal_credentials_button %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_indicator %} +
+ +
+ {% endblock %} +
+ {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_client_id %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_client_secret %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_merchant_id %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_client_id_sandbox %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_client_secret_sandbox %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_merchant_id_sandbox %} + + {% endblock %} + + {% block sw_first_run_wizard_paypal_credentials_set_default %} + + {% endblock %} + {% endblock %} +
+{% endblock %} diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.scss b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.scss new file mode 100644 index 000000000..194384243 --- /dev/null +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials-deprecated/sw-first-run-wizard-paypal-credentials.scss @@ -0,0 +1,31 @@ +@import "~scss/variables"; + +.sw-first-run-wizard-paypal-credentials { + width: 100%; + + .sw-first-run-wizard-paypal-credentials__headerText { + font-weight: bold; + color: $color-darkgray-200; + margin-bottom: 22px; + } + + .sw-first-run-wizard-paypal-credentials__button-container { + display: flex; + align-items: center; + margin-bottom: 22px; + + .sw-first-run-wizard-paypal-credentials__indicator { + display: inline; + margin-left: 25px; + + .sw-first-run-wizard-paypal-credentials__icon-successful { + color: $color-emerald-500; + margin-top: -5px; + } + + .sw-first-run-wizard-paypal-credentials__text-indicator { + margin-left: 8px; + } + } + } +} diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/index.ts b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/index.ts index c15319caa..23a5b49db 100644 --- a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/index.ts +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/index.ts @@ -1,4 +1,3 @@ -import type * as PayPal from 'src/types'; import template from './sw-first-run-wizard-paypal-credentials.html.twig'; import './sw-first-run-wizard-paypal-credentials.scss'; @@ -6,36 +5,28 @@ export default Shopware.Component.wrapComponentConfig({ template, inject: [ - 'systemConfigApiService', 'SwagPaypalPaymentMethodService', + 'SwagPayPalSettingsService', ], mixins: [ Shopware.Mixin.getByName('swag-paypal-notification'), - Shopware.Mixin.getByName('swag-paypal-credentials-loader'), + Shopware.Mixin.getByName('swag-paypal-settings'), ], - data() { + data(): { + isLoading: boolean; + asDefault: boolean; + error: { detail: string; code: string } | null; + } { return { - config: {} as PayPal.SystemConfig, isLoading: false, - setDefault: false, + asDefault: false, + error: null, }; }, computed: { - sandboxMode() { - return this.config['SwagPayPal.settings.sandbox'] || false; - }, - - onboardingUrl() { - return this.sandboxMode ? this.onboardingUrlSandbox : this.onboardingUrlLive; - }, - - onboardingCallback() { - return this.sandboxMode ? 'onboardingCallbackSandbox' : 'onboardingCallbackLive'; - }, - buttonConfig() { const prev = this.$super('buttonConfig') as { key: string; action: () => Promise }[]; @@ -48,54 +39,38 @@ export default Shopware.Component.wrapComponentConfig({ }); }, - credentialsProvided() { - return (!this.sandboxMode && this.credentialsProvidedLive) - || (this.sandboxMode && this.credentialsProvidedSandbox); - }, - - credentialsProvidedLive() { - return !!this.config['SwagPayPal.settings.clientId'] - && !!this.config['SwagPayPal.settings.clientSecret']; - }, - - credentialsProvidedSandbox() { - return !!this.config['SwagPayPal.settings.clientIdSandbox'] - && !!this.config['SwagPayPal.settings.clientSecretSandbox']; + hasLiveCredentials() { + return !!this.settingsStore.get('SwagPayPal.settings.clientId') + && !!this.settingsStore.get('SwagPayPal.settings.clientSecret'); }, - }, - - created() { - this.createdComponent(); - }, - methods: { - createdComponent() { - this.$super('createdComponent'); - this.fetchPayPalConfig(); + hasSandboxCredentials() { + return !!this.settingsStore.get('SwagPayPal.settings.clientIdSandbox') + && !!this.settingsStore.get('SwagPayPal.settings.clientSecretSandbox'); }, - onPayPalCredentialsLoadSuccess(clientId: string, clientSecret: string, merchantPayerId: string, sandbox: boolean) { - this.setConfig(clientId, clientSecret, merchantPayerId, sandbox); + hasCredentials() { + return (!this.settingsStore.isSandbox && this.hasLiveCredentials) + || (this.settingsStore.isSandbox && this.hasSandboxCredentials); }, - onPayPalCredentialsLoadFailed(sandbox: boolean) { - this.setConfig('', '', '', sandbox); - this.createNotificationError({ - message: this.$tc('swag-paypal-frw-credentials.messageFetchedError'), - // @ts-expect-error - duration is not defined correctly - duration: 10000, - }); + inputsDisabled() { + return this.isLoading || this.settingsStore.isLoading || this.savingSettings === 'loading'; }, + }, - setConfig(clientId: string, clientSecret: string, merchantPayerId: string, sandbox: boolean) { - const suffix = sandbox ? 'Sandbox' : ''; - this.$set(this.config, `SwagPayPal.settings.clientId${suffix}`, clientId); - this.$set(this.config, `SwagPayPal.settings.clientSecret${suffix}`, clientSecret); - this.$set(this.config, `SwagPayPal.settings.merchantPayerId${suffix}`, merchantPayerId); + watch: { + 'settingsStore.allConfigs': { + deep: true, + handler() { + this.resetError(); + }, }, + }, + methods: { async onClickNext(): Promise { - if (!this.credentialsProvided) { + if (!this.hasCredentials) { this.createNotificationError({ message: this.$tc('swag-paypal-frw-credentials.messageNoCredentials'), }); @@ -103,67 +78,57 @@ export default Shopware.Component.wrapComponentConfig({ return true; } - try { - // Do not test the credentials if they have been fetched from the PayPal api - if (!this.isGetCredentialsSuccessful) { - await this.testApiCredentials(); - } - - await this.saveConfig(); - - this.$emit('frw-redirect', 'sw.first.run.wizard.index.plugins'); - - return false; - } catch { - return true; - } - }, - - fetchPayPalConfig() { this.isLoading = true; - return this.systemConfigApiService.getValues('SwagPayPal.settings', null) - .then((values: PayPal.SystemConfig) => { - this.config = values; - }) - .finally(() => { - this.isLoading = false; - }); - }, - async saveConfig() { - this.isLoading = true; - await this.systemConfigApiService.saveValues(this.config, null); + const information = (await this.saveSettings())?.null; + + const haveChanged = (!this.settingsStore.isSandbox && information?.liveCredentialsChanged) || (this.settingsStore.isSandbox && information?.sandboxCredentialsChanged); + let areValid = (!this.settingsStore.isSandbox && information?.liveCredentialsValid) || (this.settingsStore.isSandbox && information?.sandboxCredentialsValid); - if (this.setDefault) { - await this.SwagPaypalPaymentMethodService.setDefaultPaymentForSalesChannel(); + if (!haveChanged) { + areValid = await this.onTest(); } this.isLoading = false; - }, - async testApiCredentials() { - this.isLoading = true; + if (!areValid) { + this.error = { + detail: this.$tc('swag-paypal-frw-credentials.messageInvalidCredentials'), + code: 'ASD', + }; - const sandbox = this.config['SwagPayPal.settings.sandbox'] ?? false; - const sandboxSetting = sandbox ? 'Sandbox' : ''; - const clientId = this.config[`SwagPayPal.settings.clientId${sandboxSetting}`]; - const clientSecret = this.config[`SwagPayPal.settings.clientSecret${sandboxSetting}`]; + return true; + } - const response = await this.SwagPayPalApiCredentialsService - .validateApiCredentials(clientId, clientSecret, sandbox) - .catch((errorResponse: PayPal.ServiceError) => { - this.createNotificationFromError({ errorResponse, title: 'swag-paypal.settingForm.messageTestError' }); + this.$emit('frw-redirect', 'sw.first.run.wizard.index.plugins'); - return { credentialsValid: false }; - }); + if (this.asDefault) { + try { + await this.SwagPaypalPaymentMethodService.setDefaultPaymentForSalesChannel(this.settingsStore.salesChannel); + } catch { + return true; + } + } - this.isLoading = false; + return false; + }, - return response.credentialsValid ? Promise.resolve() : Promise.reject(); + resetError() { + this.error = null; }, - onCredentialsChanged() { - this.isGetCredentialsSuccessful = null; + async onTest() { + const suffix = this.settingsStore.isSandbox ? 'Sandbox' : ''; + + return this.SwagPayPalSettingsService + .testApiCredentials( + this.settingsStore.get(`SwagPayPal.settings.clientId${suffix}`), + this.settingsStore.get(`SwagPayPal.settings.clientSecret${suffix}`), + this.settingsStore.get(`SwagPayPal.settings.merchantPayerId${suffix}`), + this.settingsStore.isSandbox, + ) + .then((response) => response.valid) + .catch(() => false); }, }, }); diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.html.twig b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.html.twig index f307ba9a9..09a839e7a 100644 --- a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.html.twig +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.html.twig @@ -1,122 +1,85 @@ {% block sw_first_run_wizard_paypal_credentials %}
+

+ {{ $tc('swag-paypal-frw-credentials.textIntroPayPal') }} +

- {% block sw_first_run_wizard_paypal_credentials_inner %} - - - {% block sw_first_run_wizard_paypal_credentials_intro %} -

- {{ $tc('swag-paypal-frw-credentials.textIntroPayPal') }} -

- {% endblock %} + - {% block sw_first_run_wizard_paypal_credentials_sandbox %} - + - {% endblock %} - - {% block sw_first_run_wizard_paypal_credentials_button_container %} -
- - {% block sw_first_run_wizard_paypal_credentials_button %} - - {% endblock %} - - {% block sw_first_run_wizard_paypal_credentials_indicator %} -
- -
- {% endblock %} -
- {% endblock %} + + {{ $tc('swag-paypal-frw-credentials.buttonGetSandboxCredentials') }} + - {% block sw_first_run_wizard_paypal_credentials_client_id %} - - {% endblock %} - - {% block sw_first_run_wizard_paypal_credentials_client_secret %} - - {% endblock %} - - {% block sw_first_run_wizard_paypal_credentials_merchant_id %} - - {% endblock %} - {% block sw_first_run_wizard_paypal_credentials_client_id_sandbox %} - - {% endblock %} - - {% block sw_first_run_wizard_paypal_credentials_client_secret_sandbox %} - - {% endblock %} - - {% block sw_first_run_wizard_paypal_credentials_merchant_id_sandbox %} - - {% endblock %} - {% block sw_first_run_wizard_paypal_credentials_set_default %} - {% endblock %} - {% endblock %} + + +
{% endblock %} diff --git a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.scss b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.scss index 194384243..2f3fe6ca1 100644 --- a/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.scss +++ b/src/Resources/app/administration/src/module/extension/sw-first-run-wizard/sw-first-run-wizard-paypal-credentials/sw-first-run-wizard-paypal-credentials.scss @@ -3,29 +3,29 @@ .sw-first-run-wizard-paypal-credentials { width: 100%; - .sw-first-run-wizard-paypal-credentials__headerText { + display: flex; + flex-direction: column; + gap: 20px; + + &__headerText { font-weight: bold; color: $color-darkgray-200; - margin-bottom: 22px; } - .sw-first-run-wizard-paypal-credentials__button-container { - display: flex; - align-items: center; - margin-bottom: 22px; + &__as_default { + margin: 0; + } - .sw-first-run-wizard-paypal-credentials__indicator { - display: inline; - margin-left: 25px; + .sw-field.sw-block-field, .sw-field--switch { + margin: 0; + } - .sw-first-run-wizard-paypal-credentials__icon-successful { - color: $color-emerald-500; - margin-top: -5px; - } + .sw-field--switch .sw-field__label label { + padding-top: 0; + padding-bottom: 0; + } - .sw-first-run-wizard-paypal-credentials__text-indicator { - margin-left: 8px; - } - } + .swag-paypal-onboarding-button { + align-self: start; } }