src/Controller/StripeController.php line 408

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Services\UserService;
  4. use Carbon\Carbon;
  5. use GeoIp2\Record\Location;
  6. use Pimcore\Log\ApplicationLogger;
  7. use Pimcore\Log\Simple;
  8. use Pimcore\Model\DataObject\LocationSource;
  9. use Pimcore\Model\DataObject\Data\ElementMetadata;
  10. use Pimcore\Model\DataObject\MemberStripeAccount;
  11. use Pimcore\Model\DataObject\MembersUser;
  12. use Pimcore\Model\DataObject\Price;
  13. use Pimcore\Model\DataObject\ProductDescription;
  14. use Pimcore\Model\DataObject\Promocode;
  15. use Pimcore\Model\DataObject\PromoRules;
  16. use Pimcore\Model\DataObject\StripeAccount;
  17. use Pimcore\Model\WebsiteSetting;
  18. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  19. use Symfony\Component\HttpFoundation\Response;
  20. use Symfony\Component\Routing\Annotation\Route;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Contracts\Cache\CacheInterface;
  23. use Symfony\Contracts\Cache\ItemInterface;
  24. use Stripe;
  25. use Symfony\Component\Security\Http\Attribute\CurrentUser;
  26. class StripeController extends AbstractController
  27. {
  28.     private CacheInterface $cache;
  29.     public function __construct(CacheInterface $cache)
  30.     {
  31.         $this->cache $cache;
  32.     }
  33.     protected function validateAdminRole($user): ?Response
  34.     {
  35.         if ($user === null) {
  36.             return new Response(json_encode([
  37.                 'success' => false,
  38.                 'data' => 'User not authenticated'
  39.             ]), Response::HTTP_UNAUTHORIZED);
  40.         }
  41.         
  42.         $groups $user->getGroups();
  43.         if (empty($groups) || $groups[0]->getName() !== 'Admin') {
  44.             return new Response(json_encode([
  45.                 'success' => false,
  46.                 'data' => 'Admin role required'
  47.             ]), Response::HTTP_FORBIDDEN);
  48.         }
  49.         return null;
  50.     }
  51.     private function normalizeAdditionalPriceItem(mixed $item$membership): ?array
  52.     {
  53.         $price null;
  54.         $forMembership null;
  55.         if ($item instanceof Price) {
  56.             $price $item;
  57.         } elseif ($item instanceof ElementMetadata) {
  58.             $element $item->getElement();
  59.             if ($element instanceof Price) {
  60.                 $price $element;
  61.             }
  62.             $forMembership $item->getForMembership();
  63.         }
  64.         if (!$price instanceof Price) {
  65.             return null;
  66.         }
  67.         // Include the item for members when it's member-only, or for everyone when not flagged as member-only.
  68.         if (($forMembership == && $membership) || $forMembership != 1) {
  69.             return [
  70.                 'id' => $price->getPriceid(),
  71.                 'title' => $price->getTitle(),
  72.                 'price' => $price->getPrice() / 100,
  73.             ];
  74.         }
  75.         return null;
  76.     }
  77.     private function freeTools(LocationSource $locationSource): array
  78.     {
  79.         $data = [];
  80.         if ($locationSource instanceof LocationSource) {
  81.             $freeTools $locationSource->getFreeTools();
  82.             foreach($freeTools as $tool) {
  83.                 if ($tool instanceof ProductDescription) {
  84.                     $data['services'][$tool->getServiceId()] = [
  85.                         'serviceId' => $tool->getServiceId(),
  86.                         'title' => $tool->getTitle(),
  87.                         'price' => 0.0,
  88.                         'shortDescription' => $tool->getShortDescription(),
  89.                         'fullDescription' => $tool->getFullDescription(),
  90.                         'requirements' => $tool->getRequirements(),
  91.                         'requiredDocuments' => $tool->getRequiredDocuments(),
  92.                         'estimatedTime' => $tool->getEstimatedTime(),
  93.                         'required' => $tool->getRequired()
  94.                     ];
  95.                 }
  96.             }
  97.         }
  98.         return $data;
  99.     }
  100.     #[Route('/v1/api/user/stripe'name'user_stripe',  methods: ['GET'])]
  101.     public function userStripeAction(Request $request, #[CurrentUser$user null): Response
  102.     {
  103.         $sourceKey $request->query->get('source');
  104.         if ($user === null) {
  105.             return new Response(json_encode([
  106.                 'success' => false,
  107.                 'data' => 'User not authenticated'
  108.             ]), Response::HTTP_UNAUTHORIZED);
  109.         }
  110.         $locationSource LocationSource::getByObjectKey($sourceKey1);
  111.         $MemberStripeAccount = new MemberStripeAccount\Listing();
  112.         $MemberStripeAccount->setCondition('customerId = ? AND source__id = ?', [
  113.             $user->getId(),
  114.             $locationSource->getId()
  115.         ]);
  116.         $MemberStripeAccount $MemberStripeAccount->current();
  117.         $data = [];
  118.         if (!$MemberStripeAccount instanceof MemberStripeAccount || !$MemberStripeAccount->getRules()) {
  119.             $stripeAccount $locationSource->getStripeAccount();
  120.             $attestationPrice Price::getByPriceid($stripeAccount->getAttestationItem(), 1);
  121.             $attestationDescription $attestationPrice?->getDescription();
  122.             $data['services']['attestation'] = [
  123.                 'id' => $attestationPrice?->getPriceid(),
  124.                 'title' => $attestationDescription?->getTitle(),
  125.                 'shortDescription' => $attestationDescription?->getShortDescription(),
  126.                 'fullDescription' => $attestationDescription?->getFullDescription(),
  127.                 'requirements' => $attestationDescription?->getRequirements(),
  128.                 'requiredDocuments' => $attestationDescription?->getRequiredDocuments(),
  129.                 'estimatedTime' => $attestationDescription?->getEstimatedTime(),
  130.                 'required' => $attestationDescription?->getRequired(),
  131.                 'price' => $attestationPrice?->getPrice() / 100,
  132.                 'serviceId' => $attestationDescription?->getServiceId(),
  133.             ];
  134.             $assistancePrice Price::getByPriceid($stripeAccount->getAssistanceItem(), 1);
  135.             $assistanceDescription $assistancePrice?->getDescription();
  136.             $data['services']['assistance'] = [
  137.                 'id' => $assistancePrice?->getPriceid(),
  138.                 'title' => $assistanceDescription?->getTitle(),
  139.                 'shortDescription' => $assistanceDescription?->getShortDescription(),
  140.                 'fullDescription' => $assistanceDescription?->getFullDescription(),
  141.                 'requirements' => $assistanceDescription?->getRequirements(),
  142.                 'requiredDocuments' => $assistanceDescription?->getRequiredDocuments(),
  143.                 'estimatedTime' => $assistanceDescription?->getEstimatedTime(),
  144.                 'required' => $assistanceDescription?->getRequired(),
  145.                 'price' => $assistancePrice?->getPrice() / 100,
  146.                 'serviceId' => $assistanceDescription?->getServiceId(),
  147.             ];
  148.             $rentProtectionPrice Price::getByPriceid($stripeAccount->getInsuranceItem(), 1);
  149.             $rentProtectionDescription $rentProtectionPrice?->getDescription();
  150.             $data['services']['rentProtection'] = [
  151.                 'id' => $rentProtectionPrice?->getPriceid(),
  152.                 'title' => $rentProtectionDescription?->getTitle(),
  153.                 'shortDescription' => $rentProtectionDescription?->getShortDescription(),
  154.                 'fullDescription' => $rentProtectionDescription?->getFullDescription(),
  155.                 'requirements' => $rentProtectionDescription?->getRequirements(),
  156.                 'requiredDocuments' => $rentProtectionDescription?->getRequiredDocuments(),
  157.                 'estimatedTime' => $rentProtectionDescription?->getEstimatedTime(),
  158.                 'required' => $rentProtectionDescription?->getRequired(),
  159.                 'price' => $rentProtectionPrice?->getPrice() / 100,
  160.                 'serviceId' => $rentProtectionDescription?->getServiceId(),
  161.             ];
  162.         }
  163.         
  164.         if ($MemberStripeAccount && $MemberStripeAccount->getRules()){
  165.             $rules PromoRules::getById($MemberStripeAccount->getRules());
  166.         }
  167.         
  168.         if ($MemberStripeAccount && $MemberStripeAccount->getRules() && $rules instanceof PromoRules) {
  169.             $promos $rules->getPromo();
  170.             if (!empty($promos)) {
  171.                 foreach ($promos as $promo) {
  172.                     if ($promo['price']->getData() instanceof Price) {
  173.                         $price $promo['price']->getData();
  174.                         $description $price->getDescription();
  175.                         if ($description instanceof ProductDescription) {
  176.                             $data['services'][$description->getServiceKey()] = [
  177.                                 'id' => $price->getPriceid(),
  178.                                 'title' => $description->getTitle() ? $description->getTitle() : $price->getTitle(),
  179.                                 'shortDescription' => $description->getShortDescription(),
  180.                                 'fullDescription' => $description->getFullDescription(),
  181.                                 'requirements' => $description->getRequirements(),
  182.                                 'requiredDocuments' => $description->getRequiredDocuments(),
  183.                                 'estimatedTime' => $description->getEstimatedTime(),
  184.                                 'required' => $description->getRequired(),
  185.                                 'price' => $price->getPrice() / 100,
  186.                                 'serviceId' => $description->getServiceId(),
  187.                             ];
  188.                         }
  189.                     }
  190.                 }
  191.             }
  192.         }
  193.         $membership $user->getMembershipForm();
  194.         
  195.         $stripeAccount StripeAccount::getByAccount($locationSource->getId(), 1);
  196.        
  197.         
  198.         // Aggiungi servizi aggiuntivi se disponibili
  199.         $additionalAttestationItems $stripeAccount->getAdditionalAttestationItem();
  200.         if (!empty($additionalAttestationItems)) {
  201.             foreach ($additionalAttestationItems as $item) {
  202.                 $normalized $this->normalizeAdditionalPriceItem($item$membership);
  203.                 if (!empty($normalized)) {
  204.                     $data['services']['attestation']['additional'][] = $normalized;
  205.                 }
  206.             }
  207.         }
  208.         
  209.         $additionalAssistanceItems $stripeAccount->getAdditionalAssistanceItem();
  210.         if (!empty($additionalAssistanceItems)) {
  211.             foreach ($additionalAssistanceItems as $item) {
  212.                 $normalized $this->normalizeAdditionalPriceItem($item$membership);
  213.                 if (!empty($normalized)) {
  214.                     $data['services']['assistance']['additional'][] = $normalized;
  215.                 }
  216.             }
  217.         }
  218.         
  219.         $listing = new MemberStripeAccount\Listing();
  220.         $listing->setCondition('customerId = :customerId AND source__id = :sourceId', [
  221.             'customerId' => $user->getId(),
  222.             'sourceId' => $locationSource->getId()
  223.         ]);
  224.         $listing->setLimit(1);
  225.         $memberStripeAccount $listing->current();
  226.         if ($memberStripeAccount instanceof MemberStripeAccount) {
  227.             // Verifica se ci sono promo code e aggiungi gli sconti ai servizi
  228.             if (!empty($memberStripeAccount->getPromoCode())) {
  229.                 $stripe = new \Stripe\StripeClient($stripeAccount->getStripeSecret());
  230.                 
  231.                 foreach ($memberStripeAccount->getPromoCode() as $promos) {
  232.                     $priceId $promos[0];
  233.                     $promoId $promos[1];
  234.                     
  235.                     // Determina quale servizio corrisponde a questo price_id
  236.                     $serviceKey null;
  237.                     if ($priceId === $stripeAccount->getAttestationItem()) {
  238.                         $serviceKey 'attestation';
  239.                     } elseif ($priceId === $stripeAccount->getAssistanceItem()) {
  240.                         $serviceKey 'assistance';
  241.                     } elseif ($priceId === $stripeAccount->getInsuranceItem()) {
  242.                         $serviceKey 'insurance';
  243.                     }
  244.                     
  245.                     // if ($serviceKey !== null) {
  246.                     //     // Cerca prima nel database Promocode
  247.                     //     $promo = Promocode::getByPromoid($promoId, 1);
  248.                     //     if ($promo !== null) {
  249.                     //         $coupon = \Pimcore\Model\DataObject\Coupon::getByPromo($promo, 1);
  250.                     //         if ($coupon instanceof \Pimcore\Model\DataObject\Coupon) {
  251.                     //             preg_match('/\d+$/', $coupon->getTitle(), $matches);
  252.                     //             if (!empty($matches)) {
  253.                     //                 $data['services'][$serviceKey]['promo'] = [
  254.                     //                     'discount' => (float) $matches[0],
  255.                     //                     'id' => $promoId,
  256.                     //                     'couponName' => $coupon->getTitle(),
  257.                     //                     'promoName' => $promo->getCode(),
  258.                     //                     'code' => $coupon->getCouponid() // Recupera il couponid dalla relazione con promo
  259.                     //                 ];
  260.                     //             }
  261.                     //         }
  262.                     //     } else {
  263.                     //         // Se non trovato nel database, recupera da Stripe
  264.                     //         try {
  265.                     //             $promoObj = $stripe->promotionCodes->retrieve($promoId, []);
  266.                     //             if ($promoObj->active) {
  267.                     //                 preg_match('/\d+$/', $promoObj->coupon->name, $matches);
  268.                     //                 if (!empty($matches)) {
  269.                     //                     $data['services'][$serviceKey]['promo'] = [
  270.                     //                         'discount' => (float) $matches[0],
  271.                     //                         'id' => $promoId,
  272.                     //                         'couponName' => '',
  273.                     //                         'promoName' => '',
  274.                     //                         'code' => $promoObj->code // Aggiungi il codice coupon da Stripe
  275.                     //                     ];
  276.                     //                 }
  277.                     //             }
  278.                     //         } catch (\Exception $e) {
  279.                     //             // Log dell'errore se necessario
  280.                     //         }
  281.                     //     }
  282.                     // }
  283.                 }
  284.             }
  285.         }
  286.         $freeToolsData $this->freeTools($locationSource);
  287.         
  288.         // Merge correttamente i servizi gratuiti con quelli a pagamento
  289.         if (isset($freeToolsData['services'])) {
  290.             $data['services'] = array_merge($data['services'] ?? [], $freeToolsData['services']);
  291.         }
  292.         
  293.         return new Response(json_encode([
  294.             'success' => true,
  295.             'data' => $data
  296.         ]));
  297.     }
  298.     #[Route('/v1/api/stripe/dashboard-stats'name'stripe_dashboard_stats'methods: ['GET'])]
  299.     public function dashboardStatsAction(Request $request, #[CurrentUser$user null): Response
  300.     {
  301.         // Controlla autenticazione e permessi admin
  302.         $authError $this->validateAdminRole($user);
  303.         if ($authError !== null) {
  304.             return $authError;
  305.         }
  306.         
  307.         $sourceKey $request->query->get('source');
  308.         $period $request->query->get('period''30'); // giorni
  309.         
  310.         if (empty($sourceKey)) {
  311.             return new Response(json_encode([
  312.                 'success' => false,
  313.                 'data' => 'Source parameter required'
  314.             ]), Response::HTTP_BAD_REQUEST);
  315.         }
  316.         
  317.         $locationSource LocationSource::getByObjectKey($sourceKey1);
  318.         if (!$locationSource instanceof LocationSource) {
  319.             return new Response(json_encode([
  320.                 'success' => false,
  321.                 'data' => 'Location source not found'
  322.             ]), Response::HTTP_NOT_FOUND);
  323.         }
  324.         
  325.         $stripeAccount StripeAccount::getByAccount($locationSource->getId(), 1);
  326.         if (!$stripeAccount instanceof StripeAccount) {
  327.             return new Response(json_encode([
  328.                 'success' => false,
  329.                 'data' => 'Stripe account not found'
  330.             ]), Response::HTTP_NOT_FOUND);
  331.         }
  332.         
  333.         try {
  334.             $cacheKey sprintf('stripe_dashboard_stats_%s_%s'$sourceKey$period);
  335.             
  336.             $stats $this->cache->get($cacheKey, function (ItemInterface $item) use ($stripeAccount$period) {
  337.                 $item->expiresAfter(300); // 5 minuti di cache
  338.                 
  339.                 $stripe = new \Stripe\StripeClient($stripeAccount->getStripeSecret());
  340.                 
  341.                 $startDate Carbon::now()->subDays((int)$period)->startOfDay();
  342.                 $endDate Carbon::now()->endOfDay();
  343.                 
  344.                 // Recupera i pagamenti nel periodo
  345.                 $allPayments $stripe->paymentIntents->all([
  346.                     'limit' => 100,
  347.                     'created' => [
  348.                         'gte' => $startDate->timestamp,
  349.                         'lte' => $endDate->timestamp
  350.                     ]
  351.                 ]);
  352.                 
  353.                 // Filtra i pagamenti completati e falliti
  354.                 $payments = [];
  355.                 $failedPayments = [];
  356.                 
  357.                 foreach ($allPayments->data as $payment) {
  358.                     if ($payment->status === 'succeeded') {
  359.                         $payments[] = $payment;
  360.                     } elseif ($payment->status === 'payment_failed') {
  361.                         $failedPayments[] = $payment;
  362.                     }
  363.                 }
  364.                 
  365.                 return $this->calculateDashboardStats($payments$failedPayments$period);
  366.             });
  367.             
  368.             return new Response(json_encode([
  369.                 'success' => true,
  370.                 'data' => $stats
  371.             ]));
  372.             
  373.         } catch (\Exception $e) {
  374.             $logger ApplicationLogger::getInstance();
  375.             $logger->error('Dashboard stats error: ' $e->getMessage());
  376.             
  377.             return new Response(json_encode([
  378.                 'success' => false,
  379.                 'data' => 'Error retrieving dashboard stats'
  380.             ]), Response::HTTP_INTERNAL_SERVER_ERROR);
  381.         }
  382.     }
  383.     
  384.     #[Route('/v1/api/stripe/sales-analytics'name'stripe_sales_analytics'methods: ['GET'])]
  385.     public function salesAnalyticsAction(Request $request, #[CurrentUser$user null): Response
  386.     {
  387.         // Controlla autenticazione e permessi admin
  388.         $authError $this->validateAdminRole($user);
  389.         if ($authError !== null) {
  390.             return $authError;
  391.         }
  392.         
  393.         $sourceKey $request->query->get('source');
  394.         $period $request->query->get('period''month'); // day, week, month
  395.         
  396.         if (empty($sourceKey)) {
  397.             return new Response(json_encode([
  398.                 'success' => false,
  399.                 'data' => 'Source parameter required'
  400.             ]), Response::HTTP_BAD_REQUEST);
  401.         }
  402.         
  403.         $locationSource LocationSource::getByObjectKey($sourceKey1);
  404.         $stripeAccount StripeAccount::getByAccount($locationSource->getId(), 1);
  405.         
  406.         try {
  407.             $cacheKey sprintf('stripe_sales_analytics_%s_%s'$sourceKey$period);
  408.             
  409.             $analytics $this->cache->get($cacheKey, function (ItemInterface $item) use ($stripeAccount$period) {
  410.                 // Cache più lungo per analytics storiche
  411.                 $ttl = match($period) {
  412.                     'day' => 180,    // 3 minuti per dati giornalieri
  413.                     'week' => 600,   // 10 minuti per dati settimanali  
  414.                     'month' => 1800// 30 minuti per dati mensili
  415.                     default => 600
  416.                 };
  417.                 $item->expiresAfter($ttl);
  418.                 
  419.                 $stripe = new \Stripe\StripeClient($stripeAccount->getStripeSecret());
  420.                 return $this->getSalesAnalytics($stripe$period);
  421.             });
  422.             
  423.             return new Response(json_encode([
  424.                 'success' => true,
  425.                 'data' => $analytics
  426.             ]));
  427.             
  428.         } catch (\Exception $e) {
  429.             $logger ApplicationLogger::getInstance();
  430.             $logger->error('Sales analytics error: ' $e->getMessage());
  431.             
  432.             return new Response(json_encode([
  433.                 'success' => false,
  434.                 'data' => 'Error retrieving sales analytics'
  435.             ]), Response::HTTP_INTERNAL_SERVER_ERROR);
  436.         }
  437.     }
  438.     
  439.     #[Route('/v1/api/stripe/alerts'name'stripe_alerts'methods: ['GET'])]
  440.     public function alertsAction(Request $request, #[CurrentUser$user null): Response
  441.     {
  442.         // Controlla autenticazione e permessi admin
  443.         $authError $this->validateAdminRole($user);
  444.         if ($authError !== null) {
  445.             return $authError;
  446.         }
  447.         
  448.         $sourceKey $request->query->get('source');
  449.         $threshold = (float)$request->query->get('threshold'20); // percentuale calo ricavi
  450.         
  451.         if (empty($sourceKey)) {
  452.             return new Response(json_encode([
  453.                 'success' => false,
  454.                 'data' => 'Source parameter required'
  455.             ]), Response::HTTP_BAD_REQUEST);
  456.         }
  457.         
  458.         $locationSource LocationSource::getByObjectKey($sourceKey1);
  459.         $stripeAccount StripeAccount::getByAccount($locationSource->getId(), 1);
  460.         
  461.         try {
  462.             $cacheKey sprintf('stripe_alerts_%s_%.1f'$sourceKey$threshold);
  463.             
  464.             $alerts $this->cache->get($cacheKey, function (ItemInterface $item) use ($stripeAccount$threshold) {
  465.                 $item->expiresAfter(120); // 2 minuti per gli alert (devono essere abbastanza freschi)
  466.                 
  467.                 $stripe = new \Stripe\StripeClient($stripeAccount->getStripeSecret());
  468.                 return $this->generateAlerts($stripe$threshold);
  469.             });
  470.             
  471.             return new Response(json_encode([
  472.                 'success' => true,
  473.                 'data' => $alerts
  474.             ]));
  475.             
  476.         } catch (\Exception $e) {
  477.             $logger ApplicationLogger::getInstance();
  478.             $logger->error('Alerts error: ' $e->getMessage());
  479.             
  480.             return new Response(json_encode([
  481.                 'success' => false,
  482.                 'data' => 'Error generating alerts'
  483.             ]), Response::HTTP_INTERNAL_SERVER_ERROR);
  484.         }
  485.     }
  486.     
  487.     #[Route('/v1/api/stripe/predictions'name'stripe_predictions'methods: ['GET'])]
  488.     public function predictionsAction(Request $request, #[CurrentUser$user null): Response
  489.     {
  490.         // Controlla autenticazione e permessi admin
  491.         $authError $this->validateAdminRole($user);
  492.         if ($authError !== null) {
  493.             return $authError;
  494.         }
  495.         
  496.         $sourceKey $request->query->get('source');
  497.         
  498.         if (empty($sourceKey)) {
  499.             return new Response(json_encode([
  500.                 'success' => false,
  501.                 'data' => 'Source parameter required'
  502.             ]), Response::HTTP_BAD_REQUEST);
  503.         }
  504.         
  505.         $locationSource LocationSource::getByObjectKey($sourceKey1);
  506.         $stripeAccount StripeAccount::getByAccount($locationSource->getId(), 1);
  507.         
  508.         try {
  509.             $cacheKey sprintf('stripe_predictions_%s'$sourceKey);
  510.             
  511.             $predictions $this->cache->get($cacheKey, function (ItemInterface $item) use ($stripeAccount) {
  512.                 $item->expiresAfter(900); // 15 minuti per le previsioni
  513.                 
  514.                 $stripe = new \Stripe\StripeClient($stripeAccount->getStripeSecret());
  515.                 return $this->calculateMonthlyPrediction($stripe);
  516.             });
  517.             
  518.             return new Response(json_encode([
  519.                 'success' => true,
  520.                 'data' => $predictions
  521.             ]));
  522.             
  523.         } catch (\Exception $e) {
  524.             $logger ApplicationLogger::getInstance();
  525.             $logger->error('Predictions error: ' $e->getMessage());
  526.             
  527.             return new Response(json_encode([
  528.                 'success' => false,
  529.                 'data' => 'Error calculating predictions'
  530.             ]), Response::HTTP_INTERNAL_SERVER_ERROR);
  531.         }
  532.     }
  533.     #[Route('/v1/api/stripe/clear-cache'name'stripe_clear_cache'methods: ['POST'])]
  534.     public function clearCacheAction(Request $request, #[CurrentUser$user null): Response
  535.     {
  536.         // Controlla autenticazione e permessi admin
  537.         $authError $this->validateAdminRole($user);
  538.         if ($authError !== null) {
  539.             return $authError;
  540.         }
  541.         
  542.         $sourceKey $request->request->get('source');
  543.         
  544.         if (empty($sourceKey)) {
  545.             return new Response(json_encode([
  546.                 'success' => false,
  547.                 'data' => 'Source parameter required'
  548.             ]), Response::HTTP_BAD_REQUEST);
  549.         }
  550.         
  551.         try {
  552.             // Pattern per eliminare tutte le cache relative a questa source
  553.             $patterns = [
  554.                 'stripe_dashboard_stats_' $sourceKey '_*',
  555.                 'stripe_sales_analytics_' $sourceKey '_*',
  556.                 'stripe_alerts_' $sourceKey '_*',
  557.                 'stripe_predictions_' $sourceKey
  558.             ];
  559.             
  560.             // Pimcore non ha un metodo diretto per eliminare per pattern,
  561.             // quindi eliminiamo le chiavi più comuni
  562.             $keysToDelete = [
  563.                 sprintf('stripe_dashboard_stats_%s_7'$sourceKey),
  564.                 sprintf('stripe_dashboard_stats_%s_30'$sourceKey),
  565.                 sprintf('stripe_sales_analytics_%s_day'$sourceKey),
  566.                 sprintf('stripe_sales_analytics_%s_week'$sourceKey),
  567.                 sprintf('stripe_sales_analytics_%s_month'$sourceKey),
  568.                 sprintf('stripe_alerts_%s_20.0'$sourceKey),
  569.                 sprintf('stripe_predictions_%s'$sourceKey)
  570.             ];
  571.             
  572.             foreach ($keysToDelete as $key) {
  573.                 $this->cache->delete($key);
  574.             }
  575.             
  576.             return new Response(json_encode([
  577.                 'success' => true,
  578.                 'data' => 'Cache cleared successfully'
  579.             ]));
  580.             
  581.         } catch (\Exception $e) {
  582.             $logger ApplicationLogger::getInstance();
  583.             $logger->error('Clear cache error: ' $e->getMessage());
  584.             
  585.             return new Response(json_encode([
  586.                 'success' => false,
  587.                 'data' => 'Error clearing cache'
  588.             ]), Response::HTTP_INTERNAL_SERVER_ERROR);
  589.         }
  590.     }
  591.     #[Route('/stripe/transfer'name'stripe_transfer',  methods: ['POST'])]
  592.     public function transferAction(Request $request): Response
  593.     {
  594.         $username $request->headers->get('php-auth-user');
  595.         $password $request->headers->get('php-auth-pw');
  596.         if (empty($username) && empty($password)) {
  597.             return new Response(''Response::HTTP_UNAUTHORIZED);
  598.         }
  599.         if ($username !== 'ariful' || $password !== 'ariful') {
  600.             return new Response(''Response::HTTP_UNAUTHORIZED);
  601.         }
  602.         $destination $request->request->get('destination');
  603.         $amount $request->request->get('amount');
  604.         // $message = false;
  605.         $logger ApplicationLogger::getInstance();
  606.         // $payload = $request->getContent();
  607.         // $json = json_decode($payload, true);
  608.         try {
  609.             Stripe\Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  610.             $stripe = new \Stripe\StripeClient($_ENV["STRIPE_SECRET"]);
  611.             $result $stripe->transfers->create([
  612.                 'amount' => $amount,
  613.                 'currency' => 'eur',
  614.                 'destination' => $destination
  615.             ]);
  616.         } catch (\Exception $e) {
  617.             $logger->error($e->getMessage());
  618.         }
  619.         return new Response($result->toJSON());
  620.     }
  621.     #[Route('/stripe/balance'name'stripe_balance',  methods: ['POST'])]
  622.     public function balanceAction(Request $request): Response
  623.     {
  624.         $username $request->headers->get('php-auth-user');
  625.         $password $request->headers->get('php-auth-pw');
  626.         if (empty($username) && empty($password)) {
  627.             return new Response(''Response::HTTP_UNAUTHORIZED);
  628.         }
  629.         if ($username !== 'ariful' || $password !== 'ariful') {
  630.             return new Response(''Response::HTTP_UNAUTHORIZED);
  631.         }
  632.         // $message = false;
  633.         $logger ApplicationLogger::getInstance();
  634.         // $payload = $request->getContent();
  635.         // $json = json_decode($payload, true);
  636.         // dd($json);
  637.         try {
  638.             Stripe\Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  639.             $stripe = new \Stripe\StripeClient($_ENV["STRIPE_SECRET"]);
  640.             $result $stripe->balance->retrieve([]);
  641.         } catch (\Exception $e) {
  642.             $logger->error($e->getMessage());
  643.         }
  644.         return new Response($result->toJSON());
  645.     }
  646.     private function calculateDashboardStats(array $payments, array $failedPaymentsstring $period): array
  647.     {
  648.         $totalRevenue 0;
  649.         $totalTransactions count($payments);
  650.         $dailyRevenue = [];
  651.         $lastSuccessfulPayment null;
  652.         $lastFailedPayment null;
  653.         
  654.         // Calcola ricavi totali e giornalieri
  655.         foreach ($payments as $payment) {
  656.             $totalRevenue += $payment->amount 100// Converti da centesimi
  657.             $date Carbon::createFromTimestamp($payment->created)->format('Y-m-d');
  658.             
  659.             if (!isset($dailyRevenue[$date])) {
  660.                 $dailyRevenue[$date] = 0;
  661.             }
  662.             $dailyRevenue[$date] += $payment->amount 100;
  663.             
  664.             if ($lastSuccessfulPayment === null || $payment->created $lastSuccessfulPayment['created']) {
  665.                 $lastSuccessfulPayment = [
  666.                     'id' => $payment->id,
  667.                     'amount' => $payment->amount 100,
  668.                     'currency' => $payment->currency,
  669.                     'created' => $payment->created,
  670.                     'customer' => $payment->customer ?? 'Guest'
  671.                 ];
  672.             }
  673.         }
  674.         
  675.         // Ultimo pagamento fallito
  676.         foreach ($failedPayments as $payment) {
  677.             if ($lastFailedPayment === null || $payment->created $lastFailedPayment['created']) {
  678.                 $lastFailedPayment = [
  679.                     'id' => $payment->id,
  680.                     'amount' => $payment->amount 100,
  681.                     'currency' => $payment->currency,
  682.                     'created' => $payment->created,
  683.                     'failure_reason' => $payment->last_payment_error->message ?? 'Unknown',
  684.                     'customer' => $payment->customer ?? 'Guest'
  685.                 ];
  686.             }
  687.         }
  688.         
  689.         // Trova best e worst day
  690.         $bestDay null;
  691.         $worstDay null;
  692.         $bestAmount 0;
  693.         $worstAmount PHP_FLOAT_MAX;
  694.         
  695.         foreach ($dailyRevenue as $date => $amount) {
  696.             if ($amount $bestAmount) {
  697.                 $bestAmount $amount;
  698.                 $bestDay = ['date' => $date'amount' => $amount];
  699.             }
  700.             if ($amount $worstAmount) {
  701.                 $worstAmount $amount;
  702.                 $worstDay = ['date' => $date'amount' => $amount];
  703.             }
  704.         }
  705.         
  706.         // Calcola media giornaliera
  707.         $avgDailyRevenue $totalRevenue max(1, (int)$period);
  708.         
  709.         return [
  710.             'totalRevenue' => $totalRevenue,
  711.             'totalTransactions' => $totalTransactions,
  712.             'avgDailyRevenue' => $avgDailyRevenue,
  713.             'failedTransactions' => count($failedPayments),
  714.             'dailyRevenue' => $dailyRevenue,
  715.             'bestDay' => $bestDay,
  716.             'worstDay' => $worstDay,
  717.             'lastSuccessfulPayment' => $lastSuccessfulPayment,
  718.             'lastFailedPayment' => $lastFailedPayment,
  719.             'period' => $period
  720.         ];
  721.     }
  722.     
  723.     private function getSalesAnalytics(\Stripe\StripeClient $stripestring $period): array
  724.     {
  725.         $now Carbon::now();
  726.         
  727.         switch ($period) {
  728.             case 'day':
  729.                 $startDate $now->copy()->subDays(30)->startOfDay();
  730.                 $groupBy 'day';
  731.                 break;
  732.             case 'week':
  733.                 $startDate $now->copy()->subWeeks(12)->startOfWeek();
  734.                 $groupBy 'week';
  735.                 break;
  736.             case 'month':
  737.             default:
  738.                 $startDate $now->copy()->subMonths(12)->startOfMonth();
  739.                 $groupBy 'month';
  740.                 break;
  741.         }
  742.         
  743.         $allPayments $stripe->paymentIntents->all([
  744.             'limit' => 100,
  745.             'created' => [
  746.                 'gte' => $startDate->timestamp,
  747.                 'lte' => $now->timestamp
  748.             ]
  749.         ]);
  750.         
  751.         // Filtra solo i pagamenti completati
  752.         $payments array_filter($allPayments->data, fn($payment) => $payment->status === 'succeeded');
  753.         
  754.         $analytics = [];
  755.         $totals = ['revenue' => 0'transactions' => 0];
  756.         
  757.         foreach ($payments as $payment) {
  758.             $date Carbon::createFromTimestamp($payment->created);
  759.             
  760.             $key = match($groupBy) {
  761.                 'day' => $date->format('Y-m-d'),
  762.                 'week' => $date->format('Y-W'),
  763.                 'month' => $date->format('Y-m'),
  764.                 default => $date->format('Y-m-d')
  765.             };
  766.             
  767.             if (!isset($analytics[$key])) {
  768.                 $analytics[$key] = [
  769.                     'period' => $key,
  770.                     'revenue' => 0,
  771.                     'transactions' => 0,
  772.                     'avgTransaction' => 0
  773.                 ];
  774.             }
  775.             
  776.             $amount $payment->amount 100;
  777.             $analytics[$key]['revenue'] += $amount;
  778.             $analytics[$key]['transactions']++;
  779.             $analytics[$key]['avgTransaction'] = $analytics[$key]['revenue'] / $analytics[$key]['transactions'];
  780.             
  781.             $totals['revenue'] += $amount;
  782.             $totals['transactions']++;
  783.         }
  784.         
  785.         // Ordina per periodo
  786.         ksort($analytics);
  787.         
  788.         return [
  789.             'period' => $period,
  790.             'data' => array_values($analytics),
  791.             'totals' => $totals,
  792.             'avgTransactionValue' => $totals['transactions'] > $totals['revenue'] / $totals['transactions'] : 0
  793.         ];
  794.     }
  795.     
  796.     private function generateAlerts(\Stripe\StripeClient $stripefloat $threshold): array
  797.     {
  798.         $alerts = [];
  799.         $now Carbon::now();
  800.         
  801.         // Confronta ricavi settimana corrente vs precedente
  802.         $thisWeekStart $now->copy()->startOfWeek();
  803.         $lastWeekStart $now->copy()->subWeek()->startOfWeek();
  804.         $lastWeekEnd $now->copy()->subWeek()->endOfWeek();
  805.         
  806.         $thisWeekAllPayments $stripe->paymentIntents->all([
  807.             'created' => ['gte' => $thisWeekStart->timestamp]
  808.         ]);
  809.         
  810.         $lastWeekAllPayments $stripe->paymentIntents->all([
  811.             'created' => [
  812.                 'gte' => $lastWeekStart->timestamp,
  813.                 'lte' => $lastWeekEnd->timestamp
  814.             ]
  815.         ]);
  816.         
  817.         // Filtra i pagamenti per stato
  818.         $thisWeekPayments array_filter($thisWeekAllPayments->data, fn($p) => $p->status === 'succeeded');
  819.         $lastWeekPayments array_filter($lastWeekAllPayments->data, fn($p) => $p->status === 'succeeded');
  820.         
  821.         $thisWeekRevenue array_sum(array_map(fn($p) => $p->amount 100$thisWeekPayments));
  822.         $lastWeekRevenue array_sum(array_map(fn($p) => $p->amount 100$lastWeekPayments));
  823.         
  824.         if ($lastWeekRevenue 0) {
  825.             $revenueChange = (($thisWeekRevenue $lastWeekRevenue) / $lastWeekRevenue) * 100;
  826.             
  827.             if ($revenueChange < -$threshold) {
  828.                 $alerts[] = [
  829.                     'type' => 'revenue_drop',
  830.                     'severity' => 'high',
  831.                     'message' => sprintf("Calo ricavi del %.1f%% rispetto alla settimana scorsa"$revenueChange),
  832.                     'data' => [
  833.                         'thisWeek' => $thisWeekRevenue,
  834.                         'lastWeek' => $lastWeekRevenue,
  835.                         'change' => $revenueChange
  836.                     ]
  837.                 ];
  838.             }
  839.         }
  840.         
  841.         // Alert per pagamenti falliti nelle ultime 24h
  842.         $yesterdayStart $now->copy()->subDay()->startOfDay();
  843.         $allRecentPayments $stripe->paymentIntents->all([
  844.             'created' => ['gte' => $yesterdayStart->timestamp]
  845.         ]);
  846.         
  847.         // Filtra i pagamenti falliti
  848.         $failedPayments array_filter($allRecentPayments->data, fn($p) => $p->status === 'payment_failed');
  849.         
  850.         if (count($failedPayments) > 3) {
  851.             $alerts[] = [
  852.                 'type' => 'failed_payments',
  853.                 'severity' => 'medium',
  854.                 'message' => 'Numero elevato di pagamenti falliti nelle ultime 24h (' count($failedPayments) . ')',
  855.                 'data' => [
  856.                     'count' => count($failedPayments),
  857.                     'period' => '24h'
  858.                 ]
  859.             ];
  860.         }
  861.         
  862.         return $alerts;
  863.     }
  864.     
  865.     private function calculateMonthlyPrediction(\Stripe\StripeClient $stripe): array
  866.     {
  867.         $now Carbon::now();
  868.         $monthStart $now->copy()->startOfMonth();
  869.         $monthEnd $now->copy()->endOfMonth();
  870.         $daysInMonth $monthEnd->day;
  871.         $daysPassed $now->day;
  872.         $daysRemaining $daysInMonth $daysPassed;
  873.         
  874.         // Ricavi del mese corrente
  875.         $currentMonthAllPayments $stripe->paymentIntents->all([
  876.             'created' => ['gte' => $monthStart->timestamp]
  877.         ]);
  878.         
  879.         // Filtra solo i pagamenti completati
  880.         $currentMonthPayments array_filter($currentMonthAllPayments->data, fn($p) => $p->status === 'succeeded');
  881.         $currentMonthRevenue array_sum(array_map(fn($p) => $p->amount 100$currentMonthPayments));
  882.         
  883.         // Ricavi mese precedente per confronto
  884.         $lastMonthStart $now->copy()->subMonth()->startOfMonth();
  885.         $lastMonthEnd $now->copy()->subMonth()->endOfMonth();
  886.         
  887.         $lastMonthAllPayments $stripe->paymentIntents->all([
  888.             'created' => [
  889.                 'gte' => $lastMonthStart->timestamp,
  890.                 'lte' => $lastMonthEnd->timestamp
  891.             ]
  892.         ]);
  893.         
  894.         // Filtra solo i pagamenti completati
  895.         $lastMonthPayments array_filter($lastMonthAllPayments->data, fn($p) => $p->status === 'succeeded');
  896.         $lastMonthRevenue array_sum(array_map(fn($p) => $p->amount 100$lastMonthPayments));
  897.         
  898.         // Calcola trend giornaliero
  899.         $dailyAverage $daysPassed $currentMonthRevenue $daysPassed 0;
  900.         $projectedRevenue $dailyAverage $daysInMonth;
  901.         
  902.         // Previsione basata su trend degli ultimi 7 giorni
  903.         $weekAgoStart $now->copy()->subDays(7);
  904.         $recentAllPayments $stripe->paymentIntents->all([
  905.             'created' => ['gte' => $weekAgoStart->timestamp]
  906.         ]);
  907.         
  908.         // Filtra solo i pagamenti completati
  909.         $recentPayments array_filter($recentAllPayments->data, fn($p) => $p->status === 'succeeded');
  910.         $recentRevenue array_sum(array_map(fn($p) => $p->amount 100$recentPayments));
  911.         $recentDailyAvg $recentRevenue 7;
  912.         $trendBasedProjection $currentMonthRevenue + ($recentDailyAvg $daysRemaining);
  913.         
  914.         // Media delle due previsioni
  915.         $finalPrediction = ($projectedRevenue $trendBasedProjection) / 2;
  916.         
  917.         $confidenceLevel $this->calculateConfidence($daysPassed$daysInMonth$currentMonthRevenue$lastMonthRevenue);
  918.         
  919.         return [
  920.             'currentMonthRevenue' => $currentMonthRevenue,
  921.             'lastMonthRevenue' => $lastMonthRevenue,
  922.             'projectedRevenue' => $finalPrediction,
  923.             'dailyAverage' => $dailyAverage,
  924.             'recentDailyAverage' => $recentDailyAvg,
  925.             'daysInMonth' => $daysInMonth,
  926.             'daysPassed' => $daysPassed,
  927.             'daysRemaining' => $daysRemaining,
  928.             'confidence' => $confidenceLevel,
  929.             'changeFromLastMonth' => $lastMonthRevenue ? (($finalPrediction $lastMonthRevenue) / $lastMonthRevenue) * 100 0
  930.         ];
  931.     }
  932.     
  933.     private function calculateConfidence(int $daysPassedint $daysInMonthfloat $currentRevenuefloat $lastMonthRevenue): string
  934.     {
  935.         $completionPercentage = ($daysPassed $daysInMonth) * 100;
  936.         
  937.         if ($completionPercentage 25) {
  938.             return 'low';
  939.         } elseif ($completionPercentage 50) {
  940.             return 'medium';
  941.         } elseif ($completionPercentage 75) {
  942.             return 'high';
  943.         } else {
  944.             return 'very_high';
  945.         }
  946.     }
  947.     private function fetchEmailFromOrder($url$queryParams = []): string|null
  948.     {
  949.         $logger ApplicationLogger::getInstance();
  950.         try {
  951.             // Inizializza cURL
  952.             $ch curl_init();
  953.             // Configura le opzioni cURL
  954.             curl_setopt($chCURLOPT_URL$url);
  955.             curl_setopt($chCURLOPT_RETURNTRANSFERtrue); // Per ottenere il risultato come stringa
  956.             curl_setopt($chCURLOPT_HTTPGETtrue); // Metodo GET
  957.             // Aggiungi query string (facoltativo)
  958.             curl_setopt($chCURLOPT_URL$url '/api/getOrderUser?' http_build_query($queryParams));
  959.             // Esegui la richiesta
  960.             $response curl_exec($ch);
  961.             // dd($url . '/api/getOrderUser?' . http_build_query($queryParams), $response);
  962.             // Controlla eventuali errori
  963.             if ($response === false) {
  964.                 $error curl_error($ch);
  965.                 // dd($error);
  966.                 curl_close($ch);
  967.                 return null;
  968.             }
  969.             // Recupera il codice di stato HTTP
  970.             $httpCode curl_getinfo($chCURLINFO_HTTP_CODE);
  971.             // Chiudi cURL
  972.             curl_close($ch);
  973.             // Decodifica JSON (se applicabile)
  974.             $data json_decode($responsetrue);
  975.             if (isset($data['data'])) {
  976.                 return $data['data'];
  977.             } else {
  978.                 return null;
  979.             }
  980.             
  981.         } catch(\Exception $e) {
  982.             $logger->error($e);
  983.         }
  984.         return null;
  985.     }
  986.     #[Route('/stripe/webhook'name'app_stripe_webhook',  methods: ['POST'])]
  987.     public function verify(Request $request): Response
  988.     {
  989.         $message "";
  990.         $logger ApplicationLogger::getInstance();
  991.         $payload $request->getContent();
  992.         $json json_decode($payloadtrue);
  993.         $locationSource null;
  994.         $member null;
  995.         $userStripeAccount null;
  996.         $logger->info(serialize($json['data']['object']));
  997.         
  998.         try {
  999.             
  1000.             if (empty($json['data']['object']['customer'])) {
  1001.                 if (isset($json['data']['object']['success_url']) && str_contains($json['data']['object']['success_url'], "https://servizi.confabitaremonzabrianza.com")) {
  1002.                     $locationSource LocationSource::getByPath('/location-source/monzabrianza');
  1003.                     
  1004.                     
  1005.                 } elseif(isset($json['data']['object']['success_url']) && str_contains($json['data']['object']['success_url'], "https://servizi.bergamoconfabitare.com")) {
  1006.                     $locationSource LocationSource::getByPath('/location-source/bergamo');
  1007.                     
  1008.                 } elseif(isset($json['data']['object']['metadata']['source'])) {
  1009.                     $source $json['data']['object']['metadata']['source'];
  1010.                     $locationSource LocationSource::getByPath("/location-source/$source");
  1011.                     $logger->info($source);
  1012.                 }
  1013.                 if ($locationSource instanceof LocationSource && isset($json['data']['object']['metadata']['order_id'])) {
  1014.                     if ( isset($json['data']['object']['recovered_from']) && $json['data']['object']['recovered_from'] !== null) {
  1015.                         $response $this->fetchEmailFromOrder($locationSource->getServiceEndpoint()?->getHref(), [
  1016.                             'checkout_session' => $json['data']['object']['recovered_from'],
  1017.                             'order_id' => $json['data']['object']['metadata']['order_id']
  1018.                         ]);
  1019.                     } elseif(isset($json['data']['object']['id']) && $json['data']['object']['id'] !== null) {
  1020.                         $logger->info($locationSource->getServiceEndpoint()?->getHref());
  1021.                         $response $this->fetchEmailFromOrder($locationSource->getServiceEndpoint()?->getHref(), [
  1022.                             'checkout_session' => $json['data']['object']['id'],
  1023.                             'order_id' => $json['data']['object']['metadata']['order_id']
  1024.                         ]);
  1025.                     }
  1026.                     
  1027.                     if (!empty($response)) {
  1028.                         $members = new MembersUser\Listing();
  1029.                         $members->setCondition('email = :email', [
  1030.                             'email' => $response
  1031.                         ]);
  1032.                         $members->setUnpublished(true);
  1033.                         $members->setLimit(1);
  1034.                         $member $members->current();
  1035.                         // $member = MembersUser::getByEmail($response, 1);
  1036.         
  1037.                         if ($member instanceof MembersUser) {
  1038.                             $MemberStripeAccountListing = new MemberStripeAccount\Listing();
  1039.                             $MemberStripeAccountListing->setUnpublished(false);
  1040.                             $MemberStripeAccountListing->setCondition('customerId = :customerId AND source__id = :source', [
  1041.                                 'customerId' => $member->getId(),
  1042.                                 'source' => $locationSource->getId()
  1043.                             ]);
  1044.                             $MemberStripeAccountListing->setLimit(1);
  1045.                             $userStripeAccount $MemberStripeAccountListing->current();
  1046.                         } else {
  1047.                             $logger->error("Member not found from order: "$json['id']);  
  1048.                         }
  1049.                     } else {
  1050.                         $logger->info($json['data']['object']['customer_email']);
  1051.                         $members = new MembersUser\Listing();
  1052.                         $members->setCondition('email = :email', [
  1053.                             'email' => $json['data']['object']['customer_email']
  1054.                         ]);
  1055.                         $members->setUnpublished(true);
  1056.                         $members->setLimit(1);
  1057.                         $member $members->current();
  1058.                         // $member = MembersUser::getByEmail($json['data']['object']['customer_email'], 1);
  1059.         
  1060.                         if ($member instanceof MembersUser) {
  1061.                             $MemberStripeAccountListing = new MemberStripeAccount\Listing();
  1062.                             $MemberStripeAccountListing->setUnpublished(false);
  1063.                             $MemberStripeAccountListing->setCondition('customerId = :customerId AND source__id = :source', [
  1064.                                 'customerId' => $member->getId(),
  1065.                                 'source' => $locationSource->getId()
  1066.                             ]);
  1067.                             $MemberStripeAccountListing->setLimit(1);
  1068.                             $userStripeAccount $MemberStripeAccountListing->current();
  1069.                         } else {
  1070.                             $logger->error("Member not found: "$json['id']);  
  1071.                         }
  1072.                     }
  1073.                 } else {
  1074.                     $logger->error("Source or order_id not found: "$json['id']);
  1075.                 }
  1076.             } else {
  1077.                 $userStripeAccount MemberStripeAccount::getByStripeCustomerId($json['data']['object']['customer'], 1);
  1078.             }
  1079.     
  1080.             
  1081.         } catch(\Exception $e) {
  1082.             $logger->error($e);
  1083.         }
  1084.         if ($userStripeAccount == null || !$userStripeAccount instanceof MemberStripeAccount) {
  1085.             $logger->error("STRIPE ACCOUNT NOT FOUND");
  1086.             // Invalid payload
  1087.             echo '⚠️  STRIPE ACCOUNT NOT FOUND.';
  1088.             http_response_code(200);
  1089.             exit();
  1090.         }
  1091.         
  1092.         $stripeAccount $userStripeAccount->getSource()->getStripeAccount();
  1093.         $members = new MembersUser\Listing();
  1094.         $members->setCondition('oo_id = :id', [
  1095.             'id' => $userStripeAccount->getCustomerId()
  1096.         ]);
  1097.         $members->setUnpublished(true);
  1098.         $members->setLimit(1);
  1099.         $member $members->current();
  1100.         // $member = MembersUser::getById($userStripeAccount->getCustomerId());
  1101.         if (!$member instanceof MembersUser) {
  1102.             $logger->error("MEMBER ACCOUNT NOT FOUND");
  1103.             // Invalid payload
  1104.             echo '⚠️  MEMBER ACCOUNT NOT FOUND.';
  1105.             http_response_code(200);
  1106.             exit();
  1107.         }
  1108.         
  1109.         Stripe\Stripe::setApiKey($stripeAccount->getStripeSecret());
  1110.         $stripe = new \Stripe\StripeClient($stripeAccount->getStripeSecret());
  1111.         
  1112.         // Replace this endpoint secret with your endpoint's unique secret
  1113.         // If you are testing with the CLI, find the secret by running 'stripe listen'
  1114.         // If you are using an endpoint defined with the API or dashboard, look in your webhook settings
  1115.         // at https://dashboard.stripe.com/webhooks
  1116.         $endpoint_secret $stripeAccount->getStripeWebhookSecret();
  1117.         $event null;
  1118.         try {
  1119.             $event Stripe\Event::constructFrom($json);
  1120.         } catch (\UnexpectedValueException $e) {
  1121.             $logger->error($e);
  1122.             // Invalid payload
  1123.             echo '⚠️  Webhook error while parsing basic request.';
  1124.             http_response_code(400);
  1125.             exit();
  1126.         }
  1127.         if ($endpoint_secret) {
  1128.             // Only verify the event if there is an endpoint secret defined
  1129.             // Otherwise use the basic decoded event
  1130.             $sig_header $_SERVER['HTTP_STRIPE_SIGNATURE'];
  1131.             try {
  1132.                 $event \Stripe\Webhook::constructEvent(
  1133.                     $payload,
  1134.                     $sig_header,
  1135.                     $endpoint_secret
  1136.                 );
  1137.             } catch (\Stripe\Exception\SignatureVerificationException $e) {
  1138.                 $logger->error($e);
  1139.                 // Invalid signature
  1140.                 echo '⚠️  Webhook error while validating signature.';
  1141.                 http_response_code(400);
  1142.                 exit();
  1143.             }
  1144.         }
  1145.         $subscriptEventArray = [
  1146.             'customer.subscription.created',
  1147.             'customer.subscription.deleted',
  1148.             'customer.subscription.updated',
  1149.             'checkout.session.completed',
  1150.             'checkout.session.async_payment_succeeded'
  1151.         ];
  1152.         $logger->info($event->type);
  1153.         if (in_array($event->type$subscriptEventArray)) {
  1154.             // $member = MembersUser::getByStripeCustomerId($event->data->object['customer'], ['limit' => 1, 'unpublished' => true]);
  1155.             if ($member !== null) {
  1156.                 switch ($event->type) {
  1157.                     case 'checkout.session.completed':
  1158.                     case 'checkout.session.async_payment_succeeded':
  1159.                         if ($event->data->object['status'] === 'complete' && $event->data->object['payment_status'] === 'paid') {
  1160.                             $subs $event->data->object;
  1161.                             try {
  1162.                                 // $logger->info(count($member->getPromocode()));
  1163.                                 // $logger->info($member->getGroups()[0]->getName());
  1164.                                 if (count($userStripeAccount->getPromocode()) == && isset($member->getGroups()[0]) && $member->getGroups()[0]->getName() === 'Private') {
  1165.                                     $subscriptionEnd Carbon::now()->addYear();
  1166.                                     $promoCodeAssistenza $stripe->promotionCodes->create([
  1167.                                         'coupon' => $stripeAccount->getPrivateAssitancePromo(),
  1168.                                         'customer' => $userStripeAccount->getStripeCustomerId(),
  1169.                                         'expires_at' => $subscriptionEnd->timestamp
  1170.                                     ]);
  1171.                                     $promoCodeAttestazione $stripe->promotionCodes->create([
  1172.                                         'coupon' => $stripeAccount->getPrivateAttestationPromo(),
  1173.                                         'customer' => $userStripeAccount->getStripeCustomerId(),
  1174.                                         'expires_at' => $subscriptionEnd->timestamp
  1175.                                     ]);
  1176.                                     // $userStripeAccount->setPromocode([]);
  1177.                                     // $userStripeAccount->save();
  1178.                                     $promos = [];
  1179.                                     $promos[] = [$stripeAccount->getAssistanceItem(), $promoCodeAssistenza->id];
  1180.                                     $promos[] = [$stripeAccount->getAttestationItem(), $promoCodeAttestazione->id];
  1181.                                     
  1182.                                     if ($promos) {
  1183.                                         $member->setEndDate($subscriptionEnd);
  1184.                                         $userStripeAccount->setPromocode($promos);
  1185.                                         $userStripeAccount->save();
  1186.                                         $member->save();
  1187.                                     }
  1188.                                     
  1189.                                 } else {
  1190.                                     $logger->warning("Promo not applicable. Business user for event: "$json['id']);
  1191.                                 }
  1192.                                 $logger->info($event->type ' ->SUCCESS');
  1193.                             } catch (\Exception $e) {
  1194.                                 $logger->error($e->getMessage());
  1195.                                 $message $e->getMessage();
  1196.                             }
  1197.                         } else {
  1198.                             $logger->error("Error status: "$json['id']);
  1199.                         }
  1200.                         break;
  1201.                     case 'customer.subscription.created':
  1202.                         $subs $event->data->object;
  1203.                         if ($subs instanceof \Stripe\Subscription) {
  1204.                             //                                $member->setCreatedDate(Carbon::createFromTimestamp($subs->created));
  1205.                             //                                $message = $member->save();
  1206.                         }
  1207.                         break;
  1208.                     case 'customer.subscription.deleted':
  1209.                         $subs $event->data->object;
  1210.                         if ($subs instanceof \Stripe\Subscription) {
  1211.                             //                                $member->setEndDate(Carbon::createFromTimestamp($subs->ended_at));
  1212.                             //                                $member->setActive(false);
  1213.                             //                                $message = $member->save();
  1214.                         }
  1215.                         break;
  1216.                     case 'customer.subscription.updated':
  1217.                         $subs $event->data->object;
  1218.                         if ($subs instanceof \Stripe\Subscription) {
  1219.                             //                                $member->setLastUpdateDate(Carbon::createFromTimestamp(time()));
  1220.                             //                                if ($subs->status === 'paid' || $subs->status === 'active') {
  1221.                             //                                    $member->setActive(true);
  1222.                             //                                } else {
  1223.                             //                                    $member->setActive(false);
  1224.                             //                                }
  1225.                             //
  1226.                             //                                $message = $member->save();
  1227.                         }
  1228.                         break;
  1229.                     default:
  1230.                         // Unexpected event type
  1231.                         $message 'Received unknown event type';
  1232.                 }
  1233.             } else {
  1234.                 $logger->error("Member not found: "$json['id']);
  1235.             }
  1236.         } else {
  1237.             $logger->error("Event not found: "$json['id']);
  1238.         }
  1239.         if ($message !== "") {
  1240.             $logger->error($message);
  1241.             // Invalid payload
  1242.             echo $message;
  1243.         } else {
  1244.             echo '✅  Success!';
  1245.         }
  1246.         
  1247.         http_response_code(200);
  1248.         exit();
  1249.     }
  1250.     //    #[Route('/stripe', name: 'app_stripe')]
  1251.     //    public function index(): Response
  1252.     //    {
  1253.     //        Stripe\Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  1254.     //        $checkout_session = Stripe\Checkout\Session::create([
  1255.     //            'customer_email' => "work@mdariful.com",
  1256.     //            'line_items' => [[
  1257.     //                # Provide the exact Price ID (e.g. pr_1234) of the product you want to sell
  1258.     //                'price' => "price_1Mf1fSLneZNUF8luklGXV2KU",
  1259.     //                'quantity' => 1,
  1260.     //            ]],
  1261.     //            'allow_promotion_codes' => true,
  1262.     //            'mode' => 'subscription',
  1263.     //            'success_url' => "http://localhost:8009/stripe/success?session_id={CHECKOUT_SESSION_ID}",
  1264.     //            'cancel_url' => "http://localhost:8009/stripe/cancel?session_id={CHECKOUT_SESSION_ID}",
  1265.     //            'tax_id_collection' => [
  1266.     //                'enabled' => true,
  1267.     //            ],
  1268.     //            'billing_address_collection'=> 'required'
  1269.     //        ]);
  1270.     //
  1271.     //        return $this->redirect($checkout_session->url);
  1272.     //    }
  1273.     //
  1274.     //    #[Route('/stripe/success', name: 'app_stripe_success')]
  1275.     //    public function success(Request $request): Response
  1276.     //    {
  1277.     //        Stripe\Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  1278.     //        $session_id = $request->query->get('session_id');
  1279.     //        $return_url = "http://localhost:8009";
  1280.     //
  1281.     //        $checkout_session = Stripe\Checkout\Session::retrieve($session_id);
  1282.     //        // Authenticate your user.
  1283.     //        $session = Stripe\BillingPortal\Session::create([
  1284.     //            'customer' => $checkout_session->customer,
  1285.     //            'return_url' => $return_url,
  1286.     //        ]);
  1287.     //
  1288.     //        return $this->redirect($session->url);
  1289.     //    }
  1290.     //
  1291.     //    #[Route('/stripe/cancel', name: 'app_stripe_cancel')]
  1292.     //    public function cancel(): Response
  1293.     //    {
  1294.     //        return false;
  1295.     //    }
  1296. }