where('transfer_id', $transferId) ->first(); if (! $withdrawal) { Log::channel('pagarme')->warning('Saque do prestador não encontrado para o webhook de transferência', [ 'transfer_id' => $transferId, ]); return; } $status = data_get($data, 'status'); if ($status === ProviderWithdrawalStatusEnum::TRANSFERRED->value) { $withdrawal->forceFill([ 'status' => ProviderWithdrawalStatusEnum::TRANSFERRED, 'completed_at' => now(), ])->save(); } elseif (in_array($status, [ProviderWithdrawalStatusEnum::FAILED->value, ProviderWithdrawalStatusEnum::CANCELED->value], true)) { $withdrawal->paymentSplits() ->update(['provider_withdrawal_id' => null]); $withdrawal->forceFill([ 'status' => ProviderWithdrawalStatusEnum::fromString($status), 'failed_at' => $status === ProviderWithdrawalStatusEnum::FAILED->value ? now() : null, 'bank_response' => $status === ProviderWithdrawalStatusEnum::FAILED->value ? (data_get($data, 'bank_response')) : null, ])->save(); } elseif ($status === ProviderWithdrawalStatusEnum::PROCESSING->value) { $withdrawal->forceFill([ 'status' => ProviderWithdrawalStatusEnum::PROCESSING, ])->save(); } } // public function getAvailableBalance(Provider $provider): float { $earnings = (float) PaymentSplit::query() ->where('provider_id', $provider->id) ->whereHas('payment', fn ($q) => $this->availablePaymentQuery($q)) ->sum('gross_amount'); $withdrawn = (float) ProviderWithdrawal::query() ->where('provider_id', $provider->id) ->whereIn('status', [ ProviderWithdrawalStatusEnum::PENDING_TRANSFER, ProviderWithdrawalStatusEnum::PROCESSING, ProviderWithdrawalStatusEnum::TRANSFERRED, ]) ->sum('gross_amount'); return max(0, $earnings - $withdrawn); } public function getPendingBalance(Provider $provider): float { return (float) PaymentSplit::query() ->where('provider_id', $provider->id) ->whereHas('payment', fn ($q) => $this->pendingPaymentQuery($q)) ->sum('gross_amount'); } // public function getPaymentSplits(Provider $provider): Collection { return PaymentSplit::query() ->where('provider_id', $provider->id) ->with(['payment.schedule.client.user', 'payment.servicePackage.items.schedule.client.user']) ->orderBy('created_at', 'desc') ->get(); } // public function requestWithdrawal(Provider $provider): ProviderWithdrawal { if (empty($provider->recipient_id)) { throw new \RuntimeException(__('messages.provider_requires_pagarme_recipient')); } return DB::transaction(function () use ($provider) { Provider::query() ->where('id', $provider->id) ->lockForUpdate() ->first(); $pending = ProviderWithdrawal::query() ->where('provider_id', $provider->id) ->whereIn('status', [ ProviderWithdrawalStatusEnum::PENDING_TRANSFER, ProviderWithdrawalStatusEnum::PROCESSING, ]) ->exists(); if ($pending) { throw new \RuntimeException(__('messages.withdrawal_already_in_progress')); } $available = $this->getAvailableBalance($provider); if ($available <= 0) { throw new \RuntimeException(__('messages.insufficient_withdrawal_balance')); } $paymentTransferFeeAmount = $this->paymentTransferFeeAmount(); $netAmount = max(0, round($available - $paymentTransferFeeAmount, 2)); if ($netAmount <= 0) { throw new \RuntimeException(__('messages.insufficient_balance_for_transfer_fee')); } $amountInCents = (int) round($netAmount * 100); $idempotencyKey = sprintf('withdrawal-%d-%s', $provider->id, now()->timestamp); $bankAccount = $provider->recipient_default_bank_account; $withdrawal = ProviderWithdrawal::create([ 'provider_id' => $provider->id, 'recipient_id' => $provider->recipient_id, 'idempotency_key' => $idempotencyKey, 'gross_amount' => $available, 'gateway_fee_amount' => $paymentTransferFeeAmount, 'net_amount' => $netAmount, 'status' => ProviderWithdrawalStatusEnum::PENDING_TRANSFER, 'bank_account' => $bankAccount, 'metadata' => [ 'provider_id' => $provider->id, 'gross_amount' => $available, 'payment_transfer_fee_amount' => $paymentTransferFeeAmount, 'net_amount' => $netAmount, ], ]); try { $transfer = $this->pagarmeTransfer->createTransfer( $amountInCents, $provider->recipient_id, $idempotencyKey, ); $withdrawal->forceFill([ 'transfer_id' => $transfer->id, 'status' => $transfer->status ?? ProviderWithdrawalStatusEnum::PROCESSING->value, 'type' => $transfer->type, 'gateway_payload' => $transfer->toArray(), ])->save(); PaymentSplit::query() ->where('provider_id', $provider->id) ->whereNull('provider_withdrawal_id') ->whereHas('payment', fn ($q) => $this->availablePaymentQuery($q)) ->update(['provider_withdrawal_id' => $withdrawal->id]); return $withdrawal->fresh(); } catch (\Throwable $e) { $withdrawal->forceFill([ 'status' => ProviderWithdrawalStatusEnum::FAILED, 'failed_at' => now(), 'bank_response' => $e->getMessage(), ])->save(); throw $e; } }); } // public function getWithdrawals(Provider $provider): Collection { return ProviderWithdrawal::query() ->where('provider_id', $provider->id) ->orderBy('created_at', 'desc') ->get(); } public function getAllWithdrawals(): Collection { return ProviderWithdrawal::query() ->with(['provider.user', 'paymentSplits']) ->orderBy('created_at', 'desc') ->get(); } public function getWithdrawal(int $id, Provider $provider): ProviderWithdrawal { return ProviderWithdrawal::query() ->where('id', $id) ->where('provider_id', $provider->id) ->firstOrFail(); } // public function getWithdrawalFees(): array { return [ 'payment_transfer_fee_amount' => $this->paymentTransferFeeAmount(), ]; } private function paymentTransferFeeAmount(): float { return round((float) config('services.pagarme.transfer_fee_amount'), 2); } // private function availableScheduleQuery($query) { return $query ->where('code_verified', true) ->when( ! app()->environment('local', 'development'), fn ($query) => $query->whereRaw( '(date + end_time) <= ?', [$this->withdrawalReleaseCutoff()] ) ); } private function unavailableScheduleQuery($query) { return $query->where(function ($query) { $query->where('code_verified', false) ->when( ! app()->environment('local', 'development'), fn ($query) => $query->orWhereRaw( '(date + end_time) > ?', [$this->withdrawalReleaseCutoff()] ) ); }); } private function availablePaymentQuery($query) { return $query ->where('status', PaymentStatusEnum::PAID) ->where(function ($query) { $query->whereHas('schedule', fn ($query) => $this->availableScheduleQuery($query)) ->orWhere(function ($query) { $query->whereNotNull('service_package_id') ->whereHas('servicePackage.items') ->whereDoesntHave('servicePackage.items.schedule', fn ($query) => $this->unavailableScheduleQuery($query)); }); }); } private function pendingPaymentQuery($query) { return $query ->where('status', PaymentStatusEnum::PAID) ->where(function ($query) { $query->whereHas('schedule', fn ($query) => $this->pendingScheduleQuery($query)) ->orWhereHas('servicePackage.items.schedule', fn ($query) => $this->pendingScheduleQuery($query)); }); } private function pendingScheduleQuery($query) { return $query ->whereNotIn('status', ['cancelled', 'rejected']) ->where(function ($q) { $q->where('code_verified', false) ->orWhereRaw( '(date + end_time) > ?', [$this->withdrawalReleaseCutoff()] ); }); } private function withdrawalReleaseCutoff(): string { $releaseDays = max(0, (int) config('services.pagarme.withdrawal_release_days')); return Carbon::now()->subDays($releaseDays)->format('Y-m-d H:i:s'); } }