| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- <?php
- declare(strict_types=1);
- namespace App\Service\Shop;
- use App\ApiResources\Organization\OrganizationCreationRequest;
- use App\ApiResources\Shop\NewStructureArtistPremiumTrialRequest;
- use App\Entity\Organization\Organization;
- use App\Entity\Shop\ShopRequest;
- use App\Enum\Access\AccessIdsEnum;
- use App\Enum\Organization\SettingsProductEnum;
- use App\Enum\Shop\ShopRequestStatus;
- use App\Enum\Shop\ShopRequestType;
- use App\Message\Message\Shop\NewStructureArtistPremiumTrial;
- use App\Service\Mailer\Mailer;
- use App\Service\Mailer\Model\Shop\NewStructureArtistPremium\ConfirmationToRepresentativeModel;
- use App\Service\Mailer\Model\Shop\NewStructureArtistPremium\NotificationToSalesAdminModel;
- use App\Service\Mailer\Model\Shop\TokenValidationModel;
- use App\Service\Organization\OrganizationFactory;
- use App\Service\Utils\DatesUtils;
- use App\Service\Utils\UrlBuilder;
- use Doctrine\DBAL\Exception;
- use Doctrine\ORM\EntityManagerInterface;
- use Doctrine\ORM\Exception\ORMException;
- use Doctrine\ORM\OptimisticLockException;
- use libphonenumber\PhoneNumberUtil;
- use Psr\Log\LoggerInterface;
- use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
- use Symfony\Component\Messenger\Exception\ExceptionInterface;
- use Symfony\Component\Messenger\MessageBusInterface;
- use Symfony\Component\Serializer\SerializerInterface;
- use Symfony\Component\Uid\Uuid;
- /**
- * Service for managing shop requests.
- *
- * This service handles various shop-related operations.
- * It provides functionality for:
- * - Registering new shop requests
- * - Validating and processing shop requests
- * - Creating organizations based on trial requests
- * - Starting premium trials for organizations
- * - Generating subdomains from structure names
- */
- class ShopService
- {
- protected PhoneNumberUtil $phoneNumberUtil;
- public function __construct(
- private EntityManagerInterface $entityManager,
- private Mailer $mailer,
- private string $publicBaseUrl,
- private string $publicAdminBaseUrl,
- private OrganizationFactory $organizationFactory,
- private SerializerInterface $serializer,
- private LoggerInterface $logger,
- private MessageBusInterface $messageBus,
- private Trial $trial,
- private string $faqUrl,
- private readonly string $softwareWebsiteUrl,
- ) {
- $this->phoneNumberUtil = PhoneNumberUtil::getInstance();
- }
- /**
- * A new shop request has been submitted.
- * Register the request, and send the validation link by email.
- *
- * @param array<string, mixed> $data
- *
- * @throws TransportExceptionInterface
- */
- public function registerNewShopRequest(ShopRequestType $type, array $data): ShopRequest
- {
- $this->controlShopRequestData($type, $data);
- $request = $this->createRequest($type, $data);
- $this->sendRequestValidationLink($request);
- return $request;
- }
- /**
- * Validate the shop request based on its type.
- * For NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL, check if the organization already exists.
- * For other types, throw an error.
- *
- * @param array<string, mixed> $data
- */
- protected function controlShopRequestData(ShopRequestType $type, array $data): void
- {
- // @phpstan-ignore-next-line identical.alwaysTrue
- if ($type === ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL) {
- $this->validateNewStructureArtistPremiumTrialRequest($data);
- } else {
- throw new \RuntimeException('request type not supported');
- }
- }
- /**
- * Validate the request and dispatch the appropriate job based on the request type.
- *
- * @throws \RuntimeException|ExceptionInterface
- */
- public function processShopRequest(ShopRequest $shopRequest): void
- {
- // Dispatch appropriate job based on request type
- switch ($shopRequest->getType()->value) {
- case ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL->value:
- $this->messageBus->dispatch(
- new NewStructureArtistPremiumTrial($shopRequest->getToken())
- );
- break;
- default:
- throw new \RuntimeException('request type not supported');
- }
- $shopRequest->setStatus(ShopRequestStatus::VALIDATED);
- $this->entityManager->persist($shopRequest);
- $this->entityManager->flush();
- }
- /**
- * Create and persist a new ShopRequest entity.
- *
- * @param array<string, mixed> $data
- */
- protected function createRequest(ShopRequestType $type, array $data): ShopRequest
- {
- $shopRequest = new ShopRequest();
- $shopRequest->setToken(Uuid::v4()->toRfc4122());
- $shopRequest->setType($type);
- $shopRequest->setData($data);
- $this->entityManager->persist($shopRequest);
- $this->entityManager->flush();
- return $shopRequest;
- }
- /**
- * Send validation email with link.
- *
- * @throws TransportExceptionInterface
- */
- protected function sendRequestValidationLink(ShopRequest $shopRequest): void
- {
- $validationUrl = UrlBuilder::concat(
- $this->softwareWebsiteUrl,
- ['/shop/try/validation'],
- ['token' => $shopRequest->getToken()]
- );
- $data = $shopRequest->getData();
- $model = new TokenValidationModel();
- $model
- ->setToken($shopRequest->getToken())
- ->setRepresentativeEmail($data['representativeEmail'] ?? '')
- ->setRepresentativeFirstName($data['representativeFirstName'] ?? '')
- ->setRepresentativeLastName($data['representativeLastName'] ?? '')
- ->setStructureName($data['structureName'] ?? '')
- ->setValidationUrl($validationUrl)
- ->setSenderId(AccessIdsEnum::ADMIN_2IOPENSERVICE->value);
- $this->mailer->main($model);
- $shopRequest->setStatus(ShopRequestStatus::ACTIVATION_LINK_SENT);
- $this->entityManager->persist($shopRequest);
- $this->entityManager->flush();
- }
- /**
- * Handles the processing of a new structure artist premium trial request.
- *
- * @param string $token The token identifying the shop request
- *
- * @throws Exception
- * @throws \JsonException
- * @throws ORMException
- * @throws OptimisticLockException
- */
- public function handleNewStructureArtistPremiumTrialRequest(string $token): void
- {
- // Retrieve the ShopRequest entity using its token
- $shopRequest = $this->entityManager->find(ShopRequest::class, $token);
- if (!$shopRequest) {
- $this->logger->error('Cannot find ShopRequest with token: '.$token);
- return;
- }
- // Convert the stored JSON data to a NewStructureArtistPremiumTrialRequest object
- $data = $shopRequest->getData();
- $trialRequest = $this->serializer->deserialize(
- json_encode($data),
- NewStructureArtistPremiumTrialRequest::class,
- 'json'
- );
- $organization = $this->createOrganization($trialRequest);
- // Set the admin account password
- $this->organizationFactory->setAdminAccountPassword($organization, $trialRequest->getPassword());
- // Start the artist premium trial
- $this->trial->startArtistPremiumTrialForNewStructure($organization, $trialRequest);
- // Send email to sales administration
- $this->sendMailToSalesAdministration($trialRequest);
- // Send email to representative
- $this->sendConfirmationMailToRepresentative($trialRequest);
- $this->logger->info('Successfully processed NewStructureArtistPremiumTrial for token: '.$token);
- }
- /**
- * Creates a new organization based on a trial request.
- *
- * @param NewStructureArtistPremiumTrialRequest $trialRequest The trial request containing organization data
- *
- * @return Organization The created organization
- */
- protected function createOrganization(NewStructureArtistPremiumTrialRequest $trialRequest): Organization
- {
- // Generate an OrganizationCreationRequest object
- $organizationCreationRequest = $this->createOrganizationCreationRequestFromTrialRequest($trialRequest);
- // Create the organization
- return $this->organizationFactory->create($organizationCreationRequest);
- }
- /**
- * Vérifie la validité d'une requête d'essai artist premium pour une nouvelle structure.
- */
- protected function validateNewStructureArtistPremiumTrialRequest(
- array $data,
- ): void {
- $trialRequestObj = $this->serializer->deserialize(
- json_encode($data),
- NewStructureArtistPremiumTrialRequest::class,
- 'json'
- );
- // Validate phone number
- if (!$this->phoneNumberUtil->isPossibleNumber($trialRequestObj->getRepresentativePhone())) {
- throw new \RuntimeException('Invalid phone number');
- }
- // Check if organization already exists
- $organizationCreationRequest = $this->createOrganizationCreationRequestFromTrialRequest($trialRequestObj);
- $this->organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
- }
- /**
- * Creates an OrganizationCreationRequest from a NewStructureArtistPremiumTrialRequest.
- *
- * @param NewStructureArtistPremiumTrialRequest $trialRequest The trial request containing organization data
- *
- * @return OrganizationCreationRequest The created organization creation request
- *
- * @throws \Exception
- */
- protected function createOrganizationCreationRequestFromTrialRequest(
- NewStructureArtistPremiumTrialRequest $trialRequest,
- ): OrganizationCreationRequest {
- $organizationCreationRequest = new OrganizationCreationRequest();
- $organizationCreationRequest->setName($trialRequest->getStructureName());
- $organizationCreationRequest->setStreetAddress1($trialRequest->getAddress());
- $organizationCreationRequest->setStreetAddress2($trialRequest->getAddressComplement());
- $organizationCreationRequest->setPostalCode($trialRequest->getPostalCode());
- $organizationCreationRequest->setCity($trialRequest->getCity());
- $organizationCreationRequest->setEmail($trialRequest->getStructureEmail());
- $organizationCreationRequest->setPrincipalType($trialRequest->getStructureType());
- $organizationCreationRequest->setLegalStatus($trialRequest->getLegalStatus());
- $organizationCreationRequest->setSiretNumber($trialRequest->getSiren());
- $organizationCreationRequest->setPhoneNumber($trialRequest->getRepresentativePhone());
- $organizationCreationRequest->setSubdomain($trialRequest->getStructureIdentifier());
- $organizationCreationRequest->setSendConfirmationEmailAt($trialRequest->getRepresentativeEmail());
- // Set default values
- $organizationCreationRequest->setProduct(SettingsProductEnum::FREEMIUM);
- $organizationCreationRequest->setCreateWebsite(false);
- $organizationCreationRequest->setClient(false);
- $organizationCreationRequest->setCreationDate(DatesUtils::new());
- return $organizationCreationRequest;
- }
- /**
- * Envoie un email à l'administration des ventes pour informer d'une nouvelle demande d'essai artist premium.
- *
- * @param NewStructureArtistPremiumTrialRequest $trialRequest La demande d'essai
- *
- * @throws TransportExceptionInterface
- */
- protected function sendMailToSalesAdministration(NewStructureArtistPremiumTrialRequest $trialRequest): void
- {
- // Create the email model
- $model = new NotificationToSalesAdminModel();
- $model
- ->setTrialRequest($trialRequest)
- ->setSenderId(AccessIdsEnum::ADMIN_2IOPENSERVICE->value);
- // Send the email to the sales administration
- $this->mailer->main($model);
- }
- /**
- * Envoie un email au représentant pour l'informer que sa demande d'essai artist premium a été validée
- * et lui fournir un lien pour créer son compte et accéder au logiciel.
- *
- * @param NewStructureArtistPremiumTrialRequest $trialRequest La demande d'essai
- *
- * @throws TransportExceptionInterface
- */
- protected function sendConfirmationMailToRepresentative(
- NewStructureArtistPremiumTrialRequest $trialRequest,
- ): void
- {
- // Create the admin username
- $adminUsername = 'admin' . $trialRequest->getStructureIdentifier();
- // Create the admin login URL
- $adminLoginUrl = UrlBuilder::concat($this->publicAdminBaseUrl, ['#/login/']);
- // Create the email model
- $model = new ConfirmationToRepresentativeModel();
- $model
- ->setTrialRequest($trialRequest)
- ->setAccountCreationUrl(UrlBuilder::concat($this->publicBaseUrl, ['/account/create']))
- ->setFaqUrl($this->faqUrl)
- ->setAdminUsername($adminUsername)
- ->setAdminLoginUrl($adminLoginUrl)
- ->setSenderId(AccessIdsEnum::ADMIN_2IOPENSERVICE->value);
- // Send the email to the representative
- $this->mailer->main($model);
- }
- }
|