orderBy('created_at', 'desc') ->get(); } public function findById(int $id): ?Payment { return Payment::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 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(); } 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; 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.'); } } if ( empty($clientPaymentMethod?->gateway_card_id) && empty($options['card_id']) ) { throw new \InvalidArgumentException('Cartao de pagamento invalido ou sem gateway_card_id do Pagar.me.'); } } $serviceAmount = (float) $schedule->total_amount; $platformFee = round($serviceAmount * 0.11, 2); $grossAmount = round($serviceAmount + $platformFee, 2); $items = $this->buildOrderItems($schedule, $grossAmount); $this->ensureCustomerPhoneForPayment($schedule, $options); $customer = $this->buildCustomerPayload(schedule: $schedule, options: $options, requirePhone: true); $platformRecipientId = config('services.pagarme.platform_recipient_id'); if ($platformFee > 0 && empty($platformRecipientId)) { throw new \InvalidArgumentException('PAGARME_PLATFORM_RECIPIENT_ID precisa estar configurado para receber a taxa da plataforma no split.'); } $payment = Payment::create([ 'schedule_id' => $schedule->id, 'client_id' => $schedule->client_id, 'provider_id' => $schedule->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' => $grossAmount, 'gateway_fee_amount' => 0, 'platform_fee_amount' => $platformFee, 'net_amount' => $grossAmount, 'currency' => 'BRL', 'installments' => 1, 'expires_at' => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null, 'metadata' => [ 'service_amount' => number_format($serviceAmount, 2, '.', ''), 'platform_fee' => number_format($platformFee, 2, '.', ''), ], ]); $transfer = PaymentSplit::create([ 'payment_id' => $payment->id, 'provider_id' => $schedule->provider_id, 'gateway_provider' => 'pagarme', 'gateway_transfer_target_reference' => $schedule->provider->recipient_id, 'gateway_transfer_target_label' => 'recipient', 'status' => PaymentSplitStatusEnum::PENDING, 'gross_amount' => $serviceAmount, 'gateway_fee_amount' => 0, 'net_amount' => $serviceAmount, 'metadata' => [ 'schedule_id' => (string) $schedule->id, ], ]); $split = $this->pagarmePaymentService->buildSplitFromTransfers(collect([$transfer])); if ($platformFee > 0) { $split[] = [ 'amount' => $this->pagarmePaymentService->toGatewayAmountInCents($platformFee), 'recipient_id' => $platformRecipientId, 'type' => 'flat', 'options' => [ 'charge_processing_fee' => true, 'charge_remainder_fee' => true, 'liable' => true, ], ]; } $pixOptions = config('services.pagarme.pix_disable_split') ? [] : ['split' => $split]; try { $creditCardReference = $paymentMethod === 'credit_card' ? $this->resolveCreditCardReference($clientPaymentMethod, $options) : []; $orderResponse = $paymentMethod === 'credit_card' ? $this->pagarmePaymentService->createOrderWithCreditCard( payment: $payment, items: $items, customer: $customer, creditCard: [ 'installments' => 1, 'statement_descriptor' => Str::limit((string) config('app.name', 'SOFTPAR'), 13, ''), 'operation_type' => 'auth_and_capture', ] + $creditCardReference, options: [ 'split' => $split, ], ) : $this->pagarmePaymentService->createOrderWithPix( payment: $payment, items: $items, customer: $customer, pix: [ 'expires_in' => '1800', 'additional_information' => [ [ 'name' => 'Agendamento', 'value' => (string) $schedule->id, ], ], ], options: $pixOptions, ); } catch (\Throwable $e) { $payment->forceFill([ 'status' => PaymentStatusEnum::FAILED, 'failed_at' => now(), 'failure_message' => $e->getMessage(), ])->save(); $transfer->update(['status' => PaymentSplitStatusEnum::FAILED]); throw $e; } $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse); $this->syncScheduleStatusAfterPayment($schedule, $payment); return $payment; } // private function buildOrderItems(Schedule $schedule, float $grossAmount): array { $description = $schedule->customSchedule?->serviceType?->description ?? "Servico {$schedule->id}"; return [[ 'amount' => $this->pagarmePaymentService->toGatewayAmountInCents($grossAmount), 'description' => $description, 'quantity' => 1, 'code' => "schedule-{$schedule->id}", ]]; } private function buildCustomerPayload( Schedule $schedule, array $options = [], bool $requirePhone = true ): array { $client = $schedule->client; $user = $client->user()->first(['id', 'name', 'email', 'phone']); $address = Address::with(['city.state', 'state'])->find($schedule->address_id); foreach ([ 'nome' => $user?->name, 'email' => $user?->email, 'documento' => $client->document, ] as $field => $value) { if ($value === null || $value === '') { throw new \InvalidArgumentException("Cliente precisa ter {$field} para criar pedido no Pagar.me."); } } if (! $address) { throw new \InvalidArgumentException('Endereco do agendamento nao encontrado para criar pedido no Pagar.me.'); } $document = $this->digits($client->document); $phone = $this->buildPhonePayload($user->phone) ?: $this->buildPhonePayload($options['phone'] ?? null); $state = $address->state?->code ?? $address->city?->state?->code; $city = $address->city?->name; $zipCode = $this->digits($address->zip_code); $line1 = implode(', ', array_filter([ $address->number ?: 'S/N', $address->address, $address->district, ])); $requiredFields = [ 'documento' => $document, 'estado' => $state, 'cidade' => $city, 'cep' => $zipCode, 'endereco' => $line1, ]; if ($requirePhone) { $requiredFields['telefone'] = $phone; } foreach ($requiredFields as $field => $value) { if ($value === null || $value === '' || $value === []) { throw new \InvalidArgumentException("Cliente precisa ter {$field} valido para criar pedido no Pagar.me."); } } return [ 'name' => $user->name, 'email' => $user->email, 'code' => "client-{$client->id}", 'document' => $document, 'document_type' => strlen($document) === 14 ? 'CNPJ' : 'CPF', 'type' => strlen($document) === 14 ? 'company' : 'individual', 'address' => [ 'country' => 'BR', 'state' => $state, 'city' => $city, 'zip_code' => $zipCode, 'line_1' => $line1, 'line_2' => $address->complement ?: $address->instructions, ], 'phones' => $phone ? ['mobile_phone' => $phone] : null, ]; } private function buildPhonePayload(?string $phone): ?array { $digits = $this->digits($phone); if (strlen($digits) < 10) { return null; } if (str_starts_with($digits, '55')) { $digits = substr($digits, 2); } return [ 'country_code' => '55', 'area_code' => substr($digits, 0, 2), 'number' => substr($digits, 2), ]; } private function ensureCustomerPhoneForPayment(Schedule $schedule, array $options = []): void { $userPhone = $schedule->client?->user?->phone; $phone = $this->buildPhonePayload($userPhone) ?: $this->buildPhonePayload($options['phone'] ?? null); if ($phone) { return; } throw new \InvalidArgumentException( 'Voce precisa cadastrar um numero de celular valido no seu perfil para concluir o pagamento.' ); } private function digits(?string $value): string { return preg_replace('/\D+/', '', (string) $value) ?? ''; } 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 resolveCreditCardReference(?ClientPaymentMethod $clientPaymentMethod, array $options): array { if (! empty($options['card_id'])) { return ['card_id' => $options['card_id']]; } if (! empty($clientPaymentMethod?->gateway_card_id)) { return ['card_id' => $clientPaymentMethod->gateway_card_id]; } throw new \InvalidArgumentException('Cartao de pagamento precisa ter gateway_card_id do Pagar.me.'); } public function syncScheduleStatusAfterPayment(Schedule $schedule, Payment $payment): void { if ($payment->status !== PaymentStatusEnum::PAID || $schedule->status === 'paid') { return; } $schedule->update(['status' => 'paid']); } }