Completed
Push — master ( d38f7e...cf5ef8 )
by Бабичев
12s
created

HasWallet::balance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Bavix\Wallet\Traits;
4
5
use Bavix\Wallet\Exceptions\AmountInvalid;
6
use Bavix\Wallet\Exceptions\BalanceIsEmpty;
7
use Bavix\Wallet\Interfaces\Wallet;
8
use Bavix\Wallet\Models\Transaction;
9
use Bavix\Wallet\Models\Transfer;
10
use Illuminate\Database\Eloquent\Model;
11
use Illuminate\Database\Eloquent\Relations\MorphMany;
12
use Illuminate\Support\Collection;
13
use Illuminate\Support\Facades\DB;
14
use Illuminate\Support\Str;
15
16
/**
17
 * Class HasWallet
18
 *
19
 * @package Bavix\Wallet\Traits
20
 *
21
 * @property-read int $balance
22
 */
23
trait HasWallet
24
{
25
26
    /**
27
     * @var int
28
     */
29
    protected $cachedBalance;
30
31
    /**
32
     * @param int $amount
33
     * @throws
34
     */
35
    private function checkAmount(int $amount): void
36
    {
37
        if ($amount <= 0) {
38
            throw new AmountInvalid('The amount must be greater than zero');
39
        }
40
    }
41
42
    /**
43
     * @param int $amount
44
     * @param array|null $meta
45
     * @param bool $confirmed
46
     *
47
     * @return Transaction
48
     */
49
    public function forceWithdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
50
    {
51
        $this->checkAmount($amount);
52
        return $this->change(-$amount, $meta, $confirmed);
53
    }
54
55
    /**
56
     * @param int $amount
57
     * @param array|null $meta
58
     * @param bool $confirmed
59
     *
60
     * @return Transaction
61
     */
62
    public function deposit(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
63
    {
64
        $this->checkAmount($amount);
65
        return $this->change($amount, $meta, $confirmed);
66
    }
67
68
    /**
69
     * @param int $amount
70
     * @param array|null $meta
71
     * @param bool $confirmed
72
     *
73
     * @return Transaction
74
     */
75
    public function withdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
76
    {
77
        if (!$this->canWithdraw($amount)) {
78
            throw new BalanceIsEmpty('Balance insufficient for write-off');
79
        }
80
81
        return $this->forceWithdraw($amount, $meta, $confirmed);
82
    }
83
84
    /**
85
     * @param int $amount
86
     * @return bool
87
     */
88
    public function canWithdraw(int $amount): bool
89
    {
90
        return $this->balance >= $amount;
91
    }
92
93
    /**
94
     * @param Wallet $wallet
95
     * @param int $amount
96
     * @param array|null $meta
97
     * @return Transfer
98
     * @throws
99
     */
100
    public function transfer(Wallet $wallet, int $amount, ?array $meta = null): Transfer
101
    {
102
        return DB::transaction(function() use ($amount, $wallet, $meta) {
103
            $withdraw = $this->withdraw($amount, $meta);
104
            $deposit = $wallet->deposit($amount, $meta);
105
            return $this->assemble($wallet, $withdraw, $deposit);
106
        });
107
    }
108
109
    /**
110
     * @param Wallet $wallet
111
     * @param int $amount
112
     * @param array|null $meta
113
     * @return null|Transfer
114
     */
115
    public function safeTransfer(Wallet $wallet, int $amount, ?array $meta = null): ?Transfer
116
    {
117
        try {
118
            return $this->transfer($wallet, $amount, $meta);
119
        } catch (\Throwable $throwable) {
120
            return null;
121
        }
122
    }
123
124
    /**
125
     * @param Wallet $wallet
126
     * @param int $amount
127
     * @param array|null $meta
128
     * @return Transfer
129
     */
130
    public function forceTransfer(Wallet $wallet, int $amount, ?array $meta = null): Transfer
131
    {
132
        return DB::transaction(function() use ($amount, $wallet, $meta) {
133
            $withdraw = $this->forceWithdraw($amount, $meta);
134
            $deposit = $wallet->deposit($amount, $meta);
135
            return $this->assemble($wallet, $withdraw, $deposit);
136
        });
137
    }
138
139
    /**
140
     * @param Wallet $wallet
141
     * @param Transaction $withdraw
142
     * @param Transaction $deposit
143
     * @return Transfer
144
     */
145
    protected function assemble(Wallet $wallet, Transaction $withdraw, Transaction $deposit): Transfer
146
    {
147
        /**
148
         * @var Model $wallet
149
         */
150
        return \app(config('wallet.transfer.model'))->create([
0 ignored issues
show
Bug introduced by
The method create() does not exist on Illuminate\Foundation\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

150
        return \app(config('wallet.transfer.model'))->/** @scrutinizer ignore-call */ create([

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
151
            'deposit_id' => $deposit->getKey(),
152
            'withdraw_id' => $withdraw->getKey(),
153
            'from_type' => $this->getMorphClass(),
0 ignored issues
show
Bug introduced by
It seems like getMorphClass() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
            'from_type' => $this->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
154
            'from_id' => $this->getKey(),
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

154
            'from_id' => $this->/** @scrutinizer ignore-call */ getKey(),
Loading history...
155
            'to_type' => $wallet->getMorphClass(),
156
            'to_id' => $wallet->getKey(),
157
            'uuid' => Str::uuid()->toString(),
158
        ]);
159
    }
160
161
    /**
162
     * @param int $amount
163
     * @param array|null $meta
164
     * @param bool $confirmed
165
     * @return Transaction
166
     */
167
    protected function change(int $amount, ?array $meta, bool $confirmed): Transaction
168
    {
169
        $this->getBalanceAttribute();
170
        $this->cachedBalance += $amount;
171
        return $this->transactions()->create([
172
            'type' => $amount > 0 ? 'deposit' : 'withdraw',
173
            'payable_type' => $this->getMorphClass(),
174
            'payable_id' => $this->getKey(),
175
            'uuid' => Str::uuid()->toString(),
176
            'confirmed' => $confirmed,
177
            'amount' => $amount,
178
            'meta' => $meta,
179
        ]);
180
    }
181
182
    /**
183
     * @return MorphMany
184
     */
185
    public function transactions(): MorphMany
186
    {
187
        return $this->morphMany(config('wallet.transaction.model'), 'payable');
0 ignored issues
show
Bug introduced by
It seems like morphMany() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

187
        return $this->/** @scrutinizer ignore-call */ morphMany(config('wallet.transaction.model'), 'payable');
Loading history...
188
    }
189
190
    /**
191
     * @return MorphMany
192
     */
193
    public function transfers(): MorphMany
194
    {
195
        return $this->morphMany(config('wallet.transfer.model'), 'from');
196
    }
197
198
    /**
199
     * @return MorphMany
200
     */
201
    public function balance(): MorphMany
202
    {
203
        return $this->transactions()
204
            ->selectRaw('payable_id, sum(amount) as total')
205
            ->groupBy('payable_id');
206
    }
207
208
    /**
209
     * @return int
210
     */
211
    public function getBalanceAttribute(): int
212
    {
213
        if (null === $this->cachedBalance) {
214
            if (!\array_key_exists('balance', $this->relations)) {
215
                $this->load('balance');
0 ignored issues
show
Bug introduced by
It seems like load() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

215
                $this->/** @scrutinizer ignore-call */ 
216
                       load('balance');
Loading history...
216
            }
217
218
            /**
219
             * @var Collection $collection
220
             */
221
            $collection = $this->getRelation('balance');
0 ignored issues
show
Bug introduced by
It seems like getRelation() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
            /** @scrutinizer ignore-call */ 
222
            $collection = $this->getRelation('balance');
Loading history...
222
            $relation = $collection->first();
223
            $this->cachedBalance = (int)($relation->total ?? 0);
224
        }
225
226
        return $this->cachedBalance;
227
    }
228
229
}
230