diff --git a/src/Controller/StoreApi/Config/ConfigControllerBase.php b/src/Controller/StoreApi/Config/ConfigControllerBase.php new file mode 100644 index 000000000..d7335c766 --- /dev/null +++ b/src/Controller/StoreApi/Config/ConfigControllerBase.php @@ -0,0 +1,94 @@ +settingsService = $settingsService; + $this->configService = $configService; + $this->salesChannelLocale = $salesChannelLocale; + $this->logger = $logger; + } + + + /** + * @Route("/store-api/mollie/config", name="store-api.mollie.config", methods={"GET"}) + * + * @param SalesChannelContext $context + * @throws \Exception + * @return StoreApiResponse + */ + public function getConfig(SalesChannelContext $context): StoreApiResponse + { + try { + $scId = $context->getSalesChannelId(); + + $settings = $this->settingsService->getSettings($scId); + + $profileId = (string)$settings->getProfileId(); + $locale = $this->salesChannelLocale->getLocale($context); + + if (empty($profileId)) { + # if its somehow not yet loaded (plugin config in admin when clicking save) + # then load it right now + $this->configService->fetchProfileId($scId); + + $settings = $this->settingsService->getSettings($scId); + $profileId = (string)$settings->getProfileId(); + } + + return new ConfigResponse( + $profileId, + $settings->isTestMode(), + $locale + ); + } catch (\Exception $e) { + $this->logger->error( + 'Error when fetching config in Store API: ' . $e->getMessage(), + [ + 'error' => $e, + ] + ); + + throw $e; + } + } +} diff --git a/src/Controller/StoreApi/Config/Response/ConfigResponse.php b/src/Controller/StoreApi/Config/Response/ConfigResponse.php new file mode 100644 index 000000000..37565db8a --- /dev/null +++ b/src/Controller/StoreApi/Config/Response/ConfigResponse.php @@ -0,0 +1,34 @@ + + */ + protected $object; + + + /** + * @param string $profileId + * @param bool $isTestMode + * @param string $defaultLocale + */ + public function __construct(string $profileId, bool $isTestMode, string $defaultLocale) + { + $this->object = new ArrayStruct( + [ + 'profileId' => $profileId, + 'testMode' => $isTestMode, + 'locale' => $defaultLocale, + ], + 'mollie_payments_config' + ); + + parent::__construct($this->object); + } +} diff --git a/src/Controller/StoreApi/Config/Sw6/ConfigController.php b/src/Controller/StoreApi/Config/Sw6/ConfigController.php new file mode 100644 index 000000000..bdc470999 --- /dev/null +++ b/src/Controller/StoreApi/Config/Sw6/ConfigController.php @@ -0,0 +1,14 @@ +languageRepository->search($criteria, $context); } + + /** + * @param string $languageId + * @param Context $context + * @return null|LanguageEntity + */ + public function findById(string $languageId, Context $context): ?LanguageEntity + { + $languageCriteria = new Criteria(); + $languageCriteria->addAssociation('locale'); + $languageCriteria->addFilter(new EqualsFilter('id', $languageId)); + + $languagesResult = $this->search($languageCriteria, $context); + + return $languagesResult->first(); + } } diff --git a/src/Repository/Language/LanguageRepositoryInterface.php b/src/Repository/Language/LanguageRepositoryInterface.php index af18d99f0..d772d82c3 100644 --- a/src/Repository/Language/LanguageRepositoryInterface.php +++ b/src/Repository/Language/LanguageRepositoryInterface.php @@ -6,6 +6,7 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult; +use Shopware\Core\System\Language\LanguageEntity; interface LanguageRepositoryInterface { @@ -15,4 +16,11 @@ interface LanguageRepositoryInterface * @return EntitySearchResult */ public function search(Criteria $criteria, Context $context): EntitySearchResult; + + /** + * @param string $languageId + * @param Context $context + * @return null|LanguageEntity + */ + public function findById(string $languageId, Context $context): ?LanguageEntity; } diff --git a/src/Resources/config/compatibility/controller.xml b/src/Resources/config/compatibility/controller.xml index 60e9ee634..c07eed50c 100644 --- a/src/Resources/config/compatibility/controller.xml +++ b/src/Resources/config/compatibility/controller.xml @@ -185,6 +185,13 @@ + + + + + + + diff --git a/src/Resources/config/compatibility/controller_6.5.xml b/src/Resources/config/compatibility/controller_6.5.xml index ef39bd795..933fd6fe1 100644 --- a/src/Resources/config/compatibility/controller_6.5.xml +++ b/src/Resources/config/compatibility/controller_6.5.xml @@ -153,14 +153,16 @@ - + - + @@ -176,7 +178,8 @@ - + @@ -190,7 +193,15 @@ - + + + + + + + + @@ -207,7 +218,8 @@ - + diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index c15ad23e6..da475d575 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -148,9 +148,9 @@ - + diff --git a/src/Resources/config/services/services.xml b/src/Resources/config/services/services.xml index 4c32a5a86..0a77f60ad 100644 --- a/src/Resources/config/services/services.xml +++ b/src/Resources/config/services/services.xml @@ -147,6 +147,10 @@ + + + + diff --git a/src/Service/ConfigService.php b/src/Service/ConfigService.php index 30396b3ee..1fa104f88 100644 --- a/src/Service/ConfigService.php +++ b/src/Service/ConfigService.php @@ -3,6 +3,7 @@ namespace Kiener\MolliePayments\Service; use Kiener\MolliePayments\Gateway\MollieGatewayInterface; +use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Core\System\SystemConfig\SystemConfigService; class ConfigService diff --git a/src/Service/SalesChannel/SalesChannelLocale.php b/src/Service/SalesChannel/SalesChannelLocale.php new file mode 100644 index 000000000..172d63b40 --- /dev/null +++ b/src/Service/SalesChannel/SalesChannelLocale.php @@ -0,0 +1,79 @@ +repoLanguages = $repoLanguages; + } + + + /** + * @param SalesChannelContext $salesChannelContext + * @return string + */ + public function getLocale(SalesChannelContext $salesChannelContext): string + { + # Get the language object from the sales channel context. + $locale = ''; + + $salesChannel = $salesChannelContext->getSalesChannel(); + $languageId = $salesChannel->getLanguageId(); + + $language = $this->repoLanguages->findById($languageId, $salesChannelContext->getContext()); + + if ($language !== null && $language->getLocale() !== null) { + $locale = $language->getLocale()->getCode(); + } + + # Set the locale based on the current storefront. + if ($locale !== null && $locale !== '') { + $locale = str_replace('-', '_', $locale); + } + + # Check if the shop locale is available. + if ($locale === '' || !in_array($locale, self::AVAILABLE_LOCALES, true)) { + $locale = 'en_GB'; + } + + return $locale; + } +} diff --git a/src/Subscriber/CheckoutConfirmPageSubscriber.php b/src/Subscriber/CheckoutConfirmPageSubscriber.php index 9c62e8db9..2a2755fb6 100644 --- a/src/Subscriber/CheckoutConfirmPageSubscriber.php +++ b/src/Subscriber/CheckoutConfirmPageSubscriber.php @@ -12,6 +12,7 @@ use Kiener\MolliePayments\Service\CustomerService; use Kiener\MolliePayments\Service\CustomFieldService; use Kiener\MolliePayments\Service\MandateServiceInterface; +use Kiener\MolliePayments\Service\SalesChannel\SalesChannelLocale; use Kiener\MolliePayments\Service\SettingsService; use Kiener\MolliePayments\Setting\MollieSettingStruct; use Kiener\MolliePayments\Struct\PaymentMethod\PaymentMethodAttributes; @@ -52,10 +53,9 @@ class CheckoutConfirmPageSubscriber implements EventSubscriberInterface private $settings; /** - * @var LanguageRepositoryInterface + * @var SalesChannelLocale */ - private $repoLanguages; - + private $salesChannelLocale; /** * @var MandateServiceInterface @@ -88,17 +88,17 @@ public static function getSubscribedEvents(): array /** * @param MollieApiFactory $apiFactory * @param SettingsService $settingsService - * @param LanguageRepositoryInterface $languageRepositoryInterface * @param MandateServiceInterface $mandateService * @param MollieGatewayInterface $mollieGateway + * @param SalesChannelLocale $salesChannelLocale */ - public function __construct(MollieApiFactory $apiFactory, SettingsService $settingsService, LanguageRepositoryInterface $languageRepositoryInterface, MandateServiceInterface $mandateService, MollieGatewayInterface $mollieGateway) + public function __construct(MollieApiFactory $apiFactory, SettingsService $settingsService, MandateServiceInterface $mandateService, MollieGatewayInterface $mollieGateway, SalesChannelLocale $salesChannelLocale) { $this->apiFactory = $apiFactory; $this->settingsService = $settingsService; - $this->repoLanguages = $languageRepositoryInterface; $this->mandateService = $mandateService; $this->mollieGateway = $mollieGateway; + $this->salesChannelLocale = $salesChannelLocale; } @@ -114,7 +114,7 @@ public function addDataToPage($args): void $mollieAttributes = new PaymentMethodAttributes($currentSelectedPaymentMethod); # load additional data only for mollie payment methods - if (! $mollieAttributes->isMolliePayment()) { + if (!$mollieAttributes->isMolliePayment()) { return; } @@ -144,75 +144,9 @@ public function addDataToPage($args): void */ private function addMollieLocaleVariableToPage($args): void { - /** - * Build an array of available locales. - */ - $availableLocales = [ - 'en_US', - 'en_GB', - 'nl_NL', - 'fr_FR', - 'it_IT', - 'de_DE', - 'de_AT', - 'de_CH', - 'es_ES', - 'ca_ES', - 'nb_NO', - 'pt_PT', - 'sv_SE', - 'fi_FI', - 'da_DK', - 'is_IS', - 'hu_HU', - 'pl_PL', - 'lv_LV', - 'lt_LT' - ]; - - /** - * Get the language object from the sales channel context. - */ - $locale = ''; - $salesChannelContext = $args->getSalesChannelContext(); - - $salesChannel = $salesChannelContext->getSalesChannel(); - if ($salesChannel !== null) { - $languageId = $salesChannel->getLanguageId(); - if ($languageId !== null) { - $languageCriteria = new Criteria(); - $languageCriteria->addAssociation('locale'); - $languageCriteria->addFilter(new EqualsFilter('id', $languageId)); - - $languagesResult = $this->repoLanguages->search($languageCriteria, $args->getContext()); - /** @var LanguageEntity $language */ - $language = $languagesResult->first(); - - if ($language !== null && $language->getLocale() !== null) { - $locale = $language->getLocale()->getCode(); - } - } - } - - - /** - * Set the locale based on the current storefront. - */ - - - if ($locale !== null && $locale !== '') { - $locale = str_replace('-', '_', $locale); - } - - /** - * Check if the shop locale is available. - */ - if ($locale === '' || !in_array($locale, $availableLocales, true)) { - $locale = 'en_GB'; - } - + $locale = $this->salesChannelLocale->getLocale($salesChannelContext); $args->getPage()->assign([ 'mollie_locale' => $locale, diff --git a/tests/Cypress/cypress/e2e/store-api/config.cy.js b/tests/Cypress/cypress/e2e/store-api/config.cy.js new file mode 100644 index 000000000..ec78ef665 --- /dev/null +++ b/tests/Cypress/cypress/e2e/store-api/config.cy.js @@ -0,0 +1,30 @@ +import StoreAPIClient from "Services/shopware/StoreAPIClient"; +import Shopware from "Services/shopware/Shopware" + +const shopware = new Shopware(); + +const client = new StoreAPIClient(shopware.getStoreApiToken()); + + +it('C2040032: Mollie Config can be retrieved using Store-API', () => { + + const request = new Promise((resolve) => { + client.get('/mollie/config').then(response => { + resolve({'data': response.data}); + }); + }) + + cy.wrap(request).its('data').then(response => { + cy.wrap(response).its('apiAlias').should('eq', 'mollie_payments_config') + + cy.wrap(response).its('profileId').should('exist'); + cy.wrap(response).its('profileId').should('not.eql', ''); + + cy.wrap(response).its('testMode').should('exist'); + cy.wrap(response).its('testMode').should('not.eql', ''); + + cy.wrap(response).its('locale').should('exist'); + cy.wrap(response).its('locale').should('not.eql', ''); + }); +}) + diff --git a/tests/PHPUnit/Fakes/Repositories/FakeLanguageRepository.php b/tests/PHPUnit/Fakes/Repositories/FakeLanguageRepository.php new file mode 100644 index 000000000..32c45567e --- /dev/null +++ b/tests/PHPUnit/Fakes/Repositories/FakeLanguageRepository.php @@ -0,0 +1,49 @@ +foundLanguage = $foundLanguage; + } + + + /** + * @param Criteria $criteria + * @param Context $context + * @return EntitySearchResult + */ + public function search(Criteria $criteria, Context $context): EntitySearchResult + { + // TODO: Implement search() method. + } + + /** + * @param string $languageId + * @param Context $context + * @return LanguageEntity|null + */ + public function findById(string $languageId, Context $context): ?LanguageEntity + { + return $this->foundLanguage; + } + +} diff --git a/tests/PHPUnit/Service/SalesChannel/SalesChannelLocaleTest.php b/tests/PHPUnit/Service/SalesChannel/SalesChannelLocaleTest.php new file mode 100644 index 000000000..a2a287fb3 --- /dev/null +++ b/tests/PHPUnit/Service/SalesChannel/SalesChannelLocaleTest.php @@ -0,0 +1,158 @@ +fakeSalesChannelContext = $this->getMockBuilder(SalesChannelContext::class) + ->disableOriginalConstructor() + ->getMock(); + } + + + /** + * This test verifies that the available locales are correct. + * This is a list of possible values that Mollie allows. + * + * @return void + */ + public function testAvailableLocales(): void + { + # our data provider has no flat list of values, but we still want to reuse it. + # so lets just extract the first internal value of each item to get a flat list + # of expected locales. + $expected = array_map(function ($list) { + return $list[0]; + }, $this->getAvailableLocales()); + + $this->assertEquals($expected, SalesChannelLocale::AVAILABLE_LOCALES); + } + + + /** + * @return string[] + */ + public function getAvailableLocales(): array + { + return [ + ['en_US'], + ['en_GB'], + ['nl_NL'], + ['fr_FR'], + ['it_IT'], + ['de_DE'], + ['de_AT'], + ['de_CH'], + ['es_ES'], + ['ca_ES'], + ['nb_NO'], + ['pt_PT'], + ['sv_SE'], + ['fi_FI'], + ['da_DK'], + ['is_IS'], + ['hu_HU'], + ['pl_PL'], + ['lv_LV'], + ['lt_LT'] + ]; + } + + /** + * This test verifies that a locale is correctly returned from a sales channel if available. + * We fake the repository that returns us a given locale for our sales channel. + * That locale is in the list of available locales and should therefore be correctly returned in our function. + * + * @dataProvider getAvailableLocales + * + * @param string $locale + * @return void + */ + public function testAvailableLocalesAreFound(string $locale): void + { + $scLanguage = $this->buildSalesChannelLanguage($locale); + + $repoLanguages = new FakeLanguageRepository($scLanguage); + + $service = new SalesChannelLocale($repoLanguages); + + $detectedLocale = $service->getLocale($this->fakeSalesChannelContext); + + $this->assertEquals($locale, $detectedLocale); + } + + /** + * This test verifies that an invalid locale leads to our default locale which + * is en_GB as result. + */ + public function testInvalidLocaleLeadsToEnglishDefault(): void + { + $scLanguage = $this->buildSalesChannelLanguage('zz_ZZ'); + + $repoLanguages = new FakeLanguageRepository($scLanguage); + + $service = new SalesChannelLocale($repoLanguages); + + $detectedLocale = $service->getLocale($this->fakeSalesChannelContext); + + $this->assertEquals('en_GB', $detectedLocale); + } + + /** + * This test verifies that we get en_GB as default if our sales channel + * does not have a locale or language set. + * + * @return void + */ + public function testSalesChannelWithoutLocale(): void + { + $repoLanguages = new FakeLanguageRepository(null); + + $service = new SalesChannelLocale($repoLanguages); + + $detectedLocale = $service->getLocale($this->fakeSalesChannelContext); + + $this->assertEquals('en_GB', $detectedLocale); + } + + /** + * @param string $locale + * @return LanguageEntity + */ + private function buildSalesChannelLanguage(string $locale): LanguageEntity + { + # we always need to make sure to use this pattern nl-NL + # this is how it looks like in Shopware + $locale = str_replace('_', '-', $locale); + + $foundLocale = new LocaleEntity(); + $foundLocale->setCode($locale); + + $scLanguage = new LanguageEntity(); + $scLanguage->setLocale($foundLocale); + + return $scLanguage; + } + +} diff --git a/tests/Swagger/mollie-headless.yaml b/tests/Swagger/mollie-headless.yaml index 3b36ae125..bf27c1471 100644 --- a/tests/Swagger/mollie-headless.yaml +++ b/tests/Swagger/mollie-headless.yaml @@ -142,6 +142,18 @@ paths: "200": description: "successful operation" + /store-api/mollie/config: + get: + tags: + - "Mollie Config" + summary: "Gets the basic Mollie configuration like Profile IDs and more for component integration." + security: + - AccessKey: [ ] + - AccessToken: [ ] + responses: + "200": + description: "successful operation" + /store-api/mollie/creditcard/store-token/{customerId}/{cardToken}: post: tags: