Gustavo Mantovani 2 недель назад
Родитель
Сommit
7657165b02
60 измененных файлов с 1098 добавлено и 390 удалено
  1. 1 1
      app/Commands/CreateCrud.php
  2. 2 2
      app/Commands/TestWebsocketEvent.php
  3. 23 0
      app/Data/Pagarme/Request/PagarmeTransferRequestData.php
  4. 64 0
      app/Data/Pagarme/Response/PagarmeTransferResponseData.php
  5. 16 0
      app/Enums/PaymentSplitStatusEnum.php
  6. 17 0
      app/Enums/PaymentStatusEnum.php
  7. 16 0
      app/Enums/ProviderWithdrawalStatusEnum.php
  8. 9 9
      app/Http/Controllers/AuthController.php
  9. 1 1
      app/Http/Controllers/CityController.php
  10. 1 1
      app/Http/Controllers/CustomScheduleController.php
  11. 5 4
      app/Http/Controllers/PaymentController.php
  12. 50 0
      app/Http/Controllers/PaymentSplitController.php
  13. 0 50
      app/Http/Controllers/PaymentTransferController.php
  14. 2 2
      app/Http/Controllers/ProviderController.php
  15. 73 0
      app/Http/Controllers/ProviderWithdrawalController.php
  16. 1 1
      app/Http/Controllers/ProviderWorkingDayController.php
  17. 1 1
      app/Http/Middleware/CheckPermission.php
  18. 7 7
      app/Http/Middleware/PerformanceMonitor.php
  19. 1 1
      app/Http/Requests/PaymentSplitRequest.php
  20. 9 5
      app/Http/Resources/CustomScheduleResource.php
  21. 25 31
      app/Http/Resources/PaymentResource.php
  22. 46 0
      app/Http/Resources/PaymentSplitResource.php
  23. 0 61
      app/Http/Resources/PaymentTransferResource.php
  24. 35 0
      app/Http/Resources/ProviderWithdrawalResource.php
  25. 17 9
      app/Http/Resources/ReviewDetailResource.php
  26. 10 6
      app/Http/Resources/ReviewResource.php
  27. 3 1
      app/Http/Resources/ScheduleResource.php
  28. 5 5
      app/Jobs/FinishScheduleJob.php
  29. 1 1
      app/Jobs/StartScheduleJob.php
  30. 8 8
      app/Models/Payment.php
  31. 44 34
      app/Models/PaymentSplit.php
  32. 109 0
      app/Models/ProviderWithdrawal.php
  33. 6 6
      app/Rules/ScheduleBusinessRules.php
  34. 7 7
      app/Services/AuthService.php
  35. 1 1
      app/Services/ClientService.php
  36. 11 11
      app/Services/CustomScheduleService.php
  37. 1 1
      app/Services/DashboardService.php
  38. 1 1
      app/Services/Pagarme/Concerns/SendsPagarmeRequests.php
  39. 1 1
      app/Services/Pagarme/PagarmeCardService.php
  40. 8 8
      app/Services/Pagarme/PagarmeCustomerService.php
  41. 15 14
      app/Services/Pagarme/PagarmePaymentService.php
  42. 11 11
      app/Services/Pagarme/PagarmeRecipientService.php
  43. 36 0
      app/Services/Pagarme/PagarmeTransferService.php
  44. 26 20
      app/Services/PaymentService.php
  45. 8 10
      app/Services/PaymentSplitService.php
  46. 18 18
      app/Services/ProviderService.php
  47. 197 0
      app/Services/ProviderWithdrawalService.php
  48. 1 1
      app/Services/ReviewService.php
  49. 9 9
      app/Services/ScheduleService.php
  50. 1 1
      app/Services/SearchService.php
  51. 24 12
      app/Services/WebhookService.php
  52. 2 2
      app/Traits/Base64ToFile.php
  53. 1 1
      app/Traits/UploadsBase64Image.php
  54. 41 0
      database/migrations/2026_05_26_000001_create_provider_withdrawals_table.php
  55. 17 0
      database/migrations/2026_05_26_000002_rename_payment_transfers_to_payment_splits.php
  56. 30 0
      database/migrations/2026_05_26_000003_add_provider_withdrawal_id_to_payment_splits_table.php
  57. 4 4
      database/seeders/BrazilCitiesSeeder.php
  58. 10 0
      routes/authRoutes/payment_split.php
  59. 0 10
      routes/authRoutes/payment_transfer.php
  60. 9 0
      routes/authRoutes/provider_withdrawal.php

+ 1 - 1
app/Commands/CreateCrud.php

@@ -203,7 +203,7 @@ class CreateCrud extends Command
         );
 
         $timestamp = date('Y_m_d_His');
-        $filename  = $timestamp."_create_{$tableName}_table.php";
+        $filename = $timestamp."_create_{$tableName}_table.php";
 
         $this->put(database_path("migrations/{$filename}"), $migrationTemplate);
         $this->info("✓ Created Migration: database/migrations/{$filename}");

+ 2 - 2
app/Commands/TestWebsocketEvent.php

@@ -18,9 +18,9 @@ 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')
+            $data = $this->option('data')
                 ? json_decode($this->option('data'), true, 512, JSON_THROW_ON_ERROR)
                 : ['message' => 'Test message'];
 

+ 23 - 0
app/Data/Pagarme/Request/PagarmeTransferRequestData.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+use App\Data\Pagarme\PagarmeData;
+
+readonly class PagarmeTransferRequestData extends PagarmeData
+{
+    public function __construct(
+        public int $amount,
+        public string $recipientId,
+        public ?array $metadata = null,
+    ) {}
+
+    public function toArray(): array
+    {
+        return $this->filterFilledRecursive([
+            'amount'       => $this->amount,
+            'recipient_id' => $this->recipientId,
+            'metadata'     => $this->metadata,
+        ]);
+    }
+}

+ 64 - 0
app/Data/Pagarme/Response/PagarmeTransferResponseData.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Data\Pagarme\Response;
+
+readonly class PagarmeTransferResponseData
+{
+    public function __construct(
+        public ?string $id,
+        public ?int $amount,
+        public ?string $type,
+        public ?string $status,
+        public ?int $fee,
+        public ?string $fundingDate,
+        public ?string $fundingEstimatedDate,
+        public ?array $bankAccount,
+        public ?string $bankResponse,
+        public ?string $createdAt,
+        public ?array $metadata,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            id: $payload['id'] ?? null,
+            amount: $payload['amount'] ?? null,
+            type: $payload['type'] ?? null,
+            status: $payload['status'] ?? null,
+            fee: $payload['fee'] ?? null,
+            fundingDate: $payload['funding_date'] ?? null,
+            fundingEstimatedDate: $payload['funding_estimated_date'] ?? null,
+            bankAccount: $payload['bank_account'] ?? null,
+            bankResponse: $payload['bank_response'] ?? null,
+            createdAt: $payload['created_at'] ?? $payload['date_created'] ?? null,
+            metadata: $payload['metadata'] ?? null,
+        );
+    }
+
+    public function id(): ?string
+    {
+        return $this->id;
+    }
+
+    public function status(): ?string
+    {
+        return $this->status;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'id'                      => $this->id,
+            'amount'                  => $this->amount,
+            'type'                    => $this->type,
+            'status'                  => $this->status,
+            'fee'                     => $this->fee,
+            'funding_date'            => $this->fundingDate,
+            'funding_estimated_date'  => $this->fundingEstimatedDate,
+            'bank_account'            => $this->bankAccount,
+            'bank_response'           => $this->bankResponse,
+            'created_at'              => $this->createdAt,
+            'metadata'                => $this->metadata,
+        ];
+    }
+}

+ 16 - 0
app/Enums/PaymentSplitStatusEnum.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Enums;
+
+use App\Traits\EnumHelper;
+
+enum PaymentSplitStatusEnum: string
+{
+    use EnumHelper;
+
+    case PENDING     = 'pending';
+    case PROCESSING  = 'processing';
+    case TRANSFERRED = 'transferred';
+    case FAILED      = 'failed';
+    case CANCELLED   = 'cancelled';
+}

+ 17 - 0
app/Enums/PaymentStatusEnum.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Enums;
+
+use App\Traits\EnumHelper;
+
+enum PaymentStatusEnum: string
+{
+    use EnumHelper;
+
+    case PENDING     = 'pending';
+    case PROCESSING  = 'processing';
+    case AUTHORIZED  = 'authorized';
+    case PAID        = 'paid';
+    case FAILED      = 'failed';
+    case CANCELLED   = 'cancelled';
+}

+ 16 - 0
app/Enums/ProviderWithdrawalStatusEnum.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Enums;
+
+use App\Traits\EnumHelper;
+
+enum ProviderWithdrawalStatusEnum: string
+{
+    use EnumHelper;
+
+    case PENDING_TRANSFER = 'pending_transfer';
+    case PROCESSING       = 'processing';
+    case TRANSFERRED      = 'transferred';
+    case FAILED           = 'failed';
+    case CANCELED         = 'canceled';
+}

+ 9 - 9
app/Http/Controllers/AuthController.php

@@ -173,9 +173,9 @@ class AuthController extends Controller
 
     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);
@@ -203,9 +203,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);
@@ -234,9 +234,9 @@ 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);

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

@@ -62,7 +62,7 @@ class CityController extends Controller
     public function searchStateCityByDescription(Request $request): JsonResponse
     {
         $state = $request->query('stateUf');
-        $city  = $request->query('cityName');
+        $city = $request->query('cityName');
 
         $result = $this->service->findStateCityByDescription($state, $city);
 

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

@@ -37,7 +37,7 @@ class CustomScheduleController extends Controller
 
             $customSchedules = $this->customScheduleService->create($validated);
 
-            $count   = count($customSchedules);
+            $count = count($customSchedules);
             $message = $count > 1
               ? "{$count} oportunidades criadas com sucesso!"
               : __('messages.created');

+ 5 - 4
app/Http/Controllers/PaymentController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers;
 
+use App\Enums\PaymentStatusEnum;
 use App\Http\Requests\PaymentRequest;
 use App\Http\Resources\PaymentResource;
 use App\Models\Schedule;
@@ -29,7 +30,7 @@ class PaymentController extends Controller
 
         return $this->successResponse(
             payload: new PaymentResource($item),
-            message: $this->paymentMessage($item->status),
+            message: $this->paymentMessage($item->status->value),
             code: 201,
         );
     }
@@ -57,16 +58,16 @@ class PaymentController extends Controller
             ],
         );
 
-        if ($item->status === 'failed') {
+        if ($item->status === PaymentStatusEnum::FAILED) {
             return response()->json([
                 'payload' => new PaymentResource($item),
-                'message' => $item->failure_message ?: $this->paymentMessage($item->status),
+                'message' => $item->failure_message ?: $this->paymentMessage($item->status->value),
             ], 422);
         }
 
         return $this->successResponse(
             payload: new PaymentResource($item),
-            message: $this->paymentMessage($item->status),
+            message: $this->paymentMessage($item->status->value),
             code: 201,
         );
     }

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

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

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

@@ -1,50 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Http\Requests\PaymentTransferRequest;
-use App\Http\Resources\PaymentTransferResource;
-use App\Services\PaymentTransferService;
-use Illuminate\Http\JsonResponse;
-
-class PaymentTransferController extends Controller
-{
-    public function __construct(
-        protected PaymentTransferService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-
-        return $this->successResponse(payload: PaymentTransferResource::collection($items));
-    }
-
-    public function store(PaymentTransferRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-
-        return $this->successResponse(payload: new PaymentTransferResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-
-        return $this->successResponse(payload: new PaymentTransferResource($item));
-    }
-
-    public function update(PaymentTransferRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-
-        return $this->successResponse(payload: new PaymentTransferResource($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/ProviderController.php

@@ -74,8 +74,8 @@ class ProviderController extends Controller
 
     public function pending(Request $request): JsonResponse
     {
-        $page      = $request->integer('page', 1);
-        $perPage   = $request->integer('per_page', 10);
+        $page = $request->integer('page', 1);
+        $perPage = $request->integer('per_page', 10);
         $paginated = $this->service->getPending($page, $perPage);
 
         return $this->successResponse(payload: [

+ 73 - 0
app/Http/Controllers/ProviderWithdrawalController.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Resources\ProviderWithdrawalResource;
+use App\Services\ProviderWithdrawalService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class ProviderWithdrawalController extends Controller
+{
+    public function __construct(
+        private readonly ProviderWithdrawalService $service,
+    ) {}
+
+    public function balance(): JsonResponse
+    {
+        $provider = Auth::user()->provider;
+
+        if (! $provider) {
+            return $this->errorResponse('Apenas prestadores podem acessar este recurso.', 403);
+        }
+
+        return $this->successResponse(payload: [
+            'available' => $this->service->getAvailableBalance($provider),
+            'pending'   => $this->service->getPendingBalance($provider),
+        ]);
+    }
+
+    public function index(): JsonResponse
+    {
+        $provider = Auth::user()->provider;
+
+        if (! $provider) {
+            return $this->errorResponse('Apenas prestadores podem acessar este recurso.', 403);
+        }
+
+        $items = $this->service->getWithdrawals($provider);
+
+        return $this->successResponse(payload: ProviderWithdrawalResource::collection($items));
+    }
+
+    public function store(Request $request): JsonResponse
+    {
+        $provider = Auth::user()->provider;
+
+        if (! $provider) {
+            return $this->errorResponse('Apenas prestadores podem solicitar saque.', 403);
+        }
+
+        $withdrawal = $this->service->requestWithdrawal($provider);
+
+        return $this->successResponse(
+            payload: new ProviderWithdrawalResource($withdrawal),
+            message: 'Saque solicitado com sucesso.',
+            code: 201,
+        );
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $provider = Auth::user()->provider;
+
+        if (! $provider) {
+            return $this->errorResponse('Apenas prestadores podem acessar este recurso.', 403);
+        }
+
+        $withdrawal = $this->service->getWithdrawal($id, $provider);
+
+        return $this->successResponse(payload: new ProviderWithdrawalResource($withdrawal));
+    }
+}

+ 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/Middleware/CheckPermission.php

@@ -60,7 +60,7 @@ class CheckPermission
         ];
 
         $requiredPermission = $bitwisePermissionTable[$permissionType] ?? 0;
-        $permissionRecord   = $userPermissions->first(function ($permission) use ($scope) {
+        $permissionRecord = $userPermissions->first(function ($permission) use ($scope) {
             return $permission->permission->scope === $scope;
         });
 

+ 7 - 7
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();
     }
 
@@ -106,7 +106,7 @@ class PerformanceMonitor
 
         preg_match('/^(\d+)(K|M|G)?$/i', $limit, $matches);
         $value = (int) ($matches[1] ?? 0);
-        $unit  = strtoupper($matches[2] ?? '');
+        $unit = strtoupper($matches[2] ?? '');
 
         return match ($unit) {
             'G'     => $value * 1024 * 1024 * 1024,
@@ -132,7 +132,7 @@ class PerformanceMonitor
             return null;
         }
 
-        $totalTime        = 0;
+        $totalTime = 0;
         $formattedQueries = [];
         foreach ($queries as $query) {
             $totalTime += $query['time'];
@@ -181,10 +181,10 @@ class PerformanceMonitor
             return null;
         }
 
-        $stats  = $status['opcache_statistics'];
-        $hits   = $stats['hits'];
+        $stats = $status['opcache_statistics'];
+        $hits = $stats['hits'];
         $misses = $stats['misses'];
-        $total  = $hits + $misses;
+        $total = $hits + $misses;
 
         return [
             'hit_rate'     => $total > 0 ? ($hits / $total * 100) : 0,

+ 1 - 1
app/Http/Requests/PaymentTransferRequest.php → app/Http/Requests/PaymentSplitRequest.php

@@ -4,7 +4,7 @@ namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
 
-class PaymentTransferRequest extends FormRequest
+class PaymentSplitRequest extends FormRequest
 {
     public function authorize(): bool
     {

+ 9 - 5
app/Http/Resources/CustomScheduleResource.php

@@ -15,13 +15,15 @@ class CustomScheduleResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id'                => $this->id,
-            'schedule_id'       => $this->schedule_id,
-            'address_type'      => $this->address_type,
-            'service_type_id'   => $this->service_type_id,
+            'id'              => $this->id,
+            'schedule_id'     => $this->schedule_id,
+            'address_type'    => $this->address_type,
+            'service_type_id' => $this->service_type_id,
+
             'service_type_name' => $this->whenLoaded('serviceType', function () {
                 return $this->serviceType->description;
             }),
+
             'description' => $this->description,
             'min_price'   => number_format($this->min_price, 2, '.', ''),
             'max_price'   => number_format($this->max_price, 2, '.', ''),
@@ -36,7 +38,8 @@ class CustomScheduleResource extends JsonResource
                     'provider_id' => $this->schedule->provider_id,
                     'client_name' => $this->schedule->client?->user?->name,
                     'address_id'  => $this->schedule->address_id,
-                    'address'     => $this->schedule->address ? [
+
+                    'address' => $this->schedule->address ? [
                         'address'      => $this->schedule->address->address,
                         'number'       => $this->schedule->address->number,
                         'district'     => $this->schedule->address->district,
@@ -51,6 +54,7 @@ class CustomScheduleResource extends JsonResource
                             $this->schedule->address->city ? "{$this->schedule->address->city->name}/{$this->schedule->address->state?->code}" : null,
                         ])),
                     ] : null,
+
                     'date'        => $this->schedule->date?->format('d/m/Y'),
                     'period_type' => $this->schedule->period_type,
                     'start_time'  => $this->schedule->start_time,

+ 25 - 31
app/Http/Resources/PaymentResource.php

@@ -17,42 +17,36 @@ class PaymentResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id'                       => $this->id,
-            'schedule_id'              => $this->schedule_id,
-            'client_id'                => $this->client_id,
-            'provider_id'              => $this->provider_id,
-            'client_payment_method_id' => $this->client_payment_method_id,
-
+            'id'                          => $this->id,
+            'schedule_id'                 => $this->schedule_id,
+            'client_id'                   => $this->client_id,
+            'provider_id'                 => $this->provider_id,
+            'client_payment_method_id'    => $this->client_payment_method_id,
             'gateway_provider'            => $this->gateway_provider,
             'gateway_entity_reference'    => $this->gateway_entity_reference,
             'gateway_entity_label'        => $this->gateway_entity_label,
             'gateway_operation_reference' => $this->gateway_operation_reference,
             'gateway_operation_label'     => $this->gateway_operation_label,
-
-            'payment_method' => $this->payment_method,
-            'status'         => $this->status,
-
-            'gross_amount'        => $this->gross_amount,
-            'gateway_fee_amount'  => $this->gateway_fee_amount,
-            'platform_fee_amount' => $this->platform_fee_amount,
-            'net_amount'          => $this->net_amount,
-            'currency'            => $this->currency,
-            'installments'        => $this->installments,
-
-            'authorized_at' => $this->authorized_at?->toISOString(),
-            'paid_at'       => $this->paid_at?->toISOString(),
-            'failed_at'     => $this->failed_at?->toISOString(),
-            'cancelled_at'  => $this->cancelled_at?->toISOString(),
-            'expires_at'    => $this->expires_at?->toISOString(),
-
-            'failure_code'    => $this->failure_code,
-            'failure_message' => $this->failure_message,
-            'pix'             => $this->pixData(),
-            'gateway_payload' => $this->gateway_payload,
-            'metadata'        => $this->metadata,
-
-            'created_at' => $this->created_at?->toISOString(),
-            'updated_at' => $this->updated_at?->toISOString(),
+            'payment_method'              => $this->payment_method,
+            'status'                      => $this->status,
+            'gross_amount'                => $this->gross_amount,
+            'gateway_fee_amount'          => $this->gateway_fee_amount,
+            'platform_fee_amount'         => $this->platform_fee_amount,
+            'net_amount'                  => $this->net_amount,
+            'currency'                    => $this->currency,
+            'installments'                => $this->installments,
+            'authorized_at'               => $this->authorized_at?->toISOString(),
+            'paid_at'                     => $this->paid_at?->toISOString(),
+            'failed_at'                   => $this->failed_at?->toISOString(),
+            'cancelled_at'                => $this->cancelled_at?->toISOString(),
+            'expires_at'                  => $this->expires_at?->toISOString(),
+            'failure_code'                => $this->failure_code,
+            'failure_message'             => $this->failure_message,
+            'pix'                         => $this->pixData(),
+            'gateway_payload'             => $this->gateway_payload,
+            'metadata'                    => $this->metadata,
+            'created_at'                  => $this->created_at?->toISOString(),
+            'updated_at'                  => $this->updated_at?->toISOString(),
         ];
     }
 

+ 46 - 0
app/Http/Resources/PaymentSplitResource.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Resources;
+
+use App\Models\PaymentSplit;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class PaymentSplitResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'                                => $this->id,
+            'payment_id'                        => $this->payment_id,
+            'provider_id'                       => $this->provider_id,
+            'gateway_provider'                  => $this->gateway_provider,
+            'gateway_entity_reference'          => $this->gateway_entity_reference,
+            'gateway_entity_label'              => $this->gateway_entity_label,
+            'gateway_operation_reference'       => $this->gateway_operation_reference,
+            'gateway_operation_label'           => $this->gateway_operation_label,
+            'gateway_parent_reference'          => $this->gateway_parent_reference,
+            'gateway_parent_label'              => $this->gateway_parent_label,
+            'gateway_transfer_target_reference' => $this->gateway_transfer_target_reference,
+            'gateway_transfer_target_label'     => $this->gateway_transfer_target_label,
+            'status'                            => $this->status,
+            'gross_amount'                      => $this->gross_amount,
+            'gateway_fee_amount'                => $this->gateway_fee_amount,
+            'net_amount'                        => $this->net_amount,
+            'transferred_at'                    => $this->transferred_at?->toISOString(),
+            'failed_at'                         => $this->failed_at?->toISOString(),
+            'failure_code'                      => $this->failure_code,
+            'failure_message'                   => $this->failure_message,
+            'gateway_payload'                   => $this->gateway_payload,
+            'metadata'                          => $this->metadata,
+            'created_at'                        => $this->created_at?->toISOString(),
+            'updated_at'                        => $this->updated_at?->toISOString(),
+        ];
+    }
+
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 0 - 61
app/Http/Resources/PaymentTransferResource.php

@@ -1,61 +0,0 @@
-<?php
-
-namespace App\Http\Resources;
-
-use App\Models\PaymentTransfer;
-use Illuminate\Http\Request;
-use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
-use Illuminate\Http\Resources\Json\JsonResource;
-
-class PaymentTransferResource extends JsonResource
-{
-    /**
-     * Transform the resource into an array.
-     *
-     * @return array<string, mixed>
-     */
-    public function toArray(Request $request): array
-    {
-        return [
-            'id'          => $this->id,
-            'payment_id'  => $this->payment_id,
-            'provider_id' => $this->provider_id,
-
-            'gateway_provider'                  => $this->gateway_provider,
-            'gateway_entity_reference'          => $this->gateway_entity_reference,
-            'gateway_entity_label'              => $this->gateway_entity_label,
-            'gateway_operation_reference'       => $this->gateway_operation_reference,
-            'gateway_operation_label'           => $this->gateway_operation_label,
-            'gateway_parent_reference'          => $this->gateway_parent_reference,
-            'gateway_parent_label'              => $this->gateway_parent_label,
-            'gateway_transfer_target_reference' => $this->gateway_transfer_target_reference,
-            'gateway_transfer_target_label'     => $this->gateway_transfer_target_label,
-
-            'status' => $this->status,
-
-            'gross_amount'       => $this->gross_amount,
-            'gateway_fee_amount' => $this->gateway_fee_amount,
-            'net_amount'         => $this->net_amount,
-
-            'transferred_at' => $this->transferred_at?->toISOString(),
-            'failed_at'      => $this->failed_at?->toISOString(),
-
-            'failure_code'    => $this->failure_code,
-            'failure_message' => $this->failure_message,
-            'gateway_payload' => $this->gateway_payload,
-            'metadata'        => $this->metadata,
-
-            'created_at' => $this->created_at?->toISOString(),
-            'updated_at' => $this->updated_at?->toISOString(),
-        ];
-    }
-
-    /**
-     * @param  \Illuminate\Database\Eloquent\Collection<PaymentTransfer>  $resource
-     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<PaymentTransferResource>
-     */
-    public static function collection($resource): AnonymousResourceCollection
-    {
-        return parent::collection($resource);
-    }
-}

+ 35 - 0
app/Http/Resources/ProviderWithdrawalResource.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Http\Resources;
+
+use App\Models\ProviderWithdrawal;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ProviderWithdrawalResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id'                 => $this->id,
+            'provider_id'        => $this->provider_id,
+            'recipient_id'       => $this->recipient_id,
+            'transfer_id'        => $this->transfer_id,
+            'gross_amount'       => $this->gross_amount,
+            'gateway_fee_amount' => $this->gateway_fee_amount,
+            'net_amount'         => $this->net_amount,
+            'status'             => $this->status,
+            'type'               => $this->type,
+            'bank_account'       => $this->bank_account,
+            'completed_at'       => $this->completed_at?->toISOString(),
+            'failed_at'          => $this->failed_at?->toISOString(),
+            'created_at'         => $this->created_at?->toISOString(),
+        ];
+    }
+
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 17 - 9
app/Http/Resources/ReviewDetailResource.php

@@ -9,23 +9,27 @@ class ReviewDetailResource extends JsonResource
 {
     public function toArray(Request $request): array
     {
-        $schedule       = $this->schedule;
+        $schedule = $this->schedule;
+
         $customSchedule = $schedule?->customSchedule;
-        $address        = $schedule?->address;
+
+        $address = $schedule?->address;
 
         return [
-            'id'           => $this->id,
-            'origin'       => $this->origin,
-            'origin_id'    => $this->origin_id,
-            'stars'        => $this->stars,
-            'comment'      => $this->comment,
-            'created_at'   => $this->created_at?->format('Y-m-d H:i'),
+            'id'         => $this->id,
+            'origin'     => $this->origin,
+            'origin_id'  => $this->origin_id,
+            'stars'      => $this->stars,
+            'comment'    => $this->comment,
+            'created_at' => $this->created_at?->format('Y-m-d H:i'),
+
             'improvements' => $this->whenLoaded('improvements', function () {
                 return $this->improvements->map(fn ($imp) => [
                     'id'          => $imp->id,
                     'description' => $imp->description,
                 ]);
             }),
+
             'schedule' => $schedule ? [
                 'id'            => $schedule->id,
                 'schedule_type' => $schedule->schedule_type,
@@ -36,18 +40,21 @@ class ReviewDetailResource extends JsonResource
                 'period_type'   => $schedule->period_type,
                 'total_amount'  => $schedule->total_amount,
                 'code'          => $schedule->code,
-                'client'        => $schedule->client ? [
+
+                'client' => $schedule->client ? [
                     'id'    => $schedule->client->id,
                     'name'  => $schedule->client->user?->name,
                     'email' => $schedule->client->user?->email,
                     'phone' => $schedule->client->user?->phone,
                 ] : null,
+
                 'provider' => $schedule->provider ? [
                     'id'    => $schedule->provider->id,
                     'name'  => $schedule->provider->user?->name,
                     'email' => $schedule->provider->user?->email,
                     'phone' => $schedule->provider->user?->phone,
                 ] : null,
+
                 'address' => $address ? [
                     'street'       => $address->street,
                     'number'       => $address->number,
@@ -56,6 +63,7 @@ class ReviewDetailResource extends JsonResource
                     'city'         => $address->city?->name,
                     'state'        => $address->state?->name,
                 ] : null,
+
                 'custom_schedule' => $customSchedule ? [
                     'address_type' => $customSchedule->address_type,
                     'service_type' => $customSchedule->serviceType?->description,

+ 10 - 6
app/Http/Resources/ReviewResource.php

@@ -10,18 +10,21 @@ class ReviewResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id'             => $this->id,
-            'schedule_id'    => $this->schedule_id,
+            'id'          => $this->id,
+            'schedule_id' => $this->schedule_id,
+
             'schedule_label' => $this->schedule
                 ? ($this->schedule->id.' - '.
                    ($this->schedule->client?->user?->name ?? '?').' - '.
                    ($this->schedule->provider?->user?->name ?? '?').' - '.
                    ($this->schedule->date?->format('d/m/Y') ?? '?'))
                 : null,
-            'origin'               => $this->origin,
-            'origin_id'            => $this->origin_id,
-            'stars'                => $this->stars,
-            'comment'              => $this->comment,
+
+            'origin'    => $this->origin,
+            'origin_id' => $this->origin_id,
+            'stars'     => $this->stars,
+            'comment'   => $this->comment,
+
             'reviews_improvements' => $this->whenLoaded('reviewsImprovements', function () {
                 return $this->reviewsImprovements->map(function ($type) {
                     return [
@@ -30,6 +33,7 @@ class ReviewResource extends JsonResource
                     ];
                 });
             }),
+
             'created_at' => $this->created_at?->format('Y-m-d H:i'),
             'updated_at' => $this->updated_at?->format('Y-m-d H:i'),
         ];

+ 3 - 1
app/Http/Resources/ScheduleResource.php

@@ -16,12 +16,14 @@ class ScheduleResource extends JsonResource
             'provider_id'   => $this->provider_id,
             'provider_name' => $this->provider?->user?->name,
             'address_id'    => $this->address_id,
-            'address_full'  => $this->address ? implode(', ', array_filter([
+
+            'address_full' => $this->address ? implode(', ', array_filter([
                 $this->address->address,
                 $this->address->number ? "nº {$this->address->number}" : null,
                 $this->address->district,
                 $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,

+ 5 - 5
app/Jobs/FinishScheduleJob.php

@@ -27,7 +27,7 @@ class FinishScheduleJob implements ShouldQueue
     public function handle()
     {
         try {
-            $schedule     = Schedule::find($this->scheduleId);
+            $schedule = Schedule::find($this->scheduleId);
             $date_cleaned = Carbon::parse($schedule->date)->format('Y-m-d');
 
             if (! $schedule) {
@@ -77,13 +77,13 @@ class FinishScheduleJob implements ShouldQueue
                 'total_services' => $client->total_services + 1,
             ]);
 
-            $emailService  = new EmailService;
+            $emailService = new EmailService;
             $serviceAmount = (float) $schedule->total_amount;
-            $serviceFee    = $serviceAmount * 0.11;
-            $finalAmount   = $serviceAmount + $serviceFee;
+            $serviceFee = $serviceAmount * 0.11;
+            $finalAmount = $serviceAmount + $serviceFee;
 
             $email_cliente = User::find($schedule->client->user_id)->email;
-            $address       = Address::find($schedule->address_id);
+            $address = Address::find($schedule->address_id);
 
             $emailService->sendEmailReceipt(
                 email: $email_cliente,

+ 1 - 1
app/Jobs/StartScheduleJob.php

@@ -22,7 +22,7 @@ class StartScheduleJob implements ShouldQueue
     public function handle()
     {
         try {
-            $schedule     = Schedule::find($this->scheduleId);
+            $schedule = Schedule::find($this->scheduleId);
             $date_cleaned = Carbon::parse($schedule->date)->format('Y-m-d');
 
             if (! $schedule) {

+ 8 - 8
app/Models/Payment.php

@@ -13,13 +13,12 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property int|null $provider_id
  * @property int|null $client_payment_method_id
  * @property string $gateway_provider
- * @property string|null $gateway_code
  * @property string|null $gateway_entity_reference
  * @property string|null $gateway_entity_label
  * @property string|null $gateway_operation_reference
  * @property string|null $gateway_operation_label
  * @property string $payment_method
- * @property string $status
+ * @property \App\Enums\PaymentStatusEnum $status
  * @property numeric $gross_amount Valor bruto cobrado do cliente.
  * @property numeric $gateway_fee_amount Taxa cobrada pelo gateway nesta cobranca.
  * @property numeric $platform_fee_amount Taxa da plataforma (regra interna), sem impacto na formula de net_amount.
@@ -38,15 +37,15 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property string|null $gateway_code
  * @property-read \App\Models\Client $client
  * @property-read \App\Models\ClientPaymentMethod|null $clientPaymentMethod
  * @property-read \App\Models\Provider|null $provider
  * @property-read \App\Models\Schedule $schedule
- * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentTransfer> $transfers
- * @property-read int|null $transfers_count
+ * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentSplit> $splits
+ * @property-read int|null $splits_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Webhook> $webhooks
  * @property-read int|null $webhooks_count
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment onlyTrashed()
@@ -62,6 +61,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereFailedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereFailureCode($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereFailureMessage($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereGatewayCode($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereGatewayEntityLabel($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereGatewayEntityReference($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereGatewayFeeAmount($value)
@@ -83,7 +83,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Payment withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Payment extends Model
@@ -128,6 +127,7 @@ class Payment extends Model
         'platform_fee_amount' => 'decimal:2',
         'net_amount'          => 'decimal:2',
         'installments'        => 'integer',
+        'status'              => \App\Enums\PaymentStatusEnum::class,
         'authorized_at'       => 'datetime',
         'paid_at'             => 'datetime',
         'failed_at'           => 'datetime',
@@ -160,9 +160,9 @@ class Payment extends Model
         return $this->belongsTo(ClientPaymentMethod::class);
     }
 
-    public function transfers()
+    public function splits()
     {
-        return $this->hasMany(PaymentTransfer::class);
+        return $this->hasMany(PaymentSplit::class);
     }
 
     public function webhooks()

+ 44 - 34
app/Models/PaymentTransfer.php → app/Models/PaymentSplit.php

@@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property string|null $gateway_parent_label
  * @property string|null $gateway_transfer_target_reference
  * @property string|null $gateway_transfer_target_label
- * @property string $status
+ * @property \App\Enums\PaymentSplitStatusEnum $status
  * @property numeric $gross_amount Valor bruto destinado ao repasse do provider.
  * @property numeric $gateway_fee_amount Custo do gateway associado ao repasse.
  * @property numeric $net_amount Valor liquido do repasse: net_amount = gross_amount - gateway_fee_amount.
@@ -32,50 +32,54 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property int|null $provider_withdrawal_id
  * @property-read \App\Models\Payment $payment
  * @property-read \App\Models\Provider $provider
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer onlyTrashed()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer query()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereFailedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereFailureCode($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereFailureMessage($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayEntityLabel($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayEntityReference($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayFeeAmount($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayOperationLabel($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayOperationReference($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayParentLabel($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayParentReference($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayPayload($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayProvider($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayTransferTargetLabel($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGatewayTransferTargetReference($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereGrossAmount($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereMetadata($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereNetAmount($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer wherePaymentId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereProviderId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereStatus($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereTransferredAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer whereUpdatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer withTrashed(bool $withTrashed = true)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentTransfer withoutTrashed()
+ * @property-read \App\Models\ProviderWithdrawal|null $providerWithdrawal
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit query()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereFailedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereFailureCode($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereFailureMessage($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayEntityLabel($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayEntityReference($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayFeeAmount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayOperationLabel($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayOperationReference($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayParentLabel($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayParentReference($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayPayload($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayProvider($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayTransferTargetLabel($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGatewayTransferTargetReference($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereGrossAmount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereMetadata($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereNetAmount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit wherePaymentId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereProviderId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereProviderWithdrawalId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereStatus($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereTransferredAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit withTrashed(bool $withTrashed = true)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSplit withoutTrashed()
  * @mixin \Eloquent
  */
-class PaymentTransfer extends Model
+class PaymentSplit extends Model
 {
     use HasFactory, SoftDeletes;
 
-    protected $table = 'payment_transfers';
+    protected $table = 'payment_splits';
 
     protected $fillable = [
         'payment_id',
         'provider_id',
+        'provider_withdrawal_id',
         'gateway_provider',
         'gateway_entity_reference',
         'gateway_entity_label',
@@ -101,6 +105,7 @@ class PaymentTransfer extends Model
         'gross_amount'       => 'decimal:2',
         'gateway_fee_amount' => 'decimal:2',
         'net_amount'         => 'decimal:2',
+        'status'             => \App\Enums\PaymentSplitStatusEnum::class,
         'transferred_at'     => 'datetime',
         'failed_at'          => 'datetime',
         'gateway_payload'    => 'array',
@@ -119,4 +124,9 @@ class PaymentTransfer extends Model
     {
         return $this->belongsTo(Provider::class);
     }
+
+    public function providerWithdrawal()
+    {
+        return $this->belongsTo(ProviderWithdrawal::class);
+    }
 }

+ 109 - 0
app/Models/ProviderWithdrawal.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+/**
+ * @property int $id
+ * @property int $provider_id
+ * @property string $recipient_id
+ * @property string|null $transfer_id
+ * @property string $idempotency_key
+ * @property numeric $gross_amount
+ * @property numeric $gateway_fee_amount
+ * @property numeric $net_amount
+ * @property \App\Enums\ProviderWithdrawalStatusEnum $status
+ * @property string|null $type
+ * @property array<array-key, mixed>|null $bank_account
+ * @property string|null $bank_response
+ * @property array<array-key, mixed>|null $gateway_payload
+ * @property \Illuminate\Support\Carbon|null $completed_at
+ * @property \Illuminate\Support\Carbon|null $failed_at
+ * @property array<array-key, mixed>|null $metadata
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentSplit> $paymentSplits
+ * @property-read int|null $payment_splits_count
+ * @property-read \App\Models\Provider $provider
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal query()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereBankAccount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereBankResponse($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereCompletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereFailedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereGatewayFeeAmount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereGatewayPayload($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereGrossAmount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereIdempotencyKey($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereMetadata($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereNetAmount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereProviderId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereRecipientId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereStatus($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereTransferId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereType($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal withTrashed(bool $withTrashed = true)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWithdrawal withoutTrashed()
+ * @mixin \Eloquent
+ */
+class ProviderWithdrawal extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $table = 'provider_withdrawals';
+
+    protected $fillable = [
+        'provider_id',
+        'recipient_id',
+        'transfer_id',
+        'idempotency_key',
+        'gross_amount',
+        'gateway_fee_amount',
+        'net_amount',
+        'status',
+        'type',
+        'bank_account',
+        'bank_response',
+        'gateway_payload',
+        'completed_at',
+        'failed_at',
+        'metadata',
+    ];
+
+    protected function casts(): array
+    {
+        return [
+            'gross_amount'       => 'decimal:2',
+            'gateway_fee_amount' => 'decimal:2',
+            'net_amount'         => 'decimal:2',
+            'status'             => \App\Enums\ProviderWithdrawalStatusEnum::class,
+            'bank_account'       => 'array',
+            'gateway_payload'    => 'array',
+            'metadata'           => 'array',
+            'completed_at'       => 'datetime',
+            'failed_at'          => 'datetime',
+        ];
+    }
+
+    public function provider(): BelongsTo
+    {
+        return $this->belongsTo(Provider::class);
+    }
+
+    public function paymentSplits(): HasMany
+    {
+        return $this->hasMany(PaymentSplit::class);
+    }
+}

+ 6 - 6
app/Rules/ScheduleBusinessRules.php

@@ -34,7 +34,7 @@ class ScheduleBusinessRules
         $date = Carbon::parse($date);
 
         $weekStart = $date->copy()->startOfWeek(Carbon::SUNDAY);
-        $weekEnd   = $date->copy()->endOfWeek(Carbon::SATURDAY);
+        $weekEnd = $date->copy()->endOfWeek(Carbon::SATURDAY);
 
         $weeklySchedulesCount = Schedule::where('client_id', $clientId)
             ->where('provider_id', $providerId)
@@ -125,30 +125,30 @@ class ScheduleBusinessRules
             throw new \Exception(__('validation.custom.schedule.invalid_price_range'));
         }
 
-        $provider               = Provider::find($provider_id);
+        $provider = Provider::find($provider_id);
         $min_price_proportional = 0;
         $max_price_proportional = 0;
 
         $provider_price_period = 0;
         switch ($period_type) {
             case '2': // 2 horas
-                $provider_price_period  = $provider->daily_price_2h;
+                $provider_price_period = $provider->daily_price_2h;
                 $min_price_proportional = $min_price * 0.30;
                 $max_price_proportional = $max_price * 0.30;
                 break;
             case '4': // 4 horas
-                $provider_price_period  = $provider->daily_price_4h;
+                $provider_price_period = $provider->daily_price_4h;
                 $min_price_proportional = $min_price * 0.55;
                 $max_price_proportional = $max_price * 0.55;
                 break;
             case '6': // 6 horas
-                $provider_price_period  = $provider->daily_price_6h;
+                $provider_price_period = $provider->daily_price_6h;
                 $min_price_proportional = $min_price * 0.85;
                 $max_price_proportional = $max_price * 0.85;
 
                 break;
             case '8': // 8 horas
-                $provider_price_period  = $provider->daily_price_8h;
+                $provider_price_period = $provider->daily_price_8h;
                 $min_price_proportional = $min_price;
                 $max_price_proportional = $max_price;
 

+ 7 - 7
app/Services/AuthService.php

@@ -36,7 +36,7 @@ class AuthService
         // $user = User::where('email', $email)->first();
         $deviceId = Str::uuid()->toString();
 
-        $accessToken  = $user->createAccessToken($deviceId);
+        $accessToken = $user->createAccessToken($deviceId);
         $refreshToken = $user->createRefreshToken($deviceId);
 
         return [
@@ -92,7 +92,7 @@ class AuthService
         }
 
         $tokenName = $user->currentAccessToken()->name;
-        $deviceId  = Str::afterLast($tokenName, '_');
+        $deviceId = Str::afterLast($tokenName, '_');
 
         $user
             ->tokens()
@@ -112,7 +112,7 @@ class AuthService
         ): array {
             $tokenModel->update(['expires_at' => Carbon::now()]);
 
-            $accessToken  = $user->createAccessToken($deviceId);
+            $accessToken = $user->createAccessToken($deviceId);
             $refreshToken = $user->createRefreshToken($deviceId);
 
             return [
@@ -285,7 +285,7 @@ class AuthService
     {
         $email = $data['email'] ?? null;
         $phone = $data['phone'] ?? null;
-        $code  = $data['code'] ?? '';
+        $code = $data['code'] ?? '';
 
         $user = User::where(function ($query) use ($email, $phone) {
             $query->when($email, fn ($q) => $q->where('email', $email))
@@ -309,7 +309,7 @@ class AuthService
     {
         $email = $data['email'] ?? null;
         $phone = $data['phone'] ?? null;
-        $code  = $data['code'] ?? '';
+        $code = $data['code'] ?? '';
 
         $user = User::where(function ($query) use ($email, $phone) {
             $query->when($email, fn ($q) => $q->where('email', $email))
@@ -345,7 +345,7 @@ class AuthService
     {
         $email = $data['email'] ?? null;
         $phone = $data['phone'] ?? null;
-        $code  = $data['code'] ?? '';
+        $code = $data['code'] ?? '';
 
         $user = User::where(function ($query) use ($email, $phone) {
             $query->when($email, function ($q) use ($email) {
@@ -383,7 +383,7 @@ class AuthService
 
         $deviceId = Str::uuid()->toString();
 
-        $accessToken  = $user->createAccessTokenApp($deviceId);
+        $accessToken = $user->createAccessTokenApp($deviceId);
         $refreshToken = $user->createRefreshTokenApp($deviceId);
 
         $user->validated_code = true;

+ 1 - 1
app/Services/ClientService.php

@@ -108,7 +108,7 @@ class ClientService
                     $city = City::where('name', $data['city'])->where('state_id', $state->id)->first();
 
                     $addressData['state_id'] = $state->id;
-                    $addressData['city_id']  = $city?->id;
+                    $addressData['city_id'] = $city?->id;
                 }
             }
 

+ 11 - 11
app/Services/CustomScheduleService.php

@@ -48,7 +48,7 @@ class CustomScheduleService
         DB::beginTransaction();
 
         try {
-            $quantity      = $data['quantity'] ?? 1;
+            $quantity = $data['quantity'] ?? 1;
             $specialityIds = $data['speciality_ids'] ?? [];
 
             $createdCustomSchedules = [];
@@ -116,7 +116,7 @@ class CustomScheduleService
 
         try {
             $customSchedule = CustomSchedule::findOrFail($id);
-            $schedule       = $customSchedule->schedule;
+            $schedule = $customSchedule->schedule;
 
             $scheduleUpdateData = [];
 
@@ -208,7 +208,7 @@ class CustomScheduleService
 
         try {
             $customSchedule = CustomSchedule::findOrFail($id);
-            $schedule       = $customSchedule->schedule;
+            $schedule = $customSchedule->schedule;
 
             CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id)->delete();
 
@@ -408,15 +408,15 @@ class CustomScheduleService
 
     private function checkProviderAvailability($providerId, $schedule)
     {
-        $client_id   = $schedule->client_id;
+        $client_id = $schedule->client_id;
         $provider_id = $providerId;
 
-        $date        = Carbon::parse($schedule->date);
-        $dayOfWeek   = $date->dayOfWeek; // 0-6
-        $startTime   = $schedule->start_time;
-        $endTime     = $schedule->end_time;
-        $date_ymd    = $date->format('Y-m-d');
-        $period      = $startTime < '13:00:00' ? 'morning' : 'afternoon';
+        $date = Carbon::parse($schedule->date);
+        $dayOfWeek = $date->dayOfWeek; // 0-6
+        $startTime = $schedule->start_time;
+        $endTime = $schedule->end_time;
+        $date_ymd = $date->format('Y-m-d');
+        $period = $startTime < '13:00:00' ? 'morning' : 'afternoon';
         $period_type = $schedule->period_type; // 2,4,6,8
 
         // bloqueio 2 schedules por semana para o mesmo client e provider
@@ -489,7 +489,7 @@ class CustomScheduleService
 
     public function getProvidersProposalsAndOpportunities($providerId)
     {
-        $proposals     = $this->getProviderProposals($providerId);
+        $proposals = $this->getProviderProposals($providerId);
         $opportunities = $this->formatCustomSchedules($this->getAvailableOpportunities($providerId));
 
         return [

+ 1 - 1
app/Services/DashboardService.php

@@ -113,7 +113,7 @@ class DashboardService
             ->limit(5)
             ->get();
 
-        $blockedProviderIds       = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
+        $blockedProviderIds = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
         $providersWithWorkingDays = ScheduleBusinessRules::getProviderIdsWithWorkingDays();
 
         $clientPrimaryAddress = Address::where('source', 'client')

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

@@ -16,7 +16,7 @@ trait SendsPagarmeRequests
         string $errorMessage,
         array|PagarmeData $payload,
     ): array {
-        $payload  = $payload instanceof PagarmeData ? $payload->toArray() : $payload;
+        $payload = $payload instanceof PagarmeData ? $payload->toArray() : $payload;
         $endpoint = $this->pagarmeUrl($path);
 
         try {

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

@@ -99,7 +99,7 @@ class PagarmeCardService
         }
 
         $state = $address->state?->code ?? $address->city?->state?->code;
-        $city  = $address->city?->name;
+        $city = $address->city?->name;
 
         $line1 = implode(', ', array_filter([
             $address->number ?: 'S/N',

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

@@ -25,7 +25,7 @@ class PagarmeCustomerService
 
         $client->loadMissing('user');
 
-        $name  = $client->user?->name ?? $data['name'] ?? 'Cliente';
+        $name = $client->user?->name ?? $data['name'] ?? 'Cliente';
         $email = $client->user?->email ?? $data['email'] ?? null;
 
         $document = $this->sanitizeDigits($client->document ?? $data['document'] ?? null);
@@ -43,7 +43,7 @@ class PagarmeCustomerService
         }
 
         $address = $this->buildAddress($data);
-        $phones  = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null);
+        $phones = $this->buildPhones($client->user?->phone ?? $data['phone'] ?? null);
 
         $code = $this->ensureCustomerCode($client);
 
@@ -137,12 +137,12 @@ class PagarmeCustomerService
     {
         $zipCode = $this->sanitizeDigits($data['zip_code'] ?? null);
 
-        $street       = (string) ($data['address'] ?? '');
-        $number       = (string) ($data['number'] ?? '0');
+        $street = (string) ($data['address'] ?? '');
+        $number = (string) ($data['number'] ?? '0');
         $neighborhood = (string) ($data['district'] ?? '');
 
-        $city    = (string) ($data['city'] ?? '');
-        $state   = (string) ($data['state'] ?? '');
+        $city = (string) ($data['city'] ?? '');
+        $state = (string) ($data['state'] ?? '');
         $country = (string) ($data['country'] ?? 'BR');
 
         $complement = (string) ($data['complement'] ?? '');
@@ -173,11 +173,11 @@ class PagarmeCustomerService
         $countryCode = '55';
 
         $areaCode = substr($digits, 0, 2);
-        $number   = substr($digits, 2);
+        $number = substr($digits, 2);
 
         if (strlen($digits) <= 2) {
             $areaCode = '';
-            $number   = $digits;
+            $number = $digits;
         }
 
         return new PagarmeCustomerPhonesRequestData(

+ 15 - 14
app/Services/Pagarme/PagarmePaymentService.php

@@ -4,8 +4,9 @@ namespace App\Services\Pagarme;
 
 use App\Data\Pagarme\Request\PagarmeOrderRequestData\PagarmeOrderRequestData;
 use App\Data\Pagarme\Response\PagarmeOrderResponseData\PagarmeOrderResponseData;
+use App\Enums\PaymentStatusEnum;
 use App\Models\Payment;
-use App\Models\PaymentTransfer;
+use App\Models\PaymentSplit;
 use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
 use Illuminate\Support\Collection;
 use Illuminate\Support\Str;
@@ -85,7 +86,7 @@ class PagarmePaymentService
             throw new \InvalidArgumentException('payment_method deve ser credit_card ou pix.');
         }
 
-        $customerIdPayload     = $options['customer_id'] ?? null;
+        $customerIdPayload = $options['customer_id'] ?? null;
         $customerObjectPayload = $this->filterFilledRecursive($customer);
 
         if (! $this->filled($customerIdPayload) && empty($customerObjectPayload)) {
@@ -174,11 +175,11 @@ class PagarmePaymentService
     public function buildSplitFromTransfers(Collection $transfers): array
     {
         return $transfers
-            ->filter(fn (PaymentTransfer $transfer) => ! empty($transfer->gateway_transfer_target_reference))
-            ->map(function (PaymentTransfer $transfer) {
+            ->filter(fn (PaymentSplit $split) => ! empty($split->gateway_transfer_target_reference))
+            ->map(function (PaymentSplit $split) {
                 return [
-                    'amount'       => $this->toGatewayAmountInCents((float) $transfer->gross_amount),
-                    'recipient_id' => $transfer->gateway_transfer_target_reference,
+                    'amount'       => $this->toGatewayAmountInCents((float) $split->gross_amount),
+                    'recipient_id' => $split->gateway_transfer_target_reference,
                     'type'         => 'flat',
 
                     'options' => [
@@ -338,21 +339,21 @@ class PagarmePaymentService
         return $message;
     }
 
-    private function mapPaymentStatus(?string $chargeStatus, ?string $transactionStatus): string
+    private function mapPaymentStatus(?string $chargeStatus, ?string $transactionStatus): PaymentStatusEnum
     {
         $status = strtolower((string) ($transactionStatus ?: $chargeStatus));
 
         return match ($status) {
-            'captured', 'paid', 'overpaid' => 'paid',
-            'authorized_pending_capture', 'waiting_capture' => 'authorized',
-            'pending', 'waiting_payment' => 'pending',
-            'processing' => 'processing',
+            'captured', 'paid', 'overpaid' => PaymentStatusEnum::PAID,
+            'authorized_pending_capture', 'waiting_capture' => PaymentStatusEnum::AUTHORIZED,
+            'pending', 'waiting_payment' => PaymentStatusEnum::PENDING,
+            'processing' => PaymentStatusEnum::PROCESSING,
             'not_authorized', 'with_error', 'failed',
-            'underpaid', 'chargedback' => 'failed',
+            'underpaid', 'chargedback' => PaymentStatusEnum::FAILED,
             'voided', 'partial_void', 'canceled',
             'cancelled', 'refunded', 'partial_refunded',
-            'partial_canceled' => 'cancelled',
-            default            => 'pending',
+            'partial_canceled' => PaymentStatusEnum::CANCELLED,
+            default            => PaymentStatusEnum::PENDING,
         };
     }
 

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

@@ -28,15 +28,15 @@ class PagarmeRecipientService
             return $provider->recipient_id;
         }
 
-        $bankAccountData                = $data['recipient_default_bank_account'];
+        $bankAccountData = $data['recipient_default_bank_account'];
         $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']);
-        $metadata                       = $data['recipient_metadata'] ?? [];
-        $paymentMode                    = $data['recipient_payment_mode'];
-        $recipientType                  = $data['recipient_type'] ?? 'individual';
+        $metadata = $data['recipient_metadata'] ?? [];
+        $paymentMode = $data['recipient_payment_mode'];
+        $recipientType = $data['recipient_type'] ?? 'individual';
 
-        $addressParts  = $this->extractAddressParts($data);
+        $addressParts = $this->extractAddressParts($data);
         $monthlyIncome = isset($data['monthly_income']) ? (int) $data['monthly_income'] : 1000;
-        $occupation    = $data['professional_occupation'] ?? 'autonomo';
+        $occupation = $data['professional_occupation'] ?? 'autonomo';
 
         $recipientCode = $this->ensureRecipientCode($provider);
 
@@ -126,7 +126,7 @@ class PagarmeRecipientService
             throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para atualizar a conta bancaria.');
         }
 
-        $bankAccountData                = $this->normalizeBankAccountPayload($bankAccountData);
+        $bankAccountData = $this->normalizeBankAccountPayload($bankAccountData);
         $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']);
 
         $payload = PagarmeBankAccountUpdateRequestData::fromArray($bankAccountData);
@@ -207,10 +207,10 @@ class PagarmeRecipientService
 
         $streetSegment = $segments[0] ?? '';
 
-        $streetNumber   = $data['number'] ?? null;
-        $neighborhood   = $data['district'] ?? null;
+        $streetNumber = $data['number'] ?? null;
+        $neighborhood = $data['district'] ?? null;
         $referencePoint = $data['reference_point'] ?? null;
-        $complementary  = $data['complement'] ?? null;
+        $complementary = $data['complement'] ?? null;
 
         if ($streetNumber === null) {
             preg_match('/^(\d+)/', $streetSegment, $matches);
@@ -289,7 +289,7 @@ class PagarmeRecipientService
 
         if (count($parts) >= 3) {
             $firstName = array_shift($parts);
-            $lastName  = array_pop($parts);
+            $lastName = array_pop($parts);
 
             $initials = array_map(
                 static fn (string $part): string => Str::upper(Str::substr($part, 0, 1)),

+ 36 - 0
app/Services/Pagarme/PagarmeTransferService.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Services\Pagarme;
+
+use App\Data\Pagarme\Request\PagarmeTransferRequestData;
+use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
+
+class PagarmeTransferService
+{
+    use SendsPagarmeRequests;
+
+    public function createTransfer(int $amountInCents, string $recipientId, string $idempotencyKey): array
+    {
+        return $this->pagarmeRequest(
+            method: 'POST',
+            path: '/transfers',
+            payload: new PagarmeTransferRequestData(
+                amount: $amountInCents,
+                recipientId: $recipientId,
+            ),
+            idempotencyKey: $idempotencyKey,
+            errorMessage: 'Erro ao criar transferencia (saque) no Pagar.me.',
+        );
+    }
+
+    public function getTransfer(string $transferId): array
+    {
+        return $this->pagarmeRequest(
+            method: 'GET',
+            path: "/transfers/{$transferId}",
+            payload: [],
+            idempotencyKey: "get-transfer-{$transferId}",
+            errorMessage: 'Erro ao consultar transferencia no Pagar.me.',
+        );
+    }
+}

+ 26 - 20
app/Services/PaymentService.php

@@ -2,15 +2,16 @@
 
 namespace App\Services;
 
+use App\Enums\PaymentSplitStatusEnum;
+use App\Enums\PaymentStatusEnum;
 use App\Models\Address;
 use App\Models\ClientPaymentMethod;
 use App\Models\Payment;
-use App\Models\PaymentTransfer;
+use App\Models\PaymentSplit;
 use App\Models\Schedule;
 use App\Services\Pagarme\PagarmePaymentService;
 use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Str;
 
 class PaymentService
@@ -92,19 +93,24 @@ class PaymentService
 
         $existingPayment = Payment::query()
             ->where('schedule_id', $schedule->id)
-            ->whereIn('status', ['pending', 'processing', 'authorized', 'paid'])
+            ->whereIn('status', [
+                PaymentStatusEnum::PENDING->value,
+                PaymentStatusEnum::PROCESSING->value,
+                PaymentStatusEnum::AUTHORIZED->value,
+                PaymentStatusEnum::PAID->value,
+            ])
             ->latest('id')
             ->first();
 
         if ($existingPayment) {
             if ($this->isIncompleteGatewayPayment($existingPayment)) {
                 $existingPayment->forceFill([
-                    'status'          => 'failed',
+                    'status'          => PaymentStatusEnum::FAILED,
                     'failed_at'       => now(),
                     'failure_message' => 'Pagamento pendente sem retorno do gateway.',
                 ])->save();
             } else {
-                if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== 'paid') {
+                if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
                     throw new \InvalidArgumentException('Ja existe um pagamento em andamento para este agendamento.');
                 }
 
@@ -142,9 +148,9 @@ class PaymentService
         }
 
         $serviceAmount = (float) $schedule->total_amount;
-        $platformFee   = round($serviceAmount * 0.11, 2);
-        $grossAmount   = round($serviceAmount + $platformFee, 2);
-        $items         = $this->buildOrderItems($schedule, $grossAmount);
+        $platformFee = round($serviceAmount * 0.11, 2);
+        $grossAmount = round($serviceAmount + $platformFee, 2);
+        $items = $this->buildOrderItems($schedule, $grossAmount);
 
         $this->ensureCustomerPhoneForPayment($schedule, $options);
 
@@ -164,7 +170,7 @@ class PaymentService
             'gateway_provider'         => 'pagarme',
             'gateway_code'             => 'payment-'.(string) Str::uuid(),
             'payment_method'           => $paymentMethod,
-            'status'                   => 'pending',
+            'status'                   => PaymentStatusEnum::PENDING,
             'gross_amount'             => $grossAmount,
             'gateway_fee_amount'       => 0,
             'platform_fee_amount'      => $platformFee,
@@ -179,13 +185,13 @@ class PaymentService
             ],
         ]);
 
-        $transfer = PaymentTransfer::create([
+        $transfer = PaymentSplit::create([
             'payment_id'                        => $payment->id,
             'provider_id'                       => $schedule->provider_id,
             'gateway_provider'                  => 'pagarme',
             'gateway_transfer_target_reference' => $schedule->provider->recipient_id,
             'gateway_transfer_target_label'     => 'recipient',
-            'status'                            => 'pending',
+            'status'                            => PaymentSplitStatusEnum::PENDING,
             'gross_amount'                      => $serviceAmount,
             'gateway_fee_amount'                => 0,
             'net_amount'                        => $serviceAmount,
@@ -251,12 +257,12 @@ class PaymentService
                 );
         } catch (\Throwable $e) {
             $payment->forceFill([
-                'status'          => 'failed',
+                'status'          => PaymentStatusEnum::FAILED,
                 'failed_at'       => now(),
                 'failure_message' => $e->getMessage(),
             ])->save();
 
-            $transfer->update(['status' => 'failed']);
+            $transfer->update(['status' => PaymentSplitStatusEnum::FAILED]);
 
             throw $e;
         }
@@ -288,8 +294,8 @@ class PaymentService
         array $options = [],
         bool $requirePhone = true
     ): array {
-        $client  = $schedule->client;
-        $user    = $client->user()->first(['id', 'name', 'email', 'phone']);
+        $client = $schedule->client;
+        $user = $client->user()->first(['id', 'name', 'email', 'phone']);
         $address = Address::with(['city.state', 'state'])->find($schedule->address_id);
 
         foreach ([
@@ -311,8 +317,8 @@ class PaymentService
         $phone = $this->buildPhonePayload($user->phone)
             ?: $this->buildPhonePayload($options['phone'] ?? null);
 
-        $state   = $address->state?->code ?? $address->city?->state?->code;
-        $city    = $address->city?->name;
+        $state = $address->state?->code ?? $address->city?->state?->code;
+        $city = $address->city?->name;
         $zipCode = $this->digits($address->zip_code);
 
         $line1 = implode(', ', array_filter([
@@ -382,7 +388,7 @@ class PaymentService
     private function ensureCustomerPhoneForPayment(Schedule $schedule, array $options = []): void
     {
         $userPhone = $schedule->client?->user?->phone;
-        $phone     = $this->buildPhonePayload($userPhone)
+        $phone = $this->buildPhonePayload($userPhone)
             ?: $this->buildPhonePayload($options['phone'] ?? null);
 
         if ($phone) {
@@ -401,7 +407,7 @@ class PaymentService
 
     private function isIncompleteGatewayPayment(Payment $payment): bool
     {
-        return $payment->status === 'pending'
+        return $payment->status === PaymentStatusEnum::PENDING
             && empty($payment->gateway_entity_reference)
             && empty($payment->gateway_operation_reference)
             && empty($payment->gateway_payload);
@@ -422,7 +428,7 @@ class PaymentService
 
     public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void
     {
-        if ($payment->status !== 'paid' || $schedule->status === 'paid') {
+        if ($payment->status !== PaymentStatusEnum::PAID || $schedule->status === 'paid') {
             return;
         }
 

+ 8 - 10
app/Services/PaymentTransferService.php → app/Services/PaymentSplitService.php

@@ -2,29 +2,29 @@
 
 namespace App\Services;
 
-use App\Models\PaymentTransfer;
+use App\Models\PaymentSplit;
 use Illuminate\Database\Eloquent\Collection;
 
-class PaymentTransferService
+class PaymentSplitService
 {
     public function getAll(): Collection
     {
-        return PaymentTransfer::query()
+        return PaymentSplit::query()
             ->orderBy('created_at', 'desc')
             ->get();
     }
 
-    public function findById(int $id): ?PaymentTransfer
+    public function findById(int $id): ?PaymentSplit
     {
-        return PaymentTransfer::find($id);
+        return PaymentSplit::find($id);
     }
 
-    public function create(array $data): PaymentTransfer
+    public function create(array $data): PaymentSplit
     {
-        return PaymentTransfer::create($data);
+        return PaymentSplit::create($data);
     }
 
-    public function update(int $id, array $data): ?PaymentTransfer
+    public function update(int $id, array $data): ?PaymentSplit
     {
         $model = $this->findById($id);
 
@@ -47,6 +47,4 @@ class PaymentTransferService
 
         return $model->delete();
     }
-
-    // Add custom business logic methods here
 }

+ 18 - 18
app/Services/ProviderService.php

@@ -127,7 +127,7 @@ class ProviderService
 
             $email = $data['email'] ?? null;
             $phone = $data['phone'] ?? null;
-            $code  = $data['code'] ?? null;
+            $code = $data['code'] ?? null;
 
             $user = User::query()
                 ->where('type', UserTypeEnum::PROVIDER->value)
@@ -162,9 +162,9 @@ class ProviderService
 
             $provider = new Provider;
 
-            $provider->user_id    = $user->id;
-            $provider->rg         = $data['rg'] ?? null;
-            $provider->document   = $this->sanitizeDigits($data['document'] ?? null);
+            $provider->user_id = $user->id;
+            $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;
@@ -174,9 +174,9 @@ class ProviderService
 
             $provider->approval_status = ApprovalStatusEnum::PENDING->value;
 
-            $provider->selfie_media_base64         = $data['selfie_base64'] ?? null;
+            $provider->selfie_media_base64 = $data['selfie_base64'] ?? null;
             $provider->document_front_media_base64 = $data['document_front_base64'] ?? null;
-            $provider->document_back_media_base64  = $data['document_back_base64'] ?? null;
+            $provider->document_back_media_base64 = $data['document_back_base64'] ?? null;
 
             $provider->save();
             $provider->refresh();
@@ -210,7 +210,7 @@ class ProviderService
     private function createProviderAddress(int $providerId, array $data): void
     {
         $state = null;
-        $city  = null;
+        $city = null;
 
         if (! empty($data['state'])) {
             $state = State::query()
@@ -231,17 +231,17 @@ class ProviderService
 
         $address = new Address;
 
-        $address->source         = 'provider';
-        $address->source_id      = $providerId;
-        $address->zip_code       = $this->sanitizeDigits($data['zip_code'] ?? null);
-        $address->address        = $data['address'] ?? null;
+        $address->source = 'provider';
+        $address->source_id = $providerId;
+        $address->zip_code = $this->sanitizeDigits($data['zip_code'] ?? null);
+        $address->address = $data['address'] ?? null;
         $address->has_complement = (bool) ($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->state_id       = $state?->id;
-        $address->city_id        = $city?->id;
+        $address->complement = $data['complement'] ?? null;
+        $address->nickname = $data['nickname'] ?? null;
+        $address->instructions = $data['instructions'] ?? null;
+        $address->address_type = $data['address_type'] ?? 'home';
+        $address->state_id = $state?->id;
+        $address->city_id = $city?->id;
 
         $address->save();
     }
@@ -267,7 +267,7 @@ class ProviderService
         $seen = [];
 
         foreach ($workingDays as $workingDay) {
-            $day    = (int) ($workingDay['day'] ?? -1);
+            $day = (int) ($workingDay['day'] ?? -1);
             $period = $workingDay['period'] ?? null;
 
             if ($day < 0 || $day > 6 || ! in_array($period, ['morning', 'afternoon'], true)) {

+ 197 - 0
app/Services/ProviderWithdrawalService.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace App\Services;
+
+use App\Data\Pagarme\Response\PagarmeTransferResponseData;
+use App\Enums\PaymentStatusEnum;
+use App\Enums\ProviderWithdrawalStatusEnum;
+use App\Models\PaymentSplit;
+use App\Models\Provider;
+use App\Models\ProviderWithdrawal;
+use App\Services\Pagarme\PagarmeTransferService;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class ProviderWithdrawalService
+{
+    public function __construct(
+        private readonly PagarmeTransferService $pagarmeTransfer,
+    ) {}
+
+    public function getAvailableBalance(Provider $provider): float
+    {
+        $earnings = (float) PaymentSplit::query()
+            ->where('provider_id', $provider->id)
+            ->whereHas('payment', fn ($q) => $q->where('status', PaymentStatusEnum::PAID))
+            ->whereHas('payment.schedule', fn ($q) => $q->where('status', 'finished'))
+            ->sum('gross_amount');
+
+        $withdrawn = (float) ProviderWithdrawal::query()
+            ->where('provider_id', $provider->id)
+            ->whereIn('status', [
+                ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
+                ProviderWithdrawalStatusEnum::PROCESSING,
+                ProviderWithdrawalStatusEnum::TRANSFERRED,
+            ])
+            ->sum('gross_amount');
+
+        return max(0, $earnings - $withdrawn);
+    }
+
+    public function getPendingBalance(Provider $provider): float
+    {
+        return (float) PaymentSplit::query()
+            ->where('provider_id', $provider->id)
+            ->whereHas('payment', fn ($q) => $q->where('status', PaymentStatusEnum::PAID))
+            ->whereHas('payment.schedule', fn ($q) => $q->whereNotIn('status', ['finished', 'cancelled', 'rejected']))
+            ->sum('gross_amount');
+    }
+
+    public function requestWithdrawal(Provider $provider): ProviderWithdrawal
+    {
+        if (empty($provider->recipient_id)) {
+            throw new \RuntimeException('Prestador nao possui recipient_id no Pagar.me.');
+        }
+
+        return DB::transaction(function () use ($provider) {
+            Provider::query()
+                ->where('id', $provider->id)
+                ->lockForUpdate()
+                ->first();
+
+            $pending = ProviderWithdrawal::query()
+                ->where('provider_id', $provider->id)
+                ->whereIn('status', [
+                    ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
+                    ProviderWithdrawalStatusEnum::PROCESSING,
+                ])
+                ->exists();
+
+            if ($pending) {
+                throw new \RuntimeException('Voce ja possui um saque em andamento.');
+            }
+
+            $available = $this->getAvailableBalance($provider);
+
+            if ($available <= 0) {
+                throw new \RuntimeException('Saldo disponivel insuficiente para saque.');
+            }
+
+            $amountInCents = (int) round($available * 100);
+
+            $idempotencyKey = sprintf('withdrawal-%d-%s', $provider->id, now()->timestamp);
+
+            $bankAccount = $provider->recipient_default_bank_account;
+
+            $withdrawal = ProviderWithdrawal::create([
+                'provider_id'     => $provider->id,
+                'recipient_id'    => $provider->recipient_id,
+                'idempotency_key' => $idempotencyKey,
+                'gross_amount'    => $available,
+                'net_amount'      => $available,
+                'status'          => ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
+                'bank_account'    => $bankAccount,
+                'metadata'        => [
+                    'provider_id' => $provider->id,
+                    'amount'      => $available,
+                ],
+            ]);
+
+            try {
+                $response = $this->pagarmeTransfer->createTransfer(
+                    $amountInCents,
+                    $provider->recipient_id,
+                    $idempotencyKey,
+                );
+
+                $transfer = PagarmeTransferResponseData::fromArray($response);
+
+                $withdrawal->forceFill([
+                    'transfer_id'        => $transfer->id(),
+                    'status'             => $transfer->status() ?? ProviderWithdrawalStatusEnum::PROCESSING->value,
+                    'type'               => $transfer->type,
+                    'gateway_fee_amount' => $transfer->fee ?? 0,
+                    'net_amount'         => max(0, $available - ($transfer->fee ?? 0) / 100),
+                    'gateway_payload'    => $response,
+                ])->save();
+
+                PaymentSplit::query()
+                    ->where('provider_id', $provider->id)
+                    ->whereNull('provider_withdrawal_id')
+                    ->whereHas('payment', fn ($q) => $q->where('status', PaymentStatusEnum::PAID))
+                    ->whereHas('payment.schedule', fn ($q) => $q->where('status', 'finished'))
+                    ->update(['provider_withdrawal_id' => $withdrawal->id]);
+
+                return $withdrawal->fresh();
+            } catch (\Throwable $e) {
+                $withdrawal->forceFill([
+                    'status'       => ProviderWithdrawalStatusEnum::FAILED,
+                    'failed_at'    => now(),
+                    'bank_response' => $e->getMessage(),
+                ])->save();
+
+                throw $e;
+            }
+        });
+    }
+
+    public function getWithdrawals(Provider $provider): Collection
+    {
+        return ProviderWithdrawal::query()
+            ->where('provider_id', $provider->id)
+            ->orderBy('created_at', 'desc')
+            ->get();
+    }
+
+    public function getWithdrawal(int $id, Provider $provider): ProviderWithdrawal
+    {
+        return ProviderWithdrawal::query()
+            ->where('id', $id)
+            ->where('provider_id', $provider->id)
+            ->firstOrFail();
+    }
+
+    public function handleTransferWebhook(array $data): void
+    {
+        $transferId = $data['id'] ?? null;
+
+        if (empty($transferId)) {
+            return;
+        }
+
+        $withdrawal = ProviderWithdrawal::query()
+            ->where('transfer_id', $transferId)
+            ->first();
+
+        if (! $withdrawal) {
+            Log::channel('pagarme')->warning('ProviderWithdrawal not found for transfer webhook', [
+                'transfer_id' => $transferId,
+            ]);
+
+            return;
+        }
+
+        $status = $data['status'] ?? null;
+
+        if ($status === ProviderWithdrawalStatusEnum::TRANSFERRED->value) {
+            $withdrawal->forceFill([
+                'status'       => ProviderWithdrawalStatusEnum::TRANSFERRED,
+                'completed_at' => now(),
+            ])->save();
+        } elseif (in_array($status, [ProviderWithdrawalStatusEnum::FAILED->value, ProviderWithdrawalStatusEnum::CANCELED->value], true)) {
+            $withdrawal->paymentSplits()
+                ->update(['provider_withdrawal_id' => null]);
+
+            $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,
+            ])->save();
+        } elseif ($status === ProviderWithdrawalStatusEnum::PROCESSING->value) {
+            $withdrawal->forceFill([
+                'status' => ProviderWithdrawalStatusEnum::PROCESSING,
+            ])->save();
+        }
+    }
+}

+ 1 - 1
app/Services/ReviewService.php

@@ -60,7 +60,7 @@ class ReviewService
                         break;
                     case 'provider':
                         $schedule = Schedule::find($data['schedule_id']);
-                        $client   = Client::find($schedule->client_id);
+                        $client = Client::find($schedule->client_id);
 
                         $client->updateAverageRating((float) $data['stars']);
                         break;

+ 9 - 9
app/Services/ScheduleService.php

@@ -70,9 +70,9 @@ class ScheduleService
         $schedule = Schedule::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;
-            $provider             = Provider::findOrFail($providerId);
+            $providerId = $data['provider_id'] ?? $schedule->provider_id;
+            $periodType = $data['period_type'] ?? $schedule->period_type;
+            $provider = Provider::findOrFail($providerId);
             $data['total_amount'] = $this->calculateAmount($provider, $periodType);
         }
 
@@ -111,14 +111,14 @@ class ScheduleService
     private function validateProviderAvailability(array $data, $excludeScheduleId = null)
     {
         $provider_id = $data['provider_id'];
-        $client_id   = $data['client_id'];
+        $client_id = $data['client_id'];
 
-        $date      = Carbon::parse($data['date']);
+        $date = Carbon::parse($data['date']);
         $dayOfWeek = $date->dayOfWeek;
         $startTime = $data['start_time'];
-        $endTime   = $data['end_time'];
-        $date_ymd  = $date->format('Y-m-d');
-        $period    = $startTime < '13:00:00' ? 'morning' : 'afternoon';
+        $endTime = $data['end_time'];
+        $date_ymd = $date->format('Y-m-d');
+        $period = $startTime < '13:00:00' ? 'morning' : 'afternoon';
 
         // bloqueio 2 schedules por semana para o mesmo client e provider
         ScheduleBusinessRules::validateWeeklyScheduleLimit(
@@ -277,7 +277,7 @@ class ScheduleService
                 case 'rejected':
                     break;
                 case 'paid':
-                    $date_cleaned       = Carbon::parse($schedule->date)->format('Y-m-d');
+                    $date_cleaned = Carbon::parse($schedule->date)->format('Y-m-d');
                     $date_time_dispatch = Carbon::parse($date_cleaned.' '.$schedule->start_time);
 
                     // StartScheduleJob::dispatch($schedule->id)->delay($date_time_dispatch);

+ 1 - 1
app/Services/SearchService.php

@@ -19,7 +19,7 @@ class SearchService
 
         $cliente = Client::where('user_id', $user->id)->first();
 
-        $blockedProviderIds       = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
+        $blockedProviderIds = ScheduleBusinessRules::getBlockedProviderIdsForClient($cliente->id);
         $providersWithWorkingDays = ScheduleBusinessRules::getProviderIdsWithWorkingDays();
 
         $clientPrimaryAddress = Address::where('source', 'client')

+ 24 - 12
app/Services/WebhookService.php

@@ -32,13 +32,14 @@ class WebhookService
     public function __construct(
         private readonly PagarmePaymentService $pagarmePaymentService,
         private readonly PaymentService $paymentService,
+        private readonly ProviderWithdrawalService $providerWithdrawalService,
     ) {}
 
     public function handlePagarme(array $payload): ?Payment
     {
-        $event   = $payload['type'] ?? null;
-        $data    = $payload['data'] ?? [];
-        $data    = is_array($data) ? $data : [];
+        $event = $payload['type'] ?? null;
+        $data = $payload['data'] ?? [];
+        $data = is_array($data) ? $data : [];
         $webhook = $this->recordWebhook($payload, $event, $data);
 
         Log::channel('pagarme')->info('Pagar.me webhook received', [
@@ -47,6 +48,17 @@ class WebhookService
         ]);
 
         if (! in_array($event, self::PAYMENT_EVENTS, true)) {
+            if (str_starts_with((string) $event, 'transfer.')) {
+                $this->providerWithdrawalService->handleTransferWebhook($data);
+
+                $webhook->update([
+                    'status'       => 'processed',
+                    'processed_at' => now(),
+                ]);
+
+                return null;
+            }
+
             $webhook->update([
                 'status'       => 'ignored',
                 'processed_at' => now(),
@@ -56,7 +68,7 @@ class WebhookService
         }
 
         $orderResponse = $this->normalizeOrderResponse($event, $data);
-        $payment       = $this->findPayment($orderResponse);
+        $payment = $this->findPayment($orderResponse);
 
         if (! $payment) {
             $webhook->update([
@@ -103,7 +115,7 @@ class WebhookService
     private function recordWebhook(array $payload, ?string $event, array $data): Webhook
     {
         $references = $this->extractReferences($event, $data);
-        $hookId     = $payload['id'] ?? null;
+        $hookId = $payload['id'] ?? null;
 
         $attributes = [
             'hook_id'        => $hookId,
@@ -179,14 +191,14 @@ 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;
+        $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;
-        $references    = array_filter([$metadataId, $chargeId, $transactionId, $orderId]);
+        $orderId = $orderResponse['id'] ?? null;
+        $references = array_filter([$metadataId, $chargeId, $transactionId, $orderId]);
 
         if (empty($references)) {
             return null;

+ 2 - 2
app/Traits/Base64ToFile.php

@@ -23,7 +23,7 @@ trait Base64ToFile
             throw new InvalidArgumentException('Invalid base64 string format');
         }
 
-        $mimeType      = $matches[1];
+        $mimeType = $matches[1];
         $base64Content = $matches[2];
 
         $fileContent = base64_decode($base64Content, true);
@@ -33,7 +33,7 @@ trait Base64ToFile
 
         $extension = $this->getExtensionFromMimeType($mimeType);
 
-        $fileName     = $fileName ?? uniqid('file_', true);
+        $fileName = $fileName ?? uniqid('file_', true);
         $fullFileName = "{$fileName}.{$extension}";
 
         $tempPath = sys_get_temp_dir().'/'.$fullFileName;

+ 1 - 1
app/Traits/UploadsBase64Image.php

@@ -18,7 +18,7 @@ trait UploadsBase64Image
         // Extract the file extension and base64 data
         if (preg_match('/^data:image\/(\w+);base64,/', $base64Image, $type)) {
             $base64Image = substr($base64Image, strpos($base64Image, ',') + 1);
-            $type        = strtolower($type[1]); // jpg, png, gif
+            $type = strtolower($type[1]); // jpg, png, gif
 
             if (! in_array($type, ['jpg', 'jpeg', 'png', 'gif'])) {
                 throw new Exception(__('validation.extensions', ['attribute' => __('general.image'), 'values' => 'jpg, jpeg, png, gif']));

+ 41 - 0
database/migrations/2026_05_26_000001_create_provider_withdrawals_table.php

@@ -0,0 +1,41 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('provider_withdrawals', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('provider_id')->constrained('providers')->onDelete('cascade');
+            $table->string('recipient_id');
+            $table->string('transfer_id')->nullable();
+            $table->string('idempotency_key')->unique();
+            $table->decimal('gross_amount', 10, 2);
+            $table->decimal('gateway_fee_amount', 10, 2)->default(0);
+            $table->decimal('net_amount', 10, 2);
+            $table->enum('status', ['pending_transfer', 'processing', 'transferred', 'failed', 'canceled'])->default('pending_transfer');
+            $table->string('type')->nullable();
+            $table->json('bank_account')->nullable();
+            $table->text('bank_response')->nullable();
+            $table->json('gateway_payload')->nullable();
+            $table->timestamp('completed_at')->nullable();
+            $table->timestamp('failed_at')->nullable();
+            $table->json('metadata')->nullable();
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('provider_id');
+            $table->index('transfer_id');
+            $table->index('status');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('provider_withdrawals');
+    }
+};

+ 17 - 0
database/migrations/2026_05_26_000002_rename_payment_transfers_to_payment_splits.php

@@ -0,0 +1,17 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::rename('payment_transfers', 'payment_splits');
+    }
+
+    public function down(): void
+    {
+        Schema::rename('payment_splits', 'payment_transfers');
+    }
+};

+ 30 - 0
database/migrations/2026_05_26_000003_add_provider_withdrawal_id_to_payment_splits_table.php

@@ -0,0 +1,30 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('payment_splits', function (Blueprint $table) {
+            $table->foreignId('provider_withdrawal_id')
+                ->nullable()
+                ->after('provider_id')
+                ->constrained('provider_withdrawals')
+                ->nullOnDelete();
+
+            $table->index('provider_withdrawal_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('payment_splits', function (Blueprint $table) {
+            $table->dropForeign(['provider_withdrawal_id']);
+            $table->dropIndex(['provider_withdrawal_id']);
+            $table->dropColumn('provider_withdrawal_id');
+        });
+    }
+};

+ 4 - 4
database/seeders/BrazilCitiesSeeder.php

@@ -22,8 +22,8 @@ class BrazilCitiesSeeder extends Seeder
             );
             $brasil->save();
 
-            $client_estados   = new Client;
-            $url_estados      = 'https://servicodados.ibge.gov.br/api/v1/localidades/estados';
+            $client_estados = new Client;
+            $url_estados = 'https://servicodados.ibge.gov.br/api/v1/localidades/estados';
             $response_estados = $client_estados->request('GET', $url_estados);
 
             $estados = json_decode($response_estados->getBody());
@@ -40,8 +40,8 @@ class BrazilCitiesSeeder extends Seeder
                 );
                 $new_estado->save();
 
-                $client_cidades   = new Client;
-                $url_cidades      = "https://servicodados.ibge.gov.br/api/v1/localidades/estados/$estado->id/municipios";
+                $client_cidades = new Client;
+                $url_cidades = "https://servicodados.ibge.gov.br/api/v1/localidades/estados/$estado->id/municipios";
                 $response_cidades = $client_cidades->request('GET', $url_cidades);
 
                 $cidades = json_decode($response_cidades->getBody());

+ 10 - 0
routes/authRoutes/payment_split.php

@@ -0,0 +1,10 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\PaymentSplitController;
+
+Route::get('/payment-split',         [PaymentSplitController::class, 'index'])->middleware('permission:payment-split,view');
+Route::post('/payment-split',        [PaymentSplitController::class, 'store'])->middleware('permission:payment-split,add');
+Route::get('/payment-split/{id}',    [PaymentSplitController::class, 'show'])->middleware('permission:payment-split,view');
+Route::put('/payment-split/{id}',    [PaymentSplitController::class, 'update'])->middleware('permission:payment-split,edit');
+Route::delete('/payment-split/{id}', [PaymentSplitController::class, 'destroy'])->middleware('permission:payment-split,delete');

+ 0 - 10
routes/authRoutes/payment_transfer.php

@@ -1,10 +0,0 @@
-<?php
-
-use Illuminate\Support\Facades\Route;
-use App\Http\Controllers\PaymentTransferController;
-
-Route::get('/payment-transfer',         [PaymentTransferController::class, 'index'])->middleware('permission:payment-transfer,view');
-Route::post('/payment-transfer',        [PaymentTransferController::class, 'store'])->middleware('permission:payment-transfer,add');
-Route::get('/payment-transfer/{id}',    [PaymentTransferController::class, 'show'])->middleware('permission:payment-transfer,view');
-Route::put('/payment-transfer/{id}',    [PaymentTransferController::class, 'update'])->middleware('permission:payment-transfer,edit');
-Route::delete('/payment-transfer/{id}', [PaymentTransferController::class, 'destroy'])->middleware('permission:payment-transfer,delete');

+ 9 - 0
routes/authRoutes/provider_withdrawal.php

@@ -0,0 +1,9 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\ProviderWithdrawalController;
+
+Route::get('/provider/withdrawals/balance', [ProviderWithdrawalController::class, 'balance'])->middleware('permission:config.provider,view');
+Route::get('/provider/withdrawals', [ProviderWithdrawalController::class, 'index'])->middleware('permission:config.provider,view');
+Route::post('/provider/withdrawals', [ProviderWithdrawalController::class, 'store'])->middleware('permission:config.provider,edit');
+Route::get('/provider/withdrawals/{id}', [ProviderWithdrawalController::class, 'show'])->middleware('permission:config.provider,view');