diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml index 20403c0ad..db8160685 100644 --- a/.github/workflows/code-style.yaml +++ b/.github/workflows/code-style.yaml @@ -17,7 +17,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 - name: Validate composer.json and composer.lock run: composer validate --strict diff --git a/.github/workflows/phpunits.yaml b/.github/workflows/phpunits.yaml index cc40f602e..227a44ecf 100644 --- a/.github/workflows/phpunits.yaml +++ b/.github/workflows/phpunits.yaml @@ -122,10 +122,10 @@ jobs: - name: Run test suite run: | - if [[ $DB_CONNECTION == "testing" ]]; then - composer paraunit - else + if [[ $DB_CONNECTION == "mariadb" ]]; then composer unit + else + composer parabench fi env: CACHE_DRIVER: ${{ matrix.caches }} diff --git a/.github/workflows/psalm.yaml b/.github/workflows/psalm.yaml new file mode 100644 index 000000000..a1e25d978 --- /dev/null +++ b/.github/workflows/psalm.yaml @@ -0,0 +1,38 @@ +name: psalm + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + psalm: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run psalm + run: composer psalm diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 0207165d3..dc0d4e49b 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -2,34 +2,44 @@ namespace PHPSTORM_META { - use Bavix\Wallet\Interfaces\Mathable; - use Bavix\Wallet\Interfaces\Rateable; - use Bavix\Wallet\Interfaces\Storable; - use Bavix\Wallet\Models\Transaction; - use Bavix\Wallet\Models\Transfer; - use Bavix\Wallet\Models\Wallet; - use Bavix\Wallet\Objects\Bring; + use Bavix\Wallet\Internal\BasketInterface; + use Bavix\Wallet\Internal\BookkeeperInterface; + use Bavix\Wallet\Internal\CartInterface; + use Bavix\Wallet\Internal\ConsistencyInterface; + use Bavix\Wallet\Internal\ExchangeInterface; + use Bavix\Wallet\Internal\LockInterface; + use Bavix\Wallet\Internal\MathInterface; + use Bavix\Wallet\Internal\PurchaseInterface; + use Bavix\Wallet\Internal\StorageInterface; + use Bavix\Wallet\Internal\UuidInterface; use Bavix\Wallet\Objects\Cart; - use Bavix\Wallet\Objects\EmptyLock; - use Bavix\Wallet\Objects\Operation; use Bavix\Wallet\Services\CommonService; - use Bavix\Wallet\Services\ExchangeService; + use Bavix\Wallet\Services\DbService; + use Bavix\Wallet\Services\LockService; + use Bavix\Wallet\Services\MetaService; use Bavix\Wallet\Services\WalletService; override(\app(0), map([ + BasketInterface::class => BasketInterface::class, + BookkeeperInterface::class => BookkeeperInterface::class, + CartInterface::class => CartInterface::class, + ConsistencyInterface::class => ConsistencyInterface::class, + ExchangeInterface::class => ExchangeInterface::class, + LockInterface::class => LockInterface::class, + MathInterface::class => MathInterface::class, + PurchaseInterface::class => PurchaseInterface::class, + StorageInterface::class => StorageInterface::class, + UuidInterface::class => UuidInterface::class, + + // deprecated's Cart::class => Cart::class, - Bring::class => Bring::class, - Operation::class => Operation::class, - EmptyLock::class => EmptyLock::class, - ExchangeService::class => ExchangeService::class, + + // old CommonService::class => CommonService::class, + DbService::class => DbService::class, + LockService::class => LockService::class, + MetaService::class => MetaService::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/composer.json b/composer.json index 9ba30e623..4f6965028 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "illuminate/database": "^6.0|^7.0|^8.0", "doctrine/dbal": "^2.8|^3.0", "ramsey/uuid": "^3.0|^4.0", - "brick/math": "~0.8" + "brick/math": "~0.8", + "ext-json": "*" }, "require-dev": { "brianium/paratest": "^6.2", diff --git a/config/config.php b/config/config.php index 1bbc084f5..13c24f476 100644 --- a/config/config.php +++ b/config/config.php @@ -1,120 +1,71 @@ [ - '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. - */ - 'package' => [ - 'exchange' => Exchange::class, - 'rateable' => Rate::class, - 'storable' => Store::class, - 'mathable' => MathService::class, - ], - - /** - * Lock settings for highload projects. - * - * If you want to replace the default cache with another, - * then write the name of the driver cache in the key `wallet.lock.cache`. - * @see https://laravel.com/docs/6.x/cache#driver-prerequisites - * - * @example - * 'cache' => 'redis' - */ + // infra settings + 'cache' => ['driver' => null], 'lock' => [ - 'cache' => null, - 'enabled' => false, + 'driver' => null, 'seconds' => 1, ], - /** - * Sometimes a slug may not match the currency and you need the ability to add an exception. - * The main thing is that there are not many exceptions). - * - * Syntax: - * 'slug' => 'currency' - * - * @example - * 'my-usd' => 'USD' - * - * @deprecated use table "wallets", column meta.currency - */ - 'currencies' => [], + // long arithmetic + 'math' => ['scale' => 64], - /** - * Services are the main core of the library and sometimes they need to be improved. - * This configuration will help you to quickly customize the library. - */ + // service overload 'services' => [ + 'basket' => BasketService::class, + 'bookkeeper' => BookkeeperService::class, + 'consistency' => ConsistencyService::class, 'exchange' => ExchangeService::class, + 'atomic' => AtomicService::class, + 'math' => MathService::class, + 'purchase' => PurchaseService::class, + 'storage' => StorageService::class, + 'uuid' => UuidFactoryService::class, + ], + + // legacy-service overload + 'legacy' => [ 'common' => CommonService::class, 'wallet' => WalletService::class, + 'db' => DbService::class, 'lock' => LockService::class, + 'meta' => MetaService::class, ], - 'objects' => [ - 'bring' => Bring::class, - 'cart' => Cart::class, - 'emptyLock' => EmptyLock::class, - 'operation' => Operation::class, - ], - - /** - * Transaction model configuration. - */ 'transaction' => [ 'table' => 'transactions', 'model' => Transaction::class, - 'casts' => [ - 'amount' => 'string', - ], ], - /** - * Transfer model configuration. - */ 'transfer' => [ 'table' => 'transfers', 'model' => Transfer::class, - 'casts' => [ - 'fee' => 'string', - ], ], - /** - * Wallet model configuration. - */ 'wallet' => [ 'table' => 'wallets', 'model' => Wallet::class, - 'casts' => [ - 'balance' => 'string', - ], 'creating' => [], 'default' => [ 'name' => 'Default Wallet', diff --git a/database/2018_11_15_124230_create_wallets_table.php b/database/2018_11_15_124230_create_wallets_table.php index 821c25326..7658ffd24 100644 --- a/database/2018_11_15_124230_create_wallets_table.php +++ b/database/2018_11_15_124230_create_wallets_table.php @@ -17,6 +17,7 @@ public function up(): void $table->morphs('holder'); $table->string('name'); $table->string('slug')->index(); + $table->uuid('uuid')->unique(); $table->string('description')->nullable(); $table->json('meta')->nullable(); $table->decimal('balance', 64, 0)->default(0); diff --git a/database/2021_11_02_202021_update_wallets_uuid_table.php b/database/2021_11_02_202021_update_wallets_uuid_table.php new file mode 100644 index 000000000..0394446de --- /dev/null +++ b/database/2021_11_02_202021_update_wallets_uuid_table.php @@ -0,0 +1,50 @@ +table(), 'uuid')) { + return; + } + + // upgrade from 6.x + Schema::table($this->table(), function (Blueprint $table) { + $table->uuid('uuid') + ->after('slug') + ->nullable() + ->unique() + ; + }); + + Wallet::query()->chunk(10000, static function (Collection $wallets) { + $wallets->each(function (Wallet $wallet) { + $wallet->uuid = app(UuidInterface::class)->uuid4(); + $wallet->save(); + }); + }); + + Schema::table($this->table(), static function (Blueprint $table) { + $table->uuid('uuid')->change(); + }); + } + + public function down(): void + { + Schema::dropColumns($this->table(), ['uuid']); + } + + protected function table(): string + { + return (new Wallet())->getTable(); + } +} diff --git a/ecs.php b/ecs.php index b778fe10e..459b5f571 100644 --- a/ecs.php +++ b/ecs.php @@ -20,8 +20,11 @@ //$services->set(DeclareStrictTypesFixer::class); $parameters = $containerConfigurator->parameters(); + $parameters->set(Option::PARALLEL, true); $parameters->set(Option::PATHS, [ + __DIR__ . '/config', __DIR__ . '/database', + __DIR__ . '/resources/lang', __DIR__ . '/src', __DIR__ . '/tests', ]); diff --git a/src/Commands/RefreshBalance.php b/src/Commands/RefreshBalance.php deleted file mode 100644 index 61774cb98..000000000 --- a/src/Commands/RefreshBalance.php +++ /dev/null @@ -1,34 +0,0 @@ -each(static fn (Wallet $wallet) => $wallet->refreshBalance()); - } -} diff --git a/src/Interfaces/Customer.php b/src/Interfaces/Customer.php index 3da120096..bd489b5dd 100644 --- a/src/Interfaces/Customer.php +++ b/src/Interfaces/Customer.php @@ -9,26 +9,14 @@ interface Customer extends Wallet { - /** - * @throws - */ public function pay(Product $product, bool $force = false): Transfer; - /** - * @throws - */ public function safePay(Product $product, bool $force = false): ?Transfer; - /** - * @throws - */ public function forcePay(Product $product): Transfer; public function paid(Product $product, bool $gifts = false): ?Transfer; - /** - * @throws - */ public function refund(Product $product, bool $force = false, bool $gifts = false): bool; public function safeRefund(Product $product, bool $force = false, bool $gifts = false): bool; @@ -36,29 +24,20 @@ public function safeRefund(Product $product, bool $force = false, bool $gifts = public function forceRefund(Product $product, bool $gifts = false): bool; /** - * @throws - * * @return Transfer[] */ public function payCart(CartInterface $cart, bool $force = false): array; /** - * @throws - * * @return Transfer[] */ public function safePayCart(CartInterface $cart, bool $force = false): array; /** - * @throws - * * @return Transfer[] */ public function forcePayCart(CartInterface $cart): array; - /** - * @throws - */ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool; public function safeRefundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool; diff --git a/src/Interfaces/Discount.php b/src/Interfaces/Discount.php index e91053442..3c06dd597 100644 --- a/src/Interfaces/Discount.php +++ b/src/Interfaces/Discount.php @@ -4,7 +4,7 @@ namespace Bavix\Wallet\Interfaces; -interface Discount extends Product +interface Discount { /** * @return float|int diff --git a/src/Interfaces/Mathable.php b/src/Interfaces/Mathable.php deleted file mode 100644 index 634ac0abe..000000000 --- a/src/Interfaces/Mathable.php +++ /dev/null @@ -1,15 +0,0 @@ -uuidService = $uuidService; + } + + public function create( + Model $payable, + int $walletId, + string $type, + string $amount, + bool $confirmed, + ?array $meta + ): TransactionDto { + return new TransactionDto( + $this->uuidService->uuid4(), + $payable->getMorphClass(), + $payable->getKey(), + $walletId, + $type, + $amount, + $confirmed, + $meta + ); + } +} diff --git a/src/Internal/Assembler/TransferDtoAssembler.php b/src/Internal/Assembler/TransferDtoAssembler.php new file mode 100644 index 000000000..8be776414 --- /dev/null +++ b/src/Internal/Assembler/TransferDtoAssembler.php @@ -0,0 +1,42 @@ +uuidService = $uuidService; + } + + public function create( + int $depositId, + int $withdrawId, + string $status, + Model $fromModel, + Model $toModel, + int $discount, + int $fee + ): TransferDto { + return new TransferDto( + $this->uuidService->uuid4(), + $depositId, + $withdrawId, + $status, + $fromModel->getMorphClass(), + $fromModel->getKey(), + $toModel->getMorphClass(), + $toModel->getKey(), + $discount, + $fee + ); + } +} diff --git a/src/Internal/ConsistencyInterface.php b/src/Internal/ConsistencyInterface.php index 8b5dcf374..5b95f7270 100644 --- a/src/Internal/ConsistencyInterface.php +++ b/src/Internal/ConsistencyInterface.php @@ -8,6 +8,7 @@ use Bavix\Wallet\Exceptions\BalanceIsEmpty; use Bavix\Wallet\Exceptions\InsufficientFunds; use Bavix\Wallet\Interfaces\Wallet; +use Bavix\Wallet\Internal\Dto\TransferLazyDto; interface ConsistencyInterface { @@ -24,5 +25,13 @@ public function checkPositive($amount): void; * @throws BalanceIsEmpty * @throws InsufficientFunds */ - public function checkPotential(Wallet $wallet, $amount, bool $allowZero = false): void; + public function checkPotential(Wallet $object, $amount, bool $allowZero = false): void; + + /** + * @param TransferLazyDto[] $objects + * + * @throws BalanceIsEmpty + * @throws InsufficientFunds + */ + public function checkTransfer(array $objects): void; } diff --git a/src/Internal/Dto/AvailabilityDto.php b/src/Internal/Dto/AvailabilityDto.php index c4ff9be06..b8ec47804 100644 --- a/src/Internal/Dto/AvailabilityDto.php +++ b/src/Internal/Dto/AvailabilityDto.php @@ -6,6 +6,7 @@ use Bavix\Wallet\Interfaces\Customer; +/** @psalm-immutable */ class AvailabilityDto { private BasketDto $basketDto; diff --git a/src/Internal/Dto/BasketDto.php b/src/Internal/Dto/BasketDto.php index 014d3f286..d8340e3fc 100644 --- a/src/Internal/Dto/BasketDto.php +++ b/src/Internal/Dto/BasketDto.php @@ -6,14 +6,15 @@ use Countable; +/** @psalm-immutable */ class BasketDto implements Countable { - /** @var ItemDto[] */ + /** @var non-empty-array */ private array $items; private array $meta; - /** @param ItemDto[] $items */ + /** @param non-empty-array $items */ public function __construct(array $items, array $meta) { $this->items = $items; diff --git a/src/Internal/Dto/ItemDto.php b/src/Internal/Dto/ItemDto.php index 4c6c4e0fb..f5164fe4d 100644 --- a/src/Internal/Dto/ItemDto.php +++ b/src/Internal/Dto/ItemDto.php @@ -7,6 +7,7 @@ use Bavix\Wallet\Interfaces\Product; use Countable; +/** @psalm-immutable */ class ItemDto implements Countable { private Product $product; diff --git a/src/Internal/Dto/TransactionDto.php b/src/Internal/Dto/TransactionDto.php new file mode 100644 index 000000000..586b7c8d0 --- /dev/null +++ b/src/Internal/Dto/TransactionDto.php @@ -0,0 +1,101 @@ +uuid = $uuid; + $this->payableType = $payableType; + $this->payableId = $payableId; + $this->walletId = $walletId; + $this->type = $type; + $this->amount = $amount; + $this->confirmed = $confirmed; + $this->meta = $meta; + $this->createdAt = new DateTimeImmutable(); + $this->updatedAt = new DateTimeImmutable(); + } + + public function getUuid(): string + { + return $this->uuid; + } + + public function getPayableType(): string + { + return $this->payableType; + } + + public function getPayableId(): int + { + return $this->payableId; + } + + public function getWalletId(): int + { + return $this->walletId; + } + + public function getType(): string + { + return $this->type; + } + + public function getAmount(): string + { + return $this->amount; + } + + public function isConfirmed(): bool + { + return $this->confirmed; + } + + public function getMeta(): ?array + { + return $this->meta; + } + + public function getCreatedAt(): DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): DateTimeImmutable + { + return $this->updatedAt; + } +} diff --git a/src/Internal/Dto/TransferDto.php b/src/Internal/Dto/TransferDto.php new file mode 100644 index 000000000..621441c32 --- /dev/null +++ b/src/Internal/Dto/TransferDto.php @@ -0,0 +1,116 @@ +uuid = $uuid; + $this->depositId = $depositId; + $this->withdrawId = $withdrawId; + $this->status = $status; + $this->fromType = $fromType; + $this->fromId = $fromId; + $this->toType = $toType; + $this->toId = $toId; + $this->discount = $discount; + $this->fee = $fee; + $this->createdAt = new DateTimeImmutable(); + $this->updatedAt = new DateTimeImmutable(); + } + + public function getUuid(): string + { + return $this->uuid; + } + + public function getDepositId(): int + { + return $this->depositId; + } + + public function getWithdrawId(): int + { + return $this->withdrawId; + } + + public function getStatus(): string + { + return $this->status; + } + + public function getFromType(): string + { + return $this->fromType; + } + + public function getFromId(): int + { + return $this->fromId; + } + + public function getToType(): string + { + return $this->toType; + } + + public function getToId(): int + { + return $this->toId; + } + + public function getDiscount(): int + { + return $this->discount; + } + + public function getFee(): int + { + return $this->fee; + } + + public function getCreatedAt(): DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): DateTimeImmutable + { + return $this->updatedAt; + } +} diff --git a/src/Internal/Dto/TransferLazyDto.php b/src/Internal/Dto/TransferLazyDto.php new file mode 100644 index 000000000..8116e6163 --- /dev/null +++ b/src/Internal/Dto/TransferLazyDto.php @@ -0,0 +1,74 @@ +fromWallet = $fromWallet; + $this->toWallet = $toWallet; + $this->discount = $discount; + $this->fee = $fee; + + $this->withdrawDto = $withdrawDto; + $this->depositDto = $depositDto; + + $this->status = $status; + } + + public function getFromWallet(): Wallet + { + return $this->fromWallet; + } + + public function getToWallet(): Wallet + { + return $this->toWallet; + } + + public function getDiscount(): int + { + return $this->discount; + } + + public function getFee(): string + { + return $this->fee; + } + + public function getWithdrawDto(): TransactionDto + { + return $this->withdrawDto; + } + + public function getDepositDto(): TransactionDto + { + return $this->depositDto; + } + + public function getStatus(): string + { + return $this->status; + } +} diff --git a/src/Internal/Query/TransactionQuery.php b/src/Internal/Query/TransactionQuery.php new file mode 100644 index 000000000..7818b40a8 --- /dev/null +++ b/src/Internal/Query/TransactionQuery.php @@ -0,0 +1,24 @@ + */ + private array $uuids; + + /** @param non-empty-array $uuids */ + public function __construct(array $uuids) + { + $this->uuids = $uuids; + } + + /** @return non-empty-array */ + public function getUuids(): array + { + return $this->uuids; + } +} diff --git a/src/Internal/Query/TransferQuery.php b/src/Internal/Query/TransferQuery.php new file mode 100644 index 000000000..46b8ba47d --- /dev/null +++ b/src/Internal/Query/TransferQuery.php @@ -0,0 +1,24 @@ + */ + private array $uuids; + + /** @param non-empty-array $uuids */ + public function __construct(array $uuids) + { + $this->uuids = $uuids; + } + + /** @return non-empty-array */ + public function getUuids(): array + { + return $this->uuids; + } +} diff --git a/src/Internal/Repository/TransactionRepository.php b/src/Internal/Repository/TransactionRepository.php new file mode 100644 index 000000000..fa6919148 --- /dev/null +++ b/src/Internal/Repository/TransactionRepository.php @@ -0,0 +1,44 @@ +transformer = $transformer; + $this->transaction = $transaction; + } + + /** + * @param non-empty-array $objects + */ + public function insert(array $objects): void + { + $values = array_map(fn (TransactionDto $dto): array => $this->transformer->extract($dto), $objects); + $this->transaction->newQuery()->insert($values); + } + + /** @return Transaction[] */ + public function findBy(TransactionQuery $query): array + { + return $this->transaction->newQuery() + ->whereIn('uuid', $query->getUuids()) + ->get() + ->all() + ; + } +} diff --git a/src/Internal/Repository/TransferRepository.php b/src/Internal/Repository/TransferRepository.php new file mode 100644 index 000000000..e62e885ac --- /dev/null +++ b/src/Internal/Repository/TransferRepository.php @@ -0,0 +1,44 @@ +transformer = $transformer; + $this->transfer = $transfer; + } + + /** + * @param non-empty-array $objects + */ + public function insert(array $objects): void + { + $values = array_map(fn (TransferDto $dto): array => $this->transformer->extract($dto), $objects); + $this->transfer->newQuery()->insert($values); + } + + /** @return Transfer[] */ + public function findBy(TransferQuery $query): array + { + return $this->transfer->newQuery() + ->whereIn('uuid', $query->getUuids()) + ->get() + ->all() + ; + } +} diff --git a/src/Internal/Service/AssistantService.php b/src/Internal/Service/AssistantService.php new file mode 100644 index 000000000..a77c500b0 --- /dev/null +++ b/src/Internal/Service/AssistantService.php @@ -0,0 +1,52 @@ +mathService = $mathService; + } + + /** + * @param non-empty-array|non-empty-array $objects + * + * @return non-empty-array + */ + public function getUuids(array $objects): array + { + return array_map(static fn ($object): string => $object->getUuid(), $objects); + } + + /** + * @param non-empty-array $transactions + * + * @return array + */ + public function getSums(array $transactions): array + { + $amounts = []; + foreach ($transactions as $transaction) { + if ($transaction->isConfirmed()) { + $amounts[$transaction->getWalletId()] = $this->mathService->add( + $amounts[$transaction->getWalletId()] ?? 0, + $transaction->getAmount() + ); + } + } + + return array_filter( + $amounts, + fn (string $amount): bool => $this->mathService->compare($amount, 0) !== 0 + ); + } +} diff --git a/src/Internal/Service/AtmService.php b/src/Internal/Service/AtmService.php new file mode 100644 index 000000000..a076496d2 --- /dev/null +++ b/src/Internal/Service/AtmService.php @@ -0,0 +1,76 @@ +transactionRepository = $transactionRepository; + $this->transferRepository = $transferRepository; + $this->assistantService = $assistantService; + } + + /** + * @param non-empty-array $objects + * + * @return non-empty-array + */ + public function makeTransactions(array $objects): array + { + $this->transactionRepository->insert($objects); + $uuids = $this->assistantService->getUuids($objects); + $query = new TransactionQuery($uuids); + + $items = $this->transactionRepository->findBy($query); + assert(count($items) === count($uuids)); + + $results = []; + foreach ($items as $item) { + $results[$item->uuid] = $item; + } + + return $results; + } + + /** + * @param non-empty-array $objects + * + * @return non-empty-array + */ + public function makeTransfers(array $objects): array + { + $this->transferRepository->insert($objects); + $uuids = $this->assistantService->getUuids($objects); + $query = new TransferQuery($uuids); + + $items = $this->transferRepository->findBy($query); + assert(count($items) === count($uuids)); + + $results = []; + foreach ($items as $item) { + $results[$item->uuid] = $item; + } + + return $results; + } +} diff --git a/src/Internal/Service/CastService.php b/src/Internal/Service/CastService.php new file mode 100644 index 000000000..38747dfa5 --- /dev/null +++ b/src/Internal/Service/CastService.php @@ -0,0 +1,41 @@ +getModel($object); + if (!($wallet instanceof WalletModel)) { + $wallet = $wallet->getAttribute('wallet'); + assert($wallet instanceof WalletModel); + } + + if ($save) { + $wallet->exists or $wallet->save(); + } + + return $wallet; + } + + /** @param Model|Wallet $object */ + public function getHolder($object): Model + { + return $this->getModel($object instanceof WalletModel ? $object->holder : $object); + } + + public function getModel(object $object): Model + { + assert($object instanceof Model); + + return $object; + } +} diff --git a/src/Internal/Service/JsonService.php b/src/Internal/Service/JsonService.php new file mode 100644 index 000000000..fe7920bf0 --- /dev/null +++ b/src/Internal/Service/JsonService.php @@ -0,0 +1,16 @@ +transactionDtoAssembler = $transactionDtoAssembler; + $this->transferDtoAssembler = $transferDtoAssembler; + $this->consistencyService = $consistencyService; + $this->walletService = $walletService; + $this->castService = $castService; + $this->mathService = $mathService; + } + + public function deposit(Wallet $wallet, string $amount, ?array $meta, bool $confirmed = true): TransactionDto + { + $this->consistencyService->checkPositive($amount); + + return $this->transactionDtoAssembler->create( + $this->castService->getHolder($wallet), + $this->castService->getWallet($wallet)->getKey(), + Transaction::TYPE_DEPOSIT, + $amount, + $confirmed, + $meta + ); + } + + public function withdraw(Wallet $wallet, string $amount, ?array $meta, bool $confirmed = true): TransactionDto + { + $this->consistencyService->checkPositive($amount); + + return $this->transactionDtoAssembler->create( + $this->castService->getHolder($wallet), + $this->castService->getWallet($wallet)->getKey(), + Transaction::TYPE_WITHDRAW, + $this->mathService->negative($amount), + $confirmed, + $meta + ); + } + + public function transferLazy(Wallet $from, Wallet $to, string $status, $amount, ?array $meta = null): TransferLazyDto + { + $discount = $this->walletService->discount($from, $to); + $from = $this->walletService->getWallet($from); + $fee = (string) $this->walletService->fee($to, $amount); + + // replace max => mathService.max + $depositAmount = (string) max(0, $this->mathService->sub($amount, $discount)); + + $withdrawAmount = $this->mathService->add($depositAmount, $fee, $from->decimal_places); + + return new TransferLazyDto( + $from, + $to, + $discount, + $fee, + $this->withdraw($from, $withdrawAmount, $meta), + $this->deposit($to, $depositAmount, $meta), + $status + ); + } +} diff --git a/src/Internal/Transform/TransactionDtoTransformer.php b/src/Internal/Transform/TransactionDtoTransformer.php new file mode 100644 index 000000000..1ae3dab95 --- /dev/null +++ b/src/Internal/Transform/TransactionDtoTransformer.php @@ -0,0 +1,34 @@ +jsonService = $jsonService; + } + + public function extract(TransactionDto $dto): array + { + return [ + 'uuid' => $dto->getUuid(), + 'payable_type' => $dto->getPayableType(), + 'payable_id' => $dto->getPayableId(), + 'wallet_id' => $dto->getWalletId(), + 'type' => $dto->getType(), + 'amount' => $dto->getAmount(), + 'confirmed' => $dto->isConfirmed(), + 'meta' => $this->jsonService->encode($dto->getMeta()), + 'created_at' => $dto->getCreatedAt(), + 'updated_at' => $dto->getUpdatedAt(), + ]; + } +} diff --git a/src/Internal/Transform/TransferDtoTransformer.php b/src/Internal/Transform/TransferDtoTransformer.php new file mode 100644 index 000000000..f27622237 --- /dev/null +++ b/src/Internal/Transform/TransferDtoTransformer.php @@ -0,0 +1,28 @@ + $dto->getUuid(), + 'deposit_id' => $dto->getDepositId(), + 'withdraw_id' => $dto->getWithdrawId(), + 'status' => $dto->getStatus(), + 'from_type' => $dto->getFromType(), + 'from_id' => $dto->getFromId(), + 'to_type' => $dto->getToType(), + 'to_id' => $dto->getToId(), + 'discount' => $dto->getDiscount(), + 'fee' => $dto->getFee(), + 'created_at' => $dto->getCreatedAt(), + 'updated_at' => $dto->getUpdatedAt(), + ]; + } +} diff --git a/src/Models/Transaction.php b/src/Models/Transaction.php index 81ef732e0..6274bc068 100644 --- a/src/Models/Transaction.php +++ b/src/Models/Transaction.php @@ -4,7 +4,6 @@ namespace Bavix\Wallet\Models; -use function array_merge; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Internal\MathInterface; use Bavix\Wallet\Models\Wallet as WalletModel; @@ -57,17 +56,6 @@ class Transaction extends Model 'meta' => 'json', ]; - /** - * {@inheritdoc} - */ - public function getCasts(): array - { - return array_merge( - parent::getCasts(), - config('wallet.transaction.casts', []) - ); - } - public function getTable(): string { if (!$this->table) { diff --git a/src/Models/Transfer.php b/src/Models/Transfer.php index ca33ca021..f812e951e 100644 --- a/src/Models/Transfer.php +++ b/src/Models/Transfer.php @@ -4,7 +4,6 @@ namespace Bavix\Wallet\Models; -use function array_merge; use function config; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -58,17 +57,6 @@ class Transfer extends Model 'withdraw_id' => 'int', ]; - /** - * {@inheritdoc} - */ - public function getCasts(): array - { - return array_merge( - parent::getCasts(), - config('wallet.transfer.casts', []) - ); - } - public function getTable(): string { if (!$this->table) { diff --git a/src/Models/Wallet.php b/src/Models/Wallet.php index c91b40c87..3322e0e32 100644 --- a/src/Models/Wallet.php +++ b/src/Models/Wallet.php @@ -6,7 +6,6 @@ use function app; use function array_key_exists; -use function array_merge; use Bavix\Wallet\Interfaces\Confirmable; use Bavix\Wallet\Interfaces\Customer; use Bavix\Wallet\Interfaces\Exchangeable; @@ -28,6 +27,7 @@ * @property int $holder_id * @property string $name * @property string $slug + * @property string $uuid * @property string $description * @property array $meta * @property int $decimal_places @@ -49,6 +49,7 @@ class Wallet extends Model implements Customer, WalletFloat, Confirmable, Exchan 'holder_id', 'name', 'slug', + 'uuid', 'description', 'meta', 'balance', @@ -68,17 +69,6 @@ class Wallet extends Model implements Customer, WalletFloat, Confirmable, Exchan 'decimal_places' => 2, ]; - /** - * {@inheritdoc} - */ - public function getCasts(): array - { - return array_merge( - parent::getCasts(), - config('wallet.wallet.casts', []) - ); - } - public function getTable(): string { if (!$this->table) { @@ -156,10 +146,6 @@ public function holder(): MorphTo public function getCurrencyAttribute(): string { - $currencies = config('wallet.currencies', []); - - return $currencies[$this->slug] ?? - $this->meta['currency'] ?? - Str::upper($this->slug); + return $this->meta['currency'] ?? Str::upper($this->slug); } } diff --git a/src/Objects/Bring.php b/src/Objects/Bring.php deleted file mode 100644 index 4ea885725..000000000 --- a/src/Objects/Bring.php +++ /dev/null @@ -1,214 +0,0 @@ -uuid = $uuidService->uuid4(); - } - - public function getStatus(): string - { - return $this->status; - } - - /** - * @return static - */ - public function setStatus(string $status): self - { - $this->status = $status; - - return $this; - } - - /** - * @return static - */ - public function setDiscount(int $discount): self - { - $this->discount = app(MathInterface::class)->round($discount); - - return $this; - } - - public function getFrom(): Wallet - { - return $this->from; - } - - /** - * @return static - */ - public function setFrom(Wallet $from): self - { - $this->from = $from; - - return $this; - } - - public function getTo(): Wallet - { - return $this->to; - } - - /** - * @return static - */ - public function setTo(Wallet $to): self - { - $this->to = $to; - - return $this; - } - - public function getDeposit(): Transaction - { - return $this->deposit; - } - - /** - * @return static - */ - public function setDeposit(Transaction $deposit): self - { - $this->deposit = $deposit; - - return $this; - } - - public function getWithdraw(): Transaction - { - return $this->withdraw; - } - - /** - * @return static - */ - public function setWithdraw(Transaction $withdraw): self - { - $this->withdraw = $withdraw; - - return $this; - } - - public function getUuid(): string - { - return $this->uuid; - } - - public function getDiscount(): int - { - return $this->discount; - } - - public function getFee(): int - { - $fee = $this->fee; - if ($fee === null) { - $fee = app(MathInterface::class)->round( - app(MathInterface::class)->sub( - app(MathInterface::class)->abs($this->getWithdraw()->amount), - app(MathInterface::class)->abs($this->getDeposit()->amount) - ) - ); - } - - return $fee; - } - - /** - * @param int $fee - * - * @return Bring - */ - public function setFee($fee): self - { - $this->fee = app(MathInterface::class)->round($fee); - - return $this; - } - - /** - * @throws - */ - public function create(): Transfer - { - return app(Transfer::class) - ->create($this->toArray()) - ; - } - - /** - * @throws - */ - public function toArray(): array - { - return [ - 'status' => $this->getStatus(), - 'deposit_id' => $this->getDeposit()->getKey(), - 'withdraw_id' => $this->getWithdraw()->getKey(), - 'from_type' => $this->getFrom()->getMorphClass(), - 'from_id' => $this->getFrom()->getKey(), - 'to_type' => $this->getTo()->getMorphClass(), - 'to_id' => $this->getTo()->getKey(), - 'discount' => $this->getDiscount(), - 'fee' => $this->getFee(), - 'uuid' => $this->getUuid(), - ]; - } -} diff --git a/src/Objects/Cart.php b/src/Objects/Cart.php index 178672fa9..bea1e68a0 100644 --- a/src/Objects/Cart.php +++ b/src/Objects/Cart.php @@ -7,18 +7,14 @@ use function array_unique; use Bavix\Wallet\Interfaces\Customer; use Bavix\Wallet\Interfaces\Product; -use Bavix\Wallet\Internal\BasketInterface; use Bavix\Wallet\Internal\CartInterface; -use Bavix\Wallet\Internal\Dto\AvailabilityDto; use Bavix\Wallet\Internal\Dto\BasketDto; use Bavix\Wallet\Internal\Dto\ItemDto; use Bavix\Wallet\Internal\MathInterface; -use Bavix\Wallet\Internal\PurchaseInterface; -use Bavix\Wallet\Models\Transfer; +use Bavix\Wallet\Internal\Service\CastService; use function count; use Countable; use function get_class; -use Illuminate\Database\Eloquent\Model; class Cart implements Countable, CartInterface { @@ -27,20 +23,20 @@ class Cart implements Countable, CartInterface */ private array $items = []; - /** @var array */ + /** @var array */ private array $quantity = []; private array $meta = []; - private BasketInterface $basket; + private CastService $castService; private MathInterface $math; public function __construct( - BasketInterface $basket, + CastService $castService, MathInterface $math ) { - $this->basket = $basket; + $this->castService = $castService; $this->math = $math; } @@ -56,22 +52,15 @@ public function setMeta(array $meta): self return $this; } - /** - * @return static - */ public function addItem(Product $product, int $quantity = 1): self { $this->addQuantity($product, $quantity); - for ($i = 0; $i < $quantity; ++$i) { - $this->items[] = $product; - } + $products = array_fill(0, $quantity, $product); + $this->items = array_merge($this->items, $products); return $this; } - /** - * @return static - */ public function addItems(iterable $products): self { foreach ($products as $product) { @@ -97,30 +86,6 @@ public function getUniqueItems(): array return array_unique($this->items); } - /** - * The method returns the transfers already paid for the goods. - * - * @return Transfer[] - * - * @deprecated - * @see PurchaseInterface::already() - */ - public function alreadyBuy(Customer $customer, bool $gifts = false): array - { - return app(PurchaseInterface::class)->already($customer, $this->getBasketDto(), $gifts); - } - - /** - * @deprecated - * @see BasketInterface::availability() - * - * @codeCoverageIgnore - */ - public function canBuy(Customer $customer, bool $force = false): bool - { - return $this->basket->availability(new AvailabilityDto($customer, $this->getBasketDto(), $force)); - } - public function getTotal(Customer $customer): string { $result = 0; @@ -138,32 +103,26 @@ public function count(): int public function getQuantity(Product $product): int { - /** @var Model $product */ - $uniq = (string) (method_exists($product, 'getUniqueId') - ? $product->getUniqueId() - : $product->getKey()); + $model = $this->castService->getModel($product); - return (int) ($this->quantity[get_class($product).':'.$uniq] ?? 0); + return (int) ($this->quantity[get_class($product).':'.$model->getKey()] ?? 0); } public function getBasketDto(): BasketDto { - $items = []; - foreach ($this->getUniqueItems() as $product) { - $items[] = new ItemDto($product, $this->getQuantity($product)); - } + $items = array_map( + fn (Product $product): ItemDto => new ItemDto($product, $this->getQuantity($product)), + $this->getUniqueItems() + ); return new BasketDto($items, $this->getMeta()); } protected function addQuantity(Product $product, int $quantity): void { - /** @var Model|Product $product */ - $uniq = (string) (method_exists($product, 'getUniqueId') - ? $product->getUniqueId() - : $product->getKey()); + $model = $this->castService->getModel($product); - $this->quantity[get_class($product).':'.$uniq] = $this->math + $this->quantity[get_class($product).':'.$model->getKey()] = $this->math ->add($this->getQuantity($product), $quantity) ; } diff --git a/src/Objects/EmptyLock.php b/src/Objects/EmptyLock.php deleted file mode 100644 index f07aa893a..000000000 --- a/src/Objects/EmptyLock.php +++ /dev/null @@ -1,78 +0,0 @@ -ownerId) { - $this->ownerId = Str::random(); - } - - return $this->ownerId; - } - - /** - * Releases this lock in disregard of ownership. - * - * @codeCoverageIgnore - */ - public function forceRelease(): void - { - // force lock release - } -} diff --git a/src/Objects/Operation.php b/src/Objects/Operation.php deleted file mode 100644 index 84bccb160..000000000 --- a/src/Objects/Operation.php +++ /dev/null @@ -1,163 +0,0 @@ -uuid = $uuidService->uuid4(); - } - - public function getType(): string - { - return $this->type; - } - - public function getUuid(): string - { - return $this->uuid; - } - - /** - * @return float|int - */ - public function getAmount() - { - return $this->amount; - } - - public function getMeta(): ?array - { - return $this->meta; - } - - public function isConfirmed(): bool - { - return $this->confirmed; - } - - /** - * @return static - */ - public function setType(string $type): self - { - $this->type = $type; - - return $this; - } - - /** - * @param int|string $amount - * - * @return static - */ - public function setAmount($amount): self - { - $this->amount = app(MathInterface::class)->round($amount); - - return $this; - } - - /** - * @return static - */ - public function setMeta(?array $meta): self - { - $this->meta = $meta; - - return $this; - } - - /** - * @return static - */ - public function setConfirmed(bool $confirmed): self - { - $this->confirmed = $confirmed; - - return $this; - } - - public function getWallet(): Wallet - { - return $this->wallet; - } - - /** - * @return static - */ - public function setWallet(Wallet $wallet): self - { - $this->wallet = $wallet; - - return $this; - } - - public function create(): Transaction - { - /** - * @var Transaction $model - */ - return $this->getWallet() - ->transactions() - ->create($this->toArray()) - ; - } - - /** - * @throws - */ - public function toArray(): array - { - return [ - 'type' => $this->getType(), - 'wallet_id' => $this->getWallet()->getKey(), - 'uuid' => $this->getUuid(), - 'confirmed' => $this->isConfirmed(), - 'amount' => $this->getAmount(), - 'meta' => $this->getMeta(), - ]; - } -} diff --git a/src/Services/AtomicService.php b/src/Services/AtomicService.php index 628bd9e18..28fae0271 100644 --- a/src/Services/AtomicService.php +++ b/src/Services/AtomicService.php @@ -23,7 +23,7 @@ public function __construct( ) { $this->seconds = (int) $config->get('wallet.lock.seconds', 1); $this->cache = $cacheManager->driver( - $config->get('wallet.lock.cache', 'array') + $config->get('wallet.lock.driver', 'array') ); } diff --git a/src/Services/BookkeeperService.php b/src/Services/BookkeeperService.php index cabf58e24..f8382bc69 100644 --- a/src/Services/BookkeeperService.php +++ b/src/Services/BookkeeperService.php @@ -36,10 +36,7 @@ public function amount(Wallet $wallet): string } catch (RecordNotFoundException $recordNotFoundException) { $this->lock->block( $this->getKey($wallet), - fn () => $this->storage->sync( - $this->getKey($wallet), - $wallet->getOriginalBalance(), - ), + fn () => $this->sync($wallet, $wallet->getOriginalBalance()), ); } @@ -64,6 +61,6 @@ public function increase(Wallet $wallet, $value): string private function getKey(Wallet $wallet): string { - return __CLASS__.'::'.$wallet->getKey(); + return __CLASS__.'::'.$wallet->uuid; } } diff --git a/src/Services/CommonService.php b/src/Services/CommonService.php index 7a372512f..1efdabfd8 100644 --- a/src/Services/CommonService.php +++ b/src/Services/CommonService.php @@ -2,66 +2,54 @@ namespace Bavix\Wallet\Services; -use function app; use Bavix\Wallet\Exceptions\AmountInvalid; -use Bavix\Wallet\Exceptions\BalanceIsEmpty; -use Bavix\Wallet\Exceptions\InsufficientFunds; -use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Interfaces\Wallet; -use Bavix\Wallet\Internal\ConsistencyInterface; -use Bavix\Wallet\Internal\MathInterface; +use Bavix\Wallet\Internal\Assembler\TransferDtoAssembler; +use Bavix\Wallet\Internal\BookkeeperInterface; +use Bavix\Wallet\Internal\Dto\TransactionDto; +use Bavix\Wallet\Internal\Dto\TransferLazyDto; +use Bavix\Wallet\Internal\Service\AssistantService; +use Bavix\Wallet\Internal\Service\AtmService; +use Bavix\Wallet\Internal\Service\CastService; +use Bavix\Wallet\Internal\Service\PrepareService; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Models\Wallet as WalletModel; -use Bavix\Wallet\Objects\Bring; -use Bavix\Wallet\Objects\Operation; use function compact; -use function max; use Throwable; class CommonService { private DbService $dbService; private LockService $lockService; - private MathInterface $math; + private AtmService $atmService; + private CastService $castService; private WalletService $walletService; - private Storable $store; - private ConsistencyInterface $consistency; + private AssistantService $assistantService; + private PrepareService $prepareService; + private BookkeeperInterface $bookkeeper; + private TransferDtoAssembler $transferDtoAssembler; public function __construct( DbService $dbService, LockService $lockService, - MathInterface $math, + CastService $castService, WalletService $walletService, - Storable $store, - ConsistencyInterface $consistency + BookkeeperInterface $bookkeeper, + AssistantService $satisfyService, + PrepareService $prepareService, + TransferDtoAssembler $transferDtoAssembler, + AtmService $atmService ) { $this->dbService = $dbService; $this->lockService = $lockService; - $this->math = $math; + $this->atmService = $atmService; + $this->castService = $castService; $this->walletService = $walletService; - $this->store = $store; - $this->consistency = $consistency; - } - - /** - * @param int|string $amount - * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws Throwable - */ - public function transfer(Wallet $from, Wallet $to, $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer - { - return $this->lockService->lock($this, __FUNCTION__, function () use ($from, $to, $amount, $meta, $status) { - $discount = $this->walletService->discount($from, $to); - $newAmount = max(0, $this->math->sub($amount, $discount)); - $fee = $this->walletService->fee($to, $newAmount); - $this->consistency->checkPotential($from, $this->math->add($newAmount, $fee)); - - return $this->forceTransfer($from, $to, $amount, $meta, $status); - }); + $this->bookkeeper = $bookkeeper; + $this->assistantService = $satisfyService; + $this->prepareService = $prepareService; + $this->transferDtoAssembler = $transferDtoAssembler; } /** @@ -72,194 +60,133 @@ public function transfer(Wallet $from, Wallet $to, $amount, ?array $meta = null, */ public function forceTransfer(Wallet $from, Wallet $to, $amount, ?array $meta = null, string $status = Transfer::STATUS_TRANSFER): Transfer { - return $this->lockService->lock($this, __FUNCTION__, function () use ($from, $to, $amount, $meta, $status) { - $from = $this->walletService->getWallet($from); - $discount = $this->walletService->discount($from, $to); - $fee = $this->walletService->fee($to, $amount); - - $amount = max(0, $this->math->sub($amount, $discount)); - $withdraw = $this->forceWithdraw($from, $this->math->add($amount, $fee, $from->decimal_places), $meta); - $deposit = $this->deposit($to, $amount, $meta); - - $transfers = $this->multiBrings([ - app(Bring::class) - ->setStatus($status) - ->setDeposit($deposit) - ->setWithdraw($withdraw) - ->setDiscount($discount) - ->setFrom($from) - ->setTo($to), - ]); + $transferLazyDto = $this->prepareService->transferLazy($from, $to, $status, $amount, $meta); + $transfers = $this->applyTransfers([$transferLazyDto]); - return current($transfers); - }); + return current($transfers); } /** - * @param int|string $amount + * @param TransferLazyDto[] $objects * - * @throws AmountInvalid + * @return Transfer[] */ - public function forceWithdraw(Wallet $wallet, $amount, ?array $meta, bool $confirmed = true): Transaction + public function applyTransfers(array $objects): array { - return $this->lockService->lock($this, __FUNCTION__, function () use ($wallet, $amount, $meta, $confirmed) { - $this->consistency->checkPositive($amount); - - /** @var WalletModel $wallet */ - $wallet = $this->walletService->getWallet($wallet); + return $this->dbService->transaction(function () use ($objects): array { + $wallets = []; + $operations = []; + foreach ($objects as $object) { + $fromWallet = $this->castService->getWallet($object->getFromWallet()); + $wallets[$fromWallet->getKey()] = $fromWallet; + + $toWallet = $this->castService->getWallet($object->getToWallet()); + $wallets[$toWallet->getKey()] = $toWallet; + + $operations[] = $object->getWithdrawDto(); + $operations[] = $object->getDepositDto(); + } - $transactions = $this->multiOperation($wallet, [ - app(Operation::class) - ->setType(Transaction::TYPE_WITHDRAW) - ->setConfirmed($confirmed) - ->setAmount($this->math->negative($amount)) - ->setMeta($meta), - ]); + $transactions = $this->applyTransactions($wallets, $operations); + + $transfers = []; + foreach ($objects as $object) { + $withdraw = $transactions[$object->getWithdrawDto()->getUuid()] ?? null; + assert($withdraw !== null); + + $deposit = $transactions[$object->getDepositDto()->getUuid()] ?? null; + assert($deposit !== null); + + $transfers[] = $this->transferDtoAssembler->create( + $deposit->getKey(), + $withdraw->getKey(), + $object->getStatus(), + $this->castService->getModel($object->getFromWallet()), + $this->castService->getModel($object->getToWallet()), + $object->getDiscount(), + $object->getFee() + ); + } - return current($transactions); + return $this->atmService->makeTransfers($transfers); }); } /** * @param int|string $amount * - * @throws AmountInvalid + * @deprecated */ - public function deposit(Wallet $wallet, $amount, ?array $meta, bool $confirmed = true): Transaction + public function addBalance(Wallet $wallet, $amount): bool { - return $this->lockService->lock($this, __FUNCTION__, function () use ($wallet, $amount, $meta, $confirmed) { - $this->consistency->checkPositive($amount); - + return $this->lockService->lock($this, __FUNCTION__, function () use ($wallet, $amount) { /** @var WalletModel $wallet */ - $wallet = $this->walletService->getWallet($wallet); + $walletObject = $this->walletService->getWallet($wallet); + $balance = $this->bookkeeper->increase($walletObject, $amount); + $result = 0; - $transactions = $this->multiOperation($wallet, [ - app(Operation::class) - ->setType(Transaction::TYPE_DEPOSIT) - ->setConfirmed($confirmed) - ->setAmount($amount) - ->setMeta($meta), - ]); - - return current($transactions); - }); - } - - /** - * @param int|string $amount - * - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * - * @deprecated - * @see ConsistencyInterface::potential() - * - * @codeCoverageIgnore - */ - public function verifyWithdraw(Wallet $wallet, $amount, bool $allowZero = false): void - { - $this->consistency->checkPotential($wallet, $amount, $allowZero); - } + try { + $result = $walletObject->newQuery() + ->whereKey($walletObject->getKey()) + ->update(compact('balance')) + ; - /** - * Create Operation without DB::transaction. - * - * @param Operation[] $operations - * - * @deprecated - */ - public function multiOperation(Wallet $self, array $operations): array - { - return $this->lockService->lock($this, __FUNCTION__, function () use ($self, $operations) { - $amount = 0; - $objects = []; - foreach ($operations as $operation) { - if ($operation->isConfirmed()) { - $amount = $this->math->add($amount, $operation->getAmount()); + $walletObject->fill(compact('balance'))->syncOriginalAttribute('balance'); + } finally { + if ($result === 0) { + $this->bookkeeper->missing($walletObject); } - - $objects[] = $operation - ->setWallet($self) - ->create() - ; } - $this->addBalance($self, $amount); - - return $objects; + return (bool) $result; }); } /** - * Create Bring with DB::transaction. - * - * @param Bring[] $brings - * - * @throws - * - * @deprecated + * @param float|int|string $amount */ - public function assemble(array $brings): array + public function makeTransaction(Wallet $wallet, string $type, $amount, ?array $meta, bool $confirmed = true): Transaction { - return $this->lockService->lock($this, __FUNCTION__, function () use ($brings) { - $self = $this; + assert(in_array($type, [Transaction::TYPE_DEPOSIT, Transaction::TYPE_WITHDRAW], true)); - return $this->dbService->transaction(static function () use ($self, $brings) { - return $self->multiBrings($brings); - }); - }); - } + if ($type === Transaction::TYPE_DEPOSIT) { + $dto = $this->prepareService->deposit($wallet, (string) $amount, $meta, $confirmed); + } else { + $dto = $this->prepareService->withdraw($wallet, (string) $amount, $meta, $confirmed); + } - /** - * Create Bring without DB::transaction. - * - * @deprecated - */ - public function multiBrings(array $brings): array - { - return $this->lockService->lock($this, __FUNCTION__, function () use ($brings) { - $objects = []; - foreach ($brings as $bring) { - $objects[] = $bring->create(); - } + $transactions = $this->applyTransactions( + [$dto->getWalletId() => $wallet], + [$dto], + ); - return $objects; - }); + return current($transactions); } /** - * @param int|string $amount - * - * @throws + * @param non-empty-array $wallets + * @param non-empty-array $objects * - * @deprecated + * @return non-empty-array */ - public function addBalance(Wallet $wallet, $amount): bool + public function applyTransactions(array $wallets, array $objects): array { - return $this->lockService->lock($this, __FUNCTION__, function () use ($wallet, $amount) { - /** @var WalletModel $wallet */ - $balance = $this->store->incBalance($wallet, $amount); + $transactions = $this->atmService->makeTransactions($objects); // q1 + $totals = $this->assistantService->getSums($objects); - try { - $result = $wallet->newQuery() - ->whereKey($wallet->getKey()) - ->update(compact('balance')) - ; - } catch (Throwable $throwable) { - $this->store->setBalance($wallet, $wallet->getAvailableBalance()); + foreach ($totals as $walletId => $total) { + $wallet = $wallets[$walletId] ?? null; + assert($wallet !== null); - throw $throwable; - } + $object = $this->walletService->getWallet($wallet); + assert((int) $object->getKey() === $walletId); - if ($result) { - $wallet->fill(compact('balance')) - ->syncOriginalAttributes('balance') - ; - } else { - $this->store->setBalance($wallet, $wallet->getAvailableBalance()); - } + $balance = $this->bookkeeper->increase($object, $total); - return $result; - }); + $object->newQuery()->whereKey($object->getKey())->update(compact('balance')); // ?qN + $object->fill(compact('balance'))->syncOriginalAttribute('balance'); + } + + return $transactions; } } diff --git a/src/Services/ConsistencyService.php b/src/Services/ConsistencyService.php index d68bb8f7a..ad3c9520a 100644 --- a/src/Services/ConsistencyService.php +++ b/src/Services/ConsistencyService.php @@ -9,16 +9,24 @@ use Bavix\Wallet\Exceptions\InsufficientFunds; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Internal\ConsistencyInterface; +use Bavix\Wallet\Internal\Dto\TransferLazyDto; use Bavix\Wallet\Internal\MathInterface; -use Bavix\Wallet\Traits\HasWallet; +use Bavix\Wallet\Internal\Service\CastService; class ConsistencyService implements ConsistencyInterface { - private MathInterface $math; + private CastService $castService; + private MathInterface $mathService; + private WalletService $walletService; - public function __construct(MathInterface $math) - { - $this->math = $math; + public function __construct( + WalletService $walletService, + MathInterface $mathService, + CastService $castService + ) { + $this->walletService = $walletService; + $this->mathService = $mathService; + $this->castService = $castService; } /** @@ -28,7 +36,7 @@ public function __construct(MathInterface $math) */ public function checkPositive($amount): void { - if ($this->math->compare($amount, 0) === -1) { + if ($this->mathService->compare($amount, 0) === -1) { throw new AmountInvalid(trans('wallet::errors.price_positive')); } } @@ -39,12 +47,11 @@ public function checkPositive($amount): void * @throws BalanceIsEmpty * @throws InsufficientFunds */ - public function checkPotential(Wallet $wallet, $amount, bool $allowZero = false): void + public function checkPotential(Wallet $object, $amount, bool $allowZero = false): void { - /** - * @var HasWallet $wallet - */ - if ($amount && !$wallet->balance) { + $wallet = $this->castService->getWallet($object, false); + + if (($this->mathService->compare($amount, 0) !== 0) && !$wallet->getBalanceAttribute()) { throw new BalanceIsEmpty(trans('wallet::errors.wallet_empty')); } @@ -52,4 +59,30 @@ public function checkPotential(Wallet $wallet, $amount, bool $allowZero = false) throw new InsufficientFunds(trans('wallet::errors.insufficient_funds')); } } + + /** + * @param TransferLazyDto[] $objects + * + * @throws BalanceIsEmpty + * @throws InsufficientFunds + */ + public function checkTransfer(array $objects): void + { + $wallets = []; + $totalAmount = []; + foreach ($objects as $object) { + $withdrawDto = $object->getWithdrawDto(); + $wallet = $this->castService->getWallet($object->getFromWallet(), false); + $wallets[] = $wallet; + + $totalAmount[$wallet->uuid] = $this->mathService->add( + ($totalAmount[$wallet->uuid] ?? 0), + $this->mathService->negative($withdrawDto->getAmount()) + ); + } + + foreach ($wallets as $wallet) { + $this->checkPotential($wallet, $totalAmount[$wallet->uuid] ?? -1); + } + } } diff --git a/src/Services/ExchangeService.php b/src/Services/ExchangeService.php index b1ac142fe..93a20300c 100644 --- a/src/Services/ExchangeService.php +++ b/src/Services/ExchangeService.php @@ -4,32 +4,12 @@ namespace Bavix\Wallet\Services; -use Bavix\Wallet\Interfaces\Rateable; -use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Internal\ExchangeInterface; -/** - * @deprecated - * @see ExchangeInterface - */ -class ExchangeService +class ExchangeService implements ExchangeInterface { - private Rateable $rate; - - public function __construct(Rateable $rate) - { - $this->rate = $rate; - } - - /** - * @return float|int - */ - public function rate(Wallet $from, Wallet $to) + public function convertTo(string $fromCurrency, string $toCurrency, $amount): string { - return $this->rate - ->withAmount(1) - ->withCurrency($from) - ->convertTo($to) - ; + return (string) $amount; } } diff --git a/src/Services/LockService.php b/src/Services/LockService.php index 4fdba64df..2616f73ea 100644 --- a/src/Services/LockService.php +++ b/src/Services/LockService.php @@ -5,6 +5,8 @@ namespace Bavix\Wallet\Services; use Bavix\Wallet\Internal\UuidInterface; +use Closure; +use function get_class; use Illuminate\Database\Eloquent\Model; /** @@ -14,49 +16,25 @@ class LockService { private string $ikey; - private AtomicService $atomicService; - /** - * LockService constructor. - */ public function __construct(UuidInterface $uuidService, AtomicService $atomicService) { $this->ikey = $uuidService->uuid4(); $this->atomicService = $atomicService; } - /** - * @param object $self - * - * @return mixed - */ - public function lock($self, string $name, \Closure $closure) + public function lock(object $self, string $name, Closure $closure) { - $class = \get_class($self); + $class = get_class($self); $uniqId = $class.$this->ikey; if ($self instanceof Model) { $uniqId = $class.$self->getKey(); } return $this->atomicService->block( - "{$name}.{$uniqId}", - $this->bindTo($self, $closure) + "legacy_{$name}.{$uniqId}", + $closure->bindTo($self) ); } - - /** - * @param object $self - * - * @throws - */ - protected function bindTo($self, \Closure $closure): \Closure - { - $reflect = new \ReflectionFunction($closure); - if (strpos((string) $reflect, 'static') === false) { - return $closure->bindTo($self); - } - - return $closure; - } } diff --git a/src/Services/MathService.php b/src/Services/MathService.php index 7f515d34b..8671b7d26 100644 --- a/src/Services/MathService.php +++ b/src/Services/MathService.php @@ -67,7 +67,7 @@ public function mul($first, $second, ?int $scale = null): string public function pow($first, $second, ?int $scale = null): string { return (string) BigDecimal::of($first) - ->power($second) + ->power((int) $second) ->toScale($scale ?? $this->scale, RoundingMode::DOWN) ; } diff --git a/src/Services/PurchaseService.php b/src/Services/PurchaseService.php index 1d0a8733e..4874961ec 100644 --- a/src/Services/PurchaseService.php +++ b/src/Services/PurchaseService.php @@ -8,7 +8,6 @@ use Bavix\Wallet\Internal\Dto\BasketDto; use Bavix\Wallet\Internal\PurchaseInterface; use Bavix\Wallet\Models\Transfer; -use Bavix\Wallet\Traits\HasWallet; use Illuminate\Database\Eloquent\Model; class PurchaseService implements PurchaseInterface @@ -19,8 +18,6 @@ public function already(Customer $customer, BasketDto $basketDto, bool $gifts = ? [Transfer::STATUS_PAID, Transfer::STATUS_GIFT] : [Transfer::STATUS_PAID]; - /** @var HasWallet $customer */ - /** @var Transfer $query */ $arrays = []; $query = $customer->transfers(); foreach ($basketDto->items() as $itemDto) { diff --git a/src/Services/StorageService.php b/src/Services/StorageService.php index efd26af3c..2b92a4a07 100644 --- a/src/Services/StorageService.php +++ b/src/Services/StorageService.php @@ -35,12 +35,12 @@ public function __construct( public function flush(): bool { - return $this->cache->flush(); + return $this->cache->clear(); } public function missing(string $key): bool { - return $this->cache->delete($key); + return $this->cache->forget($key); } public function get(string $key): string @@ -61,7 +61,7 @@ public function sync(string $key, $value): bool public function increase(string $key, $value): string { return $this->lock->block( - $key, + $key.'::increase', function () use ($key, $value): string { $result = $this->math->add($this->get($key), $value); $this->sync($key, $result); diff --git a/src/Services/WalletService.php b/src/Services/WalletService.php index 65507c98a..9e335e95f 100644 --- a/src/Services/WalletService.php +++ b/src/Services/WalletService.php @@ -2,39 +2,37 @@ namespace Bavix\Wallet\Services; -use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Interfaces\Customer; use Bavix\Wallet\Interfaces\Discount; use Bavix\Wallet\Interfaces\MinimalTaxable; -use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Interfaces\Taxable; use Bavix\Wallet\Interfaces\Wallet; -use Bavix\Wallet\Internal\ConsistencyInterface; +use Bavix\Wallet\Internal\BookkeeperInterface; use Bavix\Wallet\Internal\MathInterface; +use Bavix\Wallet\Internal\Service\CastService; use Bavix\Wallet\Models\Wallet as WalletModel; -use Bavix\Wallet\Traits\HasWallet; use Throwable; class WalletService { - private ConsistencyInterface $consistency; private DbService $dbService; private MathInterface $math; + private CastService $castService; private LockService $lockService; - private Storable $store; + private BookkeeperInterface $bookkeeper; public function __construct( DbService $dbService, MathInterface $math, + CastService $castService, LockService $lockService, - Storable $store, - ConsistencyInterface $consistency + BookkeeperInterface $bookkeeper ) { $this->dbService = $dbService; $this->math = $math; + $this->castService = $castService; $this->lockService = $lockService; - $this->store = $store; - $this->consistency = $consistency; + $this->bookkeeper = $bookkeeper; } /** @@ -50,34 +48,12 @@ public function discount(Wallet $customer, Wallet $product): int return 0; } - /** - * @deprecated - * @see Wallet::$decimal_places - * - * @codeCoverageIgnore - */ - public function decimalPlacesValue(Wallet $object): int - { - return $this->getWallet($object)->decimal_places ?: 2; - } - - /** - * @deprecated - * @see MathInterface::powTen() - * - * @codeCoverageIgnore - */ - public function decimalPlaces(Wallet $object): string - { - return $this->math->powTen($this->getWallet($object)->decimal_places); - } - /** * Consider the fee that the system will receive. * * @param int|string $amount * - * @return float|int + * @return float|int|string */ public function fee(Wallet $wallet, $amount) { @@ -107,38 +83,9 @@ public function fee(Wallet $wallet, $amount) return $fee; } - /** - * The amount of checks for errors. - * - * @param int|string $amount - * - * @throws AmountInvalid - * - * @deprecated - * @see ConsistencyInterface::checkPositive() - * - * @codeCoverageIgnore - */ - public function checkAmount($amount): void - { - $this->consistency->checkPositive($amount); - } - public function getWallet(Wallet $object, bool $autoSave = true): WalletModel { - /** @var WalletModel $wallet */ - $wallet = $object; - - if (!($object instanceof WalletModel)) { - /** @var HasWallet $object */ - $wallet = $object->wallet; - } - - if ($autoSave) { - $wallet->exists or $wallet->save(); - } - - return $wallet; + return $this->castService->getWallet($object, $autoSave); } /** @@ -148,12 +95,11 @@ public function getWallet(Wallet $object, bool $autoSave = true): WalletModel public function refresh(WalletModel $wallet): bool { return $this->lockService->lock($this, __FUNCTION__, function () use ($wallet) { - $this->store->getBalance($wallet); $whatIs = $wallet->balance; $balance = $wallet->getAvailableBalance(); - $wallet->balance = $balance; + $wallet->balance = (string) $balance; - return $this->store->setBalance($wallet, $balance) && + return $this->bookkeeper->sync($this->getWallet($wallet), $balance) && (!$this->math->compare($whatIs, $balance) || $wallet->save()); }); } @@ -167,8 +113,8 @@ public function refresh(WalletModel $wallet): bool public function adjustment(WalletModel $wallet, ?array $meta = null): void { $this->dbService->transaction(function () use ($wallet, $meta) { - $this->store->getBalance($wallet); - $adjustmentBalance = $wallet->balance; + $walletObject = $this->getWallet($wallet); + $adjustmentBalance = $this->bookkeeper->amount($walletObject); $wallet->refreshBalance(); $difference = $this->math->sub($wallet->balance, $adjustmentBalance); diff --git a/src/Simple/BrickMath.php b/src/Simple/BrickMath.php deleted file mode 100644 index 01af29cf9..000000000 --- a/src/Simple/BrickMath.php +++ /dev/null @@ -1,16 +0,0 @@ -exchange = $exchange; - } - - /** - * {@inheritdoc} - */ - public function withAmount($amount): Rateable - { - $this->amount = $amount; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function withCurrency(Wallet $wallet): Rateable - { - $this->withCurrency = $wallet; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function convertTo(Wallet $wallet) - { - /** @var \Bavix\Wallet\Models\Wallet $wallet */ - return $this->exchange->convertTo( - $this->withCurrency->currency, - $wallet->currency, - $this->amount - ); - } -} diff --git a/src/Simple/Store.php b/src/Simple/Store.php deleted file mode 100644 index 355b5d680..000000000 --- a/src/Simple/Store.php +++ /dev/null @@ -1,53 +0,0 @@ -getWallet($object); - - return app(BookkeeperInterface::class)->amount($wallet); - } - - /** - * {@inheritdoc} - */ - public function incBalance($object, $amount): string - { - $wallet = app(WalletService::class)->getWallet($object); - - return app(BookkeeperInterface::class)->increase($wallet, $amount); - } - - /** - * {@inheritdoc} - */ - public function setBalance($object, $amount): bool - { - $wallet = app(WalletService::class)->getWallet($object); - - return app(BookkeeperInterface::class)->sync($wallet, $amount); - } - - /** - * {@inheritdoc} - */ - public function fresh(): bool - { - return app(StorageInterface::class)->flush(); - } -} diff --git a/src/Traits/CanConfirm.php b/src/Traits/CanConfirm.php index b52a584af..008009217 100644 --- a/src/Traits/CanConfirm.php +++ b/src/Traits/CanConfirm.php @@ -7,8 +7,6 @@ use Bavix\Wallet\Exceptions\InsufficientFunds; use Bavix\Wallet\Exceptions\UnconfirmedInvalid; use Bavix\Wallet\Exceptions\WalletOwnerInvalid; -use Bavix\Wallet\Interfaces\Confirmable; -use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Internal\ConsistencyInterface; use Bavix\Wallet\Internal\MathInterface; use Bavix\Wallet\Models\Transaction; @@ -27,25 +25,15 @@ trait CanConfirm */ public function confirm(Transaction $transaction): bool { - return app(LockService::class)->lock($this, __FUNCTION__, function () use ($transaction) { - /** @var Confirmable|Wallet $self */ - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $transaction) { - $wallet = app(WalletService::class)->getWallet($self); - if (!$wallet->refreshBalance()) { - return false; - } - - if ($transaction->type === Transaction::TYPE_WITHDRAW) { - app(ConsistencyInterface::class)->checkPotential( - $wallet, - app(MathInterface::class)->abs($transaction->amount) - ); - } - - return $self->forceConfirm($transaction); - }); + return app(DbService::class)->transaction(function () use ($transaction) { + if ($transaction->type === Transaction::TYPE_WITHDRAW) { + app(ConsistencyInterface::class)->checkPotential( + app(WalletService::class)->getWallet($this), + app(MathInterface::class)->abs($transaction->amount) + ); + } + + return $this->forceConfirm($transaction); }); } @@ -66,19 +54,12 @@ public function safeConfirm(Transaction $transaction): bool public function resetConfirm(Transaction $transaction): bool { return app(LockService::class)->lock($this, __FUNCTION__, function () use ($transaction) { - /** @var Wallet $self */ - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $transaction) { - $wallet = app(WalletService::class)->getWallet($self); - if (!$wallet->refreshBalance()) { - return false; - } - + return app(DbService::class)->transaction(function () use ($transaction) { if (!$transaction->confirmed) { throw new UnconfirmedInvalid(trans('wallet::errors.unconfirmed_invalid')); } + $wallet = app(WalletService::class)->getWallet($this); $mathService = app(MathInterface::class); $negativeAmount = $mathService->negative($transaction->amount); @@ -107,18 +88,12 @@ public function safeResetConfirm(Transaction $transaction): bool public function forceConfirm(Transaction $transaction): bool { return app(LockService::class)->lock($this, __FUNCTION__, function () use ($transaction) { - /** @var Wallet $self */ - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $transaction) { - $wallet = app(WalletService::class) - ->getWallet($self) - ; - + return app(DbService::class)->transaction(function () use ($transaction) { if ($transaction->confirmed) { throw new ConfirmedInvalid(trans('wallet::errors.confirmed_invalid')); } + $wallet = app(WalletService::class)->getWallet($this); if ($wallet->getKey() !== $transaction->wallet_id) { throw new WalletOwnerInvalid(trans('wallet::errors.owner_invalid')); } diff --git a/src/Traits/CanExchange.php b/src/Traits/CanExchange.php index a820687ce..2dfafe7e2 100644 --- a/src/Traits/CanExchange.php +++ b/src/Traits/CanExchange.php @@ -4,12 +4,14 @@ use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Internal\ConsistencyInterface; +use Bavix\Wallet\Internal\Dto\TransferLazyDto; +use Bavix\Wallet\Internal\ExchangeInterface; use Bavix\Wallet\Internal\MathInterface; +use Bavix\Wallet\Internal\Service\CastService; +use Bavix\Wallet\Internal\Service\PrepareService; use Bavix\Wallet\Models\Transfer; -use Bavix\Wallet\Objects\Bring; use Bavix\Wallet\Services\CommonService; use Bavix\Wallet\Services\DbService; -use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Services\LockService; use Bavix\Wallet\Services\WalletService; @@ -44,33 +46,32 @@ public function safeExchange(Wallet $to, $amount, ?array $meta = null): ?Transfe */ 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__, static function () use ($from, $to, $amount, $meta) { - return app(DbService::class)->transaction(static function () use ($from, $to, $amount, $meta) { - $math = app(MathInterface::class); - $rate = app(ExchangeService::class)->rate($from, $to); + return app(LockService::class)->lock($this, __FUNCTION__, function () use ($to, $amount, $meta) { + return app(DbService::class)->transaction(function () use ($to, $amount, $meta) { + $prepareService = app(PrepareService::class); + $mathService = app(MathInterface::class); + $castService = app(CastService::class); $fee = app(WalletService::class)->fee($to, $amount); + $rate = app(ExchangeInterface::class)->convertTo( + $castService->getWallet($this)->currency, + $castService->getWallet($to)->currency, + 1 + ); - $withdraw = app(CommonService::class) - ->forceWithdraw($from, $math->add($amount, $fee), $meta) - ; + $withdrawDto = $prepareService->withdraw($this, $mathService->add($amount, $fee), $meta); + $depositDto = $prepareService->deposit($to, $mathService->floor($mathService->mul($amount, $rate, 1)), $meta); - $deposit = app(CommonService::class) - ->deposit($to, $math->floor($math->mul($amount, $rate, 1)), $meta) - ; + $transferLazyDto = new TransferLazyDto( + $this, + $to, + 0, + $fee, + $withdrawDto, + $depositDto, + Transfer::STATUS_EXCHANGE, + ); - $transfers = app(CommonService::class)->multiBrings([ - app(Bring::class) - ->setDiscount(0) - ->setStatus(Transfer::STATUS_EXCHANGE) - ->setDeposit($deposit) - ->setWithdraw($withdraw) - ->setFrom($from) - ->setFee($fee) - ->setTo($to), - ]); + $transfers = app(CommonService::class)->applyTransfers([$transferLazyDto]); return current($transfers); }); diff --git a/src/Traits/CanPay.php b/src/Traits/CanPay.php index bee9eb90e..837fdeff2 100644 --- a/src/Traits/CanPay.php +++ b/src/Traits/CanPay.php @@ -11,9 +11,6 @@ trait CanPay { use CartPay; - /** - * @throws - */ public function payFree(Product $product): Transfer { return current($this->payFreeCart(app(Cart::class)->addItem($product))); @@ -24,17 +21,11 @@ public function safePay(Product $product, bool $force = false): ?Transfer return current($this->safePayCart(app(Cart::class)->addItem($product), $force)) ?: null; } - /** - * @throws - */ public function pay(Product $product, bool $force = false): Transfer { return current($this->payCart(app(Cart::class)->addItem($product), $force)); } - /** - * @throws - */ public function forcePay(Product $product): Transfer { return current($this->forcePayCart(app(Cart::class)->addItem($product))); @@ -45,17 +36,11 @@ public function safeRefund(Product $product, bool $force = false, bool $gifts = return $this->safeRefundCart(app(Cart::class)->addItem($product), $force, $gifts); } - /** - * @throws - */ public function refund(Product $product, bool $force = false, bool $gifts = false): bool { return $this->refundCart(app(Cart::class)->addItem($product), $force, $gifts); } - /** - * @throws - */ public function forceRefund(Product $product, bool $gifts = false): bool { return $this->forceRefundCart(app(Cart::class)->addItem($product), $gifts); @@ -66,17 +51,11 @@ public function safeRefundGift(Product $product, bool $force = false): bool return $this->safeRefundGiftCart(app(Cart::class)->addItem($product), $force); } - /** - * @throws - */ public function refundGift(Product $product, bool $force = false): bool { return $this->refundGiftCart(app(Cart::class)->addItem($product), $force); } - /** - * @throws - */ public function forceRefundGift(Product $product): bool { return $this->forceRefundGiftCart(app(Cart::class)->addItem($product)); diff --git a/src/Traits/CartPay.php b/src/Traits/CartPay.php index 8dd97d7f3..c7c86392f 100644 --- a/src/Traits/CartPay.php +++ b/src/Traits/CartPay.php @@ -10,6 +10,7 @@ use Bavix\Wallet\Internal\ConsistencyInterface; use Bavix\Wallet\Internal\Dto\AvailabilityDto; use Bavix\Wallet\Internal\PurchaseInterface; +use Bavix\Wallet\Internal\Service\PrepareService; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Objects\Cart; use Bavix\Wallet\Services\CommonService; @@ -24,8 +25,6 @@ trait CartPay use HasWallet; /** - * @throws - * * @return Transfer[] */ public function payFreeCart(CartInterface $cart): array @@ -37,21 +36,21 @@ public function payFreeCart(CartInterface $cart): array app(ConsistencyInterface::class)->checkPotential($this, 0, true); - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $cart) { - $results = []; + return app(DbService::class)->transaction(function () use ($cart) { + $transfers = []; + $prepareService = app(PrepareService::class); + $metaService = app(MetaService::class); foreach ($cart->getBasketDto()->cursor() as $product) { - $results[] = app(CommonService::class)->forceTransfer( - $self, + $transfers[] = $prepareService->transferLazy( + $this, $product, + Transfer::STATUS_PAID, 0, - app(MetaService::class)->getMeta($cart, $product), - Transfer::STATUS_PAID + $metaService->getMeta($cart, $product) ); } - return $results; + return app(CommonService::class)->applyTransfers($transfers); }); } @@ -68,8 +67,6 @@ public function safePayCart(CartInterface $cart, bool $force = false): array } /** - * @throws - * * @return Transfer[] */ public function payCart(CartInterface $cart, bool $force = false): array @@ -79,39 +76,29 @@ public function payCart(CartInterface $cart, bool $force = false): array throw new ProductEnded(trans('wallet::errors.product_stock')); } - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $cart, $force) { - $results = []; + return app(DbService::class)->transaction(function () use ($cart, $force) { + $transfers = []; + $prepareService = app(PrepareService::class); + $metaService = app(MetaService::class); foreach ($cart->getBasketDto()->cursor() as $product) { - if ($force) { - $results[] = app(CommonService::class)->forceTransfer( - $self, - $product, - $product->getAmountProduct($self), - app(MetaService::class)->getMeta($cart, $product), - Transfer::STATUS_PAID - ); - - continue; - } - - $results[] = app(CommonService::class)->transfer( - $self, + $transfers[] = $prepareService->transferLazy( + $this, $product, - $product->getAmountProduct($self), - app(MetaService::class)->getMeta($cart, $product), - Transfer::STATUS_PAID + Transfer::STATUS_PAID, + $product->getAmountProduct($this), + $metaService->getMeta($cart, $product) ); } - return $results; + if ($force === false) { + app(ConsistencyInterface::class)->checkTransfer($transfers); + } + + return app(CommonService::class)->applyTransfers($transfers); }); } /** - * @throws - * * @return Transfer[] */ public function forcePayCart(CartInterface $cart): array @@ -128,22 +115,19 @@ public function safeRefundCart(CartInterface $cart, bool $force = false, bool $g } } - /** - * @throws - */ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool { - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $cart, $force, $gifts) { + return app(DbService::class)->transaction(function () use ($cart, $force, $gifts) { $results = []; - $transfers = app(PurchaseInterface::class)->already($self, $cart->getBasketDto(), $gifts); + $transfers = app(PurchaseInterface::class)->already($this, $cart->getBasketDto(), $gifts); if (count($transfers) !== $cart->getBasketDto()->total()) { throw (new ModelNotFoundException()) - ->setModel($self->transfers()->getMorphClass()) + ->setModel($this->transfers()->getMorphClass()) ; } + $objects = []; + $prepareService = app(PrepareService::class); foreach ($cart->getBasketDto()->cursor() as $product) { $transfer = current($transfers); next($transfers); @@ -151,19 +135,25 @@ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts * the code is extremely poorly written, a complete refactoring is required. * for version 6.x we will leave it as it is. */ - $transfer->load('withdraw.wallet'); - - if (!$force) { - app(ConsistencyInterface::class)->checkPotential($product, $transfer->deposit->amount); - } + $transfer->load('withdraw.wallet'); // fixme: need optimize - app(CommonService::class)->forceTransfer( + $objects[] = $prepareService->transferLazy( $product, $transfer->withdraw->wallet, - $transfer->deposit->amount, + Transfer::STATUS_TRANSFER, + $transfer->deposit->amount, // fixme: need optimize app(MetaService::class)->getMeta($cart, $product) ); + } + + if ($force === false) { + app(ConsistencyInterface::class)->checkTransfer($objects); + } + app(CommonService::class)->applyTransfers($objects); + + // fixme: one query update for + foreach ($transfers as $transfer) { $results[] = $transfer->update([ 'status' => Transfer::STATUS_REFUND, 'status_last' => $transfer->status, @@ -174,9 +164,6 @@ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts }); } - /** - * @throws - */ public function forceRefundCart(CartInterface $cart, bool $gifts = false): bool { return $this->refundCart($cart, true, $gifts); @@ -191,17 +178,11 @@ public function safeRefundGiftCart(CartInterface $cart, bool $force = false): bo } } - /** - * @throws - */ public function refundGiftCart(CartInterface $cart, bool $force = false): bool { return $this->refundCart($cart, $force, true); } - /** - * @throws - */ public function forceRefundGiftCart(CartInterface $cart): bool { return $this->refundGiftCart($cart, true); diff --git a/src/Traits/HasGift.php b/src/Traits/HasGift.php index 1400cd47d..7a7f36a5f 100644 --- a/src/Traits/HasGift.php +++ b/src/Traits/HasGift.php @@ -9,10 +9,13 @@ use Bavix\Wallet\Interfaces\Customer; use Bavix\Wallet\Interfaces\Product; use Bavix\Wallet\Interfaces\Wallet; +use Bavix\Wallet\Internal\Assembler\TransferDtoAssembler; use Bavix\Wallet\Internal\ConsistencyInterface; use Bavix\Wallet\Internal\MathInterface; +use Bavix\Wallet\Internal\Service\AtmService; +use Bavix\Wallet\Internal\Service\CastService; +use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; -use Bavix\Wallet\Objects\Bring; use Bavix\Wallet\Services\CommonService; use Bavix\Wallet\Services\DbService; use Bavix\Wallet\Services\LockService; @@ -79,22 +82,26 @@ public function gift(Wallet $to, Product $product, bool $force = false): Transfe app(ConsistencyInterface::class)->checkPotential($santa, $math->add($amount, $fee)); } - $withdraw = $commonService->forceWithdraw($santa, $math->add($amount, $fee), $meta); - $deposit = $commonService->deposit($product, $amount, $meta); + $withdraw = $commonService->makeTransaction($santa, Transaction::TYPE_WITHDRAW, $math->add($amount, $fee), $meta); + $deposit = $commonService->makeTransaction($product, Transaction::TYPE_DEPOSIT, $amount, $meta); $from = app(WalletService::class) ->getWallet($to) ; - $transfers = $commonService->assemble([ - app(Bring::class) - ->setStatus(Transfer::STATUS_GIFT) - ->setDiscount($discount) - ->setDeposit($deposit) - ->setWithdraw($withdraw) - ->setFrom($from) - ->setTo($product), - ]); + $castService = app(CastService::class); + + $transfer = app(TransferDtoAssembler::class)->create( + $deposit->getKey(), + $withdraw->getKey(), + Transfer::STATUS_GIFT, + $castService->getModel($from), + $castService->getModel($product), + $discount, + $fee + ); + + $transfers = app(AtmService::class)->makeTransfers([$transfer]); return current($transfers); }); diff --git a/src/Traits/HasWallet.php b/src/Traits/HasWallet.php index 537efd219..abe2788db 100644 --- a/src/Traits/HasWallet.php +++ b/src/Traits/HasWallet.php @@ -6,10 +6,11 @@ use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Exceptions\BalanceIsEmpty; use Bavix\Wallet\Exceptions\InsufficientFunds; -use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Interfaces\Wallet; +use Bavix\Wallet\Internal\BookkeeperInterface; use Bavix\Wallet\Internal\ConsistencyInterface; use Bavix\Wallet\Internal\MathInterface; +use Bavix\Wallet\Internal\Service\CastService; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Models\Wallet as WalletModel; @@ -42,12 +43,9 @@ trait HasWallet */ public function deposit($amount, ?array $meta = null, bool $confirmed = true): Transaction { - /** @var Wallet $self */ - $self = $this; - - return app(DbService::class)->transaction(static function () use ($self, $amount, $meta, $confirmed) { + return app(DbService::class)->transaction(function () use ($amount, $meta, $confirmed) { return app(CommonService::class) - ->deposit($self, $amount, $meta, $confirmed) + ->makeTransaction($this, Transaction::TYPE_DEPOSIT, $amount, $meta, $confirmed) ; }); } @@ -73,14 +71,14 @@ public function deposit($amount, ?array $meta = null, bool $confirmed = true): T * $user2->deposit(100); * var_dump($user2->balance); // 300 * - * @throws Throwable - * * @return float|int|string */ public function getBalanceAttribute() { /** @var Wallet $this */ - return app(Storable::class)->getBalance($this); + return app(BookkeeperInterface::class)->amount( + app(WalletService::class)->getWallet($this) + ); } /** @@ -96,7 +94,8 @@ public function getBalanceIntAttribute(): int */ public function transactions(): MorphMany { - return ($this instanceof WalletModel ? $this->holder : $this) + return app(CastService::class) + ->getHolder($this) ->morphMany(config('wallet.transaction.model', Transaction::class), 'payable') ; } @@ -154,7 +153,7 @@ public function withdraw($amount, ?array $meta = null, bool $confirmed = true): /** * Checks if you can withdraw funds. * - * @param int|string $amount + * @param float|int|string $amount */ public function canWithdraw($amount, bool $allowZero = false): bool { @@ -185,7 +184,7 @@ public function forceWithdraw($amount, ?array $meta = null, bool $confirmed = tr return app(DbService::class)->transaction(static function () use ($self, $amount, $meta, $confirmed) { return app(CommonService::class) - ->forceWithdraw($self, $amount, $meta, $confirmed) + ->makeTransaction($self, Transaction::TYPE_WITHDRAW, $amount, $meta, $confirmed) ; }); } diff --git a/src/Traits/HasWalletFloat.php b/src/Traits/HasWalletFloat.php index 1096bff00..d217e74c7 100644 --- a/src/Traits/HasWalletFloat.php +++ b/src/Traits/HasWalletFloat.php @@ -7,6 +7,7 @@ use Bavix\Wallet\Exceptions\InsufficientFunds; use Bavix\Wallet\Interfaces\Wallet; use Bavix\Wallet\Internal\MathInterface; +use Bavix\Wallet\Internal\Service\CastService; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Services\WalletService; @@ -89,7 +90,7 @@ public function canWithdrawFloat($amount): bool } /** - * @param float $amount + * @param float|string $amount * * @throws AmountInvalid * @throws BalanceIsEmpty @@ -108,7 +109,7 @@ public function transferFloat(Wallet $wallet, $amount, ?array $meta = null): Tra } /** - * @param float $amount + * @param float|string $amount */ public function safeTransferFloat(Wallet $wallet, $amount, ?array $meta = null): ?Transfer { @@ -145,9 +146,10 @@ public function getBalanceFloatAttribute() { /** @var Wallet $this */ $math = app(MathInterface::class); - $decimalPlacesValue = app(WalletService::class)->getWallet($this)->decimal_places; + $wallet = app(CastService::class)->getWallet($this); + $decimalPlacesValue = $wallet->decimal_places; $decimalPlaces = $math->powTen($decimalPlacesValue); - return $math->div($this->balance, $decimalPlaces, $decimalPlacesValue); + return $math->div($wallet->balance, $decimalPlaces, $decimalPlacesValue); } } diff --git a/src/Traits/HasWallets.php b/src/Traits/HasWallets.php index 28bee7ef3..1fc382ad3 100644 --- a/src/Traits/HasWallets.php +++ b/src/Traits/HasWallets.php @@ -3,6 +3,7 @@ namespace Bavix\Wallet\Traits; use function array_key_exists; +use Bavix\Wallet\Internal\UuidInterface; use Bavix\Wallet\Models\Wallet as WalletModel; use function config; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -94,7 +95,8 @@ public function createWallet(array $data): WalletModel /** @var WalletModel $wallet */ $wallet = $this->wallets()->create(array_merge( config('wallet.wallet.creating', []), - $data + $data, + ['uuid' => app(UuidInterface::class)->uuid4()] )); $this->_wallets[$wallet->slug] = $wallet; diff --git a/src/Traits/MorphOneWallet.php b/src/Traits/MorphOneWallet.php index 531ce5932..a566ba795 100644 --- a/src/Traits/MorphOneWallet.php +++ b/src/Traits/MorphOneWallet.php @@ -2,6 +2,8 @@ namespace Bavix\Wallet\Traits; +use Bavix\Wallet\Internal\Service\CastService; +use Bavix\Wallet\Internal\UuidInterface; use Bavix\Wallet\Models\Wallet as WalletModel; use Illuminate\Database\Eloquent\Relations\MorphOne; @@ -18,13 +20,15 @@ trait MorphOneWallet */ public function wallet(): MorphOne { - return ($this instanceof WalletModel ? $this->holder : $this) + return app(CastService::class) + ->getHolder($this) ->morphOne(config('wallet.wallet.model', WalletModel::class), 'holder') ->where('slug', config('wallet.wallet.default.slug', 'default')) ->withDefault(array_merge(config('wallet.wallet.creating', []), [ 'name' => config('wallet.wallet.default.name', 'Default Wallet'), 'slug' => config('wallet.wallet.default.slug', 'default'), 'meta' => config('wallet.wallet.default.meta', []), + 'uuid' => app(UuidInterface::class)->uuid4(), 'balance' => 0, ])) ; diff --git a/src/WalletServiceProvider.php b/src/WalletServiceProvider.php index 8f353d956..63a68a9a8 100644 --- a/src/WalletServiceProvider.php +++ b/src/WalletServiceProvider.php @@ -4,10 +4,6 @@ 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\Internal\BasketInterface; use Bavix\Wallet\Internal\BookkeeperInterface; use Bavix\Wallet\Internal\ConsistencyInterface; @@ -20,10 +16,6 @@ use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Models\Wallet; -use Bavix\Wallet\Objects\Bring; -use Bavix\Wallet\Objects\Cart; -use Bavix\Wallet\Objects\EmptyLock; -use Bavix\Wallet\Objects\Operation; use Bavix\Wallet\Services\AtomicService; use Bavix\Wallet\Services\BasketService; use Bavix\Wallet\Services\BookkeeperService; @@ -38,10 +30,6 @@ use Bavix\Wallet\Services\StorageService; use Bavix\Wallet\Services\UuidFactoryService; use Bavix\Wallet\Services\WalletService; -use Bavix\Wallet\Simple\BrickMath; -use Bavix\Wallet\Simple\Exchange; -use Bavix\Wallet\Simple\Rate; -use Bavix\Wallet\Simple\Store; use function config; use function dirname; use function function_exists; @@ -65,10 +53,8 @@ public function boot(): void return; } - $this->commands([RefreshBalance::class]); - if ($this->shouldMigrate()) { - $this->loadMigrationsFrom([__DIR__.'/../database']); + $this->loadMigrationsFrom([dirname(__DIR__).'/database']); } if (function_exists('config_path')) { @@ -92,60 +78,48 @@ public function register(): void 'wallet' ); - $this->singletons(); - $this->legacySingleton(); - $this->bindObjects(); + $configure = config('wallet', []); + + $this->singletons($configure['services'] ?? []); + $this->legacySingleton($configure['legacy'] ?? []); + $this->bindObjects($configure); } /** * Determine if we should register the migrations. */ - protected function shouldMigrate(): bool + private function shouldMigrate(): bool { return WalletConfigure::isRunsMigrations(); } - private function singletons(): void + private function singletons(array $configure): void { - // Bind eloquent models to IoC container - $this->app->singleton(ExchangeInterface::class, config('wallet.package.exchange', Exchange::class)); - $this->app->singleton(MathInterface::class, config('wallet.package.mathable', MathService::class)); - $this->app->singleton(CommonService::class, config('wallet.services.common', CommonService::class)); - $this->app->singleton(WalletService::class, config('wallet.services.wallet', WalletService::class)); - - $this->app->singleton(LockInterface::class, AtomicService::class); - $this->app->singleton(UuidInterface::class, UuidFactoryService::class); - $this->app->singleton(StorageInterface::class, StorageService::class); - $this->app->singleton(BookkeeperInterface::class, BookkeeperService::class); - $this->app->singleton(BasketInterface::class, BasketService::class); - $this->app->singleton(ConsistencyInterface::class, ConsistencyService::class); - $this->app->singleton(PurchaseInterface::class, PurchaseService::class); + $this->app->singleton(BasketInterface::class, $configure['basket'] ?? BasketService::class); + $this->app->singleton(BookkeeperInterface::class, $configure['bookkeeper'] ?? BookkeeperService::class); + $this->app->singleton(ConsistencyInterface::class, $configure['consistency'] ?? ConsistencyService::class); + $this->app->singleton(ExchangeInterface::class, $configure['exchange'] ?? ExchangeService::class); + $this->app->singleton(LockInterface::class, $configure['atomic'] ?? AtomicService::class); + $this->app->singleton(MathInterface::class, $configure['math'] ?? MathService::class); + $this->app->singleton(PurchaseInterface::class, $configure['purchase'] ?? PurchaseService::class); + $this->app->singleton(StorageInterface::class, $configure['storage'] ?? StorageService::class); + $this->app->singleton(UuidInterface::class, $configure['uuid'] ?? UuidFactoryService::class); } - private function legacySingleton(): void + private function legacySingleton(array $configure): void { - $this->app->singleton(ExchangeService::class, config('wallet.services.exchange', ExchangeService::class)); - $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, BrickMath::class); + $this->app->singleton(CommonService::class, $configure['common'] ?? null); + $this->app->singleton(WalletService::class, $configure['wallet'] ?? null); - $this->app->singleton(DbService::class, config('wallet.services.db', DbService::class)); - $this->app->singleton(LockService::class, config('wallet.services.lock', LockService::class)); - $this->app->singleton(MetaService::class); + $this->app->singleton(DbService::class, $configure['db'] ?? null); + $this->app->singleton(LockService::class, $configure['lock'] ?? null); + $this->app->singleton(MetaService::class, $configure['meta'] ?? null); } - private function bindObjects(): void + private function bindObjects(array $configure): void { - // models - $this->app->bind(Transaction::class, config('wallet.transaction.model', Transaction::class)); - $this->app->bind(Transfer::class, config('wallet.transfer.model', Transfer::class)); - $this->app->bind(Wallet::class, config('wallet.wallet.model', Wallet::class)); - - // object's - $this->app->bind(Bring::class, config('wallet.objects.bring', Bring::class)); - $this->app->bind(Cart::class, config('wallet.objects.cart', Cart::class)); - $this->app->bind(EmptyLock::class, config('wallet.objects.emptyLock', EmptyLock::class)); - $this->app->bind(Operation::class, config('wallet.objects.operation', Operation::class)); + $this->app->bind(Transaction::class, $configure['transaction']['model'] ?? null); + $this->app->bind(Transfer::class, $configure['transfer']['model'] ?? null); + $this->app->bind(Wallet::class, $configure['wallet']['model'] ?? null); } } diff --git a/tests/AtomicLockTest.php b/tests/AtomicLockTest.php index c4b90da2c..3fe75cb6b 100644 --- a/tests/AtomicLockTest.php +++ b/tests/AtomicLockTest.php @@ -2,7 +2,7 @@ namespace Bavix\Wallet\Test; -use Bavix\Wallet\Services\AtomicService; +use Bavix\Wallet\Internal\LockInterface; /** * @internal @@ -11,7 +11,7 @@ class AtomicLockTest extends TestCase { public function testAtomic(): void { - $atomic = app(AtomicService::class); + $atomic = app(LockInterface::class); $atomic->block('hello', static fn () => 'hello world'); $atomic->block('hello', static fn () => 'hello world'); self::assertTrue(true); diff --git a/tests/BalanceLockTest.php b/tests/BalanceLockTest.php deleted file mode 100644 index 580b03462..000000000 --- a/tests/BalanceLockTest.php +++ /dev/null @@ -1,11 +0,0 @@ -balance, 0); self::assertTrue($wallet->exists); - self::assertEquals(0, app(Storable::class)->getBalance($wallet)); - } - - /** - * @throws - */ - public function testFailUpdate(): void - { - /** @var Buyer $buyer */ - $buyer = BuyerFactory::new()->create(); - self::assertFalse($buyer->relationLoaded('wallet')); - $wallet = $buyer->wallet; - - self::assertFalse($wallet->exists); - self::assertEquals(0, $wallet->balance); - self::assertTrue($wallet->exists); - - /** @var MockObject|Wallet $mockQuery */ - $mockQuery = $this->createMock(\get_class($wallet->newQuery())); - $mockQuery->method('whereKey')->willReturn($mockQuery); - $mockQuery->method('update')->willReturn(false); - - /** @var MockObject|Wallet $mockWallet */ - $mockWallet = $this->createMock(\get_class($wallet)); - $mockWallet->method('newQuery')->willReturn($mockQuery); - $mockWallet->method('getKey')->willReturn($wallet->getKey()); - - $result = app(CommonService::class) - ->addBalance($mockWallet, 100) - ; - - self::assertFalse($result); - self::assertEquals(0, app(Storable::class)->getBalance($wallet)); + self::assertEquals(0, app(BookkeeperInterface::class)->amount($wallet)); } /** @@ -192,62 +157,11 @@ public function testThrowUpdate(): void /** @var MockObject|Wallet $mockWallet */ $mockWallet = $this->createMock(\get_class($wallet)); + $mockWallet->method('getBalanceAttribute')->willReturn('125'); $mockWallet->method('newQuery')->willReturn($mockQuery); $mockWallet->method('getKey')->willReturn($wallet->getKey()); - app(CommonService::class) - ->addBalance($mockWallet, 100) - ; - } - - /** - * @throws - */ - public function testArtisanRefresh(): void - { - /** @var UserMulti $user */ - $user = UserMultiFactory::new()->create(); - $wallets = \range('a', 'z'); - $sums = []; - $ids = []; - foreach ($wallets as $name) { - $wallet = $user->createWallet(['name' => $name]); - $ids[] = $wallet->id; - $sums[$name] = 0; - $operations = \random_int(1, 10); - for ($i = 0; $i < $operations; ++$i) { - $amount = \random_int(10, 10000); - $confirmed = (bool) \random_int(0, 1); - $deposit = $wallet->deposit($amount, null, $confirmed); - self::assertIsInt($deposit->wallet_id); - - if ($confirmed) { - $sums[$name] += $amount; - } - - self::assertEquals($amount, $deposit->amount); - self::assertEquals($confirmed, $deposit->confirmed); - self::assertEquals($sums[$name], $wallet->balance); - } - } - - /** - * Check for the number of created wallets. - * Make sure you didn't accidentally create the default wallet. - * - * @see https://github.com/bavix/laravel-wallet/issues/218 - */ - self::assertCount(count($wallets), $user->wallets); - - // fresh balance - DB::table(config('wallet.wallet.table')) - ->update(['balance' => 0]) - ; - - $this->artisan('wallet:refresh'); - Wallet::query()->whereKey($ids)->each(function (Wallet $wallet) use ($sums) { - self::assertEquals($sums[$wallet->name], $wallet->balance); - }); + $mockWallet->newQuery()->whereKey($wallet->getKey())->update(['balance' => 100]); } public function testEqualWallet(): void @@ -292,7 +206,7 @@ public function testForceUpdate(): void * * Here is an example: */ - app(Storable::class)->fresh(); + app(BookkeeperInterface::class)->missing($buyer->wallet); self::assertEquals(1000, $wallet->getRawOriginal('balance')); /** @@ -336,7 +250,7 @@ public function testAdjustment(int $account, int $adjust): void * * Here is an example: */ - app(Storable::class)->fresh(); + app(BookkeeperInterface::class)->missing($wallet); self::assertEquals($account, $wallet->getRawOriginal('balance')); /** @@ -361,6 +275,41 @@ public function testAdjustment(int $account, int $adjust): void self::assertEquals($adjust, $wallet->getRawOriginal('balance')); } + public function testFailUpdate(): void + { + /** @var Buyer $buyer */ + $buyer = BuyerFactory::new()->create(); + self::assertFalse($buyer->relationLoaded('wallet')); + $wallet = $buyer->wallet; + + self::assertFalse($wallet->exists); + self::assertEquals(0, $wallet->balance); + self::assertTrue($wallet->exists); + + /** @var MockObject|Wallet $mockQuery */ + $mockQuery = $this->createMock(\get_class($wallet->newQuery())); + $mockQuery->method('whereKey')->willReturn($mockQuery); + $mockQuery->method('update')->willReturn(0); + + /** @var MockObject|Wallet $mockWallet */ + $mockWallet = $this->createMock(\get_class($wallet)); + $mockWallet->method('newQuery')->willReturn($mockQuery); + $mockWallet->method('getKey')->willReturn($wallet->getKey()); + $mockWallet->method('fill')->willReturn($mockWallet); + $mockWallet->method('syncOriginalAttribute')->willReturn($mockWallet); + $mockWallet->method('__get')->with('uuid')->willReturn($wallet->uuid); + + $bookkeeper = app(BookkeeperInterface::class); + $bookkeeper->sync($wallet, 100500); // init + + $result = app(CommonService::class) + ->addBalance($mockWallet, 100) + ; + + self::assertFalse($result); + self::assertSame('0', $bookkeeper->amount($wallet)); + } + /** * @dataProvider providerAdjustment */ diff --git a/tests/CartLockTest.php b/tests/CartLockTest.php deleted file mode 100644 index d4b4de76d..000000000 --- a/tests/CartLockTest.php +++ /dev/null @@ -1,11 +0,0 @@ -payCart($cart); self::assertCount(count($cart), $transfers); - self::assertTrue((bool) $cart->alreadyBuy($buyer)); + self::assertTrue((bool) app(PurchaseInterface::class)->already($buyer, $cart->getBasketDto())); self::assertEquals(0, $buyer->balance); foreach ($transfers as $transfer) { @@ -146,13 +147,15 @@ public function testCartQuantity(): void $cart = app(Cart::class); $amount = 0; + $price = 0; for ($i = 0; $i < count($products) - 1; ++$i) { $rnd = random_int(1, 5); $cart->addItem($products[$i], $rnd); - $buyer->deposit($products[$i]->getAmountProduct($buyer) * $rnd); + $price += $products[$i]->getAmountProduct($buyer) * $rnd; $amount += $rnd; } + $buyer->deposit($price); self::assertCount($amount, $cart->getItems()); $transfers = $buyer->payCart($cart); @@ -270,7 +273,7 @@ public function testWithdrawal(): void $transfers = $buyer->payCart($cart); self::assertCount(count($cart), $transfers); - self::assertTrue((bool) $cart->alreadyBuy($buyer)); + self::assertTrue((bool) app(PurchaseInterface::class)->already($buyer, $cart->getBasketDto())); self::assertEquals(0, $buyer->balance); foreach ($transfers as $transfer) { diff --git a/tests/CastsTest.php b/tests/CastsTest.php deleted file mode 100644 index a1c31e859..000000000 --- a/tests/CastsTest.php +++ /dev/null @@ -1,67 +0,0 @@ -create(); - self::assertEquals($buyer->balance, 0); - - self::assertIsInt($buyer->wallet->getKey()); - self::assertEquals($buyer->wallet->getCasts()['id'], 'int'); - - config(['wallet.wallet.casts.id' => 'string']); - self::assertIsString($buyer->wallet->getKey()); - self::assertEquals($buyer->wallet->getCasts()['id'], 'string'); - } - - public function testModelTransfer(): void - { - /** - * @var Buyer $buyer - * @var User $user - */ - $buyer = BuyerFactory::new()->create(); - $user = UserFactory::new()->create(); - self::assertEquals($buyer->balance, 0); - self::assertEquals($user->balance, 0); - - $deposit = $user->deposit(1000); - self::assertEquals($user->balance, $deposit->amount); - - $transfer = $user->transfer($buyer, 700); - - self::assertIsInt($transfer->getKey()); - self::assertEquals($transfer->getCasts()['id'], 'int'); - - config(['wallet.transfer.casts.id' => 'string']); - self::assertIsString($transfer->getKey()); - self::assertEquals($transfer->getCasts()['id'], 'string'); - } - - public function testModelTransaction(): void - { - /** @var Buyer $buyer */ - $buyer = BuyerFactory::new()->create(); - self::assertEquals($buyer->balance, 0); - $deposit = $buyer->deposit(1); - - self::assertIsInt($deposit->getKey()); - self::assertEquals($deposit->getCasts()['id'], 'int'); - - config(['wallet.transaction.casts.id' => 'string']); - self::assertIsString($deposit->getKey()); - self::assertEquals($deposit->getCasts()['id'], 'string'); - } -} diff --git a/tests/Common/MyExchange.php b/tests/Common/MyExchange.php new file mode 100644 index 000000000..71cdc9fb6 --- /dev/null +++ b/tests/Common/MyExchange.php @@ -0,0 +1,44 @@ + [ + 'RUB' => 67.61, + ], + ]; + + /** + * Rate constructor. + */ + public function __construct(MathInterface $mathService) + { + $this->mathService = $mathService; + + foreach ($this->rates as $from => $rates) { + foreach ($rates as $to => $rate) { + if (empty($this->rates[$to][$from])) { + $this->rates[$to][$from] = $this->mathService->div(1, $rate); + } + } + } + } + + /** @param float|int|string $amount */ + public function convertTo(string $fromCurrency, string $toCurrency, $amount): string + { + return $this->mathService->mul($amount, Arr::get( + Arr::get($this->rates, $fromCurrency, []), + $toCurrency, + 1 + )); + } +} diff --git a/tests/Common/Rate.php b/tests/Common/Rate.php deleted file mode 100644 index 752547abf..000000000 --- a/tests/Common/Rate.php +++ /dev/null @@ -1,72 +0,0 @@ - [ - 'RUB' => 67.61, - ], - ]; - - private $walletService; - - private $mathService; - - /** - * Rate constructor. - */ - public function __construct(ExchangeInterface $exchange, MathInterface $mathService, WalletService $walletService) - { - parent::__construct($exchange); - $this->walletService = $walletService; - $this->mathService = $mathService; - - foreach ($this->rates as $from => $rates) { - foreach ($rates as $to => $rate) { - if (empty($this->rates[$to][$from])) { - $this->rates[$to][$from] = $this->mathService->div(1, $rate); - } - } - } - } - - /** - * {@inheritdoc} - */ - public function convertTo(Wallet $wallet) - { - return $this->mathService->mul( - parent::convertTo($wallet), - $this->rate($wallet) - ); - } - - /** - * @return float|int - */ - protected function rate(Wallet $wallet) - { - $from = $this->walletService->getWallet($this->withCurrency); - $to = $this->walletService->getWallet($wallet); - - /** - * @var \Bavix\Wallet\Models\Wallet $wallet - */ - return Arr::get( - Arr::get($this->rates, $from->currency, []), - $to->currency, - 1 - ); - } -} diff --git a/tests/Common/WalletServiceProvider.php b/tests/Common/WalletServiceProvider.php index 68a3aa73b..57b0b9073 100644 --- a/tests/Common/WalletServiceProvider.php +++ b/tests/Common/WalletServiceProvider.php @@ -12,8 +12,6 @@ public function boot(): void { parent::boot(); - $this->loadMigrationsFrom([ - dirname(__DIR__).'/migrations', - ]); + $this->loadMigrationsFrom([dirname(__DIR__).'/migrations']); } } diff --git a/tests/ConfirmLockTest.php b/tests/ConfirmLockTest.php deleted file mode 100644 index 20268f635..000000000 --- a/tests/ConfirmLockTest.php +++ /dev/null @@ -1,11 +0,0 @@ -create(); - $transaction = $userConfirm->deposit(100, null, false); - self::assertEquals($userConfirm->wallet->id, $transaction->wallet->id); - self::assertEquals($userConfirm->id, $transaction->payable_id); - self::assertInstanceOf(UserConfirm::class, $transaction->payable); - self::assertFalse($transaction->confirmed); - - $wallet = app(WalletService::class) - ->getWallet($userConfirm) - ; - - $mockWallet = $this->createMock(\get_class($wallet)); - $mockWallet->method('refreshBalance') - ->willReturn(false) - ; - - /** - * @var Wallet $mockWallet - */ - self::assertInstanceOf(Wallet::class, $wallet); - self::assertFalse($mockWallet->refreshBalance()); - - $userConfirm->setRelation('wallet', $mockWallet); - self::assertFalse($userConfirm->confirm($transaction)); - self::assertFalse($userConfirm->safeConfirm($transaction)); - } - - public function testFailResetConfirm(): void - { - /** @var UserConfirm $userConfirm */ - $userConfirm = UserConfirmFactory::new()->create(); - $transaction = $userConfirm->deposit(100); - self::assertEquals($userConfirm->wallet->id, $transaction->wallet->id); - self::assertEquals($userConfirm->id, $transaction->payable_id); - self::assertInstanceOf(UserConfirm::class, $transaction->payable); - self::assertTrue($transaction->confirmed); - - $wallet = app(WalletService::class) - ->getWallet($userConfirm) - ; - - $mockWallet = $this->createMock(\get_class($wallet)); - $mockWallet->method('refreshBalance') - ->willReturn(false) - ; - - /** - * @var Wallet $mockWallet - */ - self::assertInstanceOf(Wallet::class, $wallet); - self::assertFalse($mockWallet->refreshBalance()); - - $userConfirm->setRelation('wallet', $mockWallet); - self::assertFalse($userConfirm->resetConfirm($transaction)); - self::assertFalse($userConfirm->safeResetConfirm($transaction)); - } -} diff --git a/tests/DiscountTaxTest.php b/tests/DiscountTaxTest.php index 74cd3d698..968ea41d1 100644 --- a/tests/DiscountTaxTest.php +++ b/tests/DiscountTaxTest.php @@ -74,7 +74,7 @@ public function testPay(): void self::assertEquals($product->getKey(), $transfer->to->getKey()); } - public function testRefund(): void + public function testRefundPersonalDiscountAndTax(): void { /** * @var Buyer $buyer @@ -237,7 +237,7 @@ public function testForcePay(): void self::assertEquals($buyer->balance, 0); } - public function testPayFree(): void + public function testPayFreeAndRefund(): void { /** * @var Buyer $buyer diff --git a/tests/EmptyLockTest.php b/tests/EmptyLockTest.php deleted file mode 100644 index 8bfdf3157..000000000 --- a/tests/EmptyLockTest.php +++ /dev/null @@ -1,30 +0,0 @@ -block(1)); - self::assertTrue($empty->block(1, null)); - self::assertNull($empty->get()); - self::assertTrue($empty->get(static function () { - return true; - })); - } - - public function testOwner(): void - { - $empty = app(EmptyLock::class); - $str = $empty->owner(); - self::assertIsString($str); - self::assertEquals($str, $empty->owner()); - } -} diff --git a/tests/ExchangeTest.php b/tests/ExchangeTest.php index 47b122805..841bd3310 100644 --- a/tests/ExchangeTest.php +++ b/tests/ExchangeTest.php @@ -2,9 +2,12 @@ namespace Bavix\Wallet\Test; +use Bavix\Wallet\Internal\ExchangeInterface; use Bavix\Wallet\Models\Transfer; +use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Test\Factories\UserMultiFactory; use Bavix\Wallet\Test\Models\UserMulti; +use Illuminate\Support\Str; /** * @internal @@ -66,4 +69,57 @@ public function testSafe(): void $transfer = $rub->safeExchange($usd, 10000); self::assertNull($transfer); } + + public function testExchangeClass(): void + { + $service = app(ExchangeService::class); + + self::assertEquals(1, $service->convertTo('USD', 'EUR', 1)); + self::assertEquals(5, $service->convertTo('USD', 'EUR', 5)); + self::assertEquals(27, $service->convertTo('USD', 'EUR', 27)); + } + + public function testRate(): void + { + /** @var UserMulti $user */ + $user = UserMultiFactory::new()->create(); + $usd = $user->createWallet(['name' => 'Dollar USA', 'slug' => 'my-usd', 'meta' => ['currency' => 'USD']]); + self::assertEquals($usd->slug, 'my-usd'); + self::assertEquals($usd->currency, 'USD'); + self::assertEquals($usd->holder_id, $user->id); + self::assertInstanceOf($usd->holder_type, $user); + + $rub = $user->createWallet(['name' => 'RUB']); + self::assertEquals($rub->slug, 'rub'); + self::assertEquals($rub->currency, 'RUB'); + self::assertEquals($rub->holder_id, $user->id); + self::assertInstanceOf($rub->holder_type, $user); + + $superWallet = $user->createWallet(['name' => 'Super Wallet']); + self::assertEquals($superWallet->slug, Str::slug('Super Wallet')); + self::assertEquals($superWallet->currency, Str::upper(Str::slug('Super Wallet'))); + self::assertEquals($superWallet->holder_id, $user->id); + self::assertInstanceOf($superWallet->holder_type, $user); + + $rate = app(ExchangeInterface::class) + ->convertTo($usd->currency, $rub->currency, 1000) + ; + + self::assertEquals(67610., $rate); + } + + public function testExchange(): void + { + $rate = app(ExchangeInterface::class) + ->convertTo('USD', 'RUB', 1) + ; + + self::assertEquals(67.61, $rate); + + $rate = app(ExchangeInterface::class) + ->convertTo('RUB', 'USD', 1) + ; + + self::assertEquals(1 / 67.61, $rate); + } } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index c852a9a49..599b7fdd6 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -23,16 +23,12 @@ public function testMetaAccount(): void self::assertEquals(4, $buyer->transactions()->count()); - if (version_compare(PHP_VERSION, '7.3.0') < 0) { - self::markTestSkipped('You are using old php. Test not available.'); - - return; - } - + $nullable = $buyer->transactions()->whereNull('meta')->count(); $customers = $buyer->transactions()->where('meta->account', 'customers')->count(); $expenses = $buyer->transactions()->where('meta->account', 'expenses')->count(); $vendors = $buyer->transactions()->where('meta->account', 'vendors')->count(); + self::assertEquals(1, $nullable); self::assertEquals(1, $customers); self::assertEquals(1, $expenses); self::assertEquals(1, $vendors); diff --git a/tests/Models/Item.php b/tests/Models/Item.php index 5bf8b6c9c..26354e601 100644 --- a/tests/Models/Item.php +++ b/tests/Models/Item.php @@ -54,11 +54,6 @@ public function getMetaProduct(): ?array return null; } - public function getUniqueId(): string - { - return $this->getKey(); - } - /** * @param int[] $walletIds */ diff --git a/tests/MultiWalletTest.php b/tests/MultiWalletTest.php index e8c2678af..2feeec016 100644 --- a/tests/MultiWalletTest.php +++ b/tests/MultiWalletTest.php @@ -4,6 +4,7 @@ use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Exceptions\BalanceIsEmpty; +use Bavix\Wallet\Internal\UuidInterface; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Services\DbService; @@ -395,6 +396,7 @@ public function testGetWallet(): void $test2 = $user->wallets()->create([ 'name' => 'Test2', + 'uuid' => app(UuidInterface::class)->uuid4(), ]); self::assertEquals( diff --git a/tests/Objects/Operation.php b/tests/Objects/Operation.php deleted file mode 100644 index 04faf6d7a..000000000 --- a/tests/Objects/Operation.php +++ /dev/null @@ -1,13 +0,0 @@ - $this->meta['bank_method'] ?? null, - ]); - } -} diff --git a/tests/Objects/TransactionDtoTransformer.php b/tests/Objects/TransactionDtoTransformer.php new file mode 100644 index 000000000..d00452b27 --- /dev/null +++ b/tests/Objects/TransactionDtoTransformer.php @@ -0,0 +1,20 @@ +getMeta() !== null) { + $bankMethod = $dto->getMeta()['bank_method'] ?? null; + } + + return array_merge(parent::extract($dto), [ + 'bank_method' => $bankMethod, + ]); + } +} diff --git a/tests/RaceCondition.php b/tests/RaceCondition.php deleted file mode 100644 index 7788a3d66..000000000 --- a/tests/RaceCondition.php +++ /dev/null @@ -1,29 +0,0 @@ -app) { - $this->refreshApplication(); - } - - $this->app['config']->set('wallet.lock.enabled', extension_loaded('memcached')); - - return true; - } -} diff --git a/tests/RateTest.php b/tests/RateTest.php deleted file mode 100644 index 1ec30916e..000000000 --- a/tests/RateTest.php +++ /dev/null @@ -1,75 +0,0 @@ -create(); - $usd = $user->createWallet(['name' => 'Dollar USA', 'slug' => 'my-usd']); - self::assertEquals($usd->slug, 'my-usd'); - self::assertEquals($usd->currency, 'USD'); - self::assertEquals($usd->holder_id, $user->id); - self::assertInstanceOf($usd->holder_type, $user); - - $rub = $user->createWallet(['name' => 'RUB']); - self::assertEquals($rub->slug, 'rub'); - self::assertEquals($rub->currency, 'RUB'); - self::assertEquals($rub->holder_id, $user->id); - self::assertInstanceOf($rub->holder_type, $user); - - $superWallet = $user->createWallet(['name' => 'Super Wallet']); - self::assertEquals($superWallet->slug, Str::slug('Super Wallet')); - self::assertEquals($superWallet->currency, Str::upper(Str::slug('Super Wallet'))); - self::assertEquals($superWallet->holder_id, $user->id); - self::assertInstanceOf($superWallet->holder_type, $user); - - $rate = app(Rateable::class) - ->withAmount(1000) - ->withCurrency($usd) - ->convertTo($rub) - ; - - self::assertEquals($rate, 67610.); - } - - public function testExchange(): void - { - /** @var UserMulti $user */ - $user = UserMultiFactory::new()->create(); - $usd = $user->createWallet(['name' => 'USD']); - self::assertEquals($usd->slug, 'usd'); - self::assertEquals($usd->currency, 'USD'); - self::assertEquals($usd->holder_id, $user->id); - self::assertInstanceOf($usd->holder_type, $user); - - $rub = $user->createWallet(['name' => 'RUR', 'slug' => 'my-rub']); - self::assertEquals($rub->slug, 'my-rub'); - self::assertEquals($rub->currency, 'RUB'); - self::assertEquals($rub->holder_id, $user->id); - self::assertInstanceOf($rub->holder_type, $user); - - $rate = app(ExchangeService::class) - ->rate($usd, $rub) - ; - - self::assertEquals($rate, 67.61); - - $rate = app(ExchangeService::class) - ->rate($rub, $usd) - ; - - self::assertEquals($rate, 1 / 67.61); - } -} diff --git a/tests/SingletonTest.php b/tests/SingletonTest.php index 08c9dc53f..41eaeb6d9 100644 --- a/tests/SingletonTest.php +++ b/tests/SingletonTest.php @@ -2,17 +2,10 @@ namespace Bavix\Wallet\Test; -use Bavix\Wallet\Interfaces\Mathable; -use Bavix\Wallet\Interfaces\Rateable; -use Bavix\Wallet\Interfaces\Storable; use Bavix\Wallet\Internal\MathInterface; -use Bavix\Wallet\Objects\Bring; use Bavix\Wallet\Objects\Cart; -use Bavix\Wallet\Objects\EmptyLock; -use Bavix\Wallet\Objects\Operation; use Bavix\Wallet\Services\CommonService; use Bavix\Wallet\Services\DbService; -use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Services\LockService; use Bavix\Wallet\Services\WalletService; use Bavix\Wallet\Test\Common\Models\Transaction; @@ -24,41 +17,11 @@ */ class SingletonTest extends TestCase { - public function testBring(): void - { - self::assertNotEquals($this->getRefId(Bring::class), $this->getRefId(Bring::class)); - } - public function testCart(): void { self::assertNotEquals($this->getRefId(Cart::class), $this->getRefId(Cart::class)); } - public function testEmptyLock(): void - { - self::assertNotEquals($this->getRefId(EmptyLock::class), $this->getRefId(EmptyLock::class)); - } - - public function testOperation(): void - { - self::assertNotEquals($this->getRefId(Operation::class), $this->getRefId(Operation::class)); - } - - public function testRateable(): void - { - self::assertEquals($this->getRefId(Rateable::class), $this->getRefId(Rateable::class)); - } - - public function testStorable(): void - { - self::assertEquals($this->getRefId(Storable::class), $this->getRefId(Storable::class)); - } - - public function testMathable(): void - { - self::assertEquals($this->getRefId(Mathable::class), $this->getRefId(Mathable::class)); - } - public function testMathInterface(): void { self::assertEquals($this->getRefId(MathInterface::class), $this->getRefId(MathInterface::class)); @@ -79,11 +42,6 @@ public function testWallet(): void self::assertNotEquals($this->getRefId(Wallet::class), $this->getRefId(Wallet::class)); } - public function testExchangeService(): void - { - self::assertEquals($this->getRefId(ExchangeService::class), $this->getRefId(ExchangeService::class)); - } - public function testCommonService(): void { self::assertEquals($this->getRefId(CommonService::class), $this->getRefId(CommonService::class)); diff --git a/tests/TableTest.php b/tests/TableTest.php new file mode 100644 index 000000000..476537b1c --- /dev/null +++ b/tests/TableTest.php @@ -0,0 +1,39 @@ +create(); + self::assertSame('wallet', $user->wallet->getTable()); + } + + public function testTransactionTableName(): void + { + /** @var User $user */ + $user = UserFactory::new()->create(); + $transaction = $user->deposit(100); + self::assertSame('transaction', $transaction->getTable()); + } + + public function testTransferTableName(): void + { + /** + * @var User $user1 + * @var User $user2 + */ + [$user1, $user2] = UserFactory::times(2)->create(); + $user1->deposit(1000); + $transfer = $user1->transfer($user2, 1000); + self::assertSame('transfer', $transfer->getTable()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 5c1567b47..951ae6364 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,13 +2,10 @@ namespace Bavix\Wallet\Test; -use Bavix\Wallet\Interfaces\Storable; -use Bavix\Wallet\Services\MathService; -use Bavix\Wallet\Simple\Store; use Bavix\Wallet\Test\Common\Models\Transaction; use Bavix\Wallet\Test\Common\Models\Transfer; use Bavix\Wallet\Test\Common\Models\Wallet; -use Bavix\Wallet\Test\Common\Rate; +use Bavix\Wallet\Test\Common\MyExchange; use Bavix\Wallet\Test\Common\WalletServiceProvider; use Illuminate\Config\Repository; use Illuminate\Foundation\Application; @@ -22,12 +19,6 @@ class TestCase extends OrchestraTestCase { use RefreshDatabase; - public function setUp(): void - { - parent::setUp(); - app(Storable::class)->fresh(); - } - public function expectExceptionMessageStrict(string $message): void { $this->expectExceptionMessageMatches("~^{$message}$~"); @@ -35,54 +26,42 @@ public function expectExceptionMessageStrict(string $message): void /** * @param Application $app + * + * @return non-empty-array */ protected function getPackageProviders($app): array { - $this->updateConfig($app); + // Bind eloquent models to IoC container + $app['config']->set('wallet.services.exchange', MyExchange::class); + $app['config']->set('wallet.transaction.model', Transaction::class); + $app['config']->set('wallet.transfer.model', Transfer::class); + $app['config']->set('wallet.wallet.model', Wallet::class); return [WalletServiceProvider::class]; } - protected function updateConfig(Application $app): void + /** + * @param Application $app + */ + protected function getEnvironmentSetUp($app): void { /** @var Repository $config */ $config = $app['config']; - // 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', MathService::class); - // database $config->set('database.connections.testing.prefix', 'tests'); $config->set('database.connections.pgsql.prefix', 'tests'); $config->set('database.connections.mysql.prefix', 'tests'); $mysql = $config->get('database.connections.mysql'); - $mariadb = array_merge($mysql, ['port' => 3307]); - $percona = array_merge($mysql, ['port' => 3308]); - - $config->set('database.connections.mariadb', $mariadb); - $config->set('database.connections.percona', $percona); + $config->set('database.connections.mariadb', array_merge($mysql, ['port' => 3307])); // new table name's $config->set('wallet.transaction.table', 'transaction'); $config->set('wallet.transfer.table', 'transfer'); $config->set('wallet.wallet.table', 'wallet'); - // override model's - $config->set('wallet.transaction.model', Transaction::class); - $config->set('wallet.transfer.model', Transfer::class); - $config->set('wallet.wallet.model', Wallet::class); - - // wallet - $config->set('wallet.currencies', [ - 'my-usd' => 'USD', - 'my-eur' => 'EUR', - 'my-rub' => 'RUB', - 'def-curr' => 'EUR', - ]); - - $config->set('wallet.lock.enabled', false); + $config->set('wallet.cache.driver', $config->get('cache.driver')); + $config->set('wallet.lock.driver', $config->get('cache.driver')); } } diff --git a/tests/WalletExtensionTest.php b/tests/WalletExtensionTest.php index 16c0451ae..3122045e5 100644 --- a/tests/WalletExtensionTest.php +++ b/tests/WalletExtensionTest.php @@ -2,7 +2,7 @@ namespace Bavix\Wallet\Test; -use Bavix\Wallet\Objects\Operation; +use Bavix\Wallet\Internal\Transform\TransactionDtoTransformer; use Bavix\Wallet\Test\Common\Models\Transaction; use Bavix\Wallet\Test\Common\Models\TransactionMoney; use Bavix\Wallet\Test\Factories\BuyerFactory; @@ -16,7 +16,7 @@ class WalletExtensionTest extends TestCase public function setUp(): void { parent::setUp(); - $this->app->bind(Operation::class, Objects\Operation::class); + $this->app->bind(TransactionDtoTransformer::class, Objects\TransactionDtoTransformer::class); } public function testCustomAttribute(): void @@ -33,7 +33,7 @@ public function testCustomAttribute(): void public function testTransactionMoneyAttribute(): void { - $this->app['config']->set('wallet.transaction.model', TransactionMoney::class); + $this->app->bind(\Bavix\Wallet\Models\Transaction::class, TransactionMoney::class); /** * @var Buyer $buyer diff --git a/tests/WalletTest.php b/tests/WalletTest.php index a31ee473b..59365f274 100644 --- a/tests/WalletTest.php +++ b/tests/WalletTest.php @@ -62,6 +62,17 @@ public function testInvalidDeposit(): void $user->deposit(-1); } + public function testInvalidWithdraw(): void + { + $this->expectException(AmountInvalid::class); + $this->expectExceptionMessageStrict(trans('wallet::errors.price_positive')); + + /** @var User $user */ + $user = UserFactory::new()->create(); + $user->deposit(1); + $user->withdraw(-1); + } + public function testFindUserByExistsWallet(): void { /** @var Collection|User[] $users */ @@ -122,7 +133,7 @@ public function testWithdraw(): void $user->withdraw(1); } - public function testInvalidWithdraw(): void + public function testBalanceIsEmptyWithdraw(): void { $this->expectException(BalanceIsEmpty::class); $this->expectExceptionMessageStrict(trans('wallet::errors.wallet_empty'));