src/Controller/WebhookController.php line 42

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Service\Api\Subscription\SubscriptionService;
  4. use App\Service\Api\UsersProfile\Webshop\PaymentService;
  5. use Doctrine\DBAL\Exception\DeadlockException;
  6. use Psr\Log\LoggerInterface;
  7. use Stripe\Exception\SignatureVerificationException;
  8. use Stripe\Webhook;
  9. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpFoundation\Response;
  12. use Symfony\Component\Routing\Annotation\Route;
  13. class WebhookController extends AbstractController
  14. {
  15.     private $subscriptionService;
  16.     /**
  17.      * @var LoggerInterface
  18.      */
  19.     private $logger;
  20.     /**
  21.      * @var PaymentService
  22.      */
  23.     private $paymentService;
  24.     public function __construct(
  25.         SubscriptionService $subscriptionService,
  26.         LoggerInterface $logger,
  27.         PaymentService $paymentService
  28.     )
  29.     {
  30.         $this->subscriptionService $subscriptionService;
  31.         $this->logger $logger;
  32.         $this->paymentService $paymentService;
  33.     }
  34.     /**
  35.      * @Route("/webhook/stripe", name="stripe_webhook", methods={"POST"})
  36.      */
  37.     public function handleStripeWebhook(Request $request): Response
  38.     {
  39.         $payload $request->getContent();
  40.         $sigHeader $request->headers->get('Stripe-Signature');
  41.         $endpointSecret $_ENV['STRIPE_WEBHOOK_SECRET'];
  42.         try {
  43.             $event Webhook::constructEvent($payload$sigHeader$endpointSecret);
  44.         } catch (SignatureVerificationException $e) {
  45.             $this->logger->error('Webhook signature verification failed.', ['error' => $e->getMessage()]);
  46.             return new Response(''400);
  47.         }
  48.         try {
  49.             $this->processWebhookWithRetry(function() use ($event) {
  50.                 switch ($event->type) {
  51.                     case 'customer.subscription.updated':
  52.                         $this->subscriptionService->handleSubscriptionUpdated($event->data->object);
  53.                         break;
  54.                     case 'invoice.paid':
  55.                         $this->subscriptionService->handleInvoicePaid($event->data->object);
  56.                         break;
  57.                     case 'invoice.payment_failed':
  58.                         $this->subscriptionService->handleInvoicePaymentFailed($event->data->object);
  59.                         break;
  60.                     case 'customer.subscription.deleted':
  61.                         $this->subscriptionService->handleSubscriptionCanceled($event->data->object);
  62.                         break;
  63.                     case 'payment_intent.succeeded':
  64.                         $this->paymentService->distributedPayment(
  65.                             $event->data->object->id,
  66.                             $event->data->object->latest_charge,
  67.                         );
  68.                         break;
  69.                     default:
  70.                         $this->logger->info('Unhandled event type: ' $event->type);
  71.                         return new Response('Unhandled event type: ' $event->type200);
  72.                 }
  73.             });
  74.         } catch (\Exception $e) {
  75.             $this->logger->error('Error processing webhook', [
  76.                 'event' => $event->type,
  77.                 'error' => $e->getMessage(),
  78.                 'trace' => $e->getTraceAsString(),
  79.             ]);
  80.             return new Response('Webhook processing failed: ' $e->getMessage(), 500);
  81.         }
  82.         return new Response('Webhook processed successfully'200);
  83.     }
  84.     /**
  85.      * @throws DeadlockException
  86.      */
  87.     private function processWebhookWithRetry(callable $process$maxRetries 3$delay 100000): void // 100000 microseconds = 0.1 seconds
  88.     {
  89.         $attempts 0;
  90.         while ($attempts $maxRetries) {
  91.             try {
  92.                 $process();
  93.                 return;
  94.             } catch (DeadlockException $e) {
  95.                 $attempts++;
  96.                 if ($attempts >= $maxRetries) {
  97.                     throw $e;
  98.                 }
  99.                 usleep($delay $attempts); // Exponential backoff
  100.             }
  101.         }
  102.     }
  103. }