Completed
Pull Request — master (#40)
by Бабичев
14:09 queued 08:29
created

HasWallet::checkAmount()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
ccs 3
cts 3
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\Model;
17
use Illuminate\Database\Eloquent\Relations\MorphMany;
18
use Illuminate\Database\Eloquent\Relations\MorphOne;
19
use Illuminate\Support\Collection;
20
use Illuminate\Support\Facades\DB;
21
use Ramsey\Uuid\Uuid;
22
23
/**
24
 * Trait HasWallet
25
 *
26
 * @package Bavix\Wallet\Traits
27
 *
28
 * @property-read WalletModel $wallet
29
 * @property-read Collection|WalletModel[] $wallets
30
 * @property-read int $balance
31
 */
32
trait HasWallet
33
{
34
35
    /**
36
     * The input means in the system
37
     *
38
     * @param int $amount
39
     * @param array|null $meta
40
     * @param bool $confirmed
41
     *
42
     * @return Transaction
43
     */
44 31
    public function deposit(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
45
    {
46
        /**
47
         * @var WalletService $walletService
48
         */
49 31
        $walletService = \app(WalletService::class);
50 31
        $walletService->checkAmount($amount);
51
52
        /**
53
         * @var WalletModel $wallet
54
         */
55 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

55
        $wallet = $walletService->getWallet(/** @scrutinizer ignore-type */ $this);
Loading history...
56
57 28
        $transactions = \app(CommonService::class)->enforce($wallet, [
58 28
            (new Operation())
59 28
                ->setType(Transaction::TYPE_DEPOSIT)
60 28
                ->setConfirmed($confirmed)
61 28
                ->setAmount($amount)
62 28
                ->setMeta($meta)
63
        ]);
64
65 28
        return \current($transactions);
66
    }
67
68
    /**
69
     * Magic laravel framework method, makes it
70
     *  possible to call property balance
71
     *
72
     * Example:
73
     *  $user1 = User::first()->load('wallet');
74
     *  $user2 = User::first()->load('wallet');
75
     *
76
     * Without static:
77
     *  var_dump($user1->balance, $user2->balance); // 100 100
78
     *  $user1->deposit(100);
79
     *  $user2->deposit(100);
80
     *  var_dump($user1->balance, $user2->balance); // 200 200
81
     *
82
     * With static:
83
     *  var_dump($user1->balance, $user2->balance); // 100 100
84
     *  $user1->deposit(100);
85
     *  var_dump($user1->balance); // 200
86
     *  $user2->deposit(100);
87
     *  var_dump($user2->balance); // 300
88
     *
89
     * @return int
90
     * @throws
91
     */
92 38
    public function getBalanceAttribute(): int
93
    {
94 38
        if ($this instanceof WalletModel) {
95 38
            $this->exists or $this->save();
96 38
            $proxy = \app(ProxyService::class);
97 38
            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

97
            if (!$proxy->has(/** @scrutinizer ignore-type */ $this->getKey())) {
Loading history...
98 38
                $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

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

115
            ->/** @scrutinizer ignore-call */ morphMany(config('wallet.transaction.model'), 'payable');
Loading history...
116
    }
117
118
    /**
119
     * This method ignores errors that occur when transferring funds
120
     *
121
     * @param Wallet $wallet
122
     * @param int $amount
123
     * @param array|null $meta
124
     * @param string $status
125
     * @return null|Transfer
126
     */
127 3
    public function safeTransfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): ?Transfer
128
    {
129
        try {
130 3
            return $this->transfer($wallet, $amount, $meta, $status);
131 3
        } catch (\Throwable $throwable) {
132 3
            return null;
133
        }
134
    }
135
136
    /**
137
     * A method that transfers funds from host to host
138
     *
139
     * @param Wallet $wallet
140
     * @param int $amount
141
     * @param array|null $meta
142
     * @param string $status
143
     * @return Transfer
144
     * @throws
145
     */
146 15
    public function transfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer
147
    {
148
        return DB::transaction(function () use ($amount, $wallet, $meta, $status) {
149 15
            $fee = \app(WalletService::class)
150 15
                ->fee($wallet, $amount);
151
152 15
            $withdraw = $this->withdraw($amount + $fee, $meta);
153 15
            $deposit = $wallet->deposit($amount, $meta);
154 15
            return $this->assemble($wallet, $withdraw, $deposit, $status);
155 15
        });
156
    }
157
158
    /**
159
     * Withdrawals from the system
160
     *
161
     * @param int $amount
162
     * @param array|null $meta
163
     * @param bool $confirmed
164
     *
165
     * @return Transaction
166
     */
167 33
    public function withdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
168
    {
169 33
        if ($amount && !$this->balance) {
170 14
            throw new BalanceIsEmpty(trans('wallet::errors.wallet_empty'));
0 ignored issues
show
Bug introduced by
It seems like trans('wallet::errors.wallet_empty') can also be of type array; however, parameter $message of Bavix\Wallet\Exceptions\...eIsEmpty::__construct() 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

170
            throw new BalanceIsEmpty(/** @scrutinizer ignore-type */ trans('wallet::errors.wallet_empty'));
Loading history...
171
        }
172
173 27
        if (!$this->canWithdraw($amount)) {
174 1
            throw new InsufficientFunds(trans('wallet::errors.insufficient_funds'));
0 ignored issues
show
Bug introduced by
It seems like trans('wallet::errors.insufficient_funds') can also be of type array; however, parameter $message of Bavix\Wallet\Exceptions\...entFunds::__construct() 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

174
            throw new InsufficientFunds(/** @scrutinizer ignore-type */ trans('wallet::errors.insufficient_funds'));
Loading history...
175
        }
176
177 26
        return $this->forceWithdraw($amount, $meta, $confirmed);
178
    }
179
180
    /**
181
     * Checks if you can withdraw funds
182
     *
183
     * @param int $amount
184
     * @return bool
185
     */
186 27
    public function canWithdraw(int $amount): bool
187
    {
188 27
        return $this->balance >= $amount;
189
    }
190
191
    /**
192
     * Forced to withdraw funds from system
193
     *
194
     * @param int $amount
195
     * @param array|null $meta
196
     * @param bool $confirmed
197
     *
198
     * @return Transaction
199
     */
200 27
    public function forceWithdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
201
    {
202
        /**
203
         * @var WalletService $walletService
204
         */
205 27
        $walletService = \app(WalletService::class);
206 27
        $walletService->checkAmount($amount);
207
208
        /**
209
         * @var WalletModel $wallet
210
         */
211 27
        $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

211
        $wallet = $walletService->getWallet(/** @scrutinizer ignore-type */ $this);
Loading history...
212
213 27
        $transactions = \app(CommonService::class)->enforce($wallet, [
214 27
            (new Operation())
215 27
                ->setType(Transaction::TYPE_WITHDRAW)
216 27
                ->setConfirmed($confirmed)
217 27
                ->setAmount(-$amount)
218 27
                ->setMeta($meta)
219
        ]);
220
221 27
        return \current($transactions);
222
    }
223
224
    /**
225
     * this method adds a new transfer to the transfer table
226
     *
227
     * @param Wallet $wallet
228
     * @param Transaction $withdraw
229
     * @param Transaction $deposit
230
     * @param string $status
231
     * @return Transfer
232
     * @throws
233
     */
234 17
    protected function assemble(Wallet $wallet, Transaction $withdraw, Transaction $deposit, string $status = Transfer::STATUS_PAID): Transfer
235
    {
236 17
        $from = ($this instanceof WalletModel ? $this : $this->wallet);
237
238 17
        $transfers = \app(CommonService::class)->assemble([
239 17
            (new Bring())
240 17
                ->setStatus($status)
241 17
                ->setDeposit($deposit)
242 17
                ->setWithdraw($withdraw)
243 17
                ->setFrom($from)
244 17
                ->setTo($wallet)
245
        ]);
246
247 17
        return \current($transfers);
248
    }
249
250
    /**
251
     * the forced transfer is needed when the user does not have the money and we drive it.
252
     * Sometimes you do. Depends on business logic.
253
     *
254
     * @param Wallet $wallet
255
     * @param int $amount
256
     * @param array|null $meta
257
     * @param string $status
258
     * @return Transfer
259
     */
260 6
    public function forceTransfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer
261
    {
262
        return DB::transaction(function () use ($amount, $wallet, $meta, $status) {
263 6
            $fee = \app(WalletService::class)
264 6
                ->fee($wallet, $amount);
265
266 6
            $withdraw = $this->forceWithdraw($amount + $fee, $meta);
267 6
            $deposit = $wallet->deposit($amount, $meta);
268 6
            return $this->assemble($wallet, $withdraw, $deposit, $status);
269 6
        });
270
    }
271
272
    /**
273
     * the transfer table is used to confirm the payment
274
     * this method receives all transfers
275
     *
276
     * @return MorphMany
277
     */
278 10
    public function transfers(): MorphMany
279
    {
280 10
        return \app(WalletService::class)
281 10
            ->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

281
            ->getWallet(/** @scrutinizer ignore-type */ $this)
Loading history...
282 10
            ->morphMany(config('wallet.transfer.model'), 'from');
283
    }
284
285
    /**
286
     * Get default Wallet
287
     * this method is used for Eager Loading
288
     *
289
     * @return MorphOne|WalletModel
290
     */
291 38
    public function wallet(): MorphOne
292
    {
293 38
        return ($this instanceof WalletModel ? $this->holder : $this)
294 38
            ->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

294
            ->/** @scrutinizer ignore-call */ morphOne(config('wallet.wallet.model'), 'holder')
Loading history...
295 38
            ->where('slug', config('wallet.wallet.default.slug'))
296 38
            ->withDefault([
297 38
                'name' => config('wallet.wallet.default.name'),
298 38
                'slug' => config('wallet.wallet.default.slug'),
299 38
                'balance' => 0,
300
            ]);
301
    }
302
303
}
304