src/AdminBundle/Services/WebhookService.php line 21

Open in your IDE?
  1. <?php
  2. namespace AdminBundle\Services;
  3. use AdminBundle\Entity\Booking;
  4. use Psr\Log\LoggerInterface;
  5. use Psr\Log\NullLogger;
  6. /**
  7. * Webhook Service - Trimite evenimente booking către Zendesk Bridge
  8. *
  9. * INSTALARE: Copy-paste acest fișier în src/AdminBundle/Services/
  10. */
  11. class WebhookService
  12. {
  13. private $webhookUrl;
  14. private $webhookSecret;
  15. private $logger;
  16. private $enabled;
  17. public function __construct(
  18. string $webhookUrl,
  19. string $webhookSecret,
  20. bool $enabled = true,
  21. LoggerInterface $logger = null
  22. ) {
  23. $this->webhookUrl = $webhookUrl;
  24. $this->webhookSecret = $webhookSecret;
  25. $this->enabled = $enabled;
  26. $this->logger = $logger ?: new NullLogger();
  27. }
  28. /**
  29. * Trimite webhook pentru un eveniment booking
  30. */
  31. public function send(Booking $booking, string $event): bool
  32. {
  33. if (!$this->enabled) {
  34. return true;
  35. }
  36. $payload = $this->buildPayload($booking, $event);
  37. $jsonPayload = json_encode($payload);
  38. $signature = $this->generateSignature($jsonPayload);
  39. try {
  40. $ch = curl_init($this->webhookUrl);
  41. curl_setopt_array($ch, [
  42. CURLOPT_POST => true,
  43. CURLOPT_POSTFIELDS => $jsonPayload,
  44. CURLOPT_HTTPHEADER => [
  45. 'Content-Type: application/json',
  46. 'X-Webhook-Signature: ' . $signature,
  47. ],
  48. CURLOPT_RETURNTRANSFER => true,
  49. CURLOPT_TIMEOUT => 10,
  50. CURLOPT_CONNECTTIMEOUT => 5,
  51. ]);
  52. $response = curl_exec($ch);
  53. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  54. $error = curl_error($ch);
  55. curl_close($ch);
  56. if ($httpCode === 200) {
  57. $this->logger->info('Webhook sent', [
  58. 'booking' => $booking->getKey(),
  59. 'event' => $event,
  60. ]);
  61. return true;
  62. }
  63. $this->logger->error('Webhook failed', [
  64. 'booking' => $booking->getKey(),
  65. 'event' => $event,
  66. 'httpCode' => $httpCode,
  67. 'error' => $error,
  68. 'response' => $response,
  69. ]);
  70. return false;
  71. } catch (\Exception $e) {
  72. $this->logger->error('Webhook exception', [
  73. 'booking' => $booking->getKey(),
  74. 'event' => $event,
  75. 'exception' => $e->getMessage(),
  76. ]);
  77. return false;
  78. }
  79. }
  80. /**
  81. * Construiește payload-ul webhook cu toate datele necesare
  82. */
  83. private function buildPayload(Booking $booking, string $event): array
  84. {
  85. $payload = [
  86. 'key' => $booking->getKey(),
  87. 'event' => $event,
  88. 'data' => [
  89. 'booking' => [
  90. 'client_name' => trim($booking->getClientFirstName() . ' ' . $booking->getClientLastName()),
  91. 'client_email' => $booking->getClientEmail(),
  92. 'client_phone' => $booking->getClientPhone(),
  93. 'pickup_address' => $booking->getPickUpAddress(),
  94. 'pickup_datetime' => $booking->getPickUpDateTime()
  95. ? $booking->getPickUpDateTime()->format('c')
  96. : null,
  97. 'destination' => $booking->getDestinationAddress(),
  98. 'flight_number' => $booking->getFlightNumber(),
  99. 'notes' => $booking->getNotes(),
  100. 'price' => $booking->getOverridePrice(),
  101. 'payment_type' => $this->getPaymentTypeName($booking->getPaymentType()),
  102. ],
  103. ],
  104. ];
  105. // Adaugă date șofer dacă e alocat
  106. $driver = $booking->getDriver();
  107. if ($driver) {
  108. $payload['data']['driver'] = [
  109. 'id' => $driver->getId(),
  110. 'name' => $driver->getInternalName() ?: $driver->getFullName(),
  111. 'phone' => $driver->getPhone() ?: $driver->getMobilePhone(),
  112. ];
  113. $car = $driver->getCar();
  114. if ($car) {
  115. $payload['data']['driver']['vehicle'] = trim($car->getMaker() . ' ' . $car->getModel());
  116. $payload['data']['driver']['registration'] = $car->getPlateNumber();
  117. }
  118. }
  119. return $payload;
  120. }
  121. /**
  122. * Generează semnătura HMAC-SHA256
  123. */
  124. private function generateSignature(string $payload): string
  125. {
  126. $timestamp = time();
  127. $signedPayload = $timestamp . '.' . $payload;
  128. $signature = hash_hmac('sha256', $signedPayload, $this->webhookSecret);
  129. return "t={$timestamp},v1={$signature}";
  130. }
  131. /**
  132. * Convertește tipul de plată în nume
  133. */
  134. private function getPaymentTypeName(?int $type): string
  135. {
  136. $types = [
  137. 1 => 'Cash',
  138. 2 => 'Card',
  139. 3 => 'Account',
  140. 4 => 'Invoice',
  141. ];
  142. return $types[$type] ?? 'Unknown';
  143. }
  144. // =========================================
  145. // METODE HELPER - APELEAZĂ DIN EVENT LISTENER
  146. // =========================================
  147. public function onDispatched(Booking $booking): bool
  148. {
  149. return $this->send($booking, 'dispatched');
  150. }
  151. public function onAccepted(Booking $booking): bool
  152. {
  153. return $this->send($booking, 'accepted');
  154. }
  155. public function onEnroute(Booking $booking): bool
  156. {
  157. return $this->send($booking, 'enroute');
  158. }
  159. public function onArrived(Booking $booking): bool
  160. {
  161. return $this->send($booking, 'arrived');
  162. }
  163. public function onStarted(Booking $booking): bool
  164. {
  165. return $this->send($booking, 'started');
  166. }
  167. public function onCompleted(Booking $booking): bool
  168. {
  169. return $this->send($booking, 'completed');
  170. }
  171. public function onCancelled(Booking $booking): bool
  172. {
  173. return $this->send($booking, 'cancelled');
  174. }
  175. public function onNoShow(Booking $booking): bool
  176. {
  177. return $this->send($booking, 'no_show');
  178. }
  179. }