diff --git a/CHANGELOG.md b/CHANGELOG.md index c99be372..64985449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). +# [v5.5.0](https://github.com/sequra/integration-core/tree/v5.5.0) +## Added +- Affiliate configuration support: the `AffiliateSettings` entity and `AffiliateSettingsService`, the `get-affiliate-settings` and `save-affiliate-settings` configuration webhook topics, and connect time provisioning that reads the `affiliate` block from the merchant `configuration_data` and persists it. + # [v1.0.13](https://github.com/sequra/integration-core/tree/v1.0.13) ## Changed - Added compatibility with PHP8.2. diff --git a/README.md b/README.md index 6562abb4..045b9915 100644 --- a/README.md +++ b/README.md @@ -3171,6 +3171,8 @@ The Configuration WebhookAPI follows a **simplified Controller pattern** with ** - **GetShopProductsHandler**: Handles `get-shop-products` webhook topic - **GetStoreInfoHandler**: Handles `get-store-info` webhook topic - **SaveWidgetSettingsHandler**: Handles `save-widget-settings` webhook topic + - **GetAffiliateSettingsHandler**: Handles `get-affiliate-settings` webhook topic + - **SaveAffiliateSettingsHandler**: Handles `save-affiliate-settings` webhook topic #### Webhook Processing Flow diff --git a/src/BusinessLogic/BootstrapComponent.php b/src/BusinessLogic/BootstrapComponent.php index f9de9782..fd620ac8 100644 --- a/src/BusinessLogic/BootstrapComponent.php +++ b/src/BusinessLogic/BootstrapComponent.php @@ -21,6 +21,8 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Controller\ConfigurationWebhookController; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\AdvancedSettings\GetAdvancedSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\AdvancedSettings\SaveAdvancedSettingsHandler; +use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\Affiliate\GetAffiliateSettingsHandler; +use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\Affiliate\SaveAffiliateSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\BannerSettings\GetBannerSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\BannerSettings\SaveBannerSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\Enums\Topics; @@ -40,6 +42,8 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\WidgetSettings\SaveWidgetSettingsHandler; use SeQura\Core\BusinessLogic\DataAccess\AdvancedSettings\Entities\AdvancedSettings; use SeQura\Core\BusinessLogic\DataAccess\AdvancedSettings\Repositories\AdvancedSettingsRepository; +use SeQura\Core\BusinessLogic\DataAccess\Affiliate\Entities\AffiliateSettings; +use SeQura\Core\BusinessLogic\DataAccess\Affiliate\Repositories\AffiliateSettingsRepository; use SeQura\Core\BusinessLogic\DataAccess\BannerSettings\Entities\BannerSettings; use SeQura\Core\BusinessLogic\DataAccess\BannerSettings\Repositories\BannerSettingsRepository; use SeQura\Core\BusinessLogic\DataAccess\ConnectionData\Entities\ConnectionData; @@ -67,6 +71,8 @@ use SeQura\Core\BusinessLogic\Domain\AdvancedSettings\RepositoryContracts\AdvancedSettingsRepositoryInterface; use SeQura\Core\BusinessLogic\Domain\AdvancedSettings\Services\AdvancedLoggerSettingsProvider; use SeQura\Core\BusinessLogic\Domain\AdvancedSettings\Services\AdvancedSettingsService; +use SeQura\Core\BusinessLogic\Domain\Affiliate\RepositoryContracts\AffiliateSettingsRepositoryInterface; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Services\AffiliateSettingsService; use SeQura\Core\BusinessLogic\Domain\BannerSettings\RepositoryContracts\BannerSettingsRepositoryInterface; use SeQura\Core\BusinessLogic\Domain\BannerSettings\Services\BannerSettingsService; use SeQura\Core\BusinessLogic\Domain\Integration\Banner\BannerServiceInterface; @@ -332,6 +338,16 @@ static function () { ); } ); + + ServiceRegister::registerService( + AffiliateSettingsRepositoryInterface::class, + static function () { + return new AffiliateSettingsRepository( + RepositoryRegistry::getRepository(AffiliateSettings::getClassName()), + ServiceRegister::getService(StoreContext::class) + ); + } + ); } /** @@ -384,7 +400,8 @@ static function () { ServiceRegister::getService(ConnectionProxyInterface::class), ServiceRegister::getService(CredentialsRepositoryInterface::class), ServiceRegister::getService(CountryConfigurationRepositoryInterface::class), - ServiceRegister::getService(PaymentMethodRepositoryInterface::class) + ServiceRegister::getService(PaymentMethodRepositoryInterface::class), + ServiceRegister::getService(AffiliateSettingsService::class) ); } ); @@ -667,6 +684,15 @@ static function () { } ); + ServiceRegister::registerService( + AffiliateSettingsService::class, + static function () { + return new AffiliateSettingsService( + ServiceRegister::getService(AffiliateSettingsRepositoryInterface::class) + ); + } + ); + ServiceRegister::registerService( LoggerSettingsProviderInterface::CLASS_NAME, static function () { @@ -1056,6 +1082,16 @@ protected static function initTopicHandlers(): void SaveAdvancedSettingsHandler::class ); + TopicHandlerRegistry::register( + Topics::GET_AFFILIATE_SETTINGS, + GetAffiliateSettingsHandler::class + ); + + TopicHandlerRegistry::register( + Topics::SAVE_AFFILIATE_SETTINGS, + SaveAffiliateSettingsHandler::class + ); + TopicHandlerRegistry::register( Topics::GET_BANNER_SETTINGS, GetBannerSettingsHandler::class @@ -1189,6 +1225,24 @@ static function () { } ); + ServiceRegister::registerService( + GetAffiliateSettingsHandler::class, + static function () { + return new GetAffiliateSettingsHandler( + ServiceRegister::getService(AffiliateSettingsService::class) + ); + } + ); + + ServiceRegister::registerService( + SaveAffiliateSettingsHandler::class, + static function () { + return new SaveAffiliateSettingsHandler( + ServiceRegister::getService(AffiliateSettingsService::class) + ); + } + ); + ServiceRegister::registerService( GetBannerSettingsHandler::class, static function () { diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Affiliate/GetAffiliateSettingsHandler.php b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Affiliate/GetAffiliateSettingsHandler.php new file mode 100644 index 00000000..4b9ee783 --- /dev/null +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Affiliate/GetAffiliateSettingsHandler.php @@ -0,0 +1,42 @@ +affiliateSettingsService = $affiliateSettingsService; + } + + /** + * @param mixed[] $payload + * + * @return Response + */ + public function handle(array $payload): Response + { + // GET is a boolean-state read: return only whether the feature is enabled, never echo the + // offer id or security token. The service always yields settings (disabled by default when + // none are stored), so the response is enabled=false when nothing is configured. + return new AffiliateSettingsResponse($this->affiliateSettingsService->getAffiliateSettings()->isEnabled()); + } +} diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Affiliate/SaveAffiliateSettingsHandler.php b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Affiliate/SaveAffiliateSettingsHandler.php new file mode 100644 index 00000000..1d948f9a --- /dev/null +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Affiliate/SaveAffiliateSettingsHandler.php @@ -0,0 +1,41 @@ +affiliateSettingsService = $affiliateSettingsService; + } + + /** + * @inheritDoc + */ + public function handle(array $payload): Response + { + $request = SaveAffiliateSettingsRequest::fromPayload($payload); + $this->affiliateSettingsService->setAffiliateSettings($request->transformToDomainModel()); + + return new SuccessResponse(); + } +} diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Enums/Topics.php b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Enums/Topics.php index 91debd32..2d7b70d7 100644 --- a/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Enums/Topics.php +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/Enums/Topics.php @@ -77,6 +77,14 @@ interface Topics * @var string */ public const GET_STORE_INFO = 'get-store-info'; + /** + * @var string + */ + public const GET_AFFILIATE_SETTINGS = 'get-affiliate-settings'; + /** + * @var string + */ + public const SAVE_AFFILIATE_SETTINGS = 'save-affiliate-settings'; /** * @var string[] */ @@ -97,6 +105,8 @@ interface Topics self::GET_SHOP_CATEGORIES, self::GET_SHOP_PRODUCTS, self::GET_SELLING_COUNTRIES, - self::GET_STORE_INFO + self::GET_STORE_INFO, + self::GET_AFFILIATE_SETTINGS, + self::SAVE_AFFILIATE_SETTINGS ]; } diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Requests/Affiliate/SaveAffiliateSettingsRequest.php b/src/BusinessLogic/ConfigurationWebhookAPI/Requests/Affiliate/SaveAffiliateSettingsRequest.php new file mode 100644 index 00000000..7afe6a7a --- /dev/null +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Requests/Affiliate/SaveAffiliateSettingsRequest.php @@ -0,0 +1,63 @@ +isEnabled = $isEnabled; + $this->offerId = $offerId; + $this->securityToken = $securityToken; + } + + /** + * @param mixed[] $payload + * + * @return self + */ + public static function fromPayload(array $payload): object + { + return new self( + $payload['isEnabled'] ?? false, + (string)($payload['offerId'] ?? ''), + (string)($payload['securityToken'] ?? '') + ); + } + + /** + * @return AffiliateSettings + */ + public function transformToDomainModel(): AffiliateSettings + { + return new AffiliateSettings($this->isEnabled, $this->offerId, $this->securityToken); + } +} diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Responses/Affiliate/AffiliateSettingsResponse.php b/src/BusinessLogic/ConfigurationWebhookAPI/Responses/Affiliate/AffiliateSettingsResponse.php new file mode 100644 index 00000000..68dd1d92 --- /dev/null +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Responses/Affiliate/AffiliateSettingsResponse.php @@ -0,0 +1,34 @@ +isEnabled = $isEnabled; + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return ['isEnabled' => $this->isEnabled]; + } +} diff --git a/src/BusinessLogic/DataAccess/Affiliate/Entities/AffiliateSettings.php b/src/BusinessLogic/DataAccess/Affiliate/Entities/AffiliateSettings.php new file mode 100644 index 00000000..1121a518 --- /dev/null +++ b/src/BusinessLogic/DataAccess/Affiliate/Entities/AffiliateSettings.php @@ -0,0 +1,108 @@ +storeId = $data['storeId'] ?? ''; + + $this->affiliateSettings = new DomainAffiliateSettings( + (bool)self::getDataValue($affiliateSettings, 'isEnabled', false), + (string)self::getDataValue($affiliateSettings, 'offerId', ''), + (string)self::getDataValue($affiliateSettings, 'securityToken', '') + ); + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + $data = parent::toArray(); + $data['storeId'] = $this->storeId; + $data['affiliateSettings'] = [ + 'isEnabled' => $this->affiliateSettings->isEnabled(), + 'offerId' => $this->affiliateSettings->getOfferId(), + 'securityToken' => $this->affiliateSettings->getSecurityToken() + ]; + + return $data; + } + + /** + * @inheritDoc + */ + public function getConfig(): EntityConfiguration + { + $indexMap = new IndexMap(); + + $indexMap->addStringIndex('storeId'); + + return new EntityConfiguration($indexMap, 'AffiliateSettings'); + } + + /** + * @return string + */ + public function getStoreId(): string + { + return $this->storeId; + } + + /** + * @param string $storeId + */ + public function setStoreId(string $storeId): void + { + $this->storeId = $storeId; + } + + /** + * @return DomainAffiliateSettings + */ + public function getAffiliateSettings(): DomainAffiliateSettings + { + return $this->affiliateSettings; + } + + /** + * @param DomainAffiliateSettings $affiliateSettings + */ + public function setAffiliateSettings(DomainAffiliateSettings $affiliateSettings): void + { + $this->affiliateSettings = $affiliateSettings; + } +} diff --git a/src/BusinessLogic/DataAccess/Affiliate/Repositories/AffiliateSettingsRepository.php b/src/BusinessLogic/DataAccess/Affiliate/Repositories/AffiliateSettingsRepository.php new file mode 100644 index 00000000..b6eab3ea --- /dev/null +++ b/src/BusinessLogic/DataAccess/Affiliate/Repositories/AffiliateSettingsRepository.php @@ -0,0 +1,95 @@ +repository = $repository; + $this->storeContext = $storeContext; + } + + /** + * @inheritDoc + * + * @throws QueryFilterInvalidParamException + */ + public function getAffiliateSettings(): ?AffiliateSettings + { + $entity = $this->getAffiliateSettingsEntity(); + + return $entity ? $entity->getAffiliateSettings() : null; + } + + /** + * @inheritDoc + * + * @throws QueryFilterInvalidParamException + */ + public function setAffiliateSettings(AffiliateSettings $settings): void + { + $existingAffiliateSettings = $this->getAffiliateSettingsEntity(); + + if ($existingAffiliateSettings) { + $existingAffiliateSettings->setAffiliateSettings($settings); + $existingAffiliateSettings->setStoreId($this->storeContext->getStoreId()); + $this->repository->update($existingAffiliateSettings); + + return; + } + + $entity = new AffiliateSettingsEntity(); + $entity->setStoreId($this->storeContext->getStoreId()); + $entity->setAffiliateSettings($settings); + $this->repository->save($entity); + } + + /** + * Gets the affiliate settings entity from the database. + * + * @return ?AffiliateSettingsEntity + * + * @throws QueryFilterInvalidParamException + */ + protected function getAffiliateSettingsEntity(): ?AffiliateSettingsEntity + { + $queryFilter = new QueryFilter(); + $queryFilter->where('storeId', Operators::EQUALS, $this->storeContext->getStoreId()); + + /** + * @var AffiliateSettingsEntity $affiliateSettings + */ + $affiliateSettings = $this->repository->selectOne($queryFilter); + + return $affiliateSettings; + } +} diff --git a/src/BusinessLogic/Domain/Affiliate/Models/AffiliateSettings.php b/src/BusinessLogic/Domain/Affiliate/Models/AffiliateSettings.php new file mode 100644 index 00000000..0fe7533b --- /dev/null +++ b/src/BusinessLogic/Domain/Affiliate/Models/AffiliateSettings.php @@ -0,0 +1,75 @@ +isEnabled = $isEnabled && '' !== $offerId && '' !== $securityToken; + $this->offerId = $offerId; + $this->securityToken = $securityToken; + } + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->isEnabled; + } + + /** + * @return string + */ + public function getOfferId(): string + { + return $this->offerId; + } + + /** + * @return string + */ + public function getSecurityToken(): string + { + return $this->securityToken; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'isEnabled' => $this->isEnabled, + 'offerId' => $this->offerId, + 'securityToken' => $this->securityToken, + ]; + } +} diff --git a/src/BusinessLogic/Domain/Affiliate/RepositoryContracts/AffiliateSettingsRepositoryInterface.php b/src/BusinessLogic/Domain/Affiliate/RepositoryContracts/AffiliateSettingsRepositoryInterface.php new file mode 100644 index 00000000..a4712006 --- /dev/null +++ b/src/BusinessLogic/Domain/Affiliate/RepositoryContracts/AffiliateSettingsRepositoryInterface.php @@ -0,0 +1,25 @@ +affiliateSettingsRepository = $affiliateSettingsRepository; + } + + /** + * Returns the stored affiliate settings, or a safe disabled default when none are stored, so + * consumers never have to null-check and "absent" deterministically means disabled. + * + * @return AffiliateSettings + */ + public function getAffiliateSettings(): AffiliateSettings + { + return $this->affiliateSettingsRepository->getAffiliateSettings() ?? new AffiliateSettings(false, '', ''); + } + + /** + * @param AffiliateSettings $affiliateSettings + * + * @return void + */ + public function setAffiliateSettings(AffiliateSettings $affiliateSettings): void + { + $this->affiliateSettingsRepository->setAffiliateSettings($affiliateSettings); + } +} diff --git a/src/BusinessLogic/Domain/Connection/Models/Credentials.php b/src/BusinessLogic/Domain/Connection/Models/Credentials.php index da89e4dd..8e539429 100644 --- a/src/BusinessLogic/Domain/Connection/Models/Credentials.php +++ b/src/BusinessLogic/Domain/Connection/Models/Credentials.php @@ -98,7 +98,7 @@ public function getAssetsKey(): string } /** - * @return array + * @return array */ public function getPayload(): array { diff --git a/src/BusinessLogic/Domain/Connection/Services/CredentialsService.php b/src/BusinessLogic/Domain/Connection/Services/CredentialsService.php index ded13ec6..6b9f98b6 100644 --- a/src/BusinessLogic/Domain/Connection/Services/CredentialsService.php +++ b/src/BusinessLogic/Domain/Connection/Services/CredentialsService.php @@ -2,6 +2,8 @@ namespace SeQura\Core\BusinessLogic\Domain\Connection\Services; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Models\AffiliateSettings; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Services\AffiliateSettingsService; use SeQura\Core\BusinessLogic\Domain\Connection\Exceptions\BadMerchantIdException; use SeQura\Core\BusinessLogic\Domain\Connection\Exceptions\CredentialsNotFoundException; use SeQura\Core\BusinessLogic\Domain\Connection\Exceptions\WrongCredentialsException; @@ -43,22 +45,30 @@ class CredentialsService */ protected $paymentMethodRepository; + /** + * @var AffiliateSettingsService + */ + protected $affiliateSettingsService; + /** * @param ConnectionProxyInterface $connectionProxy * @param CredentialsRepositoryInterface $credentialsRepository * @param CountryConfigurationRepositoryInterface $countryConfigurationRepository * @param PaymentMethodRepositoryInterface $paymentMethodRepository + * @param AffiliateSettingsService $affiliateSettingsService */ public function __construct( ConnectionProxyInterface $connectionProxy, CredentialsRepositoryInterface $credentialsRepository, CountryConfigurationRepositoryInterface $countryConfigurationRepository, - PaymentMethodRepositoryInterface $paymentMethodRepository + PaymentMethodRepositoryInterface $paymentMethodRepository, + AffiliateSettingsService $affiliateSettingsService ) { $this->connectionProxy = $connectionProxy; $this->credentialsRepository = $credentialsRepository; $this->countryConfigurationRepository = $countryConfigurationRepository; $this->paymentMethodRepository = $paymentMethodRepository; + $this->affiliateSettingsService = $affiliateSettingsService; } /** @@ -85,10 +95,40 @@ public function validateAndUpdateCredentials(ConnectionData $connectionData): ar $this->credentialsRepository->deleteCredentialsByDeploymentId($connectionData->getDeployment()); $this->credentialsRepository->setCredentials($credentials); + $this->updateAffiliateSettingsFromCredentials($credentials); return $credentials; } + /** + * Persists the affiliate settings carried in the connect-time configuration_data, when present. + * + * The block is merchant-level (identical across the per-country credentials), so the first one + * that carries it wins. When no credential carries an affiliate block (e.g. the merchant API has + * not started emitting it yet) the stored settings are left untouched. + * + * @param Credentials[] $credentials + * + * @return void + */ + private function updateAffiliateSettingsFromCredentials(array $credentials): void + { + foreach ($credentials as $credential) { + $affiliate = $credential->getPayload()['affiliate'] ?? null; + if (!\is_array($affiliate)) { + continue; + } + + $this->affiliateSettingsService->setAffiliateSettings(new AffiliateSettings( + (bool)($affiliate['enabled'] ?? false), + (string)($affiliate['offer_id'] ?? ''), + (string)($affiliate['security_token'] ?? '') + )); + + return; + } + } + /** * Updates country configuration with new merchant ids and remove payment methods with old merchant ids * diff --git a/tests/BusinessLogic/Common/BaseTestCase.php b/tests/BusinessLogic/Common/BaseTestCase.php index b2244fa3..d67a0472 100644 --- a/tests/BusinessLogic/Common/BaseTestCase.php +++ b/tests/BusinessLogic/Common/BaseTestCase.php @@ -21,6 +21,8 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Controller\ConfigurationWebhookController; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\AdvancedSettings\GetAdvancedSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\AdvancedSettings\SaveAdvancedSettingsHandler; +use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\Affiliate\GetAffiliateSettingsHandler; +use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\Affiliate\SaveAffiliateSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\BannerSettings\GetBannerSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\BannerSettings\SaveBannerSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\Enums\Topics; @@ -39,6 +41,8 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\WidgetSettings\GetWidgetSettingsHandler; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Handlers\WidgetSettings\SaveWidgetSettingsHandler; use SeQura\Core\BusinessLogic\DataAccess\AdvancedSettings\Entities\AdvancedSettings; +use SeQura\Core\BusinessLogic\DataAccess\Affiliate\Entities\AffiliateSettings; +use SeQura\Core\BusinessLogic\DataAccess\Affiliate\Repositories\AffiliateSettingsRepository; use SeQura\Core\BusinessLogic\DataAccess\BannerSettings\Entities\BannerSettings; use SeQura\Core\BusinessLogic\DataAccess\BannerSettings\Repositories\BannerSettingsRepository; use SeQura\Core\BusinessLogic\DataAccess\ConnectionData\Entities\ConnectionData; @@ -63,6 +67,8 @@ use SeQura\Core\BusinessLogic\DataAccess\TransactionLog\Entities\TransactionLog; use SeQura\Core\BusinessLogic\DataAccess\TransactionLog\Repositories\TransactionLogRepository; use SeQura\Core\BusinessLogic\Domain\AdvancedSettings\Services\AdvancedSettingsService; +use SeQura\Core\BusinessLogic\Domain\Affiliate\RepositoryContracts\AffiliateSettingsRepositoryInterface; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Services\AffiliateSettingsService; use SeQura\Core\BusinessLogic\Domain\BannerSettings\RepositoryContracts\BannerSettingsRepositoryInterface; use SeQura\Core\BusinessLogic\Domain\BannerSettings\Services\BannerSettingsService; use SeQura\Core\BusinessLogic\Domain\Connection\ProxyContracts\ConnectionProxyInterface; @@ -300,12 +306,24 @@ protected function setUp(): void TestServiceRegister::getService(StoreIntegrationService::class) ); }, + AffiliateSettingsRepositoryInterface::class => function () { + return new AffiliateSettingsRepository( + TestRepositoryRegistry::getRepository(AffiliateSettings::getClassName()), + StoreContext::getInstance() + ); + }, + AffiliateSettingsService::class => static function () { + return new AffiliateSettingsService( + TestServiceRegister::getService(AffiliateSettingsRepositoryInterface::class) + ); + }, CredentialsService::class => static function () { return new CredentialsService( TestServiceRegister::getService(ConnectionProxyInterface::class), TestServiceRegister::getService(CredentialsRepositoryInterface::class), TestServiceRegister::getService(CountryConfigurationRepositoryInterface::class), - TestServiceRegister::getService(PaymentMethodRepositoryInterface::class) + TestServiceRegister::getService(PaymentMethodRepositoryInterface::class), + TestServiceRegister::getService(AffiliateSettingsService::class) ); }, PaymentMethodsService::class => static function () { @@ -856,6 +874,24 @@ static function () { } ); + TestServiceRegister::registerService( + GetAffiliateSettingsHandler::class, + static function () { + return new GetAffiliateSettingsHandler( + TestServiceRegister::getService(AffiliateSettingsService::class) + ); + } + ); + + TestServiceRegister::registerService( + SaveAffiliateSettingsHandler::class, + static function () { + return new SaveAffiliateSettingsHandler( + TestServiceRegister::getService(AffiliateSettingsService::class) + ); + } + ); + TestServiceRegister::registerService( GetBannerSettingsHandler::class, static function () { @@ -977,6 +1013,16 @@ static function () { SaveAdvancedSettingsHandler::class ); + TopicHandlerRegistry::register( + Topics::GET_AFFILIATE_SETTINGS, + GetAffiliateSettingsHandler::class + ); + + TopicHandlerRegistry::register( + Topics::SAVE_AFFILIATE_SETTINGS, + SaveAffiliateSettingsHandler::class + ); + TopicHandlerRegistry::register( Topics::GET_BANNER_SETTINGS, GetBannerSettingsHandler::class @@ -1045,6 +1091,7 @@ static function () { TestRepositoryRegistry::registerRepository(Credentials::getClassName(), MemoryRepository::getClassName()); TestRepositoryRegistry::registerRepository(Deployment::getClassName(), MemoryRepository::getClassName()); TestRepositoryRegistry::registerRepository(AdvancedSettings::getClassName(), MemoryRepository::getClassName()); + TestRepositoryRegistry::registerRepository(AffiliateSettings::getClassName(), MemoryRepository::getClassName()); } /** diff --git a/tests/BusinessLogic/Common/MockComponents/MockAffiliateSettingsRepository.php b/tests/BusinessLogic/Common/MockComponents/MockAffiliateSettingsRepository.php new file mode 100644 index 00000000..7a322869 --- /dev/null +++ b/tests/BusinessLogic/Common/MockComponents/MockAffiliateSettingsRepository.php @@ -0,0 +1,35 @@ +settings; + } + + /** + * @inheritDoc + */ + public function setAffiliateSettings(AffiliateSettings $settings): void + { + $this->settings = $settings; + } +} diff --git a/tests/BusinessLogic/Common/MockComponents/MockAffiliateSettingsService.php b/tests/BusinessLogic/Common/MockComponents/MockAffiliateSettingsService.php new file mode 100644 index 00000000..dc144beb --- /dev/null +++ b/tests/BusinessLogic/Common/MockComponents/MockAffiliateSettingsService.php @@ -0,0 +1,37 @@ +affiliateSettings ?? new AffiliateSettings(false, '', ''); + } + + /** + * @param ?AffiliateSettings $affiliateSettings + * + * @return void + */ + public function setAffiliateSettings(?AffiliateSettings $affiliateSettings): void + { + $this->affiliateSettings = $affiliateSettings; + } +} diff --git a/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php b/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php index 11e59241..4cb006a1 100644 --- a/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php +++ b/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php @@ -6,6 +6,8 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Responses\BannerSettings\BannerSettingsResponse; use SeQura\Core\BusinessLogic\Domain\AdvancedSettings\Models\AdvancedSettings; use SeQura\Core\BusinessLogic\Domain\AdvancedSettings\Services\AdvancedSettingsService; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Models\AffiliateSettings; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Services\AffiliateSettingsService; use SeQura\Core\BusinessLogic\Domain\BannerSettings\Exceptions\InvalidBannerUrlException; use SeQura\Core\BusinessLogic\Domain\BannerSettings\Models\Banner; use SeQura\Core\BusinessLogic\Domain\BannerSettings\Models\BannerSettings; @@ -61,6 +63,8 @@ use SeQura\Core\Tests\BusinessLogic\Common\BaseTestCase; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAdvancedSettingsRepository; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAdvancedSettingsService; +use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAffiliateSettingsRepository; +use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAffiliateSettingsService; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockBannerService; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockBannerSettingsRepository; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockBannerSettingsService; @@ -186,6 +190,11 @@ class ConfigurationWebhookAPITest extends BaseTestCase */ private $advancedSettingsService; + /** + * @var AffiliateSettingsService $affiliateSettingsService + */ + private $affiliateSettingsService; + /** * @var MockBannerService $bannerService */ @@ -365,6 +374,14 @@ function () { return $this->advancedSettingsService; }); + $this->affiliateSettingsService = new MockAffiliateSettingsService( + new MockAffiliateSettingsRepository() + ); + + TestServiceRegister::registerService(AffiliateSettingsService::class, function () { + return $this->affiliateSettingsService; + }); + $this->bannerService = new MockBannerService(); TestServiceRegister::registerService(BannerServiceInterface::class, function () { @@ -381,7 +398,8 @@ function () { new MockConnectionProxy(), new MockCredentialsRepository(), new MockCountryConfigurationRepository(), - new MockPaymentMethodRepository() + new MockPaymentMethodRepository(), + $this->affiliateSettingsService ); TestServiceRegister::registerService(CredentialsService::class, function () { @@ -1829,6 +1847,115 @@ public function testGetAdvancedSettingsResponseNoAdvancedSettings(): void self::assertEmpty($response->toArray()); } + /** + * @return void + * + * @throws InvalidEnvironmentException + * @throws EmptyCategoryParameterException + */ + public function testSaveAffiliateSettingsResponse(): void + { + //Arrange + $this->affiliateSettingsService->setAffiliateSettings(null); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-affiliate-settings", + "isEnabled" => true, + "offerId" => "1234", + "securityToken" => "abc123token" + ] + ); + + //Assert + self::assertTrue($response->isSuccessful()); + self::assertEmpty($response->toArray()); + $saved = $this->affiliateSettingsService->getAffiliateSettings(); + self::assertNotNull($saved); + self::assertTrue($saved->isEnabled()); + self::assertEquals("1234", $saved->getOfferId()); + self::assertEquals("abc123token", $saved->getSecurityToken()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + * @throws EmptyCategoryParameterException + */ + public function testSaveAffiliateSettingsEnabledWithoutCredentialsIsCoercedToDisabled(): void + { + //Arrange + $this->affiliateSettingsService->setAffiliateSettings(null); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-affiliate-settings", + "isEnabled" => true, + "offerId" => "", + "securityToken" => "" + ] + ); + + //Assert: enabled with no credentials is unusable, so it persists as disabled. + self::assertTrue($response->isSuccessful()); + $saved = $this->affiliateSettingsService->getAffiliateSettings(); + self::assertFalse($saved->isEnabled()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + * @throws EmptyCategoryParameterException + */ + public function testGetAffiliateSettingsResponse(): void + { + //Arrange + $affiliateSettings = new AffiliateSettings(true, "1234", "abc123token"); + $this->affiliateSettingsService->setAffiliateSettings($affiliateSettings); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "get-affiliate-settings" + ] + ); + + //Assert: GET returns only the enabled state, never the offer id or security token. + self::assertTrue($response->isSuccessful()); + self::assertEquals(['isEnabled' => true], $response->toArray()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + * @throws EmptyCategoryParameterException + */ + public function testGetAffiliateSettingsResponseNoAffiliateSettings(): void + { + //Arrange + $this->affiliateSettingsService->setAffiliateSettings(null); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "get-affiliate-settings" + ] + ); + + //Assert: no stored settings reads as a deterministic enabled=false. + self::assertTrue($response->isSuccessful()); + self::assertEquals(['isEnabled' => false], $response->toArray()); + } + /** * @return void * diff --git a/tests/BusinessLogic/DataAccess/Affiliate/Entities/AffiliateSettingsEntityTest.php b/tests/BusinessLogic/DataAccess/Affiliate/Entities/AffiliateSettingsEntityTest.php new file mode 100644 index 00000000..1d998331 --- /dev/null +++ b/tests/BusinessLogic/DataAccess/Affiliate/Entities/AffiliateSettingsEntityTest.php @@ -0,0 +1,22 @@ +repository = TestRepositoryRegistry::getRepository(AffiliateSettingsEntity::getClassName()); + $this->affiliateSettingsRepository = new AffiliateSettingsRepository( + TestRepositoryRegistry::getRepository(AffiliateSettingsEntity::getClassName()), + StoreContext::getInstance() + ); + + TestServiceRegister::registerService(AffiliateSettingsRepositoryInterface::class, function () { + return $this->affiliateSettingsRepository; + }); + } + + /** + * @return void + * + * @throws Exception + */ + public function testGetSettingsNoSettings(): void + { + // act + $result = StoreContext::doWithStore( + '1', + [$this->affiliateSettingsRepository, 'getAffiliateSettings'] + ); + + // assert + self::assertEmpty($result); + } + + /** + * @throws Exception + */ + public function testGetAffiliateSettings(): void + { + // arrange + $affiliateSettings = new AffiliateSettings(true, '1234', 'abc123token'); + $entity = new AffiliateSettingsEntity(); + + $entity->setAffiliateSettings($affiliateSettings); + $entity->setStoreId('1'); + $this->repository->save($entity); + + // act + $result = StoreContext::doWithStore( + '1', + [$this->affiliateSettingsRepository, 'getAffiliateSettings'] + ); + + // assert + self::assertEquals($affiliateSettings, $result); + } + + /** + * @throws Exception + */ + public function testGetSettingsDifferentStores(): void + { + // arrange + $affiliateSettings1 = new AffiliateSettings(true, '1234', 'tokenone'); + $entity = new AffiliateSettingsEntity(); + $entity->setAffiliateSettings($affiliateSettings1); + $entity->setStoreId('1'); + $this->repository->save($entity); + + $affiliateSettings2 = new AffiliateSettings(false, '5678', 'tokentwo'); + $entity = new AffiliateSettingsEntity(); + $entity->setAffiliateSettings($affiliateSettings2); + $entity->setStoreId('2'); + $this->repository->save($entity); + + // act + $result1 = StoreContext::doWithStore( + '1', + [$this->affiliateSettingsRepository, 'getAffiliateSettings'] + ); + $result2 = StoreContext::doWithStore( + '2', + [$this->affiliateSettingsRepository, 'getAffiliateSettings'] + ); + + // assert + self::assertEquals($affiliateSettings1, $result1); + self::assertEquals($affiliateSettings2, $result2); + } + + /** + * @throws Exception + */ + public function testSetAffiliateSettings(): void + { + // arrange + $affiliateSettings = new AffiliateSettings(true, '1234', 'abc123token'); + + // act + StoreContext::doWithStore( + '1', + [$this->affiliateSettingsRepository, 'setAffiliateSettings'], + [$affiliateSettings] + ); + + // assert + $savedEntity = $this->repository->select(); + self::assertEquals($affiliateSettings, $savedEntity[0]->getAffiliateSettings()); + } + + /** + * @throws Exception + */ + public function testUpdateAffiliateSettings(): void + { + // arrange + $affiliateSettings1 = new AffiliateSettings(true, '1234', 'tokenone'); + $affiliateSettings2 = new AffiliateSettings(false, '5678', 'tokentwo'); + + // act + StoreContext::doWithStore( + '1', + [$this->affiliateSettingsRepository, 'setAffiliateSettings'], + [$affiliateSettings1] + ); + StoreContext::doWithStore( + '1', + [$this->affiliateSettingsRepository, 'setAffiliateSettings'], + [$affiliateSettings2] + ); + + // assert + $savedEntity = $this->repository->select(); + self::assertCount(1, $savedEntity); + self::assertEquals($affiliateSettings2, $savedEntity[0]->getAffiliateSettings()); + } +} diff --git a/tests/BusinessLogic/Domain/Affiliate/Services/AffiliateSettingsServiceTest.php b/tests/BusinessLogic/Domain/Affiliate/Services/AffiliateSettingsServiceTest.php new file mode 100644 index 00000000..c2188f75 --- /dev/null +++ b/tests/BusinessLogic/Domain/Affiliate/Services/AffiliateSettingsServiceTest.php @@ -0,0 +1,116 @@ +repository = new MockAffiliateSettingsRepository(); + $this->service = new AffiliateSettingsService($this->repository); + } + + /** + * @return void + */ + public function testGetAffiliateSettingsNoSettings(): void + { + //Arrange + + //Act + $result = $this->service->getAffiliateSettings(); + + //Assert: absent settings yield a safe disabled default, never null. + self::assertNotNull($result); + self::assertFalse($result->isEnabled()); + self::assertSame('', $result->getOfferId()); + self::assertSame('', $result->getSecurityToken()); + } + + /** + * @return void + */ + public function testGetAffiliateSettings(): void + { + //Arrange + $affiliateSettings = new AffiliateSettings(true, '1234', 'abc123token'); + $this->repository->setAffiliateSettings($affiliateSettings); + + //Act + $result = $this->service->getAffiliateSettings(); + + //Assert + self::assertNotNull($result); + self::assertTrue($result->isEnabled()); + self::assertEquals('1234', $result->getOfferId()); + self::assertEquals('abc123token', $result->getSecurityToken()); + } + + /** + * @return void + */ + public function testSetAffiliateSettingsNoSettingsInDB(): void + { + //Arrange + $affiliateSettings = new AffiliateSettings(true, '1234', 'abc123token'); + + //Act + $this->service->setAffiliateSettings($affiliateSettings); + + //Assert + $result = $this->repository->getAffiliateSettings(); + self::assertNotNull($result); + self::assertTrue($result->isEnabled()); + self::assertEquals('1234', $result->getOfferId()); + self::assertEquals('abc123token', $result->getSecurityToken()); + } + + /** + * @return void + */ + public function testSetAffiliateSettingsSettingsChanged(): void + { + //Arrange + $affiliateSettings = new AffiliateSettings(true, '1234', 'abc123token'); + $this->repository->setAffiliateSettings(new AffiliateSettings(false, '9999', 'oldtoken')); + + //Act + $this->service->setAffiliateSettings($affiliateSettings); + + //Assert + $result = $this->repository->getAffiliateSettings(); + + self::assertNotNull($result); + self::assertTrue($result->isEnabled()); + self::assertEquals('1234', $result->getOfferId()); + self::assertEquals('abc123token', $result->getSecurityToken()); + } +} diff --git a/tests/BusinessLogic/Domain/Connection/Services/ConnectionServiceTest.php b/tests/BusinessLogic/Domain/Connection/Services/ConnectionServiceTest.php index 31d10d08..640779a6 100644 --- a/tests/BusinessLogic/Domain/Connection/Services/ConnectionServiceTest.php +++ b/tests/BusinessLogic/Domain/Connection/Services/ConnectionServiceTest.php @@ -10,6 +10,7 @@ use SeQura\Core\BusinessLogic\Domain\Connection\Exceptions\InvalidEnvironmentException; use SeQura\Core\BusinessLogic\Domain\Connection\Exceptions\WrongCredentialsException; use SeQura\Core\BusinessLogic\Domain\Connection\Models\AuthorizationCredentials; +use SeQura\Core\BusinessLogic\Domain\Affiliate\Services\AffiliateSettingsService; use SeQura\Core\BusinessLogic\Domain\Connection\Models\Credentials; use SeQura\Core\BusinessLogic\Domain\Connection\ProxyContracts\ConnectionProxyInterface; use SeQura\Core\BusinessLogic\Domain\Connection\RepositoryContracts\ConnectionDataRepositoryInterface; @@ -295,6 +296,45 @@ public function testConnectCredentialsSaved(): void self::assertEquals($credentials, $savedCredentials); } + /** + * @return void + * + * @throws BadMerchantIdException + * @throws CapabilitiesEmptyException + * @throws HttpRequestException + * @throws InvalidEnvironmentException + * @throws PaymentMethodNotFoundException + * @throws WrongCredentialsException + */ + public function testConnectAffiliateSettingsSaved(): void + { + //Arrange + $connectionData = new DomainConnectionData( + BaseProxy::TEST_MODE, + 'test_merchant', + 'sequra', + new AuthorizationCredentials('test_username', 'test_password') + ); + $this->mockConnectionProxy->setMockCredentials([ + new Credentials('logeecom1', 'PT', 'EUR', 'assetsKey1', [ + 'affiliate' => [ + 'enabled' => true, + 'offer_id' => 'mock-affiliate-offer', + 'security_token' => 'mock-affiliate-security-token', + ], + ], 'sequra'), + ]); + + //Act + $this->connectionService->connect([$connectionData]); + + //Assert: the connect-time affiliate block is persisted via the affiliate settings service. + $affiliateSettings = TestServiceRegister::getService(AffiliateSettingsService::class)->getAffiliateSettings(); + self::assertTrue($affiliateSettings->isEnabled()); + self::assertEquals('mock-affiliate-offer', $affiliateSettings->getOfferId()); + self::assertEquals('mock-affiliate-security-token', $affiliateSettings->getSecurityToken()); + } + /** * @return void * diff --git a/tests/BusinessLogic/Domain/Migration/Tasks/StoreIntegrationMigrateTaskTest.php b/tests/BusinessLogic/Domain/Migration/Tasks/StoreIntegrationMigrateTaskTest.php index 67597f9a..55680dd2 100644 --- a/tests/BusinessLogic/Domain/Migration/Tasks/StoreIntegrationMigrateTaskTest.php +++ b/tests/BusinessLogic/Domain/Migration/Tasks/StoreIntegrationMigrateTaskTest.php @@ -13,6 +13,8 @@ use SeQura\Core\Infrastructure\ORM\Exceptions\RepositoryClassException; use SeQura\Core\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException; use SeQura\Core\Tests\BusinessLogic\Common\BaseTestCase; +use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAffiliateSettingsRepository; +use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAffiliateSettingsService; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockConnectionDataRepository; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockConnectionProxy; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockConnectionService; @@ -88,7 +90,8 @@ protected function setUp(): void new MockConnectionProxy(), new MockCredentialsRepository(), new MockCountryConfigurationRepository(), - new MockPaymentMethodRepository() + new MockPaymentMethodRepository(), + new MockAffiliateSettingsService(new MockAffiliateSettingsRepository()) ), $this->storeIntegrationService ); diff --git a/tests/BusinessLogic/Domain/Order/Builders/MerchantOrderRequestBuilderTest.php b/tests/BusinessLogic/Domain/Order/Builders/MerchantOrderRequestBuilderTest.php index 260a91eb..731295f4 100644 --- a/tests/BusinessLogic/Domain/Order/Builders/MerchantOrderRequestBuilderTest.php +++ b/tests/BusinessLogic/Domain/Order/Builders/MerchantOrderRequestBuilderTest.php @@ -20,6 +20,8 @@ use SeQura\Core\BusinessLogic\Domain\StoreIntegration\Services\StoreIntegrationService; use SeQura\Core\Infrastructure\ORM\Exceptions\RepositoryClassException; use SeQura\Core\Tests\BusinessLogic\Common\BaseTestCase; +use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAffiliateSettingsRepository; +use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockAffiliateSettingsService; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockConnectionDataRepository; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockConnectionProxy; use SeQura\Core\Tests\BusinessLogic\Common\MockComponents\MockConnectionService; @@ -70,7 +72,8 @@ public function setUp(): void new MockConnectionProxy(), new MockCredentialsRepository(), new MockCountryConfigurationRepository(), - new MockPaymentMethodRepository() + new MockPaymentMethodRepository(), + new MockAffiliateSettingsService(new MockAffiliateSettingsRepository()) ); $this->connectionService = new MockConnectionService( new MockConnectionDataRepository(),