MoeCasts /
laravel-wallet
| 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\ExchangeInvalid; |
||||
| 12 | use Moecasts\Laravel\Wallet\Exceptions\InsufficientFunds; |
||||
| 13 | use Moecasts\Laravel\Wallet\Interfaces\Assemblable; |
||||
| 14 | use Moecasts\Laravel\Wallet\Interfaces\Exchangeable; |
||||
| 15 | use Moecasts\Laravel\Wallet\Interfaces\Transferable; |
||||
| 16 | use Moecasts\Laravel\Wallet\Models\Transaction; |
||||
| 17 | use Moecasts\Laravel\Wallet\Tax; |
||||
| 18 | use Moecasts\Laravel\Wallet\Traits\CanPay; |
||||
| 19 | use Moecasts\Laravel\Wallet\WalletProxy; |
||||
| 20 | use Ramsey\Uuid\Uuid; |
||||
| 21 | |||||
| 22 | class Wallet extends Model |
||||
| 23 | { |
||||
| 24 | use CanPay; |
||||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||||
| 25 | |||||
| 26 | protected $fillable = [ |
||||
| 27 | 'holder_type', |
||||
| 28 | 'holder_id', |
||||
| 29 | 'currency', |
||||
| 30 | 'balance' |
||||
| 31 | ]; |
||||
| 32 | |||||
| 33 | 24 | public function holder(): MorphTo |
|||
| 34 | { |
||||
| 35 | 24 | return $this->morphTo(); |
|||
| 36 | } |
||||
| 37 | |||||
| 38 | 23 | public function transactions(): HasMany |
|||
| 39 | { |
||||
| 40 | 23 | return $this->hasMany(Transaction::class); |
|||
| 41 | } |
||||
| 42 | |||||
| 43 | 4 | public function holderTransfers(): MorphMany |
|||
| 44 | { |
||||
| 45 | 4 | return $this->holder->transfers(); |
|||
| 46 | } |
||||
| 47 | |||||
| 48 | 3 | public function transfers(): HasMany |
|||
| 49 | { |
||||
| 50 | 3 | return $this->hasMany(Transfer::class, 'from_wallet_id'); |
|||
| 51 | } |
||||
| 52 | |||||
| 53 | 22 | public function deposit(float $amount, ?array $meta = null, bool $confirmed = true): Transaction |
|||
| 54 | { |
||||
| 55 | 22 | $this->checkAmount($amount); |
|||
|
0 ignored issues
–
show
$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
Loading history...
|
|||||
| 56 | |||||
| 57 | 22 | $amount = (int) ($amount * $this->coefficient($this->currency)); |
|||
| 58 | |||||
| 59 | 22 | return $this->change(Transaction::TYPE_DEPOSIT, $amount, $meta, $confirmed); |
|||
| 60 | } |
||||
| 61 | |||||
| 62 | 23 | private function checkAmount(int $amount): void |
|||
| 63 | { |
||||
| 64 | 23 | if ($amount < 0) { |
|||
| 65 | 1 | throw new AmountInvalid(trans('wallet::errors.price_positive')); |
|||
|
0 ignored issues
–
show
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
Loading history...
|
|||||
| 66 | } |
||||
| 67 | 23 | } |
|||
| 68 | |||||
| 69 | 23 | protected function change(string $type, int $amount, ?array $meta, bool $confirmed): Transaction |
|||
| 70 | { |
||||
| 71 | return DB::transaction(function () use ($type, $amount, $meta, $confirmed) { |
||||
| 72 | 23 | if ($confirmed) { |
|||
| 73 | 23 | $this->addBalance($amount); |
|||
| 74 | } |
||||
| 75 | |||||
| 76 | 23 | return $this->transactions()->create([ |
|||
| 77 | 23 | 'type' => $type, |
|||
| 78 | 23 | 'holder_type' => $this->holder->getMorphClass(), |
|||
| 79 | 23 | 'holder_id' => $this->holder->getKey(), |
|||
| 80 | 23 | 'wallet_id' => $this->getKey(), |
|||
| 81 | 23 | 'uuid' => Uuid::uuid4()->toString(), |
|||
| 82 | 23 | 'confirmed' => $confirmed, |
|||
| 83 | 23 | 'amount' => $amount, |
|||
| 84 | 23 | 'meta' => $meta, |
|||
| 85 | ]); |
||||
| 86 | 23 | }); |
|||
| 87 | } |
||||
| 88 | |||||
| 89 | 23 | protected function addBalance(float $amount): bool |
|||
| 90 | { |
||||
| 91 | 23 | $newBalance = $this->attributes['balance'] + $amount; |
|||
| 92 | 23 | $this->balance = $newBalance; |
|||
| 93 | 23 | $finalBalance = $newBalance / $this->coefficient($this->attributes['currency']); |
|||
| 94 | |||||
| 95 | return |
||||
| 96 | // update database wallet |
||||
| 97 | 23 | $this->save() && |
|||
| 98 | |||||
| 99 | // update static wallet |
||||
| 100 | 23 | WalletProxy::set($this->getKey(), $finalBalance); |
|||
| 101 | } |
||||
| 102 | |||||
| 103 | 24 | public function getBalanceAttribute(): float |
|||
| 104 | { |
||||
| 105 | 24 | $this->exists or $this->save(); |
|||
| 106 | |||||
| 107 | 24 | if (! WalletProxy::has($this->getKey())) { |
|||
| 108 | 5 | $balance = $this->attributes['balance'] / $this->coefficient($this->attributes['currency']); |
|||
| 109 | 5 | WalletProxy::set($this->getKey(), (float) ($balance ?? 0)); |
|||
| 110 | } |
||||
| 111 | |||||
| 112 | 24 | return WalletProxy::get($this->getKey()); |
|||
| 113 | } |
||||
| 114 | |||||
| 115 | 1 | public function safeTransfer(Transferable $transferable, float $amount, ?array $meta = null, string $action = Transfer::ACTION_TRANSFER): ?Transfer |
|||
| 116 | { |
||||
| 117 | try { |
||||
| 118 | 1 | return $this->transfer($transferable, $amount, $meta, $action); |
|||
| 119 | 1 | } catch (\Throwable $throwable) { |
|||
| 120 | 1 | return null; |
|||
| 121 | } |
||||
| 122 | } |
||||
| 123 | |||||
| 124 | 9 | public function transfer(Transferable $transferable, float $amount, ?array $meta = null, string $action = Transfer::ACTION_TRANSFER): Transfer |
|||
| 125 | { |
||||
| 126 | 9 | $wallet = $transferable->getReceiptWallet($this->currency); |
|||
| 127 | |||||
| 128 | return DB::transaction(function () use ($transferable, $amount, $wallet, $meta, $action) { |
||||
| 129 | 9 | $fee = Tax::fee($transferable, $wallet, $amount); |
|||
| 130 | 9 | $withdraw = $this->withdraw($amount + $fee, $meta); |
|||
| 131 | 7 | $deposit = $wallet->deposit($amount, $meta); |
|||
| 132 | 7 | return $this->assemble($transferable, $wallet, $withdraw, $deposit, $action); |
|||
| 133 | 9 | }); |
|||
| 134 | } |
||||
| 135 | |||||
| 136 | 12 | public function withdraw(float $amount, ?array $meta = null, bool $confirmed = true): Transaction |
|||
| 137 | { |
||||
| 138 | 12 | if (! $this->canWithdraw($amount)) { |
|||
| 139 | 5 | throw new InsufficientFunds(trans('wallet::errors.insufficient_funds')); |
|||
|
0 ignored issues
–
show
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
Loading history...
|
|||||
| 140 | } |
||||
| 141 | |||||
| 142 | 10 | return $this->forceWithdraw($amount, $meta, $confirmed); |
|||
| 143 | } |
||||
| 144 | |||||
| 145 | 12 | public function canWithdraw($amount): bool |
|||
| 146 | { |
||||
| 147 | 12 | return $this->balance >= $amount; |
|||
| 148 | } |
||||
| 149 | |||||
| 150 | 14 | public function forceWithdraw(float $amount, ?array $meta = null, bool $confirmed = true): Transaction |
|||
| 151 | { |
||||
| 152 | 14 | $amount = (int) ($amount * $this->coefficient($this->currency)); |
|||
| 153 | |||||
| 154 | 14 | $this->checkAmount($amount); |
|||
| 155 | |||||
| 156 | 14 | return $this->change(Transaction::TYPE_WITHDRAW, -$amount, $meta, $confirmed); |
|||
| 157 | } |
||||
| 158 | |||||
| 159 | 11 | protected function assemble(Assemblable $transferable, Wallet $wallet, Transaction $withdraw, Transaction $deposit, string $action = Transfer::ACTION_PAID): Transfer |
|||
| 160 | { |
||||
| 161 | 11 | return \app('moecasts.wallet::transfer')->create([ |
|||
|
0 ignored issues
–
show
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
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...
|
|||||
| 162 | 11 | 'action' => $action, |
|||
| 163 | 11 | 'deposit_id' => $deposit->getKey(), |
|||
| 164 | 11 | 'withdraw_id' => $withdraw->getKey(), |
|||
| 165 | 11 | 'from_type' => $this->holder->getMorphClass(), |
|||
| 166 | 11 | 'from_id' => $this->holder->getKey(), |
|||
| 167 | 11 | 'from_wallet_id' => $this->getKey(), |
|||
| 168 | 11 | 'to_type' => $transferable->getMorphClass(), |
|||
|
0 ignored issues
–
show
The method
getMorphClass() does not exist on Moecasts\Laravel\Wallet\Interfaces\Assemblable. It seems like you code against a sub-type of said class. However, the method does not exist in Moecasts\Laravel\Wallet\Interfaces\Transferable or Moecasts\Laravel\Wallet\Interfaces\Exchangeable or 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
Loading history...
|
|||||
| 169 | 11 | 'to_id' => $transferable->getKey(), |
|||
|
0 ignored issues
–
show
The method
getKey() does not exist on Moecasts\Laravel\Wallet\Interfaces\Assemblable. It seems like you code against a sub-type of said class. However, the method does not exist in Moecasts\Laravel\Wallet\Interfaces\Transferable or Moecasts\Laravel\Wallet\Interfaces\Exchangeable or 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
Loading history...
|
|||||
| 170 | 11 | 'to_wallet_id' => $wallet->getKey(), |
|||
| 171 | 11 | 'fee' => \abs($withdraw->amount) - \abs($deposit->amount), |
|||
| 172 | 11 | 'uuid' => Uuid::uuid4()->toString(), |
|||
| 173 | ]); |
||||
| 174 | } |
||||
| 175 | |||||
| 176 | 2 | public function forceTransfer(Transferable $transferable, float $amount, ?array $meta = null, string $action = Transfer::ACTION_TRANSFER): Transfer |
|||
| 177 | { |
||||
| 178 | 2 | $wallet = $transferable->getReceiptWallet($this->currency); |
|||
| 179 | |||||
| 180 | return DB::transaction(function () use ($transferable, $amount, $wallet, $meta, $action) { |
||||
| 181 | 2 | $fee = Tax::fee($transferable, $wallet, $amount); |
|||
| 182 | 2 | $withdraw = $this->forceWithdraw($amount + $fee, $meta); |
|||
| 183 | 2 | $deposit = $wallet->deposit($amount, $meta); |
|||
| 184 | 2 | return $this->assemble($transferable, $wallet, $withdraw, $deposit, $action); |
|||
| 185 | 2 | }); |
|||
| 186 | } |
||||
| 187 | |||||
| 188 | 4 | public function exchange(string $currency, float $amount, ?array $meta = null, $action = Transfer::ACTION_EXCHANGE): Transfer |
|||
| 189 | { |
||||
| 190 | 4 | $wallet = $this->holder->getWallet($currency); |
|||
| 191 | |||||
| 192 | 4 | $exchangeRate = config('wallet.exchange.' . $this->currency . '.' . $currency); |
|||
| 193 | |||||
| 194 | 4 | if (! $exchangeRate || |
|||
| 195 | 4 | ! $this->holder instanceof Exchangeable) { |
|||
| 196 | 3 | throw new ExchangeInvalid(trans('wallet::errors.exchange_unsupported')); |
|||
|
0 ignored issues
–
show
It seems like
trans('wallet::errors.exchange_unsupported') can also be of type array; however, parameter $message of Moecasts\Laravel\Wallet\...eInvalid::__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
Loading history...
|
|||||
| 197 | } |
||||
| 198 | |||||
| 199 | 1 | $exchangedAmount = $amount * $exchangeRate; |
|||
| 200 | |||||
| 201 | return DB::transaction(function () use ($wallet, $amount, $exchangedAmount, $meta, $action) { |
||||
| 202 | 1 | $fee = Tax::fee($this->holder, $wallet, $amount); |
|||
| 203 | 1 | $withdraw = $this->withdraw($amount + $fee, $meta); |
|||
| 204 | 1 | $deposit = $wallet->deposit($exchangedAmount, $meta); |
|||
| 205 | 1 | return $this->assemble($this->holder, $wallet, $withdraw, $deposit, $action); |
|||
| 206 | 1 | }); |
|||
| 207 | } |
||||
| 208 | |||||
| 209 | 1 | public function safeExchange(string $currency, float $amount, ?array $meta = null, $action = Transfer::ACTION_EXCHANGE): ?Transfer |
|||
| 210 | { |
||||
| 211 | try { |
||||
| 212 | 1 | return $this->exchange($currency, $amount, $meta, $action); |
|||
| 213 | 1 | } catch (\Throwable $throwable) { |
|||
| 214 | 1 | return null; |
|||
| 215 | } |
||||
| 216 | } |
||||
| 217 | |||||
| 218 | 3 | public function forceExchange(string $currency, float $amount, ?array $meta = null, $action = Transfer::ACTION_EXCHANGE): Transfer |
|||
| 219 | { |
||||
| 220 | 3 | $wallet = $this->holder->getWallet($currency); |
|||
| 221 | |||||
| 222 | 3 | $exchangeRate = config('wallet.exchange.' . $this->currency . '.' . $currency); |
|||
| 223 | |||||
| 224 | 3 | if (! $exchangeRate || |
|||
| 225 | 3 | ! $this->holder instanceof Exchangeable) { |
|||
| 226 | 2 | throw new ExchangeInvalid(trans('wallet::errors.exchange_unsupported')); |
|||
|
0 ignored issues
–
show
It seems like
trans('wallet::errors.exchange_unsupported') can also be of type array; however, parameter $message of Moecasts\Laravel\Wallet\...eInvalid::__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
Loading history...
|
|||||
| 227 | } |
||||
| 228 | |||||
| 229 | 1 | $exchangedAmount = $amount * $exchangeRate; |
|||
| 230 | |||||
| 231 | return DB::transaction(function () use ($wallet, $amount, $exchangedAmount, $meta, $action) { |
||||
| 232 | 1 | $fee = Tax::fee($this->holder, $wallet, $amount); |
|||
| 233 | 1 | $withdraw = $this->forceWithdraw($amount + $fee, $meta); |
|||
| 234 | 1 | $deposit = $wallet->deposit($exchangedAmount, $meta); |
|||
| 235 | 1 | return $this->assemble($this->holder, $wallet, $withdraw, $deposit, $action); |
|||
| 236 | 1 | }); |
|||
| 237 | } |
||||
| 238 | |||||
| 239 | 3 | public function refreshBalance(): bool |
|||
| 240 | { |
||||
| 241 | 3 | $balance = $this->getAvailableBalance(); |
|||
| 242 | |||||
| 243 | 3 | $this->attributes['balance'] = $balance; |
|||
| 244 | |||||
| 245 | 3 | WalletProxy::set($this->getKey(), $balance); |
|||
| 246 | |||||
| 247 | 3 | return $this->save(); |
|||
| 248 | } |
||||
| 249 | |||||
| 250 | 3 | public function getAvailableBalance(): int |
|||
| 251 | { |
||||
| 252 | 3 | return $this->transactions() |
|||
| 253 | 3 | ->where('wallet_id', $this->getKey()) |
|||
| 254 | 3 | ->where('confirmed', true) |
|||
| 255 | 3 | ->sum('amount'); |
|||
| 256 | } |
||||
| 257 | |||||
| 258 | 24 | public function coefficient(string $currency = ''): float |
|||
| 259 | { |
||||
| 260 | 24 | return config('wallet.coefficient.' . $currency , 100.); |
|||
| 261 | } |
||||
| 262 | } |
||||
| 263 |