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

HasWallet::change()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

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

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

92
            if (!$proxy->has(/** @scrutinizer ignore-type */ $this->getKey())) {
Loading history...
93 38
                $proxy->set($this->getKey(), (int)($this->attributes['balance'] ?? 0));
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::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

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

110
            ->/** @scrutinizer ignore-call */ morphMany(config('wallet.transaction.model'), 'payable');
Loading history...
111
    }
112
113
    /**
114
     * This method ignores errors that occur when transferring funds
115
     *
116
     * @param Wallet $wallet
117
     * @param int $amount
118
     * @param array|null $meta
119
     * @param string $status
120
     * @return null|Transfer
121
     */
122 3
    public function safeTransfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): ?Transfer
123
    {
124
        try {
125 3
            return $this->transfer($wallet, $amount, $meta, $status);
126 3
        } catch (\Throwable $throwable) {
127 3
            return null;
128
        }
129
    }
130
131
    /**
132
     * A method that transfers funds from host to host
133
     *
134
     * @param Wallet $wallet
135
     * @param int $amount
136
     * @param array|null $meta
137
     * @param string $status
138
     * @return Transfer
139
     * @throws
140
     */
141 15
    public function transfer(Wallet $wallet, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer
142
    {
143
        return DB::transaction(function () use ($amount, $wallet, $meta, $status) {
144 15
            $fee = \app(WalletService::class)
145 15
                ->fee($wallet, $amount);
146
147 15
            $withdraw = $this->withdraw($amount + $fee, $meta);
148 15
            $deposit = $wallet->deposit($amount, $meta);
149 15
            return $this->assemble($wallet, $withdraw, $deposit, $status);
150 15
        });
151
    }
152
153
    /**
154
     * Withdrawals from the system
155
     *
156
     * @param int $amount
157
     * @param array|null $meta
158
     * @param bool $confirmed
159
     *
160
     * @return Transaction
161
     */
162 33
    public function withdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction
163
    {
164 33
        if ($amount && !$this->balance) {
165 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

165
            throw new BalanceIsEmpty(/** @scrutinizer ignore-type */ trans('wallet::errors.wallet_empty'));
Loading history...
166
        }
167
168 27
        if (!$this->canWithdraw($amount)) {
169 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

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

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

273
            ->getWallet(/** @scrutinizer ignore-type */ $this)
Loading history...
274 10
            ->morphMany(config('wallet.transfer.model'), 'from');
275
    }
276
277
    /**
278
     * Get default Wallet
279
     * this method is used for Eager Loading
280
     *
281
     * @return MorphOne|WalletModel
282
     */
283 38
    public function wallet(): MorphOne
284
    {
285 38
        return ($this instanceof WalletModel ? $this->holder : $this)
286 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

286
            ->/** @scrutinizer ignore-call */ morphOne(config('wallet.wallet.model'), 'holder')
Loading history...
287 38
            ->where('slug', config('wallet.wallet.default.slug'))
288 38
            ->withDefault([
289 38
                'name' => config('wallet.wallet.default.name'),
290 38
                'slug' => config('wallet.wallet.default.slug'),
291 38
                'balance' => 0,
292
            ]);
293
    }
294
295
}
296