Преглед на файлове

chore: remove models e fluxos nao usados

Gustavo Mantovani преди 2 седмици
родител
ревизия
8384d0ebe4

+ 0 - 35
app/Data/Pagarme/Anticipation/AnticipationLimitData.php

@@ -1,35 +0,0 @@
-<?php
-
-namespace App\Data\Pagarme\Anticipation;
-
-use App\Data\Pagarme\PagarmeResponseData;
-
-final readonly class AnticipationLimitData extends PagarmeResponseData
-{
-    public function __construct(
-        public int $amount,
-        public int $anticipationFee,
-        public int $fee,
-        public int $fraudCoverageFee,
-    ) {}
-
-    public static function fromArray(array $payload): static
-    {
-        return new self(
-            amount:           static::arrInt($payload, 'amount')             ?? 0,
-            anticipationFee:  static::arrInt($payload, 'anticipation_fee')   ?? 0,
-            fee:              static::arrInt($payload, 'fee')                ?? 0,
-            fraudCoverageFee: static::arrInt($payload, 'fraud_coverage_fee') ?? 0,
-        );
-    }
-
-    public function toArray(): array
-    {
-        return [
-            'amount'              => $this->amount,
-            'anticipation_fee'    => $this->anticipationFee,
-            'fee'                 => $this->fee,
-            'fraud_coverage_fee'  => $this->fraudCoverageFee,
-        ];
-    }
-}

+ 0 - 29
app/Data/Pagarme/Anticipation/BulkAnticipationLimitsResponseData.php

@@ -1,29 +0,0 @@
-<?php
-
-namespace App\Data\Pagarme\Anticipation;
-
-use App\Data\Pagarme\PagarmeResponseData;
-
-final readonly class BulkAnticipationLimitsResponseData extends PagarmeResponseData
-{
-    public function __construct(
-        public AnticipationLimitData $maximum,
-        public AnticipationLimitData $minimum,
-    ) {}
-
-    public static function fromArray(array $payload): static
-    {
-        return new self(
-            maximum: AnticipationLimitData::fromArray(static::arrArray($payload, 'maximum')),
-            minimum: AnticipationLimitData::fromArray(static::arrArray($payload, 'minimum')),
-        );
-    }
-
-    public function toArray(): array
-    {
-        return [
-            'maximum' => $this->maximum->toArray(),
-            'minimum' => $this->minimum->toArray(),
-        ];
-    }
-}

+ 0 - 29
app/Data/Pagarme/Anticipation/BulkAnticipationRequestData.php

@@ -1,29 +0,0 @@
-<?php
-
-namespace App\Data\Pagarme\Anticipation;
-
-use App\Data\Pagarme\PagarmeData;
-
-final readonly class BulkAnticipationRequestData extends PagarmeData
-{
-    public function __construct(
-        public string $paymentDate,
-        public string $timeframe,
-        public int    $requestedAmount,
-        public bool   $automaticTransfer = false,
-    ) {
-        self::requireFilled($this->paymentDate, 'payment_date');
-        self::requireIn($this->timeframe, ['start', 'end'], 'timeframe');
-        self::requirePositiveInt($this->requestedAmount, 'requested_amount');
-    }
-
-    public function toArray(): array
-    {
-        return $this->filterFilledRecursive([
-            'payment_date'       => $this->paymentDate,
-            'timeframe'          => $this->timeframe,
-            'requested_amount'   => $this->requestedAmount,
-            'automatic_transfer' => $this->automaticTransfer,
-        ]);
-    }
-}

+ 0 - 71
app/Data/Pagarme/Anticipation/BulkAnticipationResponseData.php

@@ -1,71 +0,0 @@
-<?php
-
-namespace App\Data\Pagarme\Anticipation;
-
-use App\Data\Pagarme\PagarmeResponseData;
-
-final readonly class BulkAnticipationResponseData extends PagarmeResponseData
-{
-    public function __construct(
-        public ?string $id,
-        public ?string $status,
-        public ?int    $amount,
-        public ?int    $fee,
-        public ?int    $fraudCoverageFee,
-        public ?int    $anticipationFee,
-        public ?bool   $automaticTransfer,
-        public ?string $type,
-        public ?string $timeframe,
-        public ?string $paymentDate,
-        public ?string $createdAt       = null,
-        public ?string $updatedAt       = null,
-        public mixed   $anticipationTax = null,
-    ) {}
-
-    public function requireId(): string
-    {
-        if (! $this->id) {
-            throw new \RuntimeException('Pagar.me bulk anticipation creation returned an empty id.');
-        }
-
-        return $this->id;
-    }
-
-    public static function fromArray(array $payload): static
-    {
-        return new self(
-            id:                static::arrString($payload, 'id'),
-            status:            static::arrString($payload, 'status'),
-            amount:            static::arrInt($payload, 'amount'),
-            fee:               static::arrInt($payload, 'fee'),
-            fraudCoverageFee:  static::arrInt($payload, 'fraud_coverage_fee'),
-            anticipationFee:   static::arrInt($payload, 'anticipation_fee'),
-            automaticTransfer: static::arrBool($payload, 'automatic_transfer'),
-            type:              static::arrString($payload, 'type'),
-            timeframe:         static::arrString($payload, 'timeframe'),
-            paymentDate:       static::arrString($payload, 'payment_date'),
-            createdAt:         static::arrString($payload, 'created_at'),
-            updatedAt:         static::arrString($payload, 'updated_at'),
-            anticipationTax:   static::arrGet($payload, 'anticipation_tax'),
-        );
-    }
-
-    public function toArray(): array
-    {
-        return array_filter([
-            'id'                 => $this->id,
-            'status'             => $this->status,
-            'amount'             => $this->amount,
-            'fee'                => $this->fee,
-            'fraud_coverage_fee' => $this->fraudCoverageFee,
-            'anticipation_fee'   => $this->anticipationFee,
-            'automatic_transfer' => $this->automaticTransfer,
-            'type'               => $this->type,
-            'timeframe'          => $this->timeframe,
-            'payment_date'       => $this->paymentDate,
-            'created_at'         => $this->createdAt,
-            'updated_at'         => $this->updatedAt,
-            'anticipation_tax'   => $this->anticipationTax,
-        ], static fn ($v) => $v !== null);
-    }
-}

+ 0 - 2
app/Http/Resources/PaymentResource.php

@@ -20,8 +20,6 @@ class PaymentResource extends JsonResource
             'id'                          => $this->id,
             'schedule_id'                 => $this->schedule_id,
             'schedule'                    => new ScheduleResource($this->whenLoaded('schedule')),
-            'schedule_ids'                => $this->whenLoaded('schedules', fn () => $this->schedules->pluck('id')->values()),
-            'schedules'                   => $this->whenLoaded('schedules', fn () => ScheduleResource::collection($this->schedules)),
             'client_id'                   => $this->client_id,
             'provider_id'                 => $this->provider_id,
             'client_name'                 => $this->client?->user?->name,

+ 0 - 8
app/Models/Payment.php

@@ -43,8 +43,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property-read \App\Models\ClientPaymentMethod|null $clientPaymentMethod
  * @property-read \App\Models\Provider|null $provider
  * @property-read \App\Models\Schedule|null $schedule
- * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Schedule> $schedules
- * @property-read int|null $schedules_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentSplit> $splits
  * @property-read int|null $splits_count
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Webhook> $webhooks
@@ -160,12 +158,6 @@ class Payment extends Model
         return $this->belongsTo(Schedule::class);
     }
 
-    public function schedules()
-    {
-        return $this->belongsToMany(Schedule::class, 'payment_schedules')
-            ->withTimestamps();
-    }
-
     //
 
     public function clientPaymentMethod()

+ 0 - 52
app/Models/PaymentSchedule.php

@@ -1,52 +0,0 @@
-<?php
-
-namespace App\Models;
-
-use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Relations\BelongsTo;
-
-/**
- * @property int $id
- * @property int $payment_id
- * @property int $schedule_id
- * @property \Illuminate\Support\Carbon|null $created_at
- * @property \Illuminate\Support\Carbon|null $updated_at
- * @property-read \App\Models\Payment $payment
- * @property-read \App\Models\Schedule $schedule
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule query()
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule wherePaymentId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule whereScheduleId($value)
- * @method static \Illuminate\Database\Eloquent\Builder<static>|PaymentSchedule whereUpdatedAt($value)
- * @mixin \Eloquent
- */
-class PaymentSchedule extends Model
-{
-    use HasFactory;
-
-    protected $table = 'payment_schedules';
-
-    protected $fillable = [
-        'payment_id',
-        'schedule_id',
-    ];
-
-    protected $casts = [
-        'created_at' => 'datetime',
-        'updated_at' => 'datetime',
-    ];
-
-    public function payment(): BelongsTo
-    {
-        return $this->belongsTo(Payment::class);
-    }
-
-    public function schedule(): BelongsTo
-    {
-        return $this->belongsTo(Schedule::class);
-    }
-}

+ 15 - 0
app/Services/Pagarme/Concerns/FormatsPagarmeData.php

@@ -12,6 +12,21 @@ trait FormatsPagarmeData
         return preg_replace('/\D+/', '', (string) $value) ?? '';
     }
 
+    protected function pagarmeIdempotencyKey(string $prefix, array $parts): string
+    {
+        $segments = [Str::slug($prefix)];
+
+        foreach ($parts as $part) {
+            $slug = Str::slug(trim((string) $part));
+
+            if ($slug !== '') {
+                $segments[] = $slug;
+            }
+        }
+
+        return implode('-', array_filter($segments));
+    }
+
     //
 
     protected function customerDocument(?string $value): string

+ 0 - 239
app/Services/Pagarme/PagarmeAnticipationService.php

@@ -1,239 +0,0 @@
-<?php
-
-namespace App\Services\Pagarme;
-
-use App\Data\Pagarme\Anticipation\AnticipationLimitData;
-use App\Data\Pagarme\Anticipation\BulkAnticipationLimitsResponseData;
-use App\Data\Pagarme\Anticipation\BulkAnticipationRequestData;
-use App\Data\Pagarme\Anticipation\BulkAnticipationResponseData;
-use App\Data\Pagarme\Order\OrderRequestData;
-use App\Models\Payment;
-use App\Services\Pagarme\Concerns\MocksPagarmeRequests;
-use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
-use Illuminate\Support\Facades\Http;
-use Illuminate\Support\Facades\Log;
-use Throwable;
-
-class PagarmeAnticipationService
-{
-    use MocksPagarmeRequests;
-    use SendsPagarmeRequests;
-
-    public function createBulkAnticipation(
-        Payment $payment,
-        ?BulkAnticipationLimitsResponseData $limits = null,
-    ): ?BulkAnticipationResponseData
-    {
-        $provider = $payment->provider()->first();
-
-        if (! $provider) {
-            return null;
-        }
-
-        $recipientId = $provider->recipient_id;
-
-        if (empty($recipientId)) {
-            return null;
-        }
-
-        $providerSplit = $payment->splits()
-            ->where('provider_id', $payment->provider_id)
-            ->first();
-
-        if (! $providerSplit) {
-            return null;
-        }
-
-        $requestedAmount = OrderRequestData::amountInCents((float) $providerSplit->gross_amount);
-
-        if ($requestedAmount <= 0) {
-            return null;
-        }
-
-        $paymentDate = $this->getAnticipationPaymentDate();
-
-        $limits ??= $this->fetchAnticipationLimits($recipientId, $paymentDate);
-
-        if ($limits && $limits->maximum->amount <= 0) {
-            Log::channel('pagarme')->warning('Antecipacao ignorada: recebedor sem valor disponivel para antecipar.', [
-                'payment_id'       => $payment->id,
-                'provider_id'      => $payment->provider_id,
-                'recipient_id'     => $recipientId,
-                'requested_amount' => $requestedAmount,
-                'minimum_amount'   => $limits->minimum->amount,
-                'maximum_amount'   => $limits->maximum->amount,
-            ]);
-
-            return null;
-        }
-
-        if ($limits && $limits->minimum->amount > 0 && $requestedAmount < $limits->minimum->amount) {
-            Log::channel('pagarme')->warning('Antecipacao ignorada: valor abaixo do minimo do recebedor.', [
-                'payment_id'       => $payment->id,
-                'provider_id'      => $payment->provider_id,
-                'recipient_id'     => $recipientId,
-                'requested_amount' => $requestedAmount,
-                'minimum_amount'   => $limits->minimum->amount,
-                'maximum_amount'   => $limits->maximum->amount,
-            ]);
-
-            return null;
-        }
-
-        if ($limits && $limits->maximum->amount > 0 && $requestedAmount > $limits->maximum->amount) {
-            Log::channel('pagarme')->warning('Antecipacao ignorada: valor acima do maximo disponivel do recebedor.', [
-                'payment_id'       => $payment->id,
-                'provider_id'      => $payment->provider_id,
-                'recipient_id'     => $recipientId,
-                'requested_amount' => $requestedAmount,
-                'minimum_amount'   => $limits->minimum->amount,
-                'maximum_amount'   => $limits->maximum->amount,
-            ]);
-
-            return null;
-        }
-
-        try {
-            if ($this->shouldMockPagarmeRequest()) {
-                return new BulkAnticipationResponseData(
-                    id:                $this->mockPagarmeId('ba', "{$payment->id}-{$payment->provider_id}"),
-                    status:            'pending',
-                    amount:            $requestedAmount,
-                    fee:               0,
-                    fraudCoverageFee:  0,
-                    anticipationFee:   0,
-                    automaticTransfer: false,
-                    type:              'full',
-                    timeframe:         'start',
-                    paymentDate:       $paymentDate,
-                    createdAt:         now()->toISOString(),
-                    updatedAt:         now()->toISOString(),
-                    anticipationTax:   ['mocked' => true],
-                );
-            }
-
-            $response = BulkAnticipationResponseData::fromArray($this->pagarmeRequest(
-                method: 'POST',
-                path:   "/recipients/{$recipientId}/bulk_anticipations",
-
-                payload: new BulkAnticipationRequestData(
-                    paymentDate:       $paymentDate,
-                    timeframe:         'start',
-                    requestedAmount:   $requestedAmount,
-                    automaticTransfer: false,
-                ),
-
-                idempotencyKey: "bulk-ant-{$payment->id}-{$payment->provider_id}",
-                errorMessage:   'Erro ao criar antecipacao no Pagar.me.',
-            ));
-
-            $response->requireId();
-
-            return $response;
-        } catch (Throwable $e) {
-            Log::channel('pagarme')->warning('Falha ao criar antecipacao no Pagar.me.', [
-                'payment_id'       => $payment->id,
-                'provider_id'      => $payment->provider_id,
-                'recipient_id'     => $recipientId,
-                'requested_amount' => $requestedAmount,
-                'minimum_amount'   => $limits?->minimum->amount,
-                'maximum_amount'   => $limits?->maximum->amount,
-                'error'            => $e->getMessage(),
-            ]);
-
-            return null;
-        }
-    }
-
-    public function fetchAnticipationLimitsForPayment(Payment $payment): ?BulkAnticipationLimitsResponseData
-    {
-        $provider = $payment->provider()->first();
-
-        if (! $provider || empty($provider->recipient_id)) {
-            return null;
-        }
-
-        return $this->fetchAnticipationLimits(
-            recipientId: $provider->recipient_id,
-            paymentDate: $this->getAnticipationPaymentDate(),
-        );
-    }
-
-    private function fetchAnticipationLimits(string $recipientId, string $paymentDate): ?BulkAnticipationLimitsResponseData
-    {
-        try {
-            if ($this->shouldMockPagarmeRequest()) {
-                return new BulkAnticipationLimitsResponseData(
-                    maximum: new AnticipationLimitData(
-                        amount:           100000000,
-                        anticipationFee:  0,
-                        fee:              0,
-                        fraudCoverageFee: 0,
-                    ),
-                    minimum: new AnticipationLimitData(
-                        amount:           1,
-                        anticipationFee:  0,
-                        fee:              0,
-                        fraudCoverageFee: 0,
-                    ),
-                );
-            }
-
-            $secretKey = config('services.pagarme.secret_key');
-
-            if (empty($secretKey)) {
-                return null;
-            }
-
-            $endpoint = $this->pagarmeUrl("/recipients/{$recipientId}/bulk_anticipations/limits");
-
-            $response = Http::withBasicAuth($secretKey, '')
-                ->withHeaders([
-                    'Content-Type' => 'application/json',
-                    'Accept'       => 'application/json',
-                ])
-                ->get($endpoint, [
-                    'payment_date' => $paymentDate,
-                    'timeframe'    => 'start',
-                ])
-                ->throw();
-
-            $body = $response->json() ?? [];
-            $limits = BulkAnticipationLimitsResponseData::fromArray($body);
-
-            Log::channel('pagarme')->info('Limites de antecipacao consultados no Pagar.me.', [
-                'recipient_id'   => $recipientId,
-                'payment_date'   => $paymentDate,
-                'minimum_amount' => $limits->minimum->amount,
-                'maximum_amount' => $limits->maximum->amount,
-            ]);
-
-            return $limits;
-        } catch (Throwable $e) {
-            Log::channel('pagarme')->warning('Falha ao consultar limites de antecipacao.', [
-                'recipient_id' => $recipientId,
-                'payment_date' => $paymentDate,
-                'error'        => $e->getMessage(),
-            ]);
-
-            return null;
-        }
-    }
-
-    private function getAnticipationPaymentDate(): string
-    {
-        $now = now()->timezone('America/Sao_Paulo');
-
-        if ($now->hour < 11) {
-            return $now->toISOString();
-        }
-
-        $nextBusinessDay = $now->copy()->addDay();
-
-        while ($nextBusinessDay->isWeekend()) {
-            $nextBusinessDay->addDay();
-        }
-
-        return $nextBusinessDay->startOfDay()->toISOString();
-    }
-}

+ 16 - 1
app/Services/Pagarme/PagarmeCardService.php

@@ -7,11 +7,13 @@ use App\Data\Pagarme\Card\CardResponseData;
 use App\Data\Pagarme\Card\Parts\Request\BillingAddressData;
 use App\Models\Address;
 use App\Models\ClientPaymentMethod;
+use App\Services\Pagarme\Concerns\FormatsPagarmeData;
 use App\Services\Pagarme\Concerns\MocksPagarmeRequests;
 use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
 
 class PagarmeCardService
 {
+    use FormatsPagarmeData;
     use MocksPagarmeRequests;
     use SendsPagarmeRequests;
 
@@ -86,7 +88,20 @@ class PagarmeCardService
             return $paymentMethod->idempotency_key;
         }
 
-        $key = 'card-'.(string) \Illuminate\Support\Str::uuid();
+        $paymentMethod->loadMissing('client.user');
+
+        $email = $paymentMethod->client?->user?->email
+            ?: "client-{$paymentMethod->client_id}";
+
+        $lastFourDigits = $paymentMethod->last_four_digits
+            ?: substr($this->digits($paymentMethod->card_number), -4)
+            ?: "card-{$paymentMethod->id}";
+
+        $key = $this->pagarmeIdempotencyKey('card', [
+            $email,
+            $lastFourDigits,
+            $paymentMethod->brand ?: 'unknown-brand',
+        ]);
 
         $paymentMethod->forceFill(['idempotency_key' => $key])->save();
 

+ 6 - 1
app/Services/Pagarme/PagarmeCustomerService.php

@@ -129,7 +129,12 @@ class PagarmeCustomerService
             return $client->idempotency_key;
         }
 
-        $key = 'customer-'.(string) \Illuminate\Support\Str::uuid();
+        $client->loadMissing('user');
+
+        $key = $this->pagarmeIdempotencyKey('customer', [
+            $client->user?->email ?: "client-{$client->id}",
+            $this->customerDocument($client->document) ?: "document-client-{$client->id}",
+        ]);
 
         $client->forceFill(['idempotency_key' => $key])->save();
 

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

@@ -26,7 +26,6 @@ use App\Models\Schedule;
 use App\Services\Pagarme\Concerns\FormatsPagarmeData;
 use App\Services\Pagarme\Concerns\MocksPagarmeRequests;
 use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
-use Illuminate\Support\Collection;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Str;
 
@@ -62,15 +61,6 @@ class PagarmePaymentService
         ];
     }
 
-    public function platformFeeRates(): array
-    {
-        return [
-            'pix'                  => (float) config('services.pagarme.platform_pix_fee_rate'),
-            'credit_card'          => (float) config('services.pagarme.platform_credit_card_fee_rate'),
-            'cart_min_3_schedules' => (float) config('services.pagarme.platform_cart_min_3_schedules_fee_rate'),
-        ];
-    }
-
     private function platformFeeRate(string $paymentMethod, ?Schedule $schedule = null): float
     {
         if ($schedule && $this->scheduleBelongsToCartWithAtLeastThreeItems($schedule)) {
@@ -82,12 +72,13 @@ class PagarmePaymentService
             : (float) config('services.pagarme.platform_pix_fee_rate');
     }
 
-    private function scheduleBelongsToCartWithAtLeastThreeItems(Schedule $schedule): bool
+    public function platformFeeRates(): array
     {
-        return Cart::query()
-            ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
-            ->whereHas('items', null, '>=', 3)
-            ->exists();
+        return [
+            'pix'                  => (float) config('services.pagarme.platform_pix_fee_rate'),
+            'credit_card'          => (float) config('services.pagarme.platform_credit_card_fee_rate'),
+            'cart_min_3_schedules' => (float) config('services.pagarme.platform_cart_min_3_schedules_fee_rate'),
+        ];
     }
 
     public function processPayment(
@@ -156,84 +147,7 @@ class PagarmePaymentService
         );
     }
 
-    public function processCartPayment(
-        Payment    $payment,
-        Collection $schedules,
-        string     $paymentMethod,
-        ?string    $cardId  = null,
-        array      $options = [],
-    ): array {
-        $firstSchedule = $schedules->first();
-
-        if (! $firstSchedule) {
-            throw new \InvalidArgumentException('Carrinho precisa ter ao menos um agendamento.');
-        }
-
-        $items = $schedules
-            ->map(fn (Schedule $schedule) => $this->buildOrderItem(
-                schedule: $schedule,
-                grossAmount: (float) data_get($schedule, 'payment_gross_amount', $schedule->total_amount),
-            ))
-            ->values()
-            ->all();
-
-        $customer = $this->buildCustomer($firstSchedule, $options);
-        $split    = $this->buildSplit($payment, $options);
-
-        $metadataOptions = [
-            'metadata' => [
-                'schedule_ids' => $schedules->pluck('id')->implode(','),
-            ],
-        ];
-
-        $splitOptions = ['split' => $split];
-
-        $pixOptions = config('services.pagarme.pix_disable_split')
-            ? $metadataOptions
-            : array_merge($metadataOptions, $splitOptions);
-
-        $creditCardOptions = array_merge($metadataOptions, $splitOptions);
-
-        if ($paymentMethod === 'credit_card') {
-            $creditCard = new CreditCardData(
-                cardId:              $cardId,
-                installments:        $payment->installments,
-                statementDescriptor: Str::limit((string) config('app.name', 'SOFTPAR'), 13, ''),
-                operationType:       'auth_and_capture',
-            );
-
-            $result = $this->createOrderWithCreditCard(
-                payment:    $payment,
-                items:      $items,
-                customer:   $customer,
-                creditCard: $creditCard,
-                options:    $creditCardOptions,
-            );
-
-            $this->logCreditCardOrderResult($payment, $result, 'create_cart_order');
-
-            return $result;
-        }
-
-        $pixData = new PixData(
-            expiresIn: 1800,
-
-            additionalInformation: [
-                new PixAdditionalInformationData(
-                    name:  'Agendamentos',
-                    value: $schedules->pluck('id')->implode(','),
-                ),
-            ],
-        );
-
-        return $this->createOrderWithPix(
-            payment:  $payment,
-            items:    $items,
-            customer: $customer,
-            pix:      $pixData,
-            options:  $pixOptions,
-        );
-    }
+    //
 
     public function createOrderWithCreditCard(
         Payment             $payment,
@@ -389,6 +303,26 @@ class PagarmePaymentService
 
     //
 
+    private function scheduleBelongsToCartWithAtLeastThreeItems(Schedule $schedule): bool
+    {
+        return Cart::query()
+            ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
+            ->whereHas('items', null, '>=', 3)
+            ->exists();
+    }
+
+    //
+
+    private function mockCustomerResponse(OrderRequestData $requestData): array
+    {
+        $customer = $requestData->customer?->toArray() ?? [];
+
+        return array_merge($customer, [
+            'id'         => $requestData->customerId ?: $this->mockPagarmeId('cus', $customer['code'] ?? $requestData->code),
+            'delinquent' => false,
+        ]);
+    }
+
     private function mockOrderResponse(
         Payment $payment, OrderRequestData $requestData, PaymentData $paymentMethod,
     ): array {
@@ -461,16 +395,6 @@ class PagarmePaymentService
         ];
     }
 
-    private function mockCustomerResponse(OrderRequestData $requestData): array
-    {
-        $customer = $requestData->customer?->toArray() ?? [];
-
-        return array_merge($customer, [
-            'id'         => $requestData->customerId ?: $this->mockPagarmeId('cus', $customer['code'] ?? $requestData->code),
-            'delinquent' => false,
-        ]);
-    }
-
     //
 
     private function buildCustomer(Schedule $schedule, array $options = []): CustomerRequestData
@@ -717,7 +641,17 @@ class PagarmePaymentService
             return $payment->idempotency_key;
         }
 
-        $key = 'order-'.(string) \Illuminate\Support\Str::uuid();
+        $payment->loadMissing(['client.user', 'provider.user', 'schedule']);
+
+        $amountCents = (int) round((float) $payment->gross_amount * 100);
+
+        $key = $this->pagarmeIdempotencyKey('order', [
+            "schedule-{$payment->schedule_id}",
+            $payment->client?->user?->name ?: "client-{$payment->client_id}",
+            $payment->provider?->user?->name ?: ($payment->provider_id ? "provider-{$payment->provider_id}" : 'provider-none'),
+            $payment->payment_method,
+            "amount-{$amountCents}",
+        ]);
 
         $payment->forceFill(['idempotency_key' => $key])->save();
 

+ 9 - 3
app/Services/Pagarme/PagarmeRecipientService.php

@@ -121,7 +121,7 @@ class PagarmeRecipientService
             method:         'POST',
             path:           '/recipients',
             payload:        $payload,
-            idempotencyKey: $this->idempotencyKey($provider),
+            idempotencyKey: $this->idempotencyKey($provider, '', $data),
             errorMessage:   'Erro ao criar recebedor no Pagar.me.',
         );
 
@@ -237,12 +237,18 @@ class PagarmeRecipientService
 
     // evita criacao duplica de recipient
 
-    private function idempotencyKey(Provider $provider, string $suffix = ''): string
+    private function idempotencyKey(Provider $provider, string $suffix = '', array $data = []): string
     {
         $baseKey = $provider->idempotency_key;
 
         if (empty($baseKey)) {
-            $baseKey = 'recipient-'.(string) \Illuminate\Support\Str::uuid();
+            $provider->loadMissing('user');
+
+            $baseKey = $this->pagarmeIdempotencyKey('recipient', [
+                $data['recipient_email'] ?? $provider->recipient_email ?? $provider->user?->email ?? "provider-{$provider->id}",
+                $this->customerDocument($data['recipient_document'] ?? $provider->recipient_document ?? $provider->document)
+                    ?: "document-provider-{$provider->id}",
+            ]);
 
             $provider->forceFill(['idempotency_key' => $baseKey])->save();
         }

+ 0 - 108
app/Services/PaymentService.php

@@ -12,10 +12,7 @@ use App\Models\PaymentSplit;
 use App\Models\Schedule;
 use App\Services\Pagarme\PagarmePaymentService;
 use Carbon\Carbon;
-use Illuminate\Auth\Access\AuthorizationException;
 use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Support\Collection as SupportCollection;
-use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Str;
 
 class PaymentService
@@ -341,74 +338,6 @@ class PaymentService
         $this->syncCartsForSchedule($schedule);
     }
 
-    private function validateCartSchedules(SupportCollection $schedules, string $paymentMethod): void
-    {
-        if ($schedules->isEmpty()) {
-            throw new \InvalidArgumentException('Carrinho precisa ter ao menos um agendamento.');
-        }
-
-        if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
-            throw new \InvalidArgumentException('Forma de pagamento invalida.');
-        }
-
-        $clientIds = $schedules->pluck('client_id')->unique()->values();
-
-        if ($clientIds->count() !== 1) {
-            throw new \InvalidArgumentException('Todos os agendamentos do carrinho precisam ser do mesmo cliente.');
-        }
-
-        $schedules->each(function (Schedule $schedule): void {
-            $schedule->loadMissing(['client', 'provider', 'customSchedule.serviceType']);
-
-            if ($schedule->status !== 'accepted') {
-                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa estar aceito para ser pago.");
-            }
-
-            if (! $schedule->provider_id || ! $schedule->provider) {
-                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa ter prestador confirmado para gerar pagamento.");
-            }
-
-            if ((float) $schedule->total_amount <= 0) {
-                throw new \InvalidArgumentException("Agendamento {$schedule->id} precisa ter valor maior que zero para gerar pagamento.");
-            }
-
-            if (empty($schedule->provider->recipient_id)) {
-                throw new \InvalidArgumentException("Prestador do agendamento {$schedule->id} 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) {
-                throw new \InvalidArgumentException("Ja existe um pagamento em andamento para o agendamento {$schedule->id}.");
-            }
-        });
-    }
-
-    //
-
-    private function cartSchedules(Cart $cart): SupportCollection
-    {
-        $schedules = $cart->items
-            ->map(fn ($item) => $item->schedule)
-            ->filter()
-            ->values();
-
-        if ($schedules->isEmpty() || $schedules->count() !== $cart->items->count()) {
-            throw new \InvalidArgumentException('Um ou mais agendamentos nao foram encontrados.');
-        }
-
-        return $schedules;
-    }
-
     private function syncCartsForSchedule(Schedule $schedule): void
     {
         Cart::query()
@@ -446,41 +375,4 @@ class PaymentService
             $cart->update(['status' => CartStatusEnum::PAID->value]);
         }
     }
-
-    public function syncSchedulesStatusAfterPayment(Payment $payment): void
-    {
-        $payment->loadMissing('schedules');
-
-        if ($payment->schedules->isEmpty()) {
-            if ($payment->schedule) {
-                $this->syncScheduleStatusAfterPayment($payment->schedule, $payment);
-            }
-
-            return;
-        }
-
-        $payment->schedules->each(fn (Schedule $schedule) => $this->syncScheduleStatusAfterPayment($schedule, $payment));
-    }
-
-    //
-
-    private function cartPaymentTotals(SupportCollection $schedules, string $paymentMethod): array
-    {
-        return $schedules->reduce(function (array $totals, Schedule $schedule) use ($paymentMethod) {
-            $amounts = $this->pagarmePaymentService->calculatePaymentAmounts(
-                serviceAmount: (float) $schedule->total_amount,
-                paymentMethod: $paymentMethod,
-            );
-
-            return [
-                'service_amount'      => $totals['service_amount'] + $amounts['service_amount'],
-                'platform_fee_amount' => $totals['platform_fee_amount'] + $amounts['platform_fee_amount'],
-                'gross_amount'        => $totals['gross_amount'] + $amounts['gross_amount'],
-            ];
-        }, [
-            'service_amount'      => 0,
-            'platform_fee_amount' => 0,
-            'gross_amount'        => 0,
-        ]);
-    }
 }

+ 34 - 0
database/migrations/2026_07_14_000000_remove_payment_schedules_and_require_schedule_id_on_payments_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::dropIfExists('payment_schedules');
+
+        Schema::table('payments', function (Blueprint $table) {
+            $table->foreignId('schedule_id')->nullable(false)->change();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('payments', function (Blueprint $table) {
+            $table->foreignId('schedule_id')->nullable()->change();
+        });
+
+        Schema::create('payment_schedules', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('payment_id')->constrained('payments')->onDelete('cascade');
+            $table->foreignId('schedule_id')->constrained('schedules')->onDelete('cascade');
+            $table->timestamps();
+
+            $table->unique(['payment_id', 'schedule_id']);
+            $table->index('schedule_id');
+        });
+    }
+};