Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.03% covered (success)
94.03%
63 / 67
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RecordPaymentAction
94.03% covered (success)
94.03%
63 / 67
50.00% covered (danger)
50.00%
2 / 4
20.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
7
 syncSchedule
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
6.10
 syncTransaction
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
6.06
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Financial\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\Financial\Domain\Enums\PaymentMethod;
10use ImovelZapAi\Financial\Domain\Enums\PaymentScheduleStatus;
11use ImovelZapAi\Financial\Domain\Enums\PaymentStatus;
12use ImovelZapAi\Financial\Domain\Enums\TransactionStatus;
13use ImovelZapAi\Financial\Domain\Events\PaymentReceived;
14use ImovelZapAi\Financial\Domain\Exceptions\FinancialException;
15use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialPaymentModel;
16use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialPaymentScheduleModel;
17use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialTransactionModel;
18
19final class RecordPaymentAction
20{
21    public function __construct(
22        private readonly GenerateReceiptAction $generateReceipt,
23    ) {}
24
25    /**
26     * @param  array{
27     *     deal_id: string,
28     *     amount: float|string,
29     *     method: string,
30     *     paid_at?: string|null,
31     *     schedule_id?: string|null,
32     *     transaction_id?: string|null,
33     *     account_id?: string|null,
34     *     reference?: string|null,
35     *     proof_path?: string|null,
36     *     generate_receipt?: bool|null,
37     * }  $data
38     */
39    public function execute(string $tenantId, ?User $actor, array $data): FinancialPaymentModel
40    {
41        return DB::transaction(function () use ($tenantId, $actor, $data): FinancialPaymentModel {
42            $method = PaymentMethod::tryFrom((string) $data['method']);
43            if ($method === null) {
44                throw FinancialException::invalidState('forma de pagamento inválida');
45            }
46
47            $payment = FinancialPaymentModel::query()->create([
48                'tenant_id' => $tenantId,
49                'deal_id' => $data['deal_id'],
50                'schedule_id' => $data['schedule_id'] ?? null,
51                'transaction_id' => $data['transaction_id'] ?? null,
52                'account_id' => $data['account_id'] ?? null,
53                'method' => $method,
54                'amount' => $data['amount'],
55                'paid_at' => $data['paid_at'] ?? now(),
56                'reference' => $data['reference'] ?? null,
57                'proof_path' => $data['proof_path'] ?? null,
58                'status' => PaymentStatus::Confirmed,
59                'created_by' => $actor?->id,
60            ]);
61
62            if (! empty($data['schedule_id'])) {
63                $this->syncSchedule($tenantId, (string) $data['schedule_id']);
64            }
65
66            if (! empty($data['transaction_id'])) {
67                $this->syncTransaction($tenantId, (string) $data['transaction_id']);
68            } elseif (! empty($data['schedule_id'])) {
69                $schedule = FinancialPaymentScheduleModel::query()
70                    ->where('tenant_id', $tenantId)
71                    ->find($data['schedule_id']);
72                if ($schedule?->transaction_id !== null) {
73                    $this->syncTransaction($tenantId, (string) $schedule->transaction_id);
74                }
75            }
76
77            PaymentReceived::dispatch($tenantId, (string) $payment->id);
78
79            $autoReceipt = $data['generate_receipt']
80                ?? (bool) config('financial.auto_receipt_on_payment', true);
81
82            if ($autoReceipt) {
83                $this->generateReceipt->execute($tenantId, (string) $payment->id);
84            }
85
86            return $payment->fresh(['receipt']);
87        });
88    }
89
90    private function syncSchedule(string $tenantId, string $scheduleId): void
91    {
92        $schedule = FinancialPaymentScheduleModel::query()
93            ->where('tenant_id', $tenantId)
94            ->find($scheduleId);
95
96        if ($schedule === null) {
97            return;
98        }
99
100        $paid = (float) FinancialPaymentModel::query()
101            ->where('tenant_id', $tenantId)
102            ->where('schedule_id', $scheduleId)
103            ->where('status', PaymentStatus::Confirmed)
104            ->sum('amount');
105
106        if ($paid >= (float) $schedule->amount) {
107            $schedule->update(['status' => PaymentScheduleStatus::Paid]);
108        } elseif ($paid > 0 && $schedule->due_at !== null && $schedule->due_at->isPast()) {
109            $schedule->update(['status' => PaymentScheduleStatus::Overdue]);
110        }
111    }
112
113    private function syncTransaction(string $tenantId, string $transactionId): void
114    {
115        $transaction = FinancialTransactionModel::query()
116            ->where('tenant_id', $tenantId)
117            ->find($transactionId);
118
119        if ($transaction === null) {
120            return;
121        }
122
123        $paid = (float) FinancialPaymentModel::query()
124            ->where('tenant_id', $tenantId)
125            ->where('transaction_id', $transactionId)
126            ->where('status', PaymentStatus::Confirmed)
127            ->sum('amount');
128
129        $target = (float) $transaction->amount;
130
131        if ($paid >= $target) {
132            $transaction->update(['status' => TransactionStatus::Paid]);
133        } elseif ($paid > 0) {
134            $transaction->update(['status' => TransactionStatus::Partial]);
135        } elseif ($transaction->due_at !== null && $transaction->due_at->isPast()) {
136            $transaction->update(['status' => TransactionStatus::Overdue]);
137        }
138    }
139}