diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 1f2b87fa9..0207165d3 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -2,6 +2,7 @@ namespace PHPSTORM_META { + use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Rateable; use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Models\Transaction; @@ -13,7 +14,6 @@ use Bavix\Wallet\Objects\Operation; use Bavix\Wallet\Services\CommonService; use Bavix\Wallet\Services\ExchangeService; - use Bavix\Wallet\Services\ProxyService; use Bavix\Wallet\Services\WalletService; override(\app(0), map([ @@ -23,11 +23,11 @@ EmptyLock::class => EmptyLock::class, ExchangeService::class => ExchangeService::class, CommonService::class => CommonService::class, - ProxyService::class => ProxyService::class, WalletService::class => WalletService::class, Wallet::class => Wallet::class, Transfer::class => Transfer::class, Transaction::class => Transaction::class, + Mathable::class => Mathable::class, Rateable::class => Rateable::class, Storable::class => Storable::class, ])); diff --git a/README.md b/README.md index 3fa19c786..6f78f9bb3 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ laravel-wallet - Easy work with virtual wallet. ### Upgrade Guide +> Starting with version 5.x, support for Laravel 5 has been discontinued. +> Update laravel or use version 4.x. + To perform the migration, you will be [helped by the instruction](https://bavix.github.io/laravel-wallet/#/upgrade-guide). ### Extensions @@ -98,7 +101,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/changelog.md b/changelog.md index 9ee2820b9..bebce8e08 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- add support "Arbitrary Precision Mathematics" (`ext-bcmath`) #139 #146 +- add `Mathable` service (helps switch quickly from bcmath to php computing) + +### Changed +- add unit cases +- upgrade composer packages +- Now all casts are in the config, not in the model. If you use bcmath, then all values are reduced to a string. + +### Removed +- Strong typing (models, interfaces, etc.) +- all deprecated methods are removed +- `nesbot/carbon` is no longer needed for the library to work + ## [4.2.0] - 2020-03-08 ### Added @@ -142,7 +156,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated - class `Taxing`. -### Remove +### Removed - The ability to change the ratio `coefficient`. - Removed private and protected methods, the traits turned out to be more clean. @@ -453,7 +467,7 @@ The operation is now executed in the transaction and updates the new `refund` fi - Exceptions: AmountInvalid, BalanceIsEmpty. - Models: Transfer, Transaction. -[Unreleased]: https://github.com/bavix/laravel-wallet/compare/4.2.0...HEAD +[Unreleased]: https://github.com/bavix/laravel-wallet/compare/4.2.0...develop [4.2.0]: https://github.com/bavix/laravel-wallet/compare/4.1.2...4.2.0 [4.1.2]: https://github.com/bavix/laravel-wallet/compare/4.1.1...4.1.2 [4.1.1]: https://github.com/bavix/laravel-wallet/compare/4.1.0...4.1.1 diff --git a/composer.json b/composer.json index 8b9dcab66..2810e0e1d 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,6 @@ "php": "^7.2|^8.0", "ext-pdo": "*", "illuminate/database": "^6.0|^7.0", - "nesbot/carbon": "^2.0", "doctrine/dbal": "^2.8", "ramsey/uuid": "^3.0" }, @@ -40,6 +39,7 @@ "laravel/cashier": "^10.0" }, "suggest": { + "ext-bcmath": "Used for accurate calculations of large numbers", "bavix/laravel-wallet-swap": "Addition to the laravel-wallet library for quick setting of exchange rates", "bavix/laravel-wallet-vacuum": "Addition to the laravel-wallet library for quick fix race condition" }, @@ -54,6 +54,9 @@ } }, "extra": { + "branch-alias": { + "dev-develop": "5.0.x-dev" + }, "laravel": { "providers": [ "Bavix\\Wallet\\WalletServiceProvider" diff --git a/config/config.php b/config/config.php index 2554bd437..6800b02e4 100644 --- a/config/config.php +++ b/config/config.php @@ -6,7 +6,6 @@ use Bavix\Wallet\Objects\Operation; use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Services\CommonService; -use Bavix\Wallet\Services\ProxyService; use Bavix\Wallet\Services\WalletService; use Bavix\Wallet\Services\LockService; use Bavix\Wallet\Models\Transaction; @@ -14,8 +13,20 @@ use Bavix\Wallet\Models\Wallet; use Bavix\Wallet\Simple\Rate; use Bavix\Wallet\Simple\Store; +use Bavix\Wallet\Simple\BCMath; +use Bavix\Wallet\Simple\Math; + +$bcLoaded = extension_loaded('bcmath'); return [ + /** + * This parameter is necessary for more accurate calculations. + * PS, Arbitrary Precision Calculations + */ + 'math' => [ + 'scale' => 64, + ], + /** * The parameter is used for fast packet overload. * You do not need to search for the desired class by code, the library will do it itself. @@ -23,6 +34,9 @@ 'package' => [ 'rateable' => Rate::class, 'storable' => Store::class, + 'mathable' => $bcLoaded ? + BCMath::class : + Math::class, ], /** @@ -60,7 +74,6 @@ 'services' => [ 'exchange' => ExchangeService::class, 'common' => CommonService::class, - 'proxy' => ProxyService::class, 'wallet' => WalletService::class, 'lock' => LockService::class, ], @@ -78,7 +91,9 @@ 'transaction' => [ 'table' => 'transactions', 'model' => Transaction::class, - 'casts' => [], + 'casts' => [ + 'amount' => $bcLoaded ? 'string' : 'int', + ], ], /** @@ -87,7 +102,9 @@ 'transfer' => [ 'table' => 'transfers', 'model' => Transfer::class, - 'casts' => [], + 'casts' => [ + 'fee' => $bcLoaded ? 'string' : 'int', + ], ], /** @@ -96,7 +113,9 @@ 'wallet' => [ 'table' => 'wallets', 'model' => Wallet::class, - 'casts' => [], + 'casts' => [ + 'balance' => $bcLoaded ? 'string' : 'int', + ], 'default' => [ 'name' => 'Default Wallet', 'slug' => 'default', diff --git a/database/migrations_v1/2018_11_06_222923_create_transactions_table.php b/database/migrations_v1/2018_11_06_222923_create_transactions_table.php index 215753958..a047867e6 100644 --- a/database/migrations_v1/2018_11_06_222923_create_transactions_table.php +++ b/database/migrations_v1/2018_11_06_222923_create_transactions_table.php @@ -18,7 +18,7 @@ class CreateTransactionsTable extends Migration public function up(): void { Schema::create($this->table(), function (Blueprint $table) { - $table->increments('id'); + $table->bigIncrements('id'); $table->morphs('payable'); $table->enum('type', ['deposit', 'withdraw'])->index(); $table->bigInteger('amount'); diff --git a/database/migrations_v1/2018_11_07_192923_create_transfers_table.php b/database/migrations_v1/2018_11_07_192923_create_transfers_table.php index b88256f18..29a89fa94 100644 --- a/database/migrations_v1/2018_11_07_192923_create_transfers_table.php +++ b/database/migrations_v1/2018_11_07_192923_create_transfers_table.php @@ -15,11 +15,11 @@ class CreateTransfersTable extends Migration public function up(): void { Schema::create($this->table(), function (Blueprint $table) { - $table->increments('id'); + $table->bigIncrements('id'); $table->morphs('from'); $table->morphs('to'); - $table->unsignedInteger('deposit_id'); - $table->unsignedInteger('withdraw_id'); + $table->unsignedBigInteger('deposit_id'); + $table->unsignedBigInteger('withdraw_id'); $table->uuid('uuid')->unique(); $table->timestamps(); diff --git a/database/migrations_v2/2018_11_15_124230_create_wallets_table.php b/database/migrations_v2/2018_11_15_124230_create_wallets_table.php index c406732c4..5eb658bdc 100644 --- a/database/migrations_v2/2018_11_15_124230_create_wallets_table.php +++ b/database/migrations_v2/2018_11_15_124230_create_wallets_table.php @@ -2,7 +2,6 @@ use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Wallet; -use Carbon\Carbon; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; @@ -26,7 +25,7 @@ protected function table(): string public function up(): void { Schema::create($this->table(), function (Blueprint $table) { - $table->increments('id'); + $table->bigIncrements('id'); $table->morphs('holder'); $table->string('name'); $table->string('slug')->index(); @@ -48,8 +47,8 @@ public function up(): void ->selectRaw('? as name', [$default]) ->selectRaw('? as slug', [$slug]) ->selectRaw('sum(amount) as balance') - ->selectRaw('? as created_at', [Carbon::now()]) - ->selectRaw('? as updated_at', [Carbon::now()]) + ->selectRaw('? as created_at', [DB::raw('now()')]) + ->selectRaw('? as updated_at', [DB::raw('now()')]) ->groupBy('holder_type', 'holder_id') ->orderBy('holder_type'); diff --git a/database/migrations_v2/2018_11_19_164609_update_transactions_table.php b/database/migrations_v2/2018_11_19_164609_update_transactions_table.php index e56f2ed5a..ef55337a8 100644 --- a/database/migrations_v2/2018_11_19_164609_update_transactions_table.php +++ b/database/migrations_v2/2018_11_19_164609_update_transactions_table.php @@ -33,7 +33,7 @@ protected function walletTable(): string public function up(): void { Schema::table($this->table(), function (Blueprint $table) { - $table->unsignedInteger('wallet_id') + $table->unsignedBigInteger('wallet_id') ->nullable() ->after('payable_id'); diff --git a/docs/basic-usage.md b/docs/basic-usage.md index 70a39acab..0902a92e2 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -62,7 +62,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/cart.md b/docs/cart.md index 4367dcb0f..fca79cfbb 100644 --- a/docs/cart.md +++ b/docs/cart.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return round($this->price * 100); } diff --git a/docs/exchange.md b/docs/exchange.md index 9acaae7d0..15de3fbfe 100644 --- a/docs/exchange.md +++ b/docs/exchange.md @@ -42,7 +42,7 @@ class MyRateService extends \Bavix\Wallet\Simple\Rate ], ]; - protected function rate(Wallet $wallet): float + protected function rate(Wallet $wallet) { $from = app(WalletService::class)->getWallet($this->withCurrency); $to = app(WalletService::class)->getWallet($wallet); @@ -54,7 +54,7 @@ class MyRateService extends \Bavix\Wallet\Simple\Rate ); } - public function convertTo(Wallet $wallet): float + public function convertTo(Wallet $wallet) { return parent::convertTo($wallet) * $this->rate($wallet); } diff --git a/docs/gift.md b/docs/gift.md index ebeadbf2e..7fd1a561d 100644 --- a/docs/gift.md +++ b/docs/gift.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/pay-free.md b/docs/pay-free.md index 278db4460..3e41480ab 100644 --- a/docs/pay-free.md +++ b/docs/pay-free.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/payment.md b/docs/payment.md index c9b042dff..0bef9a8d9 100644 --- a/docs/payment.md +++ b/docs/payment.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/refund.md b/docs/refund.md index 8e809a1af..af709a744 100644 --- a/docs/refund.md +++ b/docs/refund.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/ru/basic-usage.md b/docs/ru/basic-usage.md index e59e8aefa..2d99f6fd6 100644 --- a/docs/ru/basic-usage.md +++ b/docs/ru/basic-usage.md @@ -65,7 +65,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/ru/cart.md b/docs/ru/cart.md index 2b8345561..4c2867d1a 100644 --- a/docs/ru/cart.md +++ b/docs/ru/cart.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return round($this->price * 100); } diff --git a/docs/ru/exchange.md b/docs/ru/exchange.md index cd2ae9efb..3c4e2e98c 100644 --- a/docs/ru/exchange.md +++ b/docs/ru/exchange.md @@ -41,7 +41,7 @@ class MyRateService extends \Bavix\Wallet\Simple\Rate ], ]; - protected function rate(Wallet $wallet): float + protected function rate(Wallet $wallet) { $from = app(WalletService::class)->getWallet($this->withCurrency); $to = app(WalletService::class)->getWallet($wallet); @@ -53,7 +53,7 @@ class MyRateService extends \Bavix\Wallet\Simple\Rate ); } - public function convertTo(Wallet $wallet): float + public function convertTo(Wallet $wallet) { return parent::convertTo($wallet) * $this->rate($wallet); } diff --git a/docs/ru/gift.md b/docs/ru/gift.md index e65e82c22..13466149f 100644 --- a/docs/ru/gift.md +++ b/docs/ru/gift.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/ru/pay-free.md b/docs/ru/pay-free.md index 6c621090a..c419e6f46 100644 --- a/docs/ru/pay-free.md +++ b/docs/ru/pay-free.md @@ -43,7 +43,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/ru/payment.md b/docs/ru/payment.md index 3d2635b15..e364d99f5 100644 --- a/docs/ru/payment.md +++ b/docs/ru/payment.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/ru/refund.md b/docs/ru/refund.md index d1e01e5ea..a37f6c8ab 100644 --- a/docs/ru/refund.md +++ b/docs/ru/refund.md @@ -36,7 +36,7 @@ class Item extends Model implements Product return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } diff --git a/docs/ru/taxing.md b/docs/ru/taxing.md index 930f0f25e..1f1c2c9c5 100644 --- a/docs/ru/taxing.md +++ b/docs/ru/taxing.md @@ -37,7 +37,7 @@ class Item extends Model implements Product, Taxable return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } @@ -55,7 +55,7 @@ class Item extends Model implements Product, Taxable return (string)$this->getKey(); } - public function getFeePercent() : float + public function getFeePercent() { return 0.03; // 3% } @@ -109,7 +109,7 @@ class Item extends Model implements Product, MinimalTaxable return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } @@ -127,12 +127,12 @@ class Item extends Model implements Product, MinimalTaxable return (string)$this->getKey(); } - public function getFeePercent() : float + public function getFeePercent() { return 0.03; // 3% } - public function getMinimalFee() : int + public function getMinimalFee() { return 5; // 3%, minimum int(5) } diff --git a/docs/ru/upgrade-guide.md b/docs/ru/upgrade-guide.md index a2c50731e..478133377 100644 --- a/docs/ru/upgrade-guide.md +++ b/docs/ru/upgrade-guide.md @@ -91,3 +91,44 @@ class Item extends Model implements Product return $this->price; } ``` + +## 4.0.x → 5.0.x + +> Обновляясь с версии 4.x до 5.x Вы теряете строгую типизацию. +> Данная крайность необходима для вычислений с произвольной точностью. + +В товарах: + +Ваш код в версии 4.x: +```php + public function getAmountProduct(Customer $customer): int { ... } + + public function getFeePercent(): float { ... } + + public function getMinimalFee(): int { ... } +``` + +Ваш код в версии 5.x: +```php + public function getAmountProduct(Customer $customer) { ... } + + public function getFeePercent() { ... } + + public function getMinimalFee() { ... } +``` + +В сервисе обработки курса валют: + +Ваш код в версии 4.x: +```php + protected function rate(Wallet $wallet): float { ... } + + public function convertTo(Wallet $wallet): float { ... } +``` + +Ваш код в версии 5.x: +```php + protected function rate(Wallet $wallet) { ... } + + public function convertTo(Wallet $wallet) { ... } +``` diff --git a/docs/taxing.md b/docs/taxing.md index 79cccd15c..f47b1d353 100644 --- a/docs/taxing.md +++ b/docs/taxing.md @@ -37,7 +37,7 @@ class Item extends Model implements Product, Taxable return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } @@ -55,7 +55,7 @@ class Item extends Model implements Product, Taxable return (string)$this->getKey(); } - public function getFeePercent() : float + public function getFeePercent() { return 0.03; // 3% } @@ -108,7 +108,7 @@ class Item extends Model implements Product, MinimalTaxable return true; } - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { return 100; } @@ -126,12 +126,12 @@ class Item extends Model implements Product, MinimalTaxable return (string)$this->getKey(); } - public function getFeePercent() : float + public function getFeePercent() { return 0.03; // 3% } - public function getMinimalFee() : int + public function getMinimalFee() { return 5; // 3%, minimum int(5) } diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index d0ebea962..60bd53255 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -91,3 +91,44 @@ Your code on 4.x: return $this->price; } ``` + +## 4.0.x → 5.0.x + +> By updating the library from 4.x to 5.x you lose strong typing. +> This solution was necessary to support APM (Arbitrary Precision Mathematics). + +In your goods: + +Your code on 4.x: +```php + public function getAmountProduct(Customer $customer): int { ... } + + public function getFeePercent(): float { ... } + + public function getMinimalFee(): int { ... } +``` + +Your code on 5.x: +```php + public function getAmountProduct(Customer $customer) { ... } + + public function getFeePercent() { ... } + + public function getMinimalFee() { ... } +``` + +In the exchange rate processing service: + +Your code on 4.x: +```php + protected function rate(Wallet $wallet): float { ... } + + public function convertTo(Wallet $wallet): float { ... } +``` + +Your code on 5.x: +```php + protected function rate(Wallet $wallet) { ... } + + public function convertTo(Wallet $wallet) { ... } +``` diff --git a/src/Commands/RefreshBalance.php b/src/Commands/RefreshBalance.php index 90e2e0840..f1ec0440a 100644 --- a/src/Commands/RefreshBalance.php +++ b/src/Commands/RefreshBalance.php @@ -4,7 +4,6 @@ use Bavix\Wallet\Models\Wallet; use Bavix\Wallet\Services\DbService; -use Bavix\Wallet\Services\ProxyService; use Illuminate\Console\Command; use Illuminate\Database\Query\JoinClause; use Illuminate\Database\SQLiteConnection; @@ -34,10 +33,10 @@ class RefreshBalance extends Command /** * @return void + * @throws */ public function handle(): void { - app(ProxyService::class)->fresh(); app(DbService::class)->transaction(function () { $wallet = config('wallet.wallet.table', 'wallets'); app(DbService::class) diff --git a/src/Interfaces/Discount.php b/src/Interfaces/Discount.php index b7ad22a30..33a88b96a 100644 --- a/src/Interfaces/Discount.php +++ b/src/Interfaces/Discount.php @@ -6,7 +6,7 @@ interface Discount extends Product { /** * @param Customer $customer - * @return int + * @return int|float */ - public function getPersonalDiscount(Customer $customer): int; + public function getPersonalDiscount(Customer $customer); } diff --git a/src/Interfaces/Exchangeable.php b/src/Interfaces/Exchangeable.php index 9f3c1c85b..45f03b4dd 100644 --- a/src/Interfaces/Exchangeable.php +++ b/src/Interfaces/Exchangeable.php @@ -12,7 +12,7 @@ interface Exchangeable * @param array|null $meta * @return Transfer */ - public function exchange(Wallet $to, int $amount, ?array $meta = null): Transfer; + public function exchange(Wallet $to, $amount, ?array $meta = null): Transfer; /** * @param Wallet $to @@ -20,7 +20,7 @@ public function exchange(Wallet $to, int $amount, ?array $meta = null): Transfer * @param array|null $meta * @return Transfer|null */ - public function safeExchange(Wallet $to, int $amount, ?array $meta = null): ?Transfer; + public function safeExchange(Wallet $to, $amount, ?array $meta = null): ?Transfer; /** * @param Wallet $to @@ -28,5 +28,5 @@ public function safeExchange(Wallet $to, int $amount, ?array $meta = null): ?Tra * @param array|null $meta * @return Transfer */ - public function forceExchange(Wallet $to, int $amount, ?array $meta = null): Transfer; + public function forceExchange(Wallet $to, $amount, ?array $meta = null): Transfer; } diff --git a/src/Interfaces/Mathable.php b/src/Interfaces/Mathable.php new file mode 100644 index 000000000..dd0745973 --- /dev/null +++ b/src/Interfaces/Mathable.php @@ -0,0 +1,78 @@ + 'int', - 'amount' => 'int', 'confirmed' => 'bool', 'meta' => 'json' ]; @@ -97,26 +97,28 @@ public function wallet(): BelongsTo } /** - * @return float + * @return int|float */ - public function getAmountFloatAttribute(): float + public function getAmountFloatAttribute() { $decimalPlaces = app(WalletService::class) ->decimalPlaces($this->wallet); - return $this->amount / $decimalPlaces; + return app(Mathable::class) + ->div($this->amount, $decimalPlaces); } /** - * @param float $amount - * @return float + * @param int|float $amount + * @return void */ - public function setAmountFloatAttribute(float $amount): void + public function setAmountFloatAttribute($amount): void { + $math = app(Mathable::class); $decimalPlaces = app(WalletService::class) ->decimalPlaces($this->wallet); - $this->amount = (int)round($amount * $decimalPlaces); + $this->amount = $math->round($math->mul($amount, $decimalPlaces)); } } diff --git a/src/Models/Transfer.php b/src/Models/Transfer.php index f63624b09..9bfe8dbe5 100644 --- a/src/Models/Transfer.php +++ b/src/Models/Transfer.php @@ -56,7 +56,6 @@ class Transfer extends Model protected $casts = [ 'deposit_id' => 'int', 'withdraw_id' => 'int', - 'fee' => 'int', ]; /** diff --git a/src/Models/Wallet.php b/src/Models/Wallet.php index 5067757cf..dacb42d61 100644 --- a/src/Models/Wallet.php +++ b/src/Models/Wallet.php @@ -54,7 +54,6 @@ class Wallet extends Model implements Customer, WalletFloat, Confirmable, Exchan * @var array */ protected $casts = [ - 'balance' => 'int', 'decimal_places' => 'int', ]; @@ -109,9 +108,9 @@ public function refreshBalance(): bool } /** - * @return int + * @return float|int */ - public function getAvailableBalance(): int + public function getAvailableBalance() { return $this->transactions() ->where('wallet_id', $this->getKey()) diff --git a/src/Objects/Bring.php b/src/Objects/Bring.php index a84993946..419ee6c95 100644 --- a/src/Objects/Bring.php +++ b/src/Objects/Bring.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Objects; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; @@ -182,7 +183,10 @@ public function getDiscount(): int public function getFee(): int { if ($this->fee === null) { - return abs($this->getWithdraw()->amount) - abs($this->getDeposit()->amount); + return app(Mathable::class)->sub( + app(Mathable::class)->abs($this->getWithdraw()->amount), + app(Mathable::class)->abs($this->getDeposit()->amount) + ); } return $this->fee; @@ -192,7 +196,7 @@ public function getFee(): int * @param int $fee * @return Bring */ - public function setFee(int $fee): self + public function setFee($fee): self { $this->fee = $fee; return $this; diff --git a/src/Objects/Cart.php b/src/Objects/Cart.php index 8a7871cef..e1d424d3c 100644 --- a/src/Objects/Cart.php +++ b/src/Objects/Cart.php @@ -3,6 +3,7 @@ namespace Bavix\Wallet\Objects; use Bavix\Wallet\Interfaces\Customer; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Product; use Bavix\Wallet\Models\Transfer; use Countable; @@ -23,16 +24,6 @@ class Cart implements Countable */ protected $quantity = []; - /** - * @return static - * @deprecated use app(Cart::class) - * @codeCoverageIgnore - */ - public static function make(): self - { - return new static(); - } - /** * @param Product $product * @param int $quantity @@ -132,11 +123,12 @@ public function canBuy(Customer $customer, bool $force = null): bool * @param Customer $customer * @return int */ - public function getTotal(Customer $customer): int + public function getTotal(Customer $customer): string { $result = 0; + $math = app(Mathable::class); foreach ($this->items as $item) { - $result += $item->getAmountProduct($customer); + $result = $math->add($result, $item->getAmountProduct($customer)); } return $result; } @@ -168,7 +160,8 @@ protected function addQuantity(Product $product, int $quantity): void { $class = get_class($product); $uniq = $product->getUniqueId(); - $this->quantity[$class][$uniq] = $this->getQuantity($product) + $quantity; + $math = app(Mathable::class); + $this->quantity[$class][$uniq] = $math->add($this->getQuantity($product), $quantity); } } diff --git a/src/Objects/Operation.php b/src/Objects/Operation.php index d76381d6d..c61f89360 100644 --- a/src/Objects/Operation.php +++ b/src/Objects/Operation.php @@ -65,9 +65,9 @@ public function getUuid(): string } /** - * @return int + * @return float|int */ - public function getAmount(): int + public function getAmount() { return $this->amount; } @@ -102,7 +102,7 @@ public function setType(string $type): self * @param int $amount * @return static */ - public function setAmount(int $amount): self + public function setAmount($amount): self { $this->amount = $amount; return $this; diff --git a/src/Services/CommonService.php b/src/Services/CommonService.php index fa9dcc273..c8b5b82b0 100644 --- a/src/Services/CommonService.php +++ b/src/Services/CommonService.php @@ -4,6 +4,7 @@ use Bavix\Wallet\Exceptions\BalanceIsEmpty; use Bavix\Wallet\Exceptions\InsufficientFunds; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Models\Transaction; @@ -27,13 +28,14 @@ class CommonService * @param string $status * @return Transfer */ - public function transfer(Wallet $from, Wallet $to, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer + public function transfer(Wallet $from, Wallet $to, $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer { return app(LockService::class)->lock($this, __FUNCTION__, function () use ($from, $to, $amount, $meta, $status) { + $math = app(Mathable::class); $discount = app(WalletService::class)->discount($from, $to); - $newAmount = max(0, $amount - $discount); + $newAmount = max(0, $math->sub($amount, $discount)); $fee = app(WalletService::class)->fee($to, $newAmount); - $this->verifyWithdraw($from, $newAmount + $fee); + $this->verifyWithdraw($from, $math->add($newAmount, $fee)); return $this->forceTransfer($from, $to, $amount, $meta, $status); }); } @@ -46,15 +48,17 @@ public function transfer(Wallet $from, Wallet $to, int $amount, ?array $meta = n * @param string $status * @return Transfer */ - public function forceTransfer(Wallet $from, Wallet $to, int $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer + public function forceTransfer(Wallet $from, Wallet $to, $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer { return app(LockService::class)->lock($this, __FUNCTION__, function () use ($from, $to, $amount, $meta, $status) { + $math = app(Mathable::class); $from = app(WalletService::class)->getWallet($from); $discount = app(WalletService::class)->discount($from, $to); - $amount = max(0, $amount - $discount); + $amount = max(0, $math->sub($amount, $discount)); $fee = app(WalletService::class)->fee($to, $amount); - $withdraw = $this->forceWithdraw($from, $amount + $fee, $meta); + $placesValue = app(WalletService::class)->decimalPlacesValue($from); + $withdraw = $this->forceWithdraw($from, $math->add($amount, $fee, $placesValue), $meta); $deposit = $this->deposit($to, $amount, $meta); $transfers = $this->multiBrings([ @@ -78,7 +82,7 @@ public function forceTransfer(Wallet $from, Wallet $to, int $amount, ?array $met * @param bool|null $confirmed * @return Transaction */ - public function forceWithdraw(Wallet $wallet, int $amount, ?array $meta, bool $confirmed = true): Transaction + public function forceWithdraw(Wallet $wallet, $amount, ?array $meta, bool $confirmed = true): Transaction { return app(LockService::class)->lock($this, __FUNCTION__, function () use ($wallet, $amount, $meta, $confirmed) { $walletService = app(WalletService::class); @@ -108,7 +112,7 @@ public function forceWithdraw(Wallet $wallet, int $amount, ?array $meta, bool $c * @param bool $confirmed * @return Transaction */ - public function deposit(Wallet $wallet, int $amount, ?array $meta, bool $confirmed = true): Transaction + public function deposit(Wallet $wallet, $amount, ?array $meta, bool $confirmed = true): Transaction { return app(LockService::class)->lock($this, __FUNCTION__, function () use ($wallet, $amount, $meta, $confirmed) { $walletService = app(WalletService::class); @@ -139,7 +143,7 @@ public function deposit(Wallet $wallet, int $amount, ?array $meta, bool $confirm * @throws BalanceIsEmpty * @throws InsufficientFunds */ - public function verifyWithdraw(Wallet $wallet, int $amount, bool $allowZero = null): void + public function verifyWithdraw(Wallet $wallet, $amount, bool $allowZero = null): void { /** * @var HasWallet $wallet @@ -165,9 +169,10 @@ public function multiOperation(Wallet $self, array $operations): array return app(LockService::class)->lock($this, __FUNCTION__, function () use ($self, $operations) { $amount = 0; $objects = []; + $math = app(Mathable::class); foreach ($operations as $operation) { if ($operation->isConfirmed()) { - $amount += $operation->getAmount(); + $amount = $math->add($amount, $operation->getAmount()); } $objects[] = $operation @@ -221,7 +226,7 @@ public function multiBrings(array $brings): array * @return bool * @throws */ - public function addBalance(Wallet $wallet, int $amount): bool + public function addBalance(Wallet $wallet, $amount): bool { return app(LockService::class)->lock($this, __FUNCTION__, static function () use ($wallet, $amount) { /** diff --git a/src/Services/ExchangeService.php b/src/Services/ExchangeService.php index 8bdbf36c7..32a1bf7fe 100644 --- a/src/Services/ExchangeService.php +++ b/src/Services/ExchangeService.php @@ -11,9 +11,9 @@ class ExchangeService /** * @param Wallet $from * @param Wallet $to - * @return float + * @return int|float */ - public function rate(Wallet $from, Wallet $to): float + public function rate(Wallet $from, Wallet $to) { return app(Rateable::class) ->withAmount(1) diff --git a/src/Services/ProxyService.php b/src/Services/ProxyService.php deleted file mode 100644 index 6a9841f46..000000000 --- a/src/Services/ProxyService.php +++ /dev/null @@ -1,103 +0,0 @@ -offsetExists($key); - } - - /** - * @inheritDoc - */ - public function offsetExists($offset): bool - { - return array_key_exists($offset, $this->data); - } - - /** - * @param string $key - * @return int - */ - public function get(string $key): int - { - return $this->offsetGet($key); - } - - /** - * @inheritDoc - */ - public function offsetGet($offset): int - { - return $this->data[$offset] ?? 0; - } - - /** - * @param string $key - * @param int $value - * @return static - */ - public function set(string $key, int $value): self - { - $this->offsetSet($key, $value); - return $this; - } - - /** - * @inheritDoc - */ - public function offsetSet($offset, $value): void - { - $this->data[$offset] = $value; - } - - /** - * @param string $key - * @return static - */ - public function remove(string $key): self - { - $this->offsetUnset($key); - return $this; - } - - /** - * @inheritDoc - */ - public function offsetUnset($offset): void - { - unset($this->data[$offset]); - } - - /** - * @return void - */ - public function fresh(): void - { - app()->instance(Storable::class, null); - $this->data = []; - } - -} diff --git a/src/Services/WalletService.php b/src/Services/WalletService.php index 96da46415..6ec7530dd 100644 --- a/src/Services/WalletService.php +++ b/src/Services/WalletService.php @@ -5,6 +5,7 @@ use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Interfaces\Customer; use Bavix\Wallet\Interfaces\Discount; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\MinimalTaxable; use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Interfaces\Taxable; @@ -35,10 +36,19 @@ public function discount(Wallet $customer, Wallet $product): int * @param Wallet $object * @return int */ - public function decimalPlaces(Wallet $object): int + public function decimalPlacesValue(Wallet $object): int { - $decimalPlaces = $this->getWallet($object)->decimal_places ?: 2; - return 10 ** $decimalPlaces; + return $this->getWallet($object)->decimal_places ?: 2; + } + + /** + * @param Wallet $object + * @return string + */ + public function decimalPlaces(Wallet $object): string + { + return app(Mathable::class) + ->pow(10, $this->decimalPlacesValue($object)); } /** @@ -46,13 +56,21 @@ public function decimalPlaces(Wallet $object): int * * @param Wallet $wallet * @param int $amount - * @return int + * @return float|int */ - public function fee(Wallet $wallet, int $amount): int + public function fee(Wallet $wallet, $amount) { $fee = 0; + $math = app(Mathable::class); if ($wallet instanceof Taxable) { - $fee = (int)($amount * $wallet->getFeePercent() / 100); + $placesValue = $this->decimalPlacesValue($wallet); + $fee = $math->floor( + $math->div( + $math->mul($amount, $wallet->getFeePercent(), 0), + 100, + $placesValue + ) + ); } /** @@ -62,7 +80,7 @@ public function fee(Wallet $wallet, int $amount): int */ if ($wallet instanceof MinimalTaxable) { $minimal = $wallet->getMinimalFee(); - if ($fee < $minimal) { + if (app(Mathable::class)->compare($fee, $minimal) === -1) { $fee = $minimal; } } @@ -76,9 +94,9 @@ public function fee(Wallet $wallet, int $amount): int * @param int $amount * @throws */ - public function checkAmount(int $amount): void + public function checkAmount($amount): void { - if ($amount < 0) { + if (app(Mathable::class)->compare($amount, 0) === -1) { throw new AmountInvalid(trans('wallet::errors.price_positive')); } } @@ -109,17 +127,6 @@ public function getWallet(Wallet $object, bool $autoSave = true): WalletModel return $wallet; } - /** - * @param Wallet $object - * @return int - * @deprecated use Storable::getBalance - */ - public function getBalance(Wallet $object): int - { - return app(Storable::class) - ->getBalance($object); - } - /** * @param WalletModel $wallet * @return bool @@ -127,12 +134,14 @@ public function getBalance(Wallet $object): int public function refresh(WalletModel $wallet): bool { return app(LockService::class)->lock($this, __FUNCTION__, static function () use ($wallet) { + $math = app(Mathable::class); app(Storable::class)->getBalance($wallet); + $whatIs = $wallet->balance; $balance = $wallet->getAvailableBalance(); $wallet->balance = $balance; return app(Storable::class)->setBalance($wallet, $balance) && - $wallet->save(); + (!$math->compare($whatIs, $balance) || $wallet->save()); }); } diff --git a/src/Simple/BCMath.php b/src/Simple/BCMath.php new file mode 100644 index 000000000..47f3761e6 --- /dev/null +++ b/src/Simple/BCMath.php @@ -0,0 +1,148 @@ +scale($scale)); + } + + /** + * @inheritDoc + */ + public function sub($first, $second, ?int $scale = null): string + { + return bcsub($first, $second, $this->scale($scale)); + } + + /** + * @inheritDoc + */ + public function div($first, $second, ?int $scale = null): string + { + return bcdiv($first, $second, $this->scale($scale)); + } + + /** + * @inheritDoc + */ + public function mul($first, $second, ?int $scale = null): string + { + return bcmul($first, $second, $this->scale($scale)); + } + + /** + * @inheritDoc + */ + public function pow($first, $second, ?int $scale = null): string + { + return bcpow($first, $second, $this->scale($scale)); + } + + /** + * @inheritDoc + */ + public function ceil($number): string + { + if (strpos($number, '.') === false) { + return $number; + } + + if (preg_match("~\.[0]+$~", $number)) { + return $this->round($number, 0); + } + + if ($this->isNegative($number)) { + return bcsub($number, 0, 0); + } + + return bcadd($number, 1, 0); + } + + /** + * @inheritDoc + */ + public function floor($number): string + { + if (strpos($number, '.') === false) { + return $number; + } + + if (preg_match("~\.[0]+$~", $number)) { + return $this->round($number, 0); + } + + if ($this->isNegative($number)) { + return bcsub($number, 1, 0); + } + + return bcadd($number, 0, 0); + } + + /** + * @inheritDoc + */ + public function round($number, int $precision = 0): string + { + if (strpos($number, '.') === false) { + return $number; + } + + if ($this->isNegative($number)) { + return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision); + } + + return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision); + } + + /** + * @param float|int|string $number + * @return string + */ + public function abs($number): string + { + if (!preg_match('~^-?\d*(\.\d*)?$~', $number, $matches)) { + return 0; + } + + $digits = $matches[0] ?? '0'; + $division = $matches[1] ?? '.'; + if ($digits === '.' && $division === '.') { + return 0; + } + + if ($this->isNegative($number)) { + return substr($number, 1); + } + + return $number; + } + + /** + * @inheritDoc + */ + public function compare($first, $second): int + { + return bccomp($first, $second, $this->scale()); + } + + /** + * @param $number + * @return bool + */ + protected function isNegative($number): bool + { + return strpos($number, '-') === 0; + } + +} diff --git a/src/Simple/Math.php b/src/Simple/Math.php new file mode 100644 index 000000000..8cadd962e --- /dev/null +++ b/src/Simple/Math.php @@ -0,0 +1,138 @@ +round($first + $second, $this->scale($scale)); + } + + /** + * @param string|int|float $first + * @param string|int|float $second + * @param null|int $scale + * @return string + */ + public function sub($first, $second, ?int $scale = null): string + { + return $this->round($first - $second, $this->scale($scale)); + } + + /** + * @param string|int|float $first + * @param string|int|float $second + * @param null|int $scale + * @return float|int|string|null + */ + public function div($first, $second, ?int $scale = null): string + { + return $this->round($first / $second, $this->scale($scale)); + } + + /** + * @param string|int|float $first + * @param string|int|float $second + * @param null|int $scale + * @return float|int|string + */ + public function mul($first, $second, ?int $scale = null): string + { + return $this->round($first * $second, $this->scale($scale)); + } + + /** + * @param string|int|float $first + * @param string|int|float $second + * @param null|int $scale + * @return string + */ + public function pow($first, $second, ?int $scale = null): string + { + return $this->round($first ** $second, $this->scale($scale)); + } + + /** + * @param string|int|float $number + * @return string + */ + public function ceil($number): string + { + return ceil($number); + } + + /** + * @param string|int|float $number + * @return string + */ + public function floor($number): string + { + return floor($number); + } + + /** + * @param float|int|string $number + * @return string + */ + public function abs($number): string + { + return abs($number); + } + + /** + * @param string|int|float $number + * @param int $precision + * @return string + */ + public function round($number, int $precision = 0): string + { + return round($number, $precision); + } + + /** + * @param $first + * @param $second + * @return int + */ + public function compare($first, $second): int + { + return $first <=> $second; + } + + /** + * @param int $scale + * @return int + */ + protected function scale(?int $scale = null): int + { + if ($scale !== null) { + return $scale; + } + + if ($this->scale === null) { + $this->scale = (int)config('wallet.math.scale', 64); + } + + return $this->scale; + } + +} diff --git a/src/Simple/Rate.php b/src/Simple/Rate.php index 4a9cba62b..43aaa4d75 100644 --- a/src/Simple/Rate.php +++ b/src/Simple/Rate.php @@ -25,7 +25,7 @@ class Rate implements Rateable /** * @inheritDoc */ - public function withAmount(int $amount): Rateable + public function withAmount($amount): Rateable { $this->amount = $amount; return $this; @@ -43,7 +43,7 @@ public function withCurrency(Wallet $wallet): Rateable /** * @inheritDoc */ - public function convertTo(Wallet $wallet): float + public function convertTo(Wallet $wallet) { return $this->amount; } diff --git a/src/Simple/Store.php b/src/Simple/Store.php index e33cd03c5..9116936d5 100644 --- a/src/Simple/Store.php +++ b/src/Simple/Store.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Simple; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Services\WalletService; @@ -16,14 +17,14 @@ class Store implements Storable /** * @inheritDoc */ - public function getBalance($object): int + public function getBalance($object) { $wallet = app(WalletService::class)->getWallet($object); if (!\array_key_exists($wallet->getKey(), $this->balanceSheets)) { $balance = method_exists($wallet, 'getRawOriginal') ? $wallet->getRawOriginal('balance', 0) : $wallet->getOriginal('balance', 0); - $this->balanceSheets[$wallet->getKey()] = (int)$balance; + $this->balanceSheets[$wallet->getKey()] = app(Mathable::class)->round($balance); } return $this->balanceSheets[$wallet->getKey()]; @@ -32,9 +33,10 @@ public function getBalance($object): int /** * @inheritDoc */ - public function incBalance($object, int $amount): int + public function incBalance($object, $amount) { - $balance = $this->getBalance($object) + $amount; + $math = app(Mathable::class); + $balance = $math->add($this->getBalance($object), $amount); $this->setBalance($object, $balance); return $balance; } @@ -42,10 +44,10 @@ public function incBalance($object, int $amount): int /** * @inheritDoc */ - public function setBalance($object, int $amount): bool + public function setBalance($object, $amount): bool { $wallet = app(WalletService::class)->getWallet($object); - $this->balanceSheets[$wallet->getKey()] = $amount; + $this->balanceSheets[$wallet->getKey()] = app(Mathable::class)->round($amount ?: 0); return true; } diff --git a/src/Traits/CanExchange.php b/src/Traits/CanExchange.php index 20f545a23..a7f6e7106 100644 --- a/src/Traits/CanExchange.php +++ b/src/Traits/CanExchange.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Traits; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Objects\Bring; @@ -17,7 +18,7 @@ trait CanExchange /** * @inheritDoc */ - public function exchange(Wallet $to, int $amount, ?array $meta = null): Transfer + public function exchange(Wallet $to, $amount, ?array $meta = null): Transfer { $wallet = app(WalletService::class) ->getWallet($this); @@ -31,7 +32,7 @@ public function exchange(Wallet $to, int $amount, ?array $meta = null): Transfer /** * @inheritDoc */ - public function safeExchange(Wallet $to, int $amount, ?array $meta = null): ?Transfer + public function safeExchange(Wallet $to, $amount, ?array $meta = null): ?Transfer { try { return $this->exchange($to, $amount, $meta); @@ -43,23 +44,24 @@ public function safeExchange(Wallet $to, int $amount, ?array $meta = null): ?Tra /** * @inheritDoc */ - public function forceExchange(Wallet $to, int $amount, ?array $meta = null): Transfer + public function forceExchange(Wallet $to, $amount, ?array $meta = null): Transfer { /** * @var Wallet $from */ $from = app(WalletService::class)->getWallet($this); - return app(LockService::class)->lock($this, __FUNCTION__, function () use ($from, $to, $amount, $meta) { + return app(LockService::class)->lock($this, __FUNCTION__, static function () use ($from, $to, $amount, $meta) { return app(DbService::class)->transaction(static function () use ($from, $to, $amount, $meta) { + $math = app(Mathable::class); $rate = app(ExchangeService::class)->rate($from, $to); $fee = app(WalletService::class)->fee($to, $amount); $withdraw = app(CommonService::class) - ->forceWithdraw($from, $amount + $fee, $meta); + ->forceWithdraw($from, $math->add($amount, $fee), $meta); $deposit = app(CommonService::class) - ->deposit($to, $amount * $rate, $meta); + ->deposit($to, $math->floor($math->mul($amount, $rate, 1)), $meta); $transfers = app(CommonService::class)->multiBrings([ app(Bring::class) diff --git a/src/Traits/HasGift.php b/src/Traits/HasGift.php index 01504bcfd..8c1422f69 100644 --- a/src/Traits/HasGift.php +++ b/src/Traits/HasGift.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Traits; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Product; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Models\Transfer; @@ -64,8 +65,9 @@ public function gift(Wallet $to, Product $product, bool $force = null): Transfer * That's why I address him like this! */ return app(DbService::class)->transaction(static function () use ($santa, $to, $product, $force) { + $math = app(Mathable::class); $discount = app(WalletService::class)->discount($santa, $product); - $amount = $product->getAmountProduct($santa) - $discount; + $amount = $math->sub($product->getAmountProduct($santa), $discount); $meta = $product->getMetaProduct(); $fee = app(WalletService::class) ->fee($product, $amount); @@ -76,10 +78,10 @@ public function gift(Wallet $to, Product $product, bool $force = null): Transfer * Santa pays taxes */ if (!$force) { - $commonService->verifyWithdraw($santa, $amount + $fee); + $commonService->verifyWithdraw($santa, $math->add($amount, $fee)); } - $withdraw = $commonService->forceWithdraw($santa, $amount + $fee, $meta); + $withdraw = $commonService->forceWithdraw($santa, $math->add($amount, $fee), $meta); $deposit = $commonService->deposit($product, $amount, $meta); $from = app(WalletService::class) diff --git a/src/Traits/HasWallet.php b/src/Traits/HasWallet.php index c560a916e..868c6b1fb 100644 --- a/src/Traits/HasWallet.php +++ b/src/Traits/HasWallet.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Traits; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Models\Transaction; @@ -39,7 +40,7 @@ trait HasWallet * @return Transaction * @throws */ - public function deposit(int $amount, ?array $meta = null, bool $confirmed = true): Transaction + public function deposit($amount, ?array $meta = null, bool $confirmed = true): Transaction { $self = $this; return app(DbService::class)->transaction(static function () use ($self, $amount, $meta, $confirmed) { @@ -69,10 +70,10 @@ public function deposit(int $amount, ?array $meta = null, bool $confirmed = true * $user2->deposit(100); * var_dump($user2->balance); // 300 * - * @return int + * @return int|float * @throws */ - public function getBalanceAttribute(): int + public function getBalanceAttribute() { return app(Storable::class)->getBalance($this); } @@ -96,7 +97,7 @@ public function transactions(): MorphMany * @param array|null $meta * @return null|Transfer */ - public function safeTransfer(Wallet $wallet, int $amount, ?array $meta = null): ?Transfer + public function safeTransfer(Wallet $wallet, $amount, ?array $meta = null): ?Transfer { try { return $this->transfer($wallet, $amount, $meta); @@ -114,7 +115,7 @@ public function safeTransfer(Wallet $wallet, int $amount, ?array $meta = null): * @return Transfer * @throws */ - public function transfer(Wallet $wallet, int $amount, ?array $meta = null): Transfer + public function transfer(Wallet $wallet, $amount, ?array $meta = null): Transfer { app(CommonService::class)->verifyWithdraw($this, $amount); return $this->forceTransfer($wallet, $amount, $meta); @@ -129,7 +130,7 @@ public function transfer(Wallet $wallet, int $amount, ?array $meta = null): Tran * * @return Transaction */ - public function withdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction + public function withdraw($amount, ?array $meta = null, bool $confirmed = true): Transaction { app(CommonService::class)->verifyWithdraw($this, $amount); return $this->forceWithdraw($amount, $meta, $confirmed); @@ -142,7 +143,7 @@ public function withdraw(int $amount, ?array $meta = null, bool $confirmed = tru * @param bool $allowZero * @return bool */ - public function canWithdraw(int $amount, bool $allowZero = null): bool + public function canWithdraw($amount, bool $allowZero = null): bool { /** * Allow to buy for free with a negative balance @@ -151,7 +152,7 @@ public function canWithdraw(int $amount, bool $allowZero = null): bool return true; } - return $this->balance >= $amount; + return app(Mathable::class)->compare($this->balance, $amount) >= 0; } /** @@ -164,7 +165,7 @@ public function canWithdraw(int $amount, bool $allowZero = null): bool * @return Transaction * @throws */ - public function forceWithdraw(int $amount, ?array $meta = null, bool $confirmed = true): Transaction + public function forceWithdraw($amount, ?array $meta = null, bool $confirmed = true): Transaction { $self = $this; return app(DbService::class)->transaction(static function () use ($self, $amount, $meta, $confirmed) { @@ -183,7 +184,7 @@ public function forceWithdraw(int $amount, ?array $meta = null, bool $confirmed * @return Transfer * @throws */ - public function forceTransfer(Wallet $wallet, int $amount, ?array $meta = null): Transfer + public function forceTransfer(Wallet $wallet, $amount, ?array $meta = null): Transfer { $self = $this; return app(DbService::class)->transaction(static function () use ($self, $amount, $wallet, $meta) { diff --git a/src/Traits/HasWalletFloat.php b/src/Traits/HasWalletFloat.php index 600f1314a..34a7444c9 100644 --- a/src/Traits/HasWalletFloat.php +++ b/src/Traits/HasWalletFloat.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Traits; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; @@ -25,10 +26,13 @@ trait HasWalletFloat * * @return Transaction */ - public function forceWithdrawFloat(float $amount, ?array $meta = null, bool $confirmed = true): Transaction + public function forceWithdrawFloat($amount, ?array $meta = null, bool $confirmed = true): Transaction { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->forceWithdraw((int)round($amount * $decimalPlaces), $meta, $confirmed); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->forceWithdraw($result, $meta, $confirmed); } /** @@ -38,10 +42,13 @@ public function forceWithdrawFloat(float $amount, ?array $meta = null, bool $con * * @return Transaction */ - public function depositFloat(float $amount, ?array $meta = null, bool $confirmed = true): Transaction + public function depositFloat($amount, ?array $meta = null, bool $confirmed = true): Transaction { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->deposit((int)round($amount * $decimalPlaces), $meta, $confirmed); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->deposit($result, $meta, $confirmed); } /** @@ -51,20 +58,26 @@ public function depositFloat(float $amount, ?array $meta = null, bool $confirmed * * @return Transaction */ - public function withdrawFloat(float $amount, ?array $meta = null, bool $confirmed = true): Transaction + public function withdrawFloat($amount, ?array $meta = null, bool $confirmed = true): Transaction { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->withdraw((int)round($amount * $decimalPlaces), $meta, $confirmed); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->withdraw($result, $meta, $confirmed); } /** * @param float $amount * @return bool */ - public function canWithdrawFloat(float $amount): bool + public function canWithdrawFloat($amount): bool { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->canWithdraw((int)round($amount * $decimalPlaces)); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->canWithdraw($result); } /** @@ -74,10 +87,13 @@ public function canWithdrawFloat(float $amount): bool * @return Transfer * @throws */ - public function transferFloat(Wallet $wallet, float $amount, ?array $meta = null): Transfer + public function transferFloat(Wallet $wallet, $amount, ?array $meta = null): Transfer { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->transfer($wallet, (int)round($amount * $decimalPlaces), $meta); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->transfer($wallet, $result, $meta); } /** @@ -86,10 +102,13 @@ public function transferFloat(Wallet $wallet, float $amount, ?array $meta = null * @param array|null $meta * @return null|Transfer */ - public function safeTransferFloat(Wallet $wallet, float $amount, ?array $meta = null): ?Transfer + public function safeTransferFloat(Wallet $wallet, $amount, ?array $meta = null): ?Transfer { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->safeTransfer($wallet, (int)round($amount * $decimalPlaces), $meta); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->safeTransfer($wallet, $result, $meta); } /** @@ -98,19 +117,24 @@ public function safeTransferFloat(Wallet $wallet, float $amount, ?array $meta = * @param array|null $meta * @return Transfer */ - public function forceTransferFloat(Wallet $wallet, float $amount, ?array $meta = null): Transfer + public function forceTransferFloat(Wallet $wallet, $amount, ?array $meta = null): Transfer { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->forceTransfer($wallet, (int)round($amount * $decimalPlaces), $meta); + $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + return $this->forceTransfer($wallet, $result, $meta); } /** - * @return float + * @return int|float */ - public function getBalanceFloatAttribute(): float + public function getBalanceFloatAttribute() { + $math = app(Mathable::class); + $decimalPlacesValue = app(WalletService::class)->decimalPlacesValue($this); $decimalPlaces = app(WalletService::class)->decimalPlaces($this); - return $this->balance / $decimalPlaces; + return $math->div($this->balance, $decimalPlaces, $decimalPlacesValue); } } diff --git a/src/WalletServiceProvider.php b/src/WalletServiceProvider.php index 3b61ca107..81d29a33f 100644 --- a/src/WalletServiceProvider.php +++ b/src/WalletServiceProvider.php @@ -3,6 +3,7 @@ namespace Bavix\Wallet; use Bavix\Wallet\Commands\RefreshBalance; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Rateable; use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Models\Transaction; @@ -16,7 +17,7 @@ use Bavix\Wallet\Services\DbService; use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Services\LockService; -use Bavix\Wallet\Services\ProxyService; +use Bavix\Wallet\Simple\Math; use Bavix\Wallet\Services\WalletService; use Bavix\Wallet\Simple\Rate; use Bavix\Wallet\Simple\Store; @@ -87,10 +88,10 @@ public function register(): void // Bind eloquent models to IoC container $this->app->singleton(Rateable::class, config('wallet.package.rateable', Rate::class)); $this->app->singleton(Storable::class, config('wallet.package.storable', Store::class)); + $this->app->singleton(Mathable::class, config('wallet.package.mathable', Math::class)); $this->app->singleton(DbService::class, config('wallet.services.db', DbService::class)); $this->app->singleton(ExchangeService::class, config('wallet.services.exchange', ExchangeService::class)); $this->app->singleton(CommonService::class, config('wallet.services.common', CommonService::class)); - $this->app->singleton(ProxyService::class, config('wallet.services.proxy', ProxyService::class)); $this->app->singleton(WalletService::class, config('wallet.services.wallet', WalletService::class)); $this->app->singleton(LockService::class, config('wallet.services.lock', LockService::class)); diff --git a/tests/BalanceTest.php b/tests/BalanceTest.php index ca05c2004..2b87b669d 100644 --- a/tests/BalanceTest.php +++ b/tests/BalanceTest.php @@ -5,8 +5,7 @@ use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Models\Wallet; use Bavix\Wallet\Services\CommonService; -use Bavix\Wallet\Services\ProxyService; -use Bavix\Wallet\Services\WalletService; +use Bavix\Wallet\Simple\Store; use Bavix\Wallet\Test\Models\Buyer; use Bavix\Wallet\Test\Models\UserMulti; use Illuminate\Support\Facades\DB; @@ -112,7 +111,6 @@ public function testSimple(): void /** * @return void * @throws - * @deprecated */ public function testGetBalance(): void { @@ -128,7 +126,6 @@ public function testGetBalance(): void $this->assertTrue($wallet->exists); $this->assertEquals(0, app(Storable::class)->getBalance($wallet)); - $this->assertEquals(0, app(WalletService::class)->getBalance($wallet)); } /** @@ -226,7 +223,6 @@ public function testArtisanRefresh(): void } // fresh balance - app(ProxyService::class)->fresh(); DB::table(config('wallet.wallet.table')) ->update(['balance' => 0]); @@ -256,13 +252,30 @@ public function testForceUpdate(): void Wallet::whereKey($buyer->wallet->getKey()) ->update(['balance' => 10]); - app(ProxyService::class)->fresh(); + /** + * Create a state when the cache is empty. + * For example, something went wrong and your database has incorrect data. + * Unfortunately, the library will work with what is. + * But there is an opportunity to recount the balance. + * + * Here is an example: + */ + app()->singleton(Storable::class, Store::class); + $this->assertEquals($wallet->getRawOriginal('balance'), 1000); + /** + * We load the model from the base and our balance is 10. + */ $wallet->refresh(); $this->assertEquals($wallet->balance, 10); + $this->assertEquals($wallet->getRawOriginal('balance'), 10); + /** + * Now we fill the cache with relevant data (PS, the data inside the model will be updated). + */ $wallet->refreshBalance(); $this->assertEquals($wallet->balance, 1000); + $this->assertEquals($wallet->getRawOriginal('balance'), 1000); } } diff --git a/tests/Common/Rate.php b/tests/Common/Rate.php index c27e3bf6d..37b2d06f7 100644 --- a/tests/Common/Rate.php +++ b/tests/Common/Rate.php @@ -2,6 +2,7 @@ namespace Bavix\Wallet\Test\Common; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Services\WalletService; use Illuminate\Support\Arr; @@ -26,7 +27,7 @@ public function __construct() foreach ($this->rates as $from => $rates) { foreach ($rates as $to => $rate) { if (empty($this->rates[$to][$from])) { - $this->rates[$to][$from] = 1 / $rate; + $this->rates[$to][$from] = app(Mathable::class)->div(1, $rate); } } } @@ -34,9 +35,9 @@ public function __construct() /** * @param Wallet $wallet - * @return float + * @return int|float */ - protected function rate(Wallet $wallet): float + protected function rate(Wallet $wallet) { $from = app(WalletService::class)->getWallet($this->withCurrency); $to = app(WalletService::class)->getWallet($wallet); @@ -54,9 +55,12 @@ protected function rate(Wallet $wallet): float /** * @inheritDoc */ - public function convertTo(Wallet $wallet): float + public function convertTo(Wallet $wallet) { - return parent::convertTo($wallet) * $this->rate($wallet); + return app(Mathable::class)->mul( + parent::convertTo($wallet), + $this->rate($wallet) + ); } } diff --git a/tests/MathTest.php b/tests/MathTest.php new file mode 100644 index 000000000..5f283ae42 --- /dev/null +++ b/tests/MathTest.php @@ -0,0 +1,512 @@ +assertEquals($provider->abs('.'), 0); + $this->assertEquals($provider->abs('hello'), 0); + $this->assertEquals($provider->abs('--121'), 0); + $this->assertEquals($provider->abs('---121'), 0); + + // int + $this->assertEquals($provider->abs(123), 123); + $this->assertEquals($provider->abs(-123), 123); + + // float + $this->assertEquals($provider->abs(.0), 0); + $this->assertEquals($provider->abs(123.0), 123); + $this->assertEquals($provider->abs(123.11), 123.11); + $this->assertEquals($provider->abs(-123.11), 123.11); + + // string + $this->assertEquals($provider->abs('123.'), 123); + $this->assertEquals($provider->abs('.11'), .11); + $this->assertEquals($provider->abs('123.11'), 123.11); + $this->assertEquals($provider->abs('-123.11'), 123.11); + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testCompare(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // int + $this->assertEquals($provider->compare(1, 1), 0); + $this->assertEquals($provider->compare(1, 2), -1); + $this->assertEquals($provider->compare(2, 1), 1); + + // float + $this->assertEquals($provider->compare(1.33, 1.33), 0); + $this->assertEquals($provider->compare(1.44, 2), -1); + $this->assertEquals($provider->compare(2, 1.44), 1); + + // string + $this->assertEquals($provider->compare('1.33', '1.33'), 0); + $this->assertEquals($provider->compare('1.44', '2'), -1); + $this->assertEquals($provider->compare('2', '1.44'), 1); + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testAdd(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // int + $this->assertEquals($provider->compare($provider->add(1, 5), 6), 0); + $this->assertEquals($provider->compare($provider->add(-1, 5), 4), 0); + + // float + $this->assertEquals($provider->compare($provider->add(1.17, 4.83), 6.), 0); + $this->assertEquals($provider->compare($provider->add(-1.44, 5.43), 3.99), 0); + + if ($provider instanceof BCMath) { + $this->assertEquals( + $provider->compare( + $provider->add('4.331733759839529271053448625299468628', 1.4), + '5.731733759839529271053448625299468628' + ), + 0 + ); + + $this->assertEquals( + $provider->compare( + $provider->add('5.731733759839529271053448625299468628', '-5.731733759839529271053448625299468627'), + '0.000000000000000000000000000000000001' + ), + 0 + ); + } + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testSub(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // int + $this->assertEquals($provider->sub(1, 5), -4); + $this->assertEquals($provider->sub(-1, 5), -6); + + // float + $this->assertEquals($provider->sub(1.17, 4.83), -3.66); + $this->assertEquals($provider->sub(-1.44, 5.43), -6.87); + + if ($provider instanceof BCMath) { + $this->assertEquals( + $provider->compare( + $provider->sub('4.331733759839529271053448625299468628', 1.4), + '2.931733759839529271053448625299468628' + ), + 0 + ); + + $this->assertEquals( + $provider->compare( + $provider->sub('5.731733759839529271053448625299468628', '5.731733759839529271053448625299468627'), + '0.000000000000000000000000000000000001' + ), + 0 + ); + } + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testDiv(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // int + $this->assertEquals($provider->div(1, 5), 0.2); + $this->assertEquals($provider->div(-1, 5), -0.2); + + // float + $this->assertEquals($provider->div(1.17, 4.83), 0.24223602484472); + $this->assertEquals($provider->div(-1.44, 5.43), -0.26519337016574); + + if ($provider instanceof BCMath) { + $this->assertEquals( + $provider->compare( + $provider->div('4.331733759839529271053448625299468628', 1.4), + '3.0940955427425209078953204466424775914285714285714285714285714285' + ), + 0 + ); + + $this->assertEquals( + $provider->compare( + $provider->div('5.731733759839529271053448625299468628', '5.731733759839529271053448625299468627'), + '1.0000000000000000000000000000000000001744672802157504419105369811' + ), + 0 + ); + } + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testMul(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // int + $this->assertEquals($provider->mul(1, 5), 5); + $this->assertEquals($provider->mul(-1, 5), -5); + + // float + $this->assertEquals($provider->mul(1.17, 4.83), 5.6511); + $this->assertEquals($provider->mul(-1.44, 5.43), -7.8192); + + if ($provider instanceof BCMath) { + $this->assertEquals( + $provider->compare( + $provider->mul('4.331733759839529271053448625299468628', 1.4), + '6.0644272637753409794748280754192560792000000000000000000000000000' + ), + 0 + ); + + $this->assertEquals( + $provider->compare( + $provider->mul('5.731733759839529271053448625299468628', '5.731733759839529271053448625299468627'), + '32.8527718936841866108362353549577464458763784076112941028307058338' + ), + 0 + ); + } + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testPow(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // int + $this->assertEquals($provider->pow(1, 5), 1); + $this->assertEquals($provider->pow(-1, 5), -1); + + // float + $this->assertEquals($provider->pow(1.17, 4), 1.87388721); + $this->assertEquals($provider->pow(-1.44, 5), -6.1917364224); + + if ($provider instanceof BCMath) { + $this->assertEquals( + $provider->compare( + $provider->pow('4.331733759839529271053448625299468628', 14), + '818963567.1194514424328910747572247977826032927674623819207642247854744523' + ), + 0 + ); + + $this->assertEquals( + $provider->compare( + $provider->pow('5.731733759839529271053448625299468628', 6), + '35458.1485207464760293448564751702377579632773756221209731837301291644' + ), + 0 + ); + } + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testCeil(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // positive + // int + $this->assertEquals( + $provider->ceil(35458), + 35458 + ); + + // float + $this->assertEquals($provider->ceil('35458.00000000'), 35458); + $this->assertEquals( + $provider->ceil(35458.0000001), + 35459 + ); + $this->assertEquals( + $provider->ceil(35458.4), + 35459 + ); + $this->assertEquals( + $provider->ceil(35458.5), + 35459 + ); + $this->assertEquals( + $provider->ceil(35458.6), + 35459 + ); + + // string + $this->assertEquals( + $provider->ceil('35458.1485207464760293448564751702377579632773756221209731837301291644'), + 35459 + ); + + // negative + // int + $this->assertEquals( + $provider->ceil(-35458), + -35458 + ); + + // float + $this->assertEquals( + $provider->ceil(-35458.0000001), + -35458 + ); + $this->assertEquals( + $provider->ceil(-35458.4), + -35458 + ); + $this->assertEquals( + $provider->ceil(-35458.5), + -35458 + ); + $this->assertEquals( + $provider->ceil(-35458.6), + -35458 + ); + + // string + $this->assertEquals( + $provider->ceil('-35458.1485207464760293448564751702377579632773756221209731837301291644'), + -35458 + ); + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testFloor(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // positive + // int + $this->assertEquals( + $provider->floor(35458), + 35458 + ); + + // float + $this->assertEquals($provider->floor('35458.00000000'), 35458); + $this->assertEquals( + $provider->floor(35458.0000001), + 35458 + ); + $this->assertEquals( + $provider->floor(35458.4), + 35458 + ); + $this->assertEquals( + $provider->floor(35458.5), + 35458 + ); + $this->assertEquals( + $provider->floor(35458.6), + 35458 + ); + + // string + $this->assertEquals( + $provider->floor('35458.1485207464760293448564751702377579632773756221209731837301291644'), + 35458 + ); + + // negative + // int + $this->assertEquals( + $provider->floor(-35458), + -35458 + ); + + // float + $this->assertEquals( + $provider->floor(-35458.0000001), + -35459 + ); + $this->assertEquals( + $provider->floor(-35458.4), + -35459 + ); + $this->assertEquals( + $provider->floor(-35458.5), + -35459 + ); + $this->assertEquals( + $provider->floor(-35458.6), + -35459 + ); + + // string + $this->assertEquals( + $provider->floor('-35458.1485207464760293448564751702377579632773756221209731837301291644'), + -35459 + ); + } + + /** + * @dataProvider dataProvider + * @param string $class + * @return void + */ + public function testRound(string $class): void + { + /** + * @var Mathable $provider + */ + $provider = app($class); + + // positive + // int + $this->assertEquals( + $provider->round(35458), + 35458 + ); + + // float + $this->assertEquals($provider->round('35458.00000000'), 35458); + $this->assertEquals( + $provider->round(35458.0000001), + 35458 + ); + $this->assertEquals( + $provider->round(35458.4), + 35458 + ); + $this->assertEquals( + $provider->round(35458.5), + 35459 + ); + $this->assertEquals( + $provider->round(35458.6), + 35459 + ); + + // string + $this->assertEquals( + $provider->round('35458.1485207464760293448564751702377579632773756221209731837301291644'), + 35458 + ); + + // negative + // int + $this->assertEquals( + $provider->round(-35458), + -35458 + ); + + // float + $this->assertEquals( + $provider->round(-35458.0000001), + -35458 + ); + $this->assertEquals( + $provider->round(-35458.4), + -35458 + ); + $this->assertEquals( + $provider->round(-35458.5), + -35459 + ); + $this->assertEquals( + $provider->round(-35458.6), + -35459 + ); + + // string + $this->assertEquals( + $provider->round('-35458.1485207464760293448564751702377579632773756221209731837301291644'), + -35458 + ); + } + + /** + * @return array + */ + public function dataProvider(): array + { + $providers = [[Math::class]]; + + if (extension_loaded('bcmath')) { + $providers[] = [BCMath::class]; + } + + return $providers; + } + +} diff --git a/tests/Models/Item.php b/tests/Models/Item.php index 628d18218..3754ff9c3 100644 --- a/tests/Models/Item.php +++ b/tests/Models/Item.php @@ -46,9 +46,9 @@ public function canBuy(Customer $customer, int $quantity = 1, bool $force = null /** * @param Customer $customer - * @return int + * @return float|int */ - public function getAmountProduct(Customer $customer): int + public function getAmountProduct(Customer $customer) { /** * @var Wallet $wallet diff --git a/tests/Models/ItemDiscount.php b/tests/Models/ItemDiscount.php index 9a8834c5d..96e91e98a 100644 --- a/tests/Models/ItemDiscount.php +++ b/tests/Models/ItemDiscount.php @@ -23,10 +23,9 @@ public function getTable(): string */ public function getPersonalDiscount(Customer $customer): int { - $wallet = app(WalletService::class) - ->getWallet($customer); - - return $wallet->holder->id; + return app(WalletService::class) + ->getWallet($customer) + ->holder_id; } } diff --git a/tests/Models/ItemTax.php b/tests/Models/ItemTax.php index 035ef4c1f..0cc687c11 100644 --- a/tests/Models/ItemTax.php +++ b/tests/Models/ItemTax.php @@ -23,9 +23,9 @@ public function getTable(): string * Minimum 0; Maximum 100 * Example: return 7.5; // 7.5% * - * @return float + * @return int|float */ - public function getFeePercent(): float + public function getFeePercent() { return 7.5; } diff --git a/tests/ProxyTest.php b/tests/ProxyTest.php deleted file mode 100644 index 055e7cb19..000000000 --- a/tests/ProxyTest.php +++ /dev/null @@ -1,25 +0,0 @@ -assertEquals($proxy[$i], $i); - $proxy->remove($i); - $this->assertEquals($proxy[$i], 0); - } - } - -} diff --git a/tests/SingletonTest.php b/tests/SingletonTest.php index 222941f3f..9f04dc52f 100644 --- a/tests/SingletonTest.php +++ b/tests/SingletonTest.php @@ -2,7 +2,9 @@ namespace Bavix\Wallet\Test; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Interfaces\Rateable; +use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Objects\Bring; use Bavix\Wallet\Objects\Cart; use Bavix\Wallet\Objects\EmptyLock; @@ -11,7 +13,6 @@ use Bavix\Wallet\Services\DbService; use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Services\LockService; -use Bavix\Wallet\Services\ProxyService; use Bavix\Wallet\Services\WalletService; use Bavix\Wallet\Test\Common\Models\Transaction; use Bavix\Wallet\Test\Common\Models\Transfer; @@ -69,6 +70,22 @@ public function testRateable(): void $this->assertEquals($this->getRefId(Rateable::class), $this->getRefId(Rateable::class)); } + /** + * @return void + */ + public function testStorable(): void + { + $this->assertEquals($this->getRefId(Storable::class), $this->getRefId(Storable::class)); + } + + /** + * @return void + */ + public function testMathable(): void + { + $this->assertEquals($this->getRefId(Mathable::class), $this->getRefId(Mathable::class)); + } + /** * @return void */ @@ -109,14 +126,6 @@ public function testCommonService(): void $this->assertEquals($this->getRefId(CommonService::class), $this->getRefId(CommonService::class)); } - /** - * @return void - */ - public function testProxyService(): void - { - $this->assertEquals($this->getRefId(ProxyService::class), $this->getRefId(ProxyService::class)); - } - /** * @return void */ diff --git a/tests/TestCase.php b/tests/TestCase.php index dd06c0570..aa6de7f01 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,7 +2,9 @@ namespace Bavix\Wallet\Test; -use Bavix\Wallet\Services\ProxyService; +use Bavix\Wallet\Interfaces\Storable; +use Bavix\Wallet\Simple\BCMath; +use Bavix\Wallet\Simple\Math; use Bavix\Wallet\Simple\Store; use Bavix\Wallet\Test\Common\Models\Transaction; use Bavix\Wallet\Test\Common\Models\Transfer; @@ -22,7 +24,6 @@ class TestCase extends OrchestraTestCase */ public function setUp(): void { - app(ProxyService::class)->fresh(); parent::setUp(); $this->withFactories(__DIR__ . '/factories'); $this->loadMigrationsFrom([ @@ -48,6 +49,7 @@ protected function getPackageProviders($app): array // Bind eloquent models to IoC container $app['config']->set('wallet.package.rateable', Rate::class); $app['config']->set('wallet.package.storable', Store::class); + $app['config']->set('wallet.package.mathable', extension_loaded('bcmath') ? BCMath::class : Math::class); return [WalletServiceProvider::class]; } diff --git a/tests/WalletFloatTest.php b/tests/WalletFloatTest.php index 78a02d82b..952758628 100644 --- a/tests/WalletFloatTest.php +++ b/tests/WalletFloatTest.php @@ -4,8 +4,9 @@ use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Exceptions\BalanceIsEmpty; +use Bavix\Wallet\Interfaces\Mathable; use Bavix\Wallet\Models\Transaction; -use Bavix\Wallet\Services\WalletService; +use Bavix\Wallet\Simple\BCMath; use Bavix\Wallet\Test\Models\UserFloat as User; class WalletFloatTest extends TestCase @@ -289,4 +290,61 @@ public function testMathRounding(): void $this->assertEquals($transaction->type, Transaction::TYPE_WITHDRAW); } + /** + * @return void + */ + public function testEther(): void + { + if (app(Mathable::class) instanceof BCMath) { + /** + * @var User $user + */ + $user = factory(User::class)->create(); + $this->assertEquals($user->balance, 0); + + $user->wallet->decimal_places = 18; + $user->wallet->save(); + + $math = app(Mathable::class); + + $user->depositFloat('545.8754855274419'); + $this->assertEquals($user->balance, '545875485527441900000'); + $this->assertEquals($math->compare($user->balanceFloat, '545.8754855274419'), 0); + } + } + + /** + * @return void + */ + public function testBitcoin(): void + { + if (app(Mathable::class) instanceof BCMath) { + /** + * @var User $user + */ + $user = factory(User::class)->create(); + $this->assertEquals($user->balance, 0); + + $user->wallet->decimal_places = 32; // bitcoin wallet + $user->wallet->save(); + + $math = app(Mathable::class); + + for ($i = 0; $i < 256; $i++) { + $user->depositFloat('0.00000001'); // Satoshi + } + + $this->assertEquals($user->balance, '256' . str_repeat('0', 32 - 8)); + $this->assertEquals($math->compare($user->balanceFloat, '0.00000256'), 0); + + $user->deposit(256 . str_repeat('0', 32)); + $user->depositFloat('0.' . str_repeat('0', 31) . '1'); + + [$q, $r] = explode('.', $user->balanceFloat, 2); + $this->assertEquals(strlen($r), $user->wallet->decimal_places); + $this->assertEquals($user->balance, '25600000256000000000000000000000001'); + $this->assertEquals($user->balanceFloat, '256.00000256000000000000000000000001'); + } + } + }