diff --git a/database/2024_01_24_185401_add_extra_column_in_transfer.php b/database/2024_01_24_185401_add_extra_column_in_transfer.php new file mode 100644 index 000000000..9a9f7d6ac --- /dev/null +++ b/database/2024_01_24_185401_add_extra_column_in_transfer.php @@ -0,0 +1,30 @@ +table(), static function (Blueprint $table) { + $table->json('extra') + ->nullable() + ->after('fee') + ; + }); + } + + public function down(): void + { + Schema::dropColumns($this->table(), ['extra']); + } + + private function table(): string + { + return (new Transfer())->getTable(); + } +}; diff --git a/phpstan.src.baseline.neon b/phpstan.src.baseline.neon index 42dc3658f..f6c4b3fa5 100644 --- a/phpstan.src.baseline.neon +++ b/phpstan.src.baseline.neon @@ -1,5 +1,10 @@ parameters: ignoreErrors: + - + message: "#^Constructor in Bavix\\\\Wallet\\\\External\\\\Dto\\\\Extra has parameter \\$extra with default value\\.$#" + count: 1 + path: src/External/Dto/Extra.php + - message: "#^Constructor in Bavix\\\\Wallet\\\\External\\\\Dto\\\\Extra has parameter \\$uuid with default value\\.$#" count: 1 diff --git a/src/External/Contracts/ExtraDtoInterface.php b/src/External/Contracts/ExtraDtoInterface.php index 1d157243b..8ba0c5a5a 100644 --- a/src/External/Contracts/ExtraDtoInterface.php +++ b/src/External/Contracts/ExtraDtoInterface.php @@ -11,4 +11,9 @@ public function getDepositOption(): OptionDtoInterface; public function getWithdrawOption(): OptionDtoInterface; public function getUuid(): ?string; + + /** + * @return array|null + */ + public function getExtra(): ?array; } diff --git a/src/External/Dto/Extra.php b/src/External/Dto/Extra.php index b64d7408e..2705a59e2 100644 --- a/src/External/Dto/Extra.php +++ b/src/External/Dto/Extra.php @@ -16,11 +16,13 @@ final class Extra implements ExtraDtoInterface /** * @param OptionDtoInterface|array|null $deposit * @param OptionDtoInterface|array|null $withdraw + * @param array|null $extra */ public function __construct( OptionDtoInterface|array|null $deposit, OptionDtoInterface|array|null $withdraw, - private readonly ?string $uuid = null + private readonly ?string $uuid = null, + private readonly ?array $extra = null ) { $this->deposit = $deposit instanceof OptionDtoInterface ? $deposit : new Option($deposit); $this->withdraw = $withdraw instanceof OptionDtoInterface ? $withdraw : new Option($withdraw); @@ -40,4 +42,12 @@ public function getUuid(): ?string { return $this->uuid; } + + /** + * @return array|null + */ + public function getExtra(): ?array + { + return $this->extra; + } } diff --git a/src/Internal/Assembler/TransferDtoAssembler.php b/src/Internal/Assembler/TransferDtoAssembler.php index 12ca43016..93605bf64 100644 --- a/src/Internal/Assembler/TransferDtoAssembler.php +++ b/src/Internal/Assembler/TransferDtoAssembler.php @@ -18,6 +18,7 @@ public function __construct( ) { } + /** @param array|null $extra */ public function create( int $depositId, int $withdrawId, @@ -26,7 +27,8 @@ public function create( Model $toModel, int $discount, string $fee, - ?string $uuid + ?string $uuid, + ?array $extra, ): TransferDtoInterface { return new TransferDto( $uuid ?? $this->uuidService->uuid4(), @@ -37,6 +39,7 @@ public function create( $toModel->getKey(), $discount, $fee, + $extra, $this->clockService->now(), $this->clockService->now(), ); diff --git a/src/Internal/Assembler/TransferDtoAssemblerInterface.php b/src/Internal/Assembler/TransferDtoAssemblerInterface.php index 4e6fbc9b3..4300f0c2d 100644 --- a/src/Internal/Assembler/TransferDtoAssemblerInterface.php +++ b/src/Internal/Assembler/TransferDtoAssemblerInterface.php @@ -9,6 +9,7 @@ interface TransferDtoAssemblerInterface { + /** @param array|null $extra */ public function create( int $depositId, int $withdrawId, @@ -17,6 +18,7 @@ public function create( Model $toModel, int $discount, string $fee, - ?string $uuid + ?string $uuid, + ?array $extra, ): TransferDtoInterface; } diff --git a/src/Internal/Assembler/TransferLazyDtoAssembler.php b/src/Internal/Assembler/TransferLazyDtoAssembler.php index f341c1a68..f2ed0cf46 100644 --- a/src/Internal/Assembler/TransferLazyDtoAssembler.php +++ b/src/Internal/Assembler/TransferLazyDtoAssembler.php @@ -11,6 +11,7 @@ final class TransferLazyDtoAssembler implements TransferLazyDtoAssemblerInterface { + /** @param array|null $extra */ public function create( Wallet $fromWallet, Wallet $toWallet, @@ -19,8 +20,19 @@ public function create( TransactionDtoInterface $withdrawDto, TransactionDtoInterface $depositDto, string $status, - ?string $uuid + ?string $uuid, + ?array $extra, ): TransferLazyDtoInterface { - return new TransferLazyDto($fromWallet, $toWallet, $discount, $fee, $withdrawDto, $depositDto, $status, $uuid); + return new TransferLazyDto( + $fromWallet, + $toWallet, + $discount, + $fee, + $withdrawDto, + $depositDto, + $status, + $uuid, + $extra, + ); } } diff --git a/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php b/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php index 6f007b035..08d348dc1 100644 --- a/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php +++ b/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php @@ -10,6 +10,7 @@ interface TransferLazyDtoAssemblerInterface { + /** @param array|null $extra */ public function create( Wallet $fromWallet, Wallet $toWallet, @@ -18,6 +19,7 @@ public function create( TransactionDtoInterface $withdrawDto, TransactionDtoInterface $depositDto, string $status, - ?string $uuid + ?string $uuid, + ?array $extra, ): TransferLazyDtoInterface; } diff --git a/src/Internal/Dto/BasketDto.php b/src/Internal/Dto/BasketDto.php index 22b605461..0bd162f19 100644 --- a/src/Internal/Dto/BasketDto.php +++ b/src/Internal/Dto/BasketDto.php @@ -12,10 +12,12 @@ /** * @param non-empty-array $items * @param array $meta + * @param array|null $extra */ public function __construct( private array $items, - private array $meta + private array $meta, + private ?array $extra, ) { } @@ -53,4 +55,12 @@ public function items(): array { return $this->items; } + + /** + * @return array|null + */ + public function extra(): ?array + { + return $this->extra; + } } diff --git a/src/Internal/Dto/BasketDtoInterface.php b/src/Internal/Dto/BasketDtoInterface.php index 32bcd1c21..a8d0f0a6a 100644 --- a/src/Internal/Dto/BasketDtoInterface.php +++ b/src/Internal/Dto/BasketDtoInterface.php @@ -17,6 +17,11 @@ public function total(): int; */ public function meta(): array; + /** + * @return array|null + */ + public function extra(): ?array; + /** * @return non-empty-array */ diff --git a/src/Internal/Dto/TransferDto.php b/src/Internal/Dto/TransferDto.php index cf604e4d0..cda55fe4d 100644 --- a/src/Internal/Dto/TransferDto.php +++ b/src/Internal/Dto/TransferDto.php @@ -9,6 +9,7 @@ /** @immutable */ final readonly class TransferDto implements TransferDtoInterface { + /** @param array|null $extra */ public function __construct( private string $uuid, private int $depositId, @@ -18,6 +19,7 @@ public function __construct( private int $toId, private int $discount, private string $fee, + private ?array $extra, private DateTimeImmutable $createdAt, private DateTimeImmutable $updatedAt, ) { @@ -63,6 +65,14 @@ public function getFee(): string return $this->fee; } + /** + * @return array|null + */ + public function getExtra(): ?array + { + return $this->extra; + } + public function getCreatedAt(): DateTimeImmutable { return $this->createdAt; diff --git a/src/Internal/Dto/TransferDtoInterface.php b/src/Internal/Dto/TransferDtoInterface.php index 2018de133..21c4364dc 100644 --- a/src/Internal/Dto/TransferDtoInterface.php +++ b/src/Internal/Dto/TransferDtoInterface.php @@ -24,6 +24,9 @@ public function getDiscount(): int; public function getFee(): string; + /** @return array|null */ + public function getExtra(): ?array; + public function getCreatedAt(): DateTimeImmutable; public function getUpdatedAt(): DateTimeImmutable; diff --git a/src/Internal/Dto/TransferLazyDto.php b/src/Internal/Dto/TransferLazyDto.php index c0332479e..8fb66cc75 100644 --- a/src/Internal/Dto/TransferLazyDto.php +++ b/src/Internal/Dto/TransferLazyDto.php @@ -9,6 +9,9 @@ /** @immutable */ final readonly class TransferLazyDto implements TransferLazyDtoInterface { + /** + * @param array|null $extra + */ public function __construct( private Wallet $fromWallet, private Wallet $toWallet, @@ -17,7 +20,8 @@ public function __construct( private TransactionDtoInterface $withdrawDto, private TransactionDtoInterface $depositDto, private string $status, - private ?string $uuid + private ?string $uuid, + private ?array $extra, ) { } @@ -60,4 +64,12 @@ public function getUuid(): ?string { return $this->uuid; } + + /** + * @return array|null + */ + public function getExtra(): ?array + { + return $this->extra; + } } diff --git a/src/Internal/Dto/TransferLazyDtoInterface.php b/src/Internal/Dto/TransferLazyDtoInterface.php index 7f983b485..e02e23aaa 100644 --- a/src/Internal/Dto/TransferLazyDtoInterface.php +++ b/src/Internal/Dto/TransferLazyDtoInterface.php @@ -23,4 +23,7 @@ public function getDepositDto(): TransactionDtoInterface; public function getStatus(): string; public function getUuid(): ?string; + + /** @return array|null */ + public function getExtra(): ?array; } diff --git a/src/Internal/Repository/TransferRepository.php b/src/Internal/Repository/TransferRepository.php index b89bcb0e3..19734db4a 100644 --- a/src/Internal/Repository/TransferRepository.php +++ b/src/Internal/Repository/TransferRepository.php @@ -6,6 +6,7 @@ use Bavix\Wallet\Internal\Dto\TransferDtoInterface; use Bavix\Wallet\Internal\Query\TransferQueryInterface; +use Bavix\Wallet\Internal\Service\JsonServiceInterface; use Bavix\Wallet\Internal\Transform\TransferDtoTransformerInterface; use Bavix\Wallet\Models\Transfer; @@ -13,6 +14,7 @@ { public function __construct( private TransferDtoTransformerInterface $transformer, + private JsonServiceInterface $jsonService, private Transfer $transfer ) { } @@ -22,7 +24,14 @@ public function __construct( */ public function insert(array $objects): void { - $values = array_map(fn (TransferDtoInterface $dto): array => $this->transformer->extract($dto), $objects); + $values = []; + foreach ($objects as $object) { + $values[] = array_map( + fn ($value) => is_array($value) ? $this->jsonService->encode($value) : $value, + $this->transformer->extract($object) + ); + } + $this->transfer->newQuery() ->insert($values) ; diff --git a/src/Internal/Transform/TransferDtoTransformer.php b/src/Internal/Transform/TransferDtoTransformer.php index 6aee37dad..9dbd2bda9 100644 --- a/src/Internal/Transform/TransferDtoTransformer.php +++ b/src/Internal/Transform/TransferDtoTransformer.php @@ -19,6 +19,7 @@ public function extract(TransferDtoInterface $dto): array 'to_id' => $dto->getToId(), 'discount' => $dto->getDiscount(), 'fee' => $dto->getFee(), + 'extra' => $dto->getExtra(), 'created_at' => $dto->getCreatedAt(), 'updated_at' => $dto->getUpdatedAt(), ]; diff --git a/src/Internal/Transform/TransferDtoTransformerInterface.php b/src/Internal/Transform/TransferDtoTransformerInterface.php index 6c72b54d8..c6e62aad6 100644 --- a/src/Internal/Transform/TransferDtoTransformerInterface.php +++ b/src/Internal/Transform/TransferDtoTransformerInterface.php @@ -19,6 +19,7 @@ interface TransferDtoTransformerInterface * to_id: int|string, * discount: int, * fee: string, + * extra: array|null, * created_at: DateTimeImmutable, * updated_at: DateTimeImmutable, * } diff --git a/src/Models/Transfer.php b/src/Models/Transfer.php index 661c5038d..104c3363e 100644 --- a/src/Models/Transfer.php +++ b/src/Models/Transfer.php @@ -25,6 +25,7 @@ * @property int $to_id * @property string $uuid * @property string $fee + * @property ?array $extra * @property Transaction $deposit * @property Transaction $withdraw * @property DateTimeInterface $created_at @@ -59,6 +60,7 @@ class Transfer extends Model 'to_id', 'uuid', 'fee', + 'extra', 'created_at', 'updated_at', ]; @@ -69,6 +71,7 @@ class Transfer extends Model protected $casts = [ 'deposit_id' => 'int', 'withdraw_id' => 'int', + 'extra' => 'json', ]; public function getTable(): string diff --git a/src/Objects/Cart.php b/src/Objects/Cart.php index ce1c55a2d..3d1c3a327 100644 --- a/src/Objects/Cart.php +++ b/src/Objects/Cart.php @@ -31,6 +31,11 @@ final class Cart implements Countable, CartInterface */ private array $meta = []; + /** + * @var array|null + */ + private ?array $extra = null; + public function __construct( private readonly CastServiceInterface $castService, private readonly MathServiceInterface $math @@ -56,6 +61,25 @@ public function withMeta(array $meta): self return $self; } + /** + * @return array|null + */ + public function getExtra(): ?array + { + return $this->extra; + } + + /** + * @param array $extra + */ + public function withExtra(array $extra): self + { + $self = clone $this; + $self->extra = $extra; + + return $self; + } + /** * @param positive-int $quantity */ @@ -151,7 +175,7 @@ public function getBasketDto(): BasketDtoInterface throw new CartEmptyException('Cart is empty', ExceptionInterface::CART_EMPTY); } - return new BasketDto($items, $this->getMeta()); + return new BasketDto($items, $this->getMeta(), $this->getExtra()); } private function productId(ProductInterface $product): string diff --git a/src/Services/PrepareService.php b/src/Services/PrepareService.php index 55379ae66..46e7abaaa 100644 --- a/src/Services/PrepareService.php +++ b/src/Services/PrepareService.php @@ -145,7 +145,8 @@ public function transferExtraLazy( $withdraw, $deposit, $status, - $extra->getUuid() + $extra->getUuid(), + $extra->getExtra(), ); } } diff --git a/src/Services/TransferService.php b/src/Services/TransferService.php index 18ede128a..fc2b40a8d 100644 --- a/src/Services/TransferService.php +++ b/src/Services/TransferService.php @@ -87,6 +87,7 @@ public function apply(array $objects): array $object->getDiscount(), $object->getFee(), $object->getUuid(), + $object->getExtra(), ); $transfers[] = $transfer; diff --git a/src/Traits/CanExchange.php b/src/Traits/CanExchange.php index 736ce6f7d..5433928df 100644 --- a/src/Traits/CanExchange.php +++ b/src/Traits/CanExchange.php @@ -103,7 +103,8 @@ public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface| $withdrawDto, $depositDto, Transfer::STATUS_EXCHANGE, - $extraDto->getUuid() + $extraDto->getUuid(), + $extraDto->getExtra() ); $transfers = app(TransferServiceInterface::class)->apply([$transferLazyDto]); diff --git a/src/Traits/HasGift.php b/src/Traits/HasGift.php index 975e3c2da..d98d0fe33 100644 --- a/src/Traits/HasGift.php +++ b/src/Traits/HasGift.php @@ -85,6 +85,7 @@ public function gift(Wallet $to, ProductInterface $product, bool $force = false) $castService->getWallet($product), $discount, $fee, + null, null ); diff --git a/tests/Units/Api/TransferHandlerTest.php b/tests/Units/Api/TransferHandlerTest.php index 5c576b910..94ab82ad9 100644 --- a/tests/Units/Api/TransferHandlerTest.php +++ b/tests/Units/Api/TransferHandlerTest.php @@ -6,6 +6,7 @@ use Bavix\Wallet\External\Api\TransferQuery; use Bavix\Wallet\External\Api\TransferQueryHandlerInterface; +use Bavix\Wallet\External\Dto\Extra; use Bavix\Wallet\Test\Infra\Factories\BuyerFactory; use Bavix\Wallet\Test\Infra\Models\Buyer; use Bavix\Wallet\Test\Infra\TestCase; @@ -31,13 +32,39 @@ public function testWalletNotExists(): void self::assertFalse($to->wallet->exists); $transfers = $transferQueryHandler->apply([ - new TransferQuery($from, $to, 100, null), - new TransferQuery($from, $to, 100, null), - new TransferQuery($to, $from, 50, null), + new TransferQuery( + $from, + $to, + 100, + new Extra( + null, + null, + '598c184c-e6d6-4fc2-9640-c1c7acb38093', + ['type' => 'first'], + ), + ), + new TransferQuery($from, $to, 100, + new Extra( + null, + null, + 'f303d60d-c2de-45d0-b9ed-e1487429709a', + ['type' => 'second'], + )), + new TransferQuery($to, $from, 50, + new Extra( + null, + null, + '7f0175fe-99cc-4058-92c6-157f0da18243', + ['type' => 'third'], + )), ]); self::assertSame(-150, $from->balanceInt); self::assertSame(150, $to->balanceInt); self::assertCount(3, $transfers); + + self::assertSame(['type' => 'first'], $transfers['598c184c-e6d6-4fc2-9640-c1c7acb38093']->extra); + self::assertSame(['type' => 'second'], $transfers['f303d60d-c2de-45d0-b9ed-e1487429709a']->extra); + self::assertSame(['type' => 'third'], $transfers['7f0175fe-99cc-4058-92c6-157f0da18243']->extra); } } diff --git a/tests/Units/Domain/BasketTest.php b/tests/Units/Domain/BasketTest.php index 4549f88f0..4cf5d85b6 100644 --- a/tests/Units/Domain/BasketTest.php +++ b/tests/Units/Domain/BasketTest.php @@ -20,7 +20,7 @@ public function testCount(): void $item = new Item(); $productDto1 = new ItemDto($item, 24, null, null); $productDto2 = new ItemDto($item, 26, null, null); - $basket = new BasketDto([$productDto1, $productDto2], []); + $basket = new BasketDto([$productDto1, $productDto2], [], null); self::assertEmpty($basket->meta()); self::assertSame(2, $basket->count()); @@ -39,23 +39,39 @@ public function testMeta(): void /** @var non-empty-array $items */ $items = []; - $basket1 = new BasketDto($items, []); + $basket1 = new BasketDto($items, [], null); self::assertEmpty($basket1->meta()); $basket2 = new BasketDto($items, [ 'hello' => 'world', - ]); + ], null); self::assertSame([ 'hello' => 'world', ], $basket2->meta()); } + public function testExtra(): void + { + /** @var non-empty-array $items */ + $items = []; + + $basket1 = new BasketDto($items, [], null); + self::assertNull($basket1->extra()); + + $basket2 = new BasketDto($items, [], [ + 'hello' => 'world', + ]); + self::assertSame([ + 'hello' => 'world', + ], $basket2->extra()); + } + public function testEmpty(): void { /** @var non-empty-array $items */ $items = []; - $basket = new BasketDto($items, []); + $basket = new BasketDto($items, [], null); self::assertEmpty($basket->items()); self::assertEmpty($basket->meta()); self::assertSame(0, $basket->count()); diff --git a/tests/Units/Domain/CartTest.php b/tests/Units/Domain/CartTest.php index 65621d64b..58eb8b507 100644 --- a/tests/Units/Domain/CartTest.php +++ b/tests/Units/Domain/CartTest.php @@ -37,9 +37,10 @@ public function testCartClone(): void $cart = app(Cart::class); $cartWithItems = $cart->withItems([$product]); - $cartWithMeta = $cart->withMeta([ - 'product_id' => $product->getKey(), - ]); + $cartWithMeta = $cart + ->withMeta(['product_id' => $product->getKey()]) + ->withExtra(['products' => count($cartWithItems->getItems())]) + ; self::assertCount(0, $cart->getItems()); self::assertCount(1, $cartWithItems->getItems()); @@ -48,6 +49,9 @@ public function testCartClone(): void self::assertSame([ 'product_id' => $product->getKey(), ], $cartWithMeta->getMeta()); + self::assertSame([ + 'products' => count($cartWithItems->getItems()), + ], $cartWithMeta->getExtra()); } public function testCartMeta(): void diff --git a/tests/Units/Domain/ExtraTest.php b/tests/Units/Domain/ExtraTest.php index 176480368..e9aa9d755 100644 --- a/tests/Units/Domain/ExtraTest.php +++ b/tests/Units/Domain/ExtraTest.php @@ -41,6 +41,7 @@ public function testExtraTransferWithdraw(): void ], false ), + extra: ['msg' => 'hello world'], ) ); @@ -54,6 +55,9 @@ public function testExtraTransferWithdraw(): void self::assertSame([ 'type' => 'extra-withdraw', ], $transfer->withdraw->meta); + self::assertSame([ + 'msg' => 'hello world', + ], $transfer->extra); } public function testExtraTransferUuidFixed(): void