Completed
Pull Request — master (#40)
by Бабичев
06:24
created

HasWallet::addBalance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 11
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Bavix\Wallet\Traits;
4
5
use Bavix\Wallet\Exceptions\BalanceIsEmpty;
6
use Bavix\Wallet\Exceptions\InsufficientFunds;
7
use Bavix\Wallet\Interfaces\Wallet;
8
use Bavix\Wallet\Models\Transaction;
9
use Bavix\Wallet\Models\Transfer;
10
use Bavix\Wallet\Models\Wallet as WalletModel;
11
use Bavix\Wallet\Objects\Bring;
12
use Bavix\Wallet\Objects\Operation;
13
use Bavix\Wallet\Services\CommonService;
14
use Bavix\Wallet\Services\ProxyService;
15
use Bavix\Wallet\Services\WalletService;
16
use Illuminate\Database\Eloquent\Relations\MorphMany;
17
use Illuminate\Database\Eloquent\Relations\MorphOne;
18
use Illuminate\Support\Collection;
19
use Illuminate\Support\Facades\DB;
20
use Throwable;
21
use function app;
22
use function config;
23
use function current;
24
25
/**
26
 * Trait HasWallet
27
 *
28
 * @package Bavix\Wallet\Traits
29
 *
30
 * @property-read WalletModel $wallet
31
 * @property-read Collection|WalletModel[] $wallets
32
 * @property-read int $balance
33
 */
34
trait HasWallet
35
{
36
37
    /**
38
     * The input means in the system
39
     *
40
     * @param int $amount
41
     * @param array|null $meta
42
     * @param bool $confirmed
43
     *
44
     * @return Transaction
45
     */
46 33
    public function deposit(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
47
    {
48 33
        $walletService = app(WalletService::class);
49 33
        $walletService->checkAmount($amount);
50
51
        /**
52
         * @var WalletModel $wallet
53
         */
54 30
        $wallet = $walletService->getWallet($this);
0 ignored issues
show
Bug introduced by
$this of type Bavix\Wallet\Traits\HasWallet is incompatible with the type Bavix\Wallet\Interfaces\Wallet expected by parameter $object of Bavix\Wallet\Services\WalletService::getWallet(). ( Ignorable by Annotation )

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

54
        $wallet = $walletService->getWallet(/** @scrutinizer ignore-type */ $this);
Loading history...
55
56 30
        $transactions = app(CommonService::class)->enforce($wallet, [
57 30
            (new Operation())
58 30
                ->setType(Transaction::TYPE_DEPOSIT)
59 30
                ->setConfirmed($confirmed)
60 30
                ->setAmount($amount)
61 30
                ->setMeta($meta)
62
        ]);
63
64 30
        return current($transactions);
65
    }
66
67
    /**
68
     * Magic laravel framework method, makes it
69
     *  possible to call property balance
70
     *
71
     * Example:
72
     *  $user1 = User::first()->load('wallet');
73
     *  $user2 = User::first()->load('wallet');
74
     *
75
     * Without static:
76
     *  var_dump($user1->balance, $user2->balance); // 100 100
77
     *  $user1->deposit(100);
78
     *  $user2->deposit(100);
79
     *  var_dump($user1->balance, $user2->balance); // 200 200
80
     *
81
     * With static:
82
     *  var_dump($user1->balance, $user2->balance); // 100 100
83
     *  $user1->deposit(100);
84
     *  var_dump($user1->balance); // 200
85
     *  $user2->deposit(100);
86
     *  var_dump($user2->balance); // 300
87
     *
88
     * @return int
89
     * @throws
90
     */
91 40
    public function getBalanceAttribute(): int
92
    {
93 40
        if ($this instanceof WalletModel) {
94 40
            $this->exists or $this->save();
95 40
            $proxy = app(ProxyService::class);
96 40
            if (!$proxy->has($this->getKey())) {
0 ignored issues
show
Bug introduced by
It seems like $this->getKey() can also be of type boolean and null; however, parameter $key of Bavix\Wallet\Services\ProxyService::has() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

96
            if (!$proxy->has(/** @scrutinizer ignore-type */ $this->getKey())) {
Loading history...
97 40
                $proxy->set($this->getKey(), (int)($this->attributes['balance'] ?? 0));
0 ignored issues
show
Bug Best Practice introduced by
The property $attributes is declared protected in Illuminate\Database\Eloquent\Model. Since you implement __get, consider adding a @property or @property-read.
Loading history...
Bug introduced by
It seems like $this->getKey() can also be of type boolean and null; however, parameter $key of Bavix\Wallet\Services\ProxyService::set() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

97
                $proxy->set(/** @scrutinizer ignore-type */ $this->getKey(), (int)($this->attributes['balance'] ?? 0));
Loading history...
98
            }
99
100 40
            return $proxy[$this->getKey()];
101
        }
102
103 39
        return $this->wallet->balance;
104
    }
105
106
    /**
107
     * all user actions on wallets will be in this method
108
     *
109
     * @return MorphMany
110
     */
111 30
    public function transactions(): MorphMany
112
    {
113 30
        return ($this instanceof WalletModel ? $this->holder : $this)
114 30
            ->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

114
            ->/** @scrutinizer ignore-call */ morphMany(config('wallet.transaction.model'), 'payable');
Loading history...
115
    }
116
117
    /**
118
     * This method ignores errors that occur when transferring funds
119
     *
120
     * @param Wallet $wallet
121
     * @param int $amount
122
     * @param array|null $meta
123
     * @param string $status
124
     * @return null|Transfer
125
     */
126 3
    public function safeTransfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): ?Transfer
127
    {
128
        try {
129 3
            return $this->transfer($wallet, $amount, $meta, $status);
130 3
        } catch (Throwable $throwable) {
131 3
            return null;
132
        }
133
    }
134
135
    /**
136
     * A method that transfers funds from host to host
137
     *
138
     * @param Wallet $wallet
139
     * @param int $amount
140
     * @param array|null $meta
141
     * @param string $status
142
     * @return Transfer
143
     * @throws
144
     */
145 16
    public function transfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer
146
    {
147 16
        app(CommonService::class)->verifyWithdraw($this, $amount);
0 ignored issues
show
Bug introduced by
$this of type Bavix\Wallet\Traits\HasWallet is incompatible with the type Bavix\Wallet\Interfaces\Wallet expected by parameter $wallet of Bavix\Wallet\Services\Co...rvice::verifyWithdraw(). ( Ignorable by Annotation )

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

147
        app(CommonService::class)->verifyWithdraw(/** @scrutinizer ignore-type */ $this, $amount);
Loading history...
148 16
        return $this->forceTransfer($wallet, $amount, $meta, $status);
149
    }
150
151
    /**
152
     * Withdrawals from the system
153
     *
154
     * @param int $amount
155
     * @param array|null $meta
156
     * @param bool $confirmed
157
     *
158
     * @return Transaction
159
     */
160 27
    public function withdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
161
    {
162 27
        app(CommonService::class)->verifyWithdraw($this, $amount);
0 ignored issues
show
Bug introduced by
$this of type Bavix\Wallet\Traits\HasWallet is incompatible with the type Bavix\Wallet\Interfaces\Wallet expected by parameter $wallet of Bavix\Wallet\Services\Co...rvice::verifyWithdraw(). ( Ignorable by Annotation )

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

162
        app(CommonService::class)->verifyWithdraw(/** @scrutinizer ignore-type */ $this, $amount);
Loading history...
163 20
        return $this->forceWithdraw($amount, $meta, $confirmed);
164
    }
165
166
    /**
167
     * Checks if you can withdraw funds
168
     *
169
     * @param int $amount
170
     * @return bool
171
     */
172 28
    public function canWithdraw(int $amount): bool
173
    {
174 28
        return $this->balance >= $amount;
175
    }
176
177
    /**
178
     * Forced to withdraw funds from system
179
     *
180
     * @param int $amount
181
     * @param array|null $meta
182
     * @param bool $confirmed
183
     *
184
     * @return Transaction
185
     */
186 28
    public function forceWithdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
187
    {
188 28
        $walletService = app(WalletService::class);
189 28
        $walletService->checkAmount($amount);
190
191
        /**
192
         * @var WalletModel $wallet
193
         */
194 28
        $wallet = $walletService->getWallet($this);
0 ignored issues
show
Bug introduced by
$this of type Bavix\Wallet\Traits\HasWallet is incompatible with the type Bavix\Wallet\Interfaces\Wallet expected by parameter $object of Bavix\Wallet\Services\WalletService::getWallet(). ( Ignorable by Annotation )

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

194
        $wallet = $walletService->getWallet(/** @scrutinizer ignore-type */ $this);
Loading history...
195
196 28
        $transactions = app(CommonService::class)->enforce($wallet, [
197 28
            (new Operation())
198 28
                ->setType(Transaction::TYPE_WITHDRAW)
199 28
                ->setConfirmed($confirmed)
200 28
                ->setAmount(-$amount)
201 28
                ->setMeta($meta)
202
        ]);
203
204 28
        return current($transactions);
205
    }
206
207
    /**
208
     * the forced transfer is needed when the user does not have the money and we drive it.
209
     * Sometimes you do. Depends on business logic.
210
     *
211
     * @param Wallet $wallet
212
     * @param int $amount
213
     * @param array|null $meta
214
     * @param string $status
215
     * @return Transfer
216
     */
217 17
    public function forceTransfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer
218
    {
219
        return DB::transaction(function () use ($amount, $wallet, $meta, $status) {
220 17
            $fee = app(WalletService::class)
221 17
                ->fee($wallet, $amount);
222
223 17
            $withdraw = $this->forceWithdraw($amount + $fee, $meta);
224 17
            $deposit = $wallet->deposit($amount, $meta);
225
226 17
            $from = app(WalletService::class)
227 17
                ->getWallet($this);
0 ignored issues
show
Bug introduced by
$this of type Bavix\Wallet\Traits\HasWallet is incompatible with the type Bavix\Wallet\Interfaces\Wallet expected by parameter $object of Bavix\Wallet\Services\WalletService::getWallet(). ( Ignorable by Annotation )

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

227
                ->getWallet(/** @scrutinizer ignore-type */ $this);
Loading history...
228
229 17
            $transfers = app(CommonService::class)->assemble([
230 17
                (new Bring())
231 17
                    ->setStatus($status)
232 17
                    ->setDeposit($deposit)
233 17
                    ->setWithdraw($withdraw)
234 17
                    ->setFrom($from)
235 17
                    ->setTo($wallet)
236
            ]);
237
238 17
            return current($transfers);
239 17
        });
240
    }
241
242
    /**
243
     * the transfer table is used to confirm the payment
244
     * this method receives all transfers
245
     *
246
     * @return MorphMany
247
     */
248 11
    public function transfers(): MorphMany
249
    {
250 11
        return app(WalletService::class)
251 11
            ->getWallet($this)
0 ignored issues
show
Bug introduced by
$this of type Bavix\Wallet\Traits\HasWallet is incompatible with the type Bavix\Wallet\Interfaces\Wallet expected by parameter $object of Bavix\Wallet\Services\WalletService::getWallet(). ( Ignorable by Annotation )

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

251
            ->getWallet(/** @scrutinizer ignore-type */ $this)
Loading history...
252 11
            ->morphMany(config('wallet.transfer.model'), 'from');
253
    }
254
255
    /**
256
     * Get default Wallet
257
     * this method is used for Eager Loading
258
     *
259
     * @return MorphOne|WalletModel
260
     */
261 40
    public function wallet(): MorphOne
262
    {
263 40
        return ($this instanceof WalletModel ? $this->holder : $this)
264 40
            ->morphOne(config('wallet.wallet.model'), 'holder')
0 ignored issues
show
Bug introduced by
It seems like morphOne() 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

264
            ->/** @scrutinizer ignore-call */ morphOne(config('wallet.wallet.model'), 'holder')
Loading history...
265 40
            ->where('slug', config('wallet.wallet.default.slug'))
266 40
            ->withDefault([
267 40
                'name' => config('wallet.wallet.default.name'),
268 40
                'slug' => config('wallet.wallet.default.slug'),
269 40
                'balance' => 0,
270
            ]);
271
    }
272
273
}
274