Преглед изворни кода

feat: add vinculo de cartao com customer + ajustes no pagamento

Gustavo Mantovani пре 3 недеља
родитељ
комит
9a5cf73639
79 измењених фајлова са 1449 додато и 676 уклоњено
  1. 0 9
      app/Enums/AccountTypeEnum.php
  2. 0 9
      app/Enums/BankAccountTypeEnum.php
  3. 0 1
      app/Http/Controllers/AuthController.php
  4. 48 1
      app/Http/Controllers/PaymentController.php
  5. 11 0
      app/Http/Controllers/ProviderController.php
  6. 0 63
      app/Http/Controllers/ProviderPaymentMethodController.php
  7. 40 0
      app/Http/Controllers/WebhookController.php
  8. 4 1
      app/Http/Requests/ClientPaymentMethodRequest.php
  9. 31 0
      app/Http/Requests/ProviderBankAccountRequest.php
  10. 0 29
      app/Http/Requests/ProviderPaymentMethodRequest.php
  11. 19 32
      app/Http/Requests/ProviderRequest.php
  12. 1 1
      app/Http/Requests/RegisterProviderRequest.php
  13. 1 0
      app/Http/Resources/ClientPaymentMethodResource.php
  14. 0 25
      app/Http/Resources/ProviderPaymentMethodResource.php
  15. 21 20
      app/Http/Resources/ProviderResource.php
  16. 0 2
      app/Models/Address.php
  17. 0 2
      app/Models/City.php
  18. 1 3
      app/Models/Client.php
  19. 0 2
      app/Models/ClientFavoriteProvider.php
  20. 4 2
      app/Models/ClientPaymentMethod.php
  21. 0 2
      app/Models/ClientProviderBlock.php
  22. 0 2
      app/Models/Country.php
  23. 0 2
      app/Models/CustomSchedule.php
  24. 0 2
      app/Models/CustomScheduleSpeciality.php
  25. 0 2
      app/Models/ImprovementType.php
  26. 0 2
      app/Models/Media.php
  27. 7 2
      app/Models/Payment.php
  28. 0 2
      app/Models/PaymentTransfer.php
  29. 0 2
      app/Models/Permission.php
  30. 0 2
      app/Models/PersonalAccessToken.php
  31. 0 2
      app/Models/Provider.php
  32. 0 2
      app/Models/ProviderBlockedDay.php
  33. 0 2
      app/Models/ProviderClientBlock.php
  34. 0 69
      app/Models/ProviderPaymentMethod.php
  35. 0 2
      app/Models/ProviderServicesType.php
  36. 0 2
      app/Models/ProviderSpeciality.php
  37. 0 2
      app/Models/ProviderWorkingDay.php
  38. 0 2
      app/Models/Review.php
  39. 0 2
      app/Models/ReviewImprovement.php
  40. 8 3
      app/Models/Schedule.php
  41. 0 2
      app/Models/ScheduleProposal.php
  42. 0 2
      app/Models/ScheduleRefuse.php
  43. 0 2
      app/Models/ServiceType.php
  44. 0 2
      app/Models/Speciality.php
  45. 0 2
      app/Models/State.php
  46. 0 2
      app/Models/User.php
  47. 0 2
      app/Models/UserTypePermission.php
  48. 85 0
      app/Models/Webhook.php
  49. 18 18
      app/Services/AuthService.php
  50. 20 4
      app/Services/ClientPaymentMethodService.php
  51. 1 0
      app/Services/ClientService.php
  52. 1 5
      app/Services/CustomScheduleService.php
  53. 39 4
      app/Services/DashboardService.php
  54. 191 0
      app/Services/Pagarme/PagarmeCardService.php
  55. 32 32
      app/Services/Pagarme/PagarmeCustomerService.php
  56. 17 0
      app/Services/Pagarme/PagarmeHttpLogger.php
  57. 104 80
      app/Services/Pagarme/PagarmePaymentService.php
  58. 85 28
      app/Services/Pagarme/PagarmeRecipientService.php
  59. 210 68
      app/Services/PaymentService.php
  60. 0 36
      app/Services/ProviderPaymentMethodService.php
  61. 13 0
      app/Services/ProviderService.php
  62. 51 4
      app/Services/SearchService.php
  63. 210 0
      app/Services/WebhookService.php
  64. 5 3
      config/services.php
  65. 47 0
      database/migrations/2026_05_20_000002_mark_existing_client_addresses_as_primary.php
  66. 17 0
      database/migrations/2026_05_22_000002_drop_provider_payment_methods_table.php
  67. 25 0
      database/migrations/2026_05_22_000003_add_gateway_card_id_to_client_payment_methods_table.php
  68. 49 0
      database/migrations/2026_05_22_023640_create_webhooks_table.php
  69. 0 6
      database/seeders/PermissionSeeder.php
  70. 0 51
      database/seeders/ProviderSeeder.php
  71. 0 3
      database/seeders/UserTypePermissionSeeder.php
  72. 3 0
      lang/en/messages.php
  73. 3 0
      lang/es/messages.php
  74. 3 0
      lang/pt/messages.php
  75. 16 0
      pint.json
  76. 1 0
      routes/authRoutes/payment.php
  77. 1 0
      routes/authRoutes/provider.php
  78. 0 10
      routes/authRoutes/provider_payment_method.php
  79. 6 0
      routes/noAuthRoutes/pagarme_webhook.php

+ 0 - 9
app/Enums/AccountTypeEnum.php

@@ -1,9 +0,0 @@
-<?php
-
-namespace App\Enums;
-
-enum AccountTypeEnum: string
-{
-    case PIX          = 'pix';
-    case BANK_ACCOUNT = 'bank_account';
-}

+ 0 - 9
app/Enums/BankAccountTypeEnum.php

@@ -1,9 +0,0 @@
-<?php
-
-namespace App\Enums;
-
-enum BankAccountTypeEnum: string
-{
-    case CHECKING = 'checking';
-    case SAVINGS  = 'savings';
-}

+ 0 - 1
app/Http/Controllers/AuthController.php

@@ -234,7 +234,6 @@ class AuthController extends Controller
     public function validateCode(UserAppsValidateCodeRequest $request): JsonResponse
     {
         try {
-
             $email   = $request->input('email');
             $phone   = $request->input('phone');
             $code    = $request->input('code');

+ 48 - 1
app/Http/Controllers/PaymentController.php

@@ -4,8 +4,11 @@ namespace App\Http\Controllers;
 
 use App\Http\Requests\PaymentRequest;
 use App\Http\Resources\PaymentResource;
+use App\Models\Schedule;
 use App\Services\PaymentService;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
 
 class PaymentController extends Controller
 {
@@ -24,7 +27,41 @@ class PaymentController extends Controller
     {
         $item = $this->service->create($request->validated());
 
-        return $this->successResponse(payload: new PaymentResource($item), message: __('messages.created'), code: 201);
+        return $this->successResponse(
+            payload: new PaymentResource($item),
+            message: $this->paymentMessage($item->status),
+            code: 201,
+        );
+    }
+
+    public function paySchedule(Request $request, Schedule $schedule): JsonResponse
+    {
+        $validated = $request->validate([
+            'payment_method'           => ['required', 'in:credit_card,pix'],
+            'client_payment_method_id' => ['nullable', 'integer', 'exists:client_payment_methods,id'],
+            'card_id'                  => ['nullable', 'string', 'max:255'],
+            'phone'                    => ['nullable', 'string', 'max:20'],
+        ]);
+
+        if ($schedule->client?->user_id !== Auth::id()) {
+            abort(403);
+        }
+
+        $item = $this->service->payAcceptedSchedule(
+            schedule: $schedule,
+            paymentMethod: $validated['payment_method'],
+            clientPaymentMethodId: $validated['client_payment_method_id'] ?? null,
+            options: [
+                'phone'   => $validated['phone'] ?? null,
+                'card_id' => $validated['card_id'] ?? null,
+            ],
+        );
+
+        return $this->successResponse(
+            payload: new PaymentResource($item),
+            message: $this->paymentMessage($item->status),
+            code: 201,
+        );
     }
 
     public function show(int $id): JsonResponse
@@ -47,4 +84,14 @@ class PaymentController extends Controller
 
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
+
+    private function paymentMessage(string $status): string
+    {
+        return match ($status) {
+            'paid' => __('messages.payment_confirmed'),
+            'authorized', 'processing', 'pending' => __('messages.payment_pending_confirmation'),
+            'failed' => __('messages.payment_not_confirmed'),
+            default  => __('messages.created'),
+        };
+    }
 }

+ 11 - 0
app/Http/Controllers/ProviderController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers;
 
+use App\Http\Requests\ProviderBankAccountRequest;
 use App\Http\Requests\ProviderRequest;
 use App\Http\Requests\RegisterProviderRequest;
 use App\Http\Resources\AuthResource;
@@ -51,6 +52,16 @@ class ProviderController extends Controller
         );
     }
 
+    public function updateBankAccount(ProviderBankAccountRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->updateBankAccount($id, $request->validated()['recipient_default_bank_account']);
+
+        return $this->successResponse(
+            payload: new ProviderResource($item),
+            message: __('messages.updated'),
+        );
+    }
+
     public function destroy(int $id): JsonResponse
     {
         $this->service->delete($id);

+ 0 - 63
app/Http/Controllers/ProviderPaymentMethodController.php

@@ -1,63 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Http\Requests\ProviderPaymentMethodRequest;
-use App\Http\Resources\ProviderPaymentMethodResource;
-use App\Services\ProviderPaymentMethodService;
-use Illuminate\Http\JsonResponse;
-
-class ProviderPaymentMethodController extends Controller
-{
-    public function __construct(
-        private readonly ProviderPaymentMethodService $service
-    ) {}
-
-    public function index($id): JsonResponse
-    {
-        $paymentMethods = $this->service->getByProvider($id);
-
-        return $this->successResponse(
-            payload: ProviderPaymentMethodResource::collection($paymentMethods)
-        );
-    }
-
-    public function show(int $id): ProviderPaymentMethodResource
-    {
-        $paymentMethod = $this->service->findById($id);
-
-        return new ProviderPaymentMethodResource($paymentMethod);
-    }
-
-    public function store(ProviderPaymentMethodRequest $request): JsonResponse
-    {
-        $paymentMethod = $this->service->create($request->validated());
-
-        return $this->successResponse(
-            payload: new ProviderPaymentMethodResource($paymentMethod)
-        );
-    }
-
-    public function update(ProviderPaymentMethodRequest $request, int $id): JsonResponse
-    {
-        $paymentMethod = $this->service->findById($id);
-
-        $paymentMethod = $this->service->update($paymentMethod, $request->validated());
-
-        return $this->successResponse(
-            payload: new ProviderPaymentMethodResource($paymentMethod)
-        );
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $paymentMethod = $this->service->findById($id);
-
-        $this->service->delete($paymentMethod);
-
-        return $this->successResponse(
-            message: __('messages.deleted'),
-            code: 204,
-        );
-    }
-}

+ 40 - 0
app/Http/Controllers/WebhookController.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\WebhookService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class WebhookController extends Controller
+{
+    public function __construct(
+        private readonly WebhookService $webhookService,
+    ) {}
+
+    public function pagarme(Request $request): JsonResponse
+    {
+        if (! $this->validPagarmeToken($request)) {
+            return $this->errorResponse(message: __('http.unauthorized_token'), code: 401);
+        }
+
+        $this->webhookService->handlePagarme($request->all());
+
+        return $this->successResponse(message: __('http.webhook_received'));
+    }
+
+    private function validPagarmeToken(Request $request): bool
+    {
+        $configuredToken = config('services.pagarme.webhook_token');
+
+        if (empty($configuredToken)) {
+            return true;
+        }
+
+        $receivedToken = $request->bearerToken()
+            ?: $request->header('X-Webhook-Token')
+            ?: $request->query('token');
+
+        return is_string($receivedToken) && hash_equals($configuredToken, $receivedToken);
+    }
+}

+ 4 - 1
app/Http/Requests/ClientPaymentMethodRequest.php

@@ -3,6 +3,7 @@
 namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
 
 class ClientPaymentMethodRequest extends FormRequest
 {
@@ -16,6 +17,7 @@ class ClientPaymentMethodRequest extends FormRequest
         $rules = [
             'client_id'        => ['sometimes', 'integer', 'exists:clients,id'],
             'token'            => ['sometimes', 'string', 'max:255'],
+            'gateway_card_id'  => ['sometimes', 'nullable', 'string', 'max:255'],
             'card_number'      => ['sometimes', 'nullable', 'string', 'min:13', 'max:19'],
             'holder_name'      => ['sometimes', 'string', 'max:255'],
             'expiration'       => ['sometimes', 'string', 'regex:/^(0[1-9]|1[0-2])\/\d{4}$/'],
@@ -28,7 +30,8 @@ class ClientPaymentMethodRequest extends FormRequest
 
         if ($this->isMethod('POST')) {
             $rules['client_id']        = ['required', 'integer', 'exists:clients,id'];
-            $rules['token']            = ['required', 'string', 'max:255'];
+            $rules['token']            = ['nullable', 'string', 'max:255', Rule::requiredIf(! $this->filled('gateway_card_id'))];
+            $rules['gateway_card_id']  = ['nullable', 'string', 'max:255', Rule::requiredIf(! $this->filled('token'))];
             $rules['card_number']      = ['sometimes', 'nullable', 'string', 'min:13', 'max:19'];
             $rules['holder_name']      = ['required', 'string', 'max:255'];
             $rules['expiration']       = ['required', 'string', 'regex:/^(0[1-9]|1[0-2])\/\d{4}$/'];

+ 31 - 0
app/Http/Requests/ProviderBankAccountRequest.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
+
+class ProviderBankAccountRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        return [
+            'recipient_default_bank_account'                     => ['required', 'array'],
+            'recipient_default_bank_account.holder_name'         => ['required', 'string', 'max:255'],
+            'recipient_default_bank_account.holder_type'         => ['required', Rule::in(['individual', 'company'])],
+            'recipient_default_bank_account.holder_document'     => ['required', 'string', 'max:20'],
+            'recipient_default_bank_account.bank'                => ['required', 'string', 'max:20'],
+            'recipient_default_bank_account.branch_number'       => ['required', 'string', 'max:20'],
+            'recipient_default_bank_account.branch_check_digit'  => ['sometimes', 'nullable', 'string', 'max:10'],
+            'recipient_default_bank_account.account_number'      => ['required', 'string', 'max:13'],
+            'recipient_default_bank_account.account_check_digit' => ['required', 'string', 'max:10'],
+            'recipient_default_bank_account.type'                => ['required', Rule::in(['checking', 'savings'])],
+            'recipient_default_bank_account.metadata'            => ['sometimes', 'array'],
+        ];
+    }
+}

+ 0 - 29
app/Http/Requests/ProviderPaymentMethodRequest.php

@@ -1,29 +0,0 @@
-<?php
-
-namespace App\Http\Requests;
-
-use App\Enums\AccountTypeEnum;
-use App\Enums\BankAccountTypeEnum;
-use Illuminate\Foundation\Http\FormRequest;
-use Illuminate\Validation\Rule;
-
-class ProviderPaymentMethodRequest extends FormRequest
-{
-    public function authorize(): bool
-    {
-        return true;
-    }
-
-    public function rules(): array
-    {
-        return [
-            'provider_id'       => ['required', 'exists:providers,id'],
-            'account_type'      => ['required', Rule::in([AccountTypeEnum::PIX->value, AccountTypeEnum::BANK_ACCOUNT->value])],
-            'pix_key'           => ['nullable', 'string', 'max:255', Rule::requiredIf($this->account_type === AccountTypeEnum::PIX->value)],
-            'bank_account_type' => ['nullable', Rule::in([BankAccountTypeEnum::CHECKING->value, BankAccountTypeEnum::SAVINGS->value]), Rule::requiredIf($this->account_type === AccountTypeEnum::BANK_ACCOUNT->value)],
-            'agency'            => ['nullable', 'string', 'max:255', Rule::requiredIf($this->account_type === AccountTypeEnum::BANK_ACCOUNT->value)],
-            'account'           => ['nullable', 'string', 'max:255', Rule::requiredIf($this->account_type === AccountTypeEnum::BANK_ACCOUNT->value)],
-            'digit'             => ['nullable', 'string', 'max:255', Rule::requiredIf($this->account_type === AccountTypeEnum::BANK_ACCOUNT->value)],
-        ];
-    }
-}

+ 19 - 32
app/Http/Requests/ProviderRequest.php

@@ -55,38 +55,25 @@ class ProviderRequest extends FormRequest
                     }
                 },
             ],
-            'average_rating'                                     => 'sometimes|nullable|numeric|min:0|max:5',
-            'total_services'                                     => 'sometimes|integer|min:0',
-            'birth_date'                                         => 'sometimes|nullable|date|before:today',
-            'selfie_verified'                                    => 'sometimes|boolean',
-            'document_verified'                                  => 'sometimes|boolean',
-            'approval_status'                                    => ['sometimes', Rule::enum(ApprovalStatusEnum::class)],
-            'daily_price_8h'                                     => 'sometimes|nullable|numeric|min:100|max:500',
-            'daily_price_6h'                                     => 'sometimes|nullable|numeric',
-            'daily_price_4h'                                     => 'sometimes|nullable|numeric',
-            'daily_price_2h'                                     => 'sometimes|nullable|numeric',
-            'profile_media_id'                                   => 'sometimes|nullable|exists:media,id',
-            'recipient_name'                                     => 'sometimes|string|max:255',
-            'recipient_email'                                    => 'sometimes|email|max:255',
-            'recipient_description'                              => 'sometimes|nullable|string',
-            'recipient_document'                                 => 'sometimes|string|max:20',
-            'recipient_type'                                     => ['sometimes', Rule::in(['individual', 'company'])],
-            'recipient_code'                                     => 'sometimes|string|max:255',
-            'recipient_payment_mode'                             => ['sometimes', Rule::in(['bank_transfer'])],
-            'recipient_default_bank_account'                     => 'sometimes|array',
-            'recipient_default_bank_account.holder_name'         => 'sometimes|string|max:255',
-            'recipient_default_bank_account.holder_type'         => ['sometimes', Rule::in(['individual', 'company'])],
-            'recipient_default_bank_account.holder_document'     => 'sometimes|string|max:20',
-            'recipient_default_bank_account.bank'                => 'sometimes|string|max:20',
-            'recipient_default_bank_account.branch_number'       => 'sometimes|string|max:20',
-            'recipient_default_bank_account.branch_check_digit'  => 'sometimes|nullable|string|max:10',
-            'recipient_default_bank_account.account_number'      => 'sometimes|string|max:20',
-            'recipient_default_bank_account.account_check_digit' => 'sometimes|string|max:10',
-            'recipient_default_bank_account.type'                => ['sometimes', Rule::in(['checking', 'savings'])],
-            'recipient_default_bank_account.metadata'            => 'sometimes|array',
-            'recipient_default_bank_account.metadata.*'          => 'sometimes|string',
-            'recipient_default_bank_account.pix_key'             => 'sometimes|nullable|string|max:255',
-            'recipient_metadata'                                 => 'sometimes|array',
+            'average_rating'         => 'sometimes|nullable|numeric|min:0|max:5',
+            'total_services'         => 'sometimes|integer|min:0',
+            'birth_date'             => 'sometimes|nullable|date|before:today',
+            'selfie_verified'        => 'sometimes|boolean',
+            'document_verified'      => 'sometimes|boolean',
+            'approval_status'        => ['sometimes', Rule::enum(ApprovalStatusEnum::class)],
+            'daily_price_8h'         => 'sometimes|nullable|numeric|min:100|max:500',
+            'daily_price_6h'         => 'sometimes|nullable|numeric',
+            'daily_price_4h'         => 'sometimes|nullable|numeric',
+            'daily_price_2h'         => 'sometimes|nullable|numeric',
+            'profile_media_id'       => 'sometimes|nullable|exists:media,id',
+            'recipient_name'         => 'sometimes|string|max:255',
+            'recipient_email'        => 'sometimes|email|max:255',
+            'recipient_description'  => 'sometimes|nullable|string',
+            'recipient_document'     => 'sometimes|string|max:20',
+            'recipient_type'         => ['sometimes', Rule::in(['individual', 'company'])],
+            'recipient_code'         => 'sometimes|string|max:255',
+            'recipient_payment_mode' => ['sometimes', Rule::in(['bank_transfer'])],
+            'recipient_metadata'     => 'sometimes|array',
         ];
 
         if ($this->isMethod('post')) {

+ 1 - 1
app/Http/Requests/RegisterProviderRequest.php

@@ -74,7 +74,7 @@ class RegisterProviderRequest extends FormRequest
                 Rule::exists('service_types', 'id')->where(function ($query) {
                     $query->whereNull('deleted_at')->where('is_active', true);
                 }),
-        ],
+            ],
 
             'working_days'          => 'required|array|min:1',
             'working_days.*.day'    => 'required|integer|min:0|max:6',

+ 1 - 0
app/Http/Resources/ClientPaymentMethodResource.php

@@ -12,6 +12,7 @@ class ClientPaymentMethodResource extends JsonResource
         return [
             'id'               => $this->id,
             'client_id'        => $this->client_id,
+            'gateway_card_id'  => $this->gateway_card_id,
             'holder_name'      => $this->holder_name,
             'expiration'       => $this->expiration,
             'card_name'        => $this->card_name,

+ 0 - 25
app/Http/Resources/ProviderPaymentMethodResource.php

@@ -1,25 +0,0 @@
-<?php
-
-namespace App\Http\Resources;
-
-use Illuminate\Http\Request;
-use Illuminate\Http\Resources\Json\JsonResource;
-
-class ProviderPaymentMethodResource extends JsonResource
-{
-    public function toArray(Request $request): array
-    {
-        return [
-            'id'                => $this->id,
-            'provider_id'       => $this->provider_id,
-            'account_type'      => $this->account_type?->value,
-            'pix_key'           => $this->pix_key,
-            'bank_account_type' => $this->bank_account_type?->value,
-            'agency'            => $this->agency,
-            'account'           => $this->account,
-            'digit'             => $this->digit,
-            'created_at'        => $this->created_at,
-            'updated_at'        => $this->updated_at,
-        ];
-    }
-}

+ 21 - 20
app/Http/Resources/ProviderResource.php

@@ -13,26 +13,27 @@ class ProviderResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id'                => $this->id,
-            'document'          => $this->document,
-            'rg'                => $this->rg,
-            'user_id'           => $this->user_id,
-            'user'              => $this->user,
-            'average_rating'    => $this->average_rating,
-            'total_services'    => $this->total_services,
-            'birth_date'        => $this->birth_date ? Carbon::parse($this->birth_date)->format('d/m/Y') : null,
-            'selfie_verified'   => $this->selfie_verified,
-            'document_verified' => $this->document_verified,
-            'approval_status'   => $this->approval_status?->value ?? $this->approval_status,
-            'daily_price_8h'    => $this->daily_price_8h,
-            'daily_price_6h'    => $this->daily_price_6h,
-            'daily_price_4h'    => $this->daily_price_4h,
-            'daily_price_2h'    => $this->daily_price_2h,
-            'profile_media_id'  => $this->profile_media_id,
-            'recipient_id'      => $this->recipient_id,
-            'profile_media'     => $this->profileMedia,
-            'created_at'        => Carbon::parse($this->created_at)->format('d/m/Y H:i'),
-            'updated_at'        => Carbon::parse($this->updated_at)->format('d/m/Y H:i'),
+            'id'                             => $this->id,
+            'document'                       => $this->document,
+            'rg'                             => $this->rg,
+            'user_id'                        => $this->user_id,
+            'user'                           => $this->user,
+            'average_rating'                 => $this->average_rating,
+            'total_services'                 => $this->total_services,
+            'birth_date'                     => $this->birth_date ? Carbon::parse($this->birth_date)->format('d/m/Y') : null,
+            'selfie_verified'                => $this->selfie_verified,
+            'document_verified'              => $this->document_verified,
+            'approval_status'                => $this->approval_status?->value ?? $this->approval_status,
+            'daily_price_8h'                 => $this->daily_price_8h,
+            'daily_price_6h'                 => $this->daily_price_6h,
+            'daily_price_4h'                 => $this->daily_price_4h,
+            'daily_price_2h'                 => $this->daily_price_2h,
+            'profile_media_id'               => $this->profile_media_id,
+            'recipient_id'                   => $this->recipient_id,
+            'recipient_default_bank_account' => $this->recipient_default_bank_account,
+            'profile_media'                  => $this->profileMedia,
+            'created_at'                     => Carbon::parse($this->created_at)->format('d/m/Y H:i'),
+            'updated_at'                     => Carbon::parse($this->updated_at)->format('d/m/Y H:i'),
         ];
     }
 

+ 0 - 2
app/Models/Address.php

@@ -29,7 +29,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property float|null $longitude
  * @property-read \App\Models\City|null $city
  * @property-read \App\Models\State|null $state
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Address newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Address newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Address onlyTrashed()
@@ -56,7 +55,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Address whereZipCode($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Address withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Address withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Address extends Model

+ 0 - 2
app/Models/City.php

@@ -19,7 +19,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Country $country
  * @property-read \App\Models\State $state
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|City newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|City newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|City onlyTrashed()
@@ -34,7 +33,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|City whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|City withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|City withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class City extends Model

+ 1 - 3
app/Models/Client.php

@@ -26,7 +26,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Schedule> $schedules
  * @property-read int|null $schedules_count
  * @property-read \App\Models\User $user
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client onlyTrashed()
@@ -43,7 +42,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client whereUserId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Client withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Client extends Model
@@ -65,7 +63,7 @@ class Client extends Model
 
     public function user(): BelongsTo
     {
-        return $this->belongsTo(User::class)->select('id', 'name');
+        return $this->belongsTo(User::class)->select('id', 'name', 'email', 'phone');
     }
 
     public function blockedByProviders(): HasMany

+ 0 - 2
app/Models/ClientFavoriteProvider.php

@@ -17,7 +17,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Client $client
  * @property-read \App\Models\Provider $provider
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientFavoriteProvider newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientFavoriteProvider newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientFavoriteProvider onlyTrashed()
@@ -31,7 +30,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientFavoriteProvider whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientFavoriteProvider withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientFavoriteProvider withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ClientFavoriteProvider extends Model

+ 4 - 2
app/Models/ClientPaymentMethod.php

@@ -22,8 +22,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property string|null $token
+ * @property string|null $gateway_card_id
  * @property-read \App\Models\Client $client
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod onlyTrashed()
@@ -36,6 +36,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereCvv($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereDeletedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereExpiration($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereGatewayCardId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereHolderName($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereIsActive($value)
@@ -44,7 +45,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientPaymentMethod withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ClientPaymentMethod extends Model
@@ -54,6 +54,7 @@ class ClientPaymentMethod extends Model
     protected $fillable = [
         'client_id',
         'token',
+        'gateway_card_id',
         'card_number',
         'holder_name',
         'expiration',
@@ -76,6 +77,7 @@ class ClientPaymentMethod extends Model
 
     protected $hidden = [
         'token',
+        'gateway_card_id',
         'card_number',
         'cvv',
     ];

+ 0 - 2
app/Models/ClientProviderBlock.php

@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Client $client
  * @property-read \App\Models\Provider $provider
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientProviderBlock newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientProviderBlock newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientProviderBlock onlyTrashed()
@@ -29,7 +28,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientProviderBlock whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientProviderBlock withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ClientProviderBlock withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ClientProviderBlock extends Model

+ 0 - 2
app/Models/Country.php

@@ -20,7 +20,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read int|null $cities_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\State> $states
  * @property-read int|null $states_count
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Country newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Country newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Country onlyTrashed()
@@ -34,7 +33,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Country whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Country withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Country withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Country extends Model

+ 0 - 2
app/Models/CustomSchedule.php

@@ -24,7 +24,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read \App\Models\ServiceType|null $serviceType
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CustomScheduleSpeciality> $specialities
  * @property-read int|null $specialities_count
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomSchedule newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomSchedule newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomSchedule onlyTrashed()
@@ -42,7 +41,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomSchedule whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomSchedule withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomSchedule withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class CustomSchedule extends Model

+ 0 - 2
app/Models/CustomScheduleSpeciality.php

@@ -15,7 +15,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\CustomSchedule $customSchedule
  * @property-read \App\Models\Speciality $speciality
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomScheduleSpeciality newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomScheduleSpeciality newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomScheduleSpeciality onlyTrashed()
@@ -28,7 +27,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomScheduleSpeciality whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomScheduleSpeciality withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|CustomScheduleSpeciality withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class CustomScheduleSpeciality extends Model

+ 0 - 2
app/Models/ImprovementType.php

@@ -17,7 +17,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Review> $reviews
  * @property-read int|null $reviews_count
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ImprovementType newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ImprovementType newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ImprovementType onlyTrashed()
@@ -31,7 +30,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ImprovementType whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ImprovementType withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ImprovementType withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ImprovementType extends Model

+ 0 - 2
app/Models/Media.php

@@ -19,7 +19,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\User|null $user
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Media newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Media newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Media onlyTrashed()
@@ -36,7 +35,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Media whereUserId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Media withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Media withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Media extends Model

+ 7 - 2
app/Models/Payment.php

@@ -43,7 +43,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @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\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()
@@ -80,7 +81,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
@@ -160,4 +160,9 @@ class Payment extends Model
     {
         return $this->hasMany(PaymentTransfer::class);
     }
+
+    public function webhooks()
+    {
+        return $this->hasMany(Webhook::class);
+    }
 }

+ 0 - 2
app/Models/PaymentTransfer.php

@@ -34,7 +34,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @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()
@@ -66,7 +65,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @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()
- *
  * @mixin \Eloquent
  */
 class PaymentTransfer extends Model

+ 0 - 2
app/Models/Permission.php

@@ -25,7 +25,6 @@ use Kalnoy\Nestedset\NodeTrait;
  * @property-read Permission|null $parent
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\UserTypePermission> $userTypePermissions
  * @property-read int|null $user_type_permissions_count
- *
  * @method static \Kalnoy\Nestedset\Collection<int, static> all($columns = ['*'])
  * @method static \Kalnoy\Nestedset\QueryBuilder<static>|Permission ancestorsAndSelf($id, array $columns = [])
  * @method static \Kalnoy\Nestedset\QueryBuilder<static>|Permission ancestorsOf($id, array $columns = [])
@@ -83,7 +82,6 @@ use Kalnoy\Nestedset\NodeTrait;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Permission withTrashed(bool $withTrashed = true)
  * @method static \Kalnoy\Nestedset\QueryBuilder<static>|Permission withoutRoot()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Permission withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Permission extends Model

+ 0 - 2
app/Models/PersonalAccessToken.php

@@ -17,7 +17,6 @@ use Laravel\Sanctum\PersonalAccessToken as LaravelPersonalAccessToken;
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property-read Model|\Eloquent $tokenable
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|PersonalAccessToken newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|PersonalAccessToken newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|PersonalAccessToken query()
@@ -31,7 +30,6 @@ use Laravel\Sanctum\PersonalAccessToken as LaravelPersonalAccessToken;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|PersonalAccessToken whereTokenableId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|PersonalAccessToken whereTokenableType($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|PersonalAccessToken whereUpdatedAt($value)
- *
  * @mixin \Eloquent
  */
 class PersonalAccessToken extends LaravelPersonalAccessToken

+ 0 - 2
app/Models/Provider.php

@@ -53,7 +53,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read \App\Models\Address|null $primaryAddress
  * @property-read \App\Models\Media|null $profileMedia
  * @property-read \App\Models\User $user
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Provider newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Provider newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Provider onlyTrashed()
@@ -93,7 +92,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Provider whereUserId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Provider withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Provider withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Provider extends Model

+ 0 - 2
app/Models/ProviderBlockedDay.php

@@ -20,7 +20,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Provider $provider
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderBlockedDay newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderBlockedDay newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderBlockedDay onlyTrashed()
@@ -37,7 +36,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderBlockedDay whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderBlockedDay withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderBlockedDay withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ProviderBlockedDay extends Model

+ 0 - 2
app/Models/ProviderClientBlock.php

@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Client $client
  * @property-read \App\Models\Provider $provider
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderClientBlock newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderClientBlock newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderClientBlock onlyTrashed()
@@ -29,7 +28,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderClientBlock whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderClientBlock withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderClientBlock withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ProviderClientBlock extends Model

+ 0 - 69
app/Models/ProviderPaymentMethod.php

@@ -1,69 +0,0 @@
-<?php
-
-namespace App\Models;
-
-use App\Enums\AccountTypeEnum;
-use App\Enums\BankAccountTypeEnum;
-use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Relations\BelongsTo;
-use Illuminate\Database\Eloquent\SoftDeletes;
-
-/**
- * @property int $id
- * @property int $provider_id
- * @property AccountTypeEnum $account_type
- * @property string|null $pix_key
- * @property BankAccountTypeEnum|null $bank_account_type
- * @property string|null $agency
- * @property string|null $account
- * @property string|null $digit
- * @property \Illuminate\Support\Carbon|null $created_at
- * @property \Illuminate\Support\Carbon|null $updated_at
- * @property \Illuminate\Support\Carbon|null $deleted_at
- * @property-read \App\Models\Provider $provider
- *
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod onlyTrashed()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod query()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereAccount($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereAccountType($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereAgency($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereBankAccountType($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereDigit($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod wherePixKey($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereProviderId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod whereUpdatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod withTrashed(bool $withTrashed = true)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderPaymentMethod withoutTrashed()
- *
- * @mixin \Eloquent
- */
-class ProviderPaymentMethod extends Model
-{
-    use HasFactory, SoftDeletes;
-
-    protected $fillable = [
-        'provider_id',
-        'account_type',
-        'pix_key',
-        'bank_account_type',
-        'agency',
-        'account',
-        'digit',
-    ];
-
-    protected $casts = [
-        'account_type'      => AccountTypeEnum::class,
-        'bank_account_type' => BankAccountTypeEnum::class,
-    ];
-
-    public function provider(): BelongsTo
-    {
-        return $this->belongsTo(Provider::class);
-    }
-}

+ 0 - 2
app/Models/ProviderServicesType.php

@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Provider $provider
  * @property-read \App\Models\ServiceType $serviceType
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderServicesType newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderServicesType newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderServicesType onlyTrashed()
@@ -29,7 +28,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderServicesType whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderServicesType withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderServicesType withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ProviderServicesType extends Model

+ 0 - 2
app/Models/ProviderSpeciality.php

@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Provider $provider
  * @property-read \App\Models\Speciality $speciality
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderSpeciality newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderSpeciality newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderSpeciality onlyTrashed()
@@ -29,7 +28,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderSpeciality whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderSpeciality withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderSpeciality withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ProviderSpeciality extends Model

+ 0 - 2
app/Models/ProviderWorkingDay.php

@@ -17,7 +17,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Provider $provider
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWorkingDay newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWorkingDay newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWorkingDay onlyTrashed()
@@ -31,7 +30,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWorkingDay whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWorkingDay withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ProviderWorkingDay withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ProviderWorkingDay extends Model

+ 0 - 2
app/Models/Review.php

@@ -26,7 +26,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ReviewImprovement> $reviewsImprovements
  * @property-read int|null $reviews_improvements_count
  * @property-read \App\Models\Schedule $schedule
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Review newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Review newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Review onlyTrashed()
@@ -42,7 +41,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Review whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Review withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Review withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Review extends Model

+ 0 - 2
app/Models/ReviewImprovement.php

@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\ImprovementType $improvementType
  * @property-read \App\Models\Review $review
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewImprovement newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewImprovement newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewImprovement onlyTrashed()
@@ -29,7 +28,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewImprovement whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewImprovement withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewImprovement withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ReviewImprovement extends Model

+ 8 - 3
app/Models/Schedule.php

@@ -36,7 +36,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read int|null $refuses_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Review> $reviews
  * @property-read int|null $reviews_count
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule onlyTrashed()
@@ -62,7 +61,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Schedule extends Model
@@ -96,7 +94,14 @@ class Schedule extends Model
 
     public function client()
     {
-        return $this->belongsTo(Client::class)->select('id', 'user_id', 'average_rating');
+        return $this->belongsTo(Client::class)->select(
+            'id',
+            'user_id',
+            'document',
+            'average_rating',
+            'external_customer_id',
+            'external_customer_code',
+        );
     }
 
     public function provider()

+ 0 - 2
app/Models/ScheduleProposal.php

@@ -15,7 +15,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Provider $provider
  * @property-read \App\Models\Schedule $schedule
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleProposal newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleProposal newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleProposal onlyTrashed()
@@ -28,7 +27,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleProposal whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleProposal withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleProposal withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ScheduleProposal extends Model

+ 0 - 2
app/Models/ScheduleRefuse.php

@@ -15,7 +15,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Provider $provider
  * @property-read \App\Models\Schedule $schedule
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleRefuse newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleRefuse newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleRefuse onlyTrashed()
@@ -28,7 +27,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleRefuse whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleRefuse withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ScheduleRefuse withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class ScheduleRefuse extends Model

+ 0 - 2
app/Models/ServiceType.php

@@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\Model;
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property string|null $deleted_at
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ServiceType newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ServiceType newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ServiceType query()
@@ -22,7 +21,6 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ServiceType whereId($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ServiceType whereIsActive($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|ServiceType whereUpdatedAt($value)
- *
  * @mixin \Eloquent
  */
 class ServiceType extends Model

+ 0 - 2
app/Models/Speciality.php

@@ -13,7 +13,6 @@ 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
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Speciality newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Speciality newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Speciality onlyTrashed()
@@ -26,7 +25,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Speciality whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Speciality withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|Speciality withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class Speciality extends Model

+ 0 - 2
app/Models/State.php

@@ -21,7 +21,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\City> $cities
  * @property-read int|null $cities_count
  * @property-read \App\Models\Country $country
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|State newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|State newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|State onlyTrashed()
@@ -36,7 +35,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|State whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|State withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|State withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class State extends Model

+ 0 - 2
app/Models/User.php

@@ -33,7 +33,6 @@ use Laravel\Sanctum\HasApiTokens;
  * @property-read \App\Models\Provider|null $provider
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \Laravel\Sanctum\PersonalAccessToken> $tokens
  * @property-read int|null $tokens_count
- *
  * @method static \Database\Factories\UserFactory factory($count = null, $state = [])
  * @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
@@ -51,7 +50,6 @@ use Laravel\Sanctum\HasApiTokens;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|User whereType($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|User whereValidatedCode($value)
- *
  * @mixin \Eloquent
  */
 class User extends Authenticatable

+ 0 - 2
app/Models/UserTypePermission.php

@@ -17,7 +17,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property-read \App\Models\Permission $permission
- *
  * @method static \Illuminate\Database\Eloquent\Builder<static>|UserTypePermission newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|UserTypePermission newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder<static>|UserTypePermission onlyTrashed()
@@ -31,7 +30,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static \Illuminate\Database\Eloquent\Builder<static>|UserTypePermission whereUserType($value)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|UserTypePermission withTrashed(bool $withTrashed = true)
  * @method static \Illuminate\Database\Eloquent\Builder<static>|UserTypePermission withoutTrashed()
- *
  * @mixin \Eloquent
  */
 class UserTypePermission extends Model

+ 85 - 0
app/Models/Webhook.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * @property int $id
+ * @property int|null $payment_id
+ * @property string $provider
+ * @property string|null $hook_id
+ * @property string|null $event
+ * @property string|null $account_id
+ * @property string|null $order_id
+ * @property string|null $charge_id
+ * @property string|null $transaction_id
+ * @property string $status
+ * @property int $attempts_count
+ * @property array<array-key, mixed> $payload
+ * @property \Illuminate\Support\Carbon|null $received_at
+ * @property \Illuminate\Support\Carbon|null $processed_at
+ * @property string|null $error_message
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @property-read \App\Models\Payment|null $payment
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook query()
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereAccountId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereAttemptsCount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereChargeId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereErrorMessage($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereEvent($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereHookId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereOrderId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook wherePayload($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook wherePaymentId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereProcessedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereProvider($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereReceivedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereStatus($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereTransactionId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder<static>|Webhook whereUpdatedAt($value)
+ * @mixin \Eloquent
+ */
+class Webhook extends Model
+{
+    use HasFactory;
+
+    protected $table = 'webhooks';
+
+    protected $fillable = [
+        'payment_id',
+        'provider',
+        'hook_id',
+        'event',
+        'account_id',
+        'order_id',
+        'charge_id',
+        'transaction_id',
+        'status',
+        'attempts_count',
+        'payload',
+        'received_at',
+        'processed_at',
+        'error_message',
+    ];
+
+    protected $casts = [
+        'attempts_count' => 'integer',
+        'payload'        => 'array',
+        'received_at'    => 'datetime',
+        'processed_at'   => 'datetime',
+        'created_at'     => 'datetime',
+        'updated_at'     => 'datetime',
+    ];
+
+    public function payment()
+    {
+        return $this->belongsTo(Payment::class);
+    }
+}

+ 18 - 18
app/Services/AuthService.php

@@ -133,11 +133,11 @@ class AuthService
                 $query->when(! empty($data['email']), function ($q) use ($data) {
                     $q->where('email', $data['email']);
                 })
-                ->when(! empty($data['phone']), function ($q) use ($data) {
-                    $q->where('phone', $data['phone']);
-                });
+                    ->when(! empty($data['phone']), function ($q) use ($data) {
+                        $q->where('phone', $data['phone']);
+                    });
             })
-            ->first();
+                ->first();
 
             $isLogin = false;
 
@@ -212,11 +212,11 @@ class AuthService
                 $query->when(! empty($data['email']), function ($q) use ($data) {
                     $q->where('email', $data['email']);
                 })
-                ->when(! empty($data['phone']), function ($q) use ($data) {
-                    $q->where('phone', $data['phone']);
-                });
+                    ->when(! empty($data['phone']), function ($q) use ($data) {
+                        $q->where('phone', $data['phone']);
+                    });
             })
-            ->first();
+                ->first();
 
             $isLogin = false;
 
@@ -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,14 +309,14 @@ 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))
                 ->when($phone, fn ($q) => $q->where('phone', $phone));
         })
-        ->where('code', $code)
-        ->first();
+            ->where('code', $code)
+            ->first();
 
         if (! $user) {
             return false;
@@ -345,18 +345,18 @@ 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) {
                 $q->where('email', $email);
             })
-            ->when($phone, function ($q) use ($phone) {
-                $q->where('phone', $phone);
-            });
+                ->when($phone, function ($q) use ($phone) {
+                    $q->where('phone', $phone);
+                });
         })
-        ->where('code', $code)
-        ->first();
+            ->where('code', $code)
+            ->first();
 
         if (! $user) {
             return false;

+ 20 - 4
app/Services/ClientPaymentMethodService.php

@@ -3,10 +3,16 @@
 namespace App\Services;
 
 use App\Models\ClientPaymentMethod;
+use App\Services\Pagarme\PagarmeCardService;
 use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
 
 class ClientPaymentMethodService
 {
+    public function __construct(
+        private readonly PagarmeCardService $pagarmeCardService,
+    ) {}
+
     public function getByClientId(int $clientId): Collection
     {
         return ClientPaymentMethod::where('client_id', $clientId)
@@ -22,11 +28,21 @@ class ClientPaymentMethodService
 
     public function create(array $data): ClientPaymentMethod
     {
-        if ($data['is_active'] ?? true) {
-            $this->deactivateOtherCards($data['client_id']);
-        }
+        return DB::transaction(function () use ($data) {
+            if ($data['is_active'] ?? true) {
+                $this->deactivateOtherCards($data['client_id']);
+            }
+
+            $paymentMethod = ClientPaymentMethod::create($data);
+
+            $cardId = $this->pagarmeCardService->createCardForPaymentMethod($paymentMethod);
+
+            if (empty($cardId)) {
+                throw new \RuntimeException('Nao foi possivel salvar o cartao no Pagar.me.');
+            }
 
-        return ClientPaymentMethod::create($data);
+            return $paymentMethod->fresh();
+        });
     }
 
     public function update(ClientPaymentMethod $paymentMethod, array $data): ClientPaymentMethod

+ 1 - 0
app/Services/ClientService.php

@@ -96,6 +96,7 @@ class ClientService
                 'nickname'       => $data['nickname'] ?? null,
                 'instructions'   => $data['instructions'] ?? null,
                 'address_type'   => $data['address_type'] ?? 'home',
+                'is_primary'     => true,
                 'latitude'       => $data['latitude'] ?? null,
                 'longitude'      => $data['longitude'] ?? null,
             ];

+ 1 - 5
app/Services/CustomScheduleService.php

@@ -15,9 +15,7 @@ use Illuminate\Support\Facades\Log;
 
 class CustomScheduleService
 {
-    public function __construct(
-        private readonly PaymentService $paymentService,
-    ) {}
+    public function __construct() {}
 
     public function getAll()
     {
@@ -388,8 +386,6 @@ class CustomScheduleService
                 ->where('id', '!=', $proposalId)
                 ->delete();
 
-            $this->paymentService->createPagarmeOrderForAcceptedSchedule($schedule);
-
             return $schedule->fresh(['provider.user']);
         });
     }

+ 39 - 4
app/Services/DashboardService.php

@@ -18,7 +18,9 @@ use Illuminate\Support\Facades\DB;
 
 class DashboardService
 {
-    public function __construct() {}
+    public function __construct(
+        private readonly CustomScheduleService $customScheduleService,
+    ) {}
 
     public function dadosDashboardCliente(): array
     {
@@ -119,6 +121,11 @@ class DashboardService
             ->where('is_primary', true)
             ->first();
 
+        $providersCloseDistanceSelect = $this->distanceSelect(
+            $clientPrimaryAddress?->latitude !== null ? (float) $clientPrimaryAddress->latitude : null,
+            $clientPrimaryAddress?->longitude !== null ? (float) $clientPrimaryAddress->longitude : null,
+        );
+
         $providersClose = Provider::leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
             ->leftJoin(DB::raw("
         (
@@ -139,6 +146,9 @@ class DashboardService
                 'providers.id as provider_id',
                 'provider_user.name as provider_name',
                 'provider_address.id as address_id',
+                'provider_address.district',
+                'provider_address.latitude as provider_latitude',
+                'provider_address.longitude as provider_longitude',
                 'providers.average_rating',
                 'providers.total_services',
                 'providers.daily_price_8h',
@@ -152,6 +162,7 @@ class DashboardService
           WHERE reviews.origin = 'provider'
           AND schedules.provider_id = providers.id
         ) as total_reviews"),
+                $providersCloseDistanceSelect,
             )
             ->get();
 
@@ -449,9 +460,7 @@ class DashboardService
         //   ->orderBy('schedules.date', 'asc')
         //   ->get();
 
-        $customScheduleService = new CustomScheduleService;
-
-        $opportunities = $customScheduleService->getAvailableOpportunities($provider->id);
+        $opportunities = $this->customScheduleService->getAvailableOpportunities($provider->id);
 
         return [
             'headerBar'      => $headerBar,
@@ -463,4 +472,30 @@ class DashboardService
             'opportunities'  => $opportunities,
         ];
     }
+
+    private function distanceSelect(?float $clientLatitude, ?float $clientLongitude): \Illuminate\Contracts\Database\Query\Expression
+    {
+        if ($clientLatitude === null || $clientLongitude === null) {
+            return DB::raw('NULL as distance_km');
+        }
+
+        return DB::raw("
+            CASE
+                WHEN provider_address.latitude IS NOT NULL
+                AND provider_address.longitude IS NOT NULL
+                THEN ROUND((
+                    6371 * acos(
+                        least(1, greatest(-1,
+                            cos(radians({$clientLatitude}))
+                            * cos(radians(provider_address.latitude))
+                            * cos(radians(provider_address.longitude) - radians({$clientLongitude}))
+                            + sin(radians({$clientLatitude}))
+                            * sin(radians(provider_address.latitude))
+                        ))
+                    )
+                )::numeric, 1)
+                ELSE NULL
+            END as distance_km
+        ");
+    }
 }

+ 191 - 0
app/Services/Pagarme/PagarmeCardService.php

@@ -0,0 +1,191 @@
+<?php
+
+namespace App\Services\Pagarme;
+
+use App\Models\Address;
+use App\Models\ClientPaymentMethod;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
+
+class PagarmeCardService
+{
+    public function __construct(
+        private readonly PagarmeCustomerService $pagarmeCustomerService,
+    ) {}
+
+    public function createCardForPaymentMethod(ClientPaymentMethod $paymentMethod): ?string
+    {
+        if (! empty($paymentMethod->gateway_card_id)) {
+            return $paymentMethod->gateway_card_id;
+        }
+
+        if (empty($paymentMethod->token)) {
+            throw new \InvalidArgumentException('Token do cartao e obrigatorio para salvar cartao no Pagar.me.');
+        }
+
+        $paymentMethod->loadMissing('client.user');
+
+        $customerId = $this->pagarmeCustomerService->createCustomerForClient(
+            $paymentMethod->client,
+            []
+        );
+
+        if (empty($customerId)) {
+            throw new \RuntimeException('Cliente precisa ter customer_id do Pagar.me para salvar cartao.');
+        }
+
+        $payload = $this->filterFilledRecursive([
+            'token'           => $paymentMethod->token,
+            'label'           => $paymentMethod->card_name,
+            'billing_address' => $this->buildBillingAddress($paymentMethod),
+        ]);
+
+        $endpoint = $this->pagarmeUrl("/customers/{$customerId}/cards");
+
+        PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
+
+        $response = $this->pagarmeRequest($paymentMethod->id)
+            ->post($endpoint, $payload);
+
+        if ($response->failed()) {
+            $responseBody = $response->json() ?? $response->body();
+
+            Log::channel('pagarme')->error('Pagar.me card creation failed', [
+                'client_payment_method_id' => $paymentMethod->id,
+                'client_id'                => $paymentMethod->client_id,
+                'customer_id'              => $customerId,
+                'status'                   => $response->status(),
+                'body'                     => $responseBody,
+                'payload'                  => $payload,
+            ]);
+
+            throw new \RuntimeException($this->gatewayErrorMessage($responseBody));
+        }
+
+        $cardData = $response->json();
+        $cardId   = $cardData['id'] ?? null;
+
+        if (empty($cardId)) {
+            Log::channel('pagarme')->error('Pagar.me card creation returned empty id', [
+                'client_payment_method_id' => $paymentMethod->id,
+                'client_id'                => $paymentMethod->client_id,
+                'customer_id'              => $customerId,
+                'response'                 => $cardData,
+            ]);
+
+            throw new \RuntimeException('Pagar.me card creation returned an empty id.');
+        }
+
+        $paymentMethod->forceFill([
+            'gateway_card_id'  => $cardId,
+            'brand'            => $paymentMethod->brand ?: ($cardData['brand'] ?? null),
+            'last_four_digits' => $paymentMethod->last_four_digits ?: ($cardData['last_four_digits'] ?? null),
+        ])->save();
+
+        return $cardId;
+    }
+
+    //
+
+    private function pagarmeRequest(int $paymentMethodId)
+    {
+        $secretKey = config('services.pagarme.secret_key');
+
+        if (empty($secretKey)) {
+            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+
+            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
+        }
+
+        return Http::withBasicAuth($secretKey, '')
+            ->withHeaders([
+                'Idempotency-Key' => "client-payment-method-{$paymentMethodId}-card",
+                'Content-Type'    => 'application/json',
+                'Accept'          => 'application/json',
+            ]);
+    }
+
+    private function pagarmeUrl(string $path): string
+    {
+        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+    }
+
+    //
+
+    private function buildBillingAddress(ClientPaymentMethod $paymentMethod): array
+    {
+        $address = Address::query()
+            ->with(['city.state', 'state'])
+            ->where('source', 'client')
+            ->where('source_id', $paymentMethod->client_id)
+            ->orderByDesc('is_primary')
+            ->latest('id')
+            ->first();
+
+        if (! $address) {
+            throw new \InvalidArgumentException('Cliente precisa ter endereco para salvar cartao no Pagar.me.');
+        }
+
+        $state = $address->state?->code ?? $address->city?->state?->code;
+        $city  = $address->city?->name;
+
+        $line1 = implode(', ', array_filter([
+            $address->number ?: 'S/N',
+            $address->address,
+            $address->district,
+        ]));
+
+        foreach ([
+            'estado'   => $state,
+            'cidade'   => $city,
+            'cep'      => $this->digits($address->zip_code),
+            'endereco' => $line1,
+        ] as $field => $value) {
+            if ($value === null || $value === '') {
+                throw new \InvalidArgumentException("Cliente precisa ter {$field} valido para salvar cartao no Pagar.me.");
+            }
+        }
+
+        return [
+            'line_1'   => $line1,
+            'line_2'   => $address->complement ?: $address->instructions,
+            'zip_code' => $this->digits($address->zip_code),
+            'city'     => $city,
+            'state'    => $state,
+            'country'  => 'BR',
+        ];
+    }
+
+    private function digits(?string $value): string
+    {
+        return preg_replace('/\D+/', '', (string) $value) ?? '';
+    }
+
+    private function filterFilledRecursive(array $data): array
+    {
+        $filtered = [];
+
+        foreach ($data as $key => $value) {
+            if (is_array($value)) {
+                $value = $this->filterFilledRecursive($value);
+            }
+
+            if ($value !== null && $value !== '' && $value !== []) {
+                $filtered[$key] = $value;
+            }
+        }
+
+        return $filtered;
+    }
+
+    private function gatewayErrorMessage(mixed $responseBody): string
+    {
+        $message = is_array($responseBody) ? ($responseBody['message'] ?? null) : null;
+
+        if ($message === 'Token not found.') {
+            return 'Token do cartao nao encontrado no Pagar.me. Gere o token com a public key do mesmo ambiente da secret key.';
+        }
+
+        return 'Erro ao salvar cartao no Pagar.me.';
+    }
+}

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

@@ -16,7 +16,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);
@@ -52,8 +52,12 @@ class PagarmeCustomerService
             $payload['phones'] = $phones;
         }
 
+        $endpoint = $this->pagarmeUrl('/customers');
+
+        PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
+
         $response = $this->pagarmeRequest($client->id)
-            ->post($this->pagarmeUrl('/customers'), $payload);
+            ->post($endpoint, $payload);
 
         if ($response->failed()) {
             Log::channel('pagarme')->error('Pagar.me customer creation failed', [
@@ -88,14 +92,32 @@ class PagarmeCustomerService
 
     //
 
-    private function pagarmeUrl(string $path): string
+    private function idempotencyKey(int $clientId): string
     {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+        return "client-{$clientId}-customer";
     }
 
-    private function idempotencyKey(int $clientId): string
+    private function pagarmeRequest(int $clientId)
     {
-        return "client-{$clientId}-customer";
+        $secretKey = config('services.pagarme.secret_key');
+
+        if (empty($secretKey)) {
+            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+
+            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
+        }
+
+        return Http::withBasicAuth($secretKey, '')
+            ->withHeaders([
+                'Idempotency-Key' => $this->idempotencyKey($clientId),
+                'Content-Type'    => 'application/json',
+                'Accept'          => 'application/json',
+            ]);
+    }
+
+    private function pagarmeUrl(string $path): string
+    {
+        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
     }
 
     //
@@ -163,13 +185,6 @@ class PagarmeCustomerService
         return strlen($document) === 14 ? 'CNPJ' : 'CPF';
     }
 
-    private function personType(string $document): string
-    {
-        return strlen($document) === 14 ? 'company' : 'individual';
-    }
-
-    //
-
     private function filterFilledRecursive(array $data): array
     {
         $filtered = [];
@@ -187,28 +202,13 @@ class PagarmeCustomerService
         return $filtered;
     }
 
-    private function sanitizeDigits(?string $value): string
+    private function personType(string $document): string
     {
-        return preg_replace('/\D+/', '', (string) $value) ?? '';
+        return strlen($document) === 14 ? 'company' : 'individual';
     }
 
-    //
-
-    private function pagarmeRequest(int $clientId)
+    private function sanitizeDigits(?string $value): string
     {
-        $secretKey = config('services.pagarme.secret_key');
-
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
-
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
-
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => $this->idempotencyKey($clientId),
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
+        return preg_replace('/\D+/', '', (string) $value) ?? '';
     }
 }

+ 17 - 0
app/Services/Pagarme/PagarmeHttpLogger.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services\Pagarme;
+
+use Illuminate\Support\Facades\Log;
+
+class PagarmeHttpLogger
+{
+    public static function logRequest(string $method, string $endpoint, array $payload): void
+    {
+        Log::channel('pagarme')->info('Pagar.me request', [
+            'method'   => strtoupper($method),
+            'endpoint' => $endpoint,
+            'payload'  => $payload,
+        ]);
+    }
+}

+ 104 - 80
app/Services/Pagarme/PagarmePaymentService.php

@@ -12,10 +12,10 @@ class PagarmePaymentService
 {
     public function createOrderWithCreditCard(
         Payment $payment,
-        array   $items,
-        array   $customer,
-        array   $creditCard,
-        array   $options = []
+        array $items,
+        array $customer,
+        array $creditCard,
+        array $options = []
     ): array {
         $paymentMethod = [
             'payment_method' => 'credit_card',
@@ -27,20 +27,20 @@ class PagarmePaymentService
         }
 
         return $this->createOrder(
-            payment:       $payment,
-            items:         $items,
-            customer:      $customer,
+            payment: $payment,
+            items: $items,
+            customer: $customer,
             paymentMethod: $paymentMethod,
-            options:       $options,
+            options: $options,
         );
     }
 
     public function createOrderWithPix(
         Payment $payment,
-        array   $items,
-        array   $customer,
-        array   $pix,
-        array   $options = []
+        array $items,
+        array $customer,
+        array $pix,
+        array $options = []
     ): array {
         $paymentMethod = [
             'payment_method' => 'pix',
@@ -52,22 +52,22 @@ class PagarmePaymentService
         }
 
         return $this->createOrder(
-            payment:       $payment,
-            items:         $items,
-            customer:      $customer,
+            payment: $payment,
+            items: $items,
+            customer: $customer,
             paymentMethod: $paymentMethod,
-            options:       $options,
+            options: $options,
         );
     }
 
     //
 
     public function createOrder(
-        Payment $payment, 
-        array   $items,
-        array   $customer,
-        array   $paymentMethod,
-        array   $options = []
+        Payment $payment,
+        array $items,
+        array $customer,
+        array $paymentMethod,
+        array $options = []
     ): array {
         if (empty($items)) {
             throw new \InvalidArgumentException('items nao pode estar vazio.');
@@ -115,17 +115,21 @@ class PagarmePaymentService
             $payload['channel'] = $options['channel'];
         }
 
+        PagarmeHttpLogger::logRequest('POST', $this->pagarmeUrl('/orders'), $payload);
+
         $response = $this->pagarmeRequest($this->idempotencyKey($payment))
             ->post($this->pagarmeUrl('/orders'), $payload);
 
         if ($response->failed()) {
+            $responseBody = $response->json() ?? $response->body();
+
             Log::channel('pagarme')->error('Pagar.me order creation failed', [
                 'status'  => $response->status(),
-                'body'    => $response->json() ?? $response->body(),
+                'body'    => $responseBody,
                 'payload' => $payload,
             ]);
 
-            throw new \RuntimeException('Erro ao criar pedido de pagamento no Pagar.me.');
+            throw new \RuntimeException($this->gatewayErrorMessage($responseBody));
         }
 
         $order = $response->json();
@@ -171,8 +175,6 @@ class PagarmePaymentService
         return $payment->fresh();
     }
 
-    //
-
     public function buildSplitFromTransfers(Collection $transfers): array
     {
         return $transfers
@@ -182,7 +184,7 @@ class PagarmePaymentService
                     'amount'       => $this->toGatewayAmountInCents((float) $transfer->gross_amount),
                     'recipient_id' => $transfer->gateway_transfer_target_reference,
                     'type'         => 'flat',
-                    
+
                     'options' => [
                         'charge_processing_fee' => false,
                         'charge_remainder_fee'  => false,
@@ -201,14 +203,32 @@ class PagarmePaymentService
 
     //
 
-    private function pagarmeUrl(string $path): string
+    private function idempotencyKey(Payment $payment): string
     {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+        return "payment-{$payment->id}-schedule-{$payment->schedule_id}";
     }
 
-    private function idempotencyKey(Payment $payment): string
+    private function pagarmeRequest(string $idempotencyKey)
     {
-        return "payment-{$payment->id}-schedule-{$payment->schedule_id}";
+        $secretKey = config('services.pagarme.secret_key');
+
+        if (empty($secretKey)) {
+            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+
+            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
+        }
+
+        return Http::withBasicAuth($secretKey, '')
+            ->withHeaders([
+                'Idempotency-Key' => $idempotencyKey,
+                'Content-Type'    => 'application/json',
+                'Accept'          => 'application/json',
+            ]);
+    }
+
+    private function pagarmeUrl(string $path): string
+    {
+        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
     }
 
     //
@@ -276,8 +296,6 @@ class PagarmePaymentService
         return $payload;
     }
 
-    //
-
     private function extractFailureCode(array $transaction): ?string
     {
         return $this->filledArrayValue($transaction['gateway_response'] ?? [], 'code');
@@ -285,40 +303,24 @@ class PagarmePaymentService
 
     private function extractFailureMessage(array $transaction): ?string
     {
-        return $this->filledArrayValue($transaction, 'acquirer_message');
-    }
+        $acquirerMessage = $this->filledArrayValue($transaction, 'acquirer_message');
 
-    private function resolveAuthorizedAt(?string $transactionStatus, array $transaction): ?string
-    {
-        if (in_array($transactionStatus, ['authorized_pending_capture', 'captured', 'partial_capture'], true)) {
-            return $this->filledArrayValue($transaction, 'created_at');
+        if ($acquirerMessage) {
+            return $acquirerMessage;
         }
 
-        return null;
-    }
-
-    private function validateItems(array $items): array
-    {
-        return collect($items)
-            ->map(function (array $item, int $index) {
-                foreach (['code', 'amount', 'quantity'] as $field) {
-                    if (! array_key_exists($field, $item) || ! $this->filled($item[$field])) {
-                        throw new \InvalidArgumentException("items.{$index}.{$field} e obrigatorio.");
-                    }
-                }
+        $gatewayErrors = $transaction['gateway_response']['errors'] ?? [];
 
-                if ((int) $item['amount'] <= 0 || (int) $item['quantity'] <= 0) {
-                    throw new \InvalidArgumentException("items.{$index}.amount e quantity devem ser maiores que zero.");
-                }
+        if (! is_array($gatewayErrors) || empty($gatewayErrors)) {
+            return null;
+        }
 
-                return $this->filterFilledRecursive($item);
-            })
-            ->values()
-            ->all();
+        return collect($gatewayErrors)
+            ->pluck('message')
+            ->filter()
+            ->implode('; ') ?: null;
     }
 
-    //
-
     private function filled(mixed $value): bool
     {
         return $value !== null && $value !== '' && $value !== [];
@@ -350,39 +352,61 @@ class PagarmePaymentService
         return $filtered;
     }
 
-    //
+    private function gatewayErrorMessage(mixed $responseBody): string
+    {
+        $message = is_array($responseBody) ? ($responseBody['message'] ?? null) : null;
+
+        if ($message === 'Token not found.') {
+            return 'Token do cartao nao encontrado no Pagar.me. Gere um novo card_token ou informe um card_id valido.';
+        }
+
+        return 'Erro ao criar pedido de pagamento no Pagar.me.';
+    }
 
     private function mapPaymentStatus(?string $chargeStatus, ?string $transactionStatus): string
     {
         $status = strtolower((string) ($transactionStatus ?: $chargeStatus));
 
         return match ($status) {
-            'captured', 'paid'                                         => 'paid',
-            'authorized_pending_capture', 'waiting_capture', 'pending' => 'authorized',
-            'processing'                                               => 'processing',
-            'not_authorized', 'with_error', 'failed'                   => 'failed',
-            'voided', 'partial_void'                                   => 'cancelled',
-            default                                                    => 'pending',
+            'captured', 'paid', 'overpaid' => 'paid',
+            'authorized_pending_capture', 'waiting_capture' => 'authorized',
+            'pending'    => 'pending',
+            'processing' => 'processing',
+            'not_authorized', 'with_error', 'failed',
+            'underpaid', 'chargedback' => 'failed',
+            'voided', 'partial_void', 'canceled',
+            'cancelled', 'refunded', 'partial_refunded',
+            'partial_canceled' => 'cancelled',
+            default            => 'pending',
         };
     }
 
-    //
-
-    private function pagarmeRequest(string $idempotencyKey)
+    private function resolveAuthorizedAt(?string $transactionStatus, array $transaction): ?string
     {
-        $secretKey = config('services.pagarme.secret_key');
+        if (in_array($transactionStatus, ['authorized_pending_capture', 'captured', 'partial_capture'], true)) {
+            return $this->filledArrayValue($transaction, 'created_at');
+        }
 
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+        return null;
+    }
 
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
+    private function validateItems(array $items): array
+    {
+        return collect($items)
+            ->map(function (array $item, int $index) {
+                foreach (['code', 'amount', 'quantity'] as $field) {
+                    if (! array_key_exists($field, $item) || ! $this->filled($item[$field])) {
+                        throw new \InvalidArgumentException("items.{$index}.{$field} e obrigatorio.");
+                    }
+                }
 
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => $idempotencyKey,
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
+                if ((int) $item['amount'] <= 0 || (int) $item['quantity'] <= 0) {
+                    throw new \InvalidArgumentException("items.{$index}.amount e quantity devem ser maiores que zero.");
+                }
+
+                return $this->filterFilledRecursive($item);
+            })
+            ->values()
+            ->all();
     }
 }

+ 85 - 28
app/Services/Pagarme/PagarmeRecipientService.php

@@ -44,7 +44,7 @@ class PagarmeRecipientService
                     'complementary'   => $addressParts['complementary'],
                     'street_number'   => $addressParts['street_number'],
                     'neighborhood'    => $addressParts['neighborhood'],
-                    'city'            => $data['city']  ?? null,
+                    'city'            => $data['city'] ?? null,
                     'state'           => $data['state'] ?? null,
                     'zip_code'        => preg_replace('/\D+/', '', $data['zip_code']),
                     'reference_point' => $addressParts['reference_point'],
@@ -74,8 +74,12 @@ class PagarmeRecipientService
             ],
         ]);
 
+        $endpoint = $this->pagarmeUrl('/recipients');
+
+        PagarmeHttpLogger::logRequest('POST', $endpoint, $payload);
+
         $response = $this->pagarmeRequest($provider->id)
-            ->post($this->pagarmeUrl('/recipients'), $payload);
+            ->post($endpoint, $payload);
 
         if ($response->failed()) {
             Log::channel('pagarme')->error('Pagar.me recipient creation failed', [
@@ -126,18 +130,77 @@ class PagarmeRecipientService
         return $recipientId;
     }
 
-    //
-
-    private function pagarmeUrl(string $path): string
+    public function updateDefaultBankAccount(Provider $provider, array $bankAccountData): Provider
     {
-        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+        if (empty($provider->recipient_id)) {
+            throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para atualizar a conta bancaria.');
+        }
+
+        $bankAccountData                = $this->normalizeBankAccountPayload($bankAccountData);
+        $bankAccountData['holder_name'] = $this->normalizeBankAccountHolderName($bankAccountData['holder_name']);
+
+        $payload = [
+            'bank_account' => $this->filterFilledRecursive($bankAccountData),
+        ];
+
+        $endpoint = $this->pagarmeUrl("/recipients/{$provider->recipient_id}/default-bank-account");
+
+        PagarmeHttpLogger::logRequest('PATCH', $endpoint, $payload);
+
+        $response = $this->pagarmeRequest($provider->id, 'default-bank-account-'.sha1(json_encode($payload)))
+            ->patch($endpoint, $payload);
+
+        if ($response->failed()) {
+            Log::channel('pagarme')->error('Pagar.me recipient bank account update failed', [
+                'provider_id'  => $provider->id,
+                'recipient_id' => $provider->recipient_id,
+                'status'       => $response->status(),
+                'body'         => $response->json() ?? $response->body(),
+                'payload'      => $payload,
+            ]);
+
+            throw new \RuntimeException('Erro ao atualizar conta bancaria do recebedor no Pagar.me.');
+        }
+
+        $recipientData = $response->json();
+
+        $provider->forceFill([
+            'recipient_default_bank_account' => $recipientData['default_bank_account'] ?? $bankAccountData,
+        ])->save();
+
+        return $provider->fresh();
     }
 
+    //
+
     private function idempotencyKey(int $providerId, string $suffix = 'recipient'): string
     {
         return "provider-{$providerId}-{$suffix}";
     }
 
+    private function pagarmeRequest(int $providerId, string $suffix = 'recipient')
+    {
+        $secretKey = config('services.pagarme.secret_key');
+
+        if (empty($secretKey)) {
+            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
+
+            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
+        }
+
+        return Http::withBasicAuth($secretKey, '')
+            ->withHeaders([
+                'Idempotency-Key' => $this->idempotencyKey($providerId, $suffix),
+                'Content-Type'    => 'application/json',
+                'Accept'          => 'application/json',
+            ]);
+    }
+
+    private function pagarmeUrl(string $path): string
+    {
+        return rtrim(config('services.pagarme.base_url'), '/').'/'.ltrim($path, '/');
+    }
+
     //
 
     private function buildPhoneNumbers(?string $phone): array
@@ -175,10 +238,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);
@@ -205,8 +268,6 @@ class PagarmeRecipientService
         ];
     }
 
-    //
-
     private function filterFilledRecursive(array $data): array
     {
         $filtered = [];
@@ -282,23 +343,19 @@ class PagarmeRecipientService
         return Str::limit($holderName, 29, '');
     }
 
-    //
-
-    private function pagarmeRequest(int $providerId, string $suffix = 'recipient')
+    private function normalizeBankAccountPayload(array $bankAccountData): array
     {
-        $secretKey = config('services.pagarme.secret_key');
-
-        if (empty($secretKey)) {
-            Log::channel('pagarme')->error('PAGARME_SECRET_KEY is not configured.');
-
-            throw new \RuntimeException('PAGARME_SECRET_KEY is not configured.');
-        }
-
-        return Http::withBasicAuth($secretKey, '')
-            ->withHeaders([
-                'Idempotency-Key' => $this->idempotencyKey($providerId, $suffix),
-                'Content-Type'    => 'application/json',
-                'Accept'          => 'application/json',
-            ]);
+        return [
+            'holder_name'         => $bankAccountData['holder_name'],
+            'holder_type'         => $bankAccountData['holder_type'],
+            'holder_document'     => preg_replace('/\D+/', '', $bankAccountData['holder_document']),
+            'bank'                => $bankAccountData['bank'],
+            'branch_number'       => $bankAccountData['branch_number'],
+            'branch_check_digit'  => $bankAccountData['branch_check_digit'] ?? null,
+            'account_number'      => $bankAccountData['account_number'],
+            'account_check_digit' => $bankAccountData['account_check_digit'],
+            'type'                => $bankAccountData['type'],
+            'metadata'            => $bankAccountData['metadata'] ?? null,
+        ];
     }
 }

+ 210 - 68
app/Services/PaymentService.php

@@ -3,6 +3,7 @@
 namespace App\Services;
 
 use App\Models\Address;
+use App\Models\Client;
 use App\Models\ClientPaymentMethod;
 use App\Models\Payment;
 use App\Models\PaymentTransfer;
@@ -10,6 +11,7 @@ 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
@@ -59,10 +61,24 @@ class PaymentService
         return $model->delete();
     }
 
-    public function createPagarmeOrderForAcceptedSchedule(Schedule $schedule): Payment
-    {
+    //
+
+    public function payAcceptedSchedule(
+        Schedule $schedule,
+        string $paymentMethod,
+        ?int $clientPaymentMethodId = null,
+        array $options = []
+    ): Payment {
         $schedule->loadMissing(['client', 'provider', 'customSchedule.serviceType']);
 
+        if ($schedule->status !== 'accepted') {
+            throw new \InvalidArgumentException('Agendamento precisa estar aceito para ser pago.');
+        }
+
+        if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
+            throw new \InvalidArgumentException('Forma de pagamento invalida.');
+        }
+
         if (! $schedule->provider_id || ! $schedule->provider) {
             throw new \InvalidArgumentException('Agendamento precisa ter prestador confirmado para gerar pagamento.');
         }
@@ -82,19 +98,62 @@ class PaymentService
             ->first();
 
         if ($existingPayment) {
-            return $existingPayment;
+            if ($this->isIncompleteGatewayPayment($existingPayment)) {
+                $existingPayment->forceFill([
+                    'status'          => 'failed',
+                    'failed_at'       => now(),
+                    'failure_message' => 'Pagamento pendente sem retorno do gateway.',
+                ])->save();
+            } else {
+                if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== 'paid') {
+                    throw new \InvalidArgumentException('Ja existe um pagamento em andamento para este agendamento.');
+                }
+
+                $this->syncScheduleStatusAfterPayment($schedule, $existingPayment);
+
+                return $existingPayment;
+            }
         }
 
-        $clientPaymentMethod = ClientPaymentMethod::query()
-            ->where('client_id', $schedule->client_id)
-            ->where('is_active', true)
-            ->latest('id')
-            ->first();
+        $clientPaymentMethod = null;
+
+        if ($paymentMethod === 'credit_card') {
+            if (! $clientPaymentMethodId && empty($options['card_id'])) {
+                throw new \InvalidArgumentException('Cartao de pagamento ou card_id e obrigatorio.');
+            }
 
-        $paymentMethod = $clientPaymentMethod?->token ? 'credit_card' : 'pix';
-        $serviceAmount = (float) $schedule->total_amount;
-        $platformFee   = round($serviceAmount * 0.11, 2);
-        $grossAmount   = round($serviceAmount + $platformFee, 2);
+            if ($clientPaymentMethodId) {
+                $clientPaymentMethod = ClientPaymentMethod::query()
+                    ->where('client_id', $schedule->client_id)
+                    ->where('id', $clientPaymentMethodId)
+                    ->where('is_active', true)
+                    ->first();
+
+                if (! $clientPaymentMethod) {
+                    throw new \InvalidArgumentException('Cartao de pagamento nao encontrado ou inativo para este cliente.');
+                }
+            }
+
+            if (
+                empty($clientPaymentMethod?->gateway_card_id)
+                && empty($options['card_id'])
+            ) {
+                throw new \InvalidArgumentException('Cartao de pagamento invalido ou sem gateway_card_id do Pagar.me.');
+            }
+        }
+
+        $serviceAmount       = (float) $schedule->total_amount;
+        $platformFee         = round($serviceAmount * 0.11, 2);
+        $grossAmount         = round($serviceAmount + $platformFee, 2);
+        $items               = $this->buildOrderItems($schedule, $grossAmount);
+        $client              = Client::find($schedule->client_id);
+        $customerId          = $client?->external_customer_id;
+        $customer            = empty($customerId) ? $this->buildCustomerPayload($schedule, $options) : [];
+        $platformRecipientId = config('services.pagarme.platform_recipient_id');
+
+        if ($platformFee > 0 && empty($platformRecipientId)) {
+            throw new \InvalidArgumentException('PAGARME_PLATFORM_RECIPIENT_ID precisa estar configurado para receber a taxa da plataforma no split.');
+        }
 
         $payment = Payment::create([
             'schedule_id'              => $schedule->id,
@@ -111,60 +170,107 @@ class PaymentService
             'currency'                 => 'BRL',
             'installments'             => 1,
             'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
-            'metadata'                 => [
+
+            'metadata' => [
                 'service_amount' => number_format($serviceAmount, 2, '.', ''),
                 'platform_fee'   => number_format($platformFee, 2, '.', ''),
             ],
         ]);
 
         $transfer = PaymentTransfer::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',
-            'gross_amount'                       => $serviceAmount,
-            'gateway_fee_amount'                 => 0,
-            'net_amount'                         => $serviceAmount,
-            'metadata'                           => [
+            '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',
+            'gross_amount'                      => $serviceAmount,
+            'gateway_fee_amount'                => 0,
+            'net_amount'                        => $serviceAmount,
+
+            'metadata' => [
                 'schedule_id' => (string) $schedule->id,
             ],
         ]);
 
-        $items    = $this->buildOrderItems($schedule, $grossAmount);
-        $customer = $this->buildCustomerPayload($schedule);
-        $split    = $this->pagarmePaymentService->buildSplitFromTransfers(collect([$transfer]));
-
-        $orderResponse = $paymentMethod === 'credit_card'
-            ? $this->pagarmePaymentService->createOrderWithCreditCard(
-                payment: $payment,
-                items: $items,
-                customer: $customer,
-                creditCard: [
-                    'installments'          => 1,
-                    'statement_descriptor'  => Str::limit((string) config('app.name', 'SOFTPAR'), 13, ''),
-                    'operation_type'        => 'auth_and_capture',
-                    'card_token'            => $clientPaymentMethod->token,
-                ],
-                options: ['split' => $split],
-            )
-            : $this->pagarmePaymentService->createOrderWithPix(
-                payment: $payment,
-                items: $items,
-                customer: $customer,
-                pix: [
-                    'expires_at' => $payment->expires_at?->toISOString(),
+        $split = $this->pagarmePaymentService->buildSplitFromTransfers(collect([$transfer]));
+
+        if ($platformFee > 0) {
+            $split[] = [
+                'amount'       => $this->pagarmePaymentService->toGatewayAmountInCents($platformFee),
+                'recipient_id' => $platformRecipientId,
+                'type'         => 'flat',
+
+                'options' => [
+                    'charge_processing_fee' => true,
+                    'charge_remainder_fee'  => true,
+                    'liable'                => true,
                 ],
-                options: ['split' => $split],
-            );
+            ];
+        }
+
+        try {
+            $creditCardReference = $paymentMethod === 'credit_card'
+                ? $this->resolveCreditCardReference($clientPaymentMethod, $options)
+                : [];
+
+            if ($paymentMethod === 'credit_card') {
+                Log::channel('pagarme')->info('Resolved credit card reference for payment', [
+                    'payment_id'               => $payment->id,
+                    'client_payment_method_id' => $clientPaymentMethod?->id,
+                    'reference_type'           => array_key_first($creditCardReference),
+                    'gateway_card_id'          => $clientPaymentMethod?->gateway_card_id,
+                ]);
+            }
+
+            $orderResponse = $paymentMethod === 'credit_card'
+                ? $this->pagarmePaymentService->createOrderWithCreditCard(
+                    payment: $payment,
+                    items: $items,
+                    customer: $customer,
+                    creditCard: [
+                        'installments'         => 1,
+                        'statement_descriptor' => Str::limit((string) config('app.name', 'SOFTPAR'), 13, ''),
+                        'operation_type'       => 'auth_and_capture',
+                    ] + $creditCardReference,
+                    options: [
+                        'split'       => $split,
+                        'customer_id' => $customerId,
+                    ],
+                )
+                : $this->pagarmePaymentService->createOrderWithPix(
+                    payment: $payment,
+                    items: $items,
+                    customer: $customer,
+                    pix: [
+                        'expires_at' => $payment->expires_at?->toISOString(),
+                    ],
+                    options: [
+                        'split'       => $split,
+                        'customer_id' => $customerId,
+                    ],
+                );
+        } catch (\Throwable $e) {
+            $payment->forceFill([
+                'status'          => 'failed',
+                'failed_at'       => now(),
+                'failure_message' => $e->getMessage(),
+            ])->save();
+
+            $transfer->update(['status' => 'failed']);
+
+            throw $e;
+        }
 
-        return $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+        $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+
+        $this->syncScheduleStatusAfterPayment($schedule, $payment);
+
+        return $payment;
     }
 
-    /**
-     * @return array<int, array<string, mixed>>
-     */
+    //
+
     private function buildOrderItems(Schedule $schedule, float $grossAmount): array
     {
         $description = $schedule->customSchedule?->serviceType?->description
@@ -178,17 +284,20 @@ class PaymentService
         ]];
     }
 
-    /**
-     * @return array<string, mixed>
-     */
-    private function buildCustomerPayload(Schedule $schedule): array
+    private function buildCustomerPayload(Schedule $schedule, array $options = []): array
     {
         $client  = $schedule->client;
         $user    = $client->user()->first(['id', 'name', 'email', 'phone']);
         $address = Address::with(['city.state', 'state'])->find($schedule->address_id);
 
-        if (! $user?->name || ! $user?->email || ! $client->document) {
-            throw new \InvalidArgumentException('Cliente precisa ter nome, email e documento para criar pedido no Pagar.me.');
+        foreach ([
+            'nome'      => $user?->name,
+            'email'     => $user?->email,
+            'documento' => $client->document,
+        ] as $field => $value) {
+            if ($value === null || $value === '') {
+                throw new \InvalidArgumentException("Cliente precisa ter {$field} para criar pedido no Pagar.me.");
+            }
         }
 
         if (! $address) {
@@ -196,11 +305,15 @@ class PaymentService
         }
 
         $document = $this->digits($client->document);
-        $phone    = $this->buildPhonePayload($user->phone);
-        $state    = $address->state?->code ?? $address->city?->state?->code;
-        $city     = $address->city?->name;
-        $zipCode  = $this->digits($address->zip_code);
-        $line1    = implode(', ', array_filter([
+
+        $phone = $this->buildPhonePayload($user->phone)
+            ?: $this->buildPhonePayload($options['phone'] ?? null);
+
+        $state   = $address->state?->code ?? $address->city?->state?->code;
+        $city    = $address->city?->name;
+        $zipCode = $this->digits($address->zip_code);
+
+        $line1 = implode(', ', array_filter([
             $address->number ?: 'S/N',
             $address->address,
             $address->district,
@@ -226,7 +339,8 @@ class PaymentService
             'document'      => $document,
             'document_type' => strlen($document) === 14 ? 'CNPJ' : 'CPF',
             'type'          => strlen($document) === 14 ? 'company' : 'individual',
-            'address'       => [
+
+            'address' => [
                 'country'  => 'BR',
                 'state'    => $state,
                 'city'     => $city,
@@ -234,13 +348,11 @@ class PaymentService
                 'line_1'   => $line1,
                 'line_2'   => $address->complement ?: $address->instructions,
             ],
-            'phones'        => ['mobile_phone' => $phone],
+
+            'phones' => ['mobile_phone' => $phone],
         ];
     }
 
-    /**
-     * @return array<string, string>|null
-     */
     private function buildPhonePayload(?string $phone): ?array
     {
         $digits = $this->digits($phone);
@@ -264,4 +376,34 @@ class PaymentService
     {
         return preg_replace('/\D+/', '', (string) $value) ?? '';
     }
+
+    private function isIncompleteGatewayPayment(Payment $payment): bool
+    {
+        return $payment->status === 'pending'
+            && empty($payment->gateway_entity_reference)
+            && empty($payment->gateway_operation_reference)
+            && empty($payment->gateway_payload);
+    }
+
+    private function resolveCreditCardReference(?ClientPaymentMethod $clientPaymentMethod, array $options): array
+    {
+        if (! empty($options['card_id'])) {
+            return ['card_id' => $options['card_id']];
+        }
+
+        if (! empty($clientPaymentMethod?->gateway_card_id)) {
+            return ['card_id' => $clientPaymentMethod->gateway_card_id];
+        }
+
+        throw new \InvalidArgumentException('Cartao de pagamento precisa ter gateway_card_id do Pagar.me.');
+    }
+
+    public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void
+    {
+        if ($payment->status !== 'paid' || $schedule->status === 'paid') {
+            return;
+        }
+
+        $schedule->update(['status' => 'paid']);
+    }
 }

+ 0 - 36
app/Services/ProviderPaymentMethodService.php

@@ -1,36 +0,0 @@
-<?php
-
-namespace App\Services;
-
-use App\Models\ProviderPaymentMethod;
-use Illuminate\Database\Eloquent\Collection;
-
-class ProviderPaymentMethodService
-{
-    public function getByProvider(int $providerId): Collection
-    {
-        return ProviderPaymentMethod::where('provider_id', $providerId)->get();
-    }
-
-    public function findById(int $id): ?ProviderPaymentMethod
-    {
-        return ProviderPaymentMethod::find($id);
-    }
-
-    public function create(array $data): ProviderPaymentMethod
-    {
-        return ProviderPaymentMethod::create($data);
-    }
-
-    public function update(ProviderPaymentMethod $paymentMethod, array $data): ProviderPaymentMethod
-    {
-        $paymentMethod->update($data);
-
-        return $paymentMethod->fresh();
-    }
-
-    public function delete(ProviderPaymentMethod $paymentMethod): bool
-    {
-        return $paymentMethod->delete();
-    }
-}

+ 13 - 0
app/Services/ProviderService.php

@@ -65,6 +65,19 @@ class ProviderService
         return $model->fresh(['user', 'profileMedia']);
     }
 
+    public function updateBankAccount(int $id, array $bankAccountData): ?Provider
+    {
+        $provider = $this->findById($id);
+
+        if (! $provider) {
+            return null;
+        }
+
+        $this->pagarmeRecipientService->updateDefaultBankAccount($provider, $bankAccountData);
+
+        return $provider->fresh(['user', 'profileMedia']);
+    }
+
     public function delete(int $id): bool
     {
         $model = $this->findById($id);

+ 51 - 4
app/Services/SearchService.php

@@ -27,20 +27,26 @@ class SearchService
             ->orderBy('is_primary', 'desc')
             ->first();
 
-        return Provider::leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
+        $distanceSelect = $this->distanceSelect(
+            $clientPrimaryAddress?->latitude !== null ? (float) $clientPrimaryAddress->latitude : null,
+            $clientPrimaryAddress?->longitude !== null ? (float) $clientPrimaryAddress->longitude : null,
+        );
+
+        $baseQuery = Provider::leftJoin('users as provider_user', 'provider_user.id', '=', 'providers.user_id')
             ->leftJoin(DB::raw("
         (
           SELECT DISTINCT ON (source_id)
             *
           FROM addresses
           WHERE source = 'provider'
+          AND deleted_at IS NULL
           ORDER BY source_id, is_primary DESC
         ) as provider_address
       "), 'provider_address.source_id', '=', 'providers.id')
             ->whereNotNull('provider_address.id')
-            ->where('provider_address.city_id', $clientPrimaryAddress?->city_id)
             ->whereNotIn('providers.id', $blockedProviderIds)
             ->whereIn('providers.id', $providersWithWorkingDays)
+            ->whereNull('providers.deleted_at')
             ->whereNotNull('providers.daily_price_8h')
             ->whereNotNull('providers.daily_price_6h')
             ->whereNotNull('providers.daily_price_4h')
@@ -50,6 +56,8 @@ class SearchService
                 'providers.id as provider_id',
                 'provider_user.name as provider_name',
                 'provider_address.district',
+                'provider_address.latitude as provider_latitude',
+                'provider_address.longitude as provider_longitude',
                 'providers.average_rating',
                 'providers.total_services',
                 'providers.daily_price_8h',
@@ -64,9 +72,22 @@ class SearchService
           WHERE reviews.origin = 'provider'
           AND schedules.provider_id = providers.id
         ) as total_reviews"),
+                $distanceSelect,
             )
-            ->orderBy('providers.average_rating', 'desc')
-            ->get()
+            ->orderBy('providers.average_rating', 'desc');
+
+        $providers = (clone $baseQuery)
+            ->when(
+                $clientPrimaryAddress?->city_id,
+                fn ($query, int $cityId) => $query->where('provider_address.city_id', $cityId)
+            )
+            ->get();
+
+        if ($providers->isEmpty() && $clientPrimaryAddress?->city_id) {
+            $providers = $baseQuery->get();
+        }
+
+        return $providers
             ->when(
                 $date,
                 fn ($collection) => $collection->whereIn(
@@ -79,4 +100,30 @@ class SearchService
             )
             ->toArray();
     }
+
+    private function distanceSelect(?float $clientLatitude, ?float $clientLongitude): \Illuminate\Contracts\Database\Query\Expression
+    {
+        if ($clientLatitude === null || $clientLongitude === null) {
+            return DB::raw('NULL as distance_km');
+        }
+
+        return DB::raw("
+            CASE
+                WHEN provider_address.latitude IS NOT NULL
+                AND provider_address.longitude IS NOT NULL
+                THEN ROUND((
+                    6371 * acos(
+                        least(1, greatest(-1,
+                            cos(radians({$clientLatitude}))
+                            * cos(radians(provider_address.latitude))
+                            * cos(radians(provider_address.longitude) - radians({$clientLongitude}))
+                            + sin(radians({$clientLatitude}))
+                            * sin(radians(provider_address.latitude))
+                        ))
+                    )
+                )::numeric, 1)
+                ELSE NULL
+            END as distance_km
+        ");
+    }
 }

+ 210 - 0
app/Services/WebhookService.php

@@ -0,0 +1,210 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\Payment;
+use App\Models\Webhook;
+use App\Services\Pagarme\PagarmePaymentService;
+use Illuminate\Support\Facades\Log;
+
+class WebhookService
+{
+    private const PAYMENT_EVENTS = [
+        'order.paid',
+        'order.payment_failed',
+        'order.canceled',
+        'order.created',
+        'order.closed',
+        'order.updated',
+        'charge.created',
+        'charge.paid',
+        'charge.payment_failed',
+        'charge.refunded',
+        'charge.pending',
+        'charge.processing',
+        'charge.underpaid',
+        'charge.overpaid',
+        'charge.partial_canceled',
+        'charge.chargedback',
+        'charge.updated',
+    ];
+
+    public function __construct(
+        private readonly PagarmePaymentService $pagarmePaymentService,
+        private readonly PaymentService $paymentService,
+    ) {}
+
+    public function handlePagarme(array $payload): ?Payment
+    {
+        $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', [
+            'hook_id' => $payload['id'] ?? null,
+            'event'   => $event,
+        ]);
+
+        if (! in_array($event, self::PAYMENT_EVENTS, true)) {
+            $webhook->update([
+                'status'       => 'ignored',
+                'processed_at' => now(),
+            ]);
+
+            return null;
+        }
+
+        $orderResponse = $this->normalizeOrderResponse($event, $data);
+        $payment       = $this->findPayment($orderResponse);
+
+        if (! $payment) {
+            $webhook->update([
+                'status'       => 'payment_not_found',
+                'processed_at' => now(),
+            ]);
+
+            Log::channel('pagarme')->warning('Payment not found for Pagar.me webhook', [
+                'hook_id'        => $payload['id'] ?? null,
+                'event'          => $event,
+                'order_id'       => $orderResponse['id'] ?? null,
+                'charge_id'      => $orderResponse['charges'][0]['id'] ?? null,
+                'transaction_id' => $orderResponse['charges'][0]['last_transaction']['id'] ?? null,
+                'metadata'       => $orderResponse['metadata'] ?? [],
+            ]);
+
+            return null;
+        }
+
+        try {
+            $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+
+            $this->paymentService->syncScheduleStatusAfterPayment($payment->schedule, $payment);
+
+            $webhook->update([
+                'payment_id'   => $payment->id,
+                'status'       => 'processed',
+                'processed_at' => now(),
+            ]);
+
+            return $payment;
+        } catch (\Throwable $e) {
+            $webhook->update([
+                'payment_id'    => $payment->id,
+                'status'        => 'failed',
+                'processed_at'  => now(),
+                'error_message' => $e->getMessage(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    private function recordWebhook(array $payload, ?string $event, array $data): Webhook
+    {
+        $references = $this->extractReferences($event, $data);
+        $hookId     = $payload['id'] ?? null;
+
+        $attributes = [
+            'hook_id'        => $hookId,
+            'provider'       => 'pagarme',
+            'event'          => $event,
+            'account_id'     => $payload['account']['id'] ?? null,
+            'order_id'       => $references['order_id'],
+            'charge_id'      => $references['charge_id'],
+            'transaction_id' => $references['transaction_id'],
+            'status'         => 'received',
+            'payload'        => $payload,
+            'received_at'    => now(),
+            'processed_at'   => null,
+            'error_message'  => null,
+        ];
+
+        if (empty($hookId)) {
+            return Webhook::create($attributes);
+        }
+
+        $webhook = Webhook::firstOrNew([
+            'provider' => 'pagarme',
+            'hook_id'  => $hookId,
+        ]);
+
+        $webhook->fill([
+            ...$attributes,
+            'attempts_count' => $webhook->exists ? $webhook->attempts_count + 1 : 1,
+        ])->save();
+
+        return $webhook;
+    }
+
+    private function extractReferences(?string $event, array $data): array
+    {
+        if (str_starts_with((string) $event, 'order.')) {
+            $charge = $data['charges'][0] ?? [];
+
+            return [
+                'order_id'       => $data['id'] ?? null,
+                'charge_id'      => $charge['id'] ?? null,
+                'transaction_id' => $charge['last_transaction']['id'] ?? null,
+            ];
+        }
+
+        if (str_starts_with((string) $event, 'charge.')) {
+            return [
+                'order_id'       => $data['order']['id'] ?? $data['order_id'] ?? null,
+                'charge_id'      => $data['id'] ?? null,
+                'transaction_id' => $data['last_transaction']['id'] ?? null,
+            ];
+        }
+
+        return [
+            'order_id'       => null,
+            'charge_id'      => null,
+            'transaction_id' => null,
+        ];
+    }
+
+    private function normalizeOrderResponse(?string $event, array $data): array
+    {
+        if (str_starts_with((string) $event, 'order.')) {
+            return $data;
+        }
+
+        return [
+            'id'       => $data['order']['id'] ?? $data['order_id'] ?? null,
+            'metadata' => $data['metadata'] ?? $data['order']['metadata'] ?? [],
+            'charges'  => [$data],
+        ];
+    }
+
+    private function findPayment(array $orderResponse): ?Payment
+    {
+        $charge        = $orderResponse['charges'][0] ?? [];
+        $transaction   = $charge['last_transaction'] ?? [];
+        $metadata      = $orderResponse['metadata'] ?? $charge['metadata'] ?? [];
+        $metadataId    = filter_var($metadata['payment_id'] ?? null, FILTER_VALIDATE_INT) ?: null;
+        $chargeId      = $charge['id'] ?? null;
+        $transactionId = $transaction['id'] ?? null;
+        $orderId       = $orderResponse['id'] ?? null;
+        $references    = array_filter([$metadataId, $chargeId, $transactionId, $orderId]);
+
+        if (empty($references)) {
+            return null;
+        }
+
+        return Payment::query()
+            ->where('gateway_provider', 'pagarme')
+            ->where(function ($query) use ($metadataId, $chargeId, $transactionId, $orderId) {
+                $query
+                    ->when($metadataId, fn ($query) => $query->orWhere('id', $metadataId))
+                    ->when($chargeId, fn ($query) => $query->orWhere('gateway_entity_reference', $chargeId))
+                    ->when($transactionId, fn ($query) => $query->orWhere('gateway_operation_reference', $transactionId))
+                    ->when($orderId, fn ($query) => $query->orWhere(function ($query) use ($orderId) {
+                        $query->where('gateway_entity_label', 'order')
+                            ->where('gateway_entity_reference', $orderId);
+                    }));
+            })
+            ->latest('id')
+            ->first();
+    }
+}

+ 5 - 3
config/services.php

@@ -36,9 +36,11 @@ return [
     ],
 
     'pagarme' => [
-        'secret_key'           => env('PAGARME_SECRET_KEY'),
-        'service_referer_name' => env('PAGARME_SERVICE_REFERER_NAME', env('APP_NAME', 'Laravel')),
-        'base_url'             => env('PAGARME_BASE_URL', 'https://api.pagar.me/core/v5'),
+        'secret_key'            => env('PAGARME_SECRET_KEY'),
+        'service_referer_name'  => env('PAGARME_SERVICE_REFERER_NAME', env('APP_NAME', 'Laravel')),
+        'base_url'              => env('PAGARME_BASE_URL', 'https://api.pagar.me/core/v5'),
+        'webhook_token'         => env('PAGARME_WEBHOOK_TOKEN'),
+        'platform_recipient_id' => env('PAGARME_PLATFORM_RECIPIENT_ID'),
     ],
 
 ];

+ 47 - 0
database/migrations/2026_05_20_000002_mark_existing_client_addresses_as_primary.php

@@ -0,0 +1,47 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Support\Facades\DB;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        $driver = DB::getDriverName();
+
+        if ($driver === 'pgsql') {
+            DB::statement(<<<'SQL'
+                UPDATE addresses
+                SET is_primary = true
+                WHERE id IN (
+                    SELECT DISTINCT ON (source_id) id
+                    FROM addresses
+                    WHERE source = 'client'
+                      AND deleted_at IS NULL
+                    ORDER BY source_id, is_primary DESC, id ASC
+                )
+            SQL);
+
+            return;
+        }
+
+        if ($driver === 'mysql') {
+            DB::statement(<<<'SQL'
+                UPDATE addresses a
+                JOIN (
+                    SELECT MIN(id) AS id
+                    FROM addresses
+                    WHERE source = 'client'
+                      AND deleted_at IS NULL
+                    GROUP BY source_id
+                ) first_address ON first_address.id = a.id
+                SET a.is_primary = true
+            SQL);
+        }
+    }
+
+    public function down(): void
+    {
+        // Data backfill only. Do not unset primary addresses on rollback.
+    }
+};

+ 17 - 0
database/migrations/2026_05_22_000002_drop_provider_payment_methods_table.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::dropIfExists('provider_payment_methods');
+    }
+
+    public function down(): void
+    {
+        // The provider bank account is stored in providers.recipient_default_bank_account.
+    }
+};

+ 25 - 0
database/migrations/2026_05_22_000003_add_gateway_card_id_to_client_payment_methods_table.php

@@ -0,0 +1,25 @@
+<?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('client_payment_methods', function (Blueprint $table) {
+            $table->string('gateway_card_id')->nullable()->after('token');
+
+            $table->index('gateway_card_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('client_payment_methods', function (Blueprint $table) {
+            $table->dropIndex(['gateway_card_id']);
+            $table->dropColumn('gateway_card_id');
+        });
+    }
+};

+ 49 - 0
database/migrations/2026_05_22_023640_create_webhooks_table.php

@@ -0,0 +1,49 @@
+<?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('webhooks', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('payment_id')->nullable()->constrained('payments')->nullOnDelete();
+
+            $table->string('provider')->default('pagarme');
+            $table->string('hook_id')->nullable();
+            $table->string('event')->nullable();
+            $table->string('account_id')->nullable();
+            $table->string('order_id')->nullable();
+            $table->string('charge_id')->nullable();
+            $table->string('transaction_id')->nullable();
+            $table->string('status')->default('received');
+            $table->unsignedInteger('attempts_count')->default(1);
+            $table->json('payload');
+            $table->timestamp('received_at')->nullable();
+            $table->timestamp('processed_at')->nullable();
+            $table->text('error_message')->nullable();
+
+            $table->timestamps();
+
+            $table->unique(['provider', 'hook_id']);
+            $table->index('payment_id');
+            $table->index('provider');
+            $table->index('event');
+            $table->index('account_id');
+            $table->index('order_id');
+            $table->index('charge_id');
+            $table->index('transaction_id');
+            $table->index('status');
+            $table->index('received_at');
+            $table->index('processed_at');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('webhooks');
+    }
+};

+ 0 - 6
database/seeders/PermissionSeeder.php

@@ -148,12 +148,6 @@ class PermissionSeeder extends Seeder
                         'bits'        => 271,
                         'children'    => [],
                     ],
-                    [
-                        'scope'       => 'config.provider_payment_method',
-                        'description' => 'Configurações de Métodos de Pagamento do Prestador',
-                        'bits'        => 271,
-                        'children'    => [],
-                    ],
                     [
                         'scope'       => 'config.provider_working_day',
                         'description' => 'Configurações de Dias de Trabalho do Prestador',

+ 0 - 51
database/seeders/ProviderSeeder.php

@@ -2,15 +2,12 @@
 
 namespace Database\Seeders;
 
-use App\Enums\AccountTypeEnum;
 use App\Enums\ApprovalStatusEnum;
-use App\Enums\BankAccountTypeEnum;
 use App\Enums\UserTypeEnum;
 use App\Enums\WorkingPeriodEnum;
 use App\Models\Address;
 use App\Models\City;
 use App\Models\Provider;
-use App\Models\ProviderPaymentMethod;
 use App\Models\ProviderServicesType;
 use App\Models\ProviderSpeciality;
 use App\Models\ProviderWorkingDay;
@@ -67,10 +64,6 @@ class ProviderSeeder extends Seeder
                     'latitude'     => -23.550520,
                     'longitude'    => -46.633308,
                 ],
-                'payment_method' => [
-                    'account_type' => AccountTypeEnum::PIX,
-                    'pix_key'      => 'prestador.teste@softpar.inf.br',
-                ],
                 'service_types' => [
                     'Limpeza residencial',
                     'Passadoria',
@@ -130,13 +123,6 @@ class ProviderSeeder extends Seeder
                     'latitude'     => -22.906847,
                     'longitude'    => -43.172896,
                 ],
-                'payment_method' => [
-                    'account_type'      => AccountTypeEnum::BANK_ACCOUNT,
-                    'bank_account_type' => BankAccountTypeEnum::CHECKING,
-                    'agency'            => '1234',
-                    'account'           => '998877',
-                    'digit'             => '0',
-                ],
                 'service_types' => [
                     'Limpeza comercial',
                     'Limpeza pós-obra',
@@ -196,10 +182,6 @@ class ProviderSeeder extends Seeder
                     'latitude'     => -19.916681,
                     'longitude'    => -43.934493,
                 ],
-                'payment_method' => [
-                    'account_type' => AccountTypeEnum::PIX,
-                    'pix_key'      => 'ana.costa@softpar.inf.br',
-                ],
                 'service_types' => [
                     'Lavanderia',
                     'Passadoria',
@@ -259,13 +241,6 @@ class ProviderSeeder extends Seeder
                     'latitude'     => -25.428356,
                     'longitude'    => -49.273251,
                 ],
-                'payment_method' => [
-                    'account_type'      => AccountTypeEnum::BANK_ACCOUNT,
-                    'bank_account_type' => BankAccountTypeEnum::SAVINGS,
-                    'agency'            => '4321',
-                    'account'           => '112233',
-                    'digit'             => '5',
-                ],
                 'service_types' => [
                     'Limpeza residencial',
                     'Limpeza pós-obra',
@@ -325,10 +300,6 @@ class ProviderSeeder extends Seeder
                     'latitude'     => -30.034647,
                     'longitude'    => -51.217659,
                 ],
-                'payment_method' => [
-                    'account_type' => AccountTypeEnum::PIX,
-                    'pix_key'      => 'renata.alves@softpar.inf.br',
-                ],
                 'service_types' => [
                     'Limpeza residencial',
                     'Limpeza comercial',
@@ -363,7 +334,6 @@ class ProviderSeeder extends Seeder
                 );
 
                 $this->seedAddress($provider->id, $data['address']);
-                $this->seedPaymentMethod($provider->id, $data['payment_method']);
                 $this->seedProviderServicesTypes($provider->id, $data['service_types']);
                 $this->seedProviderSpecialities($provider->id, $data['specialities']);
                 $this->seedProviderWorkingDays($provider->id, $data['working_days']);
@@ -409,27 +379,6 @@ class ProviderSeeder extends Seeder
         );
     }
 
-    private function seedPaymentMethod(int $providerId, array $data): void
-    {
-        ProviderPaymentMethod::updateOrCreate(
-            [
-                'provider_id'  => $providerId,
-                'account_type' => $data['account_type']->value,
-            ],
-            [
-                'provider_id'       => $providerId,
-                'account_type'      => $data['account_type']->value,
-                'pix_key'           => $data['pix_key'] ?? null,
-                'bank_account_type' => isset($data['bank_account_type'])
-                    ? $data['bank_account_type']->value
-                    : null,
-                'agency'  => $data['agency'] ?? null,
-                'account' => $data['account'] ?? null,
-                'digit'   => $data['digit'] ?? null,
-            ]
-        );
-    }
-
     private function seedProviderServicesTypes(int $providerId, array $descriptions): void
     {
         $serviceTypeIds = ServiceType::query()

+ 0 - 3
database/seeders/UserTypePermissionSeeder.php

@@ -44,7 +44,6 @@ class UserTypePermissionSeeder extends Seeder
                         ['scope' => 'config.provider', 'bits' => 271],
                         ['scope' => 'config.provider_speciality', 'bits' => 271],
                         ['scope' => 'config.provider_services_types', 'bits' => 271],
-                        ['scope' => 'config.provider_payment_method', 'bits' => 271],
                         ['scope' => 'config.provider_working_day', 'bits' => 271],
                         ['scope' => 'config.provider_blocked_day', 'bits' => 271],
                         ['scope' => 'config.improvement_type', 'bits' => 271],
@@ -64,7 +63,6 @@ class UserTypePermissionSeeder extends Seeder
                         ['scope' => 'config.address', 'bits' => 271],
                         ['scope' => 'config.city', 'bits' => 1],
                         ['scope' => 'config.provider', 'bits' => 5],
-                        ['scope' => 'config.provider_payment_method', 'bits' => 271],
                         ['scope' => 'config.provider_working_day', 'bits' => 271],
                         ['scope' => 'config.provider_blocked_day', 'bits' => 271],
                         ['scope' => 'config.provider_services_types', 'bits' => 271],
@@ -84,7 +82,6 @@ class UserTypePermissionSeeder extends Seeder
                         ['scope' => 'config.user', 'bits' => 5],
                         ['scope' => 'config.address', 'bits' => 271],
                         ['scope' => 'config.city', 'bits' => 1],
-                        ['scope' => 'config.provider_payment_method', 'bits' => 271],
                         ['scope' => 'config.client_favorite_provider', 'bits' => 271],
                         ['scope' => 'config.client_payment_method', 'bits' => 271],
                         ['scope' => 'config.provider_working_day', 'bits' => 271],

+ 3 - 0
lang/en/messages.php

@@ -14,4 +14,7 @@ return [
     'user_not_found_or_code_not_validated' => 'User not found or invalid code.',
     'provider_approved'                    => 'Provider approved successfully.',
     'provider_rejected'                    => 'Provider rejected.',
+    'payment_confirmed'                    => 'Payment confirmed.',
+    'payment_pending_confirmation'         => 'Payment created and awaiting confirmation.',
+    'payment_not_confirmed'                => 'Payment not confirmed.',
 ];

+ 3 - 0
lang/es/messages.php

@@ -14,4 +14,7 @@ return [
     'user_not_found_or_code_not_validated' => 'Usuario no encontrado o código inválido.',
     'provider_approved'                    => 'Prestador aprobado exitosamente.',
     'provider_rejected'                    => 'Prestador rechazado.',
+    'payment_confirmed'                    => 'Pago confirmado.',
+    'payment_pending_confirmation'         => 'Pago creado y esperando confirmación.',
+    'payment_not_confirmed'                => 'Pago no confirmado.',
 ];

+ 3 - 0
lang/pt/messages.php

@@ -14,4 +14,7 @@ return [
     'user_not_found_or_code_not_validated' => 'Usuário não encontrado ou código inválido.',
     'provider_approved'                    => 'Prestador aprovado com sucesso.',
     'provider_rejected'                    => 'Prestador recusado.',
+    'payment_confirmed'                    => 'Pagamento confirmado.',
+    'payment_pending_confirmation'         => 'Pagamento criado e aguardando confirmação.',
+    'payment_not_confirmed'                => 'Pagamento não confirmado.',
 ];

+ 16 - 0
pint.json

@@ -0,0 +1,16 @@
+{
+    "preset": "laravel",
+    "exclude": [
+        "database/migrations",
+        "routes"
+    ],
+    "rules": {
+        "binary_operator_spaces": {
+            "default": "single_space",
+            "operators": {
+                "=>": "align_single_space_minimal",
+                "=": "align_single_space_minimal"
+            }
+        }
+    }
+}

+ 1 - 0
routes/authRoutes/payment.php

@@ -5,6 +5,7 @@ use App\Http\Controllers\PaymentController;
 
 Route::get('/payment',         [PaymentController::class, 'index'])->middleware('permission:payment,view');
 Route::post('/payment',        [PaymentController::class, 'store'])->middleware('permission:payment,add');
+Route::post('/payment/schedule/{schedule}/pay', [PaymentController::class, 'paySchedule'])->middleware('permission:config.schedule,edit');
 Route::get('/payment/{id}',    [PaymentController::class, 'show'])->middleware('permission:payment,view');
 Route::put('/payment/{id}',    [PaymentController::class, 'update'])->middleware('permission:payment,edit');
 Route::delete('/payment/{id}', [PaymentController::class, 'destroy'])->middleware('permission:payment,delete');

+ 1 - 0
routes/authRoutes/provider.php

@@ -10,4 +10,5 @@ Route::get('/provider',                [ProviderController::class, 'index'])->mi
 Route::post('/provider',               [ProviderController::class, 'store'])->middleware('permission:config.provider,add');
 Route::get('/provider/{id}',           [ProviderController::class, 'show'])->middleware('permission:config.provider,view');
 Route::put('/provider/{id}',           [ProviderController::class, 'update'])->middleware('permission:config.provider,edit');
+Route::patch('/provider/{id}/bank-account', [ProviderController::class, 'updateBankAccount'])->middleware('permission:config.provider,edit');
 Route::delete('/provider/{id}',        [ProviderController::class, 'destroy'])->middleware('permission:config.provider,delete');

+ 0 - 10
routes/authRoutes/provider_payment_method.php

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

+ 6 - 0
routes/noAuthRoutes/pagarme_webhook.php

@@ -0,0 +1,6 @@
+<?php
+
+use App\Http\Controllers\WebhookController;
+use Illuminate\Support\Facades\Route;
+
+Route::post('/webhooks/pagarme', [WebhookController::class, 'pagarme']);