Passed
Push — master ( 38cc9b...2ce834 )
by Moecasts
03:22
created

Wallet::assemble()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 12
nc 1
nop 5
dl 0
loc 14
ccs 13
cts 13
cp 1
crap 1
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Moecasts\Laravel\Wallet\Models;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Relations\HasMany;
7
use Illuminate\Database\Eloquent\Relations\MorphMany;
8
use Illuminate\Database\Eloquent\Relations\MorphTo;
9
use Illuminate\Support\Facades\DB;
10
use Moecasts\Laravel\Wallet\Exceptions\AmountInvalid;
11
use Moecasts\Laravel\Wallet\Exceptions\InsufficientFunds;
12
use Moecasts\Laravel\Wallet\Interfaces\Transferable;
13
use Moecasts\Laravel\Wallet\Models\Transaction;
14
use Moecasts\Laravel\Wallet\Tax;
15
use Moecasts\Laravel\Wallet\Traits\CanPay;
16
use Moecasts\Laravel\Wallet\WalletProxy;
17
use Ramsey\Uuid\Uuid;
18
19
class Wallet extends Model
20
{
21
    use CanPay;
0 ignored issues
show
introduced by
The trait Moecasts\Laravel\Wallet\Traits\CanPay requires some properties which are not provided by Moecasts\Laravel\Wallet\Models\Wallet: $withdraw, $deposit, $fromWallet, $toWallet
Loading history...
22
23
    protected $fillable = [
24
        'holder_type',
25
        'holder_id',
26
        'currency',
27
        'balance'
28
    ];
29
30 16
    public function holder(): MorphTo
31
    {
32 16
        return $this->morphTo();
33
    }
34
35 15
    public function transactions(): HasMany
36
    {
37 15
        return $this->hasMany(Transaction::class);
38
    }
39
40 4
    public function holderTransfers(): MorphMany
41
    {
42 4
        return $this->holder->transfers();
43
    }
44
45 2
    public function transfers(): HasMany
46
    {
47 2
        return $this->hasMany(Transfer::class, 'from_wallet_id');
48
    }
49
50 14
    public function deposit(float $amount, ?array $meta = null, bool $confirmed = true): Transaction
51
    {
52 14
        $this->checkAmount($amount);
0 ignored issues
show
Bug introduced by
$amount of type double is incompatible with the type integer expected by parameter $amount of Moecasts\Laravel\Wallet\...s\Wallet::checkAmount(). ( Ignorable by Annotation )

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

52
        $this->checkAmount(/** @scrutinizer ignore-type */ $amount);
Loading history...
53
54 14
        $amount = (int) ($amount * $this->coefficient($this->currency));
55
56 14
        return $this->change(Transaction::TYPE_DEPOSIT, $amount, $meta, $confirmed);
57
    }
58
59 15
    private function checkAmount(int $amount): void
60
    {
61 15
        if ($amount < 0) {
62 1
            throw new AmountInvalid(trans('wallet::errors.price_positive'));
0 ignored issues
show
Bug introduced by
It seems like trans('wallet::errors.price_positive') can also be of type array; however, parameter $message of Moecasts\Laravel\Wallet\...tInvalid::__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

62
            throw new AmountInvalid(/** @scrutinizer ignore-type */ trans('wallet::errors.price_positive'));
Loading history...
63
        }
64 15
    }
65
66 15
    protected function change(string $type, int $amount, ?array $meta, bool $confirmed): Transaction
67
    {
68
        return DB::transaction(function () use ($type, $amount, $meta, $confirmed) {
69 15
            if ($confirmed) {
70 15
                $this->addBalance($amount);
71
            }
72
73 15
            return $this->transactions()->create([
74 15
                'type' => $type,
75 15
                'holder_type' => $this->holder->getMorphClass(),
76 15
                'holder_id' => $this->holder->getKey(),
77 15
                'wallet_id' => $this->getKey(),
78 15
                'uuid' => Uuid::uuid4()->toString(),
79 15
                'confirmed' => $confirmed,
80 15
                'amount' => $amount,
81 15
                'meta' => $meta,
82
            ]);
83 15
        });
84
    }
85
86 15
    protected function addBalance(float $amount): bool
87
    {
88 15
        $newBalance = $this->attributes['balance'] + $amount;
89 15
        $this->balance = $newBalance;
90 15
        $finalBalance = $newBalance / $this->coefficient($this->attributes['currency']);
91
92
        return
93
            // update database wallet
94 15
            $this->save() &&
95
96
            // update static wallet
97 15
            WalletProxy::set($this->getKey(), $finalBalance);
98
    }
99
100 16
    public function getBalanceAttribute(): float
101
    {
102 16
        $this->exists or $this->save();
103
104 16
        if (! WalletProxy::has($this->getKey())) {
105 3
            $balance = $this->attributes['balance'] / $this->coefficient($this->attributes['currency']);
106 3
            WalletProxy::set($this->getKey(), (float) ($balance ?? 0));
107
        }
108
109 16
        return WalletProxy::get($this->getKey());
110
    }
111
112 1
    public function safeTransfer(Transferable $transferable, float $amount, ?array $meta = null, string $action = Transfer::ACTION_TRANSFER): ?Transfer
113
    {
114
        try {
115 1
            return $this->transfer($transferable, $amount, $meta, $action);
116 1
        } catch (\Throwable $throwable) {
117 1
            return null;
118
        }
119
    }
120
121 9
    public function transfer(Transferable $transferable, float $amount, ?array $meta = null, string $action = Transfer::ACTION_TRANSFER): Transfer
122
    {
123 9
        $wallet = $transferable->getReceiptWallet($this->currency);
124
125
        return DB::transaction(function () use ($transferable, $amount, $wallet, $meta, $action) {
126 9
            $fee = Tax::fee($transferable, $wallet, $amount);
127 9
            $withdraw = $this->withdraw($amount + $fee, $meta);
128 7
            $deposit = $wallet->deposit($amount, $meta);
129 7
            return $this->assemble($transferable, $wallet, $withdraw, $deposit, $action);
130 9
        });
131
    }
132
133 11
    public function withdraw(float $amount, ?array $meta = null, bool $confirmed = true): Transaction
134
    {
135 11
        if (! $this->canWithdraw($amount)) {
136 5
            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 Moecasts\Laravel\Wallet\...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

136
            throw new InsufficientFunds(/** @scrutinizer ignore-type */ trans('wallet::errors.insufficient_funds'));
Loading history...
137
        }
138
139 9
        return $this->forceWithdraw($amount, $meta, $confirmed);
140
    }
141
142 11
    public function canWithdraw($amount): bool
143
    {
144 11
        return $this->balance >= $amount;
145
    }
146
147 11
    public function forceWithdraw(float $amount, ?array $meta = null, bool $confirmed = true): Transaction
148
    {
149 11
        $amount = (int) ($amount * $this->coefficient($this->currency));
150
151 11
        $this->checkAmount($amount);
152
153 11
        return $this->change(Transaction::TYPE_WITHDRAW, -$amount, $meta, $confirmed);
154
    }
155
156 8
    protected function assemble(Transferable $transferable, Wallet $wallet, Transaction $withdraw, Transaction $deposit, string $action = Transfer::ACTION_PAID): Transfer
157
    {
158 8
        return \app('moecasts.wallet::transfer')->create([
0 ignored issues
show
Bug introduced by
The method create() does not exist on Illuminate\Contracts\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

158
        return \app('moecasts.wallet::transfer')->/** @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...
159 8
            'action' => $action,
160 8
            'deposit_id' => $deposit->getKey(),
161 8
            'withdraw_id' => $withdraw->getKey(),
162 8
            'from_type' => $this->holder->getMorphClass(),
163 8
            'from_id' => $this->holder->getKey(),
164 8
            'from_wallet_id' => $this->getKey(),
165 8
            'to_type' => $transferable->getMorphClass(),
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not exist on Moecasts\Laravel\Wallet\Interfaces\Transferable. It seems like you code against a sub-type of said class. However, the method does not exist in Moecasts\Laravel\Wallet\Interfaces\Product. Are you sure you never get one of those? ( Ignorable by Annotation )

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

165
            'to_type' => $transferable->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
166 8
            'to_id' => $transferable->getKey(),
0 ignored issues
show
Bug introduced by
The method getKey() does not exist on Moecasts\Laravel\Wallet\Interfaces\Transferable. It seems like you code against a sub-type of said class. However, the method does not exist in Moecasts\Laravel\Wallet\Interfaces\Product. Are you sure you never get one of those? ( Ignorable by Annotation )

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

166
            'to_id' => $transferable->/** @scrutinizer ignore-call */ getKey(),
Loading history...
167 8
            'to_wallet_id' => $wallet->getKey(),
168 8
            'fee' => \abs($withdraw->amount) - \abs($deposit->amount),
169 8
            'uuid' => Uuid::uuid4()->toString(),
170
        ]);
171
    }
172
173 1
    public function forceTransfer(Transferable $transferable, float $amount, ?array $meta = null, string $action = Transfer::ACTION_TRANSFER): Transfer
174
    {
175 1
        $wallet = $transferable->getReceiptWallet($this->currency);
176
177
        return DB::transaction(function () use ($transferable, $amount, $wallet, $meta, $action) {
178 1
            $fee = Tax::fee($transferable, $wallet, $amount);
179 1
            $withdraw = $this->forceWithdraw($amount + $fee, $meta);
180 1
            $deposit = $wallet->deposit($amount, $meta);
181 1
            return $this->assemble($transferable, $wallet, $withdraw, $deposit, $action);
182 1
        });
183
    }
184
185 3
    public function refreshBalance(): bool
186
    {
187 3
        $balance = $this->getAvailableBalance();
188
189 3
        $this->attributes['balance'] = $balance;
190
191 3
        WalletProxy::set($this->getKey(), $balance);
192
193 3
        return $this->save();
194
    }
195
196 3
    public function getAvailableBalance(): int
197
    {
198 3
        return $this->transactions()
199 3
            ->where('wallet_id', $this->getKey())
200 3
            ->where('confirmed', true)
201 3
            ->sum('amount');
202
    }
203
204 16
    public function coefficient(string $currency = ''): float
205
    {
206 16
        return config('wallet.coefficient.' . $currency , 100.);
207
    }
208
}
209