Gustavo Zanatta пре 2 недеља
родитељ
комит
50dd3d14c9
2 измењених фајлова са 889 додато и 887 уклоњено
  1. 2 1
      app/Http/Controllers/PaymentController.php
  2. 887 886
      app/Services/PaymentService.php

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

@@ -13,6 +13,7 @@ use App\Models\ServicePackage;
 use App\Services\PaymentService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Log;
 
 class PaymentController extends Controller
 {
@@ -64,7 +65,7 @@ class PaymentController extends Controller
     public function payServicePackage(PayServicePackageRequest $request, int $servicePackageId): JsonResponse
     {
         $validated = $request->validated();
-
+        Log::info($validated);
         try {
             $item = $this->service->payServicePackage(
                 servicePackageId:      $servicePackageId,

+ 887 - 886
app/Services/PaymentService.php

@@ -22,1012 +22,1013 @@ use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Support\Collection as SupportCollection;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Str;
 
 class PaymentService
 {
-    public function __construct(
-        private readonly PagarmePaymentService $pagarmePaymentService,
-    ) {}
-
-    public function getAll(): Collection
-    {
-        return Payment::query()
-            ->with(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule'])
-            ->orderBy('created_at', 'desc')
-            ->get();
+  public function __construct(
+    private readonly PagarmePaymentService $pagarmePaymentService,
+  ) {}
+
+  public function getAll(): Collection
+  {
+    return Payment::query()
+      ->with(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule'])
+      ->orderBy('created_at', 'desc')
+      ->get();
+  }
+
+  public function findById(int $id): ?Payment
+  {
+    return Payment::query()
+      ->with(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule'])
+      ->find($id);
+  }
+
+  public function create(array $data): Payment
+  {
+    return Payment::create($data);
+  }
+
+  public function update(int $id, array $data): ?Payment
+  {
+    $model = $this->findById($id);
+
+    if (! $model) {
+      return null;
     }
 
-    public function findById(int $id): ?Payment
-    {
-        return Payment::query()
-            ->with(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule'])
-            ->find($id);
-    }
-
-    public function create(array $data): Payment
-    {
-        return Payment::create($data);
-    }
-
-    public function update(int $id, array $data): ?Payment
-    {
-        $model = $this->findById($id);
+    $model->update($data);
 
-        if (! $model) {
-            return null;
-        }
+    return $model->fresh();
+  }
 
-        $model->update($data);
+  public function delete(int $id): bool
+  {
+    $model = $this->findById($id);
 
-        return $model->fresh();
+    if (! $model) {
+      return false;
     }
 
-    public function delete(int $id): bool
-    {
-        $model = $this->findById($id);
+    return $model->delete();
+  }
 
-        if (! $model) {
-            return false;
-        }
+  //
 
-        return $model->delete();
-    }
+  public function platformFees(): array
+  {
+    return $this->pagarmePaymentService->platformFeeRates();
+  }
 
-    //
+  public function payServicePackage(
+    int    $servicePackageId,
+    string $paymentMethod,
+    ?int   $clientPaymentMethodId = null,
+    array  $options               = [],
+  ): Payment {
+    $userId = (int) Auth::id();
 
-    public function platformFees(): array
-    {
-        return $this->pagarmePaymentService->platformFeeRates();
+    if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
+      throw new PaymentException;
     }
+    Log::info('1');
+    $paymentData = DB::transaction(function () use (
+      $servicePackageId,
+      $userId,
+      $paymentMethod,
+      $clientPaymentMethodId,
+      $options,
+    ): array {
+      $servicePackage = ServicePackage::query()
+        ->lockForUpdate()
+        ->with(['client', 'provider', 'items.schedule.client', 'items.schedule.provider', 'items.schedule.customSchedule.serviceType'])
+        ->findOrFail($servicePackageId);
+
+      if ($servicePackage->client?->user_id !== $userId) {
+        throw new AuthorizationException;
+      }
+Log::info('2');
+      $schedules = $this->activePackageSchedules($servicePackage);
+
+      $this->validateServicePackageForPayment($servicePackage, $schedules);
+
+      $existingPayment = Payment::query()
+        ->where('service_package_id', $servicePackage->id)
+        ->whereIn('status', [
+          PaymentStatusEnum::PENDING->value,
+          PaymentStatusEnum::PROCESSING->value,
+          PaymentStatusEnum::AUTHORIZED->value,
+          PaymentStatusEnum::PAID->value,
+        ])
+        ->latest('id')
+        ->first();
+Log::info('3');
+      if ($existingPayment) {
+        if ($this->isExpiredPixPayment($existingPayment)) {
+          $existingPayment->forceFill([
+            'status'          => PaymentStatusEnum::FAILED,
+            'failed_at'       => now(),
+            'failure_message' => 'Pagamento Pix expirado.',
+          ])->save();
 
-    public function payServicePackage(
-        int    $servicePackageId,
-        string $paymentMethod,
-        ?int   $clientPaymentMethodId = null,
-        array  $options               = [],
-    ): Payment {
-        $userId = (int) Auth::id();
-
-        if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
+          PaymentSplit::query()
+            ->where('payment_id', $existingPayment->id)
+            ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+        } elseif ($this->isStaleIncompleteGatewayPayment($existingPayment)) {
+          if ($existingPayment->payment_method !== $paymentMethod) {
             throw new PaymentException;
-        }
+          }
+Log::info('4');
+          [, $cardId] = $this->resolveCard(
+            clientId: $servicePackage->client_id,
+            paymentMethod: $paymentMethod,
+            clientPaymentMethodId: $clientPaymentMethodId ?? $existingPayment->client_payment_method_id,
+            cardId: data_get($options, 'card_id'),
+          );
+
+          $this->servicePackagePaymentTotals($schedules, $paymentMethod);
+Log::info('5');
+          return [
+            'payment'   => $existingPayment,
+            'schedules' => $schedules,
+            'cardId'    => $cardId,
+          ];
+        } else {
+          if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
+            throw new PaymentException;
+          }
 
-        $paymentData = DB::transaction(function () use (
-            $servicePackageId,
-            $userId,
-            $paymentMethod,
-            $clientPaymentMethodId,
-            $options,
-        ): array {
-            $servicePackage = ServicePackage::query()
-                ->lockForUpdate()
-                ->with(['client', 'provider', 'items.schedule.client', 'items.schedule.provider', 'items.schedule.customSchedule.serviceType'])
-                ->findOrFail($servicePackageId);
-
-            if ($servicePackage->client?->user_id !== $userId) {
-                throw new AuthorizationException;
-            }
-
-            $schedules = $this->activePackageSchedules($servicePackage);
-
-            $this->validateServicePackageForPayment($servicePackage, $schedules);
-
-            $existingPayment = Payment::query()
-                ->where('service_package_id', $servicePackage->id)
-                ->whereIn('status', [
-                    PaymentStatusEnum::PENDING->value,
-                    PaymentStatusEnum::PROCESSING->value,
-                    PaymentStatusEnum::AUTHORIZED->value,
-                    PaymentStatusEnum::PAID->value,
-                ])
-                ->latest('id')
-                ->first();
-
-            if ($existingPayment) {
-                if ($this->isExpiredPixPayment($existingPayment)) {
-                    $existingPayment->forceFill([
-                        'status'          => PaymentStatusEnum::FAILED,
-                        'failed_at'       => now(),
-                        'failure_message' => 'Pagamento Pix expirado.',
-                    ])->save();
-
-                    PaymentSplit::query()
-                        ->where('payment_id', $existingPayment->id)
-                        ->update(['status' => PaymentSplitStatusEnum::FAILED]);
-                } elseif ($this->isStaleIncompleteGatewayPayment($existingPayment)) {
-                    if ($existingPayment->payment_method !== $paymentMethod) {
-                        throw new PaymentException;
-                    }
-
-                    [, $cardId] = $this->resolveCard(
-                        clientId:              $servicePackage->client_id,
-                        paymentMethod:         $paymentMethod,
-                        clientPaymentMethodId: $clientPaymentMethodId ?? $existingPayment->client_payment_method_id,
-                        cardId:                data_get($options, 'card_id'),
-                    );
-
-                    $this->servicePackagePaymentTotals($schedules, $paymentMethod);
-
-                    return [
-                        'payment'   => $existingPayment,
-                        'schedules' => $schedules,
-                        'cardId'    => $cardId,
-                    ];
-                } else {
-                    if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
-                        throw new PaymentException;
-                    }
-
-                    return ['existing' => $existingPayment];
-                }
-            }
-
-            if ($servicePackage->status !== ServicePackageStatusEnum::OPEN) {
-                throw new PaymentException;
-            }
-
-            [$clientPaymentMethod, $cardId] = $this->resolveCard(
-                clientId:              $servicePackage->client_id,
-                paymentMethod:         $paymentMethod,
-                clientPaymentMethodId: $clientPaymentMethodId,
-                cardId:                data_get($options, 'card_id'),
-            );
-
-            $totals = $this->servicePackagePaymentTotals($schedules, $paymentMethod);
-
-            $payment = Payment::create([
-                'schedule_id'              => null,
-                'service_package_id'       => $servicePackage->id,
-                'client_id'                => $servicePackage->client_id,
-                'provider_id'              => $servicePackage->provider_id,
-                'client_payment_method_id' => $paymentMethod === 'credit_card' ? $clientPaymentMethod?->id : null,
-                'gateway_provider'         => 'pagarme',
-                'gateway_code'             => 'payment-'.(string) Str::uuid(),
-                'payment_method'           => $paymentMethod,
-                'status'                   => PaymentStatusEnum::PENDING,
-                'gross_amount'             => data_get($totals, 'gross_amount'),
-                'gateway_fee_amount'       => 0,
-                'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
-                'net_amount'               => data_get($totals, 'gross_amount'),
-                'currency'                 => 'BRL',
-                'installments'             => 1,
-                'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
-
-                'metadata' => [
-                    'service_package_id' => (string) $servicePackage->id,
-                    'schedule_ids'       => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
-                    'service_amount'     => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
-                    'platform_fee'       => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
-                ],
-            ]);
-
-            PaymentSplit::create([
-                'payment_id'                        => $payment->id,
-                'provider_id'                       => $servicePackage->provider_id,
-                'gateway_provider'                  => 'pagarme',
-                'gateway_transfer_target_reference' => $servicePackage->provider->recipient_id,
-                'gateway_transfer_target_label'     => 'recipient',
-                'status'                            => PaymentSplitStatusEnum::PENDING,
-                'gross_amount'                      => data_get($totals, 'service_amount'),
-                'gateway_fee_amount'                => 0,
-                'net_amount'                        => data_get($totals, 'service_amount'),
-
-                'metadata' => [
-                    'service_package_id' => (string) $servicePackage->id,
-                    'schedule_ids'       => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
-                ],
-            ]);
-
-            return compact('payment', 'schedules', 'cardId');
-        });
-
-        if (data_get($paymentData, 'existing')) {
-            $existingPayment = data_get($paymentData, 'existing');
-
-            $this->syncPaymentTargets($existingPayment);
-
-            return $existingPayment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
+          return ['existing' => $existingPayment];
         }
+      }
+Log::info('6');
+      if ($servicePackage->status !== ServicePackageStatusEnum::OPEN) {
+        throw new PaymentException;
+      }
+
+      [$clientPaymentMethod, $cardId] = $this->resolveCard(
+        clientId: $servicePackage->client_id,
+        paymentMethod: $paymentMethod,
+        clientPaymentMethodId: $clientPaymentMethodId,
+        cardId: data_get($options, 'card_id'),
+      );
+Log::info('7');
+      $totals = $this->servicePackagePaymentTotals($schedules, $paymentMethod);
+
+      $payment = Payment::create([
+        'schedule_id'              => null,
+        'service_package_id'       => $servicePackage->id,
+        'client_id'                => $servicePackage->client_id,
+        'provider_id'              => $servicePackage->provider_id,
+        'client_payment_method_id' => $paymentMethod === 'credit_card' ? $clientPaymentMethod?->id : null,
+        'gateway_provider'         => 'pagarme',
+        'gateway_code'             => 'payment-' . (string) Str::uuid(),
+        'payment_method'           => $paymentMethod,
+        'status'                   => PaymentStatusEnum::PENDING,
+        'gross_amount'             => data_get($totals, 'gross_amount'),
+        'gateway_fee_amount'       => 0,
+        'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
+        'net_amount'               => data_get($totals, 'gross_amount'),
+        'currency'                 => 'BRL',
+        'installments'             => 1,
+        'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
+
+        'metadata' => [
+          'service_package_id' => (string) $servicePackage->id,
+          'schedule_ids'       => $schedules->pluck('id')->map(fn($id) => (string) $id)->all(),
+          'service_amount'     => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
+          'platform_fee'       => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
+        ],
+      ]);
+Log::info('8');
+      PaymentSplit::create([
+        'payment_id'                        => $payment->id,
+        'provider_id'                       => $servicePackage->provider_id,
+        'gateway_provider'                  => 'pagarme',
+        'gateway_transfer_target_reference' => $servicePackage->provider->recipient_id,
+        'gateway_transfer_target_label'     => 'recipient',
+        'status'                            => PaymentSplitStatusEnum::PENDING,
+        'gross_amount'                      => data_get($totals, 'service_amount'),
+        'gateway_fee_amount'                => 0,
+        'net_amount'                        => data_get($totals, 'service_amount'),
+
+        'metadata' => [
+          'service_package_id' => (string) $servicePackage->id,
+          'schedule_ids'       => $schedules->pluck('id')->map(fn($id) => (string) $id)->all(),
+        ],
+      ]);
+
+      return compact('payment', 'schedules', 'cardId');
+    });
+Log::info('9');
+    if (data_get($paymentData, 'existing')) {
+      $existingPayment = data_get($paymentData, 'existing');
+Log::info('10');
+      $this->syncPaymentTargets($existingPayment);
+
+      return $existingPayment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
+    }
 
-        /** @var Payment $payment */
-        $payment = data_get($paymentData, 'payment');
-
-        /** @var SupportCollection $schedules */
-        $schedules = data_get($paymentData, 'schedules');
+    /** @var Payment $payment */
+    $payment = data_get($paymentData, 'payment');
 
-        try {
-            $schedules->first()->ensureCustomerPhone(data_get($options, 'phone'));
+    /** @var SupportCollection $schedules */
+    $schedules = data_get($paymentData, 'schedules');
 
-            $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
-                payment:       $payment,
-                schedules:     $schedules,
-                paymentMethod: $paymentMethod,
-                cardId:        data_get($paymentData, 'cardId'),
-                options:       $options,
-            );
-        } catch (\Throwable $e) {
-            $this->failPayment($payment, $e->getMessage());
+    try {
+      $schedules->first()->ensureCustomerPhone(data_get($options, 'phone'));
 
-            throw $e;
-        }
+      $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
+        payment: $payment,
+        schedules: $schedules,
+        paymentMethod: $paymentMethod,
+        cardId: data_get($paymentData, 'cardId'),
+        options: $options,
+      );
+    } catch (\Throwable $e) {
+      $this->failPayment($payment, $e->getMessage());
 
-        $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+      throw $e;
+    }
 
-        $this->syncPaymentTargets($payment);
+    $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
 
-        if ($payment->status === PaymentStatusEnum::FAILED) {
-            throw new PaymentFailedException;
-        }
+    $this->syncPaymentTargets($payment);
 
-        return $payment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
+    if ($payment->status === PaymentStatusEnum::FAILED) {
+      throw new PaymentFailedException;
     }
 
-    //
+    return $payment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
+  }
 
-    public function getOrCreateServicePackagePixPayment(ServicePackage $servicePackage): Payment
-    {
-        $userId = (int) Auth::id();
+  //
 
-        if ($servicePackage->client?->user_id !== $userId) {
-            throw new AuthorizationException;
-        }
-
-        $existingPayment = Payment::query()
-            ->where('service_package_id', $servicePackage->id)
-            ->where('payment_method', 'pix')
-            ->whereIn('status', [
-                PaymentStatusEnum::PENDING->value,
-                PaymentStatusEnum::PROCESSING->value,
-                PaymentStatusEnum::AUTHORIZED->value,
-                PaymentStatusEnum::PAID->value,
-            ])
-            ->latest('id')
-            ->first();
-
-        if ($existingPayment && $this->isExpiredPixPayment($existingPayment)) {
-            $existingPayment->forceFill([
-                'status'          => PaymentStatusEnum::FAILED,
-                'failed_at'       => Carbon::now(),
-                'failure_message' => 'Pagamento Pix expirado.',
-            ])->save();
-
-            PaymentSplit::query()
-                ->where('payment_id', $existingPayment->id)
-                ->update(['status' => PaymentSplitStatusEnum::FAILED]);
-
-            $existingPayment = null;
-        }
+  public function getOrCreateServicePackagePixPayment(ServicePackage $servicePackage): Payment
+  {
+    $userId = (int) Auth::id();
 
-        if ($existingPayment) {
-            if ($this->isIncompleteGatewayPayment($existingPayment)) {
-                $existingPayment->forceFill([
-                    'status'          => PaymentStatusEnum::FAILED,
-                    'failed_at'       => Carbon::now(),
-                    'failure_message' => 'Pagamento pendente sem retorno do gateway.',
-                ])->save();
-
-                PaymentSplit::query()
-                    ->where('payment_id', $existingPayment->id)
-                    ->update(['status' => PaymentSplitStatusEnum::FAILED]);
-            } else {
-                $this->syncPaymentTargets($existingPayment);
-
-                return $existingPayment;
-            }
-        }
+    if ($servicePackage->client?->user_id !== $userId) {
+      throw new AuthorizationException;
+    }
 
-        $paymentData = DB::transaction(function () use ($servicePackage, $userId): array {
-            $servicePackage = ServicePackage::query()
-                ->lockForUpdate()
-                ->with(['client', 'provider', 'items.schedule.client', 'items.schedule.provider', 'items.schedule.customSchedule.serviceType'])
-                ->findOrFail($servicePackage->id);
-
-            if ($servicePackage->client?->user_id !== $userId) {
-                throw new AuthorizationException;
-            }
-
-            $schedules = $this->activePackageSchedules($servicePackage);
-
-            $this->validateServicePackageForPayment($servicePackage, $schedules);
-
-            if ($servicePackage->status !== ServicePackageStatusEnum::OPEN) {
-                throw new PaymentException;
-            }
-
-            $totals = $this->servicePackagePaymentTotals($schedules, 'pix');
-
-            $payment = Payment::create([
-                'schedule_id'              => null,
-                'service_package_id'       => $servicePackage->id,
-                'client_id'                => $servicePackage->client_id,
-                'provider_id'              => $servicePackage->provider_id,
-                'client_payment_method_id' => null,
-                'gateway_provider'         => 'pagarme',
-                'gateway_code'             => 'payment-'.(string) Str::uuid(),
-                'payment_method'           => 'pix',
-                'status'                   => PaymentStatusEnum::PENDING,
-                'gross_amount'             => data_get($totals, 'gross_amount'),
-                'gateway_fee_amount'       => 0,
-                'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
-                'net_amount'               => data_get($totals, 'gross_amount'),
-                'currency'                 => 'BRL',
-                'installments'             => 1,
-                'expires_at'               => Carbon::now()->addMinutes(30),
-
-                'metadata' => [
-                    'service_package_id' => (string) $servicePackage->id,
-                    'schedule_ids'       => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
-                    'service_amount'     => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
-                    'platform_fee'       => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
-                ],
-            ]);
-
-            PaymentSplit::create([
-                'payment_id'                        => $payment->id,
-                'provider_id'                       => $servicePackage->provider_id,
-                'gateway_provider'                  => 'pagarme',
-                'gateway_transfer_target_reference' => $servicePackage->provider->recipient_id,
-                'gateway_transfer_target_label'     => 'recipient',
-                'status'                            => PaymentSplitStatusEnum::PENDING,
-                'gross_amount'                      => data_get($totals, 'service_amount'),
-                'gateway_fee_amount'                => 0,
-                'net_amount'                        => data_get($totals, 'service_amount'),
-
-                'metadata' => [
-                    'service_package_id' => (string) $servicePackage->id,
-                    'schedule_ids'       => $schedules->pluck('id')->map(fn ($id) => (string) $id)->all(),
-                ],
-            ]);
-
-            return compact('payment', 'schedules');
-        });
-
-        /** @var Payment $payment */
-        $payment = data_get($paymentData, 'payment');
-
-        /** @var SupportCollection $schedules */
-        $schedules = data_get($paymentData, 'schedules');
-
-        try {
-            $schedules->first()->ensureCustomerPhone();
-
-            $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
-                payment:       $payment,
-                schedules:     $schedules,
-                paymentMethod: 'pix',
-                cardId:        null,
-                options:       [],
-            );
-        } catch (\Throwable $e) {
-            $this->failPayment($payment, $e->getMessage());
-
-            throw $e;
-        }
+    $existingPayment = Payment::query()
+      ->where('service_package_id', $servicePackage->id)
+      ->where('payment_method', 'pix')
+      ->whereIn('status', [
+        PaymentStatusEnum::PENDING->value,
+        PaymentStatusEnum::PROCESSING->value,
+        PaymentStatusEnum::AUTHORIZED->value,
+        PaymentStatusEnum::PAID->value,
+      ])
+      ->latest('id')
+      ->first();
+
+    if ($existingPayment && $this->isExpiredPixPayment($existingPayment)) {
+      $existingPayment->forceFill([
+        'status'          => PaymentStatusEnum::FAILED,
+        'failed_at'       => Carbon::now(),
+        'failure_message' => 'Pagamento Pix expirado.',
+      ])->save();
+
+      PaymentSplit::query()
+        ->where('payment_id', $existingPayment->id)
+        ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+
+      $existingPayment = null;
+    }
 
-        $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+    if ($existingPayment) {
+      if ($this->isIncompleteGatewayPayment($existingPayment)) {
+        $existingPayment->forceFill([
+          'status'          => PaymentStatusEnum::FAILED,
+          'failed_at'       => Carbon::now(),
+          'failure_message' => 'Pagamento pendente sem retorno do gateway.',
+        ])->save();
 
-        $this->syncPaymentTargets($payment);
+        PaymentSplit::query()
+          ->where('payment_id', $existingPayment->id)
+          ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+      } else {
+        $this->syncPaymentTargets($existingPayment);
 
-        return $payment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
+        return $existingPayment;
+      }
     }
 
-    public function payScheduleProposal(
-        int    $proposalId,
-        string $paymentMethod,
-        ?int   $clientPaymentMethodId = null,
-        array  $options               = [],
-    ): Payment {
-        $userId = (int) Auth::id();
-
-        if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
-            throw new PaymentException;
-        }
-
-        $paymentData = DB::transaction(function () use (
-            $proposalId,
-            $userId,
-            $paymentMethod,
-            $clientPaymentMethodId,
-            $options,
-        ): array {
-            $proposal = ScheduleProposal::query()
-                ->lockForUpdate()
-                ->with(['provider', 'schedule.client'])
-                ->findOrFail($proposalId);
-
-            $schedule = $proposal->schedule;
-            $provider = $proposal->provider;
-
-            if ($schedule?->client?->user_id !== $userId) {
-                throw new AuthorizationException;
-            }
-
-            $schedule->setAttribute('total_amount', app(CustomScheduleService::class)->resolveProposalAmount($schedule, $provider));
-
-            $schedules = collect([$schedule]);
-
-            $existingPayment = Payment::query()
-                ->where('metadata->schedule_proposal_id', (string) $proposal->id)
-                ->whereIn('status', [
-                    PaymentStatusEnum::PENDING->value,
-                    PaymentStatusEnum::PROCESSING->value,
-                    PaymentStatusEnum::AUTHORIZED->value,
-                    PaymentStatusEnum::PAID->value,
-                ])
-                ->latest('id')
-                ->first();
-
-            if (! $existingPayment && $schedule->provider_id) {
-                throw new PaymentException;
-            }
-
-            if ($existingPayment) {
-                if ($this->isExpiredPixPayment($existingPayment)) {
-                    $existingPayment->forceFill([
-                        'status'          => PaymentStatusEnum::FAILED,
-                        'failed_at'       => now(),
-                        'failure_message' => 'Pagamento Pix expirado.',
-                    ])->save();
-
-                    PaymentSplit::query()
-                        ->where('payment_id', $existingPayment->id)
-                        ->update(['status' => PaymentSplitStatusEnum::FAILED]);
-                } elseif ($this->isStaleIncompleteGatewayPayment($existingPayment)) {
-                    if ($existingPayment->payment_method !== $paymentMethod) {
-                        throw new PaymentException;
-                    }
-
-                    [, $cardId] = $this->resolveCard(
-                        clientId:              $schedule->client_id,
-                        paymentMethod:         $paymentMethod,
-                        clientPaymentMethodId: $clientPaymentMethodId ?? $existingPayment->client_payment_method_id,
-                        cardId:                data_get($options, 'card_id'),
-                    );
-
-                    $this->servicePackagePaymentTotals($schedules, $paymentMethod);
-
-                    return [
-                        'payment'   => $existingPayment,
-                        'schedules' => $schedules,
-                        'cardId'    => $cardId,
-                    ];
-                } else {
-                    if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
-                        throw new PaymentException;
-                    }
-
-                    return ['existing' => $existingPayment];
-                }
-            }
-
-            [$clientPaymentMethod, $cardId] = $this->resolveCard(
-                clientId:              $schedule->client_id,
-                paymentMethod:         $paymentMethod,
-                clientPaymentMethodId: $clientPaymentMethodId,
-                cardId:                data_get($options, 'card_id'),
-            );
-
-            $totals = $this->servicePackagePaymentTotals($schedules, $paymentMethod);
-
-            $payment = Payment::create([
-                'schedule_id'              => $schedule->id,
-                'service_package_id'       => null,
-                'client_id'                => $schedule->client_id,
-                'provider_id'              => $provider->id,
-                'client_payment_method_id' => $paymentMethod === 'credit_card' ? $clientPaymentMethod?->id : null,
-                'gateway_provider'         => 'pagarme',
-                'gateway_code'             => 'payment-'.(string) Str::uuid(),
-                'payment_method'           => $paymentMethod,
-                'status'                   => PaymentStatusEnum::PENDING,
-                'gross_amount'             => data_get($totals, 'gross_amount'),
-                'gateway_fee_amount'       => 0,
-                'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
-                'net_amount'               => data_get($totals, 'gross_amount'),
-                'currency'                 => 'BRL',
-                'installments'             => 1,
-                'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
-
-                'metadata' => [
-                    'schedule_proposal_id' => (string) $proposal->id,
-                    'schedule_id'          => (string) $schedule->id,
-                    'service_amount'       => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
-                    'platform_fee'         => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
-                ],
-            ]);
-
-            PaymentSplit::create([
-                'payment_id'                        => $payment->id,
-                'provider_id'                       => $provider->id,
-                'gateway_provider'                  => 'pagarme',
-                'gateway_transfer_target_reference' => $provider->recipient_id,
-                'gateway_transfer_target_label'     => 'recipient',
-                'status'                            => PaymentSplitStatusEnum::PENDING,
-                'gross_amount'                      => data_get($totals, 'service_amount'),
-                'gateway_fee_amount'                => 0,
-                'net_amount'                        => data_get($totals, 'service_amount'),
-
-                'metadata' => [
-                    'schedule_proposal_id' => (string) $proposal->id,
-                    'schedule_id'          => (string) $schedule->id,
-                ],
-            ]);
-
-            return compact('payment', 'schedules', 'cardId');
-        });
-
-        if (data_get($paymentData, 'existing')) {
-            $existingPayment = data_get($paymentData, 'existing');
-
-            $this->syncPaymentTargets($existingPayment);
-
-            return $existingPayment->fresh(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule']);
-        }
+    $paymentData = DB::transaction(function () use ($servicePackage, $userId): array {
+      $servicePackage = ServicePackage::query()
+        ->lockForUpdate()
+        ->with(['client', 'provider', 'items.schedule.client', 'items.schedule.provider', 'items.schedule.customSchedule.serviceType'])
+        ->findOrFail($servicePackage->id);
+
+      if ($servicePackage->client?->user_id !== $userId) {
+        throw new AuthorizationException;
+      }
+
+      $schedules = $this->activePackageSchedules($servicePackage);
+
+      $this->validateServicePackageForPayment($servicePackage, $schedules);
+
+      if ($servicePackage->status !== ServicePackageStatusEnum::OPEN) {
+        throw new PaymentException;
+      }
+
+      $totals = $this->servicePackagePaymentTotals($schedules, 'pix');
+
+      $payment = Payment::create([
+        'schedule_id'              => null,
+        'service_package_id'       => $servicePackage->id,
+        'client_id'                => $servicePackage->client_id,
+        'provider_id'              => $servicePackage->provider_id,
+        'client_payment_method_id' => null,
+        'gateway_provider'         => 'pagarme',
+        'gateway_code'             => 'payment-' . (string) Str::uuid(),
+        'payment_method'           => 'pix',
+        'status'                   => PaymentStatusEnum::PENDING,
+        'gross_amount'             => data_get($totals, 'gross_amount'),
+        'gateway_fee_amount'       => 0,
+        'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
+        'net_amount'               => data_get($totals, 'gross_amount'),
+        'currency'                 => 'BRL',
+        'installments'             => 1,
+        'expires_at'               => Carbon::now()->addMinutes(30),
+
+        'metadata' => [
+          'service_package_id' => (string) $servicePackage->id,
+          'schedule_ids'       => $schedules->pluck('id')->map(fn($id) => (string) $id)->all(),
+          'service_amount'     => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
+          'platform_fee'       => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
+        ],
+      ]);
+
+      PaymentSplit::create([
+        'payment_id'                        => $payment->id,
+        'provider_id'                       => $servicePackage->provider_id,
+        'gateway_provider'                  => 'pagarme',
+        'gateway_transfer_target_reference' => $servicePackage->provider->recipient_id,
+        'gateway_transfer_target_label'     => 'recipient',
+        'status'                            => PaymentSplitStatusEnum::PENDING,
+        'gross_amount'                      => data_get($totals, 'service_amount'),
+        'gateway_fee_amount'                => 0,
+        'net_amount'                        => data_get($totals, 'service_amount'),
+
+        'metadata' => [
+          'service_package_id' => (string) $servicePackage->id,
+          'schedule_ids'       => $schedules->pluck('id')->map(fn($id) => (string) $id)->all(),
+        ],
+      ]);
+
+      return compact('payment', 'schedules');
+    });
+
+    /** @var Payment $payment */
+    $payment = data_get($paymentData, 'payment');
+
+    /** @var SupportCollection $schedules */
+    $schedules = data_get($paymentData, 'schedules');
+
+    try {
+      $schedules->first()->ensureCustomerPhone();
+
+      $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
+        payment: $payment,
+        schedules: $schedules,
+        paymentMethod: 'pix',
+        cardId: null,
+        options: [],
+      );
+    } catch (\Throwable $e) {
+      $this->failPayment($payment, $e->getMessage());
+
+      throw $e;
+    }
 
-        /** @var Payment $payment */
-        $payment = data_get($paymentData, 'payment');
+    $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
 
-        /** @var SupportCollection $schedules */
-        $schedules = data_get($paymentData, 'schedules');
+    $this->syncPaymentTargets($payment);
 
-        try {
-            $schedules->first()->ensureCustomerPhone(data_get($options, 'phone'));
+    return $payment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
+  }
 
-            $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
-                payment:       $payment,
-                schedules:     $schedules,
-                paymentMethod: $paymentMethod,
-                cardId:        data_get($paymentData, 'cardId'),
-                options:       $options,
-            );
-        } catch (\Throwable $e) {
-            $this->failPayment($payment, $e->getMessage());
+  public function payScheduleProposal(
+    int    $proposalId,
+    string $paymentMethod,
+    ?int   $clientPaymentMethodId = null,
+    array  $options               = [],
+  ): Payment {
+    $userId = (int) Auth::id();
 
-            throw $e;
-        }
+    if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
+      throw new PaymentException;
+    }
 
-        $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+    $paymentData = DB::transaction(function () use (
+      $proposalId,
+      $userId,
+      $paymentMethod,
+      $clientPaymentMethodId,
+      $options,
+    ): array {
+      $proposal = ScheduleProposal::query()
+        ->lockForUpdate()
+        ->with(['provider', 'schedule.client'])
+        ->findOrFail($proposalId);
+
+      $schedule = $proposal->schedule;
+      $provider = $proposal->provider;
+
+      if ($schedule?->client?->user_id !== $userId) {
+        throw new AuthorizationException;
+      }
+
+      $schedule->setAttribute('total_amount', app(CustomScheduleService::class)->resolveProposalAmount($schedule, $provider));
+
+      $schedules = collect([$schedule]);
+
+      $existingPayment = Payment::query()
+        ->where('metadata->schedule_proposal_id', (string) $proposal->id)
+        ->whereIn('status', [
+          PaymentStatusEnum::PENDING->value,
+          PaymentStatusEnum::PROCESSING->value,
+          PaymentStatusEnum::AUTHORIZED->value,
+          PaymentStatusEnum::PAID->value,
+        ])
+        ->latest('id')
+        ->first();
+
+      if (! $existingPayment && $schedule->provider_id) {
+        throw new PaymentException;
+      }
+
+      if ($existingPayment) {
+        if ($this->isExpiredPixPayment($existingPayment)) {
+          $existingPayment->forceFill([
+            'status'          => PaymentStatusEnum::FAILED,
+            'failed_at'       => now(),
+            'failure_message' => 'Pagamento Pix expirado.',
+          ])->save();
 
-        $this->syncPaymentTargets($payment);
+          PaymentSplit::query()
+            ->where('payment_id', $existingPayment->id)
+            ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+        } elseif ($this->isStaleIncompleteGatewayPayment($existingPayment)) {
+          if ($existingPayment->payment_method !== $paymentMethod) {
+            throw new PaymentException;
+          }
+
+          [, $cardId] = $this->resolveCard(
+            clientId: $schedule->client_id,
+            paymentMethod: $paymentMethod,
+            clientPaymentMethodId: $clientPaymentMethodId ?? $existingPayment->client_payment_method_id,
+            cardId: data_get($options, 'card_id'),
+          );
+
+          $this->servicePackagePaymentTotals($schedules, $paymentMethod);
+
+          return [
+            'payment'   => $existingPayment,
+            'schedules' => $schedules,
+            'cardId'    => $cardId,
+          ];
+        } else {
+          if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) {
+            throw new PaymentException;
+          }
 
-        if ($payment->status === PaymentStatusEnum::FAILED) {
-            throw new PaymentFailedException;
+          return ['existing' => $existingPayment];
         }
-
-        return $payment->fresh(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule']);
+      }
+
+      [$clientPaymentMethod, $cardId] = $this->resolveCard(
+        clientId: $schedule->client_id,
+        paymentMethod: $paymentMethod,
+        clientPaymentMethodId: $clientPaymentMethodId,
+        cardId: data_get($options, 'card_id'),
+      );
+
+      $totals = $this->servicePackagePaymentTotals($schedules, $paymentMethod);
+
+      $payment = Payment::create([
+        'schedule_id'              => $schedule->id,
+        'service_package_id'       => null,
+        'client_id'                => $schedule->client_id,
+        'provider_id'              => $provider->id,
+        'client_payment_method_id' => $paymentMethod === 'credit_card' ? $clientPaymentMethod?->id : null,
+        'gateway_provider'         => 'pagarme',
+        'gateway_code'             => 'payment-' . (string) Str::uuid(),
+        'payment_method'           => $paymentMethod,
+        'status'                   => PaymentStatusEnum::PENDING,
+        'gross_amount'             => data_get($totals, 'gross_amount'),
+        'gateway_fee_amount'       => 0,
+        'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
+        'net_amount'               => data_get($totals, 'gross_amount'),
+        'currency'                 => 'BRL',
+        'installments'             => 1,
+        'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
+
+        'metadata' => [
+          'schedule_proposal_id' => (string) $proposal->id,
+          'schedule_id'          => (string) $schedule->id,
+          'service_amount'       => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
+          'platform_fee'         => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
+        ],
+      ]);
+
+      PaymentSplit::create([
+        'payment_id'                        => $payment->id,
+        'provider_id'                       => $provider->id,
+        'gateway_provider'                  => 'pagarme',
+        'gateway_transfer_target_reference' => $provider->recipient_id,
+        'gateway_transfer_target_label'     => 'recipient',
+        'status'                            => PaymentSplitStatusEnum::PENDING,
+        'gross_amount'                      => data_get($totals, 'service_amount'),
+        'gateway_fee_amount'                => 0,
+        'net_amount'                        => data_get($totals, 'service_amount'),
+
+        'metadata' => [
+          'schedule_proposal_id' => (string) $proposal->id,
+          'schedule_id'          => (string) $schedule->id,
+        ],
+      ]);
+
+      return compact('payment', 'schedules', 'cardId');
+    });
+
+    if (data_get($paymentData, 'existing')) {
+      $existingPayment = data_get($paymentData, 'existing');
+
+      $this->syncPaymentTargets($existingPayment);
+
+      return $existingPayment->fresh(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule']);
     }
 
-    public function getOrCreateScheduleProposalPixPayment(ScheduleProposal $proposal): Payment
-    {
-        $userId = (int) Auth::id();
+    /** @var Payment $payment */
+    $payment = data_get($paymentData, 'payment');
 
-        $proposal->loadMissing('schedule.client');
+    /** @var SupportCollection $schedules */
+    $schedules = data_get($paymentData, 'schedules');
 
-        $schedule = $proposal->schedule;
+    try {
+      $schedules->first()->ensureCustomerPhone(data_get($options, 'phone'));
 
-        if ($schedule?->client?->user_id !== $userId) {
-            throw new AuthorizationException;
-        }
+      $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
+        payment: $payment,
+        schedules: $schedules,
+        paymentMethod: $paymentMethod,
+        cardId: data_get($paymentData, 'cardId'),
+        options: $options,
+      );
+    } catch (\Throwable $e) {
+      $this->failPayment($payment, $e->getMessage());
 
-        $existingPayment = Payment::query()
-            ->where('metadata->schedule_proposal_id', (string) $proposal->id)
-            ->where('payment_method', 'pix')
-            ->whereIn('status', [
-                PaymentStatusEnum::PENDING->value,
-                PaymentStatusEnum::PROCESSING->value,
-                PaymentStatusEnum::AUTHORIZED->value,
-                PaymentStatusEnum::PAID->value,
-            ])
-            ->latest('id')
-            ->first();
-
-        if ($existingPayment && $this->isExpiredPixPayment($existingPayment)) {
-            $existingPayment->forceFill([
-                'status'          => PaymentStatusEnum::FAILED,
-                'failed_at'       => Carbon::now(),
-                'failure_message' => 'Pagamento Pix expirado.',
-            ])->save();
-
-            PaymentSplit::query()
-                ->where('payment_id', $existingPayment->id)
-                ->update(['status' => PaymentSplitStatusEnum::FAILED]);
-
-            $existingPayment = null;
-        }
-
-        if ($existingPayment) {
-            if ($this->isIncompleteGatewayPayment($existingPayment)) {
-                $existingPayment->forceFill([
-                    'status'          => PaymentStatusEnum::FAILED,
-                    'failed_at'       => Carbon::now(),
-                    'failure_message' => 'Pagamento pendente sem retorno do gateway.',
-                ])->save();
-
-                PaymentSplit::query()
-                    ->where('payment_id', $existingPayment->id)
-                    ->update(['status' => PaymentSplitStatusEnum::FAILED]);
-            } else {
-                $this->syncPaymentTargets($existingPayment);
-
-                return $existingPayment;
-            }
-        }
-
-        $paymentData = DB::transaction(function () use ($proposal, $userId): array {
-            $proposal = ScheduleProposal::query()
-                ->lockForUpdate()
-                ->with(['provider', 'schedule.client'])
-                ->findOrFail($proposal->id);
-
-            $schedule = $proposal->schedule;
-            $provider = $proposal->provider;
-
-            if ($schedule?->client?->user_id !== $userId) {
-                throw new AuthorizationException;
-            }
-
-            if ($schedule->provider_id) {
-                throw new PaymentException;
-            }
-
-            $schedule->setAttribute('total_amount', app(CustomScheduleService::class)->resolveProposalAmount($schedule, $provider));
-
-            $schedules = collect([$schedule]);
-
-            $totals = $this->servicePackagePaymentTotals($schedules, 'pix');
-
-            $payment = Payment::create([
-                'schedule_id'              => $schedule->id,
-                'service_package_id'       => null,
-                'client_id'                => $schedule->client_id,
-                'provider_id'              => $provider->id,
-                'client_payment_method_id' => null,
-                'gateway_provider'         => 'pagarme',
-                'gateway_code'             => 'payment-'.(string) Str::uuid(),
-                'payment_method'           => 'pix',
-                'status'                   => PaymentStatusEnum::PENDING,
-                'gross_amount'             => data_get($totals, 'gross_amount'),
-                'gateway_fee_amount'       => 0,
-                'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
-                'net_amount'               => data_get($totals, 'gross_amount'),
-                'currency'                 => 'BRL',
-                'installments'             => 1,
-                'expires_at'               => Carbon::now()->addMinutes(30),
-
-                'metadata' => [
-                    'schedule_proposal_id' => (string) $proposal->id,
-                    'schedule_id'          => (string) $schedule->id,
-                    'service_amount'       => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
-                    'platform_fee'         => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
-                ],
-            ]);
-
-            PaymentSplit::create([
-                'payment_id'                        => $payment->id,
-                'provider_id'                       => $provider->id,
-                'gateway_provider'                  => 'pagarme',
-                'gateway_transfer_target_reference' => $provider->recipient_id,
-                'gateway_transfer_target_label'     => 'recipient',
-                'status'                            => PaymentSplitStatusEnum::PENDING,
-                'gross_amount'                      => data_get($totals, 'service_amount'),
-                'gateway_fee_amount'                => 0,
-                'net_amount'                        => data_get($totals, 'service_amount'),
-
-                'metadata' => [
-                    'schedule_proposal_id' => (string) $proposal->id,
-                    'schedule_id'          => (string) $schedule->id,
-                ],
-            ]);
-
-            return compact('payment', 'schedules');
-        });
-
-        /** @var Payment $payment */
-        $payment = data_get($paymentData, 'payment');
-
-        /** @var SupportCollection $schedules */
-        $schedules = data_get($paymentData, 'schedules');
-
-        try {
-            $schedules->first()->ensureCustomerPhone();
-
-            $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
-                payment:       $payment,
-                schedules:     $schedules,
-                paymentMethod: 'pix',
-                cardId:        null,
-                options:       [],
-            );
-        } catch (\Throwable $e) {
-            $this->failPayment($payment, $e->getMessage());
-
-            throw $e;
-        }
+      throw $e;
+    }
 
-        $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+    $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
 
-        $this->syncPaymentTargets($payment);
+    $this->syncPaymentTargets($payment);
 
-        return $payment->fresh(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule']);
+    if ($payment->status === PaymentStatusEnum::FAILED) {
+      throw new PaymentFailedException;
     }
 
-    //
+    return $payment->fresh(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule']);
+  }
 
-    private function isExpiredPixPayment(Payment $payment): bool
-    {
-        if ($payment->payment_method !== 'pix') {
-            return false;
-        }
+  public function getOrCreateScheduleProposalPixPayment(ScheduleProposal $proposal): Payment
+  {
+    $userId = (int) Auth::id();
 
-        if ($payment->status === PaymentStatusEnum::PAID) {
-            return false;
-        }
+    $proposal->loadMissing('schedule.client');
 
-        return $payment->expires_at !== null
-            && $payment->expires_at->isPast();
-    }
+    $schedule = $proposal->schedule;
 
-    private function isIncompleteGatewayPayment(Payment $payment): bool
-    {
-        return $payment->status === PaymentStatusEnum::PENDING
-            && empty($payment->gateway_entity_reference)
-            && empty($payment->gateway_operation_reference)
-            && empty($payment->gateway_payload);
+    if ($schedule?->client?->user_id !== $userId) {
+      throw new AuthorizationException;
     }
 
-    private function isStaleIncompleteGatewayPayment(Payment $payment): bool
-    {
-        return $this->isIncompleteGatewayPayment($payment)
-            && ($payment->created_at?->lte(now()->subMinutes(5)) ?? false);
+    $existingPayment = Payment::query()
+      ->where('metadata->schedule_proposal_id', (string) $proposal->id)
+      ->where('payment_method', 'pix')
+      ->whereIn('status', [
+        PaymentStatusEnum::PENDING->value,
+        PaymentStatusEnum::PROCESSING->value,
+        PaymentStatusEnum::AUTHORIZED->value,
+        PaymentStatusEnum::PAID->value,
+      ])
+      ->latest('id')
+      ->first();
+
+    if ($existingPayment && $this->isExpiredPixPayment($existingPayment)) {
+      $existingPayment->forceFill([
+        'status'          => PaymentStatusEnum::FAILED,
+        'failed_at'       => Carbon::now(),
+        'failure_message' => 'Pagamento Pix expirado.',
+      ])->save();
+
+      PaymentSplit::query()
+        ->where('payment_id', $existingPayment->id)
+        ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+
+      $existingPayment = null;
     }
 
-    public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void
-    {
-        if ($payment->status !== PaymentStatusEnum::PAID) {
-            return;
-        }
+    if ($existingPayment) {
+      if ($this->isIncompleteGatewayPayment($existingPayment)) {
+        $existingPayment->forceFill([
+          'status'          => PaymentStatusEnum::FAILED,
+          'failed_at'       => Carbon::now(),
+          'failure_message' => 'Pagamento pendente sem retorno do gateway.',
+        ])->save();
 
-        if ($schedule->status !== 'paid') {
-            $schedule->update(['status' => 'paid']);
-        }
+        PaymentSplit::query()
+          ->where('payment_id', $existingPayment->id)
+          ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+      } else {
+        $this->syncPaymentTargets($existingPayment);
 
-        $this->syncServicePackagesForSchedule($schedule);
+        return $existingPayment;
+      }
     }
 
-    public function syncPaymentTargets(Payment $payment): void
-    {
-        if ($payment->status !== PaymentStatusEnum::PAID) {
-            return;
-        }
+    $paymentData = DB::transaction(function () use ($proposal, $userId): array {
+      $proposal = ScheduleProposal::query()
+        ->lockForUpdate()
+        ->with(['provider', 'schedule.client'])
+        ->findOrFail($proposal->id);
+
+      $schedule = $proposal->schedule;
+      $provider = $proposal->provider;
+
+      if ($schedule?->client?->user_id !== $userId) {
+        throw new AuthorizationException;
+      }
+
+      if ($schedule->provider_id) {
+        throw new PaymentException;
+      }
+
+      $schedule->setAttribute('total_amount', app(CustomScheduleService::class)->resolveProposalAmount($schedule, $provider));
+
+      $schedules = collect([$schedule]);
+
+      $totals = $this->servicePackagePaymentTotals($schedules, 'pix');
+
+      $payment = Payment::create([
+        'schedule_id'              => $schedule->id,
+        'service_package_id'       => null,
+        'client_id'                => $schedule->client_id,
+        'provider_id'              => $provider->id,
+        'client_payment_method_id' => null,
+        'gateway_provider'         => 'pagarme',
+        'gateway_code'             => 'payment-' . (string) Str::uuid(),
+        'payment_method'           => 'pix',
+        'status'                   => PaymentStatusEnum::PENDING,
+        'gross_amount'             => data_get($totals, 'gross_amount'),
+        'gateway_fee_amount'       => 0,
+        'platform_fee_amount'      => data_get($totals, 'platform_fee_amount'),
+        'net_amount'               => data_get($totals, 'gross_amount'),
+        'currency'                 => 'BRL',
+        'installments'             => 1,
+        'expires_at'               => Carbon::now()->addMinutes(30),
+
+        'metadata' => [
+          'schedule_proposal_id' => (string) $proposal->id,
+          'schedule_id'          => (string) $schedule->id,
+          'service_amount'       => number_format(data_get($totals, 'service_amount'), 2, '.', ''),
+          'platform_fee'         => number_format(data_get($totals, 'platform_fee_amount'), 2, '.', ''),
+        ],
+      ]);
+
+      PaymentSplit::create([
+        'payment_id'                        => $payment->id,
+        'provider_id'                       => $provider->id,
+        'gateway_provider'                  => 'pagarme',
+        'gateway_transfer_target_reference' => $provider->recipient_id,
+        'gateway_transfer_target_label'     => 'recipient',
+        'status'                            => PaymentSplitStatusEnum::PENDING,
+        'gross_amount'                      => data_get($totals, 'service_amount'),
+        'gateway_fee_amount'                => 0,
+        'net_amount'                        => data_get($totals, 'service_amount'),
+
+        'metadata' => [
+          'schedule_proposal_id' => (string) $proposal->id,
+          'schedule_id'          => (string) $schedule->id,
+        ],
+      ]);
+
+      return compact('payment', 'schedules');
+    });
+
+    /** @var Payment $payment */
+    $payment = data_get($paymentData, 'payment');
+
+    /** @var SupportCollection $schedules */
+    $schedules = data_get($paymentData, 'schedules');
+
+    try {
+      $schedules->first()->ensureCustomerPhone();
+
+      $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
+        payment: $payment,
+        schedules: $schedules,
+        paymentMethod: 'pix',
+        cardId: null,
+        options: [],
+      );
+    } catch (\Throwable $e) {
+      $this->failPayment($payment, $e->getMessage());
+
+      throw $e;
+    }
 
-        if ($payment->service_package_id) {
-            $paidSchedules = DB::transaction(function () use ($payment): SupportCollection {
-                $servicePackage = ServicePackage::query()->lockForUpdate()->with('items')->find($payment->service_package_id);
+    $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
 
-                if (! $servicePackage) {
-                    return collect();
-                }
+    $this->syncPaymentTargets($payment);
 
-                $scheduleIds = $servicePackage->items->pluck('schedule_id')->filter()->unique();
+    return $payment->fresh(['client.user', 'provider.user', 'schedule', 'servicePackage.items.schedule']);
+  }
 
-                $schedulesToPay = Schedule::query()
-                    ->with('provider')
-                    ->whereIn('id', $scheduleIds)
-                    ->where('status', 'accepted')
-                    ->get();
+  //
 
-                Schedule::query()
-                    ->whereIn('id', $schedulesToPay->pluck('id'))
-                    ->where('status', 'accepted')
-                    ->update(['status' => 'paid']);
+  private function isExpiredPixPayment(Payment $payment): bool
+  {
+    if ($payment->payment_method !== 'pix') {
+      return false;
+    }
 
-                if ($servicePackage->status !== ServicePackageStatusEnum::PAID) {
-                    $servicePackage->update(['status' => ServicePackageStatusEnum::PAID->value]);
-                }
+    if ($payment->status === PaymentStatusEnum::PAID) {
+      return false;
+    }
 
-                return $schedulesToPay->toBase();
-            });
+    return $payment->expires_at !== null
+      && $payment->expires_at->isPast();
+  }
+
+  private function isIncompleteGatewayPayment(Payment $payment): bool
+  {
+    return $payment->status === PaymentStatusEnum::PENDING
+      && empty($payment->gateway_entity_reference)
+      && empty($payment->gateway_operation_reference)
+      && empty($payment->gateway_payload);
+  }
+
+  private function isStaleIncompleteGatewayPayment(Payment $payment): bool
+  {
+    return $this->isIncompleteGatewayPayment($payment)
+      && ($payment->created_at?->lte(now()->subMinutes(5)) ?? false);
+  }
+
+  public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void
+  {
+    if ($payment->status !== PaymentStatusEnum::PAID) {
+      return;
+    }
 
-            $paidSchedules->each(fn (Schedule $schedule) => $this->notifyProviderAndScheduleStart($schedule));
+    if ($schedule->status !== 'paid') {
+      $schedule->update(['status' => 'paid']);
+    }
 
-            return;
-        }
+    $this->syncServicePackagesForSchedule($schedule);
+  }
 
-        $payment->loadMissing('schedule');
+  public function syncPaymentTargets(Payment $payment): void
+  {
+    if ($payment->status !== PaymentStatusEnum::PAID) {
+      return;
+    }
 
-        if (! $payment->schedule) {
-            return;
-        }
+    if ($payment->service_package_id) {
+      $paidSchedules = DB::transaction(function () use ($payment): SupportCollection {
+        $servicePackage = ServicePackage::query()->lockForUpdate()->with('items')->find($payment->service_package_id);
 
-        $proposalId = data_get($payment->metadata, 'schedule_proposal_id');
+        if (! $servicePackage) {
+          return collect();
+        }
 
-        if ($proposalId && ! $payment->schedule->provider_id) {
-            $servicePackage = app(CustomScheduleService::class)->acceptProposal((int) $proposalId);
+        $scheduleIds = $servicePackage->items->pluck('schedule_id')->filter()->unique();
 
-            $payment->update([
-                'schedule_id'        => null,
-                'service_package_id' => $servicePackage->id,
-            ]);
+        $schedulesToPay = Schedule::query()
+          ->with('provider')
+          ->whereIn('id', $scheduleIds)
+          ->where('status', 'accepted')
+          ->get();
 
-            $this->syncPaymentTargets($payment->fresh());
+        Schedule::query()
+          ->whereIn('id', $schedulesToPay->pluck('id'))
+          ->where('status', 'accepted')
+          ->update(['status' => 'paid']);
 
-            return;
+        if ($servicePackage->status !== ServicePackageStatusEnum::PAID) {
+          $servicePackage->update(['status' => ServicePackageStatusEnum::PAID->value]);
         }
 
-        $this->syncScheduleStatusAfterPayment($payment->schedule, $payment);
-    }
+        return $schedulesToPay->toBase();
+      });
 
-    private function notifyProviderAndScheduleStart(Schedule $schedule): void
-    {
-        if ($schedule->provider_id && $schedule->provider) {
-            app(NotificationService::class)->create([
-                'title'       => __('notifications.payment_confirmed_title'),
-                'description' => __('notifications.payment_confirmed_description'),
-                'origin'      => 'schedule',
-                'origin_id'   => $schedule->id,
-                'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_START->value,
-                'user_id'     => $schedule->provider->user_id,
-            ]);
-        }
+      $paidSchedules->each(fn(Schedule $schedule) => $this->notifyProviderAndScheduleStart($schedule));
 
-        $dateCleaned = Carbon::parse($schedule->date)->format('Y-m-d');
-
-        StartScheduleJob::dispatch($schedule->id)
-            ->delay(Carbon::parse($dateCleaned . ' ' . $schedule->start_time)->subHour());
+      return;
     }
 
-    private function syncServicePackagesForSchedule(Schedule $schedule): void
-    {
-        ServicePackage::query()
-            ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
-            ->with('items')
-            ->get()
-            ->each(fn (ServicePackage $servicePackage) => $this->syncServicePackageStatusAfterPayments($servicePackage));
+    $payment->loadMissing('schedule');
+
+    if (! $payment->schedule) {
+      return;
     }
 
-    private function syncServicePackageStatusAfterPayments(ServicePackage $servicePackage): void
-    {
-        $servicePackage->loadMissing('items');
+    $proposalId = data_get($payment->metadata, 'schedule_proposal_id');
 
-        $scheduleIds = $servicePackage->items
-            ->pluck('schedule_id')
-            ->filter()
-            ->unique()
-            ->values();
+    if ($proposalId && ! $payment->schedule->provider_id) {
+      $servicePackage = app(CustomScheduleService::class)->acceptProposal((int) $proposalId);
 
-        if ($scheduleIds->isEmpty()) {
-            return;
-        }
+      $payment->update([
+        'schedule_id'        => null,
+        'service_package_id' => $servicePackage->id,
+      ]);
 
-        $paidSchedulesCount = Schedule::query()
-            ->whereIn('id', $scheduleIds)
-            ->where('status', 'paid')
-            ->count();
+      $this->syncPaymentTargets($payment->fresh());
 
-        if ($paidSchedulesCount !== $scheduleIds->count()) {
-            return;
-        }
-
-        if ($servicePackage->status !== ServicePackageStatusEnum::PAID) {
-            $servicePackage->update(['status' => ServicePackageStatusEnum::PAID->value]);
-        }
+      return;
     }
 
-    private function activePackageSchedules(ServicePackage $servicePackage): SupportCollection
-    {
-        return $servicePackage->items
-            ->pluck('schedule')
-            ->filter()
-            ->reject(fn (Schedule $schedule) => in_array($schedule->status, ['cancelled', 'rejected'], true))
-            ->values();
+    $this->syncScheduleStatusAfterPayment($payment->schedule, $payment);
+  }
+
+  private function notifyProviderAndScheduleStart(Schedule $schedule): void
+  {
+    if ($schedule->provider_id && $schedule->provider) {
+      app(NotificationService::class)->create([
+        'title'       => __('notifications.payment_confirmed_title'),
+        'description' => __('notifications.payment_confirmed_description'),
+        'origin'      => 'schedule',
+        'origin_id'   => $schedule->id,
+        'type'        => NotificationTypeEnum::SCHEDULE_PROVIDER_START->value,
+        'user_id'     => $schedule->provider->user_id,
+      ]);
     }
 
-    private function validateServicePackageForPayment(ServicePackage $servicePackage, SupportCollection $schedules): void
-    {
-        if (! in_array($servicePackage->status, [ServicePackageStatusEnum::OPEN, ServicePackageStatusEnum::PAID], true)) {
-            throw new PaymentException;
-        }
-
-        if ($schedules->isEmpty()) {
-            throw new PaymentException;
-        }
+    $dateCleaned = Carbon::parse($schedule->date)->format('Y-m-d');
+
+    StartScheduleJob::dispatch($schedule->id)
+      ->delay(Carbon::parse($dateCleaned . ' ' . $schedule->start_time)->subHour());
+  }
+
+  private function syncServicePackagesForSchedule(Schedule $schedule): void
+  {
+    ServicePackage::query()
+      ->whereHas('items', fn($query) => $query->where('schedule_id', $schedule->id))
+      ->with('items')
+      ->get()
+      ->each(fn(ServicePackage $servicePackage) => $this->syncServicePackageStatusAfterPayments($servicePackage));
+  }
+
+  private function syncServicePackageStatusAfterPayments(ServicePackage $servicePackage): void
+  {
+    $servicePackage->loadMissing('items');
+
+    $scheduleIds = $servicePackage->items
+      ->pluck('schedule_id')
+      ->filter()
+      ->unique()
+      ->values();
+
+    if ($scheduleIds->isEmpty()) {
+      return;
+    }
 
-        $providerIds = $schedules->pluck('provider_id')->filter()->unique()->values();
-        $clientIds   = $schedules->pluck('client_id')->unique()->values();
+    $paidSchedulesCount = Schedule::query()
+      ->whereIn('id', $scheduleIds)
+      ->where('status', 'paid')
+      ->count();
 
-        if (! $servicePackage->provider_id || $providerIds->count() !== 1 || (int) $providerIds->first() !== $servicePackage->provider_id) {
-            throw new PaymentException;
-        }
-
-        if ($clientIds->count() !== 1 || (int) $clientIds->first() !== $servicePackage->client_id) {
-            throw new PaymentException;
-        }
+    if ($paidSchedulesCount !== $scheduleIds->count()) {
+      return;
+    }
 
-        if (empty($servicePackage->provider?->recipient_id)) {
-            throw new PaymentException;
-        }
+    if ($servicePackage->status !== ServicePackageStatusEnum::PAID) {
+      $servicePackage->update(['status' => ServicePackageStatusEnum::PAID->value]);
+    }
+  }
+
+  private function activePackageSchedules(ServicePackage $servicePackage): SupportCollection
+  {
+    return $servicePackage->items
+      ->pluck('schedule')
+      ->filter()
+      ->reject(fn(Schedule $schedule) => in_array($schedule->status, ['cancelled', 'rejected'], true))
+      ->values();
+  }
+
+  private function validateServicePackageForPayment(ServicePackage $servicePackage, SupportCollection $schedules): void
+  {
+    if (! in_array($servicePackage->status, [ServicePackageStatusEnum::OPEN, ServicePackageStatusEnum::PAID], true)) {
+      throw new PaymentException;
+    }
 
-        foreach ($schedules as $schedule) {
-            $expectedStatus = $servicePackage->status === ServicePackageStatusEnum::PAID ? 'paid' : 'accepted';
+    if ($schedules->isEmpty()) {
+      throw new PaymentException;
+    }
 
-            if ($schedule->status !== $expectedStatus) {
-                throw new PaymentException;
-            }
+    $providerIds = $schedules->pluck('provider_id')->filter()->unique()->values();
+    $clientIds   = $schedules->pluck('client_id')->unique()->values();
 
-            if ((float) $schedule->total_amount <= 0) {
-                throw new PaymentException;
-            }
-        }
+    if (! $servicePackage->provider_id || $providerIds->count() !== 1 || (int) $providerIds->first() !== $servicePackage->provider_id) {
+      throw new PaymentException;
     }
 
-    private function resolveCard(
-        int $clientId,
-        string $paymentMethod,
-        ?int $clientPaymentMethodId,
-        ?string $cardId,
-    ): array {
-        if ($paymentMethod !== 'credit_card') {
-            return [null, null];
-        }
-
-        if (! $clientPaymentMethodId && empty($cardId)) {
-            throw new PaymentException;
-        }
+    if ($clientIds->count() !== 1 || (int) $clientIds->first() !== $servicePackage->client_id) {
+      throw new PaymentException;
+    }
 
-        $clientPaymentMethod = $clientPaymentMethodId
-            ? ClientPaymentMethod::query()
-                ->where('client_id', $clientId)
-                ->where('id', $clientPaymentMethodId)
-                ->where('is_active', true)
-                ->first()
-            : null;
+    if (empty($servicePackage->provider?->recipient_id)) {
+      throw new PaymentException;
+    }
 
-        if ($clientPaymentMethodId && ! $clientPaymentMethod) {
-            throw new PaymentException;
-        }
+    foreach ($schedules as $schedule) {
+      $expectedStatus = $servicePackage->status === ServicePackageStatusEnum::PAID ? 'paid' : 'accepted';
 
-        $cardId = $cardId ?: $clientPaymentMethod?->gateway_card_id;
+      if ($schedule->status !== $expectedStatus) {
+        throw new PaymentException;
+      }
 
-        if (empty($cardId)) {
-            throw new PaymentException;
-        }
+      if ((float) $schedule->total_amount <= 0) {
+        throw new PaymentException;
+      }
+    }
+  }
+
+  private function resolveCard(
+    int $clientId,
+    string $paymentMethod,
+    ?int $clientPaymentMethodId,
+    ?string $cardId,
+  ): array {
+    if ($paymentMethod !== 'credit_card') {
+      return [null, null];
+    }
 
-        return [$clientPaymentMethod, $cardId];
+    if (! $clientPaymentMethodId && empty($cardId)) {
+      throw new PaymentException;
     }
 
-    private function servicePackagePaymentTotals(SupportCollection $schedules, string $paymentMethod): array
-    {
-        return $schedules->reduce(function (array $totals, Schedule $schedule) use ($paymentMethod): array {
-            $amounts = $this->pagarmePaymentService->calculatePaymentAmounts(
-                serviceAmount: (float) $schedule->total_amount,
-                paymentMethod: $paymentMethod,
-                schedule:      $schedule,
-            );
-
-            $schedule->setAttribute('payment_gross_amount', data_get($amounts, 'gross_amount'));
-
-            return [
-                'service_amount'      => round(data_get($totals, 'service_amount') + data_get($amounts, 'service_amount'), 2),
-                'platform_fee_amount' => round(data_get($totals, 'platform_fee_amount') + data_get($amounts, 'platform_fee_amount'), 2),
-                'gross_amount'        => round(data_get($totals, 'gross_amount') + data_get($amounts, 'gross_amount'), 2),
-            ];
-        }, [
-            'service_amount'      => 0.0,
-            'platform_fee_amount' => 0.0,
-            'gross_amount'        => 0.0,
-        ]);
+    $clientPaymentMethod = $clientPaymentMethodId
+      ? ClientPaymentMethod::query()
+      ->where('client_id', $clientId)
+      ->where('id', $clientPaymentMethodId)
+      ->where('is_active', true)
+      ->first()
+      : null;
+
+    if ($clientPaymentMethodId && ! $clientPaymentMethod) {
+      throw new PaymentException;
     }
 
-    private function failPayment(Payment $payment, string $message): void
-    {
-        $payment->forceFill([
-            'status'          => PaymentStatusEnum::FAILED,
-            'failed_at'       => now(),
-            'failure_message' => $message,
-        ])->save();
+    $cardId = $cardId ?: $clientPaymentMethod?->gateway_card_id;
 
-        PaymentSplit::query()
-            ->where('payment_id', $payment->id)
-            ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+    if (empty($cardId)) {
+      throw new PaymentException;
     }
+
+    return [$clientPaymentMethod, $cardId];
+  }
+
+  private function servicePackagePaymentTotals(SupportCollection $schedules, string $paymentMethod): array
+  {
+    return $schedules->reduce(function (array $totals, Schedule $schedule) use ($paymentMethod): array {
+      $amounts = $this->pagarmePaymentService->calculatePaymentAmounts(
+        serviceAmount: (float) $schedule->total_amount,
+        paymentMethod: $paymentMethod,
+        schedule: $schedule,
+      );
+
+      $schedule->setAttribute('payment_gross_amount', data_get($amounts, 'gross_amount'));
+
+      return [
+        'service_amount'      => round(data_get($totals, 'service_amount') + data_get($amounts, 'service_amount'), 2),
+        'platform_fee_amount' => round(data_get($totals, 'platform_fee_amount') + data_get($amounts, 'platform_fee_amount'), 2),
+        'gross_amount'        => round(data_get($totals, 'gross_amount') + data_get($amounts, 'gross_amount'), 2),
+      ];
+    }, [
+      'service_amount'      => 0.0,
+      'platform_fee_amount' => 0.0,
+      'gross_amount'        => 0.0,
+    ]);
+  }
+
+  private function failPayment(Payment $payment, string $message): void
+  {
+    $payment->forceFill([
+      'status'          => PaymentStatusEnum::FAILED,
+      'failed_at'       => now(),
+      'failure_message' => $message,
+    ])->save();
+
+    PaymentSplit::query()
+      ->where('payment_id', $payment->id)
+      ->update(['status' => PaymentSplitStatusEnum::FAILED]);
+  }
 }