with(['client.user', 'provider.user']) ->orderBy('created_at', 'desc') ->get(); } public function findById(int $id): ?Payment { return Payment::query() ->with(['client.user', 'provider.user']) ->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; } $model->update($data); return $model->fresh(); } public function delete(int $id): bool { $model = $this->findById($id); if (! $model) { return false; } return $model->delete(); } public function platformFees(): array { return $this->pagarmePaymentService->platformFeeRates(); } // 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.'); } if ((float) $schedule->total_amount <= 0) { throw new \InvalidArgumentException('Agendamento precisa ter valor maior que zero para gerar pagamento.'); } if (empty($schedule->provider->recipient_id)) { throw new \InvalidArgumentException('Prestador precisa ter recipient_id do Pagar.me para receber split.'); } $existingPayment = Payment::query() ->where('schedule_id', $schedule->id) ->whereIn('status', [ PaymentStatusEnum::PENDING->value, PaymentStatusEnum::PROCESSING->value, PaymentStatusEnum::AUTHORIZED->value, PaymentStatusEnum::PAID->value, ]) ->latest('id') ->first(); if ($existingPayment) { if ($this->isIncompleteGatewayPayment($existingPayment)) { $existingPayment->forceFill([ 'status' => PaymentStatusEnum::FAILED, 'failed_at' => now(), 'failure_message' => 'Pagamento pendente sem retorno do gateway.', ])->save(); } elseif ($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]); } else { if ($existingPayment->payment_method !== $paymentMethod && $existingPayment->status !== PaymentStatusEnum::PAID) { throw new \InvalidArgumentException('Ja existe um pagamento em andamento para este agendamento.'); } $this->syncScheduleStatusAfterPayment($schedule, $existingPayment); return $existingPayment; } } $clientPaymentMethod = null; $cardId = null; if ($paymentMethod === 'credit_card') { if (! $clientPaymentMethodId && empty($options['card_id'])) { throw new \InvalidArgumentException('Cartao de pagamento ou card_id e obrigatorio.'); } if ($clientPaymentMethodId) { $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.'); } } $cardId = $options['card_id'] ?? $clientPaymentMethod?->gateway_card_id ?? null; if (empty($cardId)) { throw new \InvalidArgumentException('Cartao de pagamento invalido ou sem gateway_card_id do Pagar.me.'); } } $serviceAmount = (float) $schedule->total_amount; $amounts = $this->pagarmePaymentService->calculatePaymentAmounts( serviceAmount: $serviceAmount, paymentMethod: $paymentMethod, ); $payment = Payment::create([ 'schedule_id' => $schedule->id, 'client_id' => $schedule->client_id, 'provider_id' => $schedule->provider_id, 'client_payment_method_id' => $paymentMethod === 'credit_card' ? ($clientPaymentMethod?->id ?? null) : null, 'gateway_provider' => 'pagarme', 'gateway_code' => 'payment-'.(string) Str::uuid(), 'payment_method' => $paymentMethod, 'status' => PaymentStatusEnum::PENDING, 'gross_amount' => $amounts['gross_amount'], 'gateway_fee_amount' => 0, 'platform_fee_amount' => $amounts['platform_fee_amount'], 'net_amount' => $amounts['gross_amount'], 'currency' => 'BRL', 'installments' => 1, 'expires_at' => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null, 'metadata' => [ 'service_amount' => number_format($amounts['service_amount'], 2, '.', ''), 'platform_fee' => number_format($amounts['platform_fee_amount'], 2, '.', ''), ], ]); PaymentSplit::create([ 'payment_id' => $payment->id, 'provider_id' => $schedule->provider_id, 'gateway_provider' => 'pagarme', 'gateway_transfer_target_reference' => $schedule->provider->recipient_id, 'gateway_transfer_target_label' => 'recipient', 'status' => PaymentSplitStatusEnum::PENDING, 'gross_amount' => $serviceAmount, 'gateway_fee_amount' => 0, 'net_amount' => $serviceAmount, 'metadata' => [ 'schedule_id' => (string) $schedule->id, ], ]); $schedule->ensureCustomerPhone($options['phone'] ?? null); try { $orderResponse = $this->pagarmePaymentService->processPayment( payment: $payment, schedule: $schedule, paymentMethod: $paymentMethod, cardId: $cardId, options: $options, ); } catch (\Throwable $e) { $payment->forceFill([ 'status' => PaymentStatusEnum::FAILED, 'failed_at' => now(), 'failure_message' => $e->getMessage(), ])->save(); PaymentSplit::query() ->where('payment_id', $payment->id) ->update(['status' => PaymentSplitStatusEnum::FAILED]); throw $e; } $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse); $this->syncScheduleStatusAfterPayment($schedule, $payment); return $payment; } public function getOrCreatePixPayment(Schedule $schedule): Payment { $existingPayment = Payment::query() ->where('schedule_id', $schedule->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->syncScheduleStatusAfterPayment($schedule, $existingPayment); return $existingPayment; } } return $this->payAcceptedSchedule( schedule: $schedule, paymentMethod: 'pix', ); } // private function isExpiredPixPayment(Payment $payment): bool { if ($payment->payment_method !== 'pix') { return false; } if ($payment->status === PaymentStatusEnum::PAID) { return false; } 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); } public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void { if ($payment->status !== PaymentStatusEnum::PAID || $schedule->status === 'paid') { return; } $schedule->update(['status' => 'paid']); } }