Kaynağa Gözat

refactor: services de payment agora lidam com package services, adicionadas novas exceptions customs + messages e try catch nos controllers, ajuste em payloads de arrays para usar data_get, rename de cart para package service e restricao para somente um mesmo provider estar nessas schedules

Gustavo Mantovani 1 hafta önce
ebeveyn
işleme
69ee44049e
100 değiştirilmiş dosya ile 1568 ekleme ve 1557 silme
  1. 2 2
      .env.example
  2. 7 7
      app/Broadcasting/Entity/WebsocketEventData.php
  3. 54 39
      app/Commands/CreateCrud.php
  4. 7 1
      app/Commands/RefreshPermissions.php
  5. 5 5
      app/Commands/TestWebsocketEvent.php
  6. 3 3
      app/Data/Pagarme/Order/OrderResponseData.php
  7. 9 17
      app/Data/Pagarme/PagarmeResponseData.php
  8. 12 0
      app/Enums/PaymentStatusEnum.php
  9. 1 1
      app/Enums/ServicePackageStatusEnum.php
  10. 9 0
      app/Exceptions/PaymentException.php
  11. 9 0
      app/Exceptions/PaymentFailedException.php
  12. 9 0
      app/Exceptions/ScheduleStatusTransitionException.php
  13. 42 46
      app/Http/Controllers/AuthController.php
  14. 5 0
      app/Http/Controllers/BankController.php
  15. 0 82
      app/Http/Controllers/CartController.php
  16. 0 50
      app/Http/Controllers/CartItemController.php
  17. 2 2
      app/Http/Controllers/ChatbotController.php
  18. 1 4
      app/Http/Controllers/ClientCalendarController.php
  19. 2 2
      app/Http/Controllers/ClientController.php
  20. 28 2
      app/Http/Controllers/Controller.php
  21. 18 36
      app/Http/Controllers/CustomScheduleController.php
  22. 5 12
      app/Http/Controllers/DashboardController.php
  23. 38 66
      app/Http/Controllers/PaymentController.php
  24. 1 4
      app/Http/Controllers/ProviderCalendarController.php
  25. 2 2
      app/Http/Controllers/ProviderController.php
  26. 2 2
      app/Http/Controllers/ProviderWithdrawalController.php
  27. 1 1
      app/Http/Controllers/ProviderWorkingDayController.php
  28. 1 1
      app/Http/Controllers/ReviewController.php
  29. 12 43
      app/Http/Controllers/ScheduleController.php
  30. 1 4
      app/Http/Controllers/SearchController.php
  31. 82 0
      app/Http/Controllers/ServicePackageController.php
  32. 50 0
      app/Http/Controllers/ServicePackageItemController.php
  33. 1 1
      app/Http/Controllers/SupportRequestController.php
  34. 19 15
      app/Http/Controllers/UserController.php
  35. 5 2
      app/Http/Middleware/CheckPermission.php
  36. 40 30
      app/Http/Middleware/PerformanceMonitor.php
  37. 1 0
      app/Http/Middleware/SetUserLanguage.php
  38. 23 0
      app/Http/Requests/PayScheduleRequest.php
  39. 23 0
      app/Http/Requests/PayServicePackageRequest.php
  40. 2 9
      app/Http/Requests/ScheduleRequest.php
  41. 5 5
      app/Http/Requests/ServicePackageItemRequest.php
  42. 4 3
      app/Http/Requests/ServicePackageRequest.php
  43. 3 3
      app/Http/Resources/AuthResource.php
  44. 0 37
      app/Http/Resources/CartItemResource.php
  45. 0 4
      app/Http/Resources/CityResource.php
  46. 2 2
      app/Http/Resources/ClientFavoriteProviderResource.php
  47. 4 4
      app/Http/Resources/ClientProviderBlockResource.php
  48. 0 4
      app/Http/Resources/CountryResource.php
  49. 12 12
      app/Http/Resources/DashboardClienteResource.php
  50. 8 8
      app/Http/Resources/DashboardPrestadorResource.php
  51. 0 4
      app/Http/Resources/ImprovementTypeResource.php
  52. 0 4
      app/Http/Resources/MediaResource.php
  53. 10 33
      app/Http/Resources/PaymentResource.php
  54. 2 2
      app/Http/Resources/PaymentSplitResource.php
  55. 0 4
      app/Http/Resources/ProviderResource.php
  56. 1 1
      app/Http/Resources/ReviewResource.php
  57. 21 21
      app/Http/Resources/ScheduleResource.php
  58. 37 0
      app/Http/Resources/ServicePackageItemResource.php
  59. 5 5
      app/Http/Resources/ServicePackageResource.php
  60. 0 4
      app/Http/Resources/ServiceTypeResource.php
  61. 0 4
      app/Http/Resources/SpecialityResource.php
  62. 0 4
      app/Http/Resources/StateResource.php
  63. 0 4
      app/Http/Resources/SupportRequestResource.php
  64. 29 3
      app/Models/Payment.php
  65. 20 20
      app/Models/ServicePackage.php
  66. 15 15
      app/Models/ServicePackageItem.php
  67. 1 1
      app/Rules/ScheduleBusinessRules.php
  68. 5 5
      app/Services/AddressService.php
  69. 34 34
      app/Services/AuthService.php
  70. 0 133
      app/Services/CartItemService.php
  71. 4 4
      app/Services/ClientPaymentMethodService.php
  72. 2 2
      app/Services/ClientProviderBlockService.php
  73. 35 35
      app/Services/ClientService.php
  74. 51 51
      app/Services/CustomScheduleService.php
  75. 41 41
      app/Services/DashboardService.php
  76. 3 3
      app/Services/DeviceTokenService.php
  77. 4 5
      app/Services/GeminiService.php
  78. 7 7
      app/Services/Pagarme/Concerns/FormatsPagarmeData.php
  79. 4 4
      app/Services/Pagarme/Concerns/SendsPagarmeRequests.php
  80. 12 12
      app/Services/Pagarme/PagarmeCustomerService.php
  81. 55 50
      app/Services/Pagarme/PagarmePaymentService.php
  82. 47 47
      app/Services/Pagarme/PagarmeRecipientService.php
  83. 1 1
      app/Services/Pagarme/PagarmeTransferService.php
  84. 157 119
      app/Services/PaymentService.php
  85. 2 2
      app/Services/ProviderClientBlockService.php
  86. 65 65
      app/Services/ProviderService.php
  87. 13 13
      app/Services/ProviderWithdrawalService.php
  88. 2 2
      app/Services/ReviewImprovementService.php
  89. 22 22
      app/Services/ReviewService.php
  90. 0 57
      app/Services/ScheduleCartService.php
  91. 56 42
      app/Services/ScheduleService.php
  92. 11 11
      app/Services/SearchService.php
  93. 134 0
      app/Services/ServicePackageItemService.php
  94. 31 31
      app/Services/ServicePackageService.php
  95. 1 1
      app/Services/SupportRequestService.php
  96. 7 7
      app/Services/UserService.php
  97. 31 31
      app/Services/WebhookService.php
  98. 16 16
      app/Services/ZipCodeCoordinatesService.php
  99. 1 1
      app/Tasks/DeleteExpiredTokens.php
  100. 1 1
      app/Tasks/SendPushNotificationsTask.php

+ 2 - 2
.env.example

@@ -51,7 +51,7 @@ PAGARME_PLATFORM_RECIPIENT_ID=
 PAGARME_PIX_DISABLE_SPLIT=false
 PAGARME_PLATFORM_PIX_FEE_RATE=0.12
 PAGARME_PLATFORM_CREDIT_CARD_FEE_RATE=0.16
-PAGARME_PLATFORM_CART_MIN_3_SCHEDULES_FEE_RATE=0.10
+PAGARME_PLATFORM_SERVICE_PACKAGE_MIN_3_SCHEDULES_FEE_RATE=0.10
 PAGARME_TRANSFER_FEE_AMOUNT=3.67
 PAGARME_WITHDRAWAL_RELEASE_DAYS=2
 
@@ -62,4 +62,4 @@ GOOGLE_GEOCODING_BASE_URL=https://maps.googleapis.com
 GOOGLE_GEOCODING_API_KEY=
 GOOGLE_GEOCODING_CONNECT_TIMEOUT=2
 GOOGLE_GEOCODING_TIMEOUT=5
-GOOGLE_GEOCODING_CACHE_TTL=2592000
+GOOGLE_GEOCODING_CACHE_TTL=2592000

+ 7 - 7
app/Broadcasting/Entity/WebsocketEventData.php

@@ -5,21 +5,21 @@ namespace App\Broadcasting\Entity;
 final readonly class WebsocketEventData
 {
     public function __construct(
-        public string $room,
-        public mixed $data,
-        public string $event = 'event',
+        public string  $room,
+        public mixed   $data,
+        public string  $event       = 'event',
         public ?string $projectName = null
     ) {}
 
     public static function from(
         string $room,
-        mixed $data,
+        mixed  $data,
         string $event = 'event'
     ): self {
         return new self(
-            room: $room,
-            data: $data,
-            event: $event,
+            room:        $room,
+            data:        $data,
+            event:       $event,
             projectName: config('app.name')
         );
     }

+ 54 - 39
app/Commands/CreateCrud.php

@@ -26,9 +26,12 @@ class CreateCrud extends Command
     public function __construct(Filesystem $files)
     {
         parent::__construct();
+
         $this->files = $files;
     }
 
+    //
+
     public function handle(): void
     {
         $name = Str::studly($this->argument('name'));
@@ -80,48 +83,31 @@ class CreateCrud extends Command
         if ($createAll) {
             $this->newLine();
             $this->info('Next steps:');
+
             $this->info(
                 "1. Update the migration: database/migrations/*_create_{$this->getTableName(
                     $name,
                 )}_table.php",
             );
+
             $this->info(
                 "2. Update the model properties in: app/Models/{$name}.php",
             );
+
             $this->info(
                 "3. Update validation rules in: app/Http/Requests/{$name}Request.php",
             );
+
             $this->info(
                 "4. Update resource fields in: app/Http/Resources/{$name}Resource.php",
             );
+
             $this->info('5. Run: php artisan migrate');
             $this->info('6. Run: php artisan ide-helper:models -RW');
         }
     }
 
-    protected function createModel(string $name): void
-    {
-        $modelTemplate = str_replace(
-            ['{{modelName}}', '{{tableNameSnakeCase}}'],
-            [$name, $this->getTableName($name)],
-            $this->getStub('Model'),
-        );
-
-        $this->put("app/Models/{$name}.php", $modelTemplate);
-        $this->info("✓ Created Model: app/Models/{$name}.php");
-    }
-
-    protected function createService(string $name): void
-    {
-        $serviceTemplate = str_replace(
-            ['{{modelName}}'],
-            [$name],
-            $this->getStub('Service'),
-        );
-
-        $this->put("app/Services/{$name}Service.php", $serviceTemplate);
-        $this->info("✓ Created Service: app/Services/{$name}Service.php");
-    }
+    //
 
     protected function createController(string $name): void
     {
@@ -135,11 +121,41 @@ class CreateCrud extends Command
             "app/Http/Controllers/{$name}Controller.php",
             $controllerTemplate,
         );
+
         $this->info(
             "✓ Created Controller: app/Http/Controllers/{$name}Controller.php",
         );
     }
 
+    protected function createMigration(string $name): void
+    {
+        $tableName = $this->getTableName($name);
+
+        $migrationTemplate = str_replace(
+            ['{{tableNameSnakeCase}}'],
+            [$tableName],
+            $this->getStub('Migration'),
+        );
+
+        $timestamp = date('Y_m_d_His');
+        $filename  = $timestamp."_create_{$tableName}_table.php";
+
+        $this->put(database_path("migrations/{$filename}"), $migrationTemplate);
+        $this->info("✓ Created Migration: database/migrations/{$filename}");
+    }
+
+    protected function createModel(string $name): void
+    {
+        $modelTemplate = str_replace(
+            ['{{modelName}}', '{{tableNameSnakeCase}}'],
+            [$name, $this->getTableName($name)],
+            $this->getStub('Model'),
+        );
+
+        $this->put("app/Models/{$name}.php", $modelTemplate);
+        $this->info("✓ Created Model: app/Models/{$name}.php");
+    }
+
     protected function createRequest(string $name): void
     {
         $requestTemplate = str_replace(
@@ -161,6 +177,7 @@ class CreateCrud extends Command
         );
 
         $this->put("app/Http/Resources/{$name}Resource.php", $resourceTemplate);
+
         $this->info(
             "✓ Created Resource: app/Http/Resources/{$name}Resource.php",
         );
@@ -187,38 +204,36 @@ class CreateCrud extends Command
         }
 
         $this->put($routePath, $routeTemplate);
+
         $this->info(
             '✓ Created Route: routes/authRoutes/'.Str::snake($name).'.php',
         );
     }
 
-    protected function createMigration(string $name): void
+    protected function createService(string $name): void
     {
-        $tableName = $this->getTableName($name);
-
-        $migrationTemplate = str_replace(
-            ['{{tableNameSnakeCase}}'],
-            [$tableName],
-            $this->getStub('Migration'),
+        $serviceTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Service'),
         );
 
-        $timestamp = date('Y_m_d_His');
-        $filename = $timestamp."_create_{$tableName}_table.php";
-
-        $this->put(database_path("migrations/{$filename}"), $migrationTemplate);
-        $this->info("✓ Created Migration: database/migrations/{$filename}");
+        $this->put("app/Services/{$name}Service.php", $serviceTemplate);
+        $this->info("✓ Created Service: app/Services/{$name}Service.php");
     }
 
-    protected function getTableName(string $name): string
-    {
-        return Str::snake(Str::plural($name));
-    }
+    //
 
     protected function getStub(string $type): string
     {
         return $this->files->get(storage_path("stubs/{$type}.stub"));
     }
 
+    protected function getTableName(string $name): string
+    {
+        return Str::snake(Str::plural($name));
+    }
+
     protected function put(string $path, string $contents): void
     {
         $this->files->put($path, $contents);

+ 7 - 1
app/Commands/RefreshPermissions.php

@@ -42,17 +42,23 @@ class RefreshPermissions extends Command
 
         try {
             // Run the Permission seeder
+
             $this->info('Running Permission seeder...');
+
             Permission::truncate();
+
             $this->permissionSeeder->run();
             $this->info('Permission seeder completed successfully.');
 
             // Run the UserTypePermission seeder
+
             $this->info('Running UserTypePermission seeder...');
+
             UserTypePermission::truncate();
+
             $this->userTypePermissionSeeder->run();
-            $this->info('UserTypePermission seeder completed successfully.');
 
+            $this->info('UserTypePermission seeder completed successfully.');
             $this->info('Permissions refresh completed successfully!');
 
             return Command::SUCCESS;

+ 5 - 5
app/Commands/TestWebsocketEvent.php

@@ -18,21 +18,23 @@ final class TestWebsocketEvent extends Command
     public function handle(): int
     {
         try {
-            $room = $this->argument('room');
+            $room  = $this->argument('room');
             $event = $this->option('event');
+
             $data = $this->option('data')
                 ? json_decode($this->option('data'), true, 512, JSON_THROW_ON_ERROR)
                 : ['message' => 'Test message'];
 
             $eventData = WebsocketEventData::from(
-                room: $room,
-                data: $data,
+                room:  $room,
+                data:  $data,
                 event: $event
             );
 
             event(new WebsocketEvent($eventData));
 
             $this->info('Event broadcasted successfully');
+
             $this->table(
                 ['Component', 'Value'],
                 [
@@ -45,11 +47,9 @@ final class TestWebsocketEvent extends Command
             return self::SUCCESS;
         } catch (\JsonException $e) {
             $this->error('Invalid JSON data provided');
-
             return self::FAILURE;
         } catch (\Throwable $e) {
             $this->error("Error broadcasting event: {$e->getMessage()}");
-
             return self::FAILURE;
         }
     }

+ 3 - 3
app/Data/Pagarme/Order/OrderResponseData.php

@@ -59,7 +59,7 @@ final readonly class OrderResponseData extends PagarmeResponseData
             return null;
         }
 
-        $code = $this->filledValue($transaction->gatewayResponse['code'] ?? null);
+        $code = $this->filledValue(data_get($transaction->gatewayResponse, 'code'));
 
         if ($code === null || $this->isMisleadingFailureCode($code)) {
             return $this->filledValue($transaction->status);
@@ -82,7 +82,7 @@ final readonly class OrderResponseData extends PagarmeResponseData
             return $acquirerMessage;
         }
 
-        $gatewayErrors = $transaction->gatewayResponse['errors'] ?? [];
+        $gatewayErrors = data_get($transaction->gatewayResponse, 'errors', []);
 
         if (! is_array($gatewayErrors) || empty($gatewayErrors)) {
             return $this->failureMessageFromStatus($transaction->status);
@@ -102,7 +102,7 @@ final readonly class OrderResponseData extends PagarmeResponseData
 
     public function firstCharge(): ?ChargeData
     {
-        return $this->charges[0] ?? null;
+        return data_get($this->charges, 0);
     }
 
     public function gatewayEntityLabel(): string

+ 9 - 17
app/Data/Pagarme/PagarmeResponseData.php

@@ -12,47 +12,39 @@ abstract readonly class PagarmeResponseData
 
     protected static function arrGet(array $payload, int|string $key, mixed $default = null): mixed
     {
-        return array_key_exists($key, $payload) ? $payload[$key] : $default;
+        return data_get($payload, $key, $default);
     }
 
     protected static function arrInt(array $payload, string $key): ?int
     {
-        if (! array_key_exists($key, $payload)) {
+        $value = data_get($payload, $key);
+
+        if ($value === null) {
             return null;
         }
 
-        return (int) $payload[$key];
+        return (int) $value;
     }
 
     protected static function arrString(array $payload, string $key): ?string
     {
-        if (! array_key_exists($key, $payload)) {
-            return null;
-        }
-
-        $value = $payload[$key];
+        $value = data_get($payload, $key);
 
         return $value !== null && $value !== '' ? (string) $value : null;
     }
 
     protected static function arrBool(array $payload, string $key): ?bool
     {
-        if (! array_key_exists($key, $payload)) {
-            return null;
-        }
-
-        $value = $payload[$key];
+        $value = data_get($payload, $key);
 
         return $value !== null && $value !== '' ? (bool) $value : null;
     }
 
     protected static function arrArray(array $payload, string $key): array
     {
-        if (! array_key_exists($key, $payload) || ! is_array($payload[$key])) {
-            return [];
-        }
+        $value = data_get($payload, $key, []);
 
-        return $payload[$key];
+        return is_array($value) ? $value : [];
     }
 
     protected static function arrMap(array $payload, string $key, callable $fn): array

+ 12 - 0
app/Enums/PaymentStatusEnum.php

@@ -14,4 +14,16 @@ enum PaymentStatusEnum: string
     case PAID        = 'paid';
     case FAILED      = 'failed';
     case CANCELLED   = 'cancelled';
+
+    public function message(): string
+    {
+        return match ($this) {
+            self::PAID       => __('messages.payment_confirmed'),
+            self::AUTHORIZED => __('messages.payment_pending_confirmation'),
+            self::PROCESSING => __('messages.payment_pending_confirmation'),
+            self::PENDING    => __('messages.payment_pending_confirmation'),
+            self::FAILED     => __('messages.payment_not_confirmed'),
+            default          => __('messages.created'),
+        };
+    }
 }

+ 1 - 1
app/Enums/CartStatusEnum.php → app/Enums/ServicePackageStatusEnum.php

@@ -4,7 +4,7 @@ namespace App\Enums;
 
 use App\Traits\EnumHelper;
 
-enum CartStatusEnum: string
+enum ServicePackageStatusEnum: string
 {
     use EnumHelper;
 

+ 9 - 0
app/Exceptions/PaymentException.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Exceptions;
+
+use RuntimeException;
+
+class PaymentException extends RuntimeException
+{
+}

+ 9 - 0
app/Exceptions/PaymentFailedException.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Exceptions;
+
+use RuntimeException;
+
+class PaymentFailedException extends RuntimeException
+{
+}

+ 9 - 0
app/Exceptions/ScheduleStatusTransitionException.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Exceptions;
+
+use RuntimeException;
+
+class ScheduleStatusTransitionException extends RuntimeException
+{
+}

+ 42 - 46
app/Http/Controllers/AuthController.php

@@ -20,8 +20,8 @@ class AuthController extends Controller
         $validated = $request->validated();
 
         $result = $this->authService->login(
-            email: $validated['email'],
-            password: $validated['password'],
+            email:    data_get($validated, 'email'),
+            password: data_get($validated, 'password'),
         );
 
         if (! $result) {
@@ -29,12 +29,12 @@ class AuthController extends Controller
         }
 
         return $this->successResponse(
-            payload: new AuthResource($result['payload']),
+            payload: new AuthResource(data_get($result, 'payload')),
             message: __('auth.logged_in'),
         )->withCookie(
             cookie(
                 'refresh_token',
-                $result['refreshToken'],
+                data_get($result, 'refreshToken'),
                 config('sanctum.rt_expiration') * 60,
                 '/',
                 config('session.domain'),
@@ -51,8 +51,8 @@ class AuthController extends Controller
         $validated = $request->validated();
 
         $result = $this->authService->login(
-            email: $validated['email'],
-            password: $validated['password'],
+            email:    data_get($validated, 'email'),
+            password: data_get($validated, 'password'),
         );
 
         if (! $result) {
@@ -61,8 +61,8 @@ class AuthController extends Controller
 
         return $this->successResponse(
             payload: new AuthResource([
-                ...$result['payload'],
-                'refresh_token' => $result['refreshToken'],
+                ...data_get($result, 'payload'),
+                'refresh_token' => data_get($result, 'refreshToken'),
             ]),
             message: __('auth.logged_in'),
         );
@@ -93,17 +93,16 @@ class AuthController extends Controller
 
         if (is_null($result)) {
             return $this->errorResponse(
-                message: __('auth.unauthorized'),
-                code: 403,
+                message: __('auth.unauthorized'), code: 403,
             )->withoutCookie('refresh_token');
         }
 
         return $this->successResponse(
-            payload: new AuthResource($result['payload']),
+            payload: new AuthResource(data_get($result, 'payload')),
         )->withCookie(
             cookie(
                 'refresh_token',
-                $result['refreshToken'],
+                data_get($result, 'refreshToken'),
                 config('sanctum.rt_expiration') * 60,
                 '/',
                 config('session.domain'),
@@ -122,22 +121,21 @@ class AuthController extends Controller
         if (is_null($refresh_token)) {
             return $this->errorResponse(code: 403);
         }
+
         $result = $this->authService->refresh(
-            $refresh_token,
-            isApp: true,
+            $refresh_token, isApp: true,
         );
 
         if (is_null($result)) {
             return $this->errorResponse(
-                message: __('auth.unauthorized'),
-                code: 403,
+                message: __('auth.unauthorized'), code: 403,
             );
         }
 
         return $this->successResponse(
             payload: new AuthResource([
-                ...$result['payload'],
-                'refresh_token' => $result['refreshToken'],
+                ...data_get($result, 'payload'),
+                'refresh_token' => data_get($result, 'refreshToken'),
             ]),
         );
     }
@@ -146,13 +144,13 @@ class AuthController extends Controller
     {
         $result = $this->authService->clientSendCode($request->validated());
 
-        if (is_array($result) && isset($result['error'])) {
-            return $this->errorResponse(message: __("auth.{$result['error']}"), code: 403);
+        if (is_array($result) && data_get($result, 'error') !== null) {
+            return $this->errorResponse(message: __('auth.'.data_get($result, 'error')), code: 403);
         }
 
         return $this->successResponse(
             message: __('messages.code_sent'),
-            code: 201,
+            code:    201,
             payload: ['isLogin' => $result],
         );
     }
@@ -161,22 +159,22 @@ class AuthController extends Controller
     {
         $result = $this->authService->providerSendCode($request->validated());
 
-        if (is_array($result) && isset($result['error'])) {
-            return $this->errorResponse(message: __("auth.{$result['error']}"), code: 403);
+        if (is_array($result) && data_get($result, 'error') !== null) {
+            return $this->errorResponse(message: __('auth.'.data_get($result, 'error')), code: 403);
         }
 
         return $this->successResponse(
             message: __('messages.code_sent'),
-            code: 201,
+            code:    201,
             payload: ['isLogin' => $result],
         );
     }
 
     public function validateCodeClient(UserAppsValidateCodeRequest $request): JsonResponse
     {
-        $email = $request->input('email');
-        $phone = $request->input('phone');
-        $code = $request->input('code');
+        $email   = $request->input('email');
+        $phone   = $request->input('phone');
+        $code    = $request->input('code');
         $isLogin = (bool) $request->input('isLogin', false);
 
         $result = $this->authService->validateCodeClient($request->validated(), $isLogin);
@@ -185,13 +183,13 @@ class AuthController extends Controller
             return $this->errorResponse(message: __('auth.invalid_code'), code: 400);
         }
 
-        if (is_array($result) && isset($result['error'])) {
-            return $this->errorResponse(message: __("auth.{$result['error']}"), code: 403);
+        if (is_array($result) && data_get($result, 'error') !== null) {
+            return $this->errorResponse(message: __('auth.'.data_get($result, 'error')), code: 403);
         }
 
         if ($isLogin) {
             return $this->successResponse(
-                payload: new AuthResource([...$result['payload'], 'refresh_token' => $result['refreshToken']]),
+                payload: new AuthResource([...data_get($result, 'payload'), 'refresh_token' => data_get($result, 'refreshToken')]),
                 message: __('auth.logged_in'),
             );
         }
@@ -204,9 +202,9 @@ class AuthController extends Controller
 
     public function validateCodeProvider(UserAppsValidateCodeRequest $request): JsonResponse
     {
-        $email = $request->input('email');
-        $phone = $request->input('phone');
-        $code = $request->input('code');
+        $email   = $request->input('email');
+        $phone   = $request->input('phone');
+        $code    = $request->input('code');
         $isLogin = (bool) $request->input('isLogin', false);
 
         $result = $this->authService->validateCodeProvider($request->validated(), $isLogin);
@@ -215,13 +213,13 @@ class AuthController extends Controller
             return $this->errorResponse(message: __('auth.invalid_code'), code: 400);
         }
 
-        if (is_array($result) && isset($result['error'])) {
-            return $this->errorResponse(message: __("auth.{$result['error']}"), code: 403);
+        if (is_array($result) && data_get($result, 'error') !== null) {
+            return $this->errorResponse(message: __('auth.'.data_get($result, 'error')), code: 403);
         }
 
         if ($isLogin) {
             return $this->successResponse(
-                payload: new AuthResource([...$result['payload'], 'refresh_token' => $result['refreshToken']]),
+                payload: new AuthResource([...data_get($result, 'payload'), 'refresh_token' => data_get($result, 'refreshToken')]),
                 message: __('auth.logged_in'),
             );
         }
@@ -235,25 +233,24 @@ class AuthController extends Controller
     public function validateCode(UserAppsValidateCodeRequest $request): JsonResponse
     {
         try {
-            $email = $request->input('email');
-            $phone = $request->input('phone');
-            $code = $request->input('code');
+            $email   = $request->input('email');
+            $phone   = $request->input('phone');
+            $code    = $request->input('code');
             $isLogin = $request->input('isLogin');
 
             $result = $this->authService->validateCode($request->validated(), $isLogin);
 
             if (! $result) {
                 return $this->errorResponse(
-                    message: __('auth.invalid_code'),
-                    code: 400,
+                    message: __('auth.invalid_code'), code: 400,
                 );
             }
 
             if ($isLogin) {
                 return $this->successResponse(
                     payload: new AuthResource([
-                        ...$result['payload'],
-                        'refresh_token' => $result['refreshToken'],
+                        ...data_get($result, 'payload'),
+                        'refresh_token' => data_get($result, 'refreshToken'),
                     ]),
                     message: __('auth.logged_in'),
                 );
@@ -261,14 +258,13 @@ class AuthController extends Controller
                 return $this->successResponse(
                     payload: ['email' => $email, 'phone' => $phone, 'code' => $code],
                     message: __('auth.valid_code'),
-                    code: 200,
+                    code:    200,
                 );
             }
 
         } catch (\Exception $e) {
             return $this->errorResponse(
-                message: __('auth.validation_error'),
-                code: 500,
+                message: __('auth.validation_error'), code: 500, exception: $e,
             );
         }
     }

+ 5 - 0
app/Http/Controllers/BankController.php

@@ -16,30 +16,35 @@ class BankController extends Controller
     public function index(): JsonResponse
     {
         $items = $this->service->getAll();
+
         return $this->successResponse(payload: BankResource::collection($items));
     }
 
     public function store(BankRequest $request): JsonResponse
     {
         $item = $this->service->create($request->validated());
+
         return $this->successResponse(payload: new BankResource($item), message: __('messages.created'), code: 201);
     }
 
     public function show(int $id): JsonResponse
     {
         $item = $this->service->findById($id);
+
         return $this->successResponse(payload: new BankResource($item));
     }
 
     public function update(BankRequest $request, int $id): JsonResponse
     {
         $item = $this->service->update($id, $request->validated());
+
         return $this->successResponse(payload: new BankResource($item), message: __('messages.updated'));
     }
 
     public function destroy(int $id): JsonResponse
     {
         $this->service->delete($id);
+
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
 }

+ 0 - 82
app/Http/Controllers/CartController.php

@@ -1,82 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Http\Requests\CartRequest;
-use App\Http\Resources\CartResource;
-use App\Services\CartService;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Auth;
-
-class CartController extends Controller
-{
-    public function __construct(
-        protected CartService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-
-        return $this->successResponse(payload: CartResource::collection($items));
-    }
-
-    public function store(CartRequest $request): JsonResponse
-    {
-        $validated = $request->validated();
-
-        $item = $this->hasSchedulePayload($validated)
-            ? $this->service->createWithSchedules($validated)
-            : $this->service->create($validated);
-
-        return $this->successResponse(payload: new CartResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-
-        return $this->successResponse(payload: new CartResource($item));
-    }
-
-    public function update(CartRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-
-        return $this->successResponse(payload: new CartResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-
-    //
-
-    public function myCarts(): JsonResponse
-    {
-        $items = $this->service->getByUserId(Auth::id());
-
-        return $this->successResponse(payload: CartResource::collection($items));
-    }
-
-    public function storeMine(CartRequest $request): JsonResponse
-    {
-        $request->merge(['client_id' => Auth::user()?->client?->id]);
-
-        $item = $this->service->createForUser($request->validated(), Auth::id());
-
-        return $this->successResponse(payload: new CartResource($item), message: __('messages.created'), code: 201);
-    }
-
-    //
-
-    private function hasSchedulePayload(array $data): bool
-    {
-        return ! empty($data['schedules'])
-            || ! empty($data['dates'])
-            || ! empty($data['date']);
-    }
-}

+ 0 - 50
app/Http/Controllers/CartItemController.php

@@ -1,50 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Http\Requests\CartItemRequest;
-use App\Http\Resources\CartItemResource;
-use App\Services\CartItemService;
-use Illuminate\Http\JsonResponse;
-
-class CartItemController extends Controller
-{
-    public function __construct(
-        protected CartItemService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-
-        return $this->successResponse(payload: CartItemResource::collection($items));
-    }
-
-    public function store(CartItemRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-
-        return $this->successResponse(payload: new CartItemResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-
-        return $this->successResponse(payload: new CartItemResource($item));
-    }
-
-    public function update(CartItemRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-
-        return $this->successResponse(payload: new CartItemResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 2 - 2
app/Http/Controllers/ChatbotController.php

@@ -25,9 +25,9 @@ class ChatbotController extends Controller
         $user = Auth::user();
 
         $reply = $this->geminiService->sendMessage(
-            message:  $validated['message'],
+            message:  data_get($validated, 'message'),
             userType: $user->type,
-            history:  $validated['history'] ?? [],
+            history:  data_get($validated, 'history', []),
         );
 
         return $this->successResponse(payload: ['reply' => $reply]);

+ 1 - 4
app/Http/Controllers/ClientCalendarController.php

@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
 
 use App\Services\ClientCalendarService;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Log;
 
 class ClientCalendarController extends Controller
 {
@@ -17,9 +16,7 @@ class ClientCalendarController extends Controller
 
             return $this->successResponse(payload: $dados);
         } catch (\Exception $e) {
-            Log::error('Error fetching client calendar: '.$e->getMessage());
-
-            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 }

+ 2 - 2
app/Http/Controllers/ClientController.php

@@ -72,8 +72,8 @@ class ClientController extends Controller
 
         return $this->successResponse(
             payload: new AuthResource([
-                ...$result['payload'],
-                'refresh_token' => $result['refreshToken'],
+                ...data_get($result, 'payload'),
+                'refresh_token' => data_get($result, 'refreshToken'),
             ]),
             message: __('auth.logged_in'),
         );

+ 28 - 2
app/Http/Controllers/Controller.php

@@ -3,6 +3,9 @@
 namespace App\Http\Controllers;
 
 use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Route;
+use Throwable;
 
 abstract class Controller
 {
@@ -11,8 +14,31 @@ abstract class Controller
         return response()->json(data: ['payload' => $payload, 'message' => $message], status: $code);
     }
 
-    public function errorResponse(?string $message = null, int $code = 404): JsonResponse
-    {
+    public function errorResponse(
+        ?string    $message   = null,
+        int        $code      = 404,
+        ?Throwable $exception = null,
+    ): JsonResponse {
+        if ($code >= 500) {
+            $route = Route::current();
+
+            Log::withContext([
+                'route'      => $route?->uri(),
+                'route_name' => $route?->getName(),
+                'controller' => $route?->getActionName(),
+                'method'     => request()->method(),
+            ]);
+
+            if ($exception) {
+                report($exception);
+            } else {
+                Log::error('Resposta de erro interno retornada pela API.', [
+                    'message' => $message,
+                    'code'    => $code,
+                ]);
+            }
+        }
+
         return response()->json(data: ['message' => $message], status: $code);
     }
 }

+ 18 - 36
app/Http/Controllers/CustomScheduleController.php

@@ -10,7 +10,6 @@ use App\Http\Requests\CustomScheduleVerifyCodeRequest;
 use App\Http\Resources\CustomScheduleResource;
 use App\Services\CustomScheduleService;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Log;
 
 class CustomScheduleController extends Controller
 {
@@ -38,14 +37,13 @@ class CustomScheduleController extends Controller
             $customSchedules = $this->customScheduleService->create($validated);
 
             $count = count($customSchedules);
-            $message = $count > 1
-              ? "{$count} oportunidades criadas com sucesso!"
-              : __('messages.created');
+
+            $message = trans_choice('messages.opportunities_created', $count, ['count' => $count]);
 
             return $this->successResponse(
                 payload: CustomScheduleResource::collection($customSchedules),
                 message: $message,
-                code: 201,
+                code:    201,
             );
         } catch (\Exception $e) {
             return $this->errorResponse($e->getMessage(), 422);
@@ -96,16 +94,14 @@ class CustomScheduleController extends Controller
             $providerId = $request->query('provider_id');
 
             if (! $providerId) {
-                return $this->errorResponse('Provider ID é obrigatório', 400);
+                return $this->errorResponse(__('messages.provider_id_required'), 400);
             }
 
             $opportunities = $this->customScheduleService->getAvailableOpportunities($providerId);
 
             return $this->successResponse($opportunities);
         } catch (\Exception $e) {
-            Log::error('Error fetching available opportunities: '.$e->getMessage());
-
-            return $this->errorResponse($e->getMessage(), 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 
@@ -116,9 +112,7 @@ class CustomScheduleController extends Controller
 
             return $this->successResponse($data);
         } catch (\Exception $e) {
-            Log::error('Error fetching provider proposals and opportunities: '.$e->getMessage());
-
-            return $this->errorResponse($e->getMessage(), 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 
@@ -136,9 +130,7 @@ class CustomScheduleController extends Controller
 
             return $this->successResponse($proposals);
         } catch (\Exception $e) {
-            Log::error('Error fetching opportunity proposals: '.$e->getMessage());
-
-            return $this->errorResponse($e->getMessage(), 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 
@@ -148,16 +140,14 @@ class CustomScheduleController extends Controller
             $providerId = $request->query('provider_id');
 
             if (! $providerId) {
-                return $this->errorResponse('Provider ID é obrigatório', 400);
+                return $this->errorResponse(__('messages.provider_id_required'), 400);
             }
 
             $proposals = $this->customScheduleService->getProviderProposals($providerId);
 
             return $this->successResponse($proposals);
         } catch (\Exception $e) {
-            Log::error('Error fetching provider proposals: '.$e->getMessage());
-
-            return $this->errorResponse($e->getMessage(), 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 
@@ -169,15 +159,13 @@ class CustomScheduleController extends Controller
             $providerId = $request->input('provider_id');
 
             if (! $providerId) {
-                return $this->errorResponse('Provider ID é obrigatório', 400);
+                return $this->errorResponse(__('messages.provider_id_required'), 400);
             }
 
             $proposal = $this->customScheduleService->proposeOpportunity($scheduleId, $providerId);
 
-            return $this->successResponse($proposal, 'Proposta enviada com sucesso!', 201);
+            return $this->successResponse($proposal, __('messages.proposal_sent'), 201);
         } catch (\Exception $e) {
-            Log::error('Error creating proposal: '.$e->getMessage());
-
             return $this->errorResponse($e->getMessage(), 400);
         }
     }
@@ -187,10 +175,8 @@ class CustomScheduleController extends Controller
         try {
             $schedule = $this->customScheduleService->acceptProposal($proposalId);
 
-            return $this->successResponse($schedule, 'Prestador aceito com sucesso!');
+            return $this->successResponse($schedule, __('messages.provider_accepted'));
         } catch (\Exception $e) {
-            Log::error('Error accepting proposal: '.$e->getMessage());
-
             return $this->errorResponse($e->getMessage(), 400);
         }
     }
@@ -200,23 +186,21 @@ class CustomScheduleController extends Controller
         try {
             $this->customScheduleService->refuseProposal($proposalId);
 
-            return $this->successResponse(null, 'Prestador recusado.');
+            return $this->successResponse(null, __('messages.provider_refused'));
         } catch (\Exception $e) {
-            Log::error('Error refusing proposal: '.$e->getMessage());
-
             return $this->errorResponse($e->getMessage(), 400);
         }
     }
 
+    //
+
     public function refuseOpportunity($scheduleId, CustomScheduleRefuseOpportunityRequest $request)
     {
         try {
             $this->customScheduleService->refuseOpportunity($scheduleId, $request->input('provider_id'));
 
-            return $this->successResponse(null, 'Oportunidade recusada.');
+            return $this->successResponse(null, __('messages.opportunity_refused'));
         } catch (\Exception $e) {
-            Log::error('Error refusing opportunity: '.$e->getMessage());
-
             return $this->errorResponse($e->getMessage(), 400);
         }
     }
@@ -228,11 +212,9 @@ class CustomScheduleController extends Controller
         try {
             $result = $this->customScheduleService->verifyScheduleCode($scheduleId, $request->input('code'));
 
-            return $this->successResponse($result, 'Código verificado com sucesso!');
+            return $this->successResponse($result, __('messages.code_verified'));
         } catch (\Exception $e) {
-            Log::error('Error verifying schedule code: '.$e->getMessage());
-
-            return $this->errorResponse($e->getMessage(), 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 }

+ 5 - 12
app/Http/Controllers/DashboardController.php

@@ -8,7 +8,6 @@ use App\Services\DashboardService;
 use Illuminate\Auth\Access\AuthorizationException;
 use Illuminate\Database\Eloquent\ModelNotFoundException;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Log;
 
 class DashboardController extends Controller
 {
@@ -25,11 +24,9 @@ class DashboardController extends Controller
         } catch (AuthorizationException $e) {
             return $this->errorResponse(message: $e->getMessage(), code: 403);
         } catch (ModelNotFoundException) {
-            return $this->errorResponse(message: 'Cliente não encontrado.', code: 404);
+            return $this->errorResponse(message: __('messages.client_not_found'), code: 404);
         } catch (\Exception $e) {
-            Log::error('Erro ao obter dados do dashboard do cliente: '.$e->getMessage());
-
-            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 
@@ -44,11 +41,9 @@ class DashboardController extends Controller
         } catch (AuthorizationException $e) {
             return $this->errorResponse(message: $e->getMessage(), code: 403);
         } catch (ModelNotFoundException) {
-            return $this->errorResponse(message: 'Prestador não encontrado.', code: 404);
+            return $this->errorResponse(message: __('messages.provider_not_found'), code: 404);
         } catch (\Exception $e) {
-            Log::error('Erro ao obter dados do dashboard do prestador: '.$e->getMessage());
-
-            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 
@@ -59,9 +54,7 @@ class DashboardController extends Controller
 
             return $this->successResponse(payload: $dados);
         } catch (\Exception $e) {
-            Log::error('Erro ao obter detalhes do schedule do cliente: '.$e->getMessage());
-
-            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 }

+ 38 - 66
app/Http/Controllers/PaymentController.php

@@ -3,9 +3,12 @@
 namespace App\Http\Controllers;
 
 use App\Enums\PaymentStatusEnum;
+use App\Exceptions\PaymentFailedException;
+use App\Exceptions\PaymentException;
+use App\Http\Requests\PayScheduleRequest;
+use App\Http\Requests\PayServicePackageRequest;
 use App\Http\Requests\PaymentRequest;
 use App\Http\Resources\PaymentResource;
-use App\Models\Cart;
 use App\Models\Schedule;
 use App\Services\PaymentService;
 use Illuminate\Http\JsonResponse;
@@ -31,7 +34,7 @@ class PaymentController extends Controller
 
         return $this->successResponse(
             payload: new PaymentResource($item),
-            message: $this->paymentMessage($item->status->value),
+            message: $item->status->message(),
             code:    201,
         );
     }
@@ -59,78 +62,58 @@ class PaymentController extends Controller
 
     //
 
-    public function paySchedule(Request $request, Schedule $schedule): JsonResponse
+    public function paySchedule(PayScheduleRequest $request, int $scheduleId): JsonResponse
     {
-        $validated = $request->validate([
-            'payment_method'           => ['required', 'in:credit_card,pix'],
-            'client_payment_method_id' => ['nullable', 'integer', 'exists:client_payment_methods,id'],
-            'card_id'                  => ['nullable', 'string', 'max:255'],
-            'phone'                    => ['nullable', 'string', 'max:20'],
-        ]);
+        $validated = $request->validated();
 
-        if ($schedule->client?->user_id !== Auth::id()) {
-            abort(403);
-        }
-
-        $item = $this->service->payAcceptedSchedule(
-            schedule:              $schedule,
-            paymentMethod:         $validated['payment_method'],
-            clientPaymentMethodId: $validated['client_payment_method_id'] ?? null,
-
-            options: [
-                'phone'   => $validated['phone'] ?? null,
-                'card_id' => $validated['card_id'] ?? null,
-            ],
-        );
-
-        if ($item->status === PaymentStatusEnum::FAILED) {
-            return response()->json([
-                'payload' => new PaymentResource($item),
-                'message' => $item->failure_message ?: $this->paymentMessage($item->status->value),
-            ], 422);
+        try {
+            $item = $this->service->paySchedule(
+                scheduleId:            $scheduleId,
+                paymentMethod:         data_get($validated, 'payment_method'),
+                clientPaymentMethodId: data_get($validated, 'client_payment_method_id'),
+
+                options: [
+                    'phone'   => data_get($validated, 'phone'),
+                    'card_id' => data_get($validated, 'card_id'),
+                ],
+            );
+        } catch (PaymentFailedException) {
+            return $this->errorResponse(message: __('messages.payment_not_confirmed'), code: 422);
+        } catch (PaymentException) {
+            return $this->errorResponse(message: __('messages.payment_error'), code: 422);
         }
 
         return $this->successResponse(
             payload: new PaymentResource($item),
-            message: $this->paymentMessage($item->status->value),
+            message: $item->status->message(),
             code:    201,
         );
     }
 
-    public function payCart(Request $request, Cart $cart): JsonResponse
+    public function payServicePackage(PayServicePackageRequest $request, int $servicePackageId): JsonResponse
     {
-        $validated = $request->validate([
-            'payment_method'           => ['required', 'in:credit_card,pix'],
-            'client_payment_method_id' => ['nullable', 'integer', 'exists:client_payment_methods,id'],
-            'card_id'                  => ['nullable', 'string', 'max:255'],
-            'phone'                    => ['nullable', 'string', 'max:20'],
-        ]);
+        $validated = $request->validated();
 
         try {
-            $item = $this->service->payCart(
-                cart:                  $cart,
-                userId:                (int) Auth::id(),
-                paymentMethod:         $validated['payment_method'],
-                clientPaymentMethodId: $validated['client_payment_method_id'] ?? null,
-                options:               [
-                    'phone'   => $validated['phone'] ?? null,
-                    'card_id' => $validated['card_id'] ?? null,
+            $item = $this->service->payServicePackage(
+                servicePackageId:      $servicePackageId,
+                paymentMethod:         data_get($validated, 'payment_method'),
+                clientPaymentMethodId: data_get($validated, 'client_payment_method_id'),
+
+                options: [
+                    'phone'   => data_get($validated, 'phone'),
+                    'card_id' => data_get($validated, 'card_id'),
                 ],
             );
-        } catch (\InvalidArgumentException $e) {
-            return $this->errorResponse(message: $e->getMessage(), code: 422);
-        }
-
-        if ($item->status === PaymentStatusEnum::FAILED) {
-            return response()->json([
-                'payload' => new PaymentResource($item),
-                'message' => $item->failure_message ?: $this->paymentMessage($item->status->value),
-            ], 422);
+        } catch (PaymentFailedException) {
+            return $this->errorResponse(message: __('messages.payment_not_confirmed'), code: 422);
+        } catch (PaymentException) {
+            return $this->errorResponse(message: __('messages.payment_error'), code: 422);
         }
 
         return $this->successResponse(
             payload: new PaymentResource($item),
-            message: $this->paymentMessage($item->status->value),
+            message: $item->status->message(),
             code:    201,
         );
     }
@@ -153,15 +136,4 @@ class PaymentController extends Controller
         return $this->successResponse(payload: $this->service->platformFees());
     }
 
-    //
-
-    private function paymentMessage(string $status): string
-    {
-        return match ($status) {
-            'paid'                                => __('messages.payment_confirmed'),
-            'authorized', 'processing', 'pending' => __('messages.payment_pending_confirmation'),
-            'failed'                              => __('messages.payment_not_confirmed'),
-            default                               => __('messages.created'),
-        };
-    }
 }

+ 1 - 4
app/Http/Controllers/ProviderCalendarController.php

@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
 
 use App\Services\ProviderCalendarService;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Log;
 
 class ProviderCalendarController extends Controller
 {
@@ -17,9 +16,7 @@ class ProviderCalendarController extends Controller
 
             return $this->successResponse(payload: $dados);
         } catch (\Exception $e) {
-            Log::error('Error fetching provider calendar: '.$e->getMessage());
-
-            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 }

+ 2 - 2
app/Http/Controllers/ProviderController.php

@@ -119,8 +119,8 @@ class ProviderController extends Controller
 
         return $this->successResponse(
             payload: new AuthResource([
-                ...$result['payload'],
-                'refresh_token' => $result['refreshToken'],
+                ...data_get($result, 'payload'),
+                'refresh_token' => data_get($result, 'refreshToken'),
             ]),
             message: __('messages.provider_registered_pending'),
             code: 201,

+ 2 - 2
app/Http/Controllers/ProviderWithdrawalController.php

@@ -39,8 +39,8 @@ class ProviderWithdrawalController extends Controller
 
         return $this->successResponse(
             payload: new ProviderWithdrawalResource($withdrawal),
-            message: 'Saque solicitado com sucesso.',
-            code: 201,
+            message: __('messages.withdrawal_requested'),
+            code:    201,
         );
     }
 

+ 1 - 1
app/Http/Controllers/ProviderWorkingDayController.php

@@ -30,7 +30,7 @@ class ProviderWorkingDayController extends Controller
 
     public function destroy($id, Request $request): JsonResponse
     {
-        $day = $request->query('day');
+        $day    = $request->query('day');
         $period = $request->query('period');
 
         $this->service->delete($id, $day, $period);

+ 1 - 1
app/Http/Controllers/ReviewController.php

@@ -30,7 +30,7 @@ class ReviewController extends Controller
         return $this->successResponse(
             payload: new ReviewResource($review),
             message: __('messages.created'),
-            code: 201,
+            code:    201,
         );
     }
 

+ 12 - 43
app/Http/Controllers/ScheduleController.php

@@ -2,13 +2,12 @@
 
 namespace App\Http\Controllers;
 
+use App\Exceptions\ScheduleStatusTransitionException;
 use App\Http\Requests\ScheduleRequest;
 use App\Http\Resources\ScheduleResource;
-use App\Services\ScheduleCartService;
 use App\Services\ScheduleService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
-use Illuminate\Support\Arr;
 
 class ScheduleController extends Controller
 {
@@ -33,14 +32,11 @@ class ScheduleController extends Controller
         try {
             $validated = $request->validated();
 
-            $schedules = $this->scheduleService->createSingleOrMultiple(
-                Arr::except($validated, ['date', 'dates']),
-                $this->scheduleItems($validated),
-            );
+            $schedule = $this->scheduleService->create($validated);
 
             return $this->successResponse(
-                payload: ScheduleResource::collection($schedules),
-                message: count($schedules).' '.__('schedules.schedules_created'),
+                payload: new ScheduleResource($schedule),
+                message: __('messages.created'),
                 code:    201,
             );
         } catch (\Exception $e) {
@@ -100,8 +96,8 @@ class ScheduleController extends Controller
         ]);
 
         $blocks = $this->scheduleService->getClientProviderBlocks(
-            (int) $validated['client_id'],
-            (int) $validated['provider_id'],
+            (int) data_get($validated, 'client_id'),
+            (int) data_get($validated, 'provider_id'),
         );
 
         return $this->successResponse(payload: $blocks);
@@ -123,27 +119,14 @@ class ScheduleController extends Controller
                 'cancel_text' => 'required|string|min:5|max:1000',
             ]);
 
-            $schedule = $this->scheduleService->cancelWithReason((int) $id, $validated['cancel_text']);
+            $schedule = $this->scheduleService->cancelWithReason((int) $id, data_get($validated, 'cancel_text'));
 
             return $this->successResponse(
                 payload: new ScheduleResource($schedule),
                 message: __('messages.updated'),
             );
-        } catch (\Exception $e) {
-            return $this->errorResponse($e->getMessage(), 422);
-        }
-    }
-
-    public function storeCart(ScheduleRequest $request, ScheduleCartService $cartService): JsonResponse
-    {
-        try {
-            $schedules = $cartService->create($request->validated());
-
-            return $this->successResponse(
-                payload: ScheduleResource::collection($schedules),
-                message: count($schedules).' '.__('schedules.schedules_created'),
-                code:    201,
-            );
+        } catch (ScheduleStatusTransitionException) {
+            return $this->errorResponse(__('messages.cancellation_not_allowed_for_status'), 422);
         } catch (\Exception $e) {
             return $this->errorResponse($e->getMessage(), 422);
         }
@@ -156,30 +139,16 @@ class ScheduleController extends Controller
                 'status' => 'required|in:pending,accepted,rejected,paid,cancelled,started,finished',
             ]);
 
-            $schedule = $this->scheduleService->updateStatus($id, $validated['status']);
+            $schedule = $this->scheduleService->updateStatus($id, data_get($validated, 'status'));
 
             return $this->successResponse(
                 payload: new ScheduleResource($schedule),
                 message: __('messages.updated'),
             );
+        } catch (ScheduleStatusTransitionException) {
+            return $this->errorResponse(__('messages.status_transition_not_allowed'), 422);
         } catch (\Exception $e) {
             return $this->errorResponse($e->getMessage(), 422);
         }
     }
-
-    //
-
-    private function scheduleItems(array $data): array
-    {
-        if (! empty($data['dates'])) {
-            return array_map(
-                fn (string $date) => ['date' => $date],
-                $data['dates'],
-            );
-        }
-
-        return [
-            ['date' => $data['date']],
-        ];
-    }
 }

+ 1 - 4
app/Http/Controllers/SearchController.php

@@ -5,7 +5,6 @@ namespace App\Http\Controllers;
 use App\Services\SearchService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Log;
 
 class SearchController extends Controller
 {
@@ -18,9 +17,7 @@ class SearchController extends Controller
 
             return $this->successResponse(payload: $dados);
         } catch (\Exception $e) {
-            Log::error('Erro ao buscar prestadores: '.$e->getMessage());
-
-            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500);
+            return $this->errorResponse(message: __('messages.error_fetching_data'), code: 500, exception: $e);
         }
     }
 }

+ 82 - 0
app/Http/Controllers/ServicePackageController.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\ServicePackageRequest;
+use App\Http\Resources\ServicePackageResource;
+use App\Services\ServicePackageService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Auth;
+
+class ServicePackageController extends Controller
+{
+    public function __construct(
+        protected ServicePackageService $service,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+
+        return $this->successResponse(payload: ServicePackageResource::collection($items));
+    }
+
+    public function store(ServicePackageRequest $request): JsonResponse
+    {
+        $validated = $request->validated();
+
+        $item = $this->hasSchedulePayload($validated)
+            ? $this->service->createWithSchedules($validated)
+            : $this->service->create($validated);
+
+        return $this->successResponse(payload: new ServicePackageResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+
+        return $this->successResponse(payload: new ServicePackageResource($item));
+    }
+
+    public function update(ServicePackageRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+
+        return $this->successResponse(payload: new ServicePackageResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    //
+
+    public function myServicePackages(): JsonResponse
+    {
+        $items = $this->service->getByUserId(Auth::id());
+
+        return $this->successResponse(payload: ServicePackageResource::collection($items));
+    }
+
+    public function storeMine(ServicePackageRequest $request): JsonResponse
+    {
+        $request->merge(['client_id' => Auth::user()?->client?->id]);
+
+        $item = $this->service->createForUser($request->validated(), Auth::id());
+
+        return $this->successResponse(payload: new ServicePackageResource($item), message: __('messages.created'), code: 201);
+    }
+
+    //
+
+    private function hasSchedulePayload(array $data): bool
+    {
+        return ! empty(data_get($data, 'schedules'))
+            || ! empty(data_get($data, 'dates'))
+            || ! empty(data_get($data, 'date'));
+    }
+}

+ 50 - 0
app/Http/Controllers/ServicePackageItemController.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\ServicePackageItemRequest;
+use App\Http\Resources\ServicePackageItemResource;
+use App\Services\ServicePackageItemService;
+use Illuminate\Http\JsonResponse;
+
+class ServicePackageItemController extends Controller
+{
+    public function __construct(
+        protected ServicePackageItemService $service,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+
+        return $this->successResponse(payload: ServicePackageItemResource::collection($items));
+    }
+
+    public function store(ServicePackageItemRequest $request): JsonResponse
+    {
+        $item = $this->service->create($request->validated());
+
+        return $this->successResponse(payload: new ServicePackageItemResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+
+        return $this->successResponse(payload: new ServicePackageItemResource($item));
+    }
+
+    public function update(ServicePackageItemRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+
+        return $this->successResponse(payload: new ServicePackageItemResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 1 - 1
app/Http/Controllers/SupportRequestController.php

@@ -28,7 +28,7 @@ class SupportRequestController extends Controller
             'data'  => SupportRequestResource::collection($paginated->items()),
             'total' => $paginated->total(),
             'from'  => $paginated->firstItem() ?? 0,
-            'to'    => $paginated->lastItem() ?? 0,
+            'to'    => $paginated->lastItem()  ?? 0,
         ]);
     }
 

+ 19 - 15
app/Http/Controllers/UserController.php

@@ -32,13 +32,14 @@ class UserController extends Controller
         return $this->successResponse(
             payload: new UserResource($item),
             message: __("messages.created"),
-            code: 201,
+            code:    201,
         );
     }
 
     public function show(int $id): JsonResponse
     {
         $item = $this->service->findById($id);
+
         return $this->successResponse(payload: new UserResource($item));
     }
 
@@ -58,7 +59,7 @@ class UserController extends Controller
 
         return $this->successResponse(
             message: __('messages.deleted'),
-            code: 204,
+            code:    204,
         );
     }
 
@@ -76,6 +77,7 @@ class UserController extends Controller
     public function me(): JsonResponse
     {
         $user = $this->service->me();
+
         return $this->successResponse(payload: new UserResource($user));
     }
 
@@ -89,24 +91,24 @@ class UserController extends Controller
         $avatar    = $request->file('avatar');
 
         $data       = $request->validated();
-        $hasBase64  = ! empty($data['avatar_base64']);
+        $hasBase64  = ! empty(data_get($data, 'avatar_base64'));
 
         Log::warning('[avatar-upload] Requisição recebida em updateMe', [
             'user_id'           => auth()->id(),
             'has_avatar'        => $hasAvatar,
             'has_avatar_base64' => $hasBase64,
-            'avatar_base64_len' => $hasBase64 ? strlen($data['avatar_base64']) : null,
-            'avatar_valid'      => $hasAvatar ? $avatar->isValid() : null,
-            'avatar_size'       => $hasAvatar ? $avatar->getSize() : null,
-            'avatar_mime'       => $hasAvatar ? $avatar->getMimeType() : null,
+            'avatar_base64_len' => $hasBase64 ? strlen(data_get($data, 'avatar_base64'))        : null,
+            'avatar_valid'      => $hasAvatar ? $avatar->isValid()                    : null,
+            'avatar_size'       => $hasAvatar ? $avatar->getSize()                    : null,
+            'avatar_mime'       => $hasAvatar ? $avatar->getMimeType()                : null,
             'avatar_extension'  => $hasAvatar ? $avatar->getClientOriginalExtension() : null,
-            'avatar_error'      => $hasAvatar ? $avatar->getError() : null,
+            'avatar_error'      => $hasAvatar ? $avatar->getError()                   : null,
             'method'            => $request->method(),
             'content_type'      => $request->header('Content-Type'),
         ]);
 
         if ($hasBase64) {
-            $decoded = $this->decodeBase64ToUploadedFile($data['avatar_base64']);
+            $decoded = $this->decodeBase64ToUploadedFile(data_get($data, 'avatar_base64'));
 
             Log::warning('[avatar-upload] Avatar base64 decodificado', [
                 'decode_ok'    => $decoded !== null,
@@ -142,8 +144,8 @@ class UserController extends Controller
     private function decodeBase64ToUploadedFile(string $base64): ?UploadedFile
     {
         if (preg_match('/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/s', $base64, $matches)) {
-            $mime    = $matches[1];
-            $content = base64_decode($matches[2], true);
+            $mime    = data_get($matches, 1);
+            $content = base64_decode(data_get($matches, 2), true);
         } else {
             $mime    = 'image/jpeg';
             $content = base64_decode($base64, true);
@@ -166,11 +168,13 @@ class UserController extends Controller
             return null;
         }
 
+        $originalName = 'avatar.'.$extension;
+
         return new UploadedFile(
-            path: $tmpPath,
-            originalName: 'avatar.'.$extension,
-            mimeType: $mime,
-            test: true,
+            path:         $tmpPath,
+            originalName: $originalName,
+            mimeType:     $mime,
+            test:         true,
         );
     }
 }

+ 5 - 2
app/Http/Middleware/CheckPermission.php

@@ -31,15 +31,17 @@ class CheckPermission
             : $this->userTypePermissionService->allGuestPermissions();
 
         $hasPermission = false;
+
         foreach (explode(separator: '|', string: $scopes) as $scope) {
             if ($this->hasPermission(userPermissions: $userPermissions, scope: $scope, permissionType: $permissionType)) {
                 $hasPermission = true;
+
                 break;
             }
         }
 
         if (! $hasPermission) {
-            return response()->json(data: ['message' => 'Unauthorized'], status: 403);
+            return response()->json(data: ['message' => __('auth.unauthorized')], status: 403);
         }
 
         return $next($request);
@@ -59,7 +61,8 @@ class CheckPermission
             'menu'   => 256,
         ];
 
-        $requiredPermission = $bitwisePermissionTable[$permissionType] ?? 0;
+        $requiredPermission = data_get($bitwisePermissionTable, $permissionType, 0);
+
         $permissionRecord = $userPermissions->first(function ($permission) use ($scope) {
             return $permission->permission->scope === $scope;
         });

+ 40 - 30
app/Http/Middleware/PerformanceMonitor.php

@@ -45,8 +45,8 @@ class PerformanceMonitor
 
     public function __construct()
     {
-        $this->startTime = defined('LARAVEL_START') ? LARAVEL_START : microtime(true);
-        $this->startMemory = memory_get_usage();
+        $this->startTime       = defined('LARAVEL_START') ? LARAVEL_START : microtime(true);
+        $this->startMemory     = memory_get_usage();
         $this->startPeakMemory = memory_get_peak_usage();
     }
 
@@ -100,13 +100,15 @@ class PerformanceMonitor
     private function getMemoryLimit(): int
     {
         $limit = ini_get('memory_limit');
+
         if (! $limit) {
             return 0;
         }
 
         preg_match('/^(\d+)(K|M|G)?$/i', $limit, $matches);
-        $value = (int) ($matches[1] ?? 0);
-        $unit = strtoupper($matches[2] ?? '');
+
+        $value = (int) data_get($matches, 1, 0);
+        $unit  = strtoupper(data_get($matches, 2, ''));
 
         return match ($unit) {
             'G'     => $value * 1024 * 1024 * 1024,
@@ -128,18 +130,22 @@ class PerformanceMonitor
     private function getQueryMetrics(): ?array
     {
         $queries = DB::getQueryLog();
+
         if (empty($queries)) {
             return null;
         }
 
         $totalTime = 0;
+
         $formattedQueries = [];
+
         foreach ($queries as $query) {
-            $totalTime += $query['time'];
-            if ($query['time'] > self::SLOW_QUERY_THRESHOLD) {
+            $totalTime += data_get($query, 'time');
+
+            if (data_get($query, 'time') > self::SLOW_QUERY_THRESHOLD) {
                 $formattedQueries[] = [
-                    'sql'  => $this->formatSql($query['query'], $query['bindings']),
-                    'time' => $query['time'],
+                    'sql'  => $this->formatSql(data_get($query, 'query'), data_get($query, 'bindings')),
+                    'time' => data_get($query, 'time'),
                 ];
             }
         }
@@ -165,8 +171,8 @@ class PerformanceMonitor
         $usage = getrusage();
 
         return [
-            'user'   => ($usage['ru_utime.tv_sec'] * 1000 + intval($usage['ru_utime.tv_usec'] / 1000)),
-            'system' => ($usage['ru_stime.tv_sec'] * 1000 + intval($usage['ru_stime.tv_usec'] / 1000)),
+            'user'   => (data_get($usage, 'ru_utime.tv_sec') * 1000 + intval(data_get($usage, 'ru_utime.tv_usec') / 1000)),
+            'system' => (data_get($usage, 'ru_stime.tv_sec') * 1000 + intval(data_get($usage, 'ru_stime.tv_usec') / 1000)),
         ];
     }
 
@@ -177,18 +183,19 @@ class PerformanceMonitor
         }
 
         $status = opcache_get_status(false);
+
         if (! $status) {
             return null;
         }
 
-        $stats = $status['opcache_statistics'];
-        $hits = $stats['hits'];
-        $misses = $stats['misses'];
-        $total = $hits + $misses;
+        $stats  = data_get($status, 'opcache_statistics');
+        $hits   = data_get($stats, 'hits');
+        $misses = data_get($stats, 'misses');
+        $total  = $hits + $misses;
 
         return [
             'hit_rate'     => $total > 0 ? ($hits / $total * 100) : 0,
-            'memory_usage' => $status['memory_usage'],
+            'memory_usage' => data_get($status, 'memory_usage'),
         ];
     }
 
@@ -205,6 +212,7 @@ class PerformanceMonitor
 
         try {
             $written = file_put_contents('php://stderr', $this->buffer);
+
             if ($written === false) {
                 error_log('Failed to write performance metrics to stderr');
             }
@@ -238,33 +246,34 @@ class PerformanceMonitor
         $this->appendToBuffer(sprintf(
             '➜ Time: %s%.2fms%s | Memory: %s%.2fMB%s (%.1f%%) | Peak: %s%.2fMB%s',
             self::COLORS['yellow'],
-            $metrics['time'],
+            data_get($metrics, 'time'),
             self::COLORS['reset'],
             self::COLORS['magenta'],
-            $metrics['memory'],
+            data_get($metrics, 'memory'),
             self::COLORS['reset'],
-            $metrics['memoryPercentage'],
+            data_get($metrics, 'memoryPercentage'),
             self::COLORS['magenta'],
-            $metrics['peakMemory'],
+            data_get($metrics, 'peakMemory'),
             self::COLORS['reset']
         ));
 
         // Query metrics
-        if ($metrics['queries']) {
+        if (data_get($metrics, 'queries')) {
             $this->appendToBuffer(sprintf(
                 ' | Queries: %s%d%s (%s%.2fms%s)',
                 self::COLORS['cyan'],
-                $metrics['queries']['count'],
+                data_get($metrics, 'queries.count'),
                 self::COLORS['reset'],
                 self::COLORS['cyan'],
-                $metrics['queries']['time'],
+                data_get($metrics, 'queries.time'),
                 self::COLORS['reset']
             ));
         }
 
         // CPU metrics
-        if ($metrics['cpu']) {
-            $totalCpu = $metrics['cpu']['user'] + $metrics['cpu']['system'];
+        if (data_get($metrics, 'cpu')) {
+            $totalCpu = data_get($metrics, 'cpu.user') + data_get($metrics, 'cpu.system');
+
             $this->appendToBuffer(sprintf(
                 ' | CPU: %s%.2fms%s',
                 self::COLORS['cyan'],
@@ -274,11 +283,11 @@ class PerformanceMonitor
         }
 
         // Cache metrics
-        if ($metrics['cache']) {
+        if (data_get($metrics, 'cache')) {
             $this->appendToBuffer(sprintf(
                 ' | OPcache: %s%.1f%%%s',
                 self::COLORS['cyan'],
-                $metrics['cache']['hit_rate'],
+                data_get($metrics, 'cache.hit_rate'),
                 self::COLORS['reset']
             ));
         }
@@ -286,15 +295,16 @@ class PerformanceMonitor
         $this->appendToBuffer("\n");
 
         // Log slow queries
-        if ($metrics['time'] > self::SLOW_REQUEST_THRESHOLD && ! empty($metrics['queries']['slow_queries'])) {
+        if (data_get($metrics, 'time') > self::SLOW_REQUEST_THRESHOLD && ! empty(data_get($metrics, 'queries.slow_queries'))) {
             $this->appendToBuffer("\n".$this->colorize('Slow Queries Detected:', 'yellow')."\n");
-            foreach ($metrics['queries']['slow_queries'] as $query) {
+
+            foreach (data_get($metrics, 'queries.slow_queries') as $query) {
                 $this->appendToBuffer(sprintf(
                     "➜ %s%.2fms%s: %s\n",
                     self::COLORS['yellow'],
-                    $query['time'],
+                    data_get($query, 'time'),
                     self::COLORS['reset'],
-                    $query['sql']
+                    data_get($query, 'sql')
                 ));
             }
         }

+ 1 - 0
app/Http/Middleware/SetUserLanguage.php

@@ -19,6 +19,7 @@ class SetUserLanguage
     {
         $language = substr($request->header('Accept-Language'), 0, 2);
         $language = LanguageEnum::fromString($language);
+
         if ($language) {
             App::setLocale($language->value);
         } else {

+ 23 - 0
app/Http/Requests/PayScheduleRequest.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class PayScheduleRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        return [
+            'payment_method'           => ['required', 'in:credit_card,pix'],
+            'client_payment_method_id' => ['nullable', 'integer', 'exists:client_payment_methods,id'],
+            'card_id'                  => ['nullable', 'string', 'max:255'],
+            'phone'                    => ['nullable', 'string', 'max:20'],
+        ];
+    }
+}

+ 23 - 0
app/Http/Requests/PayServicePackageRequest.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class PayServicePackageRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        return [
+            'payment_method'           => ['required', 'in:credit_card,pix'],
+            'client_payment_method_id' => ['nullable', 'integer', 'exists:client_payment_methods,id'],
+            'card_id'                  => ['nullable', 'string', 'max:255'],
+            'phone'                    => ['nullable', 'string', 'max:20'],
+        ];
+    }
+}

+ 2 - 9
app/Http/Requests/ScheduleRequest.php

@@ -18,8 +18,6 @@ class ScheduleRequest extends FormRequest
             'provider_id'              => 'sometimes|required|exists:providers,id',
             'address_id'               => 'sometimes|required|exists:addresses,id',
             'date'                     => 'nullable|date|after_or_equal:today',
-            'dates'                    => 'nullable|array',
-            'dates.*'                  => 'date|after_or_equal:today',
             'offers_meal'              => 'sometimes|nullable|boolean',
             'period_type'              => 'sometimes|required|in:2,4,6,8',
             'schedule_type'            => 'sometimes|in:default,custom',
@@ -34,9 +32,7 @@ class ScheduleRequest extends FormRequest
             $rules['client_id']    = 'required|exists:clients,id';
             $rules['provider_id']  = 'required|exists:providers,id';
             $rules['address_id']   = 'required|exists:addresses,id';
-            $rules['date']         = 'required_without:dates|nullable|date|after_or_equal:today';
-            $rules['dates']        = 'nullable|array';
-            $rules['dates.*']      = 'date|after_or_equal:today';
+            $rules['date']         = 'required|date|after_or_equal:today';
             $rules['period_type']  = 'required|in:2,4,6,8';
             $rules['start_time']   = 'required|date_format:H:i';
             $rules['end_time']     = 'required|date_format:H:i|after:start_time';
@@ -56,12 +52,9 @@ class ScheduleRequest extends FormRequest
             'provider_id.exists'     => 'Prestador não encontrado.',
             'address_id.required'    => 'O endereço é obrigatório.',
             'address_id.exists'      => 'Endereço não encontrado.',
-            'date.required_without'  => 'A data é obrigatória quando não há múltiplas datas.',
+            'date.required'          => 'A data é obrigatória.',
             'date.date'              => 'Data inválida.',
             'date.after_or_equal'    => 'A data deve ser hoje ou futura.',
-            'dates.array'            => 'Datas devem ser um array.',
-            'dates.*.date'           => 'Uma das datas é inválida.',
-            'dates.*.after_or_equal' => 'Todas as datas devem ser hoje ou futuras.',
             'period_type.required'   => 'O período é obrigatório.',
             'period_type.in'         => 'Período inválido.',
             'start_time.required'    => 'O horário de início é obrigatório.',

+ 5 - 5
app/Http/Requests/CartItemRequest.php → app/Http/Requests/ServicePackageItemRequest.php

@@ -4,18 +4,18 @@ namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class CartItemRequest extends FormRequest
+class ServicePackageItemRequest extends FormRequest
 {
     public function rules(): array
     {
         $rules = [
-            'cart_id'     => ['sometimes', 'integer', 'exists:carts,id'],
-            'schedule_id' => ['sometimes', 'integer', 'exists:schedules,id'],
+            'service_package_id' => ['sometimes', 'integer', 'exists:service_packages,id'],
+            'schedule_id'        => ['sometimes', 'integer', 'exists:schedules,id'],
         ];
 
         if ($this->isMethod('POST')) {
-            $rules['cart_id']     = ['required', 'integer', 'exists:carts,id'];
-            $rules['schedule_id'] = ['required', 'integer', 'exists:schedules,id'];
+            $rules['service_package_id'] = ['required', 'integer', 'exists:service_packages,id'];
+            $rules['schedule_id']        = ['required', 'integer', 'exists:schedules,id'];
         }
 
         return $rules;

+ 4 - 3
app/Http/Requests/CartRequest.php → app/Http/Requests/ServicePackageRequest.php

@@ -5,7 +5,7 @@ namespace App\Http\Requests;
 use Illuminate\Foundation\Http\FormRequest;
 use Illuminate\Validation\Validator;
 
-class CartRequest extends FormRequest
+class ServicePackageRequest extends FormRequest
 {
     public function authorize(): bool
     {
@@ -71,8 +71,9 @@ class CartRequest extends FormRequest
             }
 
             $rootProviderId = $this->integer('provider_id') ?: null;
+
             $providerIds = collect($this->input('schedules', []))
-                ->map(fn (array $schedule) => $schedule['provider_id'] ?? $rootProviderId)
+                ->map(fn (array $schedule) => data_get($schedule, 'provider_id', $rootProviderId))
                 ->when(
                     ! $this->filled('schedules') && $rootProviderId,
                     fn ($ids) => $ids->push($rootProviderId),
@@ -84,7 +85,7 @@ class CartRequest extends FormRequest
             if ($providerIds->count() > 1) {
                 $validator->errors()->add(
                     'schedules',
-                    'Todos os agendamentos do carrinho precisam ser do mesmo prestador.',
+                    __('messages.service_package_same_provider'),
                 );
             }
         }];

+ 3 - 3
app/Http/Resources/AuthResource.php

@@ -16,11 +16,11 @@ class AuthResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'access_token'  => $this['access_token'],
-            'refresh_token' => $this['refresh_token'] ?? null,
+            'access_token'  => data_get($this, 'access_token'),
+            'refresh_token' => data_get($this, 'refresh_token'),
             'token_type'    => 'Bearer',
             'expires_in'    => 900,
-            'user'          => new UserResource($this['user']),
+            'user'          => new UserResource(data_get($this, 'user')),
         ];
     }
 

+ 0 - 37
app/Http/Resources/CartItemResource.php

@@ -1,37 +0,0 @@
-<?php
-
-namespace App\Http\Resources;
-
-use App\Models\CartItem;
-use Illuminate\Http\Request;
-use Illuminate\Http\Resources\Json\JsonResource;
-use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
-
-class CartItemResource extends JsonResource
-{
-    /**
-     * Transform the resource into an array.
-     *
-     * @return array<string, mixed>
-     */
-    public function toArray(Request $request): array
-    {
-        return [
-            'id'          => $this->id,
-            'cart_id'     => $this->cart_id,
-            'schedule_id' => $this->schedule_id,
-            'schedule'    => new ScheduleResource($this->whenLoaded('schedule')),
-            'created_at'  => $this->created_at?->toISOString(),
-            'updated_at'  => $this->updated_at?->toISOString(),
-        ];
-    }
-
-    /**
-     * @param \Illuminate\Database\Eloquent\Collection<CartItem> $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CartItemResource>
-     */
-    public static function collection($resource): AnonymousResourceCollection
-    {
-        return parent::collection($resource);
-    }
-}

+ 0 - 4
app/Http/Resources/CityResource.php

@@ -25,10 +25,6 @@ class CityResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<City>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CityResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 2 - 2
app/Http/Resources/ClientFavoriteProviderResource.php

@@ -24,8 +24,8 @@ class ClientFavoriteProviderResource extends JsonResource
             'id'             => $this->id,
             'client_id'      => $this->client_id,
             'provider_id'    => $this->provider_id,
-            'provider_name'  => $this->provider_name ?? ($provider?->relationLoaded('user') ? $provider->user?->name : null),
-            'city_name'      => $this->city_name ?? null,
+            'provider_name'  => $this->provider_name  ?? ($provider?->relationLoaded('user') ? $provider->user?->name : null),
+            'city_name'      => $this->city_name      ?? null,
             'average_rating' => $this->average_rating ?? $provider?->average_rating,
 
             'profile_media_url' => $profileMediaPath

+ 4 - 4
app/Http/Resources/ClientProviderBlockResource.php

@@ -13,11 +13,11 @@ class ClientProviderBlockResource extends JsonResource
             'id'                => $this->id,
             'client_id'         => $this->client_id,
             'provider_id'       => $this->provider_id,
-            'provider_name'     => $this->provider_name ?? $this->provider?->user?->name,
-            'provider_email'    => $this->provider_email ?? $this->provider?->user?->email,
-            'provider_phone'    => $this->provider_phone ?? $this->provider?->user?->phone,
+            'provider_name'     => $this->provider_name     ?? $this->provider?->user?->name,
+            'provider_email'    => $this->provider_email    ?? $this->provider?->user?->email,
+            'provider_phone'    => $this->provider_phone    ?? $this->provider?->user?->phone,
             'provider_district' => $this->provider_district ?? null,
-            'provider_rating'   => $this->provider_rating ?? null,
+            'provider_rating'   => $this->provider_rating   ?? null,
             'created_at'        => $this->created_at?->format('Y-m-d H:i'),
             'updated_at'        => $this->updated_at?->format('Y-m-d H:i'),
         ];

+ 0 - 4
app/Http/Resources/CountryResource.php

@@ -22,10 +22,6 @@ class CountryResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<Country>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CountryResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 12 - 12
app/Http/Resources/DashboardClienteResource.php

@@ -15,18 +15,18 @@ class DashboardClienteResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'headerBar'                  => $this['headerBar'],
-            'summaryInfos'               => $this['summaryInfos'],
-            'pendingSchedules'           => $this['pendingSchedules'],
-            'nextSchedules'              => $this['nextSchedules'],
-            'lastDoneSchedules'          => $this['lastDoneSchedules'],
-            'favoriteProviders'          => $this['favoriteProviders'],
-            'providersClose'             => $this['providersClose'],
-            'schedulesProposals'         => $this['schedulesProposals'],
-            'customSchedulesNoProposals' => $this['customSchedulesNoProposals'],
-            'todaySchedules'             => $this['todaySchedules'],
-            'notifications'              => $this['notifications'],
-            'has_payment_methods'        => $this['has_payment_methods'],
+            'headerBar'                  => data_get($this, 'headerBar'),
+            'summaryInfos'               => data_get($this, 'summaryInfos'),
+            'pendingSchedules'           => data_get($this, 'pendingSchedules'),
+            'nextSchedules'              => data_get($this, 'nextSchedules'),
+            'lastDoneSchedules'          => data_get($this, 'lastDoneSchedules'),
+            'favoriteProviders'          => data_get($this, 'favoriteProviders'),
+            'providersClose'             => data_get($this, 'providersClose'),
+            'schedulesProposals'         => data_get($this, 'schedulesProposals'),
+            'customSchedulesNoProposals' => data_get($this, 'customSchedulesNoProposals'),
+            'todaySchedules'             => data_get($this, 'todaySchedules'),
+            'notifications'              => data_get($this, 'notifications'),
+            'has_payment_methods'        => data_get($this, 'has_payment_methods'),
         ];
     }
 }

+ 8 - 8
app/Http/Resources/DashboardPrestadorResource.php

@@ -15,14 +15,14 @@ class DashboardPrestadorResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'headerBar'      => $this['headerBar'],
-            'summaryInfos'   => $this['summaryInfos'],
-            'priceSuggested' => $this['priceSuggested'],
-            'todayServices'  => $this['todayServices'],
-            'solicitations'  => $this['solicitations'],
-            'nextSchedules'  => $this['nextSchedules'],
-            'opportunities'  => $this['opportunities'],
-            'notifications'  => $this['notifications'],
+            'headerBar'      => data_get($this, 'headerBar'),
+            'summaryInfos'   => data_get($this, 'summaryInfos'),
+            'priceSuggested' => data_get($this, 'priceSuggested'),
+            'todayServices'  => data_get($this, 'todayServices'),
+            'solicitations'  => data_get($this, 'solicitations'),
+            'nextSchedules'  => data_get($this, 'nextSchedules'),
+            'opportunities'  => data_get($this, 'opportunities'),
+            'notifications'  => data_get($this, 'notifications'),
         ];
     }
 }

+ 0 - 4
app/Http/Resources/ImprovementTypeResource.php

@@ -26,10 +26,6 @@ class ImprovementTypeResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<ImprovementType>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<ImprovementTypeResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 0 - 4
app/Http/Resources/MediaResource.php

@@ -27,10 +27,6 @@ class MediaResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<Media>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<MediaResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 10 - 33
app/Http/Resources/PaymentResource.php

@@ -17,14 +17,16 @@ class PaymentResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id'                          => $this->id,
-            'schedule_id'                 => $this->schedule_id,
-            'schedule'                    => new ScheduleResource($this->whenLoaded('schedule')),
-            'cart_id'                     => $this->cart_id,
-            'schedule_ids'                => $this->whenLoaded('cart', fn () => $this->cart?->items->pluck('schedule_id')->values()),
-            'schedules'                   => $this->whenLoaded('cart', fn () => ScheduleResource::collection(
-                $this->cart?->items->pluck('schedule')->filter()->values() ?? collect(),
+            'id'                 => $this->id,
+            'schedule_id'        => $this->schedule_id,
+            'schedule'           => new ScheduleResource($this->whenLoaded('schedule')),
+            'service_package_id' => $this->service_package_id,
+            'schedule_ids'       => $this->whenLoaded('servicePackage', fn () => $this->servicePackage?->items->pluck('schedule_id')->values()),
+
+            'schedules' => $this->whenLoaded('servicePackage', fn () => ScheduleResource::collection(
+                $this->servicePackage?->items->pluck('schedule')->filter()->values() ?? collect(),
             )),
+
             'client_id'                   => $this->client_id,
             'provider_id'                 => $this->provider_id,
             'client_name'                 => $this->client?->user?->name,
@@ -50,7 +52,7 @@ class PaymentResource extends JsonResource
             'expires_at'                  => $this->expires_at?->toISOString(),
             'failure_code'                => $this->failure_code,
             'failure_message'             => $this->failure_message,
-            'pix'                         => $this->pixData(),
+            'pix'                         => $this->resource->pixData(),
             'gateway_payload'             => $this->gateway_payload,
             'metadata'                    => $this->metadata,
             'created_at'                  => $this->created_at?->toISOString(),
@@ -58,34 +60,9 @@ class PaymentResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<Payment>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<PaymentResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);
     }
 
-    private function pixData(): ?array
-    {
-        if ($this->payment_method !== 'pix') {
-            return null;
-        }
-
-        $charge      = $this->gateway_payload['charges'][0] ?? [];
-        $transaction = $charge['last_transaction']          ?? [];
-
-        $expiresAt = $charge['expires_at']
-            ?? $transaction['expires_at']
-            ?? $this->expires_at?->toISOString();
-
-        return [
-            'qr_code'     => $transaction['qr_code']     ?? null,
-            'qr_code_url' => $transaction['qr_code_url'] ?? null,
-            'expires_at'  => $expiresAt,
-            'status'      => $transaction['status']      ?? null,
-        ];
-    }
-
 }

+ 2 - 2
app/Http/Resources/PaymentSplitResource.php

@@ -27,8 +27,8 @@ class PaymentSplitResource extends JsonResource
             'metadata'                          => $this->metadata,
             'payment_status'                    => $this->payment?->status?->value,
             'payment_paid_at'                   => $this->payment?->paid_at?->toISOString(),
-            'cart_id'                           => $this->payment?->cart_id,
-            'schedule_ids'                      => $this->payment?->cart?->items?->pluck('schedule_id')->values(),
+            'service_package_id'                => $this->payment?->service_package_id,
+            'schedule_ids'                      => $this->payment?->servicePackage?->items?->pluck('schedule_id')->values(),
             'schedule_date'                     => $this->payment?->schedule?->date,
             'schedule_status'                   => $this->payment?->schedule?->status,
             'schedule_period_type'              => $this->payment?->schedule?->period_type,

+ 0 - 4
app/Http/Resources/ProviderResource.php

@@ -39,10 +39,6 @@ class ProviderResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<Provider>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<ProviderResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 1 - 1
app/Http/Resources/ReviewResource.php

@@ -16,7 +16,7 @@ class ReviewResource extends JsonResource
 
             'schedule_label' => $this->schedule
                 ? ($this->schedule->id.' - '.
-                   ($this->schedule->client?->user?->name ?? '?').' - '.
+                   ($this->schedule->client?->user?->name   ?? '?').' - '.
                    ($this->schedule->provider?->user?->name ?? '?').' - '.
                    ($this->schedule->date?->format('d/m/Y') ?? '?'))
                 : null,

+ 21 - 21
app/Http/Resources/ScheduleResource.php

@@ -2,7 +2,7 @@
 
 namespace App\Http\Resources;
 
-use App\Models\CartItem;
+use App\Models\ServicePackageItem;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 
@@ -10,12 +10,12 @@ class ScheduleResource extends JsonResource
 {
     public function toArray(Request $request): array
     {
-        $cartId = $this->cart_id ?? CartItem::query()
+        $servicePackageId = $this->service_package_id ?? ServicePackageItem::query()
             ->where('schedule_id', $this->id)
-            ->value('cart_id');
+            ->value('service_package_id');
 
-        $cartItemsCount = $this->cart_items_count ?? (
-            $cartId ? CartItem::query()->where('cart_id', $cartId)->count() : 0
+        $servicePackageItemsCount = $this->service_package_items_count ?? (
+            $servicePackageId ? ServicePackageItem::query()->where('service_package_id', $servicePackageId)->count() : 0
         );
 
         return [
@@ -33,22 +33,22 @@ class ScheduleResource extends JsonResource
                 $this->address->city ? "{$this->address->city->name}/{$this->address->state?->code}" : null,
             ])) : null,
 
-            'address'          => new AddressResource($this->whenLoaded('address')),
-            'date'             => $this->date?->format('Y-m-d'),
-            'period_type'      => $this->period_type,
-            'schedule_type'    => $this->schedule_type,
-            'start_time'       => $this->start_time,
-            'end_time'         => $this->end_time,
-            'offers_meal'      => $this->offers_meal,
-            'status'           => $this->status,
-            'total_amount'     => $this->total_amount,
-            'code'             => $this->code,
-            'code_verified'    => $this->code_verified,
-            'cart_id'          => $cartId,
-            'cart_items_count' => $cartItemsCount,
-            'created_at'       => $this->created_at?->toISOString(),
-            'updated_at'       => $this->updated_at?->toISOString(),
-            'deleted_at'       => $this->deleted_at?->toISOString(),
+            'address'                     => new AddressResource($this->whenLoaded('address')),
+            'date'                        => $this->date?->format('Y-m-d'),
+            'period_type'                 => $this->period_type,
+            'schedule_type'               => $this->schedule_type,
+            'start_time'                  => $this->start_time,
+            'end_time'                    => $this->end_time,
+            'offers_meal'                 => $this->offers_meal,
+            'status'                      => $this->status,
+            'total_amount'                => $this->total_amount,
+            'code'                        => $this->code,
+            'code_verified'               => $this->code_verified,
+            'service_package_id'          => $servicePackageId,
+            'service_package_items_count' => $servicePackageItemsCount,
+            'created_at'                  => $this->created_at?->toISOString(),
+            'updated_at'                  => $this->updated_at?->toISOString(),
+            'deleted_at'                  => $this->deleted_at?->toISOString(),
         ];
     }
 }

+ 37 - 0
app/Http/Resources/ServicePackageItemResource.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Resources;
+
+use App\Models\ServicePackageItem;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+
+class ServicePackageItemResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'                 => $this->id,
+            'service_package_id' => $this->service_package_id,
+            'schedule_id'        => $this->schedule_id,
+            'schedule'           => new ScheduleResource($this->whenLoaded('schedule')),
+            'created_at'         => $this->created_at?->toISOString(),
+            'updated_at'         => $this->updated_at?->toISOString(),
+        ];
+    }
+
+    /**
+     * @param \Illuminate\Database\Eloquent\Collection<ServicePackageItem> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<ServicePackageItemResource>
+     */
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 5 - 5
app/Http/Resources/CartResource.php → app/Http/Resources/ServicePackageResource.php

@@ -2,12 +2,12 @@
 
 namespace App\Http\Resources;
 
-use App\Models\Cart;
+use App\Models\ServicePackage;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
 
-class CartResource extends JsonResource
+class ServicePackageResource extends JsonResource
 {
     /**
      * Transform the resource into an array.
@@ -22,7 +22,7 @@ class CartResource extends JsonResource
             'provider_id'  => $this->provider_id,
             'status'       => $this->status?->value,
             'schedule_ids' => $this->whenLoaded('items', fn () => $this->items->pluck('schedule_id')->values()),
-            'items'        => $this->whenLoaded('items', fn () => CartItemResource::collection($this->items)),
+            'items'        => $this->whenLoaded('items', fn () => ServicePackageItemResource::collection($this->items)),
 
             'schedules' => $this->whenLoaded('items', fn () => ScheduleResource::collection(
                 $this->items->pluck('schedule')->filter()->values(),
@@ -34,8 +34,8 @@ class CartResource extends JsonResource
     }
 
     /**
-     * @param \Illuminate\Database\Eloquent\Collection<Cart> $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CartResource>
+     * @param \Illuminate\Database\Eloquent\Collection<ServicePackage> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<ServicePackageResource>
      */
     public static function collection($resource): AnonymousResourceCollection
     {

+ 0 - 4
app/Http/Resources/ServiceTypeResource.php

@@ -25,10 +25,6 @@ class ServiceTypeResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<ServiceType>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<ServiceTypeResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 0 - 4
app/Http/Resources/SpecialityResource.php

@@ -25,10 +25,6 @@ class SpecialityResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<Speciality>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<SpecialityResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 0 - 4
app/Http/Resources/StateResource.php

@@ -24,10 +24,6 @@ class StateResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<State>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<StateResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 0 - 4
app/Http/Resources/SupportRequestResource.php

@@ -35,10 +35,6 @@ class SupportRequestResource extends JsonResource
         ];
     }
 
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<SupportRequest>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<SupportRequestResource>
-     */
     public static function collection($resource): AnonymousResourceCollection
     {
         return parent::collection($resource);

+ 29 - 3
app/Models/Payment.php

@@ -95,7 +95,7 @@ class Payment extends Model
 
     protected $fillable = [
         'schedule_id',
-        'cart_id',
+        'service_package_id',
         'client_id',
         'provider_id',
         'client_payment_method_id',
@@ -159,9 +159,9 @@ class Payment extends Model
         return $this->belongsTo(Schedule::class);
     }
 
-    public function cart()
+    public function servicePackage()
     {
-        return $this->belongsTo(Cart::class);
+        return $this->belongsTo(ServicePackage::class);
     }
 
     //
@@ -195,4 +195,30 @@ class Payment extends Model
 
         return $code;
     }
+
+    public function pixData(): ?array
+    {
+        if ($this->payment_method !== 'pix') {
+            return null;
+        }
+
+        $charge = data_get($this->gateway_payload, 'charges.0', []);
+
+        $transaction = data_get($charge, 'last_transaction', []);
+
+        $expiresAt = data_get($charge, 'expires_at')
+            ?? data_get($transaction, 'expires_at')
+            ?? $this->expires_at?->toISOString();
+
+        $qrCode    = data_get($transaction, 'qr_code');
+        $qrCodeUrl = data_get($transaction, 'qr_code_url');
+        $status    = data_get($transaction, 'status');
+
+        return [
+            'qr_code'     => $qrCode,
+            'qr_code_url' => $qrCodeUrl,
+            'expires_at'  => $expiresAt,
+            'status'      => $status,
+        ];
+    }
 }

+ 20 - 20
app/Models/Cart.php → app/Models/ServicePackage.php

@@ -2,7 +2,7 @@
 
 namespace App\Models;
 
-use App\Enums\CartStatusEnum;
+use App\Enums\ServicePackageStatusEnum;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\SoftDeletes;
@@ -10,34 +10,34 @@ use Illuminate\Database\Eloquent\SoftDeletes;
 /**
  * @property int $id
  * @property int $client_id
- * @property CartStatusEnum $status
+ * @property ServicePackageStatusEnum $status
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Client $client
- * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CartItem> $items
+ * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ServicePackageItem> $items
  * @property-read int|null $items_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Schedule> $schedules
  * @property-read int|null $schedules_count
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart onlyTrashed()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart query()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereClientId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereStatus($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart whereUpdatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart withTrashed(bool $withTrashed = true)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|Cart withoutTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage query()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage whereClientId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage whereStatus($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage withTrashed(bool $withTrashed = true)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackage withoutTrashed()
  * @mixin \Eloquent
  */
-class Cart extends Model
+class ServicePackage extends Model
 {
     use HasFactory, SoftDeletes;
 
-    protected $table = 'carts';
+    protected $table = 'service_packages';
 
     protected $fillable = [
         'client_id',
@@ -46,7 +46,7 @@ class Cart extends Model
     ];
 
     protected $casts = [
-        'status'     => CartStatusEnum::class,
+        'status'     => ServicePackageStatusEnum::class,
         'created_at' => 'datetime',
         'updated_at' => 'datetime',
         'deleted_at' => 'datetime',
@@ -69,12 +69,12 @@ class Cart extends Model
 
     public function items()
     {
-        return $this->hasMany(CartItem::class);
+        return $this->hasMany(ServicePackageItem::class);
     }
 
     public function schedules()
     {
-        return $this->belongsToMany(Schedule::class, 'cart_items')
+        return $this->belongsToMany(Schedule::class, 'service_package_items')
             ->withTimestamps();
     }
 }

+ 15 - 15
app/Models/CartItem.php → app/Models/ServicePackageItem.php

@@ -7,30 +7,30 @@ use Illuminate\Database\Eloquent\Model;
 
 /**
  * @property int $id
- * @property int $cart_id
+ * @property int $service_package_id
  * @property int $schedule_id
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
- * @property-read \App\Models\Cart $cart
+ * @property-read \App\Models\ServicePackage $servicePackage
  * @property-read \App\Models\Schedule $schedule
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem query()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem whereCartId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem whereScheduleId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|CartItem whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem query()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem whereServicePackageId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem whereScheduleId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ServicePackageItem whereUpdatedAt($value)
  * @mixin \Eloquent
  */
-class CartItem extends Model
+class ServicePackageItem extends Model
 {
     use HasFactory;
 
-    protected $table = 'cart_items';
+    protected $table = 'service_package_items';
 
     protected $fillable = [
-        'cart_id',
+        'service_package_id',
         'schedule_id',
     ];
 
@@ -39,9 +39,9 @@ class CartItem extends Model
         'updated_at' => 'datetime',
     ];
 
-    public function cart()
+    public function servicePackage()
     {
-        return $this->belongsTo(Cart::class);
+        return $this->belongsTo(ServicePackage::class);
     }
 
     public function schedule()

+ 1 - 1
app/Rules/ScheduleBusinessRules.php

@@ -26,7 +26,7 @@ class ScheduleBusinessRules
             ->exists();
 
         if (! $provider) {
-            throw new \Exception('Prestador indisponível para agendamento.');
+            throw new \Exception(__('messages.provider_unavailable_for_schedule'));
         }
 
         return true;

+ 5 - 5
app/Services/AddressService.php

@@ -21,7 +21,7 @@ class AddressService
     {
         $address = Address::findOrFail($id);
 
-        if (isset($data['is_primary']) && $data['is_primary']) {
+        if (data_get($data, 'is_primary') !== null && data_get($data, 'is_primary')) {
             Address::where('source', $address->source)
                 ->where('source_id', $address->source_id)
                 ->where('id', '!=', $id)
@@ -45,8 +45,8 @@ class AddressService
 
     public function findBySource(array $data): ?Collection
     {
-        $allAddresses = Address::where('source', $data['source'])
-            ->where('source_id', $data['source_id'])
+        $allAddresses = Address::where('source', data_get($data, 'source'))
+            ->where('source_id', data_get($data, 'source_id'))
             ->with(['city', 'state'])
             ->whereNull('deleted_at')
             ->orderBy('is_primary', 'desc')
@@ -57,8 +57,8 @@ class AddressService
 
     public function findPrimaryBySource(array $data): ?Address
     {
-        return Address::where('source', $data['source'])
-            ->where('source_id', $data['source_id'])
+        return Address::where('source', data_get($data, 'source'))
+            ->where('source_id', data_get($data, 'source_id'))
             ->whereNull('deleted_at')
             ->orderBy('is_primary', 'desc')
             ->first();

+ 34 - 34
app/Services/AuthService.php

@@ -74,11 +74,11 @@ class AuthService
 
         return [
             'payload' => [
-                'access_token' => $tokens['access_token'],
+                'access_token' => data_get($tokens, 'access_token'),
                 'user'         => $user,
             ],
 
-            'refreshToken' => $tokens['refresh_token'],
+            'refreshToken' => data_get($tokens, 'refresh_token'),
         ];
     }
 
@@ -138,10 +138,10 @@ class AuthService
             $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
 
             $user = User::where(function ($query) use ($data) {
-                $query->when(! empty($data['email']), function ($q) use ($data) {
-                    $q->where('email', $data['email']);
-                })->when(! empty($data['phone']), function ($q) use ($data) {
-                    $q->where('phone', $data['phone']);
+                $query->when(! empty(data_get($data, 'email')), function ($q) use ($data) {
+                    $q->where('email', data_get($data, 'email'));
+                })->when(! empty(data_get($data, 'phone')), function ($q) use ($data) {
+                    $q->where('phone', data_get($data, 'phone'));
                 });
             })->where('type', UserTypeEnum::CLIENT->value)->first();
 
@@ -167,23 +167,23 @@ class AuthService
                 $user->fill($data);
 
                 $user->code = $code;
-                $user->name = $data['name'] ?? 'Usuário';
-                $user->type = $data['type'] ?? UserTypeEnum::CLIENT->value;
+                $user->name = data_get($data, 'name', 'Usuário');
+                $user->type = data_get($data, 'type', UserTypeEnum::CLIENT->value);
 
                 $user->save();
 
                 Client::create(['user_id' => $user->id]);
             }
 
-            if (! empty($data['email'])) {
+            if (! empty(data_get($data, 'email'))) {
                 $this->emailService->sendVerificationCode(
-                    email: $data['email'],
-                    code: $code,
-                    recipientName: $data['name'] ?? '',
+                    email:         data_get($data, 'email'),
+                    code:          $code,
+                    recipientName: data_get($data, 'name', ''),
                 );
-            } elseif (! empty($data['phone'])) {
+            } elseif (! empty(data_get($data, 'phone'))) {
                 Log::info('SMS: envio de código por telefone ainda não implementado.', [
-                    'phone' => $data['phone'],
+                    'phone' => data_get($data, 'phone'),
                 ]);
             }
 
@@ -210,10 +210,10 @@ class AuthService
             $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
 
             $user = User::where(function ($query) use ($data) {
-                $query->when(! empty($data['email']), function ($q) use ($data) {
-                    $q->where('email', $data['email']);
-                })->when(! empty($data['phone']), function ($q) use ($data) {
-                    $q->where('phone', $data['phone']);
+                $query->when(! empty(data_get($data, 'email')), function ($q) use ($data) {
+                    $q->where('email', data_get($data, 'email'));
+                })->when(! empty(data_get($data, 'phone')), function ($q) use ($data) {
+                    $q->where('phone', data_get($data, 'phone'));
                 });
             })->where('type', UserTypeEnum::PROVIDER->value)->first();
 
@@ -234,21 +234,21 @@ class AuthService
                 $user->fill($data);
 
                 $user->code = $code;
-                $user->name = $data['name'] ?? 'Usuário';
-                $user->type = $data['type'] ?? UserTypeEnum::PROVIDER->value;
+                $user->name = data_get($data, 'name', 'Usuário');
+                $user->type = data_get($data, 'type', UserTypeEnum::PROVIDER->value);
 
                 $user->save();
             }
 
-            if (! empty($data['email'])) {
+            if (! empty(data_get($data, 'email'))) {
                 $this->emailService->sendVerificationCode(
-                    email: $data['email'],
+                    email: data_get($data, 'email'),
                     code: $code,
-                    recipientName: $data['name'] ?? '',
+                    recipientName: data_get($data, 'name', ''),
                 );
-            } elseif (! empty($data['phone'])) {
+            } elseif (! empty(data_get($data, 'phone'))) {
                 Log::info('SMS: envio de código por telefone ainda não implementado.', [
-                    'phone' => $data['phone'],
+                    'phone' => data_get($data, 'phone'),
                 ]);
             }
 
@@ -271,9 +271,9 @@ class AuthService
 
     public function validateCodeClient(array $data, bool $isLogin): bool|array
     {
-        $email = $data['email'] ?? null;
-        $phone = $data['phone'] ?? null;
-        $code  = $data['code']  ?? '';
+        $email = data_get($data, 'email');
+        $phone = data_get($data, 'phone');
+        $code  = data_get($data, 'code', '');
 
         $user = User::where(function ($query) use ($email, $phone) {
             $query->when($email, fn ($q) => $q->where('email', $email))
@@ -305,9 +305,9 @@ class AuthService
 
     public function validateCodeProvider(array $data, bool $isLogin): bool|array
     {
-        $email = $data['email'] ?? null;
-        $phone = $data['phone'] ?? null;
-        $code  = $data['code']  ?? '';
+        $email = data_get($data, 'email');
+        $phone = data_get($data, 'phone');
+        $code  = data_get($data, 'code', '');
 
         $user = User::where(function ($query) use ($email, $phone) {
             $query->when($email, fn ($q) => $q->where('email', $email))
@@ -337,9 +337,9 @@ class AuthService
 
     public function validateCode(array $data, bool $isLogin): bool|array
     {
-        $email = $data['email'] ?? null;
-        $phone = $data['phone'] ?? null;
-        $code  = $data['code']  ?? '';
+        $email = data_get($data, 'email');
+        $phone = data_get($data, 'phone');
+        $code  = data_get($data, 'code', '');
 
         $user = User::where(function ($query) use ($email, $phone) {
             $query->when($email, function ($q) use ($email) {

+ 0 - 133
app/Services/CartItemService.php

@@ -1,133 +0,0 @@
-<?php
-
-namespace App\Services;
-
-use App\Enums\CartStatusEnum;
-use App\Enums\PaymentStatusEnum;
-use App\Models\Cart;
-use App\Models\CartItem;
-use App\Models\Payment;
-use App\Models\Schedule;
-use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Validation\ValidationException;
-
-class CartItemService
-{
-    public function getAll(): Collection
-    {
-        return CartItem::query()
-            ->orderBy('created_at', 'desc')
-            ->get();
-    }
-
-    public function findById(int $id): ?CartItem
-    {
-        return CartItem::find($id);
-    }
-
-    public function create(array $data): CartItem
-    {
-        return DB::transaction(function () use ($data) {
-            [$cart, $schedule] = $this->validateItem($data['cart_id'], $data['schedule_id']);
-
-            if (! $cart->provider_id) {
-                $cart->update(['provider_id' => $schedule->provider_id]);
-            }
-
-            return CartItem::create($data);
-        });
-    }
-
-    public function update(int $id, array $data): ?CartItem
-    {
-        return DB::transaction(function () use ($id, $data): ?CartItem {
-            $model = $this->findById($id);
-
-            if (! $model) {
-                return null;
-            }
-
-            $cartId     = $data['cart_id']     ?? $model->cart_id;
-            $scheduleId = $data['schedule_id'] ?? $model->schedule_id;
-
-            [$cart, $schedule] = $this->validateItem($cartId, $scheduleId);
-
-            if (! $cart->provider_id) {
-                $cart->update(['provider_id' => $schedule->provider_id]);
-            }
-
-            $model->update($data);
-
-            return $model->fresh();
-        });
-    }
-
-    public function delete(int $id): bool
-    {
-        $model = $this->findById($id);
-
-        if (!$model) {
-            return false;
-        }
-
-        return DB::transaction(function () use ($model): bool {
-            $this->validateItem($model->cart_id, $model->schedule_id);
-
-            return $model->delete();
-        });
-    }
-
-    private function validateItem(int $cartId, int $scheduleId): array
-    {
-        $cart     = Cart::query()->lockForUpdate()->findOrFail($cartId);
-        $schedule = Schedule::query()->findOrFail($scheduleId);
-
-        if ($cart->status !== CartStatusEnum::OPEN) {
-            throw ValidationException::withMessages([
-                'cart_id' => 'Somente carrinhos abertos podem ser alterados.',
-            ]);
-        }
-
-        if ($schedule->client_id !== $cart->client_id) {
-            throw ValidationException::withMessages([
-                'schedule_id' => 'O agendamento precisa pertencer ao mesmo cliente do carrinho.',
-            ]);
-        }
-
-        if (! $schedule->provider_id || ($cart->provider_id && $schedule->provider_id !== $cart->provider_id)) {
-            throw ValidationException::withMessages([
-                'schedule_id' => 'Todos os agendamentos do carrinho precisam ser do mesmo prestador.',
-            ]);
-        }
-
-        $belongsToAnotherCart = CartItem::query()
-            ->where('schedule_id', $scheduleId)
-            ->where('cart_id', '!=', $cartId)
-            ->exists();
-
-        if ($belongsToAnotherCart) {
-            throw ValidationException::withMessages([
-                'schedule_id' => 'O agendamento já pertence a outro carrinho.',
-            ]);
-        }
-
-        $hasActivePayment = Payment::query()
-            ->where('schedule_id', $scheduleId)
-            ->whereIn('status', [
-                PaymentStatusEnum::PENDING->value,
-                PaymentStatusEnum::PROCESSING->value,
-                PaymentStatusEnum::AUTHORIZED->value,
-                PaymentStatusEnum::PAID->value,
-            ])
-            ->exists();
-
-        if ($hasActivePayment) {
-            throw ValidationException::withMessages([
-                'schedule_id' => 'O agendamento já possui um pagamento e não pode ser adicionado ao carrinho.',
-            ]);
-        }
-
-        return [$cart, $schedule];
-    }
-}

+ 4 - 4
app/Services/ClientPaymentMethodService.php

@@ -36,8 +36,8 @@ class ClientPaymentMethodService
     public function create(array $data): ClientPaymentMethod
     {
         return DB::transaction(function () use ($data) {
-            if ($data['is_active'] ?? true) {
-                $this->deactivateOtherCards($data['client_id']);
+            if (data_get($data, 'is_active', true)) {
+                $this->deactivateOtherCards(data_get($data, 'client_id'));
             }
 
             $paymentMethod = ClientPaymentMethod::create($data);
@@ -45,7 +45,7 @@ class ClientPaymentMethodService
             $cardId = $this->pagarmeCardService->createCardForPaymentMethod($paymentMethod);
 
             if (empty($cardId)) {
-                throw new \RuntimeException('Nao foi possivel salvar o cartao no Pagar.me.');
+                throw new \RuntimeException(__('messages.pagarme_card_save_failed'));
             }
 
             return $paymentMethod->fresh();
@@ -54,7 +54,7 @@ class ClientPaymentMethodService
 
     public function update(ClientPaymentMethod $paymentMethod, array $data): ClientPaymentMethod
     {
-        if (isset($data['is_active']) && $data['is_active']) {
+        if (data_get($data, 'is_active') !== null && data_get($data, 'is_active')) {
             $this->deactivateOtherCards($paymentMethod->client_id, $paymentMethod->id);
         }
 

+ 2 - 2
app/Services/ClientProviderBlockService.php

@@ -35,8 +35,8 @@ class ClientProviderBlockService
 
     public function create(array $data): ClientProviderBlock
     {
-        $existing = ClientProviderBlock::where('client_id', $data['client_id'])
-            ->where('provider_id', $data['provider_id'])
+        $existing = ClientProviderBlock::where('client_id', data_get($data, 'client_id'))
+            ->where('provider_id', data_get($data, 'provider_id'))
             ->first();
 
         if ($existing) {

+ 35 - 35
app/Services/ClientService.php

@@ -45,13 +45,13 @@ class ClientService
     {
         $client = Client::with(['profileMedia'])->findOrFail($id);
 
-        if (isset($data['avatar']) && $data['avatar'] instanceof UploadedFile) {
+        if (data_get($data, 'avatar') !== null && data_get($data, 'avatar') instanceof UploadedFile) {
             $media = $this->mediaService->replaceFile(
-                newFile: $data['avatar'],
-                folder: "client/avatar/{$client->id}",
-                source: 'client',
+                newFile:  data_get($data, 'avatar'),
+                folder:   "client/avatar/{$client->id}",
+                source:   'client',
                 sourceId: $client->id,
-                old: $client->profileMedia,
+                old:      $client->profileMedia,
             );
 
             $data['profile_media_id'] = $media->id;
@@ -78,47 +78,47 @@ class ClientService
         try {
             DB::beginTransaction();
 
-            $user = User::when(! empty($data['email']), function ($q) use ($data) {
-                $q->where('email', $data['email']);
+            $user = User::when(! empty(data_get($data, 'email')), function ($q) use ($data) {
+                $q->where('email', data_get($data, 'email'));
             })
-                ->when(! empty($data['phone']), function ($q) use ($data) {
-                    $q->where('phone', $data['phone']);
+                ->when(! empty(data_get($data, 'phone')), function ($q) use ($data) {
+                    $q->where('phone', data_get($data, 'phone'));
                 })
                 ->where('type', 'CLIENT')
-                ->where('code', $data['code'])
+                ->where('code', data_get($data, 'code'))
                 ->first();
 
             if (! $user) {
                 throw new \Exception(__('messages.user_not_found_or_code_not_validated'));
             }
 
-            if (! empty($data['name'])) {
-                $user->name = $data['name'];
+            if (! empty(data_get($data, 'name'))) {
+                $user->name = data_get($data, 'name');
 
                 $user->save();
             }
 
             $client = Client::firstOrCreate(
-                ['user_id' => $user->id], ['document' => $data['document'] ?? null]
+                ['user_id' => $user->id], ['document' => data_get($data, 'document')]
             );
 
-            if (! empty($data['document'])) {
-                $client->document = $data['document'];
+            if (! empty(data_get($data, 'document'))) {
+                $client->document = data_get($data, 'document');
 
                 $client->save();
             }
 
             $client->refresh();
 
-            if (isset($data['avatar']) && $data['avatar'] instanceof UploadedFile) {
+            if (data_get($data, 'avatar') !== null && data_get($data, 'avatar') instanceof UploadedFile) {
                 $client->load('profileMedia');
 
                 $media = $this->mediaService->replaceFile(
-                    newFile: $data['avatar'],
-                    folder: "client/avatar/{$client->id}",
-                    source: 'client',
+                    newFile:  data_get($data, 'avatar'),
+                    folder:   "client/avatar/{$client->id}",
+                    source:   'client',
                     sourceId: $client->id,
-                    old: $client->profileMedia,
+                    old:      $client->profileMedia,
                 );
 
                 $client->profile_media_id = $media->id;
@@ -127,25 +127,25 @@ class ClientService
             }
 
             $addressData = [
-                'zip_code'       => $data['zip_code']       ?? null,
-                'address'        => $data['address']        ?? null,
-                'number'         => $data['number']         ?? null,
-                'district'       => $data['district']       ?? null,
-                'has_complement' => $data['has_complement'] ?? false,
-                'complement'     => $data['complement']     ?? null,
-                'nickname'       => $data['nickname']       ?? null,
-                'instructions'   => $data['instructions']   ?? null,
-                'address_type'   => $data['address_type']   ?? 'home',
+                'zip_code'       => data_get($data, 'zip_code'),
+                'address'        => data_get($data, 'address'),
+                'number'         => data_get($data, 'number'),
+                'district'       => data_get($data, 'district'),
+                'has_complement' => data_get($data, 'has_complement', false),
+                'complement'     => data_get($data, 'complement'),
+                'nickname'       => data_get($data, 'nickname'),
+                'instructions'   => data_get($data, 'instructions'),
+                'address_type'   => data_get($data, 'address_type', 'home'),
                 'is_primary'     => true,
-                'latitude'       => $data['latitude']   ?? null,
-                'longitude'      => $data['longitude']  ?? null,
+                'latitude'       => data_get($data, 'latitude'),
+                'longitude'      => data_get($data, 'longitude'),
             ];
 
-            if (! empty($data['state']) && ! empty($data['city'])) {
-                $state = State::where('code', $data['state'])->first();
+            if (! empty(data_get($data, 'state')) && ! empty(data_get($data, 'city'))) {
+                $state = State::where('code', data_get($data, 'state'))->first();
 
                 if ($state) {
-                    $city = City::where('name', $data['city'])->where('state_id', $state->id)->first();
+                    $city = City::where('name', data_get($data, 'city'))->where('state_id', $state->id)->first();
 
                     $addressData['state_id'] = $state->id;
                     $addressData['city_id']  = $city?->id;
@@ -181,7 +181,7 @@ class ClientService
         } catch (\Exception $e) {
             DB::rollBack();
 
-            Log::error('Error registering client: '.$e->getMessage());
+            Log::error('Erro ao cadastrar cliente: '.$e->getMessage());
 
             throw $e;
         }

+ 51 - 51
app/Services/CustomScheduleService.php

@@ -55,22 +55,22 @@ class CustomScheduleService
         DB::beginTransaction();
 
         try {
-            $quantity = $data['quantity'] ?? 1;
+            $quantity = data_get($data, 'quantity', 1);
 
-            $specialityIds = $data['speciality_ids'] ?? [];
+            $specialityIds = data_get($data, 'speciality_ids', []);
 
             $createdCustomSchedules = [];
 
             for ($i = 0; $i < $quantity; $i++) {
                 $scheduleData = [
-                    'client_id'     => $data['client_id'],
+                    'client_id'     => data_get($data, 'client_id'),
                     'provider_id'   => null,
-                    'address_id'    => $data['address_id'],
-                    'date'          => $data['date'],
-                    'period_type'   => $data['period_type'],
+                    'address_id'    => data_get($data, 'address_id'),
+                    'date'          => data_get($data, 'date'),
+                    'period_type'   => data_get($data, 'period_type'),
                     'schedule_type' => 'custom',
-                    'start_time'    => $data['start_time'],
-                    'end_time'      => $data['end_time'],
+                    'start_time'    => data_get($data, 'start_time'),
+                    'end_time'      => data_get($data, 'end_time'),
                     'status'        => 'pending',
                     'total_amount'  => 0,
                     'code'          => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT),
@@ -81,12 +81,12 @@ class CustomScheduleService
 
                 $customScheduleData = [
                     'schedule_id'     => $schedule->id,
-                    'address_type'    => $data['address_type'],
-                    'service_type_id' => $data['service_type_id'],
-                    'description'     => $data['description'] ?? null,
-                    'min_price'       => $data['min_price'],
-                    'max_price'       => $data['max_price'],
-                    'offers_meal'     => $data['offers_meal'] ?? false,
+                    'address_type'    => data_get($data, 'address_type'),
+                    'service_type_id' => data_get($data, 'service_type_id'),
+                    'description'     => data_get($data, 'description'),
+                    'min_price'       => data_get($data, 'min_price'),
+                    'max_price'       => data_get($data, 'max_price'),
+                    'offers_meal'     => data_get($data, 'offers_meal', false),
                 ];
 
                 $customSchedule = CustomSchedule::create($customScheduleData);
@@ -114,7 +114,7 @@ class CustomScheduleService
         } catch (\Exception $e) {
             DB::rollBack();
 
-            Log::error('Error creating custom schedule: '.$e->getMessage());
+            Log::error('Erro ao criar agendamento personalizado: '.$e->getMessage());
 
             throw $e;
         }
@@ -131,24 +131,24 @@ class CustomScheduleService
 
             $scheduleUpdateData = [];
 
-            if (isset($data['address_id'])) {
-                $scheduleUpdateData['address_id'] = $data['address_id'];
+            if (data_get($data, 'address_id') !== null) {
+                $scheduleUpdateData['address_id'] = data_get($data, 'address_id');
             }
 
-            if (isset($data['date'])) {
-                $scheduleUpdateData['date'] = $data['date'];
+            if (data_get($data, 'date') !== null) {
+                $scheduleUpdateData['date'] = data_get($data, 'date');
             }
 
-            if (isset($data['period_type'])) {
-                $scheduleUpdateData['period_type'] = $data['period_type'];
+            if (data_get($data, 'period_type') !== null) {
+                $scheduleUpdateData['period_type'] = data_get($data, 'period_type');
             }
 
-            if (isset($data['start_time'])) {
-                $scheduleUpdateData['start_time'] = $data['start_time'];
+            if (data_get($data, 'start_time') !== null) {
+                $scheduleUpdateData['start_time'] = data_get($data, 'start_time');
             }
 
-            if (isset($data['end_time'])) {
-                $scheduleUpdateData['end_time'] = $data['end_time'];
+            if (data_get($data, 'end_time') !== null) {
+                $scheduleUpdateData['end_time'] = data_get($data, 'end_time');
             }
 
             if (! empty($scheduleUpdateData)) {
@@ -157,40 +157,40 @@ class CustomScheduleService
 
             $customScheduleUpdateData = [];
 
-            if (isset($data['address_type'])) {
-                $customScheduleUpdateData['address_type'] = $data['address_type'];
+            if (data_get($data, 'address_type') !== null) {
+                $customScheduleUpdateData['address_type'] = data_get($data, 'address_type');
             }
 
-            if (isset($data['service_type_id'])) {
-                $customScheduleUpdateData['service_type_id'] = $data['service_type_id'];
+            if (data_get($data, 'service_type_id') !== null) {
+                $customScheduleUpdateData['service_type_id'] = data_get($data, 'service_type_id');
             }
 
-            if (isset($data['description'])) {
-                $customScheduleUpdateData['description'] = $data['description'];
+            if (data_get($data, 'description') !== null) {
+                $customScheduleUpdateData['description'] = data_get($data, 'description');
             }
 
-            if (isset($data['min_price'])) {
-                $customScheduleUpdateData['min_price'] = $data['min_price'];
+            if (data_get($data, 'min_price') !== null) {
+                $customScheduleUpdateData['min_price'] = data_get($data, 'min_price');
             }
 
-            if (isset($data['max_price'])) {
-                $customScheduleUpdateData['max_price'] = $data['max_price'];
+            if (data_get($data, 'max_price') !== null) {
+                $customScheduleUpdateData['max_price'] = data_get($data, 'max_price');
             }
 
-            if (isset($data['offers_meal'])) {
-                $customScheduleUpdateData['offers_meal'] = $data['offers_meal'];
+            if (data_get($data, 'offers_meal') !== null) {
+                $customScheduleUpdateData['offers_meal'] = data_get($data, 'offers_meal');
             }
 
             if (! empty($customScheduleUpdateData)) {
                 $customSchedule->update($customScheduleUpdateData);
             }
 
-            if (isset($data['speciality_ids'])) {
+            if (data_get($data, 'speciality_ids') !== null) {
                 $custom_schedule = CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id);
 
                 $custom_schedule->delete();
 
-                foreach ($data['speciality_ids'] as $specialityId) {
+                foreach (data_get($data, 'speciality_ids') as $specialityId) {
                     CustomScheduleSpeciality::create([
                         'custom_schedule_id' => $customSchedule->id,
                         'speciality_id'      => $specialityId,
@@ -209,7 +209,7 @@ class CustomScheduleService
         } catch (\Exception $e) {
             DB::rollBack();
 
-            Log::error('Error updating custom schedule: '.$e->getMessage());
+            Log::error('Erro ao atualizar agendamento personalizado: '.$e->getMessage());
 
             throw $e;
         }
@@ -235,7 +235,7 @@ class CustomScheduleService
         } catch (\Exception $e) {
             DB::rollBack();
 
-            Log::error('Error deleting custom schedule: '.$e->getMessage());
+            Log::error('Erro ao excluir agendamento personalizado: '.$e->getMessage());
 
             throw $e;
         }
@@ -405,8 +405,8 @@ class CustomScheduleService
         $notificationService = app(NotificationService::class);
 
         $notificationService->create([
-            'title'       => 'Nova proposta recebida!',
-            'description' => $provider->user->name . ' enviou uma proposta para seu agendamento sob medida.',
+            'title'       => __('notifications.new_proposal_title'),
+            'description' => __('notifications.new_proposal_description', ['provider' => $provider->user->name]),
             'origin'      => 'schedule',
             'origin_id'   => $schedule->id,
             'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_NEW_SOLICITATION->value,
@@ -434,8 +434,8 @@ class CustomScheduleService
         $notificationService = app(NotificationService::class);
 
         $notificationService->create([
-            'title'       => 'Oportunidade recusada!',
-            'description' => $provider->user->name . ' recusou sua solicitação sob medida.',
+            'title'       => __('notifications.opportunity_refused_title'),
+            'description' => __('notifications.opportunity_refused_description', ['provider' => $provider->user->name]),
             'origin'      => 'schedule',
             'origin_id'   => $scheduleId,
             'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_REFUSED->value,
@@ -473,7 +473,7 @@ class CustomScheduleService
                     $baseAmount = $provider->daily_price_2h;
                     break;
                 default:
-                    throw new \Exception('Periodo do agendamento invalido.');
+                    throw new \Exception(__('messages.invalid_schedule_period'));
             }
 
             $schedule->total_amount = $baseAmount;
@@ -489,8 +489,8 @@ class CustomScheduleService
             $schedule->load(['provider.user', 'client.user']);
 
             $notificationService->create([
-                'title'       => 'Proposta aceita!',
-                'description' => 'O cliente aceitou sua proposta de diária.',
+                'title'       => __('notifications.proposal_accepted_title'),
+                'description' => __('notifications.proposal_accepted_description'),
                 'origin'      => 'schedule',
                 'origin_id'   => $schedule->id,
                 'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_PROPOSAL_ACCEPTED->value,
@@ -520,8 +520,8 @@ class CustomScheduleService
             $notificationService = app(NotificationService::class);
 
             $notificationService->create([
-                'title'       => 'Proposta recusada!',
-                'description' => 'O cliente recusou sua proposta de diária.',
+                'title'       => __('notifications.proposal_refused_title'),
+                'description' => __('notifications.proposal_refused_description'),
                 'origin'      => 'schedule',
                 'origin_id'   => $proposal->schedule_id,
                 'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_REFUSED->value,
@@ -574,7 +574,7 @@ class CustomScheduleService
                             'address'    => $schedule->address->address,
                             'complement' => $schedule->address->complement,
                             'zip_code'   => $schedule->address->zip_code,
-                            'city'       => $schedule->address->city->name ?? '',
+                            'city'       => $schedule->address->city->name        ?? '',
                             'state'      => $schedule->address->city->state->name ?? '',
                         ] : null,
 

+ 41 - 41
app/Services/DashboardService.php

@@ -33,7 +33,7 @@ class DashboardService
         $user = Auth::user();
 
         if ($user->type !== UserTypeEnum::CLIENT) {
-            throw new AuthorizationException('Apenas clientes podem acessar este recurso.');
+            throw new AuthorizationException(__('messages.only_clients_allowed'));
         }
 
         $cliente = Client::with('profileMedia')->where('user_id', $user->id)->first();
@@ -95,24 +95,24 @@ class DashboardService
 
                 DB::raw("
                     (
-                        SELECT ci.cart_id
-                        FROM cart_items ci
-                        WHERE ci.schedule_id = schedules.id
+                        SELECT spi.service_package_id
+                        FROM service_package_items spi
+                        WHERE spi.schedule_id = schedules.id
                         LIMIT 1
-                    ) AS cart_id
+                    ) AS service_package_id
                 "),
 
                 DB::raw("
                     (
                         SELECT COUNT(*)
-                        FROM cart_items ci_count
-                        WHERE ci_count.cart_id = (
-                            SELECT ci.cart_id
-                            FROM cart_items ci
-                            WHERE ci.schedule_id = schedules.id
+                        FROM service_package_items spi_count
+                        WHERE spi_count.service_package_id = (
+                            SELECT spi.service_package_id
+                            FROM service_package_items spi
+                            WHERE spi.schedule_id = schedules.id
                             LIMIT 1
                         )
-                    ) AS cart_items_count
+                    ) AS service_package_items_count
                 "),
             )
             ->orderBy('schedules.date', 'asc')
@@ -215,8 +215,8 @@ class DashboardService
         );
 
         $providersCloseDistanceSelect = $this->distanceSelect(
-            $clientCoordinates['latitude'] ?? null,
-            $clientCoordinates['longitude'] ?? null,
+            data_get($clientCoordinates, 'latitude'),
+            data_get($clientCoordinates, 'longitude'),
         );
 
         $providerAddressLatestSubquery = DB::raw("
@@ -249,7 +249,7 @@ class DashboardService
             )
             ->whereNull('providers.deleted_at');
 
-        Log::info('Dashboard providersClose context', [
+        Log::info('Contexto de prestadores próximos no dashboard', [
             'client_id'                       => $cliente->id,
             'user_id'                         => $user->id,
             'client_primary_address_id'       => $clientPrimaryAddress?->id,
@@ -262,7 +262,7 @@ class DashboardService
             'providers_working_days_count'    => $providersWithWorkingDays->count(),
         ]);
 
-        Log::info('Dashboard providersClose filter counts', [
+        Log::info('Contagem dos filtros de prestadores próximos no dashboard', [
             'visible_with_bank' => (clone $providersCloseDebugQuery)
                 ->distinct('providers.id')
                 ->count('providers.id'),
@@ -363,7 +363,7 @@ class DashboardService
             ->orderByRaw('distance_km ASC NULLS LAST')
             ->get();
 
-        Log::info('Dashboard providersClose query result', [
+        Log::info('Resultado da consulta de prestadores próximos no dashboard', [
             'count'                => $providersClose->count(),
             'provider_ids_sample'  => $providersClose->pluck('provider_id')->take(20)->values()->all(),
             'null_distance_count'  => $providersClose->whereNull('distance_km')->count(),
@@ -379,7 +379,7 @@ class DashboardService
         ]);
 
         if ($providersClose->whereNull('distance_km')->isEmpty()) {
-            Log::info('Dashboard providersClose zip fallback skipped', [
+            Log::info('Fallback por CEP de prestadores próximos ignorado no dashboard', [
                 'reason' => 'all_distances_calculated_by_sql',
                 'count'  => $providersClose->count(),
             ]);
@@ -422,7 +422,7 @@ class DashboardService
             ->sortBy(fn ($provider) => $provider->distance_km ?? PHP_FLOAT_MAX)
             ->values();
 
-        Log::info('Dashboard providersClose final result', [
+        Log::info('Resultado final de prestadores próximos no dashboard', [
             'count' => $providersClose->count(),
             'items_sample' => $providersClose->take(5)->map(fn ($provider) => [
                 'provider_id'   => $provider->provider_id,
@@ -476,24 +476,24 @@ class DashboardService
 
                 DB::raw("
                     (
-                        SELECT ci.cart_id
-                        FROM cart_items ci
-                        WHERE ci.schedule_id = schedules.id
+                        SELECT spi.service_package_id
+                        FROM service_package_items spi
+                        WHERE spi.schedule_id = schedules.id
                         LIMIT 1
-                    ) AS cart_id
+                    ) AS service_package_id
                 "),
 
                 DB::raw("
                     (
                         SELECT COUNT(*)
-                        FROM cart_items ci_count
-                        WHERE ci_count.cart_id = (
-                            SELECT ci.cart_id
-                            FROM cart_items ci
-                            WHERE ci.schedule_id = schedules.id
+                        FROM service_package_items spi_count
+                        WHERE spi_count.service_package_id = (
+                            SELECT spi.service_package_id
+                            FROM service_package_items spi
+                            WHERE spi.schedule_id = schedules.id
                             LIMIT 1
                         )
-                    ) AS cart_items_count
+                    ) AS service_package_items_count
                 "),
             )
             ->orderBy('schedules.date', 'asc')
@@ -508,8 +508,8 @@ class DashboardService
         });
 
         $proposalsDistanceSelect = DistanceService::sqlExpression(
-            $clientCoordinates['latitude'] ?? null,
-            $clientCoordinates['longitude'] ?? null,
+            data_get($clientCoordinates, 'latitude'),
+            data_get($clientCoordinates, 'longitude'),
         );
 
         $schedulesProposals = ScheduleProposal::query()
@@ -648,24 +648,24 @@ class DashboardService
 
                 DB::raw("
                     (
-                        SELECT ci.cart_id
-                        FROM cart_items ci
-                        WHERE ci.schedule_id = schedules.id
+                        SELECT spi.service_package_id
+                        FROM service_package_items spi
+                        WHERE spi.schedule_id = schedules.id
                         LIMIT 1
-                    ) AS cart_id
+                    ) AS service_package_id
                 "),
 
                 DB::raw("
                     (
                         SELECT COUNT(*)
-                        FROM cart_items ci_count
-                        WHERE ci_count.cart_id = (
-                            SELECT ci.cart_id
-                            FROM cart_items ci
-                            WHERE ci.schedule_id = schedules.id
+                        FROM service_package_items spi_count
+                        WHERE spi_count.service_package_id = (
+                            SELECT spi.service_package_id
+                            FROM service_package_items spi
+                            WHERE spi.schedule_id = schedules.id
                             LIMIT 1
                         )
-                    ) AS cart_items_count
+                    ) AS service_package_items_count
                 "),
 
                 DB::raw("
@@ -731,7 +731,7 @@ class DashboardService
         $user = Auth::user();
 
         if ($user->type !== UserTypeEnum::PROVIDER) {
-            throw new AuthorizationException('Apenas prestadores podem acessar este recurso.');
+            throw new AuthorizationException(__('messages.only_providers_allowed'));
         }
 
         $provider = Provider::with('profileMedia')->where('user_id', $user->id)->first();

+ 3 - 3
app/Services/DeviceTokenService.php

@@ -10,11 +10,11 @@ class DeviceTokenService
     public function register(array $data): DeviceToken
     {
         return DeviceToken::updateOrCreate(
-            ['token' => $data['token']],
+            ['token' => data_get($data, 'token')],
             [
                 'user_id'  => Auth::id(),
-                'platform' => $data['platform'],
-                'app_type' => $data['app_type'],
+                'platform' => data_get($data, 'platform'),
+                'app_type' => data_get($data, 'app_type'),
                 'active'   => true,
             ]
         );

+ 4 - 5
app/Services/GeminiService.php

@@ -47,8 +47,7 @@ class GeminiService
 
             $data = json_decode($response->getBody()->getContents(), true);
 
-            return $data['candidates'][0]['content']['parts'][0]['text']
-                ?? __('chatbot.error_response');
+            return data_get($data, 'candidates.0.content.parts.0.text', __('chatbot.error_response'));
         } catch (GuzzleException $e) {
             $this->logGeminiError($e, $model);
 
@@ -68,11 +67,11 @@ class GeminiService
         $contents = [];
 
         foreach ($history as $entry) {
-            $role = $entry['role'] === 'model' ? 'model' : 'user';
+            $role = data_get($entry, 'role') === 'model' ? 'model' : 'user';
 
             $contents[] = [
                 'role'  => $role,
-                'parts' => [['text' => $entry['text']]],
+                'parts' => [['text' => data_get($entry, 'text')]],
             ];
         }
 
@@ -114,7 +113,7 @@ class GeminiService
             $context['body']   = $this->sanitizeApiKey((string) $response->getBody());
         }
 
-        Log::error('Gemini API error', $context);
+        Log::error('Erro na API do Gemini', $context);
     }
 
     private function sanitizeApiKey(string $message): string

+ 7 - 7
app/Services/Pagarme/Concerns/FormatsPagarmeData.php

@@ -57,20 +57,20 @@ trait FormatsPagarmeData
 
     protected function extractAddressParts(array $data): array
     {
-        $addressLine   = trim((string) ($data['address'] ?? ''));
+        $addressLine   = trim((string) data_get($data, 'address', ''));
         $segments      = array_map('trim', explode(',', $addressLine));
-        $streetSegment = $segments[0] ?? '';
+        $streetSegment = data_get($segments, 0, '');
 
-        if (($data['number'] ?? null) === null) {
+        if ((data_get($data, 'number')) === null) {
             preg_match('/^(\d+)/', $streetSegment, $matches);
         }
 
         return [
             'street'          => $streetSegment,
-            'street_number'   => (string) ($data['number'] ?? $matches[1] ?? 'S/N'),
-            'neighborhood'    => (string) ($data['district'] ?? $segments[1] ?? 'N/A'),
-            'reference_point' => (string) ($data['reference_point'] ?? 'N/A'),
-            'complementary'   => (string) ($data['complement'] ?? 'N/A'),
+            'street_number'   => (string) data_get($data, 'number', data_get($matches, 1, 'S/N')),
+            'neighborhood'    => (string) data_get($data, 'district', data_get($segments, 1, 'N/A')),
+            'reference_point' => (string) data_get($data, 'reference_point', 'N/A'),
+            'complementary'   => (string) data_get($data, 'complement', 'N/A'),
         ];
     }
 

+ 4 - 4
app/Services/Pagarme/Concerns/SendsPagarmeRequests.php

@@ -26,7 +26,7 @@ trait SendsPagarmeRequests
             $result = $response->json() ?? [];
 
             if (app()->environment('local', 'development')) {
-                Log::channel('pagarme')->info('Pagar.me request succeeded', [
+                Log::channel('pagarme')->info('Requisição ao Pagar.me concluída com sucesso', [
                     'method'    => strtoupper($method),
                     'endpoint'  => $endpoint,
                     'origin_ip' => $this->pagarmeOriginIp(),
@@ -45,7 +45,7 @@ trait SendsPagarmeRequests
                 $responseBody = $e->response->json();
             }
 
-            Log::channel('pagarme')->error('Pagar.me request failed', [
+            Log::channel('pagarme')->error('Falha na requisição ao Pagar.me', [
                 'method'    => strtoupper($method),
                 'endpoint'  => $endpoint,
                 'origin_ip' => $this->pagarmeOriginIp(),
@@ -55,7 +55,7 @@ trait SendsPagarmeRequests
             ]);
 
             $message = $errorMessage;
-            $errors  = $responseBody['errors'] ?? [];
+            $errors  = data_get($responseBody, 'errors', []);
 
             if ($errors) {
                 $details = [];
@@ -82,7 +82,7 @@ trait SendsPagarmeRequests
         $secretKey = config('services.pagarme.secret_key');
 
         if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+            Log::channel('pagarme')->error('Chave PAGARME_SECRET_KEY não está configurada.');
 
             throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
         }

+ 12 - 12
app/Services/Pagarme/PagarmeCustomerService.php

@@ -26,15 +26,15 @@ class PagarmeCustomerService
 
         $client->loadMissing('user');
 
-        $name  = $client->user?->name  ?? $data['name']  ?? 'Cliente';
-        $email = $client->user?->email ?? $data['email'] ?? null;
+        $name  = $client->user?->name  ?? data_get($data, 'name', 'Cliente');
+        $email = $client->user?->email ?? data_get($data, 'email');
         $code  = $client->ensureGatewayCode();
 
-        $document     = $this->customerDocument($client->document ?? $data['document'] ?? null);
+        $document     = $this->customerDocument($client->document ?? data_get($data, 'document'));
         $documentType = $this->customerDocumentType($document);
 
         $address = $this->buildAddressData($data);
-        $phones  = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null);
+        $phones  = $this->buildPhones($client->user?->phone ?? data_get($data, 'phone'));
 
         if ($this->shouldMockPagarmeRequest()) {
             $customerId = $this->mockPagarmeId('cus', $client->id);
@@ -81,18 +81,18 @@ class PagarmeCustomerService
     private function buildAddressData(array $data): AddressData
     {
         $line1Parts = array_filter([
-            (string) ($data['number'] ?? '0'),
-            (string) ($data['address'] ?? ''),
-            (string) ($data['district'] ?? ''),
+            (string) data_get($data, 'number', '0'),
+            (string) data_get($data, 'address', ''),
+            (string) data_get($data, 'district', ''),
         ], static fn ($value) => $value !== '');
 
         return new AddressData(
             line1:   implode(', ', $line1Parts),
-            line2:   (string) ($data['complement'] ?? $data['instructions'] ?? ''),
-            zipCode: $this->digits($data['zip_code'] ?? null),
-            city:    (string) ($data['city'] ?? ''),
-            state:   (string) ($data['state'] ?? ''),
-            country: (string) ($data['country'] ?? 'BR'),
+            line2:   (string) data_get($data, 'complement', data_get($data, 'instructions', '')),
+            zipCode: $this->digits(data_get($data, 'zip_code')),
+            city:    (string) data_get($data, 'city', ''),
+            state:   (string) data_get($data, 'state', ''),
+            country: (string) data_get($data, 'country', 'BR'),
         );
     }
 

+ 55 - 50
app/Services/Pagarme/PagarmePaymentService.php

@@ -2,6 +2,7 @@
 
 namespace App\Services\Pagarme;
 
+use App\Exceptions\PaymentException;
 use App\Data\Pagarme\Customer\CustomerRequestData;
 use App\Data\Pagarme\Customer\Parts\Request\AddressData;
 use App\Data\Pagarme\Customer\Parts\Request\PhoneData;
@@ -18,7 +19,7 @@ use App\Data\Pagarme\Order\Parts\Request\SplitOptionsData;
 use App\Enums\PaymentSplitStatusEnum;
 use App\Enums\PaymentStatusEnum;
 use App\Models\Address;
-use App\Models\Cart;
+use App\Models\ServicePackage;
 use App\Models\Client;
 use App\Models\Payment;
 use App\Models\PaymentSplit;
@@ -39,11 +40,11 @@ class PagarmePaymentService
     public function calculatePaymentAmounts(float $serviceAmount, string $paymentMethod, ?Schedule $schedule = null): array
     {
         if ($serviceAmount <= 0) {
-            throw new \InvalidArgumentException('Valor do servico precisa ser maior que zero.');
+            throw new PaymentException;
         }
 
         if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
-            throw new \InvalidArgumentException('Forma de pagamento invalida.');
+            throw new PaymentException;
         }
 
         $platformFeeRate = $this->platformFeeRate($paymentMethod, $schedule);
@@ -64,8 +65,8 @@ class PagarmePaymentService
 
     private function platformFeeRate(string $paymentMethod, ?Schedule $schedule = null): float
     {
-        if ($schedule && $this->scheduleBelongsToCartWithAtLeastThreeItems($schedule)) {
-            return $this->configuredPlatformFeeRate('platform_cart_min_3_schedules_fee_rate');
+        if ($schedule && $this->scheduleBelongsToServicePackageWithAtLeastThreeItems($schedule)) {
+            return $this->configuredPlatformFeeRate('platform_service_package_min_3_schedules_fee_rate');
         }
 
         return $paymentMethod === 'credit_card'
@@ -76,9 +77,9 @@ class PagarmePaymentService
     public function platformFeeRates(): array
     {
         return [
-            'pix'                  => $this->configuredPlatformFeeRate('platform_pix_fee_rate'),
-            'credit_card'          => $this->configuredPlatformFeeRate('platform_credit_card_fee_rate'),
-            'cart_min_3_schedules' => $this->configuredPlatformFeeRate('platform_cart_min_3_schedules_fee_rate'),
+            'pix'                             => $this->configuredPlatformFeeRate('platform_pix_fee_rate'),
+            'credit_card'                     => $this->configuredPlatformFeeRate('platform_credit_card_fee_rate'),
+            'service_package_min_3_schedules' => $this->configuredPlatformFeeRate('platform_service_package_min_3_schedules_fee_rate'),
         ];
     }
 
@@ -161,17 +162,17 @@ class PagarmePaymentService
         );
     }
 
-    public function processCartPayment(
-        Payment $payment,
+    public function processServicePackagePayment(
+        Payment    $payment,
         Collection $schedules,
-        string $paymentMethod,
-        ?string $cardId = null,
-        array $options = [],
+        string     $paymentMethod,
+        ?string    $cardId  = null,
+        array      $options = [],
     ): array {
         $firstSchedule = $schedules->first();
 
         if (! $firstSchedule) {
-            throw new \InvalidArgumentException('Carrinho precisa ter ao menos um agendamento.');
+            throw new PaymentException;
         }
 
         $items = $schedules
@@ -183,13 +184,14 @@ class PagarmePaymentService
 
         $customer = $this->buildCustomer($firstSchedule, $options);
         $split    = $this->buildSplit($payment, $options);
+
         $paymentOptions = config('services.pagarme.pix_disable_split')
             ? []
             : ['split' => $split];
 
         $metadata = [
-            'cart_id'      => (string) $payment->cart_id,
-            'schedule_ids' => $schedules->pluck('id')->implode(','),
+            'service_package_id' => (string) $payment->service_package_id,
+            'schedule_ids'       => $schedules->pluck('id')->implode(','),
         ];
 
         if ($paymentMethod === 'credit_card') {
@@ -208,7 +210,7 @@ class PagarmePaymentService
                 options:    ['split' => $split, 'metadata' => $metadata],
             );
 
-            $this->logCreditCardOrderResult($payment, $result, 'create_cart_order');
+            $this->logCreditCardOrderResult($payment, $result, 'create_service_package_order');
 
             return $result;
         }
@@ -248,7 +250,7 @@ class PagarmePaymentService
 
             paymentMethod: OrderRequestData::creditCardPaymentMethod(
                 creditCard: $creditCard,
-                split:      is_array($options['split'] ?? null) ? $options['split'] : null,
+                split:      is_array(data_get($options, 'split')) ? data_get($options, 'split') : null,
             ),
 
             options: $options,
@@ -269,7 +271,7 @@ class PagarmePaymentService
 
             paymentMethod: OrderRequestData::pixPaymentMethod(
                 pix:   $pix,
-                split: is_array($options['split'] ?? null) ? $options['split'] : null,
+                split: is_array(data_get($options, 'split')) ? data_get($options, 'split') : null,
             ),
 
             options: $options,
@@ -284,14 +286,14 @@ class PagarmePaymentService
         array               $options = []
     ): array {
         $metadata = array_filter([
-            'payment_id'  => (string) $payment->id,
-            'schedule_id' => $payment->schedule_id ? (string) $payment->schedule_id : null,
-            'cart_id'     => $payment->cart_id ? (string) $payment->cart_id : null,
-            'client_id'   => (string) $payment->client_id,
-            'provider_id' => $payment->provider_id ? (string) $payment->provider_id : null,
+            'payment_id'         => (string) $payment->id,
+            'schedule_id'        => $payment->schedule_id ? (string) $payment->schedule_id : null,
+            'service_package_id' => $payment->service_package_id ? (string) $payment->service_package_id : null,
+            'client_id'          => (string) $payment->client_id,
+            'provider_id'        => $payment->provider_id ? (string) $payment->provider_id : null,
         ], fn ($value) => $value !== null);
 
-        $metadata = array_merge($metadata, $options['metadata'] ?? []);
+        $metadata = array_merge($metadata, data_get($options, 'metadata', []));
 
         $requestData = new OrderRequestData(
             code:       $payment->ensureGatewayCode(),
@@ -299,9 +301,9 @@ class PagarmePaymentService
             payments:   [$paymentMethod],
             metadata:   $metadata,
             customer:   $customer,
-            customerId: $options['customer_id'] ?? null,
-            closed:     $options['closed']      ?? true,
-            channel:    $options['channel']     ?? null,
+            customerId: data_get($options, 'customer_id'),
+            closed:     data_get($options, 'closed', true),
+            channel:    data_get($options, 'channel'),
         );
 
         if ($this->shouldMockPagarmeRequest()) {
@@ -389,9 +391,9 @@ class PagarmePaymentService
 
     //
 
-    private function scheduleBelongsToCartWithAtLeastThreeItems(Schedule $schedule): bool
+    private function scheduleBelongsToServicePackageWithAtLeastThreeItems(Schedule $schedule): bool
     {
-        return Cart::query()
+        return ServicePackage::query()
             ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
             ->whereHas('items', null, '>=', 3)
             ->exists();
@@ -404,7 +406,7 @@ class PagarmePaymentService
         $customer = $requestData->customer?->toArray() ?? [];
 
         return array_merge($customer, [
-            'id'         => $requestData->customerId ?: $this->mockPagarmeId('cus', $customer['code'] ?? $requestData->code),
+            'id'         => $requestData->customerId ?: $this->mockPagarmeId('cus', data_get($customer, 'code', $requestData->code)),
             'delinquent' => false,
         ]);
     }
@@ -421,8 +423,8 @@ class PagarmePaymentService
         $transactionId = $this->mockPagarmeId('tran', $payment->id);
 
         $amount = array_sum(array_map(
-            static fn (array $item): int => ((int) ($item['amount'] ?? 0)) * ((int) ($item['quantity'] ?? 1)),
-            $payload['items'] ?? [],
+            static fn (array $item): int => ((int) data_get($item, 'amount', 0)) * ((int) data_get($item, 'quantity', 1)),
+            data_get($payload, 'items', []),
         ));
 
         $isCreditCard = $paymentMethod->paymentMethod === 'credit_card';
@@ -458,7 +460,7 @@ class PagarmePaymentService
             'currency' => 'BRL',
             'closed'   => true,
             'status'   => $chargeStatus,
-            'items'    => $payload['items'] ?? [],
+            'items'    => data_get($payload, 'items', []),
             'customer' => $this->mockCustomerResponse($requestData),
 
             'charges' => [[
@@ -495,19 +497,19 @@ class PagarmePaymentService
             'documento' => $client->document,
         ] as $field => $value) {
             if ($value === null || $value === '') {
-                throw new \InvalidArgumentException("Cliente precisa ter {$field} para criar pedido no Pagar.me.");
+                throw new PaymentException;
             }
         }
 
         if (! $address) {
-            throw new \InvalidArgumentException('Endereco do agendamento nao encontrado para criar pedido no Pagar.me.');
+            throw new PaymentException;
         }
 
         $document     = $this->customerDocument($client->document);
         $documentType = $this->customerDocumentType($document);
 
         $phone = $this->buildPhonePayload($user->phone)
-            ?: $this->buildPhonePayload($options['phone'] ?? null);
+            ?: $this->buildPhonePayload(data_get($options, 'phone'));
 
         $state   = $address->state?->code ?? $address->city?->state?->code;
         $city    = $address->city?->name;
@@ -528,7 +530,7 @@ class PagarmePaymentService
             'telefone'  => $phone,
         ] as $field => $value) {
             if ($value === null || $value === '' || $value === []) {
-                throw new \InvalidArgumentException("Cliente precisa ter {$field} valido para criar pedido no Pagar.me.");
+                throw new PaymentException;
             }
         }
 
@@ -546,9 +548,9 @@ class PagarmePaymentService
         if ($phone) {
             $customerPhones = new PhonesData(
                 mobilePhone: new PhoneData(
-                    countryCode: $phone['country_code'],
-                    areaCode:    $phone['area_code'],
-                    number:      $phone['number'],
+                    countryCode: data_get($phone, 'country_code'),
+                    areaCode:    data_get($phone, 'area_code'),
+                    number:      data_get($phone, 'number'),
                 ),
             );
         }
@@ -663,7 +665,7 @@ class PagarmePaymentService
         $transaction = $order->lastTransaction();
         $failureCode = $order->failureCode();
 
-        Log::channel('pagarme')->info('Pagar.me credit card order result', [
+        Log::channel('pagarme')->info('Resultado do pedido com cartão de crédito no Pagar.me', [
             'source'             => $source,
             'payment_id'         => $payment->id,
             'provider_id'        => $payment->provider_id,
@@ -689,12 +691,13 @@ class PagarmePaymentService
         $payload['failure_code']    = $failureCode;
         $payload['failure_message'] = $failureMessage;
 
-        if (isset($payload['charges'][0]['last_transaction']['gateway_response'])
-            && is_array($payload['charges'][0]['last_transaction']['gateway_response'])) {
-            $payload['charges'][0]['last_transaction']['gateway_response'] = $this->normalizeGatewayResponseForFailure(
-                $payload['charges'][0]['last_transaction']['gateway_response'],
+        $gatewayResponse = data_get($payload, 'charges.0.last_transaction.gateway_response');
+
+        if (is_array($gatewayResponse)) {
+            data_set($payload, 'charges.0.last_transaction.gateway_response', $this->normalizeGatewayResponseForFailure(
+                $gatewayResponse,
                 $failureCode,
-            );
+            ));
         }
 
         return $payload;
@@ -702,7 +705,7 @@ class PagarmePaymentService
 
     private function normalizeGatewayResponseForFailure(array $gatewayResponse, ?string $failureCode): array
     {
-        $code = $gatewayResponse['code'] ?? null;
+        $code = data_get($gatewayResponse, 'code');
 
         if (! $failureCode || ! $this->isMisleadingGatewayCode($code)) {
             return $gatewayResponse;
@@ -727,13 +730,15 @@ class PagarmePaymentService
             return $payment->idempotency_key;
         }
 
-        $payment->loadMissing(['client.user', 'provider.user', 'schedule', 'cart']);
+        $payment->loadMissing(['client.user', 'provider.user', 'schedule', 'servicePackage']);
 
         $amountCents = (int) round((float) $payment->gross_amount * 100);
 
         $key = $this->pagarmeIdempotencyKey('order', [
             "payment-{$payment->id}",
-            $payment->cart_id ? "cart-{$payment->cart_id}" : "schedule-{$payment->schedule_id}",
+            $payment->service_package_id
+                ? "service-package-{$payment->service_package_id}"
+                : "schedule-{$payment->schedule_id}",
             $payment->client?->user?->name ?: "client-{$payment->client_id}",
             $payment->provider?->user?->name ?: ($payment->provider_id ? "provider-{$payment->provider_id}" : 'provider-none'),
             $payment->payment_method,

+ 47 - 47
app/Services/Pagarme/PagarmeRecipientService.php

@@ -29,39 +29,39 @@ class PagarmeRecipientService
             return $provider->recipient_id;
         }
 
-        $metadata      = $data['recipient_metadata'] ?? [];
-        $paymentMode   = $data['recipient_payment_mode'];
+        $metadata      = data_get($data, 'recipient_metadata', []);
+        $paymentMode   = data_get($data, 'recipient_payment_mode');
         $recipientCode = $provider->ensureGatewayCode();
 
         $addressParts = $this->extractAddressParts($data);
 
         $registerInformation = new RegisterInformationData(
-            name:                   $data['recipient_name'],
-            email:                  $data['recipient_email'],
-            document:               $this->digits($data['recipient_document'] ?? null),
-            type:                   $data['recipient_type'] ?? 'individual',
-            birthdate:              $this->formatBirthdate($data['birth_date'] ?? null),
-            monthlyIncome:          isset($data['monthly_income']) ? (int) $data['monthly_income'] : 1000,
-            professionalOccupation: $data['professional_occupation'] ?? 'autonomo',
+            name:                   data_get($data, 'recipient_name'),
+            email:                  data_get($data, 'recipient_email'),
+            document:               $this->digits(data_get($data, 'recipient_document')),
+            type:                   data_get($data, 'recipient_type', 'individual'),
+            birthdate:              $this->formatBirthdate(data_get($data, 'birth_date')),
+            monthlyIncome:          data_get($data, 'monthly_income') !== null ? (int) data_get($data, 'monthly_income') : 1000,
+            professionalOccupation: data_get($data, 'professional_occupation', 'autonomo'),
 
             phoneNumbers: new PhoneNumbersData(
-                $this->buildRecipientPhone($data['phone'] ?? null),
+                $this->buildRecipientPhone(data_get($data, 'phone')),
             ),
 
             address: new AddressData(
-                street:         $addressParts['street'],
-                complementary:  $addressParts['complementary'],
-                streetNumber:   $addressParts['street_number'],
-                neighborhood:   $addressParts['neighborhood'],
-                city:           $data['city']  ?? null,
-                state:          $data['state'] ?? null,
-                zipCode:        $this->digits($data['zip_code'] ?? null),
-                referencePoint: $addressParts['reference_point'],
+                street:         data_get($addressParts, 'street'),
+                complementary:  data_get($addressParts, 'complementary'),
+                streetNumber:   data_get($addressParts, 'street_number'),
+                neighborhood:   data_get($addressParts, 'neighborhood'),
+                city:           data_get($data, 'city'),
+                state:          data_get($data, 'state'),
+                zipCode:        $this->digits(data_get($data, 'zip_code')),
+                referencePoint: data_get($addressParts, 'reference_point'),
             ),
         );
 
         $defaultBankAccount = $this->buildRecipientBankAccount(
-            $data['recipient_default_bank_account'],
+            data_get($data, 'recipient_default_bank_account'),
         );
 
         $payload = new RecipientRequestData(
@@ -90,10 +90,10 @@ class PagarmeRecipientService
 
             $provider->forceFill([
                 'recipient_id'                   => $recipientId,
-                'recipient_name'                 => $data['recipient_name'],
-                'recipient_email'                => $data['recipient_email'],
-                'recipient_description'          => $data['recipient_description'] ?? '',
-                'recipient_document'             => $data['recipient_document'],
+                'recipient_name'                 => data_get($data, 'recipient_name'),
+                'recipient_email'                => data_get($data, 'recipient_email'),
+                'recipient_description'          => data_get($data, 'recipient_description', ''),
+                'recipient_document'             => data_get($data, 'recipient_document'),
                 'recipient_type'                 => $payload->registerInformation->type,
                 'recipient_code'                 => $recipientCode,
                 'recipient_payment_mode'         => $paymentMode,
@@ -131,10 +131,10 @@ class PagarmeRecipientService
 
         $provider->forceFill([
             'recipient_id'                   => $recipientId,
-            'recipient_name'                 => $data['recipient_name'],
-            'recipient_email'                => $data['recipient_email'],
-            'recipient_description'          => $data['recipient_description'] ?? '',
-            'recipient_document'             => $data['recipient_document'],
+            'recipient_name'                 => data_get($data, 'recipient_name'),
+            'recipient_email'                => data_get($data, 'recipient_email'),
+            'recipient_description'          => data_get($data, 'recipient_description', ''),
+            'recipient_document'             => data_get($data, 'recipient_document'),
             'recipient_type'                 => $payload->registerInformation->type,
             'recipient_code'                 => $recipientCode,
             'recipient_payment_mode'         => $paymentMode,
@@ -161,15 +161,15 @@ class PagarmeRecipientService
     public function updateDefaultBankAccount(Provider $provider, array $bankAccountData): Provider
     {
         $payload = new BankAccountUpdateRequestData(
-            holderName:        $this->normalizeHolderName($bankAccountData['holder_name']),
-            holderType:        $bankAccountData['holder_type'],
-            holderDocument:    $this->digits($bankAccountData['holder_document']),
-            bank:              $bankAccountData['bank'],
-            branchNumber:      $bankAccountData['branch_number'],
-            branchCheckDigit:  $bankAccountData['branch_check_digit'] ?? null,
-            accountNumber:     $bankAccountData['account_number'],
-            accountCheckDigit: $bankAccountData['account_check_digit'],
-            type:              $bankAccountData['type'],
+            holderName:        $this->normalizeHolderName(data_get($bankAccountData, 'holder_name')),
+            holderType:        data_get($bankAccountData, 'holder_type'),
+            holderDocument:    $this->digits(data_get($bankAccountData, 'holder_document')),
+            bank:              data_get($bankAccountData, 'bank'),
+            branchNumber:      data_get($bankAccountData, 'branch_number'),
+            branchCheckDigit:  data_get($bankAccountData, 'branch_check_digit'),
+            accountNumber:     data_get($bankAccountData, 'account_number'),
+            accountCheckDigit: data_get($bankAccountData, 'account_check_digit'),
+            type:              data_get($bankAccountData, 'type'),
         );
 
         if ($this->shouldMockPagarmeRequest()) {
@@ -200,15 +200,15 @@ class PagarmeRecipientService
     private function buildRecipientBankAccount(array $data): BankAccountData
     {
         return new BankAccountData(
-            holderName:        $this->normalizeHolderName($data['holder_name']),
-            holderType:        $data['holder_type'],
-            holderDocument:    $this->digits($data['holder_document']),
-            bank:              $data['bank'],
-            branchNumber:      $data['branch_number'],
-            branchCheckDigit:  $data['branch_check_digit'] ?? null,
-            accountNumber:     $data['account_number'],
-            accountCheckDigit: $data['account_check_digit'],
-            type:              $data['type'],
+            holderName:        $this->normalizeHolderName(data_get($data, 'holder_name')),
+            holderType:        data_get($data, 'holder_type'),
+            holderDocument:    $this->digits(data_get($data, 'holder_document')),
+            bank:              data_get($data, 'bank'),
+            branchNumber:      data_get($data, 'branch_number'),
+            branchCheckDigit:  data_get($data, 'branch_check_digit'),
+            accountNumber:     data_get($data, 'account_number'),
+            accountCheckDigit: data_get($data, 'account_check_digit'),
+            type:              data_get($data, 'type'),
         );
     }
 
@@ -245,8 +245,8 @@ class PagarmeRecipientService
             $provider->loadMissing('user');
 
             $baseKey = $this->pagarmeIdempotencyKey('recipient', [
-                $data['recipient_email'] ?? $provider->recipient_email ?? $provider->user?->email ?? "provider-{$provider->id}",
-                $this->customerDocument($data['recipient_document'] ?? $provider->recipient_document ?? $provider->document)
+                data_get($data, 'recipient_email', $provider->recipient_email ?? $provider->user?->email ?? "provider-{$provider->id}"),
+                $this->customerDocument(data_get($data, 'recipient_document', $provider->recipient_document ?? $provider->document))
                     ?: "document-provider-{$provider->id}",
             ]);
 

+ 1 - 1
app/Services/Pagarme/PagarmeTransferService.php

@@ -104,7 +104,7 @@ class PagarmeTransferService
         return new TransferResponseData(
             id:                   $transferId,
             amount:               $withdrawal ? (int) round((float) $withdrawal->gross_amount * 100) : 0,
-            type:                 $withdrawal?->type ?? 'credito_em_conta',
+            type:                 $withdrawal?->type           ?? 'credito_em_conta',
             status:               $withdrawal?->status?->value ?? 'pending_transfer',
             fee:                  $withdrawal ? (int) round((float) $withdrawal->gateway_fee_amount * 100) : 0,
             fundingDate:          $withdrawal?->completed_at?->toISOString(),

+ 157 - 119
app/Services/PaymentService.php

@@ -2,10 +2,12 @@
 
 namespace App\Services;
 
-use App\Enums\CartStatusEnum;
+use App\Enums\ServicePackageStatusEnum;
 use App\Enums\PaymentSplitStatusEnum;
 use App\Enums\PaymentStatusEnum;
-use App\Models\Cart;
+use App\Exceptions\PaymentFailedException;
+use App\Exceptions\PaymentException;
+use App\Models\ServicePackage;
 use App\Models\ClientPaymentMethod;
 use App\Models\Payment;
 use App\Models\PaymentSplit;
@@ -15,6 +17,7 @@ use Carbon\Carbon;
 use Illuminate\Auth\Access\AuthorizationException;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Support\Collection as SupportCollection;
+use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Str;
 
@@ -27,7 +30,7 @@ class PaymentService
     public function getAll(): Collection
     {
         return Payment::query()
-            ->with(['client.user', 'provider.user', 'schedule', 'cart.items.schedule'])
+            ->with(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule'])
             ->orderBy('created_at', 'desc')
             ->get();
     }
@@ -35,7 +38,7 @@ class PaymentService
     public function findById(int $id): ?Payment
     {
         return Payment::query()
-            ->with(['client.user', 'provider.user', 'schedule', 'cart.items.schedule'])
+            ->with(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule'])
             ->find($id);
     }
 
@@ -77,29 +80,55 @@ class PaymentService
 
     //
 
-    public function payAcceptedSchedule(
-        Schedule $schedule, string $paymentMethod, ?int $clientPaymentMethodId = null, array $options = []
+    public function paySchedule(
+        int    $scheduleId,
+        string $paymentMethod,
+        ?int   $clientPaymentMethodId = null,
+        array  $options               = [],
+    ): Payment {
+        $schedule = Schedule::query()
+            ->with(['client', 'provider', 'customSchedule.serviceType'])
+            ->findOrFail($scheduleId);
+
+        if ($schedule->client?->user_id !== (int) Auth::id()) {
+            throw new AuthorizationException;
+        }
+
+        return $this->processAcceptedSchedulePayment(
+            schedule:              $schedule,
+            paymentMethod:         $paymentMethod,
+            clientPaymentMethodId: $clientPaymentMethodId,
+
+            options: $options,
+        );
+    }
+
+    private function processAcceptedSchedulePayment(
+        Schedule $schedule,
+        string   $paymentMethod,
+        ?int     $clientPaymentMethodId = null,
+        array    $options               = [],
     ): Payment {
         $schedule->loadMissing(['client', 'provider', 'customSchedule.serviceType']);
 
         if ($schedule->status !== 'accepted') {
-            throw new \InvalidArgumentException('Agendamento precisa estar aceito para ser pago.');
+            throw new PaymentException;
         }
 
         if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
-            throw new \InvalidArgumentException('Forma de pagamento invalida.');
+            throw new PaymentException;
         }
 
         if (! $schedule->provider_id || ! $schedule->provider) {
-            throw new \InvalidArgumentException('Agendamento precisa ter prestador confirmado para gerar pagamento.');
+            throw new PaymentException;
         }
 
         if ((float) $schedule->total_amount <= 0) {
-            throw new \InvalidArgumentException('Agendamento precisa ter valor maior que zero para gerar pagamento.');
+            throw new PaymentException;
         }
 
         if (empty($schedule->provider->recipient_id)) {
-            throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para receber split.');
+            throw new PaymentException;
         }
 
         $this->ensureScheduleCanBePaidIndividually($schedule);
@@ -138,7 +167,7 @@ class PaymentService
 
             else {
                 if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
-                    throw new \InvalidArgumentException('Ja existe um pagamento em andamento para este agendamento.');
+                    throw new PaymentException;
                 }
 
                 $this->syncScheduleStatusAfterPayment($schedule, $existingPayment);
@@ -151,8 +180,8 @@ class PaymentService
         $cardId              = null;
 
         if ($paymentMethod === 'credit_card') {
-            if (! $clientPaymentMethodId && empty($options['card_id'])) {
-                throw new \InvalidArgumentException('Cartao de pagamento ou card_id e obrigatorio.');
+            if (! $clientPaymentMethodId && empty(data_get($options, 'card_id'))) {
+                throw new PaymentException;
             }
 
             if ($clientPaymentMethodId) {
@@ -163,14 +192,14 @@ class PaymentService
                     ->first();
 
                 if (! $clientPaymentMethod) {
-                    throw new \InvalidArgumentException('Cartao de pagamento nao encontrado ou inativo para este cliente.');
+                    throw new PaymentException;
                 }
             }
 
-            $cardId = $options['card_id'] ?? $clientPaymentMethod?->gateway_card_id ?? null;
+            $cardId = data_get($options, 'card_id', $clientPaymentMethod?->gateway_card_id);
 
             if (empty($cardId)) {
-                throw new \InvalidArgumentException('Cartao de pagamento invalido ou sem gateway_card_id do Pagar.me.');
+                throw new PaymentException;
             }
         }
 
@@ -191,17 +220,17 @@ class PaymentService
             'gateway_code'             => 'payment-'.(string) Str::uuid(),
             'payment_method'           => $paymentMethod,
             'status'                   => PaymentStatusEnum::PENDING,
-            'gross_amount'             => $amounts['gross_amount'],
+            'gross_amount'             => data_get($amounts, 'gross_amount'),
             'gateway_fee_amount'       => 0,
-            'platform_fee_amount'      => $amounts['platform_fee_amount'],
-            'net_amount'               => $amounts['gross_amount'],
+            'platform_fee_amount'      => data_get($amounts, 'platform_fee_amount'),
+            'net_amount'               => data_get($amounts, 'gross_amount'),
             'currency'                 => 'BRL',
             'installments'             => 1,
             'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
 
             'metadata' => [
-                'service_amount' => number_format($amounts['service_amount'], 2, '.', ''),
-                'platform_fee'   => number_format($amounts['platform_fee_amount'], 2, '.', ''),
+                'service_amount' => number_format(data_get($amounts, 'service_amount'), 2, '.', ''),
+                'platform_fee'   => number_format(data_get($amounts, 'platform_fee_amount'), 2, '.', ''),
             ],
         ]);
 
@@ -221,7 +250,7 @@ class PaymentService
             ],
         ]);
 
-        $schedule->ensureCustomerPhone($options['phone'] ?? null);
+        $schedule->ensureCustomerPhone(data_get($options, 'phone'));
 
         try {
             $orderResponse = $this->pagarmePaymentService->processPayment(
@@ -249,46 +278,47 @@ class PaymentService
 
         $this->syncScheduleStatusAfterPayment($schedule, $payment);
 
+        if ($payment->status === PaymentStatusEnum::FAILED) {
+            throw new PaymentFailedException;
+        }
+
         return $payment;
     }
 
-    public function payCart(
-        Cart $cart,
-        int $userId,
+    public function payServicePackage(
+        int    $servicePackageId,
         string $paymentMethod,
-        ?int $clientPaymentMethodId = null,
-        array $options = [],
+        ?int   $clientPaymentMethodId = null,
+        array  $options               = [],
     ): Payment {
-        if ($cart->client?->user_id !== $userId) {
-            throw new AuthorizationException;
-        }
+        $userId = (int) Auth::id();
 
         if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
-            throw new \InvalidArgumentException('Forma de pagamento invalida.');
+            throw new PaymentException;
         }
 
         $paymentData = DB::transaction(function () use (
-            $cart,
+            $servicePackageId,
             $userId,
             $paymentMethod,
             $clientPaymentMethodId,
             $options,
         ): array {
-            $cart = Cart::query()
+            $servicePackage = ServicePackage::query()
                 ->lockForUpdate()
                 ->with(['client', 'provider', 'items.schedule.client', 'items.schedule.provider', 'items.schedule.customSchedule.serviceType'])
-                ->findOrFail($cart->id);
+                ->findOrFail($servicePackageId);
 
-            if ($cart->client?->user_id !== $userId) {
+            if ($servicePackage->client?->user_id !== $userId) {
                 throw new AuthorizationException;
             }
 
-            $schedules = $cart->items->pluck('schedule')->filter()->values();
+            $schedules = $servicePackage->items->pluck('schedule')->filter()->values();
 
-            $this->validateCartForPayment($cart, $schedules);
+            $this->validateServicePackageForPayment($servicePackage, $schedules);
 
             $existingPayment = Payment::query()
-                ->where('cart_id', $cart->id)
+                ->where('service_package_id', $servicePackage->id)
                 ->whereIn('status', [
                     PaymentStatusEnum::PENDING->value,
                     PaymentStatusEnum::PROCESSING->value,
@@ -311,109 +341,113 @@ class PaymentService
                         ->update(['status' => PaymentSplitStatusEnum::FAILED]);
                 } elseif ($this->isStaleIncompleteGatewayPayment($existingPayment)) {
                     if ($existingPayment->payment_method !== $paymentMethod) {
-                        throw new \InvalidArgumentException('Ja existe um pagamento em andamento para este carrinho.');
+                        throw new PaymentException;
                     }
 
                     [, $cardId] = $this->resolveCard(
-                        clientId:              $cart->client_id,
+                        clientId:              $servicePackage->client_id,
                         paymentMethod:         $paymentMethod,
                         clientPaymentMethodId: $clientPaymentMethodId ?? $existingPayment->client_payment_method_id,
-                        cardId:                $options['card_id'] ?? null,
+                        cardId:                data_get($options, 'card_id'),
                     );
 
-                    $this->cartPaymentTotals($schedules, $paymentMethod);
+                    $this->servicePackagePaymentTotals($schedules, $paymentMethod);
 
                     return [
-                        'payment'  => $existingPayment,
+                        'payment'   => $existingPayment,
                         'schedules' => $schedules,
                         'cardId'    => $cardId,
                     ];
                 } else {
                     if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
-                        throw new \InvalidArgumentException('Ja existe um pagamento em andamento para este carrinho.');
+                        throw new PaymentException;
                     }
 
                     return ['existing' => $existingPayment];
                 }
             }
 
-            if ($cart->status !== CartStatusEnum::OPEN) {
-                throw new \InvalidArgumentException('Carrinho precisa estar aberto para ser pago.');
+            if ($servicePackage->status !== ServicePackageStatusEnum::OPEN) {
+                throw new PaymentException;
             }
 
             [$clientPaymentMethod, $cardId] = $this->resolveCard(
-                clientId:             $cart->client_id,
-                paymentMethod:        $paymentMethod,
+                clientId:              $servicePackage->client_id,
+                paymentMethod:         $paymentMethod,
                 clientPaymentMethodId: $clientPaymentMethodId,
-                cardId:               $options['card_id'] ?? null,
+                cardId:                data_get($options, 'card_id'),
             );
 
-            $totals = $this->cartPaymentTotals($schedules, $paymentMethod);
+            $totals = $this->servicePackagePaymentTotals($schedules, $paymentMethod);
 
             $payment = Payment::create([
                 'schedule_id'              => null,
-                'cart_id'                  => $cart->id,
-                'client_id'                => $cart->client_id,
-                'provider_id'              => $cart->provider_id,
+                'service_package_id'       => $servicePackage->id,
+                'client_id'                => $servicePackage->client_id,
+                'provider_id'              => $servicePackage->provider_id,
                 'client_payment_method_id' => $paymentMethod === 'credit_card' ? $clientPaymentMethod?->id : null,
                 'gateway_provider'         => 'pagarme',
                 'gateway_code'             => 'payment-'.(string) Str::uuid(),
                 'payment_method'           => $paymentMethod,
                 'status'                   => PaymentStatusEnum::PENDING,
-                'gross_amount'             => $totals['gross_amount'],
+                'gross_amount'             => data_get($totals, 'gross_amount'),
                 'gateway_fee_amount'       => 0,
-                'platform_fee_amount'      => $totals['platform_fee_amount'],
-                'net_amount'               => $totals['gross_amount'],
+                'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
+                'net_amount'               => data_get($totals, 'gross_amount'),
                 'currency'                 => 'BRL',
                 'installments'             => 1,
                 'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
-                'metadata'                 => [
-                    'cart_id'             => (string) $cart->id,
-                    'schedule_ids'        => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
-                    'service_amount'      => number_format($totals['service_amount'], 2, '.', ''),
-                    'platform_fee'        => number_format($totals['platform_fee_amount'], 2, '.', ''),
+
+                'metadata' => [
+                    'service_package_id' => (string) $servicePackage->id,
+                    'schedule_ids'       => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
+                    'service_amount'     => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
+                    'platform_fee'       => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
                 ],
             ]);
 
             PaymentSplit::create([
                 'payment_id'                        => $payment->id,
-                'provider_id'                       => $cart->provider_id,
+                'provider_id'                       => $servicePackage->provider_id,
                 'gateway_provider'                  => 'pagarme',
-                'gateway_transfer_target_reference' => $cart->provider->recipient_id,
+                'gateway_transfer_target_reference' => $servicePackage->provider->recipient_id,
                 'gateway_transfer_target_label'     => 'recipient',
                 'status'                            => PaymentSplitStatusEnum::PENDING,
-                'gross_amount'                      => $totals['service_amount'],
+                'gross_amount'                      => data_get($totals, 'service_amount'),
                 'gateway_fee_amount'                => 0,
-                'net_amount'                        => $totals['service_amount'],
-                'metadata'                          => [
-                    'cart_id'      => (string) $cart->id,
-                    'schedule_ids' => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
+                'net_amount'                        => data_get($totals, 'service_amount'),
+
+                'metadata' => [
+                    'service_package_id' => (string) $servicePackage->id,
+                    'schedule_ids'       => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
                 ],
             ]);
 
             return compact('payment', 'schedules', 'cardId');
         });
 
-        if (isset($paymentData['existing'])) {
-            $existingPayment = $paymentData['existing'];
+        if (data_get($paymentData, 'existing')) {
+            $existingPayment = data_get($paymentData, 'existing');
+
             $this->syncPaymentTargets($existingPayment);
 
-            return $existingPayment->fresh(['client.user', 'provider.user', 'cart.items.schedule']);
+            return $existingPayment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
         }
 
         /** @var Payment $payment */
-        $payment = $paymentData['payment'];
+        $payment = data_get($paymentData, 'payment');
+
         /** @var SupportCollection $schedules */
-        $schedules = $paymentData['schedules'];
+        $schedules = data_get($paymentData, 'schedules');
 
         try {
-            $schedules->first()->ensureCustomerPhone($options['phone'] ?? null);
+            $schedules->first()->ensureCustomerPhone(data_get($options, 'phone'));
 
-            $orderResponse = $this->pagarmePaymentService->processCartPayment(
+            $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
                 payment:       $payment,
                 schedules:     $schedules,
                 paymentMethod: $paymentMethod,
-                cardId:        $paymentData['cardId'],
+                cardId:        data_get($paymentData, 'cardId'),
                 options:       $options,
             );
         } catch (\Throwable $e) {
@@ -426,7 +460,11 @@ class PaymentService
 
         $this->syncPaymentTargets($payment);
 
-        return $payment->fresh(['client.user', 'provider.user', 'cart.items.schedule']);
+        if ($payment->status === PaymentStatusEnum::FAILED) {
+            throw new PaymentFailedException;
+        }
+
+        return $payment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
     }
 
     //
@@ -479,7 +517,7 @@ class PaymentService
             }
         }
 
-        return $this->payAcceptedSchedule(
+        return $this->processAcceptedSchedulePayment(
             schedule:      $schedule,
             paymentMethod: 'pix',
         );
@@ -525,7 +563,7 @@ class PaymentService
             $schedule->update(['status' => 'paid']);
         }
 
-        $this->syncCartsForSchedule($schedule);
+        $this->syncServicePackagesForSchedule($schedule);
     }
 
     public function syncPaymentTargets(Payment $payment): void
@@ -534,23 +572,23 @@ class PaymentService
             return;
         }
 
-        if ($payment->cart_id) {
+        if ($payment->service_package_id) {
             DB::transaction(function () use ($payment): void {
-                $cart = Cart::query()->lockForUpdate()->with('items')->find($payment->cart_id);
+                $servicePackage = ServicePackage::query()->lockForUpdate()->with('items')->find($payment->service_package_id);
 
-                if (! $cart) {
+                if (! $servicePackage) {
                     return;
                 }
 
-                $scheduleIds = $cart->items->pluck('schedule_id')->filter()->unique();
+                $scheduleIds = $servicePackage->items->pluck('schedule_id')->filter()->unique();
 
                 Schedule::query()
                     ->whereIn('id', $scheduleIds)
                     ->where('status', 'accepted')
                     ->update(['status' => 'paid']);
 
-                if ($cart->status !== CartStatusEnum::PAID) {
-                    $cart->update(['status' => CartStatusEnum::PAID->value]);
+                if ($servicePackage->status !== ServicePackageStatusEnum::PAID) {
+                    $servicePackage->update(['status' => ServicePackageStatusEnum::PAID->value]);
                 }
             });
 
@@ -564,20 +602,20 @@ class PaymentService
         }
     }
 
-    private function syncCartsForSchedule(Schedule $schedule): void
+    private function syncServicePackagesForSchedule(Schedule $schedule): void
     {
-        Cart::query()
+        ServicePackage::query()
             ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
             ->with('items')
             ->get()
-            ->each(fn (Cart $cart) => $this->syncCartStatusAfterPayments($cart));
+            ->each(fn (ServicePackage $servicePackage) => $this->syncServicePackageStatusAfterPayments($servicePackage));
     }
 
-    private function syncCartStatusAfterPayments(Cart $cart): void
+    private function syncServicePackageStatusAfterPayments(ServicePackage $servicePackage): void
     {
-        $cart->loadMissing('items');
+        $servicePackage->loadMissing('items');
 
-        $scheduleIds = $cart->items
+        $scheduleIds = $servicePackage->items
             ->pluck('schedule_id')
             ->filter()
             ->unique()
@@ -596,45 +634,45 @@ class PaymentService
             return;
         }
 
-        if ($cart->status !== CartStatusEnum::PAID) {
-            $cart->update(['status' => CartStatusEnum::PAID->value]);
+        if ($servicePackage->status !== ServicePackageStatusEnum::PAID) {
+            $servicePackage->update(['status' => ServicePackageStatusEnum::PAID->value]);
         }
     }
 
-    private function validateCartForPayment(Cart $cart, SupportCollection $schedules): void
+    private function validateServicePackageForPayment(ServicePackage $servicePackage, SupportCollection $schedules): void
     {
-        if (! in_array($cart->status, [CartStatusEnum::OPEN, CartStatusEnum::PAID], true)) {
-            throw new \InvalidArgumentException('Carrinho precisa estar aberto para ser pago.');
+        if (! in_array($servicePackage->status, [ServicePackageStatusEnum::OPEN, ServicePackageStatusEnum::PAID], true)) {
+            throw new PaymentException;
         }
 
         if ($schedules->isEmpty()) {
-            throw new \InvalidArgumentException('Carrinho precisa ter ao menos um agendamento.');
+            throw new PaymentException;
         }
 
         $providerIds = $schedules->pluck('provider_id')->filter()->unique()->values();
         $clientIds   = $schedules->pluck('client_id')->unique()->values();
 
-        if (! $cart->provider_id || $providerIds->count() !== 1 || (int) $providerIds->first() !== $cart->provider_id) {
-            throw new \InvalidArgumentException('Todos os agendamentos do carrinho precisam ser do mesmo prestador.');
+        if (! $servicePackage->provider_id || $providerIds->count() !== 1 || (int) $providerIds->first() !== $servicePackage->provider_id) {
+            throw new PaymentException;
         }
 
-        if ($clientIds->count() !== 1 || (int) $clientIds->first() !== $cart->client_id) {
-            throw new \InvalidArgumentException('Todos os agendamentos do carrinho precisam ser do mesmo cliente.');
+        if ($clientIds->count() !== 1 || (int) $clientIds->first() !== $servicePackage->client_id) {
+            throw new PaymentException;
         }
 
-        if (empty($cart->provider?->recipient_id)) {
-            throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para receber split.');
+        if (empty($servicePackage->provider?->recipient_id)) {
+            throw new PaymentException;
         }
 
         foreach ($schedules as $schedule) {
-            $expectedStatus = $cart->status === CartStatusEnum::PAID ? 'paid' : 'accepted';
+            $expectedStatus = $servicePackage->status === ServicePackageStatusEnum::PAID ? 'paid' : 'accepted';
 
             if ($schedule->status !== $expectedStatus) {
-                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa estar aceito para o carrinho ser pago.");
+                throw new PaymentException;
             }
 
             if ((float) $schedule->total_amount <= 0) {
-                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa ter valor maior que zero para gerar pagamento.");
+                throw new PaymentException;
             }
         }
     }
@@ -650,7 +688,7 @@ class PaymentService
         }
 
         if (! $clientPaymentMethodId && empty($cardId)) {
-            throw new \InvalidArgumentException('Cartao de pagamento ou card_id e obrigatorio.');
+            throw new PaymentException;
         }
 
         $clientPaymentMethod = $clientPaymentMethodId
@@ -662,19 +700,19 @@ class PaymentService
             : null;
 
         if ($clientPaymentMethodId && ! $clientPaymentMethod) {
-            throw new \InvalidArgumentException('Cartao de pagamento nao encontrado ou inativo para este cliente.');
+            throw new PaymentException;
         }
 
         $cardId = $cardId ?: $clientPaymentMethod?->gateway_card_id;
 
         if (empty($cardId)) {
-            throw new \InvalidArgumentException('Cartao de pagamento invalido ou sem gateway_card_id do Pagar.me.');
+            throw new PaymentException;
         }
 
         return [$clientPaymentMethod, $cardId];
     }
 
-    private function cartPaymentTotals(SupportCollection $schedules, string $paymentMethod): array
+    private function servicePackagePaymentTotals(SupportCollection $schedules, string $paymentMethod): array
     {
         return $schedules->reduce(function (array $totals, Schedule $schedule) use ($paymentMethod): array {
             $amounts = $this->pagarmePaymentService->calculatePaymentAmounts(
@@ -683,12 +721,12 @@ class PaymentService
                 schedule:      $schedule,
             );
 
-            $schedule->setAttribute('payment_gross_amount', $amounts['gross_amount']);
+            $schedule->setAttribute('payment_gross_amount', data_get($amounts, 'gross_amount'));
 
             return [
-                'service_amount'      => round($totals['service_amount'] + $amounts['service_amount'], 2),
-                'platform_fee_amount' => round($totals['platform_fee_amount'] + $amounts['platform_fee_amount'], 2),
-                'gross_amount'        => round($totals['gross_amount'] + $amounts['gross_amount'], 2),
+                'service_amount'      => round(data_get($totals, 'service_amount') + data_get($amounts, 'service_amount'), 2),
+                'platform_fee_amount' => round(data_get($totals, 'platform_fee_amount') + data_get($amounts, 'platform_fee_amount'), 2),
+                'gross_amount'        => round(data_get($totals, 'gross_amount') + data_get($amounts, 'gross_amount'), 2),
             ];
         }, [
             'service_amount'      => 0.0,
@@ -712,14 +750,14 @@ class PaymentService
 
     private function ensureScheduleCanBePaidIndividually(Schedule $schedule): void
     {
-        $belongsToMultiItemCart = Cart::query()
-            ->where('status', CartStatusEnum::OPEN->value)
+        $belongsToMultiItemServicePackage = ServicePackage::query()
+            ->where('status', ServicePackageStatusEnum::OPEN->value)
             ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
             ->whereHas('items', null, '>', 1)
             ->exists();
 
-        if ($belongsToMultiItemCart) {
-            throw new \InvalidArgumentException('Agendamento pertencente a carrinho com múltiplos itens deve ser pago pelo checkout do carrinho.');
+        if ($belongsToMultiItemServicePackage) {
+            throw new PaymentException;
         }
     }
 }

+ 2 - 2
app/Services/ProviderClientBlockService.php

@@ -19,8 +19,8 @@ class ProviderClientBlockService
     {
         // Validar duplicidade (ignorando soft-deleted)
 
-        $existing = ProviderClientBlock::where('provider_id', $data['provider_id'])
-            ->where('client_id', $data['client_id'])
+        $existing = ProviderClientBlock::where('provider_id', data_get($data, 'provider_id'))
+            ->where('client_id', data_get($data, 'client_id'))
             ->first();
 
         if ($existing) {

+ 65 - 65
app/Services/ProviderService.php

@@ -49,7 +49,7 @@ class ProviderService
         return DB::transaction(function () use ($data) {
             $provider = Provider::create($data);
 
-            if (! empty($data['recipient_name']) && ! empty($data['recipient_default_bank_account'])) {
+            if (! empty(data_get($data, 'recipient_name')) && ! empty(data_get($data, 'recipient_default_bank_account'))) {
                 $this->pagarmeRecipientService->createRecipientForProvider($provider, $data);
             }
 
@@ -67,13 +67,13 @@ class ProviderService
 
         $wasAccepted = $model->approval_status === ApprovalStatusEnum::ACCEPTED;
 
-        if (isset($data['avatar']) && $data['avatar'] instanceof UploadedFile) {
+        if (data_get($data, 'avatar') !== null && data_get($data, 'avatar') instanceof UploadedFile) {
             $media = $this->mediaService->replaceFile(
-                newFile: $data['avatar'],
-                folder: "provider/avatar/{$model->id}",
-                source: 'provider',
+                newFile:  data_get($data, 'avatar'),
+                folder:   "provider/avatar/{$model->id}",
+                source:   'provider',
                 sourceId: $model->id,
-                old: $model->profileMedia,
+                old:      $model->profileMedia,
             );
 
             $data['profile_media_id'] = $media->id;
@@ -119,11 +119,11 @@ class ProviderService
             'id'                              => $provider->id,
             'provider_id'                     => $provider->id,
             'account_type'                    => 'bank_account',
-            'pix_key'                         => $bankAccount['pix_key']             ?? null,
-            'bank_account_type'               => $bankAccount['type']                ?? null,
-            'agency'                          => $bankAccount['branch_number']       ?? null,
-            'account'                         => $bankAccount['account_number']      ?? null,
-            'digit'                           => $bankAccount['account_check_digit'] ?? null,
+            'pix_key'                         => data_get($bankAccount, 'pix_key'),
+            'bank_account_type'               => data_get($bankAccount, 'type'),
+            'agency'                          => data_get($bankAccount, 'branch_number'),
+            'account'                         => data_get($bankAccount, 'account_number'),
+            'digit'                           => data_get($bankAccount, 'account_check_digit'),
             'recipient_default_bank_account'  => $bankAccount,
         ]];
     }
@@ -144,9 +144,9 @@ class ProviderService
         try {
             DB::beginTransaction();
 
-            $email = $data['email'] ?? null;
-            $phone = $data['phone'] ?? null;
-            $code  = $data['code']  ?? null;
+            $email = data_get($data, 'email');
+            $phone = data_get($data, 'phone');
+            $code  = data_get($data, 'code');
 
             $user = User::query()
                 ->where('type', UserTypeEnum::PROVIDER->value)
@@ -167,7 +167,7 @@ class ProviderService
                 throw new \Exception(__('messages.user_not_found_or_code_not_validated'));
             }
 
-            $user->name = $data['name'];
+            $user->name = data_get($data, 'name');
 
             if (empty($user->email) && ! empty($email)) {
                 $user->email = $email;
@@ -189,13 +189,13 @@ class ProviderService
                 $provider->restore();
             }
 
-            $provider->rg              = $data['rg'] ?? null;
-            $provider->document        = $this->sanitizeDigits($data['document'] ?? null);
-            $provider->birth_date      = $data['birth_date']     ?? null;
-            $provider->daily_price_8h  = $data['daily_price_8h'] ?? null;
-            $provider->daily_price_6h  = $data['daily_price_6h'] ?? null;
-            $provider->daily_price_4h  = $data['daily_price_4h'] ?? null;
-            $provider->daily_price_2h  = $data['daily_price_2h'] ?? null;
+            $provider->rg              = data_get($data, 'rg');
+            $provider->document        = $this->sanitizeDigits(data_get($data, 'document'));
+            $provider->birth_date      = data_get($data, 'birth_date');
+            $provider->daily_price_8h  = data_get($data, 'daily_price_8h');
+            $provider->daily_price_6h  = data_get($data, 'daily_price_6h');
+            $provider->daily_price_4h  = data_get($data, 'daily_price_4h');
+            $provider->daily_price_2h  = data_get($data, 'daily_price_2h');
             $provider->approval_status = ApprovalStatusEnum::PENDING->value;
 
             $provider->save();
@@ -205,44 +205,44 @@ class ProviderService
             $provider->load('profileMedia', 'documentFrontMedia', 'documentBackMedia');
 
             $selfie = $this->mediaService->replaceFile(
-                newFile: $data['selfie'],
-                folder: "provider/avatar/{$provider->id}",
-                source: 'provider',
+                newFile:  data_get($data, 'selfie'),
+                folder:   "provider/avatar/{$provider->id}",
+                source:   'provider',
                 sourceId: $provider->id,
-                old: $provider->profileMedia,
+                old:      $provider->profileMedia,
             );
 
             $provider->profile_media_id = $selfie->id;
 
             $front = $this->mediaService->replaceFile(
-                newFile: $data['document_front'],
-                folder: "provider/documentos/{$provider->id}",
-                source: 'provider_document',
+                newFile:  data_get($data, 'document_front'),
+                folder:   "provider/documentos/{$provider->id}",
+                source:   'provider_document',
                 sourceId: $provider->id,
-                old: $provider->documentFrontMedia,
-                filename: 'frente.'.$data['document_front']->getClientOriginalExtension(),
+                old:      $provider->documentFrontMedia,
+                filename: 'frente.'.data_get($data, 'document_front')->getClientOriginalExtension(),
             );
 
             $provider->document_front_media_id = $front->id;
 
             $back = $this->mediaService->replaceFile(
-                newFile: $data['document_back'],
-                folder: "provider/documentos/{$provider->id}",
-                source: 'provider_document',
+                newFile:  data_get($data, 'document_back'),
+                folder:   "provider/documentos/{$provider->id}",
+                source:   'provider_document',
                 sourceId: $provider->id,
-                old: $provider->documentBackMedia,
-                filename: 'verso.'.$data['document_back']->getClientOriginalExtension(),
+                old:      $provider->documentBackMedia,
+                filename: 'verso.'.data_get($data, 'document_back')->getClientOriginalExtension(),
             );
 
             $provider->document_back_media_id = $back->id;
 
             $provider->save();
 
-            $bankAccount = $data['recipient_default_bank_account'] ?? [];
+            $bankAccount = data_get($data, 'recipient_default_bank_account', []);
 
-            $hasBankData = ! empty($bankAccount['bank']) && ! empty($bankAccount['account_number']);
+            $hasBankData = ! empty(data_get($bankAccount, 'bank')) && ! empty(data_get($bankAccount, 'account_number'));
 
-            if (! empty($data['recipient_name']) && $hasBankData && empty($provider->recipient_id)) {
+            if (! empty(data_get($data, 'recipient_name')) && $hasBankData && empty($provider->recipient_id)) {
                 $this->pagarmeRecipientService->createRecipientForProvider($provider, $data);
             }
 
@@ -277,7 +277,7 @@ class ProviderService
         } catch (\Exception $e) {
             DB::rollBack();
 
-            Log::error('Error registering provider: '.$e->getMessage(), [
+            Log::error('Erro ao cadastrar prestador: '.$e->getMessage(), [
                 'data' => $data,
             ]);
 
@@ -293,9 +293,9 @@ class ProviderService
             return null;
         }
 
-        $bankAccountData['holder_name']     = $bankAccountData['holder_name']     ?? $provider->user?->name;
-        $bankAccountData['holder_document'] = $bankAccountData['holder_document'] ?? $provider->document;
-        $bankAccountData['holder_type']     = $bankAccountData['holder_type']     ?? 'individual';
+        $bankAccountData['holder_name']     = data_get($bankAccountData, 'holder_name', $provider->user?->name);
+        $bankAccountData['holder_document'] = data_get($bankAccountData, 'holder_document', $provider->document);
+        $bankAccountData['holder_type']     = data_get($bankAccountData, 'holder_type', 'individual');
 
         if (empty($provider->recipient_id)) {
             $this->pagarmeRecipientService->createRecipientForProvider(
@@ -370,15 +370,15 @@ class ProviderService
         $state = null;
         $city  = null;
 
-        if (! empty($data['state'])) {
+        if (! empty(data_get($data, 'state'))) {
             $state = State::query()
-                ->whereRaw('LOWER(code) = ?', [mb_strtolower($data['state'])])
+                ->whereRaw('LOWER(code) = ?', [mb_strtolower(data_get($data, 'state'))])
                 ->first();
         }
 
-        if (! empty($data['city'])) {
+        if (! empty(data_get($data, 'city'))) {
             $cityQuery = City::query()
-                ->whereRaw('LOWER(name) = ?', [mb_strtolower($data['city'])]);
+                ->whereRaw('LOWER(name) = ?', [mb_strtolower(data_get($data, 'city'))]);
 
             if ($state) {
                 $cityQuery->where('state_id', $state->id);
@@ -391,31 +391,31 @@ class ProviderService
 
         $address->source    = 'provider';
         $address->source_id = $providerId;
-        $address->zip_code  = $this->sanitizeDigits($data['zip_code'] ?? null);
+        $address->zip_code  = $this->sanitizeDigits(data_get($data, 'zip_code'));
 
-        $address->address  = $data['address']  ?? null;
-        $address->number   = $data['number']   ?? null;
-        $address->district = $data['district'] ?? null;
+        $address->address  = data_get($data, 'address');
+        $address->number   = data_get($data, 'number');
+        $address->district = data_get($data, 'district');
 
-        $address->has_complement = (bool) ($data['has_complement'] ?? false);
+        $address->has_complement = (bool) data_get($data, 'has_complement', false);
 
-        $address->complement   = $data['complement']   ?? null;
-        $address->nickname     = $data['nickname']     ?? null;
-        $address->instructions = $data['instructions'] ?? null;
-        $address->address_type = $data['address_type'] ?? 'home';
+        $address->complement   = data_get($data, 'complement');
+        $address->nickname     = data_get($data, 'nickname');
+        $address->instructions = data_get($data, 'instructions');
+        $address->address_type = data_get($data, 'address_type', 'home');
 
         $address->state_id = $state?->id;
         $address->city_id  = $city?->id;
 
-        $address->latitude  = $data['latitude']  ?? null;
-        $address->longitude = $data['longitude'] ?? null;
+        $address->latitude  = data_get($data, 'latitude');
+        $address->longitude = data_get($data, 'longitude');
 
         $address->save();
     }
 
     private function createProviderServicesTypes(int $providerId, array $data): void
     {
-        $serviceTypeIds = $data['services_types_ids'] ?? $data['service_types_ids'] ?? [];
+        $serviceTypeIds = data_get($data, 'services_types_ids', data_get($data, 'service_types_ids', []));
 
         $uniqueIds = array_values(array_unique(array_map('intval', $serviceTypeIds)));
 
@@ -429,14 +429,14 @@ class ProviderService
 
     private function createProviderWorkingDays(int $providerId, array $data): void
     {
-        $workingDays = $data['working_days'] ?? [];
+        $workingDays = data_get($data, 'working_days', []);
 
         $seen = [];
 
         foreach ($workingDays as $workingDay) {
-            $day = (int) ($workingDay['day'] ?? -1);
+            $day = (int) data_get($workingDay, 'day', -1);
 
-            $period = $workingDay['period'] ?? null;
+            $period = data_get($workingDay, 'period');
 
             if ($day < 0 || $day > 6 || ! in_array($period, ['morning', 'afternoon'], true)) {
                 continue;
@@ -444,7 +444,7 @@ class ProviderService
 
             $uniqueKey = $day.'-'.$period;
 
-            if (isset($seen[$uniqueKey])) {
+            if (data_get($seen, $uniqueKey) !== null) {
                 continue;
             }
 
@@ -481,7 +481,7 @@ class ProviderService
                     locale: $provider->user->language?->value,
                 );
             } catch (\Throwable $exception) {
-                Log::error('Provider approval email failed', [
+                Log::error('Falha ao enviar e-mail de aprovação do prestador', [
                     'provider_id' => $provider->id,
                     'user_id'     => $provider->user?->id,
                     'email'       => $provider->user?->email,
@@ -492,7 +492,7 @@ class ProviderService
             return;
         }
 
-        Log::warning('Provider approval email skipped: provider user has no email', [
+        Log::warning('E-mail de aprovação do prestador ignorado: usuário não possui e-mail', [
             'provider_id' => $provider->id,
             'user_id'     => $provider->user?->id,
         ]);

+ 13 - 13
app/Services/ProviderWithdrawalService.php

@@ -21,7 +21,7 @@ class ProviderWithdrawalService
 
     public function handleTransferWebhook(array $data): void
     {
-        $transferId = $data['id'] ?? null;
+        $transferId = data_get($data, 'id');
 
         if (empty($transferId)) {
             return;
@@ -32,14 +32,14 @@ class ProviderWithdrawalService
             ->first();
 
         if (! $withdrawal) {
-            Log::channel('pagarme')->warning('ProviderWithdrawal not found for transfer webhook', [
+            Log::channel('pagarme')->warning('Saque do prestador não encontrado para o webhook de transferência', [
                 'transfer_id' => $transferId,
             ]);
 
             return;
         }
 
-        $status = $data['status'] ?? null;
+        $status = data_get($data, 'status');
 
         if ($status === ProviderWithdrawalStatusEnum::TRANSFERRED->value) {
             $withdrawal->forceFill([
@@ -53,7 +53,7 @@ class ProviderWithdrawalService
             $withdrawal->forceFill([
                 'status'        => ProviderWithdrawalStatusEnum::fromString($status),
                 'failed_at'     => $status === ProviderWithdrawalStatusEnum::FAILED->value ? now() : null,
-                'bank_response' => $status === ProviderWithdrawalStatusEnum::FAILED->value ? ($data['bank_response'] ?? null) : null,
+                'bank_response' => $status === ProviderWithdrawalStatusEnum::FAILED->value ? (data_get($data, 'bank_response')) : null,
             ])->save();
         } elseif ($status === ProviderWithdrawalStatusEnum::PROCESSING->value) {
             $withdrawal->forceFill([
@@ -97,7 +97,7 @@ class ProviderWithdrawalService
     {
         return PaymentSplit::query()
             ->where('provider_id', $provider->id)
-            ->with(['payment.schedule.client.user', 'payment.cart.items.schedule.client.user'])
+            ->with(['payment.schedule.client.user', 'payment.servicePackage.items.schedule.client.user'])
             ->orderBy('created_at', 'desc')
             ->get();
     }
@@ -107,7 +107,7 @@ class ProviderWithdrawalService
     public function requestWithdrawal(Provider $provider): ProviderWithdrawal
     {
         if (empty($provider->recipient_id)) {
-            throw new \RuntimeException('Prestador nao possui recipient_id no Pagar.me.');
+            throw new \RuntimeException(__('messages.provider_requires_pagarme_recipient'));
         }
 
         return DB::transaction(function () use ($provider) {
@@ -125,13 +125,13 @@ class ProviderWithdrawalService
                 ->exists();
 
             if ($pending) {
-                throw new \RuntimeException('Voce ja possui um saque em andamento.');
+                throw new \RuntimeException(__('messages.withdrawal_already_in_progress'));
             }
 
             $available = $this->getAvailableBalance($provider);
 
             if ($available <= 0) {
-                throw new \RuntimeException('Saldo disponivel insuficiente para saque.');
+                throw new \RuntimeException(__('messages.insufficient_withdrawal_balance'));
             }
 
             $paymentTransferFeeAmount = $this->paymentTransferFeeAmount();
@@ -139,7 +139,7 @@ class ProviderWithdrawalService
             $netAmount = max(0, round($available - $paymentTransferFeeAmount, 2));
 
             if ($netAmount <= 0) {
-                throw new \RuntimeException('Saldo disponivel insuficiente para cobrir a taxa de transferencia.');
+                throw new \RuntimeException(__('messages.insufficient_balance_for_transfer_fee'));
             }
 
             $amountInCents = (int) round($netAmount * 100);
@@ -275,9 +275,9 @@ class ProviderWithdrawalService
             ->where(function ($query) {
                 $query->whereHas('schedule', fn ($query) => $this->availableScheduleQuery($query))
                     ->orWhere(function ($query) {
-                        $query->whereNotNull('cart_id')
-                            ->whereHas('cart.items')
-                            ->whereDoesntHave('cart.items.schedule', fn ($query) => $this->unavailableScheduleQuery($query));
+                        $query->whereNotNull('service_package_id')
+                            ->whereHas('servicePackage.items')
+                            ->whereDoesntHave('servicePackage.items.schedule', fn ($query) => $this->unavailableScheduleQuery($query));
                     });
             });
     }
@@ -288,7 +288,7 @@ class ProviderWithdrawalService
             ->where('status', PaymentStatusEnum::PAID)
             ->where(function ($query) {
                 $query->whereHas('schedule', fn ($query) => $this->pendingScheduleQuery($query))
-                    ->orWhereHas('cart.items.schedule', fn ($query) => $this->pendingScheduleQuery($query));
+                    ->orWhereHas('servicePackage.items.schedule', fn ($query) => $this->pendingScheduleQuery($query));
             });
     }
 

+ 2 - 2
app/Services/ReviewImprovementService.php

@@ -17,8 +17,8 @@ class ReviewImprovementService
 
     public function create(array $data): ReviewImprovement
     {
-        $existing = ReviewImprovement::where('review_id', $data['review_id'])
-            ->where('improvement_type_id', $data['improvement_type_id'])
+        $existing = ReviewImprovement::where('review_id', data_get($data, 'review_id'))
+            ->where('improvement_type_id', data_get($data, 'improvement_type_id'))
             ->first();
 
         if ($existing) {

+ 22 - 22
app/Services/ReviewService.php

@@ -37,32 +37,32 @@ class ReviewService
             $review->save();
             $review->refresh();
 
-            if (isset($data['origin']) && isset($data['origin_id'])) {
-                switch ($data['origin']) {
+            if (data_get($data, 'origin') !== null && data_get($data, 'origin_id') !== null) {
+                switch (data_get($data, 'origin')) {
                     case 'client':
-                        $schedule = Schedule::find($data['schedule_id']);
+                        $schedule = Schedule::find(data_get($data, 'schedule_id'));
                         $provider = Provider::find($schedule->provider_id);
 
-                        $provider->updateAverageRating((float) $data['stars']);
+                        $provider->updateAverageRating((float) data_get($data, 'stars'));
 
                         break;
 
                     case 'provider':
-                        $schedule = Schedule::find($data['schedule_id']);
+                        $schedule = Schedule::find(data_get($data, 'schedule_id'));
                         $client   = Client::find($schedule->client_id);
 
-                        $client->updateAverageRating((float) $data['stars']);
+                        $client->updateAverageRating((float) data_get($data, 'stars'));
 
                         break;
                 }
             }
 
-            if (isset($data['improvements_ids'])) {
-                $review->improvements()->sync($data['improvements_ids']);
+            if (data_get($data, 'improvements_ids') !== null) {
+                $review->improvements()->sync(data_get($data, 'improvements_ids'));
             }
 
-            if (! empty($data['block_provider'])) {
-                $schedule = Schedule::find($data['schedule_id']);
+            if (! empty(data_get($data, 'block_provider'))) {
+                $schedule = Schedule::find(data_get($data, 'schedule_id'));
 
                 $alreadyBlocked = ClientProviderBlock::where('client_id', $schedule->client_id)
                     ->where('provider_id', $schedule->provider_id)
@@ -77,8 +77,8 @@ class ReviewService
                 }
             }
 
-            if (! empty($data['block_client'])) {
-                $schedule = Schedule::find($data['schedule_id']);
+            if (! empty(data_get($data, 'block_client'))) {
+                $schedule = Schedule::find(data_get($data, 'schedule_id'));
 
                 $alreadyBlocked = ProviderClientBlock::where('provider_id', $schedule->provider_id)
                     ->where('client_id', $schedule->client_id)
@@ -93,8 +93,8 @@ class ReviewService
                 }
             }
 
-            if (! empty($data['favorite_provider'])) {
-                $schedule = Schedule::find($data['schedule_id']);
+            if (! empty(data_get($data, 'favorite_provider'))) {
+                $schedule = Schedule::find(data_get($data, 'schedule_id'));
 
                 $alreadyFavorited = ClientFavoriteProvider::where('client_id', $schedule->client_id)
                     ->where('provider_id', $schedule->provider_id)
@@ -109,16 +109,16 @@ class ReviewService
                 }
             }
 
-            if (! empty($data['photos'])) {
-                $schedule = Schedule::find($data['schedule_id']);
+            if (! empty(data_get($data, 'photos'))) {
+                $schedule = Schedule::find(data_get($data, 'schedule_id'));
 
-                $origin = $data['origin'];
+                $origin = data_get($data, 'origin');
 
-                foreach ($data['photos'] as $photo) {
+                foreach (data_get($data, 'photos') as $photo) {
                     $media = $this->mediaService->createFromFile(
-                        file: $photo,
-                        folder: "review/{$schedule->id}/{$origin}",
-                        source: 'review',
+                        file:     $photo,
+                        folder:   "review/{$schedule->id}/{$origin}",
+                        source:   'review',
                         sourceId: $review->id,
                     );
 
@@ -136,7 +136,7 @@ class ReviewService
         } catch (Exception $e) {
             DB::rollBack();
 
-            Log::error('Error creating review: ' . $e->getMessage());
+            Log::error('Erro ao criar avaliação: '.$e->getMessage());
 
             throw $e;
         }

+ 0 - 57
app/Services/ScheduleCartService.php

@@ -1,57 +0,0 @@
-<?php
-
-namespace App\Services;
-
-use Illuminate\Support\Arr;
-
-class ScheduleCartService
-{
-    public function __construct(
-        private readonly ScheduleService $scheduleService,
-    ) {}
-
-    public function create(array $data): array
-    {
-        return $this->scheduleService->createSingleOrMultiple(
-            baseData:  $this->baseScheduleData($data),
-            schedules: $this->scheduleItems($data),
-        );
-    }
-
-    //
-
-    private function baseScheduleData(array $data): array
-    {
-        return Arr::except($data, [
-            'schedules',
-            'dates',
-            'date',
-            'period_type',
-            'start_time',
-            'end_time',
-            'total_amount',
-            'offers_meal',
-        ]);
-    }
-
-    private function scheduleItems(array $data): array
-    {
-        if (! empty($data['schedules'])) {
-            return $data['schedules'];
-        }
-
-        if (! empty($data['dates'])) {
-            return array_map(
-                fn (string $date) => array_merge(
-                    Arr::only($data, ['period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
-                    ['date' => $date],
-                ),
-                $data['dates'],
-            );
-        }
-
-        return [
-            Arr::only($data, ['date', 'period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
-        ];
-    }
-}

+ 56 - 42
app/Services/ScheduleService.php

@@ -2,6 +2,7 @@
 
 namespace App\Services;
 
+use App\Exceptions\ScheduleStatusTransitionException;
 use App\Enums\UserTypeEnum;
 use App\Enums\NotificationTypeEnum;
 use App\Jobs\StartScheduleJob;
@@ -32,6 +33,11 @@ class ScheduleService
         return Schedule::with(['client.user', 'provider.user', 'address'])->findOrFail($id);
     }
 
+    public function create(array $data): Schedule
+    {
+        return data_get($this->createSingleOrMultiple([], [$data]), 0);
+    }
+
     public function createSingleOrMultiple(array $baseData, array $schedules)
     {
         try {
@@ -42,12 +48,12 @@ class ScheduleService
             foreach ($schedules as $schedule) {
                 $datasMerged = array_merge($baseData, $schedule);
 
-                if (($datasMerged['schedule_type'] ?? 'default') === 'default') {
-                    $provider = Provider::findOrFail($datasMerged['provider_id']);
+                if (data_get($datasMerged, 'schedule_type', 'default') === 'default') {
+                    $provider = Provider::findOrFail(data_get($datasMerged, 'provider_id'));
 
                     $datasMerged['total_amount'] = $this->calculateAmount(
                         $provider,
-                        (string) $datasMerged['period_type'],
+                        (string) data_get($datasMerged, 'period_type'),
                     );
                 }
 
@@ -65,8 +71,8 @@ class ScheduleService
                     $notificationService = app(NotificationService::class);
 
                     $notificationService->create([
-                        'title'       => 'Nova solicitação de diária!',
-                        'description' => 'Você recebeu uma nova solicitação de diária.',
+                        'title'       => __('notifications.new_schedule_request_title'),
+                        'description' => __('notifications.new_schedule_request_description'),
                         'origin'      => 'schedule',
                         'origin_id'   => $newSchedule->id,
                         'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_NEW_SOLICITATION->value,
@@ -81,7 +87,7 @@ class ScheduleService
         } catch (\Exception $e) {
             DB::rollBack();
 
-            throw new \Exception(__($e->getMessage()));
+            throw $e;
         }
 
         return $createdSchedules;
@@ -93,16 +99,16 @@ class ScheduleService
 
         $schedule = Schedule::with(['provider.user', 'client.user', 'address'])->findOrFail($id);
 
-        if (isset($data['provider_id']) || isset($data['period_type'])) {
-            $providerId = $data['provider_id'] ?? $schedule->provider_id;
-            $periodType = $data['period_type'] ?? $schedule->period_type;
+        if (data_get($data, 'provider_id') !== null || data_get($data, 'period_type') !== null) {
+            $providerId = data_get($data, 'provider_id', $schedule->provider_id);
+            $periodType = data_get($data, 'period_type', $schedule->period_type);
 
             $provider = Provider::findOrFail($providerId);
 
             $data['total_amount'] = $this->calculateAmount($provider, $periodType);
         }
 
-        if (isset($data['date']) || isset($data['start_time']) || isset($data['provider_id'])) {
+        if (data_get($data, 'date') !== null || data_get($data, 'start_time') !== null || data_get($data, 'provider_id') !== null) {
             $validationData = array_merge($schedule->toArray(), $data);
 
             $this->validateProviderAvailability($validationData, $id);
@@ -143,12 +149,12 @@ class ScheduleService
 
             $currentStatus = $schedule->status;
 
-            if (! isset($allowedTransitions[$currentStatus])) {
-                throw new \Exception('Status atual inválido.');
+            if (data_get($allowedTransitions, $currentStatus) === null) {
+                throw new ScheduleStatusTransitionException;
             }
 
-            if (! in_array($status, $allowedTransitions[$currentStatus])) {
-                throw new \Exception("Transição de status não permitida: {$currentStatus} → {$status}");
+            if (! in_array($status, data_get($allowedTransitions, $currentStatus))) {
+                throw new ScheduleStatusTransitionException;
             }
 
             $schedule->update(['status' => $status]);
@@ -166,8 +172,8 @@ class ScheduleService
                     switch (Auth::user()->type) {
                         case UserTypeEnum::PROVIDER:
                             $notificationService->create([
-                                'title'       => 'Agendamento aceito!',
-                                'description' => $schedule->provider->user->name . ' aceitou sua solicitação de diária.',
+                                'title'       => __('notifications.schedule_accepted_title'),
+                                'description' => __('notifications.provider_accepted_schedule_description', ['provider' => $schedule->provider->user->name]),
                                 'origin'      => 'schedule',
                                 'origin_id'   => $schedule->id,
                                 'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_ACCEPTED->value,
@@ -179,8 +185,8 @@ class ScheduleService
                         case UserTypeEnum::CLIENT:
                             if ($schedule->provider_id) {
                                 $notificationService->create([
-                                    'title'       => 'Proposta aceita!',
-                                    'description' => 'O cliente aceitou sua proposta de diária.',
+                                    'title'       => __('notifications.proposal_accepted_title'),
+                                    'description' => __('notifications.proposal_accepted_description'),
                                     'origin'      => 'schedule',
                                     'origin_id'   => $schedule->id,
                                     'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_PROPOSAL_ACCEPTED->value,
@@ -202,8 +208,8 @@ class ScheduleService
                     switch (Auth::user()->type) {
                         case UserTypeEnum::CLIENT:
                             $notificationService->create([
-                                'title'       => 'Agendamento cancelado!',
-                                'description' => 'O cliente cancelou o agendamento.',
+                                'title'       => __('notifications.schedule_cancelled_title'),
+                                'description' => __('notifications.client_cancelled_schedule_description'),
                                 'origin'      => 'schedule',
                                 'origin_id'   => $schedule->id,
                                 'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_CLIENT_CANCELLED->value,
@@ -214,8 +220,8 @@ class ScheduleService
 
                         case UserTypeEnum::PROVIDER:
                             $notificationService->create([
-                                'title'       => 'Agendamento cancelado!',
-                                'description' => $schedule->provider->user->name . ' cancelou sua solicitação de diária.',
+                                'title'       => __('notifications.schedule_cancelled_title'),
+                                'description' => __('notifications.provider_cancelled_schedule_description', ['provider' => $schedule->provider->user->name]),
                                 'origin'      => 'schedule',
                                 'origin_id'   => $schedule->id,
                                 'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_CANCELLED->value,
@@ -236,8 +242,8 @@ class ScheduleService
                     // CLIENTE
 
                     $notificationService->create([
-                        'title'       => 'Diarista a caminho!',
-                        'description' => 'Informe o código ' . $schedule->code . ' para liberar o início do serviço.',
+                        'title'       => __('notifications.provider_on_the_way_title'),
+                        'description' => __('notifications.provider_on_the_way_description', ['code' => $schedule->code]),
                         'origin'      => 'schedule',
                         'origin_id'   => $schedule->id,
                         'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_COMING->value,
@@ -247,8 +253,8 @@ class ScheduleService
                     // PRESTADOR
 
                     $notificationService->create([
-                        'title'       => 'Início do serviço!',
-                        'description' => 'Solicite o código ao cliente para iniciar a diária.',
+                        'title'       => __('notifications.service_start_title'),
+                        'description' => __('notifications.service_start_description'),
                         'origin'      => 'schedule',
                         'origin_id'   => $schedule->id,
                         'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_START->value,
@@ -263,8 +269,8 @@ class ScheduleService
                     // CLIENTE
 
                     $notificationService->create([
-                        'title'       => 'Serviço finalizado!',
-                        'description' => 'Sua diária foi finalizada com sucesso.',
+                        'title'       => __('notifications.service_finished_title'),
+                        'description' => __('notifications.service_finished_description'),
                         'origin'      => 'schedule',
                         'origin_id'   => $schedule->id,
                         'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_FINISHED->value,
@@ -280,8 +286,8 @@ class ScheduleService
                         case UserTypeEnum::CLIENT:
                             if ($schedule->provider_id) {
                                 $notificationService->create([
-                                    'title'       => 'Pagamento confirmado!',
-                                    'description' => 'O cliente confirmou o pagamento da diária.',
+                                    'title'       => __('notifications.payment_confirmed_title'),
+                                    'description' => __('notifications.payment_confirmed_description'),
                                     'origin'      => 'schedule',
                                     'origin_id'   => $schedule->id,
                                     'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_START->value,
@@ -311,8 +317,8 @@ class ScheduleService
                     $notificationService = app(NotificationService::class);
 
                     $notificationService->create([
-                        'title'       => 'Agendamento recusado!',
-                        'description' => 'O diarista não poderá atender. Veja outros profissionais disponíveis.',
+                        'title'       => __('notifications.schedule_refused_title'),
+                        'description' => __('notifications.schedule_refused_description'),
                         'origin'      => 'schedule',
                         'origin_id'   => $schedule->id,
                         'type'        => NotificationTypeEnum::SCHEDULE_CLIENT_PROVIDER_REFUSED->value,
@@ -325,12 +331,16 @@ class ScheduleService
             DB::commit();
 
             return $schedule->fresh(['client.user', 'provider.user', 'address']);
+        } catch (ScheduleStatusTransitionException $e) {
+            DB::rollBack();
+
+            throw $e;
         } catch (\Exception $e) {
             DB::rollBack();
 
             Log::error('Erro ao atualizar status do agendamento: ' . $e->getMessage());
 
-            throw new \Exception('Não foi possível atualizar o status do agendamento.');
+            throw $e;
         }
     }
 
@@ -467,7 +477,7 @@ class ScheduleService
             $allowedStatuses = ['accepted', 'paid', 'pending'];
 
             if (! in_array($schedule->status, $allowedStatuses)) {
-                throw new \Exception("Cancelamento não permitido para o status atual: {$schedule->status}");
+                throw new ScheduleStatusTransitionException;
             }
 
             $cancelled_by = Auth::user()->type;
@@ -481,12 +491,16 @@ class ScheduleService
             DB::commit();
 
             return $schedule->fresh(['client.user', 'provider.user', 'address']);
+        } catch (ScheduleStatusTransitionException $e) {
+            DB::rollBack();
+
+            throw $e;
         } catch (\Exception $e) {
             DB::rollBack();
 
             Log::error('Erro ao cancelar agendamento: '.$e->getMessage());
 
-            throw new \Exception('Não foi possível cancelar o agendamento.');
+            throw $e;
         }
     }
 
@@ -501,19 +515,19 @@ class ScheduleService
             '8' => $provider->daily_price_8h ?? 0,
         ];
 
-        return $hourlyRates[$periodType] ?? 0;
+        return data_get($hourlyRates, $periodType, 0);
     }
 
     private function validateProviderAvailability(array $data, $excludeScheduleId = null)
     {
-        $provider_id = $data['provider_id'];
-        $client_id   = $data['client_id'];
+        $provider_id = data_get($data, 'provider_id');
+        $client_id   = data_get($data, 'client_id');
 
-        $date = Carbon::parse($data['date']);
+        $date = Carbon::parse(data_get($data, 'date'));
 
         $dayOfWeek = $date->dayOfWeek;
-        $startTime = $data['start_time'];
-        $endTime   = $data['end_time'];
+        $startTime = data_get($data, 'start_time');
+        $endTime   = data_get($data, 'end_time');
         $date_ymd  = $date->format('Y-m-d');
         $period    = $startTime < '13:00:00' ? 'morning' : 'afternoon';
 
@@ -524,7 +538,7 @@ class ScheduleService
         ScheduleBusinessRules::validateWeeklyScheduleLimit(
             $client_id,
             $provider_id,
-            $data['date'],
+            data_get($data, 'date'),
             $excludeScheduleId
         );
 

+ 11 - 11
app/Services/SearchService.php

@@ -42,8 +42,8 @@ class SearchService
         );
 
         $distanceSelect = $this->distanceSelect(
-            $clientCoordinates['latitude'] ?? null,
-            $clientCoordinates['longitude'] ?? null,
+            data_get($clientCoordinates, 'latitude'),
+            data_get($clientCoordinates, 'longitude'),
         );
 
         $baseQuery = Provider::leftJoin(
@@ -172,15 +172,15 @@ class SearchService
         return $filtered->map(function ($item) use ($mediaUrls) {
             $arr = is_array($item) ? $item : $item->toArray();
 
-            $arr['profile_media_url'] = $mediaUrls[$arr['profile_media_id'] ?? null] ?? null;
-            $arr['daily_price_8h_base'] = $arr['daily_price_8h'] ?? null;
-            $arr['daily_price_6h_base'] = $arr['daily_price_6h'] ?? null;
-            $arr['daily_price_4h_base'] = $arr['daily_price_4h'] ?? null;
-            $arr['daily_price_2h_base'] = $arr['daily_price_2h'] ?? null;
-            $arr['daily_price_8h']      = $this->applyCreditCardFee($arr['daily_price_8h'] ?? null);
-            $arr['daily_price_6h']      = $this->applyCreditCardFee($arr['daily_price_6h'] ?? null);
-            $arr['daily_price_4h']      = $this->applyCreditCardFee($arr['daily_price_4h'] ?? null);
-            $arr['daily_price_2h']      = $this->applyCreditCardFee($arr['daily_price_2h'] ?? null);
+            $arr['profile_media_url']   = data_get($mediaUrls, data_get($arr, 'profile_media_id'));
+            $arr['daily_price_8h_base'] = data_get($arr, 'daily_price_8h');
+            $arr['daily_price_6h_base'] = data_get($arr, 'daily_price_6h');
+            $arr['daily_price_4h_base'] = data_get($arr, 'daily_price_4h');
+            $arr['daily_price_2h_base'] = data_get($arr, 'daily_price_2h');
+            $arr['daily_price_8h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_8h'));
+            $arr['daily_price_6h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_6h'));
+            $arr['daily_price_4h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_4h'));
+            $arr['daily_price_2h']      = $this->applyCreditCardFee(data_get($arr, 'daily_price_2h'));
 
             unset(
                 $arr['profile_media_id'],

+ 134 - 0
app/Services/ServicePackageItemService.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace App\Services;
+
+use App\Enums\ServicePackageStatusEnum;
+use App\Enums\PaymentStatusEnum;
+use App\Models\ServicePackage;
+use App\Models\ServicePackageItem;
+use App\Models\Payment;
+use App\Models\Schedule;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\ValidationException;
+
+class ServicePackageItemService
+{
+    public function getAll(): Collection
+    {
+        return ServicePackageItem::query()
+            ->orderBy('created_at', 'desc')
+            ->get();
+    }
+
+    public function findById(int $id): ?ServicePackageItem
+    {
+        return ServicePackageItem::find($id);
+    }
+
+    public function create(array $data): ServicePackageItem
+    {
+        return DB::transaction(function () use ($data) {
+            [$servicePackage, $schedule] = $this->validateItem(data_get($data, 'service_package_id'), data_get($data, 'schedule_id'));
+
+            if (! $servicePackage->provider_id) {
+                $servicePackage->update(['provider_id' => $schedule->provider_id]);
+            }
+
+            return ServicePackageItem::create($data);
+        });
+    }
+
+    public function update(int $id, array $data): ?ServicePackageItem
+    {
+        return DB::transaction(function () use ($id, $data): ?ServicePackageItem {
+            $model = $this->findById($id);
+
+            if (! $model) {
+                return null;
+            }
+
+            $servicePackageId = data_get($data, 'service_package_id', $model->service_package_id);
+
+            $scheduleId = data_get($data, 'schedule_id', $model->schedule_id);
+
+            [$servicePackage, $schedule] = $this->validateItem($servicePackageId, $scheduleId);
+
+            if (! $servicePackage->provider_id) {
+                $servicePackage->update(['provider_id' => $schedule->provider_id]);
+            }
+
+            $model->update($data);
+
+            return $model->fresh();
+        });
+    }
+
+    public function delete(int $id): bool
+    {
+        $model = $this->findById($id);
+
+        if (!$model) {
+            return false;
+        }
+
+        return DB::transaction(function () use ($model): bool {
+            $this->validateItem($model->service_package_id, $model->schedule_id);
+
+            return $model->delete();
+        });
+    }
+
+    private function validateItem(int $servicePackageId, int $scheduleId): array
+    {
+        $servicePackage = ServicePackage::query()->lockForUpdate()->findOrFail($servicePackageId);
+        $schedule = Schedule::query()->findOrFail($scheduleId);
+
+        if ($servicePackage->status !== ServicePackageStatusEnum::OPEN) {
+            throw ValidationException::withMessages([
+                'service_package_id' => 'Somente pacotes abertos podem ser alterados.',
+            ]);
+        }
+
+        if ($schedule->client_id !== $servicePackage->client_id) {
+            throw ValidationException::withMessages([
+                'schedule_id' => 'O agendamento precisa pertencer ao mesmo cliente do pacote.',
+            ]);
+        }
+
+        if (! $schedule->provider_id || ($servicePackage->provider_id && $schedule->provider_id !== $servicePackage->provider_id)) {
+            throw ValidationException::withMessages([
+                'schedule_id' => __('messages.service_package_same_provider'),
+            ]);
+        }
+
+        $belongsToAnotherPackage = ServicePackageItem::query()
+            ->where('schedule_id', $scheduleId)
+            ->where('service_package_id', '!=', $servicePackageId)
+            ->exists();
+
+        if ($belongsToAnotherPackage) {
+            throw ValidationException::withMessages([
+                'schedule_id' => 'O agendamento já pertence a outro pacote.',
+            ]);
+        }
+
+        $hasActivePayment = Payment::query()
+            ->where('schedule_id', $scheduleId)
+            ->whereIn('status', [
+                PaymentStatusEnum::PENDING->value,
+                PaymentStatusEnum::PROCESSING->value,
+                PaymentStatusEnum::AUTHORIZED->value,
+                PaymentStatusEnum::PAID->value,
+            ])
+            ->exists();
+
+        if ($hasActivePayment) {
+            throw ValidationException::withMessages([
+                'schedule_id' => 'O agendamento já possui um pagamento e não pode ser adicionado ao pacote.',
+            ]);
+        }
+
+        return [$servicePackage, $schedule];
+    }
+}

+ 31 - 31
app/Services/CartService.php → app/Services/ServicePackageService.php

@@ -2,36 +2,36 @@
 
 namespace App\Services;
 
-use App\Models\Cart;
+use App\Models\ServicePackage;
 use App\Models\Client;
 use Illuminate\Support\Arr;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Validation\ValidationException;
 
-class CartService
+class ServicePackageService
 {
     public function getAll(): Collection
     {
-        return Cart::query()
+        return ServicePackage::query()
             ->with(['items.schedule'])
             ->orderBy('created_at', 'desc')
             ->get();
     }
 
-    public function findById(int $id): ?Cart
+    public function findById(int $id): ?ServicePackage
     {
-        return Cart::query()
+        return ServicePackage::query()
             ->with(['items.schedule'])
             ->find($id);
     }
 
-    public function create(array $data): Cart
+    public function create(array $data): ServicePackage
     {
-        return Cart::create($data);
+        return ServicePackage::create($data);
     }
 
-    public function update(int $id, array $data): ?Cart
+    public function update(int $id, array $data): ?ServicePackage
     {
         $model = $this->findById($id);
 
@@ -39,26 +39,26 @@ class CartService
             return null;
         }
 
-        if (isset($data['provider_id'])) {
+        if (data_get($data, 'provider_id') !== null) {
             $hasDifferentProvider = $model->items()
-                ->whereHas('schedule', fn ($query) => $query->where('provider_id', '!=', $data['provider_id']))
+                ->whereHas('schedule', fn ($query) => $query->where('provider_id', '!=', data_get($data, 'provider_id')))
                 ->exists();
 
             if ($hasDifferentProvider) {
                 throw ValidationException::withMessages([
-                    'provider_id' => 'O prestador não corresponde aos agendamentos existentes no carrinho.',
+                    'provider_id' => 'O prestador não corresponde aos agendamentos existentes no pacote.',
                 ]);
             }
         }
 
-        if (isset($data['client_id'])) {
+        if (data_get($data, 'client_id') !== null) {
             $hasDifferentClient = $model->items()
-                ->whereHas('schedule', fn ($query) => $query->where('client_id', '!=', $data['client_id']))
+                ->whereHas('schedule', fn ($query) => $query->where('client_id', '!=', data_get($data, 'client_id')))
                 ->exists();
 
             if ($hasDifferentClient) {
                 throw ValidationException::withMessages([
-                    'client_id' => 'O cliente não corresponde aos agendamentos existentes no carrinho.',
+                    'client_id' => 'O cliente não corresponde aos agendamentos existentes no pacote.',
                 ]);
             }
         }
@@ -83,14 +83,14 @@ class CartService
 
     public function getByUserId(int $userId): Collection
     {
-        return Cart::query()
+        return ServicePackage::query()
             ->with(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address'])
             ->whereHas('client', fn ($query) => $query->where('user_id', $userId))
             ->orderBy('created_at', 'desc')
             ->get();
     }
 
-    public function createForUser(array $data, int $userId): Cart
+    public function createForUser(array $data, int $userId): ServicePackage
     {
         $client = Client::query()
             ->where('user_id', $userId)
@@ -103,7 +103,7 @@ class CartService
             : $this->create($data);
     }
 
-    public function createWithSchedules(array $data): Cart
+    public function createWithSchedules(array $data): ServicePackage
     {
         return DB::transaction(function () use ($data) {
             $items = $this->scheduleItems($data);
@@ -114,18 +114,18 @@ class CartService
                 schedules: $items,
             );
 
-            $cart = Cart::create([
-                'client_id'   => $data['client_id'],
+            $servicePackage = ServicePackage::create([
+                'client_id'   => data_get($data, 'client_id'),
                 'provider_id' => $providerId,
             ]);
 
             foreach ($schedules as $schedule) {
-                $cart->items()->create([
+                $servicePackage->items()->create([
                     'schedule_id' => $schedule->id,
                 ]);
             }
 
-            return $cart->fresh(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address']);
+            return $servicePackage->fresh(['items.schedule.client.user', 'items.schedule.provider.user', 'items.schedule.address']);
         });
     }
 
@@ -147,24 +147,24 @@ class CartService
 
     private function hasSchedulePayload(array $data): bool
     {
-        return ! empty($data['schedules'])
-            || ! empty($data['dates'])
-            || ! empty($data['date']);
+        return ! empty(data_get($data, 'schedules'))
+            || ! empty(data_get($data, 'dates'))
+            || ! empty(data_get($data, 'date'));
     }
 
     private function scheduleItems(array $data): array
     {
-        if (! empty($data['schedules'])) {
-            return $data['schedules'];
+        if (! empty(data_get($data, 'schedules'))) {
+            return data_get($data, 'schedules');
         }
 
-        if (! empty($data['dates'])) {
+        if (! empty(data_get($data, 'dates'))) {
             return array_map(
                 fn (string $date) => array_merge(
                     Arr::only($data, ['period_type', 'start_time', 'end_time', 'total_amount', 'offers_meal']),
                     ['date' => $date],
                 ),
-                $data['dates'],
+                data_get($data, 'dates'),
             );
         }
 
@@ -175,17 +175,17 @@ class CartService
 
     private function singleProviderId(array $data, array $items): int
     {
-        $rootProviderId = $data['provider_id'] ?? null;
+        $rootProviderId = data_get($data, 'provider_id');
 
         $providerIds = collect($items)
-            ->map(fn (array $item) => $item['provider_id'] ?? $rootProviderId)
+            ->map(fn (array $item) => data_get($item, 'provider_id', $rootProviderId))
             ->filter()
             ->unique()
             ->values();
 
         if ($providerIds->count() !== 1) {
             throw ValidationException::withMessages([
-                'schedules' => 'Todos os agendamentos do carrinho precisam ser do mesmo prestador.',
+                'schedules' => __('messages.service_package_same_provider'),
             ]);
         }
 

+ 1 - 1
app/Services/SupportRequestService.php

@@ -83,7 +83,7 @@ class SupportRequestService
             Mail::to($recipient)->send(new SupportRequestAlert($supportRequest));
         } catch (\Throwable $e) {
             // Alerta é best-effort: o registro no backoffice continua sendo a fonte da verdade.
-            Log::error('Support request alert email failed', [
+            Log::error('Falha ao enviar e-mail de alerta da solicitação de suporte', [
                 'support_request_id' => $supportRequest->id,
                 'error'              => $e->getMessage(),
             ]);

+ 7 - 7
app/Services/UserService.php

@@ -77,8 +77,8 @@ class UserService
             Log::warning('[avatar-upload] UserService::updateMe iniciado', [
                 'user_id'        => $user->id,
                 'client_id'      => $user->client?->id,
-                'has_avatar_key' => isset($data['avatar']),
-                'avatar_is_file' => isset($data['avatar']) && $data['avatar'] instanceof UploadedFile,
+                'has_avatar_key' => data_get($data, 'avatar') !== null,
+                'avatar_is_file' => data_get($data, 'avatar') !== null && data_get($data, 'avatar') instanceof UploadedFile,
                 'old_media_id'   => $user->client?->profile_media_id,
                 'old_media_path' => $user->client?->profileMedia?->path,
             ]);
@@ -95,12 +95,12 @@ class UserService
             if (array_key_exists('document', $data)) {
                 $client = $user->client ?? Client::create(['user_id' => $user->id]);
 
-                $client->document = $data['document'];
+                $client->document = data_get($data, 'document');
 
                 $client->save();
             }
 
-            if (isset($data['avatar']) && $data['avatar'] instanceof UploadedFile) {
+            if (data_get($data, 'avatar') !== null && data_get($data, 'avatar') instanceof UploadedFile) {
                 $client = $user->client ?? Client::create(['user_id' => $user->id]);
 
                 Log::warning('[avatar-upload] Iniciando replaceFile para avatar do cliente', [
@@ -112,7 +112,7 @@ class UserService
                 ]);
 
                 $media = $this->mediaService->replaceFile(
-                    newFile: $data['avatar'],
+                    newFile: data_get($data, 'avatar'),
                     folder: "client/avatar/{$client->id}",
                     source: 'client',
                     sourceId: $client->id,
@@ -135,8 +135,8 @@ class UserService
                 ]);
             } else {
                 Log::warning('[avatar-upload] Avatar NÃO processado no service', [
-                    'isset_avatar'   => isset($data['avatar']),
-                    'is_upload_file' => isset($data['avatar']) ? ($data['avatar'] instanceof UploadedFile) : false,
+                    'isset_avatar'   => data_get($data, 'avatar') !== null,
+                    'is_upload_file' => data_get($data, 'avatar') !== null ? (data_get($data, 'avatar') instanceof UploadedFile) : false,
                     'data_keys'      => array_keys($data),
                 ]);
             }

+ 31 - 31
app/Services/WebhookService.php

@@ -38,14 +38,14 @@ class WebhookService
 
     public function handlePagarme(array $payload): ?Payment
     {
-        $event = $payload['type'] ?? null;
-        $data  = $payload['data'] ?? [];
+        $event = data_get($payload, 'type');
+        $data  = data_get($payload, 'data', []);
         $data  = is_array($data) ? $data : [];
 
         $webhook = $this->recordWebhook($payload, $event, $data);
 
-        Log::channel('pagarme')->info('Pagar.me webhook received', [
-            'hook_id' => $payload['id'] ?? null,
+        Log::channel('pagarme')->info('Webhook do Pagar.me recebido', [
+            'hook_id' => data_get($payload, 'id'),
             'event'   => $event,
         ]);
 
@@ -79,13 +79,13 @@ class WebhookService
                 'processed_at' => now(),
             ]);
 
-            Log::channel('pagarme')->warning('Payment not found for Pagar.me webhook', [
-                'hook_id'        => $payload['id'] ?? null,
+            Log::channel('pagarme')->warning('Pagamento não encontrado para o webhook do Pagar.me', [
+                'hook_id'        => data_get($payload, 'id'),
                 'event'          => $event,
-                'order_id'       => $orderResponse['id']                                   ?? null,
-                'charge_id'      => $orderResponse['charges'][0]['id']                     ?? null,
-                'transaction_id' => $orderResponse['charges'][0]['last_transaction']['id'] ?? null,
-                'metadata'       => $orderResponse['metadata']                             ?? [],
+                'order_id'       => data_get($orderResponse, 'id'),
+                'charge_id'      => data_get($orderResponse, 'charges.0.id'),
+                'transaction_id' => data_get($orderResponse, 'charges.0.last_transaction.id'),
+                'metadata'       => data_get($orderResponse, 'metadata', []),
             ]);
 
             return null;
@@ -121,16 +121,16 @@ class WebhookService
     {
         $references = $this->extractReferences($event, $data);
 
-        $hookId = $payload['id'] ?? null;
+        $hookId = data_get($payload, 'id');
 
         $attributes = [
             'hook_id'        => $hookId,
             'provider'       => 'pagarme',
             'event'          => $event,
-            'account_id'     => $payload['account']['id'] ?? null,
-            'order_id'       => $references['order_id'],
-            'charge_id'      => $references['charge_id'],
-            'transaction_id' => $references['transaction_id'],
+            'account_id'     => data_get($payload, 'account.id'),
+            'order_id'       => data_get($references, 'order_id'),
+            'charge_id'      => data_get($references, 'charge_id'),
+            'transaction_id' => data_get($references, 'transaction_id'),
             'status'         => 'received',
             'payload'        => $payload,
             'received_at'    => now(),
@@ -160,20 +160,20 @@ class WebhookService
     private function extractReferences(?string $event, array $data): array
     {
         if (str_starts_with((string) $event, 'order.')) {
-            $charge = $data['charges'][0] ?? [];
+            $charge = data_get($data, 'charges.0', []);
 
             return [
-                'order_id'       => $data['id']                       ?? null,
-                'charge_id'      => $charge['id']                     ?? null,
-                'transaction_id' => $charge['last_transaction']['id'] ?? null,
+                'order_id'       => data_get($data, 'id'),
+                'charge_id'      => data_get($charge, 'id'),
+                'transaction_id' => data_get($charge, 'last_transaction.id'),
             ];
         }
 
         if (str_starts_with((string) $event, 'charge.')) {
             return [
-                'order_id'       => $data['order']['id'] ?? $data['order_id'] ?? null,
-                'charge_id'      => $data['id'] ?? null,
-                'transaction_id' => $data['last_transaction']['id'] ?? null,
+                'order_id'       => data_get($data, 'order.id', data_get($data, 'order_id')),
+                'charge_id'      => data_get($data, 'id'),
+                'transaction_id' => data_get($data, 'last_transaction.id'),
             ];
         }
 
@@ -186,13 +186,13 @@ class WebhookService
 
     private function findPayment(array $orderResponse): ?Payment
     {
-        $charge        = $orderResponse['charges'][0] ?? [];
-        $transaction   = $charge['last_transaction']  ?? [];
-        $metadata      = $orderResponse['metadata']   ?? $charge['metadata'] ?? [];
-        $metadataId    = filter_var($metadata['payment_id'] ?? null, FILTER_VALIDATE_INT) ?: null;
-        $chargeId      = $charge['id']        ?? null;
-        $transactionId = $transaction['id']   ?? null;
-        $orderId       = $orderResponse['id'] ?? null;
+        $charge        = data_get($orderResponse, 'charges.0', []);
+        $transaction   = data_get($charge, 'last_transaction', []);
+        $metadata      = data_get($orderResponse, 'metadata', data_get($charge, 'metadata', []));
+        $metadataId    = filter_var(data_get($metadata, 'payment_id'), FILTER_VALIDATE_INT) ?: null;
+        $chargeId      = data_get($charge, 'id');
+        $transactionId = data_get($transaction, 'id');
+        $orderId       = data_get($orderResponse, 'id');
         $references    = array_filter([$metadataId, $chargeId, $transactionId, $orderId]);
 
         if (empty($references)) {
@@ -222,8 +222,8 @@ class WebhookService
         }
 
         return [
-            'id'       => $data['order']['id'] ?? $data['order_id']          ?? null,
-            'metadata' => $data['metadata']    ?? $data['order']['metadata'] ?? [],
+            'id'       => data_get($data, 'order.id', data_get($data, 'order_id')),
+            'metadata' => data_get($data, 'metadata', data_get($data, 'order.metadata', [])),
             'charges'  => [$data],
         ];
     }

+ 16 - 16
app/Services/ZipCodeCoordinatesService.php

@@ -37,10 +37,10 @@ class ZipCodeCoordinatesService
         $target = $this->resolve($targetLatitude, $targetLongitude, $targetZipCode);
 
         $distance = DistanceService::calculate(
-            $origin['latitude']  ?? null,
-            $origin['longitude'] ?? null,
-            $target['latitude']  ?? null,
-            $target['longitude'] ?? null,
+            data_get($origin, 'latitude'),
+            data_get($origin, 'longitude'),
+            data_get($target, 'latitude'),
+            data_get($target, 'longitude'),
         );
 
         return $distance;
@@ -55,11 +55,11 @@ class ZipCodeCoordinatesService
         }
 
         if (array_key_exists($zipCode, $this->resolvedZipCodes)) {
-            return $this->resolvedZipCodes[$zipCode];
+            return data_get($this->resolvedZipCodes, $zipCode);
         }
 
         if ($this->restoreFromCache($zipCode)) {
-            return $this->resolvedZipCodes[$zipCode];
+            return data_get($this->resolvedZipCodes, $zipCode);
         }
 
         if (! $this->googleGeocodingIsConfigured()) {
@@ -103,7 +103,7 @@ class ZipCodeCoordinatesService
             return;
         }
 
-        Log::channel('google_geocoding')->info('ZipCodeCoordinates Google Geocoding lookup required', [
+        Log::channel('google_geocoding')->info('Consulta ao Google Geocoding necessária para as coordenadas do CEP', [
             'mode'             => 'preload',
             'pending_count'    => count($pending),
             'zip_codes_sample' => array_slice(array_values($pending), 0, 20),
@@ -128,7 +128,7 @@ class ZipCodeCoordinatesService
                 $pending,
             ));
         } catch (Throwable $exception) {
-            Log::channel('google_geocoding')->warning('ZipCodeCoordinates Google Geocoding preload failed', [
+            Log::channel('google_geocoding')->warning('Falha ao pré-carregar coordenadas de CEP pelo Google Geocoding', [
                 'pending_count' => count($pending),
                 'elapsed_ms'    => isset($startedAt) ? round((microtime(true) - $startedAt) * 1000, 2) : null,
                 'error'         => $exception->getMessage(),
@@ -138,7 +138,7 @@ class ZipCodeCoordinatesService
         }
 
         foreach ($pending as $zipCode) {
-            $response = $responses[$zipCode] ?? null;
+            $response = data_get($responses, $zipCode);
 
             $coordinates = $response instanceof Response
                 ? $this->coordinatesFromResponse($response)
@@ -223,8 +223,8 @@ class ZipCodeCoordinatesService
                 $query->whereNull('latitude')->orWhereNull('longitude');
             })
             ->update([
-                'latitude'  => $coordinates['latitude'],
-                'longitude' => $coordinates['longitude'],
+                'latitude'  => data_get($coordinates, 'latitude'),
+                'longitude' => data_get($coordinates, 'longitude'),
             ]);
     }
 
@@ -255,7 +255,7 @@ class ZipCodeCoordinatesService
         $startedAt = microtime(true);
 
         try {
-            Log::channel('google_geocoding')->info('ZipCodeCoordinates calling Google Geocoding', [
+            Log::channel('google_geocoding')->info('Consulta de coordenadas de CEP no Google Geocoding iniciada', [
                 'zip_code'        => $zipCode,
                 'base_url'        => rtrim((string) config('services.google_geocoding.base_url'), '/'),
                 'connect_timeout' => (int) config('services.google_geocoding.connect_timeout', 2),
@@ -273,7 +273,7 @@ class ZipCodeCoordinatesService
 
             return $this->coordinatesFromResponse($response);
         } catch (Throwable $exception) {
-            Log::channel('google_geocoding')->warning('ZipCodeCoordinates Google Geocoding request failed', [
+            Log::channel('google_geocoding')->warning('Falha na consulta de coordenadas de CEP ao Google Geocoding', [
                 'zip_code'   => $zipCode,
                 'elapsed_ms' => round((microtime(true) - $startedAt) * 1000, 2),
                 'error'      => $exception->getMessage(),
@@ -293,14 +293,14 @@ class ZipCodeCoordinatesService
             return false;
         }
 
-        if (! $cached['found']) {
+        if (! data_get($cached, 'found')) {
             Cache::forget($cacheKey);
 
             return false;
         }
 
-        $this->resolvedZipCodes[$zipCode] = $cached['found']
-            ? $cached['coordinates']
+        $this->resolvedZipCodes[$zipCode] = data_get($cached, 'found')
+            ? data_get($cached, 'coordinates')
             : null;
 
         return true;

+ 1 - 1
app/Tasks/DeleteExpiredTokens.php

@@ -10,6 +10,6 @@ class DeleteExpiredTokens
     public function __invoke(): void
     {
         $deletedTokens = PersonalAccessToken::where(column: 'expires_at', operator: '<', value: now())->delete();
-        Log::channel(channel: 'expiredTokens')->info(message: 'Deleted '.$deletedTokens.' expired tokens');
+        Log::channel(channel: 'expiredTokens')->info(message: 'Tokens expirados excluídos: '.$deletedTokens);
     }
 }

+ 1 - 1
app/Tasks/SendPushNotificationsTask.php

@@ -15,7 +15,7 @@ class SendPushNotificationsTask
         try {
             $this->dispatcher->dispatch();
         } catch (Throwable $e) {
-            Log::error('SendPushNotificationsTask failed: '.$e->getMessage(), [
+            Log::error('Falha ao executar SendPushNotificationsTask: '.$e->getMessage(), [
                 'exception' => $e,
             ]);
         }

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor