Gustavo Mantovani 2 дней назад
Родитель
Сommit
3bcb876339

+ 4 - 0
.env.example

@@ -50,3 +50,7 @@ PAGARME_WEBHOOK_PASSWORD=
 PAGARME_PLATFORM_RECIPIENT_ID=
 
 PAGARME_PIX_DISABLE_SPLIT=false
+
+PAGARME_PLATFORM_FEE_RATE=0.11
+
+PAGARME_TRANSFER_FEE_AMOUNT=3.67

+ 44 - 0
_ide_helper.php

@@ -22782,6 +22782,49 @@ namespace Illuminate\Support\Facades {
             }
     }
 
+namespace Kreait\Laravel\Firebase\Facades {
+    /**
+     * @method static AppCheck appCheck()
+     * @method static Auth auth()
+     * @method static Database database()
+     * @method static Firestore firestore()
+     * @method static Messaging messaging()
+     * @method static RemoteConfig remoteConfig()
+     * @method static Storage storage()
+     * @see FirebaseProjectManager
+     * @see FirebaseProject
+     */
+    class Firebase {
+        /**
+         * @static
+         */
+        public static function project($name = null)
+        {
+            /** @var \Kreait\Laravel\Firebase\FirebaseProjectManager $instance */
+            return $instance->project($name);
+        }
+
+        /**
+         * @static
+         */
+        public static function getDefaultProject()
+        {
+            /** @var \Kreait\Laravel\Firebase\FirebaseProjectManager $instance */
+            return $instance->getDefaultProject();
+        }
+
+        /**
+         * @static
+         */
+        public static function setDefaultProject($name)
+        {
+            /** @var \Kreait\Laravel\Firebase\FirebaseProjectManager $instance */
+            return $instance->setDefaultProject($name);
+        }
+
+            }
+    }
+
 namespace Illuminate\Http {
     /**
      */
@@ -27678,6 +27721,7 @@ namespace  {
     class Validator extends \Illuminate\Support\Facades\Validator {}
     class View extends \Illuminate\Support\Facades\View {}
     class Vite extends \Illuminate\Support\Facades\Vite {}
+    class Firebase extends \Kreait\Laravel\Firebase\Facades\Firebase {}
 }
 
 

+ 9 - 9
app/Data/Pagarme/Request/BankAccountUpdateRequestData.php

@@ -31,15 +31,15 @@ final readonly class BankAccountUpdateRequestData extends PagarmeData
     {
         return $this->filterFilledRecursive([
             'bank_account' => [
-                'holder_name'         => $this->holderName,
-                'holder_type'         => $this->holderType,
-                'holder_document'     => $this->holderDocument,
-                'bank'                => $this->bank,
-                'branch_number'       => $this->branchNumber,
-                'branch_check_digit'  => $this->branchCheckDigit,
-                'account_number'      => $this->accountNumber,
-                'account_check_digit' => $this->accountCheckDigit,
-                'type'                => $this->type,
+                'holder_name'          => $this->holderName,
+                'holder_type'          => $this->holderType,
+                'holder_document'      => $this->holderDocument,
+                'bank'                 => $this->bank,
+                'branch_number'        => $this->branchNumber,
+                'branch_check_digit'   => $this->branchCheckDigit,
+                'account_number'       => $this->accountNumber,
+                'account_check_digit'  => $this->accountCheckDigit,
+                'type'                 => $this->type,
             ],
         ]);
     }

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

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Data\Pagarme\Request;
+
+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 - 3
app/Data/Pagarme/Request/OrderRequestData/OrderPaymentData/OrderPaymentData.php

@@ -11,7 +11,6 @@ final readonly class OrderPaymentData extends PagarmeData
     /**
      * @param  OrderSplitData[]|null  $split
      */
-
     public function __construct(
         public string $paymentMethod,
 
@@ -34,7 +33,6 @@ final readonly class OrderPaymentData extends PagarmeData
     /**
      * @param  OrderSplitData[]|null  $split
      */
-
     public static function creditCard(OrderCreditCardData $creditCard, ?array $split = null): self
     {
         return new self(
@@ -47,7 +45,6 @@ final readonly class OrderPaymentData extends PagarmeData
     /**
      * @param  OrderSplitData[]|null  $split
      */
-
     public static function pix(OrderPixData $pix, ?array $split = null): self
     {
         return new self(

+ 0 - 4
app/Data/Pagarme/Request/OrderRequestData/OrderRequestData.php

@@ -18,7 +18,6 @@ final readonly class OrderRequestData extends PagarmeData
      * @param  OrderItemData[]  $items
      * @param  OrderPaymentData[]  $payments
      */
-
     public function __construct(
         public string  $code,
         public array   $items,
@@ -55,7 +54,6 @@ final readonly class OrderRequestData extends PagarmeData
     /**
      * @param  OrderSplitData[]|null  $split
      */
-
     public static function creditCardPaymentMethod(OrderCreditCardData $creditCard, ?array $split = null): OrderPaymentData
     {
         return OrderPaymentData::creditCard($creditCard, $split);
@@ -64,7 +62,6 @@ final readonly class OrderRequestData extends PagarmeData
     /**
      * @param  OrderSplitData[]|null  $split
      */
-
     public static function pixPaymentMethod(OrderPixData $pix, ?array $split = null): OrderPaymentData
     {
         return OrderPaymentData::pix($pix, $split);
@@ -74,7 +71,6 @@ final readonly class OrderRequestData extends PagarmeData
      * @param  Collection<PaymentSplit>  $transfers
      * @return OrderSplitData[]
      */
-
     public static function splitFromTransfers(Collection $transfers): array
     {
         return $transfers

+ 9 - 9
app/Data/Pagarme/Request/RecipientRequestData/RecipientBankAccountData.php

@@ -30,15 +30,15 @@ final readonly class RecipientBankAccountData extends PagarmeData
     public function toArray(): array
     {
         return $this->filterFilledRecursive([
-            'holder_name'         => $this->holderName,
-            'holder_type'         => $this->holderType,
-            'holder_document'     => $this->holderDocument,
-            'bank'                => $this->bank,
-            'branch_number'       => $this->branchNumber,
-            'branch_check_digit'  => $this->branchCheckDigit,
-            'account_number'      => $this->accountNumber,
-            'account_check_digit' => $this->accountCheckDigit,
-            'type'                => $this->type,
+            'holder_name'          => $this->holderName,
+            'holder_type'          => $this->holderType,
+            'holder_document'      => $this->holderDocument,
+            'bank'                 => $this->bank,
+            'branch_number'        => $this->branchNumber,
+            'branch_check_digit'   => $this->branchCheckDigit,
+            'account_number'       => $this->accountNumber,
+            'account_check_digit'  => $this->accountCheckDigit,
+            'type'                 => $this->type,
         ]);
     }
 }

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

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Data\Pagarme\Response;
+
+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);
+    }
+}

+ 7 - 1
app/Jobs/FinishScheduleJob.php

@@ -28,6 +28,7 @@ class FinishScheduleJob implements ShouldQueue
     {
         try {
             $schedule = Schedule::find($this->scheduleId);
+
             $date_cleaned = Carbon::parse($schedule->date)->format('Y-m-d');
 
             if (! $schedule) {
@@ -78,11 +79,16 @@ class FinishScheduleJob implements ShouldQueue
             ]);
 
             $emailService = new EmailService;
+
             $serviceAmount = (float) $schedule->total_amount;
-            $serviceFee = $serviceAmount * 0.11;
+
+            $serviceFee = ($serviceAmount * (float) config('services.pagarme.platform_fee_rate', 0.11))
+                + (float) config('services.pagarme.transfer_fee_amount', 3.67);
+
             $finalAmount = $serviceAmount + $serviceFee;
 
             $email_cliente = User::find($schedule->client->user_id)->email;
+
             $address = Address::find($schedule->address_id);
 
             $emailService->sendEmailReceipt(

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

@@ -0,0 +1,76 @@
+<?php
+
+namespace App\Services\Pagarme;
+
+use App\Data\Pagarme\Request\BulkAnticipationRequestData;
+use App\Data\Pagarme\Request\OrderRequestData\OrderRequestData;
+use App\Data\Pagarme\Response\BulkAnticipationResponseData;
+use App\Models\Payment;
+use App\Services\Pagarme\Concerns\SendsPagarmeRequests;
+use Illuminate\Support\Facades\Log;
+use Throwable;
+
+class PagarmeAnticipationService
+{
+    use SendsPagarmeRequests;
+
+    public function createBulkAnticipation(Payment $payment): ?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;
+        }
+
+        try {
+            $response = BulkAnticipationResponseData::fromArray($this->pagarmeRequest(
+                method: 'POST',
+                path:   "/recipients/{$recipientId}/bulk_anticipations",
+
+                payload: new BulkAnticipationRequestData(
+                    paymentDate:      now()->toISOString(),
+                    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,
+                'error'         => $e->getMessage(),
+            ]);
+
+            return null;
+        }
+    }
+}

+ 9 - 1
app/Services/Pagarme/PagarmePaymentService.php

@@ -31,6 +31,10 @@ class PagarmePaymentService
     use FormatsPagarmeData;
     use SendsPagarmeRequests;
 
+    public function __construct(
+        private readonly PagarmeAnticipationService $anticipationService,
+    ) {}
+
     public function processPayment(
         Payment  $payment,
         Schedule $schedule,
@@ -58,13 +62,17 @@ class PagarmePaymentService
                 operationType:       'auth_and_capture',
             );
 
-            return $this->createOrderWithCreditCard(
+            $result = $this->createOrderWithCreditCard(
                 payment:    $payment,
                 items:      $items,
                 customer:   $customer,
                 creditCard: $creditCard,
                 options:    $orderOptions,
             );
+
+            $this->anticipationService->createBulkAnticipation($payment);
+
+            return $result;
         }
 
         $pixData = new OrderPixData(

+ 4 - 2
app/Services/PaymentService.php

@@ -162,8 +162,9 @@ class PaymentService
 
         $serviceAmount = (float) $schedule->total_amount;
 
-        $platformFee = round($serviceAmount * 0.11, 2);
-        $grossAmount = round($serviceAmount + $platformFee, 2);
+        $platformFee = round($serviceAmount * (float) config('services.pagarme.platform_fee_rate', 0.11), 2);
+        $transferFee = round((float) config('services.pagarme.transfer_fee_amount', 3.67), 2);
+        $grossAmount = round($serviceAmount + $platformFee + $transferFee, 2);
 
         $platformRecipientId = config('services.pagarme.platform_recipient_id');
 
@@ -191,6 +192,7 @@ class PaymentService
             'metadata' => [
                 'service_amount' => number_format($serviceAmount, 2, '.', ''),
                 'platform_fee'   => number_format($platformFee, 2, '.', ''),
+                'transfer_fee'   => number_format($transferFee, 2, '.', ''),
             ],
         ]);
 

+ 4 - 2
app/Services/ProviderWithdrawalService.php

@@ -101,7 +101,8 @@ class ProviderWithdrawalService
                 'net_amount'      => $available,
                 'status'          => ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
                 'bank_account'    => $bankAccount,
-                'metadata'        => [
+
+                'metadata' => [
                     'provider_id' => $provider->id,
                     'amount'      => $available,
                 ],
@@ -118,7 +119,7 @@ class ProviderWithdrawalService
                     'transfer_id'        => $transfer->id,
                     'status'             => $transfer->status ?? ProviderWithdrawalStatusEnum::PROCESSING->value,
                     'type'               => $transfer->type,
-                    'gateway_fee_amount' => $transfer->fee ?? 0,
+                    'gateway_fee_amount' => ($transfer->fee ?? 0) / 100,
                     'net_amount'         => max(0, $available - ($transfer->fee ?? 0) / 100),
                     'gateway_payload'    => $transfer->toArray(),
                 ])->save();
@@ -249,4 +250,5 @@ class ProviderWithdrawalService
     {
         return Carbon::now()->subDays(5)->format('Y-m-d H:i:s');
     }
+
 }

+ 0 - 1
composer.json

@@ -19,7 +19,6 @@
     "require-dev": {
         "barryvdh/laravel-ide-helper": "^3.6",
         "fakerphp/faker": "^1.23",
-        "laravel/pint": "^1.13",
         "laravel/sail": "^1.26",
         "mockery/mockery": "^1.6",
         "nunomaduro/collision": "^8.0",

+ 4 - 73
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "73e33a3fb94ef20d8acfd663e2eea0c5",
+    "content-hash": "2d12a35327a589e43b4d781782e651a2",
     "packages": [
         {
             "name": "aws/aws-crt-php",
@@ -8292,75 +8292,6 @@
             },
             "time": "2025-04-30T06:54:44+00:00"
         },
-        {
-            "name": "laravel/pint",
-            "version": "v1.24.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/laravel/pint.git",
-                "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a",
-                "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-mbstring": "*",
-                "ext-tokenizer": "*",
-                "ext-xml": "*",
-                "php": "^8.2.0"
-            },
-            "require-dev": {
-                "friendsofphp/php-cs-fixer": "^3.82.2",
-                "illuminate/view": "^11.45.1",
-                "larastan/larastan": "^3.5.0",
-                "laravel-zero/framework": "^11.45.0",
-                "mockery/mockery": "^1.6.12",
-                "nunomaduro/termwind": "^2.3.1",
-                "pestphp/pest": "^2.36.0"
-            },
-            "bin": [
-                "builds/pint"
-            ],
-            "type": "project",
-            "autoload": {
-                "files": [
-                    "overrides/Runner/Parallel/ProcessFactory.php"
-                ],
-                "psr-4": {
-                    "App\\": "app/",
-                    "Database\\Seeders\\": "database/seeders/",
-                    "Database\\Factories\\": "database/factories/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nuno Maduro",
-                    "email": "enunomaduro@gmail.com"
-                }
-            ],
-            "description": "An opinionated code formatter for PHP.",
-            "homepage": "https://laravel.com",
-            "keywords": [
-                "format",
-                "formatter",
-                "lint",
-                "linter",
-                "php"
-            ],
-            "support": {
-                "issues": "https://github.com/laravel/pint/issues",
-                "source": "https://github.com/laravel/pint"
-            },
-            "time": "2025-07-10T18:09:32+00:00"
-        },
         {
             "name": "laravel/sail",
             "version": "v1.43.1",
@@ -10343,12 +10274,12 @@
     ],
     "aliases": [],
     "minimum-stability": "stable",
-    "stability-flags": [],
+    "stability-flags": {},
     "prefer-stable": true,
     "prefer-lowest": false,
     "platform": {
         "php": "^8.3"
     },
-    "platform-dev": [],
-    "plugin-api-version": "2.3.0"
+    "platform-dev": {},
+    "plugin-api-version": "2.6.0"
 }

+ 2 - 0
config/services.php

@@ -47,6 +47,8 @@ return [
         'webhook_password'      => env('PAGARME_WEBHOOK_PASSWORD'),
         'platform_recipient_id' => env('PAGARME_PLATFORM_RECIPIENT_ID'),
         'pix_disable_split'     => env('PAGARME_PIX_DISABLE_SPLIT', false),
+        'platform_fee_rate'     => env('PAGARME_PLATFORM_FEE_RATE', 0.11),
+        'transfer_fee_amount'   => env('PAGARME_TRANSFER_FEE_AMOUNT', 3.67),
     ],
 
 ];

+ 0 - 20
pint.json

@@ -1,20 +0,0 @@
-{
-    "preset": "laravel",
-    "exclude": [
-        "database/migrations",
-        "routes",
-        "app/Data/Pagarme",
-        "app/Enums",
-        "app/Http/Requests",
-        "app/Models",
-        "app/Services/Pagarme"
-    ],
-    "rules": {
-        "binary_operator_spaces": {
-            "default": "single_space",
-            "operators": {
-                "=>": "align_single_space_minimal"
-            }
-        }
-    }
-}