Prechádzať zdrojové kódy

refactor: retira payments para schedule agora o fluxo é somente por service-packages

Gustavo Mantovani 5 dní pred
rodič
commit
27415bf860

+ 4 - 35
app/Http/Controllers/PaymentController.php

@@ -2,17 +2,14 @@
 
 namespace App\Http\Controllers;
 
-use App\Enums\PaymentStatusEnum;
 use App\Exceptions\PaymentFailedException;
 use App\Exceptions\PaymentException;
-use App\Http\Requests\PayScheduleRequest;
 use App\Http\Requests\PayServicePackageRequest;
 use App\Http\Requests\PaymentRequest;
 use App\Http\Resources\PaymentResource;
-use App\Models\Schedule;
+use App\Models\ServicePackage;
 use App\Services\PaymentService;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 
 class PaymentController extends Controller
@@ -62,34 +59,6 @@ class PaymentController extends Controller
 
     //
 
-    public function paySchedule(PayScheduleRequest $request, int $scheduleId): JsonResponse
-    {
-        $validated = $request->validated();
-
-        try {
-            $item = $this->service->paySchedule(
-                scheduleId:            $scheduleId,
-                paymentMethod:         data_get($validated, 'payment_method'),
-                clientPaymentMethodId: data_get($validated, 'client_payment_method_id'),
-
-                options: [
-                    'phone'   => data_get($validated, 'phone'),
-                    'card_id' => data_get($validated, 'card_id'),
-                ],
-            );
-        } catch (PaymentFailedException) {
-            return $this->errorResponse(message: __('messages.payment_not_confirmed'), code: 422);
-        } catch (PaymentException) {
-            return $this->errorResponse(message: __('messages.payment_error'), code: 422);
-        }
-
-        return $this->successResponse(
-            payload: new PaymentResource($item),
-            message: $item->status->message(),
-            code:    201,
-        );
-    }
-
     public function payServicePackage(PayServicePackageRequest $request, int $servicePackageId): JsonResponse
     {
         $validated = $request->validated();
@@ -120,13 +89,13 @@ class PaymentController extends Controller
 
     //
 
-    public function getSchedulePix(Schedule $schedule): JsonResponse
+    public function getServicePackagePix(ServicePackage $servicePackage): JsonResponse
     {
-        if ($schedule->client?->user_id !== Auth::id()) {
+        if ($servicePackage->client?->user_id !== Auth::id()) {
             abort(403);
         }
 
-        $item = $this->service->getOrCreatePixPayment($schedule);
+        $item = $this->service->getOrCreateServicePackagePixPayment($servicePackage);
 
         return $this->successResponse(payload: new PaymentResource($item));
     }

+ 1 - 0
app/Http/Resources/DashboardClienteResource.php

@@ -27,6 +27,7 @@ class DashboardClienteResource extends JsonResource
             'todaySchedules'             => data_get($this, 'todaySchedules'),
             'notifications'              => data_get($this, 'notifications'),
             'has_payment_methods'        => data_get($this, 'has_payment_methods'),
+            'pendingServicePackages'     => ServicePackageResource::collection(data_get($this, 'pendingServicePackages')),
         ];
     }
 }

+ 4 - 0
app/Http/Resources/ServicePackageResource.php

@@ -28,6 +28,10 @@ class ServicePackageResource extends JsonResource
                 $this->items->pluck('schedule')->filter()->values(),
             )),
 
+            'total_amount' => $this->whenLoaded('items', fn () =>
+                (float) $this->items->sum(fn($item) => (float) ($item->schedule?->total_amount ?? 0))
+            ),
+
             'created_at'   => $this->created_at?->toISOString(),
             'updated_at'   => $this->updated_at?->toISOString(),
         ];

+ 12 - 0
app/Services/DashboardService.php

@@ -3,6 +3,7 @@
 namespace App\Services;
 
 use App\Enums\GenderEnum;
+use App\Enums\ServicePackageStatusEnum;
 use App\Enums\UserTypeEnum;
 use App\Models\Address;
 use App\Models\Client;
@@ -14,6 +15,7 @@ use App\Models\ProviderSpeciality;
 use App\Models\Review;
 use App\Models\Schedule;
 use App\Models\ScheduleProposal;
+use App\Models\ServicePackage;
 use App\Models\Speciality;
 use App\Rules\ScheduleBusinessRules;
 use Illuminate\Auth\Access\AuthorizationException;
@@ -587,6 +589,15 @@ class DashboardService
 
         $hasPaymentMethods = ClientPaymentMethod::where('client_id', $cliente->id)->exists();
 
+        $pendingServicePackages = ServicePackage::query()
+            ->where('client_id', $cliente->id)
+            ->where('status', ServicePackageStatusEnum::OPEN->value)
+            ->with(['items.schedule' => function ($query) {
+                $query->with(['provider.user', 'address']);
+            }])
+            ->with('provider.user')
+            ->get();
+
         return [
             'headerBar'                  => $headerBar,
             'summaryInfos'               => $summaryInfos,
@@ -600,6 +611,7 @@ class DashboardService
             'customSchedulesNoProposals' => $custom_schedules_with_no_proposals,
             'notifications'              => $notifications,
             'has_payment_methods'        => $hasPaymentMethods,
+            'pendingServicePackages'     => $pendingServicePackages,
         ];
     }
 

+ 99 - 228
app/Services/PaymentService.php

@@ -78,213 +78,6 @@ class PaymentService
         return $this->pagarmePaymentService->platformFeeRates();
     }
 
-    //
-
-    public function paySchedule(
-        int    $scheduleId,
-        string $paymentMethod,
-        ?int   $clientPaymentMethodId = null,
-        array  $options               = [],
-    ): Payment {
-        $schedule = Schedule::query()
-            ->with(['client', 'provider', 'customSchedule.serviceType'])
-            ->findOrFail($scheduleId);
-
-        if ($schedule->client?->user_id !== (int) Auth::id()) {
-            throw new AuthorizationException;
-        }
-
-        return $this->processAcceptedSchedulePayment(
-            schedule:              $schedule,
-            paymentMethod:         $paymentMethod,
-            clientPaymentMethodId: $clientPaymentMethodId,
-
-            options: $options,
-        );
-    }
-
-    private function processAcceptedSchedulePayment(
-        Schedule $schedule,
-        string   $paymentMethod,
-        ?int     $clientPaymentMethodId = null,
-        array    $options               = [],
-    ): Payment {
-        $schedule->loadMissing(['client', 'provider', 'customSchedule.serviceType']);
-
-        if ($schedule->status !== 'accepted') {
-            throw new PaymentException;
-        }
-
-        if (! in_array($paymentMethod, ['credit_card', 'pix'], true)) {
-            throw new PaymentException;
-        }
-
-        if (! $schedule->provider_id || ! $schedule->provider) {
-            throw new PaymentException;
-        }
-
-        if ((float) $schedule->total_amount <= 0) {
-            throw new PaymentException;
-        }
-
-        if (empty($schedule->provider->recipient_id)) {
-            throw new PaymentException;
-        }
-
-        $this->ensureScheduleCanBePaidIndividually($schedule);
-
-        $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 PaymentException;
-                }
-
-                $this->syncScheduleStatusAfterPayment($schedule, $existingPayment);
-
-                return $existingPayment;
-            }
-        }
-
-        $clientPaymentMethod = null;
-        $cardId              = null;
-
-        if ($paymentMethod === 'credit_card') {
-            if (! $clientPaymentMethodId && empty(data_get($options, 'card_id'))) {
-                throw new PaymentException;
-            }
-
-            if ($clientPaymentMethodId) {
-                $clientPaymentMethod = ClientPaymentMethod::query()
-                    ->where('client_id', $schedule->client_id)
-                    ->where('id', $clientPaymentMethodId)
-                    ->where('is_active', true)
-                    ->first();
-
-                if (! $clientPaymentMethod) {
-                    throw new PaymentException;
-                }
-            }
-
-            $cardId = data_get($options, 'card_id', $clientPaymentMethod?->gateway_card_id);
-
-            if (empty($cardId)) {
-                throw new PaymentException;
-            }
-        }
-
-        $serviceAmount = (float) $schedule->total_amount;
-
-        $amounts = $this->pagarmePaymentService->calculatePaymentAmounts(
-            serviceAmount: $serviceAmount,
-            paymentMethod: $paymentMethod,
-            schedule:      $schedule,
-        );
-
-        $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'             => data_get($amounts, 'gross_amount'),
-            'gateway_fee_amount'       => 0,
-            'platform_fee_amount'      => data_get($amounts, 'platform_fee_amount'),
-            'net_amount'               => data_get($amounts, 'gross_amount'),
-            'currency'                 => 'BRL',
-            'installments'             => 1,
-            'expires_at'               => $paymentMethod === 'pix' ? Carbon::now()->addMinutes(30) : null,
-
-            'metadata' => [
-                'service_amount' => number_format(data_get($amounts, 'service_amount'), 2, '.', ''),
-                'platform_fee'   => number_format(data_get($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(data_get($options, 'phone'));
-
-        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);
-
-        if ($payment->status === PaymentStatusEnum::FAILED) {
-            throw new PaymentFailedException;
-        }
-
-        return $payment;
-    }
-
     public function payServicePackage(
         int    $servicePackageId,
         string $paymentMethod,
@@ -469,12 +262,16 @@ class PaymentService
 
     //
 
-    public function getOrCreatePixPayment(Schedule $schedule): Payment
+    public function getOrCreateServicePackagePixPayment(ServicePackage $servicePackage): Payment
     {
-        $this->ensureScheduleCanBePaidIndividually($schedule);
+        $userId = (int) Auth::id();
+
+        if ($servicePackage->client?->user_id !== $userId) {
+            throw new AuthorizationException;
+        }
 
         $existingPayment = Payment::query()
-            ->where('schedule_id', $schedule->id)
+            ->where('service_package_id', $servicePackage->id)
             ->where('payment_method', 'pix')
             ->whereIn('status', [
                 PaymentStatusEnum::PENDING->value,
@@ -511,16 +308,103 @@ class PaymentService
                     ->where('payment_id', $existingPayment->id)
                     ->update(['status' => PaymentSplitStatusEnum::FAILED]);
             } else {
-                $this->syncScheduleStatusAfterPayment($schedule, $existingPayment);
+                $this->syncPaymentTargets($existingPayment);
 
                 return $existingPayment;
             }
         }
 
-        return $this->processAcceptedSchedulePayment(
-            schedule:      $schedule,
-            paymentMethod: 'pix',
-        );
+        $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 = $servicePackage->items->pluck('schedule')->filter()->values();
+
+            $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 {
+            $orderResponse = $this->pagarmePaymentService->processServicePackagePayment(
+                payment:       $payment,
+                schedules:     $schedules,
+                paymentMethod: 'pix',
+                cardId:        null,
+                options:       [],
+            );
+        } catch (\Throwable $e) {
+            $this->failPayment($payment, $e->getMessage());
+
+            throw $e;
+        }
+
+        $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
+
+        $this->syncPaymentTargets($payment);
+
+        return $payment->fresh(['client.user', 'provider.user', 'servicePackage.items.schedule']);
     }
 
     //
@@ -747,17 +631,4 @@ class PaymentService
             ->where('payment_id', $payment->id)
             ->update(['status' => PaymentSplitStatusEnum::FAILED]);
     }
-
-    private function ensureScheduleCanBePaidIndividually(Schedule $schedule): void
-    {
-        $belongsToMultiItemServicePackage = ServicePackage::query()
-            ->where('status', ServicePackageStatusEnum::OPEN->value)
-            ->whereHas('items', fn ($query) => $query->where('schedule_id', $schedule->id))
-            ->whereHas('items', null, '>', 1)
-            ->exists();
-
-        if ($belongsToMultiItemServicePackage) {
-            throw new PaymentException;
-        }
-    }
 }

+ 2 - 3
routes/authRoutes/payment.php

@@ -6,10 +6,9 @@ use Illuminate\Support\Facades\Route;
 Route::get('/payment',  [PaymentController::class, 'index'])->middleware('permission:payment,view');
 Route::post('/payment', [PaymentController::class, 'store'])->middleware('permission:payment,add');
 
-Route::get('/payment/platform-fees',                         [PaymentController::class, 'platformFees']);
+Route::get('/payment/platform-fees',                           [PaymentController::class, 'platformFees']);
 Route::post('/payment/service-package/{servicePackageId}/pay', [PaymentController::class, 'payServicePackage'])->middleware('permission:config.schedule,edit');
-Route::get('/payment/schedule/{schedule}/pix',               [PaymentController::class, 'getSchedulePix'])->middleware('permission:config.schedule,view');
-Route::post('/payment/schedule/{scheduleId}/pay',            [PaymentController::class, 'paySchedule'])->middleware('permission:config.schedule,edit');
+Route::get('/payment/service-package/{servicePackage}/pix',    [PaymentController::class, 'getServicePackagePix'])->middleware('permission:config.schedule,view');
 
 Route::get('/payment/{id}',    [PaymentController::class, 'show'])->middleware('permission:payment,view');
 Route::put('/payment/{id}',    [PaymentController::class, 'update'])->middleware('permission:payment,edit');