custom/plugins/ShopWithMeUI/src/Controller/AppStorefrontController.php line 242

Open in your IDE?
  1. <?php
  2. namespace ShopWithMe\UI\Controller;
  3. use Divante\Nuvei\Service\Handler\NuveiSubscriptionPaymentHandler;
  4. use Econsor\Amity\Services\AmityPosts\AmityPostService;
  5. use Econsor\Amity\Services\Stream\StreamSorter;
  6. use Econsor\Shopware\SearchBar\Storefront\Controller\EconsorTagSaverController;
  7. use EconsorAppStorefront\Core\Validator\Duplications;
  8. use EconsorAppStorefront\Services\CustomerDataService;
  9. use EconsorAppStorefront\Services\DiscountCodeGenerator;
  10. use EconsorAppStorefront\Storefront\registration\RegistrationAppPageLoader;
  11. use Psr\Log\LoggerInterface;
  12. use Shopware\Core\Checkout\Cart\CartCalculator;
  13. use Shopware\Core\Checkout\Cart\CartPersisterInterface;
  14. use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
  15. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  16. use Shopware\Core\Checkout\Cart\Order\OrderPersister;
  17. use Shopware\Core\Checkout\Cart\Order\Transformer\CustomerTransformer;
  18. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  19. use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity;
  20. use Shopware\Core\Checkout\Customer\CustomerDefinition;
  21. use Shopware\Core\Checkout\Customer\CustomerEntity;
  22. use Shopware\Core\Checkout\Customer\Exception\AddressNotFoundException;
  23. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractListAddressRoute;
  24. use Shopware\Core\Checkout\Customer\SalesChannel\AccountService;
  25. use Shopware\Core\Checkout\Customer\Validation\Constraint\CustomerEmailUnique;
  26. use Shopware\Core\Checkout\Customer\Validation\CustomerValidationFactory;
  27. use Shopware\Core\Checkout\Document\FileGenerator\FileTypes;
  28. use Shopware\Core\Checkout\Document\Struct\DocumentGenerateOperation;
  29. use Shopware\Core\Checkout\Document\Service\DocumentGenerator;
  30. use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
  31. use Shopware\Core\Content\Media\File\MediaFile;
  32. use Shopware\Core\Defaults;
  33. use Shopware\Core\Framework\Context;
  34. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  35. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  36. use Shopware\Core\Framework\DataAbstractionLayer\Validation\EntityExists;
  37. use Shopware\Core\Framework\Feature;
  38. use Shopware\Core\Framework\Routing\RequestContextResolverInterface;
  39. use Shopware\Core\Framework\Struct\ArrayStruct;
  40. use Shopware\Core\Framework\Uuid\Exception\InvalidUuidException;
  41. use Shopware\Core\Framework\Uuid\Uuid;
  42. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  43. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  44. use Shopware\Core\Framework\Validation\DataValidationDefinition;
  45. use Shopware\Core\Framework\Validation\DataValidator;
  46. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  47. use Shopware\Core\System\Language\LanguageEntity;
  48. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory;
  49. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
  50. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  51. use Shopware\Core\System\SystemConfig\SystemConfigService;
  52. use Shopware\Storefront\Controller\RegisterController;
  53. use Shopware\Storefront\Controller\StorefrontController;
  54. use Shopware\Storefront\Framework\Cache\Annotation\HttpCache;
  55. use Shopware\Storefront\Framework\Routing\Router;
  56. use Shopware\Storefront\Page\Address\AddressEditorModalStruct;
  57. use Shopware\Storefront\Page\Address\Listing\AddressBookWidgetLoadedHook;
  58. use Shopware\Storefront\Page\Address\Listing\AddressListingPageLoader;
  59. use Shopware\Storefront\Page\GenericPageLoader;
  60. use Shopware\Storefront\Page\Product\ProductPageLoadedHook;
  61. use Shopware\Storefront\Page\Product\ProductPageLoader;
  62. use ShopWithMe\LP\Service\RankingService;
  63. use ShopWithMe\Reward\Entities\Enums\RewardPointType;
  64. use ShopWithMe\Reward\Entities\Enums\RewardShipLaterType;
  65. use ShopWithMe\Reward\Service\RewardPointService;
  66. use ShopWithMe\Reward\Service\RewardShipLaterService;
  67. use ShopWithMe\UI\Service\CustomerBusinessNameService;
  68. use ShopWithMe\UI\Service\HomePageService;
  69. use Divante\Nuvei\Controller\NuveiSubscriptionController;
  70. use Symfony\Component\HttpFoundation\RedirectResponse;
  71. use Symfony\Component\HttpFoundation\Request;
  72. use Symfony\Component\HttpFoundation\Response;
  73. use Symfony\Component\Routing\Annotation\Route;
  74. use Symfony\Component\Validator\Constraints\Email;
  75. use Symfony\Component\Validator\Constraints\Length;
  76. use Symfony\Component\Validator\Constraints\NotBlank;
  77. use Shopware\Storefront\Page\Navigation\NavigationPageLoaderInterface;
  78. use Shopware\Storefront\Page\Navigation\NavigationPageLoadedHook;
  79. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractChangeCustomerProfileRoute;
  80. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractChangeEmailRoute;
  81. use Shopware\Core\System\SalesChannel\SuccessResponse;
  82. use Shopware\Storefront\Page\Address\Detail\AddressDetailPageLoader;
  83. use Shopware\Storefront\Page\Address\Detail\AddressDetailPageLoadedHook;
  84. use Shopware\Storefront\Page\Account\Profile\AccountProfilePageLoader;
  85. use Shopware\Storefront\Page\Account\Profile\AccountProfilePageLoadedHook;
  86. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractUpsertAddressRoute;
  87. use Shopware\Core\Content\Newsletter\SalesChannel\AbstractNewsletterSubscribeRoute;
  88. use Shopware\Core\Content\Newsletter\SalesChannel\AbstractNewsletterUnsubscribeRoute;
  89. use Shopware\Storefront\Framework\Routing\RequestTransformer;
  90. use Shopware\Storefront\Page\Account\Overview\AccountOverviewPageLoadedHook;
  91. use Shopware\Storefront\Page\Account\Overview\AccountOverviewPageLoader;
  92. use EconsorAppStorefront\Services\AccountService as EconsorAccountService;
  93. use Shopware\Storefront\Page\Account\Order\AccountOrderPageLoader;
  94. use Shopware\Storefront\Page\Account\Order\AccountOrderPageLoadedHook;
  95. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  96. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  97. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  98. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  99. use Shopware\Core\Content\Newsletter\Aggregate\NewsletterRecipient\NewsletterRecipientEntity;
  100. use Symfony\Component\Validator\Constraints\NotEqualTo;
  101. use Symfony\Component\Validator\Validation;
  102. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
  103. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
  104. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
  105. use ShopWithMe\MobileApi\Service\AIStreamService;
  106. use ShopWithMe\Reward\Service\LuckyWheelService\LuckyWheelApiService;
  107. use Symfony\Component\HttpFoundation\JsonResponse;
  108. use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct;
  109. use Shopware\Core\Checkout\Payment\PaymentService;
  110. use Shopware\Core\Checkout\Cart\Transaction\Struct;
  111. use ShopWithMe\UI\Service\SubscriptionService;
  112. use Shopware\Core\System\StateMachine\StateMachineRegistry;
  113. use Shopware\Core\System\StateMachine\Transition;
  114. use Shopware\Core\Checkout\Order\OrderDefinition;
  115. use Doctrine\DBAL\Connection;
  116. #[Route(defaults: ['_routeScope' => ['storefront']])]
  117. class AppStorefrontController extends StorefrontController
  118. {
  119.     private const ADDRESS_TYPE_BILLING 'billing';
  120.     private const ADDRESS_TYPE_SHIPPING 'shipping';
  121.     const LIMIT 100;
  122.     /**
  123.      * @var RegistrationAppPageLoader
  124.      */
  125.     private $registrationAppPageLoader;
  126.     /**
  127.      * @var RegisterController
  128.      */
  129.     private $registerController;
  130.     /**
  131.      * @var CustomerValidationFactory
  132.      */
  133.     private $customerValidationFactory;
  134.     /**
  135.      * @var DataValidator
  136.      */
  137.     private $dataValidator;
  138.     /**
  139.      * @var SystemConfigService
  140.      */
  141.     private $systemConfigService;
  142.     /**
  143.      * @var RequestContextResolverInterface
  144.      */
  145.     private $contextResolver;
  146.     /**
  147.      * @var EconsorTagSaverController
  148.      */
  149.     private $tagSaverController;
  150.     /**
  151.      * @var EntityRepository
  152.      */
  153.     private $customerEntity;
  154.     /**
  155.      * @var Router
  156.      */
  157.     private $router;
  158.     private NavigationPageLoaderInterface $navigationPageLoader;
  159.     private AbstractChangeCustomerProfileRoute $changeCustomerProfileRoute;
  160.     private AbstractChangeEmailRoute $changeEmailRoute;
  161.     private AddressDetailPageLoader $addressDetailPageLoader;
  162.     private AccountOverviewPageLoader $overviewPageLoader;
  163.     private AccountProfilePageLoader $profilePageLoader;
  164.     private AbstractUpsertAddressRoute $updateAddressRoute;
  165.     /**
  166.      * @var AbstractNewsletterSubscribeRoute
  167.      */
  168.     private $newsletterSubscribeRoute;
  169.     /**
  170.      * @var AbstractNewsletterUnsubscribeRoute
  171.      */
  172.     private $newsletterUnsubscribeRoute;
  173.     private EconsorAccountService $econsorAccountService;
  174.     private AccountOrderPageLoader $orderPageLoader;
  175.     /**
  176.      * @var EntityRepositoryInterface
  177.      */
  178.     private $newsletterRecipientRepository;
  179.     protected EntityRepositoryInterface $languageRepository;
  180.     protected EntityRepositoryInterface $countryRepository;
  181.     protected $homepageService;
  182.     private LoggerInterface $logger;
  183.     private StreamSorter $amityService;
  184.     private EntityRepositoryInterface $luckyWheelTurnRepository;
  185.     private EntityRepositoryInterface $productBundleItemsRepository;
  186.     protected SalesChannelRepositoryInterface $salesChannelProductRepository;
  187.     protected NuveiSubscriptionController $nuveiSubscriptionController;
  188.     protected PaymentService $paymentService;
  189.     private EntityRepositoryInterface $orderRepository;
  190.     private EntityRepository $subscriptionRepository;
  191.     private EntityRepository $subscriptionOrderRepository;
  192.     private StateMachineRegistry $stateMachineRegistry;
  193.     private Connection $connection;
  194.     private EntityRepository $stateMachineStateRepository;
  195.     private DocumentGenerator $documentGenerator;
  196.     private EntityRepository $documentRepository;
  197.     public function __construct(
  198.         RegistrationAppPageLoader                    $registrationAppPageLoader,
  199.         RegisterController                           $registerController,
  200.         CustomerValidationFactory                    $customerValidationFactory,
  201.         DataValidator                                $dataValidator,
  202.         SystemConfigService                          $systemConfigService,
  203.         RequestContextResolverInterface              $contextResolver,
  204.         EconsorTagSaverController                    $tagSaverController,
  205.         EntityRepository                             $customerEntity,
  206.         Router                                       $router,
  207.         NavigationPageLoaderInterface                $navigationPageLoader,
  208.         AbstractChangeCustomerProfileRoute           $changeCustomerProfileRoute,
  209.         AbstractChangeEmailRoute                     $changeEmailRoute,
  210.         AddressDetailPageLoader                      $addressDetailPageLoader,
  211.         AccountProfilePageLoader                     $profilePageLoader,
  212.         AbstractUpsertAddressRoute                   $updateAddressRoute,
  213.         AbstractNewsletterSubscribeRoute             $newsletterSubscribeRoute,
  214.         AbstractNewsletterUnsubscribeRoute           $newsletterUnsubscribeRoute,
  215.         AccountOverviewPageLoader                    $overviewPageLoader,
  216.         EconsorAccountService                        $econsorAccountService,
  217.         AccountOrderPageLoader                       $orderPageLoader,
  218.         EntityRepositoryInterface                    $newsletterRecipientRepository,
  219.         EntityRepositoryInterface                    $languageRepository,
  220.         EntityRepositoryInterface                    $countryRepository,
  221.         HomePageService                              $homePageService,
  222.         LoggerInterface                              $logger,
  223.         protected AmityPostService                   $postService,
  224.         protected ProductPageLoader                  $productPageLoader,
  225.         protected DiscountCodeGenerator              $discountCodeGenerator,
  226.         protected CustomerDataService                $customerDataService,
  227.         protected EntityRepository                   $customerNotificationRepository,
  228.         protected AccountService                     $accountService,
  229.         protected AddressListingPageLoader           $addressListingPageLoader,
  230.         protected AbstractChangeCustomerProfileRoute $updateCustomerProfileRoute,
  231.         protected AbstractListAddressRoute           $listAddressRoute,
  232.         EntityRepositoryInterface                    $luckyWheelTurnRepository,
  233.         protected CustomerBusinessNameService        $customerBusinessNameService,
  234.         protected EntityRepositoryInterface          $videoRecordRepository,
  235.         protected EntityRepository                   $inviteFriendHistory,
  236.         protected AIStreamService                    $aiStreamService,
  237.         protected RankingService                     $rankingService,
  238.         EntityRepository                             $customerAcceptCookieRepository,
  239.         protected LuckyWheelApiService               $luckyWheelApiService,
  240.         protected RewardShipLaterService             $rewardShipLaterService,
  241.         protected EntityRepository                   $product,
  242.         protected CartService                        $cartService,
  243.         protected OrderPersister                     $orderPersister,
  244.         protected EntityRepository                   $paymentMethodRepository,
  245.         SalesChannelRepositoryInterface              $salesChannelProductRepository,
  246.         protected EntityRepositoryInterface          $customerAddressRepository,
  247.         protected NuveiSubscriptionPaymentHandler    $nuveiSubscriptionPaymentHandler,
  248.         NuveiSubscriptionController                  $nuveiSubscriptionController,
  249.         PaymentService                               $paymentService,
  250.         protected SubscriptionService                $subscriptionService,
  251.         EntityRepositoryInterface                    $orderRepository,
  252.         EntityRepository                             $subscriptionRepository,
  253.         EntityRepository                             $subscriptionOrderRepository,
  254.         StateMachineRegistry                         $stateMachineRegistry,
  255.         Connection                                   $connection,
  256.         EntityRepository                             $stateMachineStateRepository,
  257.         protected GenericPageLoader                  $genericLoader,
  258.         EntityRepositoryInterface                    $productBundleItemsRepository null,
  259.         protected SalesChannelRepositoryInterface    $countrySalesChannel,
  260.         DocumentGenerator                            $documentGenerator,
  261.         EntityRepository                             $documentRepository,
  262.     )
  263.     {
  264.         $this->registrationAppPageLoader $registrationAppPageLoader;
  265.         $this->registerController $registerController;
  266.         $this->customerValidationFactory $customerValidationFactory;
  267.         $this->dataValidator $dataValidator;
  268.         $this->systemConfigService $systemConfigService;
  269.         $this->contextResolver $contextResolver;
  270.         $this->tagSaverController $tagSaverController;
  271.         $this->customerEntity $customerEntity;
  272.         $this->router $router;
  273.         $this->navigationPageLoader $navigationPageLoader;
  274.         $this->changeCustomerProfileRoute $changeCustomerProfileRoute;
  275.         $this->changeEmailRoute $changeEmailRoute;
  276.         $this->addressDetailPageLoader $addressDetailPageLoader;
  277.         $this->profilePageLoader $profilePageLoader;
  278.         $this->overviewPageLoader $overviewPageLoader;
  279.         $this->updateAddressRoute $updateAddressRoute;
  280.         $this->newsletterSubscribeRoute $newsletterSubscribeRoute;
  281.         $this->newsletterUnsubscribeRoute $newsletterUnsubscribeRoute;
  282.         $this->econsorAccountService $econsorAccountService;
  283.         $this->orderPageLoader $orderPageLoader;
  284.         $this->newsletterRecipientRepository $newsletterRecipientRepository;
  285.         $this->languageRepository $languageRepository;
  286.         $this->countryRepository $countryRepository;
  287.         $this->homepageService $homePageService;
  288.         $this->logger $logger;
  289.         $this->luckyWheelTurnRepository $luckyWheelTurnRepository;
  290.         $this->customerAcceptCookieRepository $customerAcceptCookieRepository;
  291.         $this->product $product;
  292.         $this->cartService $cartService;
  293.         $this->orderPersister $orderPersister;
  294.         $this->salesChannelProductRepository $salesChannelProductRepository;
  295.         $this->nuveiSubscriptionController $nuveiSubscriptionController;
  296.         $this->paymentService $paymentService;
  297.         $this->orderRepository $orderRepository;
  298.         $this->subscriptionRepository $subscriptionRepository;
  299.         $this->subscriptionOrderRepository $subscriptionOrderRepository;
  300.         $this->customerAddressRepository $customerAddressRepository;
  301.         $this->stateMachineRegistry $stateMachineRegistry;
  302.         $this->connection $connection;
  303.         $this->stateMachineStateRepository $stateMachineStateRepository;
  304.         $this->productBundleItemsRepository $productBundleItemsRepository;
  305.         $this->documentGenerator $documentGenerator;
  306.         $this->documentRepository $documentRepository;
  307.     }
  308.     #[Route('/product/search'name'frontend.product.search'options: ["seo" => false], defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
  309.     public function searchProduct(Request $requestSalesChannelContext $context): Response
  310.     {
  311.         $products $this->homepageService->searchProduct($request->request->get('keyword') ?? $request->query->get('keyword'), $context);
  312.         return $this->renderStorefront('@Storefront/storefront/component/header/product-search-result.html.twig', ['products' => $products]);
  313.     }
  314.     #[Route('/app/login'name'frontend.econsor.app.storefront.login')]
  315.     public function showApp(Request $requestSalesChannelContext $context): Response
  316.     {
  317.         $url $this->router->generate('frontend.account.login');
  318.         return $this->redirect($url);
  319.         if ($context->getCustomer() != null) {
  320.             $url $this->router->generate('frontend.current-deals');
  321.             return $this->redirect($url);
  322.         }
  323.         if ($request->isMethod('POST')) {
  324.             if ($request->request->has('register')) {
  325.                 $url $this->router->generate('frontend.econsor.app.storefront.register');
  326.                 return $this->redirect($url);
  327.             } else if ($request->request->has('login')) {
  328.                 $url $this->router->generate('frontend.account.login');
  329.                 return $this->redirect($url);
  330.             } else if ($request->request->has('loginFacebook')) {
  331.                 $url $this->router->generate('frontend.account.login');
  332.                 return $this->redirect($url);
  333.             }
  334.         }
  335.         $request->getSession()->remove('before-login-video-seen');
  336.         $page $this->registrationAppPageLoader->load($request$context);
  337.         return $this->renderStorefront('@Storefront/storefront/page/account/register/index.html.twig', [
  338.             'page' => $page,
  339.             'salesChannelContext' => $context,
  340.             'controllerName' => 'EconsorAppStorefrontController',
  341.             'controllerAction' => 'showApp'
  342.         ]);
  343.     }
  344.     #[Route('/app/register'name'frontend.econsor.app.storefront.register')]
  345.     public function registration(Request $requestSalesChannelContext $context): Response
  346.     {
  347.         if ($context->getCustomer() != null) {
  348.             $url $this->router->generate('frontend.current-deals');
  349.             return $this->redirect($url);
  350.         }
  351.         $page $this->registrationAppPageLoader->load($request$context);
  352.         return $this->renderStorefront('@Storefront/storefront/page/custom/register/index.html.twig', ['page' => $page]);
  353.     }
  354.     #[Route('/app/google/login/finish'name'frontend.google.login')]
  355.     public function googleLoginFinish(Request $requestSalesChannelContext $context): Response
  356.     {
  357.         if ($context->getCustomer() != null) {
  358.             $url $this->router->generate('frontend.current-deals');
  359.             return $this->redirect($url);
  360.         }
  361.         $data $request->query->all();
  362.         $page $this->registrationAppPageLoader->load($request$context);
  363.         return $this->renderStorefront('@Storefront/storefront/page/custom/google-login/google-login-finish.html.twig', [
  364.             'page' => $page,
  365.             'salesChannelContext' => $context,
  366.             'tag' => array_key_exists('tag'$data) ? $data['tag'] : [],
  367.             'salutationId' => array_key_exists('salutationId'$data) ? $data['salutationId'] : '',
  368.             'phoneNumber' => array_key_exists('phoneNumber'$data) ? $data['phoneNumber'] : '',
  369.             'firstName' => array_key_exists('firstName'$data) ? $data['firstName'] : '',
  370.             'lastName' => array_key_exists('lastName'$data) ? $data['lastName'] : '',
  371.             'userName' => array_key_exists('email'$data) ? $data['email'] : '',
  372.             'birthday' => array_key_exists('birthday'$data) ? $data['birthday'] : '',
  373.             'email' => array_key_exists('email'$data) ? $data['email'] : '',
  374.             'password' => array_key_exists('password'$data) ? $data['password'] : '',
  375.             'isConfirm' => array_key_exists('isConfirm'$data) ? $data['isConfirm'] : '',
  376.             'violations' => array_key_exists('violations'$data) ? $data['violations'] : [],
  377.             'controllerName' => 'EconsorAppStorefrontController',
  378.             'controllerAction' => 'registration'
  379.         ]);
  380.     }
  381.     #[Route('/app/register/form'name'frontend.econsor.app.storefront.register.form')]
  382.     public function registrationInterest(Request $requestSalesChannelContext $context): Response
  383.     {
  384.         if ($context->getCustomer() != null) {
  385.             $url $this->router->generate('frontend.current-deals');
  386.             return $this->redirect($url);
  387.         }
  388.         $data $request->query->all();
  389.         $page $this->registrationAppPageLoader->load($request$context);
  390.         $criteria = new Criteria();
  391.         $criteria->addFilter(new EqualsFilter('active'true));
  392.         $country $this->countrySalesChannel->search(new Criteria(), $context)->getElements();
  393.         
  394.         return $this->renderStorefront('@Storefront/storefront/page/custom/register-interest/index.html.twig', [
  395.             'page' => $page,
  396.             'salesChannelContext' => $context,
  397.             'tag' => array_key_exists('tag'$data) ? $data['tag'] : [],
  398.             'salutationId' => array_key_exists('salutationId'$data) ? $data['salutationId'] : '',
  399.             'phoneNumber' => array_key_exists('phoneNumber'$data) ? $data['phoneNumber'] : '',
  400.             'firstName' => array_key_exists('firstName'$data) ? $data['firstName'] : '',
  401.             'lastName' => array_key_exists('lastName'$data) ? $data['lastName'] : '',
  402.             'userName' => array_key_exists('email'$data) ? $data['email'] : '',
  403.             'birthday' => array_key_exists('birthday'$data) ? $data['birthday'] : '',
  404.             'email' => array_key_exists('email'$data) ? $data['email'] : '',
  405.             'password' => array_key_exists('password'$data) ? $data['password'] : '',
  406.             'isConfirm' => array_key_exists('isConfirm'$data) ? $data['isConfirm'] : '',
  407.             'violations' => array_key_exists('violations'$data) ? $data['violations'] : [],
  408.             'controllerName' => 'EconsorAppStorefrontController',
  409.             'country' => $country,
  410.             'controllerAction' => 'registration'
  411.         ]);
  412.     }
  413.     #[Route('/'name'frontend.home.page')]
  414.     public function home(Request $requestSalesChannelContext $context): Response
  415.     {
  416. //         $currentLocaleId = $context->getSalesChannel()->getLanguageId();
  417. //         if (!empty($currentLocaleId)) {
  418. //             $localeCriteria = new Criteria([$currentLocaleId]);
  419. //             $localeCriteria->addAssociation('translationCode');
  420. //             /** @var LanguageEntity|null $currentLocale */
  421. //             $currentLocale = $this->languageRepository->search($localeCriteria, $context->getContext())->first();
  422. //             if (!empty(explode('-', $currentLocale->getTranslationCode()->getCode())[1])) {
  423. //                 $isoCode = strtolower(explode('-', $currentLocale->getTranslationCode()->getCode())[1]);
  424. //                 if ($isoCode === 'vn') {
  425. //                     $page = $this->genericLoader->load($request, $context);
  426. //                     return $this->renderStorefront('@Storefront/storefront/page/content/index-vn.html.twig', ['page' => $page]);
  427. //                 }
  428. //                else if ($isoCode === 'hk') {
  429. //                    $page = $this->genericLoader->load($request, $context);
  430. //                    return $this->renderStorefront('@Storefront/storefront/page/content/index-en.html.twig', ['page' => $page]);
  431. //                }
  432. //             }
  433. //         }
  434.         return $this->redirectToRoute('frontend.current-deals');
  435.     }
  436.     #[Route('/account'name'frontend.account.home.page'defaults: ['_loginRequired' => true], methods: ['GET'])]
  437.     public function accountOverView(Request $requestSalesChannelContext $contextContext $sale): Response
  438.     {
  439.         if ($request->query->get('confirmDoneOrder')){
  440.             $orderId $request->query->get('orderIdConfirmDone');
  441.             if ($orderId) {
  442.                 try {
  443.                     // Get current custom fields for the order
  444.                     $orderCriteria = new Criteria([$orderId]);
  445.                     $orderCriteria->addAssociation('stateMachineState');
  446.                     $order $this->orderRepository->search($orderCriteria$context->getContext())->first();
  447.                     if (!$order) {
  448.                         throw new \Exception('Order not found');
  449.                     }
  450.                     if ($order->getStateMachineState()->getTechnicalName() === 'completed') {
  451.                         throw new \Exception('Order completed!');
  452.                     }
  453.                     $customFields $order->getCustomFields() ?? [];
  454.                     // Set the typeDoneOrder field
  455.                     $customFields['typeDoneOrder'] = 'confirm';
  456.                     try {
  457.                         // Update order state to 'complete' using StateMachineRegistry
  458.                         // First update the custom fields
  459.                         $this->orderRepository->update([
  460.                             [
  461.                                 'id' => $orderId,
  462.                                 'customFields' => $customFields
  463.                             ]
  464.                         ], $context->getContext());
  465.                         // Then transition the order state to 'complete'
  466.                         $this->stateMachineRegistry->transition(
  467.                             new Transition(
  468.                                 'order',
  469.                                 $orderId,
  470.                                 'complete',
  471.                                 'stateId'
  472.                             ),
  473.                             $context->getContext()
  474.                         );
  475.                         $this->logger->info('Order state updated successfully', [
  476.                             'orderId' => $orderId,
  477.                             'processState' => 'process',
  478.                             'finalState' => 'complete',
  479.                             'method' => 'StateMachineRegistry transition'
  480.                         ]);
  481.                     } catch (\Exception $dbException) {
  482.                         $this->logger->error('Error updating order state:', [
  483.                             'orderId' => $orderId,
  484.                             'method' => 'StateMachineRegistry transition',
  485.                             'error' => $dbException->getMessage(),
  486.                             'trace' => $dbException->getTraceAsString()
  487.                         ]);
  488.                         throw new \Exception('Failed to update order state: ' $dbException->getMessage());
  489.                     }
  490.                     // Add a success message for the user with more details
  491.                     $this->addFlash('success''Order has been marked as complete successfully and custom fields have been updated using StateMachineRegistry transition.');
  492.                 } catch (\Exception $e) {
  493.                     // Log the error with detailed information
  494.                     $this->logger->error('Failed to update order status:', [
  495.                         'orderId' => $orderId,
  496.                         'error' => $e->getMessage(),
  497.                         'trace' => $e->getTraceAsString()
  498.                     ]);
  499.                     // Add an error message for the user
  500.                     $this->addFlash('danger''Failed to update order status: ' $e->getMessage());
  501.                 }
  502.             }
  503.         }
  504.         $page $this->genericLoader->load($request$context);
  505.         $status $request->query->get('status');
  506.         $orders $this->homepageService->getCustomerOrderByStatus($status$context);
  507.         $customerCredit $this->homepageService->getCustomerCredit($context$sale);
  508.         $nextCreditProductLevel $this->homepageService->loadNextCreditProductOfCustomer($customerCredit$context);
  509.         $customerInvitedCount $this->homepageService->getCustomerInvitedCount($context);
  510.         $interest $this->homepageService->getCustomerTag($context);
  511.         $customerSubscriptions $this->homepageService->getSubscriptions($context);
  512.         $sumPriceSubscriptions $this->homepageService->sumPriceSubscriptions($customerSubscriptions);
  513.         $customer $context->getCustomer();
  514.         $isPartner $customer !== null
  515.             && $customer->getAffiliateCode() !== null
  516.             && $customer->getAffiliateCode() === $customer->getCampaignCode();
  517.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/over-view/index.html.twig', [
  518.             'page' => $page,
  519.             'customer' => $customerCredit,
  520.             'orders' => $orders,
  521.             'interest' => $interest,
  522.             'nextCreditProductLevel' => $nextCreditProductLevel,
  523.             'customerInvitedCount' => $customerInvitedCount,
  524.             'sumPriceSubscriptions' => $sumPriceSubscriptions,
  525.             'isPartner' => $isPartner,
  526.         ]);
  527.     }
  528.     #[Route('/account/dashboard'name'frontend.account.dashboard'defaults: ['_loginRequired' => true], methods: ['GET'])]
  529.     public function accountDashboard(Request $requestSalesChannelContext $contextContext $sale): Response
  530.     {
  531.         $page $this->genericLoader->load($request$context);
  532.         $customer $context->getCustomer();
  533.         $dataStreamLiked $this->postService->fetchStream($context->getCustomer()->getId(), $context"liked");
  534.         $dataStreamUpload $this->postService->fetchStream($context->getCustomer()?->getId(), $context"uploaded");
  535.         $dataStreamSaved $this->postService->fetchStream($context->getCustomer()?->getId(), $context"saved");
  536.         return $this->renderStorefront('@Storefront/storefront/page/account/dashboard/index.html.twig', ['page' => $page'dataStreamUpload' => $dataStreamUpload'dataStreamLiked' => $dataStreamLiked'dataStreamSaved' => $dataStreamSaved'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  537.     }
  538.     #[Route('/account/order'name'frontend.account.order.page'defaults: ['_loginRequired' => true], methods: ['GET'])]
  539.     public function accountMyOrder(Request $requestSalesChannelContext $context): Response
  540.     {
  541.         $status $request->query->get('status');
  542.         $page $this->genericLoader->load($request$context);
  543.         $orders $this->homepageService->getCustomerOrderByStatus($status$context);
  544.         $customer $context->getCustomer();
  545.         $isPartner $customer !== null
  546.             && $customer->getAffiliateCode() !== null
  547.             && $customer->getAffiliateCode() === $customer->getCampaignCode();
  548.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/my-order/index.html.twig', ['page' => $page'orders' => $orders'isPartner' => $isPartner]);
  549.     }
  550.     #[Route('/account/partner-dashboard'name'frontend.account.partner.dashboard'defaults: ['_loginRequired' => true], methods: ['GET'])]
  551.     public function accountPartnerDashboard(Request $requestSalesChannelContext $context): Response
  552.     {
  553.         $page $this->genericLoader->load($request$context);
  554.         $invitedList $this->homepageService->getCustomerInvitedList($context);
  555.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/partner-dashboard/index.html.twig', [
  556.             'page' => $page,
  557.             'invitedList' => $invitedList,
  558.             'totalVideoWatchedToday' => $this->videoRecordRepository->search((new Criteria())->addFilter(
  559.                 new EqualsFilter('customerId'$context->getCustomerId()),
  560.                 new EqualsFilter('type''watch'),
  561.                 new RangeFilter('createdAt', [
  562.                     RangeFilter::GTE => (new \DateTime())->setTime(00)->format('Y-m-d H:i:s'),
  563.                     RangeFilter::LTE => (new \DateTime())->setTime(235959)->format('Y-m-d H:i:s')
  564.                 ]),
  565.             ), $context->getContext())->count(),
  566.             'totalVideoSharedToday' => $this->videoRecordRepository->search((new Criteria())->addFilter(
  567.                 new EqualsFilter('customerId'$context->getCustomerId()),
  568.                 new EqualsFilter('type''share'),
  569.                 new RangeFilter('createdAt', [
  570.                     RangeFilter::GTE => (new \DateTime())->setTime(00)->format('Y-m-d H:i:s'),
  571.                     RangeFilter::LTE => (new \DateTime())->setTime(235959)->format('Y-m-d H:i:s')
  572.                 ]),
  573.             ), $context->getContext())->count(),
  574.         ]);
  575.     }
  576.     #[Route('/account/order-list'name'frontend.account.order.page.list'options: ["seo" => false], defaults: ['_loginRequired' => true'XmlHttpRequest' => true], methods: ['POST'])]
  577.     public function accountMyOrderList(Request $requestSalesChannelContext $context): Response
  578.     {
  579.         $status $request->request->get('status');
  580.         $isMobile $request->request->get('isMobile');
  581.         $orders $this->homepageService->getCustomerOrderByStatus($status$context);
  582.         $customer $context->getCustomer();
  583.         $isPartner $customer !== null
  584.             && $customer->getAffiliateCode() !== null
  585.             && $customer->getAffiliateCode() === $customer->getCampaignCode();
  586.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/my-order/list-order.html.twig', ['orders' => empty($orders['errorMsg']) ? $orders : [], 'isMobile' => $isMobile'isPartner' => $isPartner]);
  587.     }
  588.     #[Route(path'/notification/list'name'frontend.notification.list'defaults: ['_loginRequired' => true'XmlHttpRequest' => true], methods: ['POST''GET'])]
  589.     public function getNotificationList(Request $requestSalesChannelContext $context): Response
  590.     {
  591.         $customerId $context->getCustomer()->getId();
  592.         $type $request->request->get('type'null);
  593.         $criteria = new Criteria();
  594.         $criteria->addFilter(new EqualsFilter('customerId'$customerId));
  595.         $criteria->addAssociation('notification');
  596.         $criteria->addSorting(new FieldSorting('createdAt'FieldSorting::DESCENDING));
  597.         if (!empty($type)) {
  598.             $criteria->addFilter(new EqualsFilter('notification.type'$type));
  599.         }
  600.         $notifications $this->customerNotificationRepository->search($criteria$context->getContext());
  601.         // Update view notification of customer
  602.         $unviewNotifications $notifications->filter(fn($notification) => $notification->getIsView() == 0);
  603.         if (count($unviewNotifications) > 0) {
  604.             foreach ($unviewNotifications as $customerNotification) {
  605.                 $this->customerNotificationRepository->update([
  606.                     [
  607.                         'id' => $customerNotification->getId(),
  608.                         'isView' => 1
  609.                     ]
  610.                 ], $context->getContext());
  611.             }
  612.         }
  613.         // Quantity of lucky wheel
  614.         $criteriaLuckyWheel = new Criteria();
  615.         $currentDate = (new \DateTime())->format('Y-m-d H:i:s');
  616.         $criteriaLuckyWheel->addFilter(
  617.             new MultiFilter(
  618.                 MultiFilter::CONNECTION_AND,
  619.                 [
  620.                     new EqualsFilter('customerId'$customerId),
  621.                     new RangeFilter('amountRemain', [RangeFilter::GT => 0]),
  622.                     new OrFilter([
  623.                         new EqualsFilter('expiredDate'null),
  624.                         new RangeFilter('expiredDate', [RangeFilter::GT => $currentDate])
  625.                     ])
  626.                 ]
  627.             )
  628.         )->addFilter(
  629.             new NotFilter(
  630.                 MultiFilter::CONNECTION_AND,
  631.                 [
  632.                     new EqualsFilter('reason'"Invalid"),
  633.                 ]
  634.             )
  635.         );
  636.         $notificationsLuckyWheel $this->luckyWheelTurnRepository->search($criteriaLuckyWheel$context->getContext());
  637.         $quantityLuckyWheel array_reduce($notificationsLuckyWheel->getEntities()->getElements(), function ($carry$item) {
  638.             return $carry $item->get('amountRemain');
  639.         }, 0);
  640.         return $this->renderStorefront('@Storefront/storefront/component/header/notification.html.twig', ['notifications' => $notifications'quantityLuckyWheel' => $quantityLuckyWheel]);
  641.     }
  642.     #[Route(path'/notification/count-not-view'name'api.notification.count-not-view'defaults: ['_loginRequired' => true'XmlHttpRequest' => true], methods: [Request::METHOD_POST])]
  643.     public function getCountNotificationNotView(SalesChannelContext $context): Response
  644.     {
  645.         $customerId $context->getCustomer()->getId();
  646.         $countNotViewNotification 0;
  647.         $criteria = new Criteria();
  648.         $criteria->addFilter(new EqualsFilter('customerId'$customerId));
  649.         $criteria->addFilter(new EqualsFilter('isView'0));
  650.         $notificationsNotView $this->customerNotificationRepository->search($criteria$context->getContext());
  651.         $countNotViewNotification count($notificationsNotView);
  652.         return new JsonResponse([
  653.             'countNotViewNotification' => $countNotViewNotification
  654.         ]);
  655.     }
  656.     #[Route('/my-deals'name'frontend.my.deals'defaults: ['_loginRequired' => true], methods: ['GET'])]
  657.     public function myDeals(Request $requestSalesChannelContext $context): Response
  658.     {
  659.         $page $this->genericLoader->load($request$context);
  660.         $customer $context->getCustomer();
  661.         $activeDeals = [];
  662.         if ($customer) {
  663.             $activeDeals $this->econsorAccountService->getActiveDeals($customer$context);
  664.         }
  665.         $credits $this->homepageService->getCredits($context);
  666.         $redeemedItems $this->homepageService->getRedeemedItems($context);
  667.         return $this->renderStorefront('@Storefront/storefront/page/custom/my-deal/pageDeal.html.twig', ['page' => $page'activeDeals' => $activeDeals'customer' => $customer'credits' => $credits'redeemedItems' => $redeemedItems]);
  668.     }
  669.     #[Route('/ai-stream'name'frontend.ai.stream')]
  670.     public function aiStream(Request $requestSalesChannelContext $context)
  671.     {
  672.         $page $this->genericLoader->load($request$context);
  673.         $customer $context->getCustomer();
  674.         $experienceId $request->query->get('experienceId');
  675.         if ($experienceId) {
  676.             return $this->redirectToRoute('frontend.ai.stream.detail.page', ['id' => $experienceId]);
  677.         }
  678.         $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"tags"null$experienceId);
  679.         $dataStream $dataStream;
  680.         $targetObject null;
  681.         $index = -1;
  682.         if ($experienceId !== null) {
  683.             foreach ($dataStream as $key => $value) {
  684.                 if ($value->id == $experienceId) {
  685.                     $index $key;
  686.                     $targetObject $value;
  687.                     break;
  688.                 }
  689.             }
  690.             if ($targetObject !== null && $index !== -1) {
  691.                 unset($dataStream[$index]);
  692.                 $dataStream array_values($dataStream);
  693.                 array_unshift($dataStream$targetObject);
  694.             }
  695.         }
  696.         return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/home/index.html.twig', ['page' => $page'customer' => $customer'dataStream' => $dataStream'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  697.     }
  698.     // #[Route('/ai-stream-list', name: 'frontend.ai.stream.page.list', options: ["seo" => false], defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
  699.     // public function aiStreamList(Request $request, SalesChannelContext $context)
  700.     // {
  701.         // $page = $this->genericLoader->load($request, $context);
  702.         // $customer = $context->getCustomer();
  703.         // $experienceId = null;
  704.         // $pageNumber = 2;
  705.         // $dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "tags", null, $experienceId, $pageNumber);
  706.         // return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/home/list-video.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream['posts'], 'dashboardData' => $this->customerDataService->getBoughtProducts($context), 'totalVideo' => $dataStream['total']]);
  707.         // return 1;
  708.     // }
  709.     #[Route('/ai-stream/liked'name'frontend.ai.stream.liked'defaults: ['_loginRequired' => true], methods: ['GET'])]
  710.     public function aiStreamLiked(Request $requestSalesChannelContext $context): Response
  711.     {
  712.         $page $this->genericLoader->load($request$context);
  713.         $customer $context->getCustomer();
  714.         $dataStream $this->postService->fetchStream($context->getCustomer()->getId(), $context"liked");
  715.         return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/liked/index.html.twig', ['page' => $page'customer' => $customer'dataStream' => $dataStream'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  716.     }
  717.     #[Route('/ai-stream/upload/list'name'frontend.ai.stream.upload.list'defaults: ['_loginRequired' => true], methods: ['GET'])]
  718.     public function aiStreamUploadList(Request $requestSalesChannelContext $context): Response
  719.     {
  720.         $page $this->genericLoader->load($request$context);
  721.         $customer $context->getCustomer();
  722.         $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"uploaded");
  723.         return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/upload-list/index.html.twig', ['page' => $page'customer' => $customer'dataStream' => $dataStream'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  724.     }
  725.     #[Route('/ai-stream/upload'name'frontend.ai.stream.upload'defaults: ['_loginRequired' => true], methods: ['GET'])]
  726.     public function aiStreamUpload(Request $requestSalesChannelContext $context): Response
  727.     {
  728.         $page $this->genericLoader->load($request$context);
  729.         $customer $context->getCustomer();
  730.         return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/upload/index.html.twig', ['page' => $page'customer' => $customer'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  731.     }
  732.     #[Route('/story-mode/upload'name'frontend.story.mode.upload'defaults: ['_loginRequired' => true], methods: ['GET'])]
  733.     public function storyModeUpload(Request $requestSalesChannelContext $context): Response
  734.     {
  735.         $page $this->genericLoader->load($request$context);
  736.         $customer $context->getCustomer();
  737.         return $this->renderStorefront('@Storefront/storefront/page/custom/story-mode/upload/index.html.twig', ['page' => $page'customer' => $customer'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  738.     }
  739.     #[Route('/dealDetail'name'frontend.my.deal.detail'defaults: ['_loginRequired' => true], methods: ['GET'])]
  740.     public function myDealDetail(Request $requestSalesChannelContext $contextContext $sale): Response
  741.     {
  742.         $page $this->genericLoader->load($request$context);
  743.         $customer $this->homepageService->getCustomerCredit($context$sale);
  744.         $orderId $request->query->get('orderId');
  745.         $orderData $this->homepageService->getOrderById($orderId$context);
  746.         $creditType $this->homepageService->getOrderCreditType($orderId$context);
  747.         return $this->renderStorefront('@Storefront/storefront/page/custom/my-deal/pageDealDetail.html.twig', ['page' => $page'orderData' => $orderData'customer' => $customer'creditType' => $creditType]);
  748.     }
  749.     #[Route('/trackingDetail'name'frontend.tracking.detail'defaults: ['_loginRequired' => true], methods: ['GET'])]
  750.     public function trackingDetail(Request $requestSalesChannelContext $context): Response
  751.     {
  752.         $page $this->genericLoader->load($request$context);
  753.         return $this->renderStorefront('@Storefront/storefront/page/custom/tracking/tracking-detail.html.twig', ['page' => $page]);
  754.     }
  755.     #[Route('/ai-stream/account/{customerId}'name'frontend.ai-stream.detail'defaults: ['_loginRequired' => true], methods: ['GET'])]
  756.     public function aistreamDetail(Request $requestSalesChannelContext $context): Response
  757.     {
  758.         $page $this->genericLoader->load($request$context);
  759.         $customerId $request->attributes->get('customerId');
  760.         /** @var CustomerEntity $customer */
  761.         $customer $this->customerEntity->search(new Criteria([$customerId]), $context->getContext())->first();
  762.         $dataStream = [];
  763.         $topProducts = [];
  764.         if (!empty($customer?->getId())) {
  765.             $topProducts $this->homepageService->getTopProducts($context);
  766.             $dataStream $this->postService->fetchStream($customerId$context"uploaded");
  767.             $customerStats $this->postService->fetchCustomerStats($customerId$context);
  768.             $customer->addArrayExtension('aiStreamStats'$customerStats);
  769.         }
  770.         return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/ai-stream-detail/index.html.twig', [
  771.             'page' => $page,
  772.             'dataStream' => $dataStream,
  773.             'topProducts' => $topProducts,
  774.             'aiStreamAccount' => $customer,
  775.             'dashboardData' => $this->customerDataService->getBoughtProducts($context)
  776.         ]);
  777.     }
  778.     #[Route(path'/lucky-wheel'name'frontend.credit.lucky-wheel'defaults: ['_loginRequired' => false], methods: ['GET'])]
  779.     public function luckyWheel(Request $requestSalesChannelContext $context): Response
  780.     {
  781.         $page $this->genericLoader->load($request$context);
  782.         return $this->renderStorefront('@Storefront/storefront/page/custom/lucky-wheel/index.html.twig', ['page' => $page]);
  783.     }
  784.     #[Route(path'/rewards'name'frontend.rewards'defaults: ['_loginRequired' => true], methods: ['GET'])]
  785.     public function rewardsPage(Request $requestSalesChannelContext $context): Response
  786.     {
  787.         $page $this->genericLoader->load($request$context);
  788.         $customer $context->getCustomer();
  789.         $prizes $this->luckyWheelApiService->queryUserPrize(nullnull$customer->getId(), $context)->getElements();
  790.         foreach ($prizes as $key => &$prize) {
  791.             if ($prize->prize == null) {
  792.                 $prize->prize = [];
  793.             }
  794.         }
  795.         return $this->renderStorefront('@Storefront/storefront/page/custom/rewards/index.html.twig', ['page' => $page'dashboardData' => $this->customerDataService->getBoughtProducts($context), 'prizes' => $prizes]);
  796.     }
  797.     #[Route(path'/rewards/add-to-cart'name'frontend.addToCart'defaults: ['_loginRequired' => true'XmlHttpRequest' => true], methods: ['POST'])]
  798.     public function addToCart(Request $requestSalesChannelContext $context): Response
  799.     {
  800.         $customer $context->getCustomer();
  801.         $prizeId $request->request->get('prizeId');
  802.         $prize $this->luckyWheelApiService->queryUserPrizeById($prizeId$customer->getId(), $context);
  803.         if (!$prize) {
  804.             return new JsonResponse([
  805.                 'success' => false,
  806.                 'message' => 'Prize not found',
  807.             ], 404);
  808.         }
  809.         if ($prize->getCustomer()->getId() != $customer->getId()) {
  810.             return new JsonResponse([
  811.                 'success' => false,
  812.                 'message' => 'Not authorized',
  813.             ], 403);
  814.         }
  815.         if ($prize->getIsClaimed()) {
  816.             return new JsonResponse([
  817.                 'success' => false,
  818.                 'message' => 'Prize not claimed',
  819.             ], 400);
  820.         }
  821.         if ($prize->getTurn()->getReason() == 'Invalid') {
  822.             return new JsonResponse([
  823.                 'success' => false,
  824.                 'message' => 'Invalid prize',
  825.             ], 400);
  826.         }
  827.         $this->rewardShipLaterService->createRewardShipLater([
  828.             'customerId' => $customer->getId(),
  829.             'fromOrderId' => null,
  830.             'referencedId' => $prize->getPrize()->getProduct()->getId(),
  831.             'payload' => [
  832.                 'productId' => $prize->getPrize()->getProduct()->getId(),
  833.                 'isShipWithNextOrder' => false,
  834.             ],
  835.             'quantity' => 1,
  836.             'isShipped' => false,
  837.             'type' => RewardShipLaterType::LuckyWheel->value
  838.         ]);
  839.         $this->luckyWheelApiService->updateIsClaimedById($prize->getId(), $context);
  840.         return new JsonResponse([
  841.             'success' => true,
  842.             'message' => 'Added to cart successfully'
  843.         ]);
  844.     }
  845.     #[Route(path'/policy'name'frontend.app.policy')]
  846.     public function policy(Request $requestSalesChannelContext $context): Response
  847.     {
  848.         return $this->renderStorefront('@Storefront/storefront/page/condition/index.html.twig');
  849.     }
  850.     /**
  851.      * @HttpCache()
  852.      * @Route("/detail/{productId}", name="frontend.detail.page", methods={"GET"})
  853.      */
  854.     public function productDetail(SalesChannelContext $contextRequest $request): Response
  855.     {
  856.         $page $this->productPageLoader->load($request$context);
  857.         $this->hook(new ProductPageLoadedHook($page$context));
  858.         $productId $request->attributes->get('productId');
  859.         $customerId $context->getCustomer()?->getId();
  860.         // Check if the customer has an active subscription for this product
  861.         $hasActiveSubscription $this->subscriptionService->hasActiveSubscription(
  862.             $customerId,
  863.             $productId,
  864.             $context
  865.         );
  866.         $product $page->getProduct();
  867.         if (isset($product->getCustomFields()['productIsBuyWithCredit']) && $product->getCustomFields()['productIsBuyWithCredit'] === true) {
  868.             $url $this->router->generate('frontend.shop4free');
  869.             return $this->redirect($url);
  870.         }
  871.         $outOfStock false;
  872.         if ($product->getAvailableStock() > 0) {
  873.             $customFields $product->getCustomFields();
  874.             if (isset($customFields['isProductBundle']) && $customFields['isProductBundle'] == true) {
  875.                 $outOfStock $this->isOutOfStock($product->getId(), $context);
  876.             }
  877.         } else {
  878.             $outOfStock true;
  879.         }
  880. //        $this->hook(new ProductPageLoadedHook($page, $context));
  881.         return $this->renderStorefront('@Storefront/storefront/page/product-detail/index.html.twig', ['page' => $page'ourOfStock' => $outOfStock'hasActiveSubscription' => $hasActiveSubscription]);
  882.     }
  883.     private function isOutOfStock(string $productIdSalesChannelContext $context): bool
  884.     {
  885.         // Create criteria to search for bundle items by product ID
  886.         $criteria = new Criteria();
  887.         $criteria->addFilter(new EqualsFilter('productId'$productId));
  888.         $criteria->addAssociation('relatedProduct');
  889.         $criteria->addAssociation('relatedProduct.swagDynamicAccessRules');
  890.         $criteria->addAssociation('product.swagDynamicAccessRules');
  891.         $criteria->addAssociation('product.swagDynamicAccessRules');
  892.         // Search for bundle items using the repository
  893.         $bundleItems $this->productBundleItemsRepository->search($criteria$context->getContext());
  894.         // If there are no bundle items, the product is not a bundle
  895.         if ($bundleItems->count() === 0) {
  896.             return true;
  897.         }
  898.         // Check if any of the bundle items are out of stock
  899.         $outOfStock true;
  900.         foreach ($bundleItems as $bundleItem) {
  901.             // Get the related product and quantity
  902.             $relatedProduct $bundleItem->getRelatedProduct();
  903.             // If the related product doesn't exist or doesn't have enough stock, the bundle is out of stock
  904.             if ($relatedProduct && $relatedProduct->getAvailableStock() > 0) {
  905.                 $rulesProduct $bundleItem->getProduct()->get('swagDynamicAccessRules');
  906.                 $rulesRelatedProduct $bundleItem->getRelatedProduct()->get('swagDynamicAccessRules');
  907.                 if (count($rulesRelatedProduct) > && count($rulesProduct) > 0) {
  908.                     foreach ($rulesProduct as $ruleProduct) {
  909.                         foreach ($rulesRelatedProduct as $ruleRelatedProduct) {
  910.                             if ($ruleRelatedProduct->getId() === $ruleProduct->getId()) {
  911.                                 $outOfStock false;
  912.                             }
  913.                         }
  914.                     }
  915.                 } elseif (count($rulesRelatedProduct) == && count($rulesProduct) == 0) {
  916.                     $outOfStock false;
  917.                 } else {
  918.                     return true;
  919.                 }
  920.             } else {
  921.                 return true;
  922.             }
  923.         }
  924.         return $outOfStock;
  925.     }
  926.     /**
  927.      * @HttpCache()
  928.      * @Route("/ai-stream/{id}", name="frontend.ai.stream.detail.page", methods={"GET"})
  929.      */
  930.     #[Route('/ai-stream/{id}'name'frontend.ai.stream.detail.page'defaults: ['_loginRequired' => false], methods: ['GET'])]
  931.     public function aiStreamDetailHomePage(Request $requestSalesChannelContext $context)
  932.     {
  933.         $page $this->genericLoader->load($request$context);
  934.         $customer $context->getCustomer();
  935.         $experienceId $request->attributes->get('id');
  936.         $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"tags"null$experienceId);
  937.         $targetObject null;
  938.         $index = -1;
  939.         if ($experienceId !== null) {
  940.             foreach ($dataStream as $key => $value) {
  941.                 if ($value->id == $experienceId) {
  942.                     $index $key;
  943.                     $targetObject $value;
  944.                     break;
  945.                 }
  946.             }
  947.             if ($targetObject !== null && $index !== -1) {
  948.                 unset($dataStream[$index]);
  949.                 $dataStream array_values($dataStream);
  950.                 array_unshift($dataStream$targetObject);
  951.             }
  952.         }
  953.         return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/home/index.html.twig', ['page' => $page'customer' => $customer'dataStream' => $dataStream'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  954.     }
  955.     // #[Route('/current-deals', name: 'frontend.current-deals')]
  956.     /**
  957.      * @HttpCache()
  958.      * @Route("/current-deals", name="frontend.current-deals", methods={"GET"})
  959.      */
  960.     public function currentDeals(Request $requestSalesChannelContext $context): Response
  961.     {
  962.         $arr_productId = [];
  963.         $page $this->genericLoader->load($request$context);
  964.         $categories $this->homepageService->getCategories($context);
  965.         return $this->renderStorefront('@Storefront/storefront/page/content/current-deals.html.twig', ['page' => $page'repertusProductReservationOptions' => $arr_productId'categories' => $categories]);
  966.     }
  967.     #[Route(path'/data-stream'name'frontend.data-stream'defaults: ['XmlHttpRequest' => true])]
  968.     public function getDataStream(Request $requestSalesChannelContext $context)
  969.     {
  970.         $page $this->genericLoader->load($request$context);
  971.         $productId $request->request->get('productId');
  972.         $type $request->request->get('type');
  973.         if ($type == 'details') {
  974.             $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"tags"$productIdnull110);
  975.             return $this->renderStorefront('@Storefront/storefront/page/custom/list-data-stream/list-data-stream-detail.html.twig', ['page' => $page'dataStream' => $dataStream]);
  976.         } else {
  977.             $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"tags"nullnull110);
  978.         }
  979.         return $this->renderStorefront('@Storefront/storefront/page/custom/list-data-stream/list-data-stream.html.twig', ['page' => $page'dataStream' => $dataStream]);
  980.     }
  981.     // #[Route('/invite-friends', name: 'frontend.invite-friends', defaults: ['_loginRequired' => true], methods: ['GET'])]
  982.     // public function inviteFriends(Request $request, SalesChannelContext $context): Response
  983.     // {
  984.     //     $page = $this->genericLoader->load($request, $context);
  985.     //     return $this->renderStorefront('@Storefront/storefront/page/custom/invite-friends/index.html.twig', ['page' => $page]);
  986.     // }
  987. //    #[Route('/navigation/{navigationId}', name: 'frontend.navigation.page')]
  988. //    public function index(SalesChannelContext $context, Request $request): Response
  989. //    {
  990. //        $page = $this->genericLoader->load($request, $context);
  991. //        $this->hook(new NavigationPageLoadedHook($page, $context));
  992. //
  993. //        return $this->renderStorefront('@Storefront/storefront/page/content/index.html.twig', ['page' => $page]);
  994. //    }
  995.     #[Route('/app/register/create-account'name'frontend.econsor.app.storefront.register.create-account')]
  996.     public function register(Request $requestSalesChannelContext $context): Response
  997.     {
  998.         if ($request->isMethod('POST')) {
  999.             if (empty($request->request->get('userName')) && !empty($request->request->get('email'))) {
  1000.                 $request->request->set('userName'$request->request->get('email'));
  1001.             }
  1002.             if (!empty($request->request->get('countryId'))) {
  1003.                 $countryCriteria = new Criteria();
  1004.                 $countryCriteria->addFilter(new EqualsFilter('id'strtoupper($request->request->get('countryId'))));;
  1005.                 $country $this->countryRepository->search($countryCriteria$context->getContext())->first();
  1006.                 if (!empty($country)) {
  1007.                     $request->request->set('countryCode'$country->getIso());
  1008.                 }
  1009.             } else {
  1010.                 if (!empty($request->request->get('countryCode'))) {
  1011.                     $countryCriteria = new Criteria();
  1012.                     $countryCriteria->addFilter(new EqualsFilter('iso'strtoupper($request->request->get('countryCode'))));
  1013.                     $country $this->countryRepository->search($countryCriteria$context->getContext())->first();
  1014.                     if (!empty($country)) {
  1015.                         $request->request->set('countryId'$country->getId());
  1016.                     }
  1017.                 }
  1018.             }
  1019.             $validation $this->validateCustomer($request$context);
  1020.             if ($validation instanceof CustomerEntity) {
  1021.                 if (array_key_exists('tag'$request->request->all())) {
  1022.                     $this->contextResolver->resolve($request);
  1023.                     $context $request->attributes->get('sw-sales-channel-context');
  1024.                     $this->setInterests($context$request->request->all()['tag']);
  1025.                     $this->setCustomFields($context, [
  1026.                         'customerCountryCode' => $request->request->get('countryCode'),
  1027.                         'customerLanguageCode' => $request->request->get('languageCode'),
  1028.                         'customerCountryId' => $request->request->get('countryId'),
  1029.                         'customerLanguageId' => $request->request->get('languageId'),
  1030.                     ]);
  1031.                 }
  1032.                 // if (in_array($request->request->get('countryCode'), ['vn'])) {
  1033.                 //     return $this->redirectToRoute('frontend.account.progress');
  1034.                 // }
  1035.                 $url $this->router->generate('frontend.home.page');
  1036.                 return $this->redirect($url);
  1037.             }
  1038.             return $validation;
  1039.         } else {
  1040.             $url $this->router->generate('frontend.econsor.app.storefront.register.form');
  1041.             return $this->redirect($url);
  1042.         }
  1043.     }
  1044.     private function validateCustomer(Request $requestSalesChannelContext $context): RedirectResponse|CustomerEntity
  1045.     {
  1046.         $definition $this->customerValidationFactory->create($context);
  1047.         $minLength $this->systemConfigService->get('core.loginRegistration.passwordMinLength'$context->getSalesChannel()->getId());
  1048.         $definition->add('password', new NotBlank(), new Length(['min' => $minLength]));
  1049.         $options = ['context' => $context->getContext(), 'salesChannelContext' => $context];
  1050.         $definition->add('email', new CustomerEmailUnique($options));
  1051.         $definition->add('phoneNumber', new NotBlank());
  1052.         $definition->add('salutationId', new NotBlank());
  1053.         $definition->add('userName', new NotBlank());
  1054.         $definition->add('birthday', new NotBlank());
  1055.         $definition->add('tag', new NotBlank());
  1056.         if ($request->getSession()->has('affiliateCode')) {
  1057.             $request->request->set('affiliateCode'$request->getSession()->get('affiliateCode'));
  1058.         }
  1059.         $constraints $this->getAffiliateCodeConstraints($context->getContext());
  1060.         foreach ($constraints as $constraint) {
  1061.             $definition->add('affiliateCode'$constraint);
  1062.         }
  1063.         $validation $this->dataValidator->getViolations($request->request->all(), $definition);
  1064.         //check for validation errors.
  1065.         if ($validation->count() > 0) {
  1066.             $violations = ['violations' => []];
  1067.             foreach ($validation as $violation) {
  1068.                 $violations['violations'][$violation->getPropertyPath()] = $violation->getMessage();
  1069.             }
  1070.             $request->request->add($violations);
  1071.             $url $this->router->generate('frontend.econsor.app.storefront.register.form'$request->request->all());
  1072.             return $this->redirect($url);
  1073.         }
  1074.         //create account
  1075.         $billingAddress = (array)$request->request->get('billingAddress');
  1076.         $billingAddress['phoneNumber'] = $request->request->get('phoneNumber');
  1077.         $billingAddress['countryId'] = $request->request->get('countryId');
  1078.         $request->request->set('billingAddress'$billingAddress);
  1079.         $birthDay = !empty($request->request->get('birthday')) ? date_parse_from_format('Y-m-d'$request->request->get('birthday')) : null;
  1080.         if (!empty($birthDay)) {
  1081.             $request->request->set('birthdayDay'$birthDay['day']);
  1082.             $request->request->set('birthdayMonth'$birthDay['month']);
  1083.             $request->request->set('birthdayYear'$birthDay['year']);
  1084.         }
  1085.         $requestDataBag = new RequestDataBag($request->request->all());
  1086.         $this->registerController->register($request$requestDataBag$context);
  1087.         $this->contextResolver->resolve($request);
  1088.         return $request->attributes->get('sw-sales-channel-context')->getCustomer();
  1089.     }
  1090.     /**
  1091.      * @param Context $context
  1092.      * @return array
  1093.      */
  1094.     protected function getAffiliateCodeConstraints(Context $context): array
  1095.     {
  1096.         $notBlankConstraint = new NotBlank();
  1097.         $customerExistsConstraint = new EntityExists([
  1098.             'entity' => CustomerDefinition::ENTITY_NAME,
  1099.             'context' => $context,
  1100.         ]);
  1101.         $customerExistsConstraint->primaryProperty 'affiliateCode';
  1102.         return [
  1103.             $notBlankConstraint,
  1104.             $customerExistsConstraint
  1105.         ];
  1106.     }
  1107.     private function setCustomFields(SalesChannelContext $context, array $customFields)
  1108.     {
  1109.         $customer $context->getCustomer();
  1110.         $customerCustomFields $customer?->getCustomFields() ?? [];
  1111.         foreach ($customFields as $key => $value) {
  1112.             $customerCustomFields[$key] = $value;
  1113.         }
  1114.         $this->customerEntity->update(
  1115.             [
  1116.                 [
  1117.                     'id' => $customer->getId(),
  1118.                     'customFields' => $customerCustomFields,
  1119.                 ]
  1120.             ],
  1121.             $context->getContext()
  1122.         );
  1123.     }
  1124.     private function setInterests(SalesChannelContext $context, array $tags)
  1125.     {
  1126.         $customer $context->getCustomer();
  1127.         $tagsTemp = [];
  1128.         foreach ($tags as $tag => $ignored) {
  1129.             array_push($tagsTemp, ['id' => $tag]);
  1130.         }
  1131.         $this->customerEntity->update(
  1132.             [
  1133.                 [
  1134.                     'id' => $customer->getId(),
  1135.                     'customerInterests' => [
  1136.                         'id' => $this->getExtensionIdByCustomer($customer),
  1137.                         'customerId' => $customer->getId(),
  1138.                         'tags' => $tagsTemp
  1139.                     ]
  1140.                 ]
  1141.             ],
  1142.             $context->getContext()
  1143.         );
  1144.     }
  1145.     private function getExtensionIdByCustomer(CustomerEntity $customer): string
  1146.     {
  1147.         if (
  1148.             $customer->getExtension('customerInterests') && isset(
  1149.                 $customer->getExtension('customerInterests')->all()['id']
  1150.             )
  1151.         ) {
  1152.             $extensionId $customer->getExtension('customerInterests')->all()['id'];
  1153.         } else {
  1154.             $extensionId Uuid::randomHex();
  1155.         }
  1156.         return $extensionId;
  1157.     }
  1158.     #[Route('/app/intro'name'frontend.econsor.app.storefront.intro')]
  1159.     public function intro(Request $requestSalesChannelContext $context): Response
  1160.     {
  1161.         if ($context->getCustomer() == null) {
  1162.             $url $this->router->generate('frontend.econsor.app.storefront.login');
  1163.             return $this->redirect($url);
  1164.         }
  1165.         $page $this->registrationAppPageLoader->load($request$context);
  1166.         return $this->renderStorefront('@Storefront/storefront/page/custom/intro/index.html.twig', [
  1167.             'page' => $page,
  1168.             'salesChannelContext' => $context,
  1169.             'request' => $request,
  1170.             'controllerName' => 'EconsorAppStorefrontController',
  1171.             'controllerAction' => 'intro'
  1172.         ]);
  1173.     }
  1174.     #[Route('/termCondition'name'frontend.term.condition')]
  1175.     public function termCondition(Request $requestSalesChannelContext $context): Response
  1176.     {
  1177.         $page $this->registrationAppPageLoader->load($request$context);
  1178.         return $this->renderStorefront('@Storefront/storefront/page/condition/index.html.twig', ['page' => $page]);
  1179.     }
  1180.     #[Route('/app/wishlist'name'frontend.wishlist.modal.note')]
  1181.     public function wishlist(Request $requestSalesChannelContext $context): Response
  1182.     {
  1183.         return new Response("WISHLIST");
  1184.     }
  1185.     #[Route('/upload/create'name'frontend.econsor.app.storefront.upload.create'methods: ['GET'], defaults: ['_loginRequired' => true])]
  1186.     public function create(Request $requestSalesChannelContext $context): Response
  1187.     {
  1188.         $page $this->profilePageLoader->load($request$context);
  1189. //        $this->hook(new AccountProfilePageLoadedHook($page, $context));
  1190.         return $this->renderStorefront('@Storefront/storefront/page/custom/uploads/index.html.twig', ['page' => $page]);
  1191.     }
  1192.     #[Route('/account/profile'name'frontend.account.profile.page'methods: ['GET'], defaults: ['_loginRequired' => true])]
  1193.     public function profileOverview(Request $requestSalesChannelContext $contextCustomerEntity $customer): Response
  1194.     {
  1195.         $page $this->profilePageLoader->load($request$context);
  1196.         $request->attributes->set('addressId'$customer->getDefaultBillingAddressId());
  1197.         $country $this->addressDetailPageLoader->load($request$context$customer);
  1198.         $address $this->homepageService->getCustomerAddress($context);
  1199.         $pendingCustomerBusinessName $this->customerBusinessNameService->getPendingCustomerBusinessName($customer$context->getContext());
  1200.         $customer $context->getCustomer();
  1201. //        $this->hook(new AccountProfilePageLoadedHook($page, $context));
  1202. //        $this->hook(new AddressDetailPageLoadedHook($country, $context));
  1203.         return $this->renderStorefront('@Storefront/storefront/page/account/profile/index.html.twig', [
  1204.             'page' => $page,
  1205.             'address' => $address,
  1206.             'customer' => $customer,
  1207.             'country' => $country,
  1208.             'pendingCustomerBusinessName' => $pendingCustomerBusinessName,
  1209.             'passwordFormViolation' => $request->get('passwordFormViolation'),
  1210.             'emailFormViolation' => $request->get('emailFormViolation'),
  1211.         ]);
  1212.     }
  1213.     #[Route('/account/profile'name'frontend.account.profile.save'methods: ['POST'], defaults: ['_loginRequired' => true])]
  1214.     public function saveProfile(Request $requestRequestDataBag $dataSalesChannelContext $contextCustomerEntity $customer): Response
  1215.     {
  1216.         $updateType $data->get('updateType');
  1217.         if (!empty($updateType)) {
  1218.             switch ($updateType) {
  1219.                 case 'interest':
  1220.                     {
  1221.                         $this->setInterests($context$request->request->all()['tag']);
  1222.                         return $this->redirectToRoute('frontend.account.home.page');
  1223.                     }
  1224.                     break;
  1225.                 case 'profile':
  1226.                     {
  1227.                         $businessNameUpdateId $data->get('businessNameUpdateId');
  1228.                         $currentBusinessName $data->get('currentBusinessName');
  1229.                         $businessName $data->get('businessName');
  1230.                         $birthDay = !empty($data->get('birthday')) ? date_parse_from_format('Y-m-d'$data->get('birthday')) : null;
  1231.                         $dataProfile = new RequestDataBag([
  1232.                             'firstName' => $data->get('firstName'),
  1233.                             'lastName' => $data->get('lastName'),
  1234.                             'birthdayDay' => $birthDay['day'],
  1235.                             'birthdayMonth' => $birthDay['month'],
  1236.                             'birthdayYear' => $birthDay['year'],
  1237.                         ]);
  1238.                         $customerData = [
  1239.                             'id' => $customer->getId(),
  1240.                             'email' => $data->get('email'),
  1241.                             'customFields' => [
  1242.                                 'phoneNumber' => $data->get('phoneNumber'),
  1243.                                 'countryCode' => $data->get('countryCode'),
  1244.                             ],
  1245.                         ];
  1246.                         if (!empty($data->get('email')) && $data->get('email') != $customer->getEmail()) {
  1247.                             $definition = new DataValidationDefinition('customer.email.update');
  1248.                             $definition->add('email', new CustomerEmailUnique(['context' => $context->getContext(), 'salesChannelContext' => $context]));
  1249.                             $validations $this->dataValidator->getViolations([
  1250.                                 'email' => $data->get('email'),
  1251.                             ], $definition);
  1252.                             //check for validation errors.
  1253.                             $isValid true;
  1254.                             if ($validations->count() > 0) {
  1255.                                 foreach ($validations as $validation) {
  1256.                                     if ($validation->getPropertyPath() == '/email') {
  1257.                                         $this->addFlash('danger'$validation->getMessage());
  1258.                                         $isValid false;
  1259.                                     }
  1260.                                 }
  1261.                             }
  1262.                             if ($isValid) {
  1263.                                 $customerData['email'] = $data->get('email');
  1264.                             }
  1265.                         }
  1266.                         $this->changeCustomerProfileRoute->change($dataProfile$context$customer);
  1267.                         $this->customerEntity->update([$customerData], $context->getContext());
  1268.                         if ($businessName != null && (empty($currentBusinessName) || $currentBusinessName != $businessName || !empty($businessNameUpdateId))) {
  1269.                             $this->customerBusinessNameService->store($customer$businessName$context->getContext());
  1270.                         }
  1271.                     }
  1272.                     break;
  1273.                 case 'avatar':
  1274.                     {
  1275.                         $avatarFile $request->files->get('avatar');
  1276.                         if (!empty($avatarFile)) {
  1277.                             $media $this->homepageService->customerAvatarUpload($avatarFile$customer$context);
  1278.                             if (!empty($media?->getId())) {
  1279.                                 $customerData = [
  1280.                                     'id' => $customer->getId(),
  1281.                                     'customFields' => [
  1282.                                         'avatar' => $media?->getId()
  1283.                                     ],
  1284.                                 ];
  1285.                                 $this->customerEntity->update([$customerData], $context->getContext());
  1286.                             }
  1287.                         }
  1288.                     }
  1289.                     break;
  1290.                 case 'newsletter':
  1291.                     {
  1292.                         $dataNewsletter = new RequestDataBag();
  1293.                         $dataNewsletter->set('email'$customer->getEmail());
  1294.                         $dataNewsletter->set('zipCode', ($customer->getDefaultShippingAddress() ? $customer->getDefaultShippingAddress()->getZipCode() : null));
  1295.                         $dataNewsletter->set('title'$customer->getTitle());
  1296.                         $dataNewsletter->set('storefrontUrl'$request->attributes->get(RequestTransformer::STOREFRONT_URL));
  1297.                         $dataNewsletter->set('city', ($customer->getDefaultShippingAddress() ? $customer->getDefaultShippingAddress()->getCity() : null));
  1298.                         $dataNewsletter->set('street', ($customer->getDefaultShippingAddress() ? $customer->getDefaultShippingAddress()->getStreet() : null));
  1299.                         if ($data->get('newsletter') == 'on') {
  1300.                             $dataNewsletter->set('option''subscribe');
  1301.                             $this->newsletterSubscribeRoute->subscribe(
  1302.                                 $dataNewsletter,
  1303.                                 $context,
  1304.                                 false
  1305.                             );
  1306.                             $this->setNewsletterFlag($customertrue$context);
  1307.                         } else if ($this->getNewsletterRecipient($dataNewsletter->get('email'), $context) !== null) {
  1308.                             $dataNewsletter->set('option''unsubscribe');
  1309.                             $this->newsletterUnsubscribeRoute->unsubscribe(
  1310.                                 $dataNewsletter,
  1311.                                 $context
  1312.                             );
  1313.                             $this->setNewsletterFlag($customerfalse$context);
  1314.                         }
  1315.                     }
  1316.                     break;
  1317.             }
  1318.         }
  1319.         return $this->redirectToRoute('frontend.account.profile.page');
  1320.     }
  1321.     private function setNewsletterFlag(CustomerEntity $customerbool $newsletterSalesChannelContext $context): void
  1322.     {
  1323.         $customer->setNewsletter($newsletter);
  1324.         $this->customerEntity->update(
  1325.             [
  1326.                 ['id' => $customer->getId(), 'newsletter' => $newsletter],
  1327.             ],
  1328.             $context->getContext()
  1329.         );
  1330.     }
  1331.     private function getNewsletterRecipient(string $emailSalesChannelContext $context): ?NewsletterRecipientEntity
  1332.     {
  1333.         $criteria = new Criteria();
  1334.         $criteria->addFilter(
  1335.             new MultiFilter(MultiFilter::CONNECTION_AND),
  1336.             new EqualsFilter('email'$email),
  1337.             new EqualsFilter('salesChannelId'$context->getSalesChannel()->getId())
  1338.         );
  1339.         $criteria->addAssociation('salutation');
  1340.         $criteria->setLimit(1);
  1341.         return $this->newsletterRecipientRepository
  1342.             ->search($criteria$context->getContext())
  1343.             ->first();
  1344.     }
  1345.     #[Route('/progress'name'frontend.account.progress'methods: ['GET'], defaults: ["_loginRequired" => true])]
  1346.     public function progress(Request $requestSalesChannelContext $contextCustomerEntity $customer): Response
  1347.     {
  1348.         $page $this->overviewPageLoader->load($request$context$customer);
  1349. //        $this->hook(new AccountOverviewPageLoadedHook($page, $context));
  1350.         $customerCustomFields $customer->getCustomFields();
  1351.         if (!empty($customerCustomFields['customerCountryCode']) && in_array(strtolower($customerCustomFields['customerCountryCode']), ['vn'])) {
  1352.             $invites $this->econsorAccountService->getInvites($customer$context);
  1353.             $creditBalance $this->econsorAccountService->getCustomerCreditBalance($customer$context);
  1354.             $view '@Storefront/storefront/page/progress/index-vn.html.twig';
  1355.             // if ($customerCustomFields['customerCountryCode'] == 'hk') {
  1356.             //     $view = '@Storefront/storefront/page/progress/index-en.html.twig';
  1357.             // }
  1358.             return $this->renderStorefront($view, [
  1359.                 'page' => $page,
  1360.                 'invites' => $invites,
  1361.                 'creditBalance' => $creditBalance,
  1362.                 'customer' => $customer,
  1363.             ]);
  1364.         }
  1365.         $order $this->orderPageLoader->load($request$context);
  1366. //        $this->hook(new AccountOrderPageLoadedHook($order, $context));
  1367.         $activeDeals $this->econsorAccountService->getActiveDeals($customer$context);
  1368.         $credits $this->econsorAccountService->getCredits($customer$context);
  1369.         $redeemedItems $this->econsorAccountService->getRedeemedItems($customer$context);
  1370.         $homepage $this->genericLoader->load($request$context);
  1371.         $test $homepage->getCmsPage()->getSections()->getBlocks()->getSlots('content')->getElements();
  1372.         foreach ($test as $key => $value) {
  1373.             if ($value->getType() == 'product-listing') {
  1374.                 $arr $value->getData();
  1375.             }
  1376.         }
  1377.         $listing $arr->getListing()->getElements();
  1378.         return $this->renderStorefront('@Storefront/storefront/page/progress/index.html.twig', ['page' => $page'order' => $order'activeDeals' => $activeDeals'credits' => $credits'redeemedItems' => $redeemedItems'listing' => $listing'customer' => $customer]);
  1379.     }
  1380.     #[Route('/shop4free'name'frontend.shop4free')]
  1381.     public function shop4freeCustom(Request $requestSalesChannelContext $contextContext $sale): Response
  1382.     {
  1383.         $page $this->genericLoader->load($request$context);
  1384.         $products $this->homepageService->loadProductsWithCreditOption($context);
  1385.         $customer $this->homepageService->getCustomerCredit($context$sale);
  1386.         return $this->renderStorefront('@Storefront/storefront/page/shop4free/index.html.twig', ['page' => $page'products' => $products'customer' => $customer]);
  1387.     }
  1388.      #[Route('/contact'name'frontend.contact')]
  1389.     public function contactCustom(Request $requestSalesChannelContext $contextContext $sale): Response
  1390.     {
  1391.         $page $this->genericLoader->load($request$context);
  1392.         $products $this->homepageService->loadProductsWithCreditOption($context);
  1393.         $customer $this->homepageService->getCustomerCredit($context$sale);
  1394.         return $this->renderStorefront('@Storefront/storefront/page/custom/contact/index.html.twig', ['page' => $page'products' => $products'customer' => $customer]);
  1395.     }
  1396.     //    #[Route('/account/recover', name: 'frontend.account.recover')]
  1397. //    public function accountRecover(Request $request, SalesChannelContext $context): Response
  1398. //    {
  1399. //        $page = $this->genericLoader->load($request, $context);
  1400. //
  1401. //        $this->hook(new NavigationPageLoadedHook($page, $context));
  1402. //        return $this->renderStorefront('@Storefront/storefront/page/account/profile/recover-password.html.twig');
  1403. //    }
  1404.     #[Route('/progress/credits/{creditId}'name'frontend.account.progress.credits.detail'methods: ['GET'], defaults: ["_loginRequired" => true])]
  1405.     public function creditDetail(string $creditIdRequest $requestSalesChannelContext $contextCustomerEntity $customer): Response
  1406.     {
  1407.         $page $this->overviewPageLoader->load($request$context$customer);
  1408. //        $this->hook(new AccountOverviewPageLoadedHook($page, $context));
  1409.         $order $this->orderPageLoader->load($request$context);
  1410. //        $this->hook(new AccountOrderPageLoadedHook($order, $context));
  1411.         $credit $this->econsorAccountService->getCreditDetail($creditId$context);
  1412.         return $this->renderStorefront('@Storefront/storefront/page/progress/credits/detail/index.html.twig', ['page' => $page'credit' => $credit]);
  1413.     }
  1414.     #[Route('/progress/redeemed-items/{itemId}'name'frontend.account.progress.redeemed-items.detail'methods: ['GET'], defaults: ["_loginRequired" => true])]
  1415.     public function redeemedItemDetail(string $itemIdRequest $requestSalesChannelContext $contextCustomerEntity $customer): Response
  1416.     {
  1417.         $page $this->overviewPageLoader->load($request$context$customer);
  1418. //        $this->hook(new AccountOverviewPageLoadedHook($page, $context));
  1419.         $order $this->orderPageLoader->load($request$context);
  1420. //        $this->hook(new AccountOrderPageLoadedHook($order, $context));
  1421.         $item $this->econsorAccountService->getRedeemedItemDetail($itemId$context);
  1422.         return $this->renderStorefront('@Storefront/storefront/page/progress/redeemded-items/detail/index.html.twig', ['page' => $page'item' => $item]);
  1423.     }
  1424. //     #[Route('/account/friend-invite', name: 'frontend.friend.invite', defaults: ["XmlHttpRequest" => true], methods: ['POST', 'GET'])]
  1425. //     public function reservationDeal(Request $request, SalesChannelContext $context): Response
  1426. //     {
  1427. //         if ($context->getCustomer() == null) {
  1428. //             $url = $this->router->generate('frontend.econsor.app.storefront.login');
  1429. //             return $this->redirect($url);
  1430. //         }
  1431. //         $violations = [];
  1432. //         $emails = $request->request->has('email') ? array_map('trim', array_filter($request->request->get('email'))) : [];
  1433. //         $schemeAndHost = $request->getSchemeAndHttpHost();
  1434. //         // Parse the scheme and host to get the hostname
  1435. //         $parsedUrl = parse_url($schemeAndHost);
  1436. //         $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
  1437. //         if ($request->isMethod('POST')) {
  1438. //             $validator = Validation::createValidator();
  1439. //             $constraintEmail = new Email([], 'The input {{ value }} is not an valid e-mail address.');
  1440. //             $constraintNotBlank = new NotBlank([], 'The e-mail field should not be blank.');
  1441. //             $constraintUnique = new Duplications();
  1442. //             $constraintNotEqualTo = new NotEqualTo($context->getCustomer()->getEmail(), null, "The e-mail should be of your friends.");
  1443. //             foreach ($emails as $id => $email) {
  1444. //                 if (!array_key_exists($id, $violations)) {
  1445. //                     $violations[$id] = [];
  1446. //                 }
  1447. //                 $violations[$id][] = $validator->validate($email, [$constraintEmail, $constraintNotBlank, $constraintUnique, $constraintNotEqualTo]);
  1448. //             }
  1449. //             $success = count($emails) > 0;
  1450. //             foreach ($violations as $violation) {
  1451. //                 if (count($violation[0]) > 0) {
  1452. //                     $success = false;
  1453. //                 }
  1454. //             }
  1455. //             if ($success && $this->discountCodeGenerator->canGenerate($context, DiscountCodeGenerator::SHARE_WITH_FRIENDS_CODE)) {
  1456. //                 $result = $this->discountCodeGenerator->generateCode($context, DiscountCodeGenerator::SHARE_WITH_FRIENDS_CODE, $emails, $url);
  1457. //                 if (array_key_exists('error', $result)) {
  1458. //                     //add flashes
  1459. //                     if (array_key_exists('flash', $result['error'])) {
  1460. // //                        $this->addFlash($result['error']['flash']['type'], $this->trans($result['error']['flash']['message']));
  1461. //                     }
  1462. //                     return $this->redirectToRoute('frontend.home.page');
  1463. //                 } else if (array_key_exists('success', $result)) {
  1464. //                     $customer = $context->getCustomer();
  1465. //                     $currentEmailsInvited = !empty($customer->getCustomFields()['emailsInvited']) ? (is_array($customer->getCustomFields()['emailsInvited']) ? $customer->getCustomFields()['emailsInvited'] : json_decode($customer->getCustomFields()['emailsInvited'])) : [];
  1466. //                     $currentEmailListInvited = !empty($customer->getCustomFields()['emailListInvited']) ? (is_array($customer->getCustomFields()['emailListInvited']) ? $customer->getCustomFields()['emailListInvited'] : json_decode($customer->getCustomFields()['emailListInvited'])) : [];
  1467. //                     $emailsListInvited = [];
  1468. //                     $emailsListInvite = [];
  1469. //                     foreach ($emails as $email) {
  1470. //                         if (!in_array($email, $currentEmailsInvited)) {
  1471. //                             $currentEmailListInvited[] = [
  1472. //                                 'email' => $email,
  1473. //                                 'inviteDate' => (new \DateTime())->getTimestamp(),
  1474. //                             ];
  1475. //                             $criteria = new Criteria();
  1476. //                             $criteria->addFilter(new EqualsFilter('email', $email));
  1477. //                             $this->inviteFriendHistory->create([
  1478. //                                 [
  1479. //                                     'email' => $email,
  1480. //                                     'customerId' => $context->getCustomerId(),
  1481. //                                 ]
  1482. //                             ], $context->getContext());
  1483. //                             $emailsListInvite[] = $email;
  1484. //                         } else {
  1485. //                             $emailsListInvited[] = $email;
  1486. //                         }
  1487. //                     }
  1488. //                     $uniqueEmails = array_unique(array_merge($currentEmailsInvited, $emailsListInvite));
  1489. //                     $this->customerEntity->update(
  1490. //                         [
  1491. //                             [
  1492. //                                 'id' => $context->getCustomerId(),
  1493. //                                 'customFields' => [
  1494. //                                     'emailsInvited' => $uniqueEmails,
  1495. //                                     'emailListInvited' => $currentEmailListInvited,
  1496. //                                 ],
  1497. //                             ]
  1498. //                         ],
  1499. //                         $context->getContext()
  1500. //                     );
  1501. //                     $wheelCriteria = new Criteria();
  1502. //                     $wheelCriteria->addFilter(new EqualsFilter('customerId', $context->getCustomerId()));
  1503. //                     $wheelCriteria->addFilter(new EqualsFilter('reason', '10 emails invited'));
  1504. //                     if (count($uniqueEmails) >= $this->luckyWheelTurnRepository->search($wheelCriteria, $context->getContext())->count() * 10) {
  1505. //                         $startOfWeek = (new \DateTime('monday this week'))->setTime(0, 0, 0);
  1506. //                         $endOfWeek = (new \DateTime('sunday this week'))->setTime(23, 59, 59);
  1507. //                         $result = $this->luckyWheelTurnRepository->search($wheelCriteria
  1508. //                             ->addFilter(new RangeFilter('createdAt', [
  1509. //                                 RangeFilter::GTE => $startOfWeek->format('Y-m-d H:i:s'),
  1510. //                                 RangeFilter::LTE => $endOfWeek->format('Y-m-d H:i:s')
  1511. //                             ])), $context->getContext());
  1512. //                         if ($result->count() == 0) {
  1513. //                             $this->luckyWheelTurnRepository->create([
  1514. //                                 [
  1515. //                                     'customerId' => $context->getCustomerId(),
  1516. //                                     'amountTotal' => 2,
  1517. //                                     'amountRemain' => 2,
  1518. //                                     'type' => 'awarded',
  1519. //                                     'reason' => '10 emails invited',
  1520. //                                 ]
  1521. //                             ], $context->getContext());
  1522. //                         }
  1523. //                     }
  1524. //                     //                    $records = $this->rewardService->getRewardRecordFromCustomer(
  1525. // //                        $context->getCustomer()->getId(),
  1526. // //                        "10 emails invited"
  1527. // //                    );
  1528. // //                    if ($records->count() === 0) {
  1529. // //                        $this->rewardService->createRewardPoint(
  1530. // //                            $context->getCustomer()->getId(),
  1531. // //                            null,
  1532. // //                            1000,
  1533. // //                            RewardPointType::AwardedPoint,
  1534. // //                            null,
  1535. // //                            null,
  1536. // //                            null,
  1537. // //                            "10 emails invited"
  1538. // //                        );
  1539. // //                    }
  1540. // //                    $this->addFlash('success', $this->trans('app.onboarding.inviteFriendsModal.emailSent'));
  1541. //                     return $this->renderStorefront('@Storefront/storefront/component/account/friend-invite.html.twig', [
  1542. //                         "page" => $this->registrationAppPageLoader->load($request, $context),
  1543. //                         'emailGenerated' => true,
  1544. //                         'emailsInvited' => $uniqueEmails,
  1545. //                         'emailsListInvited' => $emailsListInvited
  1546. //                     ]);
  1547. //                 }
  1548. //             }
  1549. //         }
  1550. //         return $this->renderStorefront('@Storefront/storefront/component/account/friend-invite.html.twig', [
  1551. //             'controllerName' => 'ReservationDealController',
  1552. //             'controllerAction' => 'reservationDeal',
  1553. //             'violations' => $violations,
  1554. //             'emails' => $emails,
  1555. //             "page" => $this->registrationAppPageLoader->load($request, $context)
  1556. //         ]);
  1557. //     }
  1558.     /**
  1559.      * @Route("/account/address/default-{type}/{addressId}", name="frontend.account.address.set-default-address", methods={"POST"}, defaults={"_loginRequired"=true})
  1560.      */
  1561.     public function switchDefaultAddress(Request $requeststring $typestring $addressIdSalesChannelContext $contextCustomerEntity $customer): mixed
  1562.     {
  1563.         if (!Uuid::isValid($addressId)) {
  1564.             throw new InvalidUuidException($addressId);
  1565.         }
  1566.         $success true;
  1567.         try {
  1568.             if ($type === self::ADDRESS_TYPE_SHIPPING) {
  1569.                 $this->accountService->setDefaultShippingAddress($addressId$context$customer);
  1570.             } elseif ($type === self::ADDRESS_TYPE_BILLING) {
  1571.                 $this->accountService->setDefaultBillingAddress($addressId$context$customer);
  1572.             } else {
  1573.                 $success false;
  1574.             }
  1575.         } catch (AddressNotFoundException $exception) {
  1576.             $success false;
  1577.         }
  1578.         if ($request->get('redirectTo') || $request->get('redirectTo') === '') {
  1579.             return $this->createActionResponse($request);
  1580.         }
  1581.         return new RedirectResponse(
  1582.             $this->generateUrl('frontend.account.address.page', ['changedDefaultAddress' => $success])
  1583.         );
  1584.     }
  1585.     /**
  1586.      * @Route("/widgets/account/address-book", name="frontend.account.addressbook", options={"seo"=true}, methods={"POST"}, defaults={"XmlHttpRequest"=true, "_loginRequired"=true, "_loginRequiredAllowGuest"=true})
  1587.      */
  1588.     public function addressBook(Request $requestRequestDataBag $dataBagSalesChannelContext $contextCustomerEntity $customer): Response
  1589.     {
  1590.         $viewData = new AddressEditorModalStruct();
  1591.         $params = [];
  1592.         try {
  1593.             $this->handleChangeableAddresses($viewData$dataBag$context$customer);
  1594.             $this->handleAddressCreation($viewData$dataBag$context$customer);
  1595.             $this->handleAddressSelection($viewData$dataBag$context$customer);
  1596.             $page $this->addressListingPageLoader->load($request$context$customer);
  1597. //            $this->hook(new AddressBookWidgetLoadedHook($page, $context));
  1598.             $viewData->setPage($page);
  1599.             if (Feature::isActive('FEATURE_NEXT_15957')) {
  1600.                 $this->handleCustomerVatIds($dataBag$context$customer);
  1601.             }
  1602.         } catch (ConstraintViolationException $formViolations) {
  1603.             $params['formViolations'] = $formViolations;
  1604.             $params['postedData'] = $dataBag->get('address');
  1605.         } catch (\Exception $exception) {
  1606.             $viewData->setSuccess(false);
  1607.             $viewData->setMessages([
  1608.                 'type' => self::DANGER,
  1609.                 'text' => $this->trans('error.message-default'),
  1610.             ]);
  1611.         }
  1612.         if ($request->get('redirectTo') || $request->get('forwardTo')) {
  1613.             return $this->createActionResponse($request);
  1614.         }
  1615.         $params array_merge($params$viewData->getVars());
  1616.         $response $this->renderStorefront(
  1617.             '@Storefront/storefront/component/address/address-editor-modal.html.twig',
  1618.             $params
  1619.         );
  1620.         $response->headers->set('x-robots-tag''noindex');
  1621.         return $response;
  1622.     }
  1623.     private function handleAddressCreation(
  1624.         AddressEditorModalStruct $viewData,
  1625.         RequestDataBag           $dataBag,
  1626.         SalesChannelContext      $context,
  1627.         CustomerEntity           $customer
  1628.     ): void
  1629.     {
  1630.         /** @var DataBag|null $addressData */
  1631.         $addressData $dataBag->get('address');
  1632.         if ($addressData === null) {
  1633.             return;
  1634.         }
  1635.         $response $this->updateAddressRoute->upsert(
  1636.             $addressData->get('id'),
  1637.             $addressData->toRequestDataBag(),
  1638.             $context,
  1639.             $customer
  1640.         );
  1641.         $addressId $response->getAddress()->getId();
  1642.         $addressType null;
  1643.         if ($viewData->isChangeBilling()) {
  1644.             $addressType self::ADDRESS_TYPE_BILLING;
  1645.         } elseif ($viewData->isChangeShipping()) {
  1646.             $addressType self::ADDRESS_TYPE_SHIPPING;
  1647.         }
  1648.         // prepare data to set newly created address as customers default
  1649.         if ($addressType) {
  1650.             $dataBag->set('selectAddress', new RequestDataBag([
  1651.                 'id' => $addressId,
  1652.                 'type' => $addressType,
  1653.                 'isChangeBilling' => $viewData->isChangeBilling(),
  1654.                 'isChangeShipping' => $viewData->isChangeShipping(),
  1655.             ]));
  1656.         }
  1657.         $viewData->setAddressId($addressId);
  1658.         $viewData->setSuccess(true);
  1659.         $viewData->setMessages(['type' => 'success''text' => $this->trans('account.addressSaved')]);
  1660.     }
  1661.     private function handleChangeableAddresses(
  1662.         AddressEditorModalStruct $viewData,
  1663.         RequestDataBag           $dataBag,
  1664.         SalesChannelContext      $context,
  1665.         CustomerEntity           $customer
  1666.     ): void
  1667.     {
  1668.         $changeableAddresses $dataBag->get('changeableAddresses');
  1669.         if ($changeableAddresses === null) {
  1670.             return;
  1671.         }
  1672.         $viewData->setChangeShipping((bool)$changeableAddresses->get('changeShipping'));
  1673.         $viewData->setChangeBilling((bool)$changeableAddresses->get('changeBilling'));
  1674.         $addressId $dataBag->get('id');
  1675.         if (!$addressId) {
  1676.             return;
  1677.         }
  1678.         $viewData->setAddress($this->getById($addressId$context$customer));
  1679.     }
  1680.     /**
  1681.      * @throws CustomerNotLoggedInException
  1682.      * @throws InvalidUuidException
  1683.      */
  1684.     private function handleAddressSelection(
  1685.         AddressEditorModalStruct $viewData,
  1686.         RequestDataBag           $dataBag,
  1687.         SalesChannelContext      $context,
  1688.         CustomerEntity           $customer
  1689.     ): void
  1690.     {
  1691.         $selectedAddress $dataBag->get('selectAddress');
  1692.         if ($selectedAddress === null) {
  1693.             return;
  1694.         }
  1695.         $isChangeBilling $selectedAddress->get('isChangeBilling');
  1696.         $isChangeShipping $selectedAddress->get('isChangeShipping');
  1697.         $addressType $selectedAddress->get('type');
  1698.         $addressId $selectedAddress->get('id');
  1699.         if (!Uuid::isValid($addressId)) {
  1700.             throw new InvalidUuidException($addressId);
  1701.         }
  1702.         $success true;
  1703.         try {
  1704.             if ($isChangeShipping) {
  1705.                 $address $this->getById($addressId$context$customer);
  1706.                 $customer->setDefaultShippingAddress($address);
  1707.                 $this->accountService->setDefaultShippingAddress($addressId$context$customer);
  1708.             }
  1709.             if ($isChangeBilling) {
  1710.                 $address $this->getById($addressId$context$customer);
  1711.                 $customer->setDefaultBillingAddress($address);
  1712.                 $this->accountService->setDefaultBillingAddress($addressId$context$customer);
  1713.             }
  1714.             if (!$isChangeBilling && !$isChangeShipping) {
  1715.                 $success false;
  1716.             }
  1717.         } catch (AddressNotFoundException $exception) {
  1718.             $success false;
  1719.         }
  1720. //        if ($success) {
  1721. //            $this->addFlash(self::SUCCESS, $this->trans('account.addressDefaultChanged'));
  1722. //        } else {
  1723. //            $this->addFlash(self::DANGER, $this->trans('account.addressDefaultNotChanged'));
  1724. //        }
  1725.         $viewData->setSuccess($success);
  1726.     }
  1727.     private function getById(string $addressIdSalesChannelContext $contextCustomerEntity $customer): CustomerAddressEntity
  1728.     {
  1729.         if (!Uuid::isValid($addressId)) {
  1730.             throw new InvalidUuidException($addressId);
  1731.         }
  1732.         $criteria = new Criteria();
  1733.         $criteria->addFilter(new EqualsFilter('id'$addressId));
  1734.         $criteria->addFilter(new EqualsFilter('customerId'$customer->getId()));
  1735.         $address $this->listAddressRoute->load($criteria$context$customer)->getAddressCollection()->get($addressId);
  1736.         if (!$address) {
  1737.             throw new AddressNotFoundException($addressId);
  1738.         }
  1739.         return $address;
  1740.     }
  1741.     private function handleCustomerVatIds(RequestDataBag $dataBagSalesChannelContext $contextCustomerEntity $customer): void
  1742.     {
  1743.         if (!$dataBag->has('vatIds')) {
  1744.             return;
  1745.         }
  1746.         $newVatIds $dataBag->get('vatIds')->all();
  1747.         $oldVatIds $customer->getVatIds() ?? [];
  1748.         if (!array_diff($newVatIds$oldVatIds) && !array_diff($oldVatIds$newVatIds)) {
  1749.             return;
  1750.         }
  1751.         $dataCustomer CustomerTransformer::transform($customer);
  1752.         $dataCustomer['vatIds'] = $newVatIds;
  1753.         $dataCustomer['accountType'] = $customer->getCompany() === null CustomerEntity::ACCOUNT_TYPE_PRIVATE CustomerEntity::ACCOUNT_TYPE_BUSINESS;
  1754.         $newDataBag = new RequestDataBag($dataCustomer);
  1755.         $this->updateCustomerProfileRoute->change($newDataBag$context$customer);
  1756.     }
  1757.     /**
  1758.      * @Route("/account/accept-cookie", name="frontend.account.accept-cookie", methods={"POST"}, defaults={"XmlHttpRequest"=true})
  1759.      */
  1760.     public function saveAcceptCookie(Request $requestSalesChannelContext $context): mixed
  1761.     {
  1762.         $isAcceptCookie $request->get('acceptCookie'false);
  1763.         $ip $request->getClientIp();
  1764.         $devices $request->headers->get('user-agent');
  1765.         $acceptCookieId $request->get('acceptCookieId'0);
  1766.         if ($customer $context->getCustomer()) {
  1767.             $criteria = new Criteria();
  1768.             $criteria->addFilter(new EqualsFilter('customerId'$customer->getId()));
  1769.         } else {
  1770.             $criteria = new Criteria();
  1771.             $criteria->addFilter(new EqualsFilter('acceptCookieId'$acceptCookieId));
  1772.             $criteria->addFilter(new NotFilter(NotFilter::CONNECTION_AND, [
  1773.                 new EqualsFilter('acceptCookieId'0)
  1774.             ]));
  1775.         }
  1776.         $customerAcceptCookie $this->customerAcceptCookieRepository->search($criteria$context->getContext())->first();
  1777.         if (!$customerAcceptCookie) {
  1778.             $dataCreate = [
  1779.                 'id' => Uuid::randomHex(),
  1780.                 'customerId' => null,
  1781.                 'acceptCookie' => $isAcceptCookie,
  1782.                 'acceptCookieIp' => $ip,
  1783.                 'acceptCookieId' => $acceptCookieId,
  1784.                 'acceptCookieDevice' => $devices,
  1785.                 'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
  1786.             ];
  1787.             if ($customer) {
  1788.                 $dataCreate['customerId'] = $customer->getId();
  1789.                 $this->customerEntity->update([
  1790.                     [
  1791.                         'id' => $customer->getId(),
  1792.                         'customFields' => [
  1793.                             'customerAcceptCookie' => $isAcceptCookie
  1794.                         ],
  1795.                     ]
  1796.                 ], $context->getContext());
  1797.             }
  1798.             $this->customerAcceptCookieRepository->create([
  1799.                 $dataCreate
  1800.             ], $context->getContext());
  1801.             return new JsonResponse([
  1802.                 'success' => true,
  1803.                 'message' => 'Accept Cookie Saved Successfully!'
  1804.             ], 200);
  1805.         }
  1806.         return new JsonResponse([
  1807.             'success' => true,
  1808.             'message' => 'Cookies already accepted!'
  1809.         ], 200);
  1810.     }
  1811.     #[Route('/video-star-ranking'name'frontend.daily.ranking'methods: ['GET'])]
  1812.     public function dailyRanking(Request $requestSalesChannelContext $context): Response
  1813.     {
  1814.         $page $this->genericLoader->load($request$context);
  1815.         $topThreeCustomer $this->rankingService->getTopThreeCustomerByLike($request$context);
  1816.         $topVideoOfCurrentUser $this->rankingService->getTopVideoOfCurrentUser($request$context);
  1817.         return $this->renderStorefront('@Storefront/storefront/page/custom/daily-ranking/index.html.twig', ['page' => $page'topThreeCustomer' => $topThreeCustomer'topVideoOfCurrentUser' => $topVideoOfCurrentUser]);
  1818.     }
  1819.     #[Route('/sharing-center'name'frontend.sharing.center'methods: ['GET'])]
  1820.     public function sharingCenter(Request $requestSalesChannelContext $context): Response
  1821.     {
  1822.         $page $this->genericLoader->load($request$context);
  1823.         $customer $context->getCustomer();
  1824.         $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"uploaded");
  1825.         return $this->renderStorefront('@Storefront/storefront/page/custom/sharing-center/index.html.twig', ['page' => $page'customer' => $customer'dataStream' => $dataStream'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  1826.     }
  1827.     #[Route('/short-link'name'frontend.short.link'defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
  1828.     public function renderShortLink(Request $requestSalesChannelContext $context)
  1829.     {
  1830.         $url $this->aiStreamService->createShortLink($request$context);
  1831.         return new JsonResponse(['data' => $url]);
  1832.     }
  1833.     #[Route('/checkout/subscription/{id?}'name'frontend.checkout.subscription'defaults: ['XmlHttpRequest' => true'_loginRequired' => true,], methods: ['GET'])]
  1834.     public function subscriptionPage(Request $requestSalesChannelContext $contextRequestDataBag $dataBagstring $id null): Response
  1835.     {
  1836.         $id $id ?? $request->query->get('productId');
  1837.         if ($id === null) {
  1838.             return $this->redirectToRoute('frontend.home.page');
  1839.         }
  1840.         $criteria = new Criteria();
  1841.         $criteria->addFilter(
  1842.             new EqualsFilter('customerId'$context->getCustomer()->getId()),
  1843.             new EqualsFilter('productId'$id),
  1844.             new EqualsFilter('status''active')
  1845.         );
  1846.         $subscription $this->subscriptionRepository->search($criteria$context->getContext())->first();
  1847.         if ($subscription) {
  1848.             return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'error-create-order']);
  1849.         }
  1850.         $context->getCustomer()->setAddresses($this->customerAddressRepository->search((new Criteria())->addFilter(new EqualsFilter('customerId'$context->getCustomerId())), $context->getContext())->getEntities());
  1851.         $criteria = new Criteria([$id]);
  1852.         $criteria->addAssociation('cover');
  1853.         $criteria->addAssociation('visibilities');
  1854.         $product $this->salesChannelProductRepository->search($criteria$context)->first();
  1855.         if (!$product || !$product->getCustomFields()['isBuyProductSubscription'] || empty($product->getCustomFields()['planId'])) {
  1856.             throw new \RuntimeException('Product not found for the current Sales Channel!');
  1857.         }
  1858.         $productId $product->getId();
  1859.         $token NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME "-" $context->getToken();
  1860.         $cart $this->cartService->getCart($token$contextNuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME);
  1861.         if (!empty($cart->getLineItems())) {
  1862.             foreach ($cart->getLineItems() as $item) {
  1863.                 $item->setRemovable(true);
  1864.                 $this->cartService->remove($cart$item->getId(), $context);
  1865.             }
  1866.         }
  1867.         $lineItem = new LineItem(
  1868.             $productId,
  1869.             LineItem::PRODUCT_LINE_ITEM_TYPE,
  1870.             $productId,
  1871.         );
  1872.         $lineItem->setLabel($product->getName());
  1873.         $lineItem->setPayloadValue('productId'$product->getId());
  1874.         $lineItem->setPayloadValue('planId'$product->getCustomFields()['planId']);
  1875.         $lineItem->setRemovable(true);
  1876.         $this->cartService->add($cart, [$lineItem], $context);
  1877.         // $cart = $this->cartService->createNew(NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME . "-" . $context->getToken(), NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME . "-" . $context->getToken());
  1878.         // $cart = $this->cartService->add($cart, [$lineItem], $context);
  1879.         $cart $this->cartService->recalculate($cart$context);
  1880.         $page $this->overviewPageLoader->load($request$context$context->getCustomer());
  1881.         return $this->renderStorefront('@Storefront/storefront/page/custom/subscription-checkout/index.html.twig', [
  1882.             'product' => $product,
  1883.             'cart' => $cart,
  1884.             'page' => $page,
  1885.         ]);
  1886.     }
  1887.     #[Route('/order/subscription'name'frontend.order.subscription'defaults: ['XmlHttpRequest' => true'_loginRequired' => true,], methods: ['POST'])]
  1888.     public function createOrderSubscription(RequestDataBag $requestSalesChannelContext $context)
  1889.     {
  1890.         $isConfirm = (bool)$request->get('isConfirm');
  1891.         if (!$isConfirm) {
  1892.             return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'error']);
  1893.         }
  1894.         $token NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME "-" $context->getToken();
  1895.         $paymentMethod $this->getSubscriptionPaymentMethod($context->getContext());
  1896.         $context->assign([
  1897.             'paymentMethod' => $paymentMethod,
  1898.         ]);
  1899.         $cart $this->cartService->getCart($token$contextNuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME);
  1900.         $lineItem $cart->getLineItems()->first();
  1901.         $shippingAddressId $context->getCustomer()->getDefaultShippingAddressId();
  1902.         $request->set('paymentMethodId'$paymentMethod->getId());
  1903.         $request->set('shippingAddressId'$shippingAddressId);
  1904.         $request->set('planId'$lineItem->getPayload()['planId']);
  1905.         $orderId $this->cartService->order($cart$context$request);
  1906.         $criteria = new Criteria();
  1907.         $criteria->addFilter(new EqualsFilter('id'$orderId));
  1908.         $criteria->addAssociation('transactions');
  1909.         $criteria->addAssociation('lineItems');
  1910.         $criteria->addAssociation('deliveries.shippingOrderAddress');
  1911.         $criteria->addAssociation('addresses');
  1912.         $order $this->orderRepository->search($criteria$context->getContext())->first();
  1913.         // Update order customFields to mark it as a subscription order
  1914.         $customFields $order->getCustomFields() ?? [];
  1915.         $customFields['isOrderSubscription'] = true;
  1916.         $this->orderRepository->update([
  1917.             [
  1918.                 'id' => $orderId,
  1919.                 'customFields' => $customFields
  1920.             ]
  1921.         ], $context->getContext());
  1922.         // Get the first line item as the subscription product
  1923.         $lineItem $order->getLineItems()->first();
  1924.         if (!$lineItem) {
  1925.             throw new \Exception('No line items found in order');
  1926.         }
  1927.         $productId $lineItem->getProductId();
  1928.         $customerId $context->getCustomerId();
  1929.         if (!$customerId) {
  1930.             throw new \Exception('Customer not logged in');
  1931.         }
  1932.         $criteria = (new Criteria())
  1933.             ->addFilter(new EqualsFilter('customerId'$context->getCustomerId()))
  1934.             ->addSorting(new FieldSorting('createdAt'FieldSorting::DESCENDING));
  1935.         $addressId $this->customerAddressRepository
  1936.             ->search($criteria$context->getContext())
  1937.             ->first()->getId();
  1938.         // Get the Nuvei token from the TransactionSubscription
  1939.         if (!$token) {
  1940.             throw new \Exception('No payment token found in subscription transaction');
  1941.         }
  1942.         // Calculate next payment date (30 days from now)
  1943.         $nextPaymentDate = (new \DateTime())->modify('+1 month');
  1944.         $now = new \DateTime();
  1945.         $subscriptionActive $this->findSubscription($customerId$productId'active'$context->getContext());
  1946.         if ($subscriptionActive) {
  1947.             return $this->redirectToRoute('frontend.account.order-subscription', [
  1948.                 'status' => 'error-create-order'
  1949.             ]);
  1950.         }
  1951.         $subscriptionPending $this->findSubscription($customerId$productId'pending'$context->getContext());
  1952.         if ($subscriptionPending) {
  1953.             $this->subscriptionRepository->update([[
  1954.                 'id' => $subscriptionPending->getId(),
  1955.                 'status' => 'cancelled'
  1956.             ]], $context->getContext());
  1957.         }
  1958.         $id Uuid::randomHex();
  1959.         $this->subscriptionRepository->create([[
  1960.             'id' => $id,
  1961.             'customerId' => $customerId,
  1962.             'productId' => $productId,
  1963.             'addressId' => $addressId,
  1964.             'nextPaymentDate' => $nextPaymentDate,
  1965.             'status' => 'pending',
  1966.             'createdAt' => (new \DateTime())->format('Y-m-d H:i:s')
  1967.         ]], $context->getContext());
  1968.         // Create subscription order
  1969.         $this->subscriptionOrderRepository->create([
  1970.             [
  1971.                 'subscriptionId' => $id,
  1972.                 'orderId' => $orderId,
  1973.                 'createdAt' => $now->format('Y-m-d H:i:s')
  1974.             ]
  1975.         ], $context->getContext());
  1976.         // Use the standard Shopware payment process
  1977.         // The finishUrl will be used after the payment is completed and the finalize method is called
  1978.         // $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]);
  1979.         $finishUrl $this->generateUrl('frontend.account.order-subscription.finish', ['id' => $orderId]);
  1980.         $errorUrl $this->generateUrl('frontend.account.order-subscription');
  1981.         try {
  1982.             $this->logger->info('Starting payment process for subscription order', [
  1983.                 'orderId' => $orderId,
  1984.                 'customerId' => $context->getCustomerId()
  1985.             ]);
  1986.             // This will generate a payment token and redirect to the payment gateway
  1987.             // The payment gateway will redirect back to /payment/finalize-transaction with the token
  1988.             // which will call the finalize method in NuveiSubscriptionPaymentHandler
  1989.             $response $this->paymentService->handlePaymentByOrder($orderId$request$context$finishUrl$errorUrl);
  1990.             if ($response) {
  1991.                 $this->logger->info('Redirecting to payment gateway', [
  1992.                     'orderId' => $orderId,
  1993.                     'redirectUrl' => $response->getTargetUrl()
  1994.                 ]);
  1995.                 return $response;
  1996.             }
  1997.         } catch (\Exception $e) {
  1998.             $this->logger->error('Error processing subscription payment', [
  1999.                 'orderId' => $orderId,
  2000.                 'error' => $e->getMessage(),
  2001.                 'trace' => $e->getTraceAsString()
  2002.             ]);
  2003.             return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'error']);
  2004.         }
  2005.         return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'success']);
  2006.     }
  2007.     private function findSubscription(string $customerIdstring $productIdstring $statusContext $context)
  2008.     {
  2009.         $criteria = new Criteria();
  2010.         $criteria->addFilter(
  2011.             new EqualsFilter('customerId'$customerId),
  2012.             new EqualsFilter('productId'$productId),
  2013.             new EqualsFilter('status'$status)
  2014.         );
  2015.         return $this->subscriptionRepository->search($criteria$context)->first();
  2016.     }
  2017.     protected function getSubscriptionPaymentMethod(Context $context): PaymentMethodEntity|null
  2018.     {
  2019.         $criteria = new Criteria();
  2020.         $criteria->addFilter(new EqualsFilter('handlerIdentifier'NuveiSubscriptionPaymentHandler::class));
  2021.         return $this->paymentMethodRepository->search($criteria$context)->getEntities()->first();
  2022.     }
  2023.     #[Route('/account/order-subscription'name'frontend.account.order-subscription'defaults: ['_loginRequired' => true], methods: ['GET'])]
  2024.     public function accountMyOrderSubscription(Request $requestSalesChannelContext $context): Response
  2025.     {
  2026.         $errorCode $request->query->get('error-code');
  2027.         if ($errorCode !== null) {
  2028.             return $this->redirectToRoute('frontend.account.order-subscription');
  2029.         }
  2030.         $page $this->navigationPageLoader->load($request$context);
  2031.         $response $this->nuveiSubscriptionController->searchSubscriptionsByCustomer($request$context);
  2032.         $data json_decode($response->getContent(), true);
  2033.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/order-subscription/index.html.twig', [
  2034.             'page' => $page,
  2035.             'subscriptions' => $data['subscriptions']
  2036.         ]);
  2037.     }
  2038.     #[Route('/account/order-subscription/{id}'name'frontend.account.order-subscription.detail'defaults: ['_loginRequired' => true], methods: ['GET'])]
  2039.     public function accountMyOrderSubscriptionDetail(String $idRequest $requestSalesChannelContext $context): Response
  2040.     {
  2041.         $page $this->navigationPageLoader->load($request$context);
  2042.         $response $this->nuveiSubscriptionController->searchOrdersByCustomerSubscription($id$context);
  2043.         if ($response['error'] != ){
  2044.             $subscriptionOrders = [];
  2045.         } else {
  2046.             $subscriptionOrders $response['subscriptionOrders'];
  2047.         }
  2048.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/subscription-detail/index.html.twig', [
  2049.             'page' => $page,
  2050.             'subscriptionOrders' => $subscriptionOrders
  2051.         ]);
  2052.     }
  2053.     #[Route('/account/order-subscription/finish/{id}'name'frontend.account.order-subscription.finish'defaults: ['_loginRequired' => true], methods: ['GET'])]
  2054.     public function accountMyOrderSubscriptionFinish(String $idRequest $requestSalesChannelContext $context): Response
  2055.     {
  2056.         if ($id === null) {
  2057.             return $this->redirectToRoute('frontend.home.page');
  2058.         }
  2059.         $criteria = new Criteria();
  2060.         $criteria->addFilter(new EqualsFilter('id'$id));
  2061.         $criteria->addAssociation('transactions.paymentMethod');
  2062.         $criteria->addAssociation('lineItems.cover');
  2063.         $criteria->addAssociation('deliveries.shippingOrderAddress');
  2064.         $criteria->addAssociation('deliveries.stateId');
  2065.         $criteria->addAssociation('addresses');
  2066.         $order $this->orderRepository->search($criteria$context->getContext())->first();
  2067.         $productId $order->getLineItems()->first()->getproductId();
  2068.         $customerId $context->getCustomer()?->getId();
  2069.         // Check if the customer has an active subscription for this product
  2070.         $hasActiveSubscription $this->subscriptionService->hasActiveSubscription(
  2071.             $customerId,
  2072.             $productId,
  2073.             $context
  2074.         );
  2075.         $page $this->navigationPageLoader->load($request$context);
  2076.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/subscription-finish/index.html.twig', [
  2077.             'page' => $page,
  2078.             'order' => $order,
  2079.             'hasActiveSubscription' => $hasActiveSubscription
  2080.         ]);
  2081.     }
  2082.     #[Route('/search/orders-by-customer-subscription'name'frontend.search.orders_by_customer_subscription'options: ["seo" => false], defaults: ['_loginRequired' => true'XmlHttpRequest' => true], methods: ['POST'])]
  2083.     public function accountMyProductSubscription(Request $requestSalesChannelContext $context): Response
  2084.     {
  2085.         $status $request->request->get('status');
  2086.         $isMobile $request->request->get('isMobile');
  2087.         $isProduct $request->request->get('isProduct');
  2088.         // $page = $this->navigationPageLoader->load($request, $context);
  2089.         $response $this->nuveiSubscriptionController->searchSubscriptionsByCustomer($request$context);
  2090.         $data json_decode($response->getContent(), true);
  2091.        return $this->renderStorefront('@Storefront/storefront/page/custom/account/order-subscription/list-product.html.twig', [
  2092.            'subscriptions' => $data['subscriptions'],
  2093.             'isProduct' => $isProduct,
  2094.             'isMobile' => $isMobile,
  2095.             'status' => $status
  2096.         ]);
  2097.     }
  2098.     #[Route('/mobile/order/finish'name'frontend.mobile.order.finish'methods: ['GET'])]
  2099.     public function mobileOrderFinish(Request $requestSalesChannelContext $context): Response
  2100.     {
  2101.         $page $this->navigationPageLoader->load($request$context);
  2102.         $customer $context->getCustomer();
  2103.         $this->hook(new NavigationPageLoadedHook($page$context));
  2104.         return $this->renderStorefront('@Storefront/storefront/page/checkout/finish/mobile/index.html.twig', ['page' => $page'customer' => $customer'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
  2105.     }
  2106.     #[Route('/join-app'name'frontend.join.app'methods: ['GET'])]
  2107.     public function joinApp(Request $requestSalesChannelContext $context): Response
  2108.     {
  2109.         $page $this->genericLoader->load($request$context);
  2110.         return $this->renderStorefront('@Storefront/storefront/page/custom/account/join-app/index.html.twig', ['page' => $page]);
  2111.     }
  2112.     #[Route('/save4more-home'name'frontend.save4more.home'methods: ['GET'])]
  2113.     public function save4more(Request $requestSalesChannelContext $context)
  2114.     {
  2115.         $productCriteria = new Criteria();
  2116.         $productCriteria->addFilter(new EqualsFilter('customFields.isBuyProductSubscription'true));
  2117.         $productResult $this->salesChannelProductRepository->search($productCriteria$context)->first();
  2118.         $productId $productResult $productResult->getId() : null;
  2119.         $page $this->navigationPageLoader->load($request$context);
  2120.         $pageNumber 1;
  2121.         $this->hook(new NavigationPageLoadedHook($page$context));
  2122.         $dataStream $this->postService->fetchStream($context->getCustomer()?->getId(), $context"details"$productIdnull$pageNumber);
  2123.         return $this->renderStorefront('@Storefront/storefront/page/custom/save-4-more/index.html.twig', ['page' => $page'dataStream' => $dataStream'productResult' => $productResult]);
  2124.     }
  2125.     #[Route('/save4more-terms-condition'name'frontend.save4more.terms'methods: ['GET'])]
  2126.     public function save4moreTerms(Request $requestSalesChannelContext $context)
  2127.     {
  2128.         $page $this->navigationPageLoader->load($request$context);
  2129.         $this->hook(new NavigationPageLoadedHook($page$context));
  2130.         return $this->renderStorefront('@Storefront/storefront/page/custom/save-4-more/terms/index.html.twig', ['page' => $page]);
  2131.     }
  2132.     // Storefront API: Download invoice PDF for an order. If missing, create it first.
  2133.     // Only accessible by the owning customer; requires login (guest allowed).
  2134.     #[Route(
  2135.         path'/account/order/{orderId}/invoice',
  2136.         name'frontend.account.order.invoice.download',
  2137.         methods: ['POST'],
  2138.         defaults: ['_routeScope' => ['storefront'], '_loginRequired' => true'_loginRequiredAllowGuest' => true"XmlHttpRequest" => true]
  2139.     )]
  2140.     public function downloadInvoice(Request $requeststring $orderIdSalesChannelContext $salesChannelContext): Response
  2141.     {
  2142.         $customer $salesChannelContext->getCustomer();
  2143.         if ($customer === null) {
  2144.             return new Response('Not Login!'Response::HTTP_UNAUTHORIZED);
  2145.         }
  2146.         $context $salesChannelContext->getContext();
  2147.         // Try to find latest invoice document for this order
  2148.         $docCriteria = new Criteria();
  2149.         $docCriteria->addFilter(new EqualsFilter('orderId'$orderId));
  2150.         $docCriteria->addFilter(new EqualsFilter('documentType.technicalName''invoice'));
  2151.         $docCriteria->addAssociation('documentType');
  2152.         $docCriteria->setLimit(1);
  2153.         // Sort by createdAt DESC (newest first)
  2154.         $docCriteria->addSorting(new \Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting('createdAt'\Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting::DESCENDING));
  2155.         $existing $this->documentRepository->search($docCriteria$context)->first();
  2156.         if ($existing) {
  2157.             $documentId $existing->getId();
  2158.             $deepLinkCode $existing->getDeepLinkCode();
  2159.         } else {
  2160.             // Create a new invoice document
  2161.             // Avoid duplicated media filename by providing a unique filename
  2162.             $uniqueSuffix = (new \DateTimeImmutable())->format('Ymd_His');
  2163.             $fileName sprintf('invoice_%s_%s.pdf'$orderId$uniqueSuffix);
  2164.             $operation = new DocumentGenerateOperation(
  2165.                 $orderId,
  2166.                 FileTypes::PDF,
  2167.                 [
  2168.                     'fileName' => $fileName,
  2169.                 ],
  2170.                 null,
  2171.                 false
  2172.             );
  2173.             $result $this->documentGenerator->generate('invoice', [$orderId => $operation], $context);
  2174.             $success $result->getSuccess();
  2175.             if ($success->count() === 0) {
  2176.                 return new Response('Cannot generate invoice'Response::HTTP_INTERNAL_SERVER_ERROR);
  2177.             }
  2178.             /** @var \Shopware\Core\Checkout\Document\DocumentIdStruct $document */
  2179.             $document $success->first();
  2180.             $deepLinkCode $document->getDeepLinkCode();
  2181.             $documentId   $document->getId();
  2182.         }
  2183.         // Build URL to built-in storefront document download route to stream the PDF
  2184.         $url $this->generateUrl('frontend.account.order.single.document', [
  2185.             'documentId' => $documentId,
  2186.             'deepLinkCode' => $deepLinkCode,
  2187.             'download' => 1,
  2188.         ]);
  2189.         // Return JSON response as requested
  2190.         return new JsonResponse([
  2191.             'url' => $url,
  2192.             'success' => true,
  2193.         ], Response::HTTP_OK);
  2194.     }
  2195. }