diff --git a/config/config.php b/config/config.php index 57e131599..cb5e88f51 100644 --- a/config/config.php +++ b/config/config.php @@ -57,83 +57,261 @@ /** * Arbitrary Precision Calculator. * - * 'scale' - length of the mantissa + * The 'scale' option defines the number of decimal places + * that the calculator will use when performing calculations. + * + * @see MathService */ 'math' => [ + /** + * The scale of the calculator. + * + * @var int + */ 'scale' => env('WALLET_MATH_SCALE', 64), ], /** * Storage of the state of the balance of wallets. + * + * This is used to cache the results of calculations + * in order to improve the performance of the package. + * + * @see StorageService */ 'cache' => [ + /** + * The driver for the cache. + * + * @var string + */ 'driver' => env('WALLET_CACHE_DRIVER', 'array'), + + /** + * The time to live for the cache in seconds. + * + * @var int + */ 'ttl' => env('WALLET_CACHE_TTL', 24 * 3600), ], /** * A system for dealing with race conditions. + * + * This is used to protect against race conditions + * when updating the balance of a wallet. + * + * @see LockService */ 'lock' => [ + /** + * The driver for the lock. + * + * The following drivers are supported: + * - array + * - redis + * - memcached + * - database + * + * @var string + */ 'driver' => env('WALLET_LOCK_DRIVER', 'array'), + + /** + * The time to live for the lock in seconds. + * + * @var int + */ 'seconds' => env('WALLET_LOCK_TTL', 1), ], /** * Internal services that can be overloaded. + * + * This section contains the list of services that can be overloaded by + * the user. These services are used internally by the package and are + * critical for it to function properly. + * + * @var array */ 'internal' => [ + /** + * The service for getting the current time. + * + * @var string + */ 'clock' => ClockService::class, + + /** + * The service for getting the database connection. + * + * @var string + */ 'connection' => ConnectionService::class, + + /** + * The service for managing the database. + * + * @var string + */ 'database' => DatabaseService::class, + + /** + * The service for dispatching events. + * + * @var string + */ 'dispatcher' => DispatcherService::class, + + /** + * The service for serializing and deserializing JSON. + * + * @var string + */ 'json' => JsonService::class, + + /** + * The service for handling locks. + * + * @var string + */ 'lock' => LockService::class, + + /** + * The service for performing mathematical operations. + * + * @var string + */ 'math' => MathService::class, + + /** + * The service for managing the state of the wallet. + * + * @var string + */ 'state' => StateService::class, + + /** + * The service for managing the storage of the wallet. + * + * @var string + */ 'storage' => StorageService::class, + + /** + * The service for translating messages. + * + * @var string + */ 'translator' => TranslatorService::class, + + /** + * The service for generating UUIDs. + * + * @var string + */ 'uuid' => UuidFactoryService::class, ], /** * Services that can be overloaded. + * + * Each key is the name of the service, and the value is the fully qualified class name of the service. + * The default service class is provided here. + * + * @var array + * + * @see \Bavix\Wallet\Services */ 'services' => [ + // Service for performing operations related to the assistant. 'assistant' => AssistantService::class, + // Service for handling ATM operations. 'atm' => AtmService::class, + // Service for handling atomic operations. 'atomic' => AtomicService::class, + // Service for managing the user's basket. 'basket' => BasketService::class, + // Service for handling bookkeeping operations. 'bookkeeper' => BookkeeperService::class, + // Service for handling regulation operations. 'regulator' => RegulatorService::class, + // Service for casting values. 'cast' => CastService::class, + // Service for handling consistency operations. 'consistency' => ConsistencyService::class, + // Service for handling discount operations. 'discount' => DiscountService::class, + // Service for handling eager loading operations. 'eager_loader' => EagerLoaderService::class, + // Service for handling exchange operations. 'exchange' => ExchangeService::class, + // Service for formatting values. 'formatter' => FormatterService::class, + // Service for preparing operations. 'prepare' => PrepareService::class, + // Service for handling purchase operations. 'purchase' => PurchaseService::class, + // Service for handling tax operations. 'tax' => TaxService::class, + // Service for handling transaction operations. 'transaction' => TransactionService::class, + // Service for handling transfer operations. 'transfer' => TransferService::class, + // Service for managing wallet operations. 'wallet' => WalletService::class, ], /** * Repositories for fetching data from the database. + * + * Each repository is responsible for fetching data from the database for a specific entity. + * + * @see \Bavix\Wallet\Interfaces\Wallet + * @see \Bavix\Wallet\Interfaces\Transaction + * @see \Bavix\Wallet\Interfaces\Transfer */ 'repositories' => [ + /** + * Repository for fetching transaction data. + * + * @see \Bavix\Wallet\Interfaces\Transaction + */ 'transaction' => TransactionRepository::class, + /** + * Repository for fetching transfer data. + * + * @see \Bavix\Wallet\Interfaces\Transfer + */ 'transfer' => TransferRepository::class, + /** + * Repository for fetching wallet data. + * + * @see \Bavix\Wallet\Interfaces\Wallet + */ 'wallet' => WalletRepository::class, ], /** - * Objects of transformer from DTO to array. + * Defines the mapping of DTO (Data Transfer Object) types to their respective transformer classes. + * Transformers are used to convert DTOs into a structured array format, suitable for further processing + * or output. This allows for a clean separation between the internal data representation and the format + * required by clients or external systems. */ 'transformers' => [ + /** + * Transformer for converting transaction DTOs. + * This transformer handles the conversion of transaction data, ensuring that all necessary + * information is presented in a structured and consistent manner for downstream processing. + */ 'transaction' => TransactionDtoTransformer::class, + + /** + * Transformer for converting transfer DTOs. + * Similar to the transaction transformer, this class is responsible for taking transfer-related + * DTOs and converting them into a standardized array format. This is essential for operations + * involving the movement of funds or assets between accounts or entities. + */ 'transfer' => TransferDtoTransformer::class, ], @@ -141,53 +319,180 @@ * Builder class, needed to create DTO. */ 'assemblers' => [ + /** + * Assembler for creating Availability DTO. + */ 'availability' => AvailabilityDtoAssembler::class, + /** + * Assembler for creating Balance Updated Event DTO. + */ 'balance_updated_event' => BalanceUpdatedEventAssembler::class, + /** + * Assembler for creating Extra DTO. + */ 'extra' => ExtraDtoAssembler::class, + /** + * Assembler for creating Option DTO. + */ 'option' => OptionDtoAssembler::class, + /** + * Assembler for creating Transaction DTO. + */ 'transaction' => TransactionDtoAssembler::class, + /** + * Assembler for creating Transfer Lazy DTO. + */ 'transfer_lazy' => TransferLazyDtoAssembler::class, + /** + * Assembler for creating Transfer DTO. + */ 'transfer' => TransferDtoAssembler::class, + /** + * Assembler for creating Transaction Created Event DTO. + */ 'transaction_created_event' => TransactionCreatedEventAssembler::class, + /** + * Assembler for creating Transaction Query DTO. + */ 'transaction_query' => TransactionQueryAssembler::class, + /** + * Assembler for creating Transfer Query DTO. + */ 'transfer_query' => TransferQueryAssembler::class, ], /** * Package system events. + * + * @var array */ 'events' => [ + /** + * The event triggered when the balance is updated. + */ 'balance_updated' => BalanceUpdatedEvent::class, + + /** + * The event triggered when a wallet is created. + */ 'wallet_created' => WalletCreatedEvent::class, + + /** + * The event triggered when a transaction is created. + */ 'transaction_created' => TransactionCreatedEvent::class, ], /** * Base model 'transaction'. + * + * @see Transaction */ 'transaction' => [ + /** + * The table name for transactions. + * + * This value is used to store transactions in a database. + * + * @see Transaction + */ 'table' => env('WALLET_TRANSACTION_TABLE_NAME', 'transactions'), + + /** + * The model class for transactions. + * + * This value is used to create new transactions. + * + * @see Transaction + */ 'model' => Transaction::class, ], /** * Base model 'transfer'. + * + * Contains the configuration for the transfer model. + * + * @see Transfer */ 'transfer' => [ + /** + * The table name for transfers. + * + * This value is used to store transfers in a database. + * + * @see Transfer + */ 'table' => env('WALLET_TRANSFER_TABLE_NAME', 'transfers'), + + /** + * The model class for transfers. + * + * This value is used to create new transfers. + * + * @see Transfer + */ 'model' => Transfer::class, ], /** * Base model 'wallet'. + * + * Contains the configuration for the wallet model. + * + * @see Wallet */ 'wallet' => [ + /** + * The table name for wallets. + * + * This value is used to store wallets in a database. + * + * @see Wallet + */ 'table' => env('WALLET_WALLET_TABLE_NAME', 'wallets'), + + /** + * The model class for wallets. + * + * This value is used to create new wallets. + * + * @see Wallet + */ 'model' => Wallet::class, + + /** + * The configuration options for creating wallets. + * + * @var array + */ 'creating' => [], + + /** + * The default configuration for wallets. + * + * @var array + */ 'default' => [ + /** + * The name of the default wallet. + * + * @var string + */ 'name' => env('WALLET_DEFAULT_WALLET_NAME', 'Default Wallet'), + + /** + * The slug of the default wallet. + * + * @var string + */ 'slug' => env('WALLET_DEFAULT_WALLET_SLUG', 'default'), + + /** + * The meta information of the default wallet. + * + * @var array + */ 'meta' => [], ], ], diff --git a/ecs.php b/ecs.php index fcf4a7dce..1e605066b 100644 --- a/ecs.php +++ b/ecs.php @@ -17,6 +17,7 @@ $config->skip([ GeneralPhpdocAnnotationRemoveFixer::class, + \PhpCsFixer\Fixer\Import\FullyQualifiedStrictTypesFixer::class, ]); $config->sets([ diff --git a/phpstan.src.baseline.neon b/phpstan.src.baseline.neon index 349091a29..ce5b7ad55 100644 --- a/phpstan.src.baseline.neon +++ b/phpstan.src.baseline.neon @@ -35,6 +35,66 @@ parameters: count: 1 path: src/Internal/Assembler/TransferDtoAssembler.php + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Decorator\\\\StorageServiceLockDecorator\\:\\:multiIncrease\\(\\) should return non\\-empty\\-array\\ but returns non\\-empty\\-array\\\\.$#" + count: 1 + path: src/Internal/Decorator/StorageServiceLockDecorator.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\ItemDto\\:\\:getPricePerItem\\(\\) should return int\\|non\\-empty\\-string\\|null but returns int\\|string\\|null\\.$#" + count: 1 + path: src/Internal/Dto/ItemDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransactionDto\\:\\:getAmount\\(\\) should return float\\|int\\|non\\-empty\\-string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/Internal/Dto/TransactionDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransactionDto\\:\\:getPayableType\\(\\) should return class\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Dto/TransactionDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransactionDto\\:\\:getUuid\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Dto/TransactionDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransferDto\\:\\:getFee\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Dto/TransferDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransferDto\\:\\:getUuid\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Dto/TransferDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransferLazyDto\\:\\:getFee\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Dto/TransferLazyDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Dto\\\\TransferLazyDto\\:\\:getUuid\\(\\) should return non\\-empty\\-string\\|null but returns string\\|null\\.$#" + count: 1 + path: src/Internal/Dto/TransferLazyDto.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Events\\\\BalanceUpdatedEvent\\:\\:getBalance\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Events/BalanceUpdatedEvent.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Events\\\\BalanceUpdatedEvent\\:\\:getWalletUuid\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Events/BalanceUpdatedEvent.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Events\\\\WalletCreatedEvent\\:\\:getWalletUuid\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Events/WalletCreatedEvent.php + - message: "#^Parameter \\#1 \\$name of method Illuminate\\\\Database\\\\ConnectionResolverInterface\\:\\:connection\\(\\) expects string\\|null, mixed given\\.$#" count: 1 @@ -46,10 +106,85 @@ parameters: path: src/Internal/Service/LockService.php - - message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StateService\\:\\:get\\(\\) should return string\\|null but returns mixed\\.$#" + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:abs\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:add\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:ceil\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:div\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:floor\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:mul\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:negative\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:pow\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:round\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathService\\:\\:sub\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/MathService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StateService\\:\\:get\\(\\) should return non\\-empty\\-string\\|null but returns mixed\\.$#" count: 1 path: src/Internal/Service/StateService.php + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StateService\\:\\:get\\(\\) should return non\\-empty\\-string\\|null but returns string\\|null\\.$#" + count: 1 + path: src/Internal/Service/StateService.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StorageService\\:\\:increase\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: src/Internal/Service/StorageService.php + + - + message: "#^Parameter \\#1 \\$number of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:round\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: src/Internal/Service/StorageService.php + + - + message: "#^Parameter \\#1 \\$uuids of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StorageService\\:\\:multiGet\\(\\) expects non\\-empty\\-array\\, non\\-empty\\-array\\ given\\.$#" + count: 1 + path: src/Internal/Service/StorageService.php + + - + message: "#^Parameter \\#2 \\$second of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:add\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: src/Internal/Service/StorageService.php + - message: "#^Method Bavix\\\\Wallet\\\\Models\\\\Transaction\\:\\:wallet\\(\\) should return Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsTo\\ but returns Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsTo\\\\.$#" count: 1 @@ -112,19 +247,134 @@ parameters: - message: "#^Cannot cast mixed to string\\.$#" + count: 2 + path: src/Models/Wallet.php + + - + message: "#^Method Bavix\\\\Wallet\\\\Models\\\\Wallet\\:\\:getCurrencyAttribute\\(\\) should return string but returns mixed\\.$#" count: 1 path: src/Models/Wallet.php + - + message: "#^Parameter \\#1 \\$amount of method Bavix\\\\Wallet\\\\Interfaces\\\\Wallet\\:\\:forceWithdraw\\(\\) expects int\\|non\\-empty\\-string, int\\|string given\\.$#" + count: 2 + path: src/Models/Wallet.php + - message: "#^Parameter \\#1 \\.\\.\\.\\$arrays of function array_merge expects array, mixed given\\.$#" count: 2 path: src/Models/Wallet.php + - + message: "#^Parameter \\#2 \\$amount of method Bavix\\\\Wallet\\\\Interfaces\\\\Wallet\\:\\:forceTransfer\\(\\) expects int\\|non\\-empty\\-string, int\\|string given\\.$#" + count: 2 + path: src/Models/Wallet.php + - message: "#^Property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$table \\(string\\) does not accept mixed\\.$#" count: 1 path: src/Models/Wallet.php + - + message: "#^Parameter \\#1 \\$objects \\(non\\-empty\\-array\\\\) of method Bavix\\\\Wallet\\\\Services\\\\AssistantService\\:\\:getUuids\\(\\) should be compatible with parameter \\$objects \\(non\\-empty\\-array\\\\) of method Bavix\\\\Wallet\\\\Services\\\\AssistantServiceInterface\\:\\:getUuids\\(\\)$#" + count: 1 + path: src/Services/AssistantService.php + + - + message: "#^Parameter \\#1 \\$objects of method Bavix\\\\Wallet\\\\Services\\\\AssistantServiceInterface\\:\\:getUuids\\(\\) expects non\\-empty\\-array\\, non\\-empty\\-array\\ given\\.$#" + count: 1 + path: src/Services/AtmService.php + + - + message: "#^Parameter \\#1 \\$objects of method Bavix\\\\Wallet\\\\Services\\\\AssistantServiceInterface\\:\\:getUuids\\(\\) expects non\\-empty\\-array\\, non\\-empty\\-array\\ given\\.$#" + count: 1 + path: src/Services/AtmService.php + + - + message: "#^Unable to resolve the template type T in call to method Bavix\\\\Wallet\\\\Services\\\\AssistantServiceInterface\\:\\:getUuids\\(\\)$#" + count: 2 + path: src/Services/AtmService.php + + - + message: "#^Parameter \\#1 \\$uuid of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StateServiceInterface\\:\\:drop\\(\\) expects non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Services/AtomicService.php + + - + message: "#^Parameter \\#1 \\$uuids of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StateServiceInterface\\:\\:multiFork\\(\\) expects array\\, non\\-empty\\-array\\ given\\.$#" + count: 1 + path: src/Services/AtomicService.php + + - + message: "#^Parameter \\#2 \\$value of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StateServiceInterface\\:\\:multiFork\\(\\) expects callable\\(\\)\\: array\\, Closure\\(\\)\\: non\\-empty\\-array\\ given\\.$#" + count: 1 + path: src/Services/AtomicService.php + + - + message: "#^Parameter \\#1 \\$inputs of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StorageServiceInterface\\:\\:multiIncrease\\(\\) expects non\\-empty\\-array\\, T of non\\-empty\\-array\\ given\\.$#" + count: 2 + path: src/Services/BookkeeperService.php + + - + message: "#^Parameter \\#1 \\$inputs of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StorageServiceInterface\\:\\:multiSync\\(\\) expects non\\-empty\\-array\\, non\\-empty\\-array\\ given\\.$#" + count: 1 + path: src/Services/BookkeeperService.php + + - + message: "#^Parameter \\#1 \\$uuids of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\StorageServiceInterface\\:\\:multiGet\\(\\) expects non\\-empty\\-array\\, non\\-empty\\-array\\ given\\.$#" + count: 2 + path: src/Services/BookkeeperService.php + + - + message: "#^Parameter \\#1 \\$first of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:compare\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 2 + path: src/Services/ConsistencyService.php + + - + message: "#^Parameter \\#2 \\$second of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:add\\(\\) expects float\\|int\\|non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Services/ConsistencyService.php + + - + message: "#^Parameter \\#1 \\$first of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:sub\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: src/Services/PrepareService.php + + - + message: "#^Parameter \\#1 \\$number of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:negative\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: src/Services/PrepareService.php + + - + message: "#^Parameter \\#2 \\$second of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:add\\(\\) expects float\\|int\\|non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Services/PrepareService.php + + - + message: "#^Parameter \\#1 \\$first of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:add\\(\\) expects float\\|int\\|non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Services/RegulatorService.php + + - + message: "#^Parameter \\#1 \\$number of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:negative\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: src/Services/RegulatorService.php + + - + message: "#^PHPDoc tag @return with type string is incompatible with native type bool\\.$#" + count: 1 + path: src/Services/RegulatorServiceInterface.php + + - + message: "#^Parameter \\#1 \\$first of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:mul\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: src/Services/TaxService.php + + - + message: "#^Parameter \\#2 \\$value of method Bavix\\\\Wallet\\\\Services\\\\RegulatorServiceInterface\\:\\:increase\\(\\) expects float\\|int\\|non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Services/TransactionService.php + - message: "#^Parameter \\#1 \\$attributes of method Bavix\\\\Wallet\\\\Internal\\\\Repository\\\\WalletRepositoryInterface\\:\\:create\\(\\) expects array\\{holder_type\\: string, holder_id\\: int\\|string, name\\: string, slug\\?\\: string, uuid\\: string, description\\?\\: string, meta\\: array\\|null, balance\\?\\: int, \\.\\.\\.\\}, non\\-empty\\-array given\\.$#" count: 1 diff --git a/phpstan.tests.baseline.neon b/phpstan.tests.baseline.neon index 67d8873d2..1ee7bcf51 100644 --- a/phpstan.tests.baseline.neon +++ b/phpstan.tests.baseline.neon @@ -1,5 +1,30 @@ parameters: ignoreErrors: + - + message: "#^Parameter \\#2 \\$currency of class Bavix\\\\Wallet\\\\Test\\\\Infra\\\\Values\\\\Money constructor expects string, mixed given\\.$#" + count: 1 + path: tests/Infra/PackageModels/TransactionMoney.php + + - + message: "#^Parameter \\#1 \\$first of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:mul\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: tests/Infra/Services/ExchangeUsdToBtcService.php + + - + message: "#^Parameter \\#1 \\$first of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:mul\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: tests/Infra/Services/MyExchangeService.php + + - + message: "#^Parameter \\#2 \\$second of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:div\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: tests/Infra/Services/MyExchangeService.php + + - + message: "#^Parameter \\#2 \\$second of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:mul\\(\\) expects float\\|int\\|non\\-empty\\-string, float\\|int\\|string given\\.$#" + count: 1 + path: tests/Infra/Services/MyExchangeService.php + - message: "#^Class Bavix\\\\Wallet\\\\Test\\\\Infra\\\\TestCase is not final\\.$#" count: 1 @@ -9,3 +34,8 @@ parameters: message: "#^Method Bavix\\\\Wallet\\\\Test\\\\Infra\\\\TestCase\\:\\:setUp\\(\\) is not final, but since the containing class is abstract, it should be\\.$#" count: 1 path: tests/Infra/TestCase.php + + - + message: "#^Parameter \\#1 \\$number of method Bavix\\\\Wallet\\\\Internal\\\\Service\\\\MathServiceInterface\\:\\:abs\\(\\) expects float\\|int\\|non\\-empty\\-string, string given\\.$#" + count: 1 + path: tests/Units/Service/MathTest.php diff --git a/src/Interfaces/CartInterface.php b/src/Interfaces/CartInterface.php index 8a27c59dc..a5c61a086 100644 --- a/src/Interfaces/CartInterface.php +++ b/src/Interfaces/CartInterface.php @@ -8,12 +8,19 @@ use Bavix\Wallet\Internal\Exceptions\CartEmptyException; /** - * A kind of cart hydrate, needed for a smooth transition from a convenient dto to a less convenient internal dto. + * The `CartInterface` is a kind of cart hydrate, needed for a smooth transition + * from a convenient DTO to a less convenient internal DTO. */ interface CartInterface { /** - * @throws CartEmptyException + * Returns the basket DTO containing the products and their metadata. + * + * When the cart is empty, this method will throw a `CartEmptyException`. + * + * @return BasketDtoInterface The basket DTO. + * + * @throws CartEmptyException If the cart is empty. */ public function getBasketDto(): BasketDtoInterface; } diff --git a/src/Interfaces/Confirmable.php b/src/Interfaces/Confirmable.php index 3c9c08f34..6196aef43 100644 --- a/src/Interfaces/Confirmable.php +++ b/src/Interfaces/Confirmable.php @@ -18,38 +18,95 @@ interface Confirmable { /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws ConfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Confirm the transaction. + * + * This method confirms the given transaction if it is not already confirmed. + * + * @param Transaction $transaction The transaction to confirm. + * @return bool Returns true if the transaction was confirmed, false otherwise. + * + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If there are insufficient funds. + * @throws ConfirmedInvalid If the transaction is already confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function confirm(Transaction $transaction): bool; + /** + * Safely confirms the transaction. + * + * This method attempts to confirm the given transaction. If an exception occurs during the confirmation process, + * it will be caught and handled. If the confirmation is successful, true will be returned. If an exception occurs, + * false will be returned. + * + * @param Transaction $transaction The transaction to confirm. + * @return bool Returns true if the transaction was confirmed, false otherwise. + * + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If there are insufficient funds. + * @throws ConfirmedInvalid If the transaction is already confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. + */ public function safeConfirm(Transaction $transaction): bool; /** - * @throws UnconfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Reset the confirmation of the transaction. + * + * This method is used to remove the confirmation from a transaction. + * If the transaction is already confirmed, a `ConfirmedInvalid` exception will be thrown. + * If the transaction does not belong to the wallet, a `WalletOwnerInvalid` exception will be thrown. + * If the transaction was not found, a `RecordNotFoundException` will be thrown. + * + * @param Transaction $transaction The transaction to reset. + * @return bool Returns true if the confirmation was reset, false otherwise. + * + * @throws UnconfirmedInvalid If the transaction is not confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function resetConfirm(Transaction $transaction): bool; + /** + * Safely reset the confirmation of the transaction. + * + * This method is used to remove the confirmation from a transaction. + * If the transaction is already confirmed, the confirmation will be reset. + * If the transaction does not belong to the wallet, a `WalletOwnerInvalid` exception will be thrown. + * If the transaction was not found, a `RecordNotFoundException` will be thrown. + * + * @param Transaction $transaction The transaction to reset. + * @return bool Returns true if the confirmation was reset, false otherwise. + */ public function safeResetConfirm(Transaction $transaction): bool; /** - * @throws ConfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Force confirm the transaction. + * + * This method forces the confirmation of the given transaction even if it is already confirmed. + * If the transaction is already confirmed, a `ConfirmedInvalid` exception will be thrown. + * If the transaction does not belong to the wallet, a `WalletOwnerInvalid` exception will be thrown. + * If the transaction was not found, a `RecordNotFoundException` will be thrown. + * + * @param Transaction $transaction The transaction to confirm. + * @return bool Returns true if the transaction was confirmed, false otherwise. + * + * @throws ConfirmedInvalid If the transaction is already confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function forceConfirm(Transaction $transaction): bool; } diff --git a/src/Interfaces/Customer.php b/src/Interfaces/Customer.php index cf56b3286..bdbf82117 100644 --- a/src/Interfaces/Customer.php +++ b/src/Interfaces/Customer.php @@ -17,101 +17,229 @@ interface Customer extends Wallet { /** - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Purchase a product without payment. + * + * This method allows the user to purchase the provided product without any payment involved. + * If the purchase is successful, the method returns the transfer object representing the purchase. + * + * @param ProductInterface $product The product to be purchased. + * @return Transfer The transfer object representing the purchase. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the wallet or the product record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ModelNotFoundException If the wallet or the product is not found. + * @throws ExceptionInterface If an exception occurs. */ public function payFree(ProductInterface $product): Transfer; + /** + * Attempts to purchase a product without payment. + * + * This method attempts to purchase the provided product without payment. If the purchase is successful, + * the method returns the transfer object. If the purchase fails due to insufficient funds or + * empty balance, it returns null. If the purchase fails due to a reason + * other than the above, it throws a more specific exception. + * + * @param ProductInterface $product The product to be purchased. + * @param bool $force [optional] Whether to force the purchase. Defaults to false. + * @return Transfer|null The transfer object representing the purchase, or null if the purchase fails. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + */ public function safePay(ProductInterface $product, bool $force = false): ?Transfer; /** - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Pays for the given product. + * + * This method pays for the provided product. If the payment is successful, + * the method returns the transfer object. If the payment fails due to insufficient funds or + * empty balance, it throws an exception. If the payment fails due to a reason + * other than the above, it throws a more specific exception. + * + * @param ProductInterface $product The product to pay for. + * @param bool $force [optional] Whether to force the payment. Defaults to false. + * @return Transfer The transfer object representing the payment. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function pay(ProductInterface $product, bool $force = false): Transfer; /** - * @throws ProductEnded - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Forces the payment of the given product. + * + * This method forcefully attempts to pay the given product. If the payment is successful, + * the method returns the transfer object. If the payment fails due to insufficient funds or + * empty balance, it throws an exception. If the payment fails due to a reason + * other than the above, it throws a more specific exception. + * + * @throws ProductEnded If the product is ended. + * @throws RecordNotFoundException If the product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the product. + * @throws TransactionFailedException If the payment transaction fails. + * @throws ExceptionInterface If the payment fails for any other reason. */ public function forcePay(ProductInterface $product): Transfer; + /** + * Safely refunds the given product. + * + * This method attempts to refund the given product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or + * empty balance, it returns false. If the refund fails due to a reason + * other than the above, it throws a more specific exception. + * + * @param ProductInterface $product The product to be refunded. + * @param bool $force Whether to force the refund. + * @param bool $gifts Whether to refund gifts. + * @return bool Whether the refund was successful. + * + * @throws ProductEnded If the product is ended. + * @throws RecordNotFoundException If the product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ExceptionInterface If the refund fails for any other reason. + */ public function safeRefund(ProductInterface $product, bool $force = false, bool $gifts = false): bool; /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds the given product. + * + * This method attempts to refund the given product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or + * empty balance, it returns false. If the refund fails due to a reason + * other than the above, it throws a more specific exception. + * + * @throws BalanceIsEmpty If the wallet's balance is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the refund transaction fails. + * @throws ExceptionInterface If the refund fails for any other reason. */ public function refund(ProductInterface $product, bool $force = false, bool $gifts = false): bool; /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Force refunds a gift product. + * + * This method forcefully attempts to refund a gift product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or + * empty balance, it throws an exception. If the refund fails due to a reason + * other than the above, it throws a more specific exception. + * + * @throws BalanceIsEmpty If the wallet's balance is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the gift product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the gift product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the refund transaction fails. + * @throws ExceptionInterface If the refund fails for any other reason. */ public function forceRefund(ProductInterface $product, bool $gifts = false): bool; + /** + * Safely refunds a gift product. + * + * This method attempts to refund a gift product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or + * empty balance, it returns false. If the refund fails due to a reason + * other than the above, it throws a more specific exception. + * + * @throws BalanceIsEmpty If the wallet's balance is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the gift product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the gift product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the refund transaction fails. + * @throws ExceptionInterface If the refund fails for any other reason. + */ public function safeRefundGift(ProductInterface $product, bool $force = false): bool; /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds a gift product. + * + * This method attempts to refund a gift product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or + * empty balance, it throws an exception. If the refund fails due to a reason + * other than the above, it throws a more specific exception. + * + * @throws BalanceIsEmpty If the wallet's balance is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the gift product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the gift product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the refund transaction fails. + * @throws ExceptionInterface If the refund fails for any other reason. */ public function refundGift(ProductInterface $product, bool $force = false): bool; /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Forcibly refunds a gift product. + * + * This method attempts to forcibly refund a gift product. If the refund is successful, + * the method returns true. If the refund fails, it throws an exception. + * + * @return bool True if the refund is successful, false otherwise. + * + * @throws RecordNotFoundException If the gift product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the gift product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the wallet for the gift product cannot be found. + * @throws ExceptionInterface If an unspecified exception occurs. */ public function forceRefundGift(ProductInterface $product): bool; /** - * @return non-empty-array + * Pay for all items in the given cart. * - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method pays for all items in the provided cart. If the payment is successful, + * the method returns an array of Transfer instances representing the successfully paid items. + * If the payment fails, an empty array is returned. + * + * @return non-empty-array An array of Transfer instances representing the successfully paid items. + * + * @throws ProductEnded If any of the items in the cart has expired. + * @throws BalanceIsEmpty If the customer's balance is empty. + * @throws InsufficientFunds If the customer's balance is not enough to cover the cost of all items in the cart. + * @throws RecordNotFoundException If any of the items in the cart was not found. + * @throws RecordsNotFoundException If no items were found in the cart. + * @throws TransactionFailedException If the payment transaction failed. + * @throws ExceptionInterface If any other exception occurred during the payment process. */ public function payFreeCart(CartInterface $cart): array; /** - * @return Transfer[] + * Safely pays for the items in the given cart. + * + * This method attempts to pay for all items in the provided cart. If the payment is successful, + * the method returns an array of Transfer instances. If the payment fails, an empty array is returned. + * + * @return Transfer[] An array of Transfer instances representing the successfully paid items, or an empty array if the payment failed. */ public function safePayCart(CartInterface $cart, bool $force = false): array; /** + * Pays for the items in the given cart. + * + * This method pays for all items in the provided cart. If the payment is successful, + * the method returns an array of Transfer instances. If the payment fails, the method throws an exception. + * * @return non-empty-array * * @throws ProductEnded @@ -125,6 +253,16 @@ public function safePayCart(CartInterface $cart, bool $force = false): array; public function payCart(CartInterface $cart, bool $force = false): array; /** + * Forcibly pays for the items in the given cart. + * + * This method attempts to pay for all items in the provided cart. + * If the payment is successful, the method returns an array of + * Transfer instances. If the payment fails, the method throws + * an exception. + * + * Please note that paying for a cart is a complex process and may + * involve multiple transactions and database queries. + * * @return non-empty-array * * @throws ProductEnded @@ -135,54 +273,112 @@ public function payCart(CartInterface $cart, bool $force = false): array; */ public function forcePayCart(CartInterface $cart): array; + /** + * Refunds all items in the cart and returns true if successful. + * If refund fails, returns false. + */ public function safeRefundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool; /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds all items in the cart. + * + * This method attempts to refund all items in the provided cart. + * If the refund is successful, the method returns true. If the refund + * fails, the method returns false. + * + * Please note that refunding a cart is a complex process and may + * involve multiple transactions and database queries. + * + * @param CartInterface $cart The cart containing the items to be refunded. + * @param bool $force Whether to force the refund even if the item is not + * refundable. + * @param bool $gifts Whether to refund gifts as well. + * + * @throws BalanceIsEmpty If the customer does not have enough balance. + * @throws InsufficientFunds If the customer does not have enough balance to + * refund all items in the cart. + * @throws RecordNotFoundException If a record is not found. + * @throws RecordsNotFoundException If records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ModelNotFoundException If a model is not found. + * @throws ExceptionInterface If any other exception occurs. */ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool; /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds all items in the cart safely. + * + * This method attempts to refund all items in the provided cart safely. + * If the refund is successful, the method returns true. If the refund + * fails, the method returns false. + * + * @param CartInterface $cart The cart containing the items to be refunded. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + * + * @throws RecordNotFoundException If the cart or any of its items are not found. + * @throws RecordsNotFoundException If the records for the cart or any of its items are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model for the cart or any of its items is not found. + * @throws ExceptionInterface If any other exception occurs during the refund process. */ public function forceRefundCart(CartInterface $cart, bool $gifts = false): bool; + /** + * Refunds all gifts in the cart safely. + * + * This method attempts to refund all gifts in the provided cart safely. + * If the refund is successful, the method returns true. If the refund + * fails, the method returns false. + * + * @param CartInterface $cart The cart containing the gifts to be refunded. + * @param bool $force Whether to force the refund even if the balance is empty. + */ public function safeRefundGiftCart(CartInterface $cart, bool $force = false): bool; /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds all gifts in the cart. + * + * This method attempts to refund all gifts in the provided cart. + * If the refund is successful, the method returns true. If the refund + * fails, the method throws an exception. + * + * @param CartInterface $cart The cart containing the gifts to be refunded. + * @param bool $force Whether to force the refund even if the balance is empty. + * @return bool True if the refund was successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty and $force is false. + * @throws InsufficientFunds If the balance of the customer is insufficient to cover the refund. + * @throws RecordNotFoundException If the cart or its items are not found. + * @throws RecordsNotFoundException If the records for the refund are not found. + * @throws TransactionFailedException If the transaction fails for any reason. + * @throws ModelNotFoundException If the wallet or the transfer is not found. + * @throws ExceptionInterface If any other exception occurs during the refund process. */ public function refundGiftCart(CartInterface $cart, bool $force = false): bool; /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Forcefully refunds all gifts in the cart. + * + * @param CartInterface $cart The cart to refund gifts from. + * @return bool True if the gift refund was successful, false otherwise. + * + * @throws RecordNotFoundException If the cart or its items are not found. + * @throws RecordsNotFoundException If the records for the refund are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If an unexpected error occurs. */ public function forceRefundGiftCart(CartInterface $cart): bool; /** * Checks acquired product your wallet. * - * @deprecated The method is slow and will be removed in the future + * Deprecated: This method is slow and will be removed in the future. + * Instead, use the `PurchaseServiceInterface` interface. + * With it, you can check the availability of all products with one request, + * there will be no N-queries in the database. + * * @see PurchaseServiceInterface */ public function paid(ProductInterface $product, bool $gifts = false): ?Transfer; diff --git a/src/Interfaces/Discount.php b/src/Interfaces/Discount.php index fc57c4846..c4a9e43b4 100644 --- a/src/Interfaces/Discount.php +++ b/src/Interfaces/Discount.php @@ -6,5 +6,13 @@ interface Discount { + /** + * Receive a personal discount for the client. + * + * This method should return a numeric value of how much cheaper the selected product will be for a specific customer. + * + * The method returns a value that will be subtracted from the cost of the product. + * For example, a product costs 100 and the method returns 75, then the product for the client will cost 25. + */ public function getPersonalDiscount(Customer $customer): float|int; } diff --git a/src/Interfaces/Exchangeable.php b/src/Interfaces/Exchangeable.php index cb57281cc..88cee7bad 100644 --- a/src/Interfaces/Exchangeable.php +++ b/src/Interfaces/Exchangeable.php @@ -16,29 +16,58 @@ interface Exchangeable { /** - * @param ExtraDtoInterface|array|null $meta - * - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Exchange currency from this wallet to another wallet. + * + * @param Wallet $to The wallet to exchange the currency to. + * @param int|non-empty-string $amount The amount to exchange. + * @param ExtraDtoInterface|array|null $meta The extra data for the transaction. + * @return Transfer The created transfer. + * + * @throws BalanceIsEmpty if the wallet does not have enough funds to make the exchange. + * @throws InsufficientFunds if the wallet does not have enough funds to make the exchange. + * @throws RecordNotFoundException if the wallet does not exist. + * @throws RecordsNotFoundException if the wallet does not exist. + * @throws TransactionFailedException if the transaction fails. + * @throws ExceptionInterface if an unexpected error occurs. */ public function exchange(Wallet $to, int|string $amount, ExtraDtoInterface|array|null $meta = null): Transfer; /** - * @param ExtraDtoInterface|array|null $meta + * Safely exchanges currency from this wallet to another wallet. + * + * If an error occurs during the process, null is returned. + * + * @param Wallet $to The wallet to exchange the currency to. + * @param int|non-empty-string $amount The amount to exchange. + * @param ExtraDtoInterface|array|null $meta The extra data for the transaction. + * @return null|Transfer The created transfer, or null if an error occurred. */ - public function safeExchange(Wallet $to, int|string $amount, ExtraDtoInterface|array|null $meta = null): ?Transfer; + public function safeExchange( + Wallet $to, + int|string $amount, + ExtraDtoInterface|array|null $meta = null + ): ?Transfer; /** - * @param ExtraDtoInterface|array|null $meta + * Force exchange currency from this wallet to another wallet. + * + * This method will throw an exception if the exchange is not possible. + * + * @param Wallet $to The wallet to exchange the currency to. + * @param int|non-empty-string $amount The amount to exchange. + * @param ExtraDtoInterface|array|null $meta The extra data for the transaction. + * @return Transfer The created transfer. + * + * @throws RecordNotFoundException If the wallet does not exist. + * @throws RecordsNotFoundException If the wallet does not exist. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an unexpected error occurs. * - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @see Exchangeable::exchange() */ - public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface|array|null $meta = null): Transfer; + public function forceExchange( + Wallet $to, + int|string $amount, + ExtraDtoInterface|array|null $meta = null + ): Transfer; } diff --git a/src/Interfaces/MaximalTaxable.php b/src/Interfaces/MaximalTaxable.php index c06daa052..88b58e1c3 100644 --- a/src/Interfaces/MaximalTaxable.php +++ b/src/Interfaces/MaximalTaxable.php @@ -6,5 +6,18 @@ interface MaximalTaxable extends Taxable { + /** + * Returns the maximum fee that can be charged for a payment. + * + * This method returns the maximum fee that can be charged for a payment. + * The return value can be either a float or an integer. + * + * For example, if the commission is 50% and the upper limit is $10, + * then for $100 the commission will be $10. + * + * @return float|int The maximum fee that can be charged for a payment. + * + * @see TaxService::getFee() + */ public function getMaximalFee(): float|int; } diff --git a/src/Interfaces/MinimalTaxable.php b/src/Interfaces/MinimalTaxable.php index 900400c62..ad4b19acb 100644 --- a/src/Interfaces/MinimalTaxable.php +++ b/src/Interfaces/MinimalTaxable.php @@ -6,5 +6,24 @@ interface MinimalTaxable extends Taxable { + /** + * Returns the minimum fee for a transaction. + * + * The minimum fee specifies the minimum amount of money that should be charged for a transaction. + * It can be either a fixed amount or a percentage of the transaction amount. + * The fee can be a float or integer. + * + * @return float|int The minimum fee for the transaction. + * + * @example + * + * If the transaction amount is $100 and the fee is 1% and the minimal fee is $10, + * then the minimum fee for the transaction will be $10. + * + * If the transaction amount is $100 and the fee is 2% and the minimal fee is $5, + * then the minimum fee for the transaction will be $5. + * + * @see \Bavix\Wallet\Services\TaxService::getFee() + */ public function getMinimalFee(): float|int; } diff --git a/src/Interfaces/ProductInterface.php b/src/Interfaces/ProductInterface.php index dc322bbc2..7d084dedb 100644 --- a/src/Interfaces/ProductInterface.php +++ b/src/Interfaces/ProductInterface.php @@ -6,10 +6,37 @@ interface ProductInterface extends Wallet { + /** + * Get the amount of the product that can be purchased by the given customer. + * + * This method is used to determine the amount of the product that can be purchased + * by the given customer. The amount can be an integer or a string. + * + * @param Customer $customer The customer to get the amount for. + * @return int|non-empty-string The amount of the product that can be purchased. + */ public function getAmountProduct(Customer $customer): int|string; /** - * @return array|null + * Get the meta data for the product. + * + * The meta data is an array of key-value pairs that provides additional + * information about the product. It can be used to include a title, + * description, or any other relevant details. + * + * @return array|null The meta data for the product, or null if there is no meta data. + * + * @example + * The return value should be an array with key-value pairs, for example: + * + * return [ + * 'title' => 'Product Title', + * 'description' => 'Product Description', + * 'images' => [ + * 'https://example.com/image1.jpg', + * 'https://example.com/image2.jpg', + * ], + * ]; */ public function getMetaProduct(): ?array; } diff --git a/src/Interfaces/ProductLimitedInterface.php b/src/Interfaces/ProductLimitedInterface.php index 745400e35..05250b9d7 100644 --- a/src/Interfaces/ProductLimitedInterface.php +++ b/src/Interfaces/ProductLimitedInterface.php @@ -7,9 +7,20 @@ interface ProductLimitedInterface extends ProductInterface { /** - * The method is only needed for simple projects. For more complex projects, deprecate this method and redefine the - * "BasketServiceInterface" interface. Typically, in projects, this method always returns false, and the presence - * interface goes to the microservice and receives data on products. + * Check if the customer can buy the product. + * + * @param Customer $customer The customer entity + * @param int $quantity The quantity of the product to buy. Default is 1. + * @param bool $force Flag to force the purchase. Default is false. + * @return bool Returns true if the customer can buy the product, false otherwise. + * + * The method checks if the customer can buy the product based on the quantity and the force flag. + * If the force flag is set to true, the method returns true regardless of the quantity. + * If the force flag is set to false, the method returns true if the quantity of the product is + * greater than or equal to the quantity to buy. + * + * The method does not check if the customer has already bought the product. It is the responsibility + * of the caller to check if the customer has already bought the product. */ public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool; } diff --git a/src/Interfaces/Taxable.php b/src/Interfaces/Taxable.php index ad291d595..0cb4dcab7 100644 --- a/src/Interfaces/Taxable.php +++ b/src/Interfaces/Taxable.php @@ -7,9 +7,20 @@ interface Taxable { /** - * Specify the percentage of the amount. For example, the product costs $100, the equivalent of 15%. That's $115. + * Returns the percentage fee of the amount. * - * Minimum 0; Maximum 100 Example: return 7.5; // 7.5% + * This method should return the percentage fee of the amount. + * For example, if the product costs $100 and the fee is 15%, + * then the fee will be $15 ($115 - $100). + * + * The return value must be a float or integer. + * + * @return float|int The percentage fee of the amount. + * + * @example + * + * If the product costs $100 and the fee is 15%, then the fee will be $15 ($115 - $100). + * If the product costs $100 and the fee is 7.5%, then the fee will be $7.5 ($107.5 - $100). */ public function getFeePercent(): float|int; } diff --git a/src/Interfaces/Wallet.php b/src/Interfaces/Wallet.php index 6ecc37eaa..60170afcf 100644 --- a/src/Interfaces/Wallet.php +++ b/src/Interfaces/Wallet.php @@ -19,51 +19,88 @@ interface Wallet { /** - * @param array|null $meta + * Deposit the specified amount of money into the wallet. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @param int|non-empty-string $amount The amount to deposit. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function deposit(int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction; /** - * @param array|null $meta - * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Withdraw the specified amount of money from the wallet. + * + * @param int|non-empty-string $amount The amount to withdraw. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function withdraw(int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction; /** - * @param array|null $meta + * Forced to withdraw funds from the wallet. + * + * @param int|non-empty-string $amount The amount to withdraw. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forceWithdraw(int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction; /** - * @param ExtraDtoInterface|array|null $meta - * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Transfer funds from this wallet to another. + * + * @param self $wallet The wallet to transfer funds to. + * @param int|non-empty-string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * @return Transfer The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function transfer(self $wallet, int|string $amount, ExtraDtoInterface|array|null $meta = null): Transfer; /** - * @param ExtraDtoInterface|array|null $meta + * Safely transfers funds from this wallet to another. + * + * This method attempts to transfer funds from this wallet to another wallet. + * If an error occurs during the process, null is returned. + * + * @param self $wallet The wallet to transfer funds to. + * @param int|non-empty-string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return null|Transfer The created transaction, or null if an error occurred. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function safeTransfer( self $wallet, @@ -72,12 +109,25 @@ public function safeTransfer( ): ?Transfer; /** - * @param ExtraDtoInterface|array|null $meta + * Forces a transfer of funds from this wallet to another, bypassing certain safety checks. + * + * This method is intended for use in scenarios where a transfer must be completed regardless of + * the usual validation checks (e.g., sufficient funds, wallet status). It is critical to use this + * method with caution as it can result in negative balances or other unintended consequences. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @param self $wallet The wallet instance to which funds will be transferred. + * @param int|non-empty-string $amount The amount of funds to transfer. Can be specified as an integer or a string. + * @param ExtraDtoInterface|array|null $meta Additional metadata associated with the transfer. This + * can be used to store extra information about the transaction, such as reasons for the transfer or + * identifiers linking to other systems. + * @return Transfer Returns a Transfer object representing the completed transaction. + * + * @throws AmountInvalid If the amount specified is invalid (e.g., negative values). + * @throws RecordsNotFoundException If the target wallet cannot be found. + * @throws TransactionFailedException It indicates that the transfer could not be completed due to a failure + * in the underlying transaction system. + * @throws ExceptionInterface A generic exception interface catch-all for any other exceptions that + * might occur during the execution of the transfer. */ public function forceTransfer( self $wallet, @@ -85,29 +135,61 @@ public function forceTransfer( ExtraDtoInterface|array|null $meta = null ): Transfer; + /** + * Checks if the wallet can safely withdraw the specified amount. + * + * @param int|non-empty-string $amount The amount to withdraw. + * @param bool $allowZero Whether to allow withdrawing when the balance is zero. + * @return bool Returns true if the wallet can withdraw the specified amount, false otherwise. + */ public function canWithdraw(int|string $amount, bool $allowZero = false): bool; + /** + * Returns the balance of the wallet as a string. + * + * The balance is the total amount of funds held by the wallet. + * + * @return non-empty-string The balance of the wallet. + */ public function getBalanceAttribute(): string; + /** + * Returns the balance of the wallet as an integer. + * + * @return int The balance of the wallet. This value is the result of + * {@see getBalanceAttribute()} converted to an integer. + */ public function getBalanceIntAttribute(): int; /** - * @return HasMany + * Represents a relationship where a wallet has many transactions. + * + * @return HasMany A collection of transactions associated with this wallet. */ public function walletTransactions(): HasMany; /** - * @return MorphMany + * Returns all the transactions associated with this wallet. + * + * This method returns a morph many relationship that represents all the transactions + * associated with this wallet. The transactions may be of different types, such as + * deposits, withdrawals, or transfers. + * + * @return MorphMany A collection of transactions associated with this wallet. */ public function transactions(): MorphMany; /** - * @return HasMany + * Returns all the transfers sent by this wallet. + * + * @return HasMany A collection of transfers sent by this wallet. */ public function transfers(): HasMany; /** - * @return HasMany + * Returns all the transfers received by this wallet. + * + * @return HasMany A collection of transfers received by this wallet. */ public function receivedTransfers(): HasMany; } diff --git a/src/Interfaces/WalletFloat.php b/src/Interfaces/WalletFloat.php index 4df4147c2..3ac1f5f0a 100644 --- a/src/Interfaces/WalletFloat.php +++ b/src/Interfaces/WalletFloat.php @@ -17,34 +17,57 @@ interface WalletFloat { /** - * @param null|array $meta + * Deposit a float amount of money into the wallet. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @param float|int|non-empty-string $amount The amount to deposit. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ - public function depositFloat(float|int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction; + public function depositFloat( + float|int|string $amount, + ?array $meta = null, + bool $confirmed = true + ): Transaction; /** - * @param null|array $meta - * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Withdraw the specified float amount of money from the wallet. + * + * @param float|int|non-empty-string $amount The amount to withdraw. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ - public function withdrawFloat(float|int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction; + public function withdrawFloat( + float|int|string $amount, + ?array $meta = null, + bool $confirmed = true + ): Transaction; /** - * @param null|array $meta + * Forced to withdraw funds from the wallet. + * + * @param float|int|non-empty-string $amount The amount to withdraw. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forceWithdrawFloat( float|int|string $amount, @@ -53,14 +76,21 @@ public function forceWithdrawFloat( ): Transaction; /** - * @param null|array $meta - * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Transfer funds from this wallet to another. + * + * @param Wallet $wallet The wallet to transfer funds to. + * @param float|int|non-empty-string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return Transfer The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function transferFloat( Wallet $wallet, @@ -69,7 +99,18 @@ public function transferFloat( ): Transfer; /** - * @param ExtraDtoInterface|array|null $meta + * Safely transfers funds from this wallet to another. + * + * This method will not throw an exception if the transfer fails. Instead, it will return null. + * + * @param Wallet $wallet The wallet to transfer funds to. + * @param float|int|non-empty-string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return Transfer|null The created transaction, or null if the transfer fails. + * + * @throws AmountInvalid If the amount is invalid. */ public function safeTransferFloat( Wallet $wallet, @@ -78,12 +119,25 @@ public function safeTransferFloat( ): ?Transfer; /** - * @param ExtraDtoInterface|array|null $meta + * Forces a transfer of funds from this wallet to another, bypassing certain safety checks. + * + * This method is intended for use in scenarios where a transfer must be completed regardless of + * the usual validation checks (e.g., sufficient funds, wallet status). It is critical to use this + * method with caution as it can result in negative balances or other unintended consequences. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @param Wallet $wallet The wallet instance to which funds will be transferred. + * @param float|int|non-empty-string $amount The amount of funds to transfer. Can be specified as a float, int, or string. + * @param ExtraDtoInterface|array|null $meta Additional metadata associated with the transfer. This + * can be used to store extra information about the transaction, such as reasons for the transfer or + * identifiers linking to other systems. + * @return Transfer Returns a Transfer object representing the completed transaction. + * + * @throws AmountInvalid If the amount specified is invalid (e.g., negative values). + * @throws RecordsNotFoundException If the target wallet cannot be found. + * @throws TransactionFailedException If the transfer could not be completed due to a failure + * in the underlying transaction system. + * @throws ExceptionInterface A generic exception interface catch-all for any other exceptions that + * might occur during the execution of the transfer. */ public function forceTransferFloat( Wallet $wallet, @@ -91,9 +145,34 @@ public function forceTransferFloat( ExtraDtoInterface|array|null $meta = null ): Transfer; + /** + * Checks if the user can withdraw the specified amount of funds. + * + * @param float|int|non-empty-string $amount The amount of funds to withdraw. Can be specified as a float, int, or string. + * @return bool Returns TRUE if the withdrawal is possible, FALSE otherwise. + * + * @throws AmountInvalid If the amount is invalid (e.g., negative values). + */ public function canWithdrawFloat(float|int|string $amount): bool; + /** + * Returns the user's current balance as a string value. + * + * This method returns the balance of the wallet as a string. The balance is the total amount of funds + * held by the wallet. + * + * @return non-empty-string The user's current balance as a string (e.g. "1.23"). + */ public function getBalanceFloatAttribute(): string; + /** + * Returns the user's current balance as a float value. + * + * The float value is the actual value of the balance, which may not be + * the same as the value stored in the database. This method is useful + * when you need to perform calculations or formatting on the balance. + * + * @return float The user's current balance as a float (e.g. 1.23). + */ public function getBalanceFloatNumAttribute(): float; } diff --git a/src/Internal/Assembler/AvailabilityDtoAssemblerInterface.php b/src/Internal/Assembler/AvailabilityDtoAssemblerInterface.php index a6cb0bc64..9dbbdfbf8 100644 --- a/src/Internal/Assembler/AvailabilityDtoAssemblerInterface.php +++ b/src/Internal/Assembler/AvailabilityDtoAssemblerInterface.php @@ -10,5 +10,13 @@ interface AvailabilityDtoAssemblerInterface { + /** + * Create a new AvailabilityDto instance. + * + * @param Customer $customer The customer object + * @param BasketDtoInterface $basketDto The basket DTO object + * @param bool $force Whether the creation is forced + * @return AvailabilityDtoInterface The created Availability DTO instance + */ public function create(Customer $customer, BasketDtoInterface $basketDto, bool $force): AvailabilityDtoInterface; } diff --git a/src/Internal/Assembler/BalanceUpdatedEventAssemblerInterface.php b/src/Internal/Assembler/BalanceUpdatedEventAssemblerInterface.php index 4cf7a6544..8615821b5 100644 --- a/src/Internal/Assembler/BalanceUpdatedEventAssemblerInterface.php +++ b/src/Internal/Assembler/BalanceUpdatedEventAssemblerInterface.php @@ -9,5 +9,11 @@ interface BalanceUpdatedEventAssemblerInterface { + /** + * Create a balance updated event from a wallet. + * + * @param Wallet $wallet The wallet to create the event from. + * @return BalanceUpdatedEventInterface The created event. + */ public function create(Wallet $wallet): BalanceUpdatedEventInterface; } diff --git a/src/Internal/Assembler/ExtraDtoAssemblerInterface.php b/src/Internal/Assembler/ExtraDtoAssemblerInterface.php index 7dc6ba48b..d863a811b 100644 --- a/src/Internal/Assembler/ExtraDtoAssemblerInterface.php +++ b/src/Internal/Assembler/ExtraDtoAssemblerInterface.php @@ -9,7 +9,12 @@ interface ExtraDtoAssemblerInterface { /** + * Create ExtraDto. + * * @param ExtraDtoInterface|array|null $data + * The data to create ExtraDto from. Can be either ExtraDtoInterface object, array or null. + * @return ExtraDtoInterface + * The created ExtraDto. */ public function create(ExtraDtoInterface|array|null $data): ExtraDtoInterface; } diff --git a/src/Internal/Assembler/OptionDtoAssemblerInterface.php b/src/Internal/Assembler/OptionDtoAssemblerInterface.php index 1e82c68a0..837243534 100644 --- a/src/Internal/Assembler/OptionDtoAssemblerInterface.php +++ b/src/Internal/Assembler/OptionDtoAssemblerInterface.php @@ -9,7 +9,12 @@ interface OptionDtoAssemblerInterface { /** - * @param null|array $data + * Create an OptionDto object from the given data. + * + * @param array|null $data The data to create the OptionDto from. + * This can be null, in which case an empty + * OptionDto object will be created. + * @return OptionDtoInterface The created OptionDto object. */ public function create(array|null $data): OptionDtoInterface; } diff --git a/src/Internal/Assembler/TransactionCreatedEventAssemblerInterface.php b/src/Internal/Assembler/TransactionCreatedEventAssemblerInterface.php index 443582dcb..7f8938c74 100644 --- a/src/Internal/Assembler/TransactionCreatedEventAssemblerInterface.php +++ b/src/Internal/Assembler/TransactionCreatedEventAssemblerInterface.php @@ -9,5 +9,11 @@ interface TransactionCreatedEventAssemblerInterface { + /** + * Creates a new instance of the TransactionCreatedEventInterface from the given Transaction model. + * + * @param Transaction $transaction The transaction model to create the event from. + * @return TransactionCreatedEventInterface The created event. + */ public function create(Transaction $transaction): TransactionCreatedEventInterface; } diff --git a/src/Internal/Assembler/TransactionDtoAssemblerInterface.php b/src/Internal/Assembler/TransactionDtoAssemblerInterface.php index 874b2b1a0..33c32181d 100644 --- a/src/Internal/Assembler/TransactionDtoAssemblerInterface.php +++ b/src/Internal/Assembler/TransactionDtoAssemblerInterface.php @@ -10,6 +10,8 @@ interface TransactionDtoAssemblerInterface { /** + * Create TransactionDto + * * @param null|array $meta */ public function create( diff --git a/src/Internal/Assembler/TransactionQueryAssemblerInterface.php b/src/Internal/Assembler/TransactionQueryAssemblerInterface.php index 70b36f7f3..50b648859 100644 --- a/src/Internal/Assembler/TransactionQueryAssemblerInterface.php +++ b/src/Internal/Assembler/TransactionQueryAssemblerInterface.php @@ -9,7 +9,10 @@ interface TransactionQueryAssemblerInterface { /** - * @param non-empty-array $uuids + * Creates a new transaction query from the given uuids. + * + * @param non-empty-array $uuids The uuids of the transactions. + * @return TransactionQueryInterface The transaction query. */ public function create(array $uuids): TransactionQueryInterface; } diff --git a/src/Internal/Assembler/TransferDtoAssemblerInterface.php b/src/Internal/Assembler/TransferDtoAssemblerInterface.php index 8da6079f6..55d320ba7 100644 --- a/src/Internal/Assembler/TransferDtoAssemblerInterface.php +++ b/src/Internal/Assembler/TransferDtoAssemblerInterface.php @@ -10,7 +10,17 @@ interface TransferDtoAssemblerInterface { /** - * @param array|null $extra + * Create transfer dto. + * + * @param int $depositId ID of deposit transaction + * @param int $withdrawId ID of withdraw transaction + * @param string $status Status of transfer + * @param Model $fromModel From wallet model + * @param Model $toModel To wallet model + * @param int $discount Discount of transfer + * @param string $fee Fee of transfer + * @param string|null $uuid UUID of transfer + * @param array|null $extra Extra data of transfer */ public function create( int $depositId, diff --git a/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php b/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php index 9bb99dd86..49f35802f 100644 --- a/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php +++ b/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php @@ -11,7 +11,18 @@ interface TransferLazyDtoAssemblerInterface { /** - * @param array|null $extra + * Create transfer lazy dto. + * + * @param Wallet $fromWallet The source wallet. + * @param Wallet $toWallet The destination wallet. + * @param int $discount The discount amount. + * @param string $fee The fee amount. + * @param TransactionDtoInterface $withdrawDto The withdrawal transaction DTO. + * @param TransactionDtoInterface $depositDto The deposit transaction DTO. + * @param string $status The transfer status. + * @param string|null $uuid The transfer UUID. + * @param array|null $extra The extra data. + * @return TransferLazyDtoInterface The transfer lazy DTO. */ public function create( Wallet $fromWallet, diff --git a/src/Internal/Assembler/TransferQueryAssemblerInterface.php b/src/Internal/Assembler/TransferQueryAssemblerInterface.php index 2b5134f92..ce44ac8ad 100644 --- a/src/Internal/Assembler/TransferQueryAssemblerInterface.php +++ b/src/Internal/Assembler/TransferQueryAssemblerInterface.php @@ -9,7 +9,10 @@ interface TransferQueryAssemblerInterface { /** - * @param non-empty-array $uuids + * Create a new TransferQuery object with the given UUIDs. + * + * @param non-empty-array $uuids The UUIDs of the transfers. + * @return TransferQueryInterface The newly created TransferQuery object. */ public function create(array $uuids): TransferQueryInterface; } diff --git a/src/Internal/Assembler/WalletCreatedEventAssemblerInterface.php b/src/Internal/Assembler/WalletCreatedEventAssemblerInterface.php index e8c89a540..25539a5c5 100644 --- a/src/Internal/Assembler/WalletCreatedEventAssemblerInterface.php +++ b/src/Internal/Assembler/WalletCreatedEventAssemblerInterface.php @@ -9,5 +9,8 @@ interface WalletCreatedEventAssemblerInterface { + /** + * Assemble the wallet created event. + */ public function create(Wallet $wallet): WalletCreatedEventInterface; } diff --git a/src/Internal/Dto/AvailabilityDtoInterface.php b/src/Internal/Dto/AvailabilityDtoInterface.php index ee2fe21d3..036d3fbc7 100644 --- a/src/Internal/Dto/AvailabilityDtoInterface.php +++ b/src/Internal/Dto/AvailabilityDtoInterface.php @@ -8,9 +8,18 @@ interface AvailabilityDtoInterface { + /** + * Returns the basket DTO object. + */ public function getBasketDto(): BasketDtoInterface; + /** + * Returns the customer object. + */ public function getCustomer(): Customer; + /** + * Returns whether the creation is forced. + */ public function isForce(): bool; } diff --git a/src/Internal/Dto/BasketDtoInterface.php b/src/Internal/Dto/BasketDtoInterface.php index a8d0f0a6a..6b1cf4d35 100644 --- a/src/Internal/Dto/BasketDtoInterface.php +++ b/src/Internal/Dto/BasketDtoInterface.php @@ -10,24 +10,35 @@ interface BasketDtoInterface extends Countable { + /** + * Calculate the total amount of the basket. + */ public function total(): int; /** + * Retrieve the metadata of the basket. + * * @return array */ public function meta(): array; /** + * Retrieve the extra data of the basket. + * * @return array|null */ public function extra(): ?array; /** + * Retrieve the items of the basket. + * * @return non-empty-array */ public function items(): array; /** + * Retrieve the generator for the items of the basket. + * * @return Generator */ public function cursor(): Generator; diff --git a/src/Internal/Dto/ItemDtoInterface.php b/src/Internal/Dto/ItemDtoInterface.php index 45c18c14e..f5fd0d243 100644 --- a/src/Internal/Dto/ItemDtoInterface.php +++ b/src/Internal/Dto/ItemDtoInterface.php @@ -11,13 +11,26 @@ interface ItemDtoInterface extends Countable { /** + * Returns an array of items in the DTO. + * * @return ProductInterface[] */ public function getItems(): array; + /** + * Returns the price per item in the DTO. + * + * @return int|non-empty-string|null + */ public function getPricePerItem(): int|string|null; + /** + * Returns the product in the DTO. + */ public function getProduct(): ProductInterface; + /** + * Returns the receiving wallet in the DTO. + */ public function getReceiving(): ?Wallet; } diff --git a/src/Internal/Dto/TransactionDtoInterface.php b/src/Internal/Dto/TransactionDtoInterface.php index 9f664e166..063261675 100644 --- a/src/Internal/Dto/TransactionDtoInterface.php +++ b/src/Internal/Dto/TransactionDtoInterface.php @@ -8,26 +8,61 @@ interface TransactionDtoInterface { + /** + * Get the UUID of the transaction. + * + * @return non-empty-string + */ public function getUuid(): string; + /** + * Get the type of the payable. + * + * @return class-string + */ public function getPayableType(): string; + /** + * Get the ID of the payable. + */ public function getPayableId(): int|string; + /** + * Get the ID of the wallet. + */ public function getWalletId(): int; + /** + * Get the type of the transaction. + */ public function getType(): string; + /** + * Get the amount of the transaction. + * + * @return float|int|non-empty-string + */ public function getAmount(): float|int|string; + /** + * Check if the transaction is confirmed. + */ public function isConfirmed(): bool; /** + * Get the meta information of the transaction. + * * @return null|array */ public function getMeta(): ?array; + /** + * Get the created at timestamp of the transaction. + */ public function getCreatedAt(): DateTimeImmutable; + /** + * Get the updated at timestamp of the transaction. + */ public function getUpdatedAt(): DateTimeImmutable; } diff --git a/src/Internal/Dto/TransferDtoInterface.php b/src/Internal/Dto/TransferDtoInterface.php index 4aab8c16e..5de0506d1 100644 --- a/src/Internal/Dto/TransferDtoInterface.php +++ b/src/Internal/Dto/TransferDtoInterface.php @@ -8,28 +8,64 @@ interface TransferDtoInterface { + /** + * Get the UUID of the transfer. + * + * @return non-empty-string + */ public function getUuid(): string; + /** + * Get the ID of the deposit transaction. + */ public function getDepositId(): int; + /** + * Get the ID of the withdraw transaction. + */ public function getWithdrawId(): int; + /** + * Get the status of the transfer. + */ public function getStatus(): string; + /** + * Get the ID of the wallet that the transfer is from. + */ public function getFromId(): int|string; + /** + * Get the ID of the wallet that the transfer is to. + */ public function getToId(): int|string; + /** + * Get the discount amount of the transfer. + */ public function getDiscount(): int; + /** + * Get the fee amount of the transfer. + * + * @return non-empty-string + */ public function getFee(): string; /** + * Get the extra information of the transfer. + * * @return array|null */ public function getExtra(): ?array; + /** + * Get the created at timestamp of the transfer. + */ public function getCreatedAt(): DateTimeImmutable; + /** + * Get the updated at timestamp of the transfer. + */ public function getUpdatedAt(): DateTimeImmutable; } diff --git a/src/Internal/Dto/TransferLazyDtoInterface.php b/src/Internal/Dto/TransferLazyDtoInterface.php index 21bd70196..2095f39a1 100644 --- a/src/Internal/Dto/TransferLazyDtoInterface.php +++ b/src/Internal/Dto/TransferLazyDtoInterface.php @@ -8,23 +8,53 @@ interface TransferLazyDtoInterface { + /** + * Get the wallet from which the transfer is made. + */ public function getFromWallet(): Wallet; + /** + * Get the wallet to which the transfer is made. + */ public function getToWallet(): Wallet; + /** + * Get the discount amount of the transfer. + */ public function getDiscount(): int; + /** + * Get the fee amount of the transfer. + * + * @return non-empty-string + */ public function getFee(): string; + /** + * Get the withdraw transaction DTO. + */ public function getWithdrawDto(): TransactionDtoInterface; + /** + * Get the deposit transaction DTO. + */ public function getDepositDto(): TransactionDtoInterface; + /** + * Get the status of the transfer. + */ public function getStatus(): string; + /** + * Get the UUID of the transfer. + * + * @return non-empty-string|null + */ public function getUuid(): ?string; /** + * Get the extra information of the transfer. + * * @return array|null */ public function getExtra(): ?array; diff --git a/src/Internal/Events/BalanceUpdatedEventInterface.php b/src/Internal/Events/BalanceUpdatedEventInterface.php index c2e206b09..e2cdf338c 100644 --- a/src/Internal/Events/BalanceUpdatedEventInterface.php +++ b/src/Internal/Events/BalanceUpdatedEventInterface.php @@ -8,11 +8,31 @@ interface BalanceUpdatedEventInterface extends EventInterface { + /** + * Returns the ID of the wallet that was updated. + * + * @return int The ID of the wallet. + */ public function getWalletId(): int; + /** + * Returns the UUID of the wallet that was updated. + * + * @return non-empty-string The UUID of the wallet. + */ public function getWalletUuid(): string; + /** + * Returns the balance of the wallet after the update. + * + * @return non-empty-string The balance of the wallet. + */ public function getBalance(): string; + /** + * Returns the date and time of the update. + * + * @return DateTimeImmutable The date and time of the update. + */ public function getUpdatedAt(): DateTimeImmutable; } diff --git a/src/Internal/Events/EventInterface.php b/src/Internal/Events/EventInterface.php index 34fcc3798..db6e8191b 100644 --- a/src/Internal/Events/EventInterface.php +++ b/src/Internal/Events/EventInterface.php @@ -4,6 +4,12 @@ namespace Bavix\Wallet\Internal\Events; +/** + * Interface EventInterface. + * + * This interface is used to mark events that are emitted by the package. + * All events should implement this interface. + */ interface EventInterface { } diff --git a/src/Internal/Events/TransactionCreatedEventInterface.php b/src/Internal/Events/TransactionCreatedEventInterface.php index a105800cb..2798af34e 100644 --- a/src/Internal/Events/TransactionCreatedEventInterface.php +++ b/src/Internal/Events/TransactionCreatedEventInterface.php @@ -8,11 +8,31 @@ interface TransactionCreatedEventInterface extends EventInterface { + /** + * Returns the ID of the transaction. + * + * @return int The transaction ID. + */ public function getId(): int; + /** + * Returns the type of the transaction. + * + * @return string The transaction type. + */ public function getType(): string; + /** + * Returns the ID of the wallet associated with the transaction. + * + * @return int The wallet ID. + */ public function getWalletId(): int; + /** + * Returns the creation date and time of the transaction. + * + * @return DateTimeImmutable The creation date and time. + */ public function getCreatedAt(): DateTimeImmutable; } diff --git a/src/Internal/Events/WalletCreatedEventInterface.php b/src/Internal/Events/WalletCreatedEventInterface.php index 185a3a08b..028751620 100644 --- a/src/Internal/Events/WalletCreatedEventInterface.php +++ b/src/Internal/Events/WalletCreatedEventInterface.php @@ -8,13 +8,30 @@ interface WalletCreatedEventInterface extends EventInterface { + /** + * Returns the type of the holder. + */ public function getHolderType(): string; + /** + * Returns the ID of the holder. + */ public function getHolderId(): int|string; + /** + * Returns the ID of the wallet. + */ public function getWalletId(): int; + /** + * Returns the UUID of the wallet. + * + * @return non-empty-string + */ public function getWalletUuid(): string; + /** + * Returns the creation date of the wallet. + */ public function getCreatedAt(): DateTimeImmutable; } diff --git a/src/Internal/Listeners/TransactionBeginningListener.php b/src/Internal/Listeners/TransactionBeginningListener.php index 4c928f5e5..3df11bb98 100644 --- a/src/Internal/Listeners/TransactionBeginningListener.php +++ b/src/Internal/Listeners/TransactionBeginningListener.php @@ -9,10 +9,26 @@ final class TransactionBeginningListener { + /** + * This listener is responsible for purging all transactions and transfers + * if it is the top level of a transaction. + */ public function __invoke(): void { - if (app(ConnectionServiceInterface::class)->get()->transactionLevel() === 1) { - app(RegulatorServiceInterface::class)->purge(); + // Get the current transaction level from the database connection + $transactionLevel = app(ConnectionServiceInterface::class)->get()->transactionLevel(); + + // If the transaction level is 1, it means it is the top level of a transaction + if ($transactionLevel === 1) { + // Get the regulator service instance + /** @var RegulatorServiceInterface $regulatorService */ + $regulatorService = app(RegulatorServiceInterface::class); + + // Purge all transactions and transfers + // This method is called to ensure that all changes made to the database within the transaction + // are reflected in the wallet's balance. It is important to note that this action is not reversible + // and data loss is possible. + $regulatorService->purge(); } } } diff --git a/src/Internal/Listeners/TransactionCommittedListener.php b/src/Internal/Listeners/TransactionCommittedListener.php index 10029d332..298033711 100644 --- a/src/Internal/Listeners/TransactionCommittedListener.php +++ b/src/Internal/Listeners/TransactionCommittedListener.php @@ -9,9 +9,33 @@ final class TransactionCommittedListener { + /** + * This listener is responsible for performing actions after a transaction has been successfully committed. + * + * It checks the transaction level from the database connection and if it is 0 (indicating the top level of the transaction), + * it calls the `committed` method of the `RegulatorServiceInterface` to perform actions like updating the transaction status in the database. + * + * @see ConnectionServiceInterface::get() + * @see ConnectionInterface::transactionLevel() + * @see RegulatorServiceInterface::committed() + */ public function __invoke(): void { - if (app(ConnectionServiceInterface::class)->get()->transactionLevel() === 0) { + // Get the database connection + $connection = app(ConnectionServiceInterface::class)->get(); + + // Get the transaction level from the database connection + // The transaction level represents the nesting level of the transaction. + // The top level of the transaction is 0, indicating that the current transaction is the outermost transaction. + $transactionLevel = $connection->transactionLevel(); + + // Check if the transaction level is 0 indicating the top level of the transaction + if ($transactionLevel === 0) { + // The transaction is at the top level, so perform actions when the transaction is successfully committed + + // Call the `committed` method of the `RegulatorServiceInterface` to perform actions like updating the transaction status in the database + // This method is responsible for performing actions after a transaction has been successfully committed. + // It is typically used to update the transaction status in the database. app(RegulatorServiceInterface::class)->committed(); } } diff --git a/src/Internal/Listeners/TransactionCommittingListener.php b/src/Internal/Listeners/TransactionCommittingListener.php index 12b7ad872..d4491f5a1 100644 --- a/src/Internal/Listeners/TransactionCommittingListener.php +++ b/src/Internal/Listeners/TransactionCommittingListener.php @@ -9,13 +9,30 @@ final class TransactionCommittingListener { + /** + * This listener is responsible for performing actions when a transaction is successfully committed. + * + * It checks the transaction level from the database connection and if it is 1 (indicating the top level of the transaction), + * it calls the `committing` method of the `RegulatorServiceInterface` to perform actions like updating the transaction status in the database. + * + * @see ConnectionServiceInterface::get() + * @see ConnectionInterface::transactionLevel() + * @see RegulatorServiceInterface::committing() + */ public function __invoke(): void { - /** - * In fact, this if is not needed here. - * But in order to protect the code from changes in the framework, I added a check here. - */ - if (app(ConnectionServiceInterface::class)->get()->transactionLevel() === 1) { + // Get the database connection + // This service is responsible for getting the database connection. + $connection = app(ConnectionServiceInterface::class)->get(); + + // Check if the transaction level is 1 indicating the top level of the transaction + // The transaction level represents the nesting level of the transaction. + // The top level of the transaction is 1, indicating that the current transaction is the outermost transaction. + if ($connection->transactionLevel() === 1) { + // Call the `committing` method of the `RegulatorServiceInterface` + // This method is responsible for performing actions when a transaction is successfully committed. + // It is typically used to update the transaction status in the database. + // The `committing` method is called to perform actions like updating the transaction status in the database. app(RegulatorServiceInterface::class)->committing(); } } diff --git a/src/Internal/Listeners/TransactionRolledBackListener.php b/src/Internal/Listeners/TransactionRolledBackListener.php index 9983ea88a..b9bd4b7dc 100644 --- a/src/Internal/Listeners/TransactionRolledBackListener.php +++ b/src/Internal/Listeners/TransactionRolledBackListener.php @@ -9,10 +9,36 @@ final class TransactionRolledBackListener { + /** + * This listener is responsible for purging the regulator service + * if the current transaction level is 0. + * + * The transaction level represents the nesting level of the transaction. + * The top level of the transaction is 0, indicating that the current transaction is the outermost transaction. + */ public function __invoke(): void { - if (app(ConnectionServiceInterface::class)->get()->transactionLevel() === 0) { - app(RegulatorServiceInterface::class)->purge(); + // Get the database connection + // This service is responsible for getting the database connection. + // The database connection is used to communicate with the database. + $connection = app(ConnectionServiceInterface::class)->get(); + + // Get the current transaction level from the database connection + // The transaction level represents the nesting level of the transaction. + // The top level of the transaction is 0, indicating that the current transaction is the outermost transaction. + $transactionLevel = $connection->transactionLevel(); + + // If the transaction level is 0, it means it is the top level of a transaction + if ($transactionLevel === 0) { + // Call the `purge` method of the `RegulatorServiceInterface` + // This method is responsible for purging the regulator service. + // It clears any cached data related to the wallet. + $regulatorService = app(RegulatorServiceInterface::class); + + // Purge the regulator service + // This method clears any cached data related to the wallet. + // It is necessary to purge the regulator service when the current transaction is the outermost transaction. + $regulatorService->purge(); } } } diff --git a/src/Internal/Observers/TransactionObserver.php b/src/Internal/Observers/TransactionObserver.php index 0ac0e7911..cef4bed9d 100644 --- a/src/Internal/Observers/TransactionObserver.php +++ b/src/Internal/Observers/TransactionObserver.php @@ -15,15 +15,27 @@ final class TransactionObserver { /** - * @throws UnconfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Handle the deleting event for the Transaction model. + * + * This method is called when a transaction is being deleted. + * It safely resets the confirmation of the transaction. + * + * @param Transaction $model The transaction model being deleted. + * @return bool Returns true if the confirmation was reset, false otherwise. + * + * @throws UnconfirmedInvalid If the transaction is not confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function deleting(Transaction $model): bool { + // Reset the confirmation of the transaction. + // This method removes the confirmation of the transaction only if it is already confirmed. + // If the transaction does not belong to the wallet, a WalletOwnerInvalid exception will be thrown. + // If the transaction was not found, a RecordNotFoundException will be thrown. return $model->wallet->safeResetConfirm($model); } } diff --git a/src/Internal/Observers/TransferObserver.php b/src/Internal/Observers/TransferObserver.php index 3b2517c94..0f0e24715 100644 --- a/src/Internal/Observers/TransferObserver.php +++ b/src/Internal/Observers/TransferObserver.php @@ -21,17 +21,34 @@ public function __construct( } /** - * @throws UnconfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Handle the deleting event for the Transfer model. + * + * This method is called when a transfer is being deleted. + * It safely resets the confirmation of the transfer. + * + * @param Transfer $model The transfer model being deleted. + * @return bool Returns true if the confirmation was reset, false otherwise. + * + * @throws UnconfirmedInvalid If the transfer is not confirmed. + * @throws WalletOwnerInvalid If the transfer does not belong to the wallet. + * @throws RecordNotFoundException If the transfer was not found. + * @throws RecordsNotFoundException If no transfers were found. + * @throws TransactionFailedException If the transfer failed. + * @throws ExceptionInterface If an exception occurred. */ public function deleting(Transfer $model): bool { + // Reset confirmation. + // This method removes the confirmation of the transfer only if it is already confirmed. + // If the transfer does not belong to the wallet, a WalletOwnerInvalid exception will be thrown. + // If the transfer was not found, a RecordNotFoundException will be thrown. + // Block both the wallet of the user who is sending the money and the wallet of the user who is receiving the money. return $this->atomicService->blocks([$model->from, $model->to], function () use ($model) { + // Reset confirmation of the transfer for the sender's wallet. + // Returns true if the confirmation was reset, false otherwise. return $model->from->safeResetConfirm($model->withdraw) + // Reset confirmation of the transfer for the receiver's wallet. + // Returns true if the confirmation was reset, false otherwise. && $model->to->safeResetConfirm($model->deposit); }); } diff --git a/src/Internal/Query/TransactionQueryInterface.php b/src/Internal/Query/TransactionQueryInterface.php index e00cfe3eb..e46514b10 100644 --- a/src/Internal/Query/TransactionQueryInterface.php +++ b/src/Internal/Query/TransactionQueryInterface.php @@ -7,7 +7,11 @@ interface TransactionQueryInterface { /** - * @return non-empty-array + * Returns an array of UUIDs for the transactions. + * + * The array should not be empty and should contain only non-empty-strings or integers. + * + * @return non-empty-array An array of transaction UUIDs. */ public function getUuids(): array; } diff --git a/src/Internal/Query/TransferQueryInterface.php b/src/Internal/Query/TransferQueryInterface.php index 8bef2ac91..e287ac04d 100644 --- a/src/Internal/Query/TransferQueryInterface.php +++ b/src/Internal/Query/TransferQueryInterface.php @@ -7,7 +7,11 @@ interface TransferQueryInterface { /** - * @return non-empty-array + * Returns an array of UUIDs for the transfers. + * + * The array should not be empty and should contain only non-empty-strings or integers. + * + * @return non-empty-array An array of transfer UUIDs. */ public function getUuids(): array; } diff --git a/src/Internal/Repository/TransactionRepositoryInterface.php b/src/Internal/Repository/TransactionRepositoryInterface.php index e3c061a97..d2b814760 100644 --- a/src/Internal/Repository/TransactionRepositoryInterface.php +++ b/src/Internal/Repository/TransactionRepositoryInterface.php @@ -11,14 +11,25 @@ interface TransactionRepositoryInterface { /** - * @param non-empty-array $objects + * Inserts multiple transactions into the repository. + * + * @param non-empty-array $objects The array of transaction objects to insert. */ public function insert(array $objects): void; + /** + * Inserts a single transaction into the repository. + * + * @param TransactionDtoInterface $dto The transaction object to insert. + * @return Transaction The inserted transaction object. + */ public function insertOne(TransactionDtoInterface $dto): Transaction; /** - * @return Transaction[] + * Retrieves transactions from the repository based on the given query. + * + * @param TransactionQueryInterface $query The query to filter the transactions. + * @return Transaction[] An array of transactions that match the query. */ public function findBy(TransactionQueryInterface $query): array; } diff --git a/src/Internal/Repository/TransferRepositoryInterface.php b/src/Internal/Repository/TransferRepositoryInterface.php index bc7207a69..70a1462a1 100644 --- a/src/Internal/Repository/TransferRepositoryInterface.php +++ b/src/Internal/Repository/TransferRepositoryInterface.php @@ -11,19 +11,39 @@ interface TransferRepositoryInterface { /** - * @param non-empty-array $objects + * Inserts multiple transfers into the repository. + * + * @param non-empty-array $objects The array of transfer objects to insert. */ public function insert(array $objects): void; + /** + * Inserts a single transfer into the repository. + * + * @param TransferDtoInterface $dto The transfer object to insert. + * @return Transfer The inserted transfer. + */ public function insertOne(TransferDtoInterface $dto): Transfer; /** - * @return Transfer[] + * Retrieves transfers from the repository based on the given query. + * + * @param TransferQueryInterface $query The query used to filter the transfers. + * @return Transfer[] The array of transfers that match the query. */ public function findBy(TransferQueryInterface $query): array; /** - * @param non-empty-array $ids + * Updates the status of transfers identified by their IDs. + * + * This method updates the status field of transfers in the repository + * to the provided status, for all transfers whose IDs are included + * in the provided array. The method returns the number of transfers + * that were updated. + * + * @param string $status The new status to set for the specified transfers. + * @param non-empty-array $ids A non-empty array of transfer IDs to update. + * @return int The number of transfers whose status was updated. */ public function updateStatusByIds(string $status, array $ids): int; } diff --git a/src/Internal/Repository/WalletRepositoryInterface.php b/src/Internal/Repository/WalletRepositoryInterface.php index 9b91480de..2414df5e5 100644 --- a/src/Internal/Repository/WalletRepositoryInterface.php +++ b/src/Internal/Repository/WalletRepositoryInterface.php @@ -10,6 +10,8 @@ interface WalletRepositoryInterface { /** + * Create a new wallet. + * * @param array{ * holder_type: string, * holder_id: string|int, @@ -25,34 +27,77 @@ interface WalletRepositoryInterface public function create(array $attributes): Wallet; /** - * @param non-empty-array $data + * Update the balances of wallets based on the provided data. + * + * @param non-empty-array $data An array containing wallet IDs as keys and new balances as values. + * @return int The number of wallets whose balances were successfully updated. */ public function updateBalances(array $data): int; + /** + * Find a wallet by its ID. + * + * @param int $id The ID of the wallet to find. + * @return Wallet|null The wallet with the given ID if found, otherwise null. + */ public function findById(int $id): ?Wallet; + /** + * Find a wallet by its UUID. + * + * @param string $uuid The UUID of the wallet to find. + * @return Wallet|null The wallet with the given UUID if found, otherwise null. + */ public function findByUuid(string $uuid): ?Wallet; + /** + * Find a wallet by its holder type, holder ID, and slug. + * + * @param string $holderType The type of the wallet's holder. + * @param int|string $holderId The ID of the wallet's holder. + * @param string $slug The wallet's slug. + * @return Wallet|null The wallet with the given holder type, holder ID, and slug if found, otherwise null. + */ public function findBySlug(string $holderType, int|string $holderId, string $slug): ?Wallet; /** - * @param array $holderIds - * @return Wallet[] + * Find all wallets that are default wallets for the given holder type and holder IDs. + * + * @param string $holderType The type of the wallet's holder. + * @param array $holderIds An array of holder IDs. + * @return Wallet[] An array of default wallets, indexed by their holder IDs. */ public function findDefaultAll(string $holderType, array $holderIds): array; /** - * @throws ModelNotFoundException + * Retrieve a wallet by its ID. + * + * @param int $id The ID of the wallet to retrieve. + * @return Wallet The wallet with the given ID. + * + * @throws ModelNotFoundException If no wallet with the given ID is found. */ public function getById(int $id): Wallet; /** - * @throws ModelNotFoundException + * Retrieve a wallet by its UUID. + * + * @param string $uuid The UUID of the wallet to retrieve. + * @return Wallet The wallet with the given UUID. + * + * @throws ModelNotFoundException If no wallet with the given UUID is found. */ public function getByUuid(string $uuid): Wallet; /** - * @throws ModelNotFoundException + * Retrieve a wallet by its slug. + * + * @param string $holderType The type of the wallet's holder. + * @param int|string $holderId The ID of the wallet's holder. + * @param string $slug The wallet's slug. + * @return Wallet The wallet with the given holder type, holder ID, and slug. + * + * @throws ModelNotFoundException If no wallet with the given holder type, holder ID, and slug is found. */ public function getBySlug(string $holderType, int|string $holderId, string $slug): Wallet; } diff --git a/src/Internal/Service/ClockServiceInterface.php b/src/Internal/Service/ClockServiceInterface.php index de673fcfb..0ac6f83dc 100644 --- a/src/Internal/Service/ClockServiceInterface.php +++ b/src/Internal/Service/ClockServiceInterface.php @@ -7,9 +7,23 @@ use DateTimeImmutable; /** - * @see https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md + * Interface for a clock service. + * + * This interface provides a way to get the current datetime immutably. */ interface ClockServiceInterface { + /** + * Returns a new DateTimeImmutable object representing the current date and time. + * + * This method is compliant with the Clock interface from the PHP-FIG proposed standard + * for a standardized way to get the current date and time. + * + * @see https://www.php-fig.org/psr/psr-20/ PSR-20: Clock Interface + * + * @return DateTimeImmutable The current date and time immutably. + * + * @psalm-immutable + */ public function now(): DateTimeImmutable; } diff --git a/src/Internal/Service/ConnectionServiceInterface.php b/src/Internal/Service/ConnectionServiceInterface.php index b8e96f81c..e97dfdc1c 100644 --- a/src/Internal/Service/ConnectionServiceInterface.php +++ b/src/Internal/Service/ConnectionServiceInterface.php @@ -6,7 +6,46 @@ use Illuminate\Database\ConnectionInterface; +/** + * Interface ConnectionServiceInterface. + * + * This interface defines the contract for retrieving a database connection. + * + * @see https://laravel.com/docs/11.x/database#running-queries + * @see https://laravel.com/docs/11.x/database#database-transactions + * @see https://laravel.com/docs/11.x/database#introduction + */ interface ConnectionServiceInterface { + /** + * Get a database connection instance. + * + * This method returns a database connection instance. + * + * @return ConnectionInterface The database connection instance. + * + * @example + * // Get a database connection instance. + * $connection = $this->get(); + * + * // Run a query on the "users" table. + * * $connection->table('users')->where('id', 1)->update(['name' => 'Jane']); + * + * // Start a database transaction. + * $connection->beginTransaction(); + * + * try { + * // Run queries... + * + * // Commit the transaction. + * $connection->commit(); + * } catch (Exception $e) { + * // Rollback the transaction. + * $connection->rollback(); + * + * // Rethrow the exception. + * throw $e; + * } + */ public function get(): ConnectionInterface; } diff --git a/src/Internal/Service/DatabaseServiceInterface.php b/src/Internal/Service/DatabaseServiceInterface.php index f7288d6c2..2410130d7 100644 --- a/src/Internal/Service/DatabaseServiceInterface.php +++ b/src/Internal/Service/DatabaseServiceInterface.php @@ -11,14 +11,20 @@ interface DatabaseServiceInterface { /** + * Executes a database transaction and returns the result of the given callback. + * + * This method wraps a set of database operations with a transaction. If any exception occurs within the + * transaction, the transaction will be automatically rolled back. If the callback function returns a value, + * that value will be returned by the `transaction` method. + * * @template T * - * @param callable(): T $callback - * @return T + * @param callable(): T $callback The callback function containing the database operations. + * @return T The result of the callback function. * - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws RecordsNotFoundException If the queried records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If any other exception occurs. */ public function transaction(callable $callback): mixed; } diff --git a/src/Internal/Service/DispatcherServiceInterface.php b/src/Internal/Service/DispatcherServiceInterface.php index 1ebf41b09..a0ef86bca 100644 --- a/src/Internal/Service/DispatcherServiceInterface.php +++ b/src/Internal/Service/DispatcherServiceInterface.php @@ -6,13 +6,42 @@ use Bavix\Wallet\Internal\Events\EventInterface; +/** + * Service for dispatching events to the listeners. + */ interface DispatcherServiceInterface { + /** + * Dispatches an event to the listeners. + * + * This method sends the given event to all registered listeners. + * The event object is passed to each listener's `handle` method. + * + * @param EventInterface $event Event object to be dispatched + */ public function dispatch(EventInterface $event): void; + /** + * Removes all events from the dispatcher. + * + * This method clears all events from the dispatcher. After calling this method, + * the dispatcher will not have any events to dispatch. + */ public function forgot(): void; + /** + * Flushes all events. + * + * This method sends all events that have been dispatched to the listeners + * to the listeners. After calling this method, the dispatcher will not + * have any events to dispatch. + */ public function flush(): void; + /** + * Flushes all events, but without throwing an exception if the transaction is rolled back. + * + * This method flushes all events that have not been sent yet, but does not throw an exception if the transaction is rolled back. + */ public function lazyFlush(): void; } diff --git a/src/Internal/Service/JsonServiceInterface.php b/src/Internal/Service/JsonServiceInterface.php index cece1238a..1fc3d1db1 100644 --- a/src/Internal/Service/JsonServiceInterface.php +++ b/src/Internal/Service/JsonServiceInterface.php @@ -7,7 +7,18 @@ interface JsonServiceInterface { /** - * @param array|null $data + * Encode an array of data into a JSON string. + * + * @param array|null $data The data to encode. If null, returns null. + * @return string|null The JSON encoded string, or null if the input is null. + * + * @note The input data is expected to be an array of mixed type data. + * If the input is not an array, it will be converted to an array + * before being encoded. + * + * @see https://www.php.net/manual/en/function.json-encode.php + * @see https://www.php.net/manual/en/json.constants.php + * @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php */ public function encode(?array $data): ?string; } diff --git a/src/Internal/Service/LockServiceInterface.php b/src/Internal/Service/LockServiceInterface.php index 543586497..0ad2350ca 100644 --- a/src/Internal/Service/LockServiceInterface.php +++ b/src/Internal/Service/LockServiceInterface.php @@ -7,26 +7,43 @@ interface LockServiceInterface { /** + * Locks the transaction for other concurrent requests. + * * @template T * - * @param callable(): T $callback - * @return T + * @param string $key The key to lock. + * @param callable(): T $callback The callback to execute after the lock has been acquired. + * @return T The result of the callback. */ public function block(string $key, callable $callback): mixed; /** + * Locks multiple transactions for other concurrent requests. + * + * This method attempts to acquire the locks for all the given keys. If all the locks are acquired, + * it executes the callback and returns the result. If any of the locks cannot be acquired, + * it releases any acquired locks and returns the result of the callback immediately. + * * @template T * - * @param string[] $keys - * @param callable(): T $callback - * @return T + * @param string[] $keys The keys to lock. + * @param callable(): T $callback The callback to execute after the locks have been acquired. + * @return T The result of the callback. */ public function blocks(array $keys, callable $callback): mixed; /** - * @param string[] $keys + * Releases the locks for the given keys. + * + * @param string[] $keys The keys to release the locks for. */ public function releases(array $keys): void; + /** + * Check if the given key is locked. + * + * @param string $key The key to check. + * @return bool Whether the key is locked or not. + */ public function isBlocked(string $key): bool; } diff --git a/src/Internal/Service/MathServiceInterface.php b/src/Internal/Service/MathServiceInterface.php index cbb4dd26d..73ba914f4 100644 --- a/src/Internal/Service/MathServiceInterface.php +++ b/src/Internal/Service/MathServiceInterface.php @@ -4,29 +4,149 @@ namespace Bavix\Wallet\Internal\Service; +use Brick\Math\Exception\DivisionByZeroException; + interface MathServiceInterface { + /** + * Add two numbers. + * + * This method adds two numbers and returns the result as a string. + * + * @param float|int|non-empty-string $first The first number to add. + * @param float|int|non-empty-string $second The second number to add. + * @param int|null $scale The scale to use for rounding. Defaults to null, which means the scale will be determined automatically. + * @return non-empty-string The sum of the two numbers. + */ public function add(float|int|string $first, float|int|string $second, ?int $scale = null): string; + /** + * Subtract two numbers. + * + * This method subtracts the second number from the first number and returns the result as a string. + * + * @param float|int|non-empty-string $first The first number to subtract from. + * @param float|int|non-empty-string $second The number to subtract. + * @param int|null $scale The scale to use for rounding. Defaults to null, which means the scale will be determined automatically. + * @return non-empty-string The difference between the two numbers. + */ public function sub(float|int|string $first, float|int|string $second, ?int $scale = null): string; + /** + * Divide two numbers. + * + * This method divides the first number by the second number and returns the result as a string. + * + * @param float|int|non-empty-string $first The first number to divide. + * @param float|int|non-empty-string $second The number to divide by. + * @param int|null $scale The scale to use for rounding. Defaults to null, which means the scale will be determined automatically. + * @return non-empty-string The result of the division. + * + * @throws DivisionByZeroException If the second number is zero. + */ public function div(float|int|string $first, float|int|string $second, ?int $scale = null): string; + /** + * Multiply two numbers. + * + * This method multiplies the first number by the second number and returns the result as a string. + * The result can be scaled to a specific number of decimal places as specified by the $scale parameter. + * If $scale is not provided, a default scale (defined elsewhere in the implementation) will be used. + * + * @param float|int|non-empty-string $first The first number to multiply. This can be a float, int, or a numeric string. + * @param float|int|non-empty-string $second The second number to multiply. Similar to $first, it accepts float, int, or numeric string. + * @param int|null $scale Optional. The scale to use for rounding the result. Defaults to null, indicating automatic scale determination. + * @return non-empty-string The product of the two numbers, represented as a string. + */ public function mul(float|int|string $first, float|int|string $second, ?int $scale = null): string; + /** + * Raise a number to the power of another number. + * + * This method calculates the result of raising the first number to the power of the second number and returns the result as a string. + * The result can be scaled to a specific number of decimal places as specified by the $scale parameter. + * If $scale is not provided, a default scale (defined elsewhere in the implementation) will be used. + * + * @param float|int|non-empty-string $first The base number to raise. + * @param float|int|non-empty-string $second The exponent to raise the base number to. + * @param int|null $scale Optional. The scale to use for rounding the result. Defaults to null, indicating automatic scale determination. + * @return non-empty-string The result of the exponentiation, represented as a string. + */ public function pow(float|int|string $first, float|int|string $second, ?int $scale = null): string; + /** + * Raise the number 10 to the power of another number. + * + * This method calculates the result of raising the number 10 to the power of the given number and returns + * the result as a string. + * + * @param float|int|non-empty-string $number The exponent to raise the number 10 to. + * @return non-empty-string The result of the exponentiation, represented as a string. + */ public function powTen(float|int|string $number): string; + /** + * Round a number to a specified precision. + * + * This method provides a way to round numerical values (whether they are floats, integers, or numeric strings) + * to a specified level of precision. The precision is defined by the number of decimal places to round to. + * The rounding follows the standard mathematical rules for rounding. + * + * @param float|int|non-empty-string $number The number to be rounded. Can be of type float, int, or a numeric string. + * @param int $precision The number of decimal places to round to. Defaults to 0, meaning rounding to the nearest whole number. + * @return non-empty-string The rounded number, represented as a string. This ensures consistent precision and format, especially useful in financial calculations. + */ public function round(float|int|string $number, int $precision = 0): string; + /** + * Get the floor value of a number. + * + * This method returns the largest integer less than or equal to the specified number. + * + * @param float|int|non-empty-string $number The number to get the floor value for. + * @return non-empty-string The floor value of the number represented as a string. + */ public function floor(float|int|string $number): string; + /** + * Get the ceiling value of a number. + * + * This method returns the smallest integer greater than or equal to the specified number. + * + * @param float|int|non-empty-string $number The number to get the ceiling value for. + * @return non-empty-string The ceiling value of the number represented as a string. + */ public function ceil(float|int|string $number): string; + /** + * Get the absolute value of a number. + * + * The absolute value of a number is the value without considering whether it is positive or negative. + * + * @param float|int|non-empty-string $number The number for which to get the absolute value. + * @return non-empty-string The absolute value of the number represented as a string. + */ public function abs(float|int|string $number): string; + /** + * Get the negative value of a number. + * + * The negative value of a number is the same as the number multiplied by -1. + * + * @param float|int|non-empty-string $number The number for which to get the negative value. + * @return non-empty-string The negative value of the number represented as a string. + */ public function negative(float|int|string $number): string; + /** + * Compare two numbers. + * + * This method compares two numbers and returns an integer value indicating their relationship. + * + * @param float|int|non-empty-string $first The first number to compare. + * @param float|int|non-empty-string $second The second number to compare. + * @return int Returns an integer less than, equal to, or greater than zero if the first number is considered + * to be respectively less than, equal to, or greater than the second. + */ public function compare(float|int|string $first, float|int|string $second): int; } diff --git a/src/Internal/Service/StateServiceInterface.php b/src/Internal/Service/StateServiceInterface.php index dc01b6945..b6d2efe3d 100644 --- a/src/Internal/Service/StateServiceInterface.php +++ b/src/Internal/Service/StateServiceInterface.php @@ -7,12 +7,39 @@ interface StateServiceInterface { /** - * @param string[] $uuids - * @param callable(): array $value + * Execute a transaction that will fork the state of multiple wallets. + * + * This method is used to ensure that the state of multiple wallets is consistent across a series of operations. + * When this method is called, it creates a new transaction that locks the wallets and executes the provided + * callback function. The callback function should perform a series of operations on the wallets, and return + * an associative array where the keys are the UUIDs of the wallets and the values are the new state of the wallets. + * + * The state of each wallet is validated against the state of the wallet at the beginning of the transaction. + * If any of the wallets have been modified since the transaction began, a `WalletStateConsistencyException` + * exception will be thrown. + * + * @param non-empty-string[] $uuids The UUIDs of the wallets to be forked. + * @param callable(): array $value A callback function that performs a series of operations on the + * wallets and returns an associative array of the new state of the wallets. + * The keys of the array should be the UUIDs of the wallets, and the values + * should be the new state of the wallets. */ public function multiFork(array $uuids, callable $value): void; + /** + * Get the state of a wallet. + * + * @param non-empty-string $uuid The UUID of the wallet to retrieve the state of. + * @return non-empty-string|null The state of the wallet, or null if the wallet does not exist. + */ public function get(string $uuid): ?string; + /** + * Delete the state of a wallet. + * + * This method is used to remove the state of a wallet from the storage. + * + * @param non-empty-string $uuid The UUID of the wallet to delete the state of. + */ public function drop(string $uuid): void; } diff --git a/src/Internal/Service/StorageService.php b/src/Internal/Service/StorageService.php index 40bdfe1a5..a9259b727 100644 --- a/src/Internal/Service/StorageService.php +++ b/src/Internal/Service/StorageService.php @@ -55,10 +55,10 @@ public function increase(string $uuid, float|int|string $value): string } /** - * @template T of non-empty-array + * @template T of non-empty-array * * @param T $uuids - * @return non-empty-array, string> + * @return non-empty-array, non-empty-string> * * @throws RecordNotFoundException */ @@ -80,7 +80,7 @@ public function multiGet(array $uuids): array } $results = []; - /** @var array $values */ + /** @var array $values */ foreach ($values as $key => $value) { $uuid = $keys[$key]; if ($value === null) { diff --git a/src/Internal/Service/StorageServiceInterface.php b/src/Internal/Service/StorageServiceInterface.php index 5426e808d..5cc295240 100644 --- a/src/Internal/Service/StorageServiceInterface.php +++ b/src/Internal/Service/StorageServiceInterface.php @@ -8,44 +8,109 @@ interface StorageServiceInterface { + /** + * Flushes all the stored values. + * + * This method clears all the stored values, effectively removing them from the storage. + * The method returns a boolean value indicating whether the flush operation was successful + * or not. + * + * @return bool True if the flush operation was successful, false otherwise. + */ public function flush(): bool; + /** + * Forgets the stored value for the given UUID. + * + * This method removes the stored value associated with the provided UUID from the storage. + * + * @param non-empty-string $uuid The UUID of the stored value to forget. + * @return bool True if the value was successfully forgotten, false otherwise. + */ public function forget(string $uuid): bool; /** - * @throws RecordNotFoundException + * Retrieves the stored value for the given UUID. + * + * This method retrieves the stored value associated with the provided UUID from the storage. + * If the value with the given UUID does not exist, a `RecordNotFoundException` is thrown. + * + * @param non-empty-string $uuid The UUID of the stored value. + * @return non-empty-string The stored value. + * + * @throws RecordNotFoundException If the value with the given UUID is not found. */ public function get(string $uuid): string; + /** + * Synchronizes the stored value for the given UUID. + * + * This method updates the stored value associated with the provided UUID with the specified value. + * If the value does not exist, it will be created. If the value already exists, it will be updated. + * + * @param non-empty-string $uuid The UUID of the stored value. + * @param float|int|non-empty-string $value The value to synchronize. + * @return bool Returns `true` if the synchronization was successful, `false` otherwise. + */ public function sync(string $uuid, float|int|string $value): bool; /** - * @throws RecordNotFoundException + * Increases the stored value for the given UUID by the specified amount. + * + * This method increases the stored value associated with the provided UUID by the specified amount. + * If the value with the given UUID does not exist, a `RecordNotFoundException` is thrown. + * + * @param non-empty-string $uuid The UUID of the stored value. + * @param float|int|non-empty-string $value The amount to increase the stored value by. + * @return non-empty-string The updated stored value. + * + * @throws RecordNotFoundException If the value with the given UUID is not found. */ public function increase(string $uuid, float|int|string $value): string; /** - * @template T of non-empty-array + * Retrieves the stored values for the given UUIDs. * - * @param T $uuids - * @return non-empty-array, string> + * This method retrieves the stored values associated with the provided UUIDs from the storage. + * If any of the values with the given UUIDs do not exist, a `RecordNotFoundException` is thrown. * - * @throws RecordNotFoundException + * @param non-empty-array $uuids The UUIDs of the stored values. + * @return non-empty-array The stored values. The keys are the UUIDs and the values are the corresponding + * stored values. + * + * @throws RecordNotFoundException If any of the values with the given UUIDs are not found. */ public function multiGet(array $uuids): array; /** - * @param non-empty-array $inputs + * Synchronizes multiple stored values at once. + * + * This method updates the stored values associated with the provided UUIDs with the specified values. + * If any of the values with the given UUIDs do not exist, a `RecordNotFoundException` is thrown. + * + * @param non-empty-array $inputs An associative array + * where the keys are UUIDs and the values are the corresponding + * stored values. + * @return bool Returns `true` if the synchronization was successful, `false` otherwise. + * + * @throws RecordNotFoundException If any of the values with the given UUIDs are not found. */ public function multiSync(array $inputs): bool; /** - * @template T of non-empty-array + * Increase multiple stored values at once. + * + * This method takes an associative array where the keys are UUIDs and the values are the amounts to increase the + * corresponding stored values by. + * + * @template T of non-empty-array * - * @param T $inputs - * @return non-empty-array, string> + * @param T $inputs An associative array where the keys are UUIDs and the values are the amounts to increase the + * corresponding stored values by. + * @return non-empty-array, non-empty-string> An associative array where the keys are the UUIDs and the values are the + * updated stored values. * - * @throws RecordNotFoundException + * @throws RecordNotFoundException If any of the values with the given UUIDs are not found. */ public function multiIncrease(array $inputs): array; } diff --git a/src/Internal/Service/TranslatorServiceInterface.php b/src/Internal/Service/TranslatorServiceInterface.php index e09c0c667..8ce69d9cf 100644 --- a/src/Internal/Service/TranslatorServiceInterface.php +++ b/src/Internal/Service/TranslatorServiceInterface.php @@ -6,5 +6,14 @@ interface TranslatorServiceInterface { + /** + * Returns a translated string for the given key. + * + * @param string $key The key of the translation. This is the identifier of the + * translation string in the translation files. + * @return string The translated string. This is the translated version of the + * translation string specified by the given key. If the translation + * string does not exist, the key itself is returned. + */ public function get(string $key): string; } diff --git a/src/Internal/Service/UuidFactoryServiceInterface.php b/src/Internal/Service/UuidFactoryServiceInterface.php index bdfbbf8d4..e295586e7 100644 --- a/src/Internal/Service/UuidFactoryServiceInterface.php +++ b/src/Internal/Service/UuidFactoryServiceInterface.php @@ -6,5 +6,20 @@ interface UuidFactoryServiceInterface { + /** + * Generate a version 4 UUID. + * + * Version 4 UUIDs are randomly generated and therefore do not contain any information + * identifying the originator of the UUID, the generating system, or the time of generation. + * + * @see https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) + * + * @return non-empty-string The generated version 4 UUID. + * + * @throws \Ramsey\Uuid\Exception\InvalidArgumentException If a field is invalid in the UUID. + * @throws \Ramsey\Uuid\Exception\InvalidUuidStringException If the string we are parsing is not a valid UUID. + * @throws \Ramsey\Uuid\Exception\UnsupportedOperationException If the UUID implementation can't support a feature. + * @throws \Ramsey\Uuid\Exception\UuidExceptionInterface If there is an error generating the UUID. + */ public function uuid4(): string; } diff --git a/src/Internal/Transform/TransactionDtoTransformerInterface.php b/src/Internal/Transform/TransactionDtoTransformerInterface.php index 7d27d6272..81cbd6dfe 100644 --- a/src/Internal/Transform/TransactionDtoTransformerInterface.php +++ b/src/Internal/Transform/TransactionDtoTransformerInterface.php @@ -12,11 +12,11 @@ interface TransactionDtoTransformerInterface /** * @return array{ * uuid: string, - * payable_type: string, + * payable_type: class-string, * payable_id: int|string, * wallet_id: int, * type: string, - * amount: float|int|string, + * amount: float|int|non-empty-string, * confirmed: bool, * meta: array|null, * created_at: DateTimeImmutable, diff --git a/src/Internal/Transform/TransferDtoTransformerInterface.php b/src/Internal/Transform/TransferDtoTransformerInterface.php index c6e62aad6..b86c5e8de 100644 --- a/src/Internal/Transform/TransferDtoTransformerInterface.php +++ b/src/Internal/Transform/TransferDtoTransformerInterface.php @@ -11,14 +11,14 @@ interface TransferDtoTransformerInterface { /** * @return array{ - * uuid: string, + * uuid: non-empty-string, * deposit_id: int, * withdraw_id: int, * status: string, * from_id: int|string, * to_id: int|string, * discount: int, - * fee: string, + * fee: non-empty-string, * extra: array|null, * created_at: DateTimeImmutable, * updated_at: DateTimeImmutable, diff --git a/src/Models/Transaction.php b/src/Models/Transaction.php index 30f278c8e..b804eb3b9 100644 --- a/src/Models/Transaction.php +++ b/src/Models/Transaction.php @@ -22,13 +22,13 @@ * @property class-string $payable_type * @property int|string $payable_id * @property int $wallet_id - * @property string $uuid + * @property non-empty-string $uuid * @property string $type - * @property string $amount + * @property non-empty-string $amount * @property int $amountInt - * @property string $amountFloat + * @property non-empty-string $amountFloat * @property bool $confirmed - * @property array $meta + * @property array $meta * @property Wallet $payable * @property WalletModel $wallet * @property DateTimeInterface $created_at diff --git a/src/Models/Transfer.php b/src/Models/Transfer.php index 72468d1ab..133fa1462 100644 --- a/src/Models/Transfer.php +++ b/src/Models/Transfer.php @@ -16,15 +16,15 @@ * * @property string $status * @property string $status_last - * @property string $discount + * @property non-empty-string $discount * @property int $deposit_id * @property int $withdraw_id * @property Wallet $from * @property int $from_id * @property Wallet $to * @property int $to_id - * @property string $uuid - * @property string $fee + * @property non-empty-string $uuid + * @property non-empty-string $fee * @property ?array $extra * @property Transaction $deposit * @property Transaction $withdraw diff --git a/src/Models/Wallet.php b/src/Models/Wallet.php index dbdc01f00..bbd18ba44 100644 --- a/src/Models/Wallet.php +++ b/src/Models/Wallet.php @@ -32,15 +32,15 @@ * Class Wallet. * * @property class-string $holder_type - * @property int|string $holder_id + * @property int|non-empty-string $holder_id * @property string $name * @property string $slug - * @property string $uuid + * @property non-empty-string $uuid * @property string $description - * @property null|array $meta + * @property null|array $meta * @property int $decimal_places * @property Model $holder - * @property string $credit + * @property non-empty-string $credit * @property string $currency * @property DateTimeInterface $created_at * @property DateTimeInterface $updated_at @@ -138,14 +138,24 @@ public function refreshBalance(): bool public function getOriginalBalanceAttribute(): string { - return (string) $this->getRawOriginal('balance', 0); + $balance = (string) $this->getRawOriginal('balance', 0); + + // Perform assertion to check if balance is not an empty string + assert($balance !== '', 'Balance should not be an empty string'); + + return $balance; } public function getAvailableBalanceAttribute(): float|int|string { - return $this->walletTransactions() + $balance = $this->walletTransactions() ->where('confirmed', true) ->sum('amount'); + + // Perform assertion to check if balance is not an empty string + assert($balance !== '', 'Balance should not be an empty string'); + + return $balance; } /** @@ -158,7 +168,22 @@ public function holder(): MorphTo public function getCreditAttribute(): string { - return (string) ($this->meta['credit'] ?? '0'); + $credit = (string) ($this->meta['credit'] ?? '0'); + + /** + * Assert that the credit attribute is not an empty string. + * + * This is to ensure that the credit attribute always has a value. + * If the credit attribute is empty, it can cause issues with the math service. + * + * @throws \AssertionError If the credit attribute is an empty string. + */ + // Assert that credit is not an empty string + // This is to ensure that the credit attribute always has a value + // If the credit attribute is empty, it can cause issues with the math service + assert($credit !== '', 'Credit should not be an empty string. It can cause issues with the math service.'); + + return $credit; } public function getCurrencyAttribute(): string diff --git a/src/Services/AssistantServiceInterface.php b/src/Services/AssistantServiceInterface.php index bf33649d3..e14e5c682 100644 --- a/src/Services/AssistantServiceInterface.php +++ b/src/Services/AssistantServiceInterface.php @@ -9,6 +9,7 @@ use Bavix\Wallet\Internal\Dto\BasketDtoInterface; use Bavix\Wallet\Internal\Dto\TransactionDtoInterface; use Bavix\Wallet\Internal\Dto\TransferDtoInterface; +use Bavix\Wallet\Internal\Exceptions\ModelNotFoundException; /** * @api @@ -16,31 +17,73 @@ interface AssistantServiceInterface { /** - * @param non-empty-array $objects - * @return non-empty-array + * Returns an array of wallets from the objects. + * + * @param non-empty-array $objects An array of objects that implement the Wallet interface. + * @return non-empty-array An array of wallets. The keys are the wallet IDs and the values are the + * wallet objects. + * + * @throws ModelNotFoundException If any of the objects does not have a wallet. */ public function getWallets(array $objects): array; /** - * Helps to quickly extract the uuid from an object. + * Returns an array of UUIDs for the objects. + * + * @template T of non-empty-array * - * @param non-empty-array $objects - * @return non-empty-array + * @param T $objects An array of objects that implement the TransactionDtoInterface or TransferDtoInterface + * interfaces. + * @return non-empty-array, string> An array of UUIDs. The keys are the same keys of the input array. + * The values are the UUIDs extracted from the objects. + * + * @throws ModelNotFoundException If any of the objects does not have a UUID. */ public function getUuids(array $objects): array; /** - * Helps to quickly calculate the amount. + * Calculates the total amount for each wallet from an array of transactions. + * + * This function helps to quickly calculate the amount for each wallet. The array of transactions must contain + * objects that implement the TransactionDtoInterface interface. + * + * @param non-empty-array $transactions An array of transactions. + * @return array An array with the total amount for each wallet. + * The keys are the wallet IDs and the values are the total amounts as strings. + * The amounts are formatted as strings with the decimal part rounded to the wallet's + * decimal places. * - * @param non-empty-array $transactions - * @return array + * @throws ModelNotFoundException If any of the transactions does not have a wallet. + * + * @see TransactionDtoInterface */ public function getSums(array $transactions): array; /** - * Helps to get cart meta data for a product. + * Get the meta data for the cart. + * + * This function helps to get the meta data for the cart. The meta data is an array of key-value pairs that + * provides additional information about the cart. It can be used to include a title, description, or any other + * relevant details. + * + * @param BasketDtoInterface $basketDto The basket DTO object. + * @param ProductInterface $product The product object. + * @return array|null The meta data for the cart, or null if there is no meta data. + * + * @example + * The return value should be an array with key-value pairs, for example: + * + * return [ + * 'title' => 'Cart Title', + * 'description' => 'Cart Description', + * 'images' => [ + * 'https://example.com/image1.jpg', + * 'https://example.com/image2.jpg', + * ], + * ]; * - * @return array|null + * @see BasketDtoInterface + * @see ProductInterface */ public function getMeta(BasketDtoInterface $basketDto, ProductInterface $product): ?array; } diff --git a/src/Services/AtmServiceInterface.php b/src/Services/AtmServiceInterface.php index 0955f7ee4..5b8d9471b 100644 --- a/src/Services/AtmServiceInterface.php +++ b/src/Services/AtmServiceInterface.php @@ -15,18 +15,44 @@ interface AtmServiceInterface { /** - * Helps to get to create a bunch of transaction objects. + * This function helps to create a bunch of transaction objects. + * + * It takes an array of objects that implement the TransactionDtoInterface interface. + * Each object represents a transaction and contains information such as the wallet, + * transaction type, amount, and other details. + * + * The function returns an array of transaction objects. The keys are the transaction UUIDs + * and the values are the transaction objects. * * @param non-empty-array $objects + * The array of objects that represent the transactions. * @return non-empty-array + * An array of transaction objects. The keys are the transaction UUIDs and the values are the transaction + * objects. + * + * @throws \Bavix\Wallet\Internal\Exceptions\ModelNotFoundException + * If any of the objects does not have a wallet. */ public function makeTransactions(array $objects): array; /** * Helps to get to create a bunch of transfer objects. * + * The function takes an array of objects that implement the TransferDtoInterface interface. + * Each object represents a transfer and contains information such as the deposit wallet, + * withdraw wallet, amount, and other details. + * + * The function returns an array of transfer objects. The keys are the transfer UUIDs + * and the values are the transfer objects. + * * @param non-empty-array $objects + * The array of objects that represent the transfers. * @return non-empty-array + * An array of transfer objects. The keys are the transfer UUIDs and the values are the transfer + * objects. + * + * @throws \Bavix\Wallet\Internal\Exceptions\ModelNotFoundException + * If any of the objects does not have a wallet. */ public function makeTransfers(array $objects): array; } diff --git a/src/Services/AtomicServiceInterface.php b/src/Services/AtomicServiceInterface.php index 42f5c6d25..7085a4c8f 100644 --- a/src/Services/AtomicServiceInterface.php +++ b/src/Services/AtomicServiceInterface.php @@ -17,30 +17,39 @@ interface AtomicServiceInterface /** * The method atomically locks the transaction for other concurrent requests. * + * The method tries to acquire a lock for the given wallet object. If the lock is acquired, it executes the + * callback function and returns the result. If the lock is not acquired, it throws an exception. + * * @template T * - * @param callable(): T $callback - * @return T + * @param Wallet $object The wallet object to lock the transaction. + * @param callable(): T $callback The callback function to execute atomically. + * @return T The result of the callback function. * - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function block(Wallet $object, callable $callback): mixed; /** - * Use when you need to atomically change a lot of wallets and atomic in its pure form is not suitable. Use with - * caution, generates N requests to the lock service. + * This method is similar to the `block` method, but it allows you to atomically change a lot of wallets at once. + * + * It's useful when you need to perform multiple changes to different wallets in a single transaction. + * + * However, use it with caution. It generates N requests to the lock service, where N is the number of wallets. * * @template T * - * @param non-empty-array $objects - * @param callable(): T $callback - * @return T + * @param non-empty-array $objects The array of wallet objects to lock the transactions. + * @param callable(): T $callback The callback function to execute atomically. + * @return T The result of the callback function. + * + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. * - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @see AtomicServiceInterface::block */ public function blocks(array $objects, callable $callback): mixed; } diff --git a/src/Services/BasketService.php b/src/Services/BasketService.php index 1dab0df09..183750210 100644 --- a/src/Services/BasketService.php +++ b/src/Services/BasketService.php @@ -19,10 +19,10 @@ public function availability(AvailabilityDtoInterface $availabilityDto): bool foreach ($basketDto->items() as $itemDto) { $product = $itemDto->getProduct(); if ($product instanceof ProductLimitedInterface && ! $product->canBuy( - $customer, - $itemDto->count(), - $availabilityDto->isForce() - )) { + $customer, + $itemDto->count(), + $availabilityDto->isForce() + )) { return false; } } diff --git a/src/Services/BasketServiceInterface.php b/src/Services/BasketServiceInterface.php index b8508440f..adf2de042 100644 --- a/src/Services/BasketServiceInterface.php +++ b/src/Services/BasketServiceInterface.php @@ -12,7 +12,18 @@ interface BasketServiceInterface { /** - * A quick way to check stock. Able to check in batches, necessary for quick payments. + * Checks the availability of products in the basket. + * + * This method is used to quickly check the stock of products in the basket. + * It allows batch checks, which is necessary for quick payments. + * + * @param AvailabilityDtoInterface $availabilityDto The DTO containing the basket and customer information. + * The DTO contains the basket DTO and the customer object. + * @return bool True if all products are available, false otherwise. + * Returns true if all products in the basket are available, false otherwise. + * The method checks the availability of each product in the basket. + * If any product is not available, the method returns false. + * If all products are available, the method returns true. */ public function availability(AvailabilityDtoInterface $availabilityDto): bool; } diff --git a/src/Services/BookkeeperServiceInterface.php b/src/Services/BookkeeperServiceInterface.php index 711f5f685..e76bc6515 100644 --- a/src/Services/BookkeeperServiceInterface.php +++ b/src/Services/BookkeeperServiceInterface.php @@ -12,43 +12,127 @@ */ interface BookkeeperServiceInterface { + /** + * Forgets the given wallet. + * + * This method removes any data related to the specified wallet. + * It is used to remove the wallet from the bookkeeper's records. + * + * @param Wallet $wallet The wallet to forget. + * @return bool True if the wallet was successfully forgotten, false otherwise. + * + * @throws RecordNotFoundException If the wallet does not exist in the database. + */ public function forget(Wallet $wallet): bool; + /** + * Retrieves the current balance of the specified wallet. + * + * This method returns the current balance of the specified wallet as a string. + * The balance is always returned as a string to preserve the accuracy of the decimal value. + * + * @param Wallet $wallet The wallet to retrieve the balance from. + * @return string The current balance of the wallet as a string. + * + * @throws RecordNotFoundException If the wallet does not exist in the database. + */ public function amount(Wallet $wallet): string; /** - * @throws RecordNotFoundException + * Synchronizes the balance of the given wallet with the specified value. + * + * This method updates the balance of the wallet by setting it to the specified value. + * If the specified value is greater than the current balance, the balance will be increased. + * If the specified value is less than the current balance, the balance will be decreased. + * If the specified value is equal to the current balance, the balance will remain unchanged. + * + * @param Wallet $wallet The wallet whose balance needs to be synchronized. + * @param float|int|string $value The new balance value for the wallet. + * @return bool Returns `true` on successful synchronization, `false` otherwise. + * + * @throws RecordNotFoundException If the wallet does not exist. */ public function sync(Wallet $wallet, float|int|string $value): bool; /** - * @throws RecordNotFoundException + * Increases the balance of a wallet by the given value. + * + * This method updates the balance of the specified wallet by adding the given value. + * If the given value is positive, the balance will be increased. + * If the given value is negative, the balance will be decreased. + * If the given value is zero, the balance will remain unchanged. + * + * @param Wallet $wallet The wallet to increase the balance for. + * @param float|int|string $value The value to increase the balance by. + * @return string The new balance as a string. + * + * @throws RecordNotFoundException If the wallet does not exist. */ public function increase(Wallet $wallet, float|int|string $value): string; /** + * Retrieves the balance amounts for multiple wallets. + * + * This method takes an array of wallets and returns an associative array + * with the wallet UUIDs as keys and their balance amounts as values. + * * @template T of non-empty-array * - * @param T $wallets - * @return non-empty-array, string> + * @param T $wallets An array of wallets. + * @return non-empty-array, string> An associative array with the wallet UUIDs as keys + * and their balance amounts as values. */ public function multiAmount(array $wallets): array; /** - * @param non-empty-array $balances + * Synchronizes multiple wallet balances with the proposed values. + * + * This comprehensive method takes an associative array with wallet UUIDs as keys and their new balance values as values. + * It aims to align each wallet's current balance with its corresponding new value provided in the array. The process + * determines whether to increase, decrease, or maintain the current balance based on a comparison between the current + * and the given balance values. + * + * Operations: + * - If the proposed balance is higher than the existing one, the wallet's balance is increased accordingly. + * - If the proposed balance is lower, the wallet's balance is decreased to match the new value. + * - If the proposed balance matches the current one, no changes are made to the wallet's balance. * - * @throws RecordNotFoundException + * The method ensures that all specified wallets undergo the synchronization process, adhering to the given values, + * and returns a boolean indicating the overall success of the operation. + * + * @param non-empty-array $balances An associative array mapping wallet UUIDs to their new balance values. + * Each entry specifies the target balance for a wallet identified by its UUID. + * @return bool True if all specified wallets were successfully synchronized with the given balances, false otherwise. + * + * @throws RecordNotFoundException Thrown if any of the specified wallets cannot be found in the system. This ensures + * that only existing wallets are processed, safeguarding against synchronization + * attempts on non-existent wallets. + * + * @see Wallet The Wallet entity that represents the individual wallets to be synchronized. + * @see Wallet::uuid The property within the Wallet class that uniquely identifies each wallet. */ public function multiSync(array $balances): bool; /** + * Increases multiple wallet balances by the given values. + * + * This method takes an associative array of wallets and an associative array of increment values. + * The keys of the first array are wallet UUIDs and the values are the wallet objects. + * The keys of the second array are wallet UUIDs and the values are the increment values. + * + * The method updates the balance of each wallet by the corresponding given value. + * The balance of each wallet will be increased by the corresponding value. + * * @template T of non-empty-array * - * @param non-empty-array, Wallet> $wallets - * @param T $incrementValues - * @return non-empty-array, string> + * @param non-empty-array, Wallet> $wallets An associative array of wallets. + * The keys are wallet UUIDs and the values are the wallet objects. + * @param T $incrementValues An associative array of increment values. + * The keys are wallet UUIDs and the values are the increment values. + * @return non-empty-array, string> An associative array with the new balance amounts of the wallets. + * The keys are the wallet UUIDs and the values are the new balance amounts as strings. * - * @throws RecordNotFoundException + * @throws RecordNotFoundException If any of the wallets do not exist. */ public function multiIncrease(array $wallets, array $incrementValues): array; } diff --git a/src/Services/CastServiceInterface.php b/src/Services/CastServiceInterface.php index f467e1a38..0c58a8b80 100644 --- a/src/Services/CastServiceInterface.php +++ b/src/Services/CastServiceInterface.php @@ -13,9 +13,55 @@ */ interface CastServiceInterface { + /** + * Retrieve a wallet from an object that implements the Wallet interface. + * + * @param Wallet $object The object that implements the Wallet interface. + * This can be either a `Model` instance or an instance of a class that implements the + * `Wallet` interface. + * @param bool $save Flag indicating whether to save the wallet. + * If set to `true`, the wallet will be saved if it does not exist yet. + * If set to `false`, the wallet will be retrieved from the database if it exists, + * otherwise a new wallet will be created. + * @return WalletModel The retrieved wallet model. + * + * @throws \Bavix\Wallet\Internal\Exceptions\ModelNotFoundException If the wallet does not exist and `$save` is set to `false`. + * + * @see Wallet + * @see WalletModel + */ public function getWallet(Wallet $object, bool $save = true): WalletModel; + /** + * Get the holder associated with the object. + * + * This method retrieves the holder model associated with the provided object. The object can be an instance of + * the `Model` class or an instance of the `Wallet` interface. If the object is an instance of the `Wallet` interface, + * the method directly retrieves the holder model from the `Wallet` instance. If the object is an instance of the + * `Model` class, the method first checks if the object has a `getAttribute` method. If it does, the method attempts + * to retrieve an attribute named 'wallet'. If the attribute exists and is an instance of `WalletModel`, the method + * returns the `WalletModel` instance. + * + * @param Model|Wallet $object The object to retrieve the holder from. + * @return Model The holder model. + */ public function getHolder(Model|Wallet $object): Model; + /** + * Retrieve the model from a given object. + * + * This method is responsible for extracting a model instance from the provided object, ensuring it adheres + * to the `Model` interface. It first checks if the object itself is an instance of the `Model` class. If + * it is, it returns the object immediately. If not, it further checks if the object has a `getAttribute` + * method. If it does, it attempts to retrieve an attribute named 'wallet'. This attribute is then validated + * to be an instance of `WalletModel` class before returning. If these conditions are not met, an assertion + * error is triggered. + * + * @param object $object The object to extract the model from. Must either be an instance of `Model` or possess + * a 'wallet' attribute of type `WalletModel`. + * @return Model The model extracted from the provided object, ensuring it is of type `Model`. + * + * @throws \AssertionError If the 'wallet' attribute does not exist or is not an instance of the `WalletModel` class. + */ public function getModel(object $object): Model; } diff --git a/src/Services/ConsistencyServiceInterface.php b/src/Services/ConsistencyServiceInterface.php index 8c2f5461c..87ec5a81f 100644 --- a/src/Services/ConsistencyServiceInterface.php +++ b/src/Services/ConsistencyServiceInterface.php @@ -16,23 +16,56 @@ interface ConsistencyServiceInterface { /** - * @throws AmountInvalid + * Checks if the given amount is positive. + * + * This method throws an AmountInvalid exception if the given amount is not positive. + * + * @param float|int|string $amount The amount to check. + * + * @throws AmountInvalid If the given amount is not positive. */ public function checkPositive(float|int|string $amount): void; /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds + * Checks if the given amount is within the wallet's balance. + * + * This method throws a BalanceIsEmpty exception if the wallet's balance is empty. + * It throws an InsufficientFunds exception if the amount exceeds the wallet's balance. + * + * @param Wallet $object The wallet to check. + * @param float|int|string $amount The amount to check. + * @param bool $allowZero Whether to allow zero amounts. Defaults to false. + * + * @throws BalanceIsEmpty If the wallet's balance is empty. + * @throws InsufficientFunds If the amount exceeds the wallet's balance. */ public function checkPotential(Wallet $object, float|int|string $amount, bool $allowZero = false): void; + /** + * Checks if the given balance can be safely withdrawn by the specified amount. + * + * This method returns true if the balance can be withdrawn, and false otherwise. + * + * @param float|int|string $balance The balance to check. + * @param float|int|string $amount The amount to withdraw. + * @param bool $allowZero Whether to allow zero amounts. Defaults to false. + * @return bool Returns true if the balance can be withdrawn, false otherwise. + * + * @throws AmountInvalid If the given balance or amount is not positive. + */ public function canWithdraw(float|int|string $balance, float|int|string $amount, bool $allowZero = false): bool; /** - * @param TransferLazyDtoInterface[] $objects + * Checks the consistency of multiple transfers between wallets. + * + * This method ensures that the transfer of the specified amount from each wallet can be performed without + * exceeding the wallet's balance. It throws a BalanceIsEmpty exception if any wallet's balance is empty. + * It throws an InsufficientFunds exception if the total amount of the transfers exceeds any wallet's balance. + * + * @param TransferLazyDtoInterface[] $objects An array of transfer objects. * - * @throws BalanceIsEmpty - * @throws InsufficientFunds + * @throws BalanceIsEmpty If any wallet's balance is empty. + * @throws InsufficientFunds If the total amount of the transfers exceeds any wallet's balance. */ public function checkTransfer(array $objects): void; } diff --git a/src/Services/DiscountServiceInterface.php b/src/Services/DiscountServiceInterface.php index 83db202e0..19dfa4059 100644 --- a/src/Services/DiscountServiceInterface.php +++ b/src/Services/DiscountServiceInterface.php @@ -11,5 +11,21 @@ */ interface DiscountServiceInterface { + /** + * Calculates the discount amount for the given customer and product. + * + * The discount amount is calculated by subtracting the product's price from the customer's available balance. + * This method is used to determine the cost of the product after applying the discount. + * + * @param Wallet $customer The wallet object representing the customer. The discount is calculated based on the + * customer's available balance. + * @param Wallet $product The wallet object representing the product. The price of the product is used to calculate + * the discount amount. + * @return int The discount amount. The value is negative and represents the amount that the customer will pay less + * for the product. The returned value is the sum of the product's price and the discount amount. + * + * @see \Bavix\Wallet\Interfaces\Wallet::getBalance() + * @see \Bavix\Wallet\Interfaces\Wallet::getAmount() + */ public function getDiscount(Wallet $customer, Wallet $product): int; } diff --git a/src/Services/EagerLoaderServiceInterface.php b/src/Services/EagerLoaderServiceInterface.php index ee9b597ed..16057ea20 100644 --- a/src/Services/EagerLoaderServiceInterface.php +++ b/src/Services/EagerLoaderServiceInterface.php @@ -8,11 +8,26 @@ use Bavix\Wallet\Internal\Dto\BasketDtoInterface; /** - * Ad hoc solution... Needed for internal purposes only. Helps to optimize greedy queries inside laravel. + * This class is responsible for eager loading wallets by basket. + * + * Eager loading is a database query optimization technique that helps to reduce the number of queries executed in + * a program by preloading all of the related data at once. + * + * This interface provides a method for loading wallets by basket. * * @api */ interface EagerLoaderServiceInterface { + /** + * Load wallets by basket. + * + * This method is responsible for loading wallets by basket. + * The Customer object represents the customer who created the basket. + * The BasketDtoInterface object represents the basket. + * + * @param Customer $customer The customer who created the basket. + * @param BasketDtoInterface $basketDto The basket. + */ public function loadWalletsByBasket(Customer $customer, BasketDtoInterface $basketDto): void; } diff --git a/src/Services/ExchangeServiceInterface.php b/src/Services/ExchangeServiceInterface.php index 00ef5d78e..b2c5eb564 100644 --- a/src/Services/ExchangeServiceInterface.php +++ b/src/Services/ExchangeServiceInterface.php @@ -12,7 +12,12 @@ interface ExchangeServiceInterface { /** - * Currency conversion method. + * Performs a currency conversion from the specified source currency to the target currency. + * + * @param string $fromCurrency The source currency code. + * @param string $toCurrency The target currency code. + * @param float|int|string $amount The amount to be converted. + * @return string The converted amount. */ public function convertTo(string $fromCurrency, string $toCurrency, float|int|string $amount): string; } diff --git a/src/Services/FormatterServiceInterface.php b/src/Services/FormatterServiceInterface.php index 1b1ff1767..b51255bfe 100644 --- a/src/Services/FormatterServiceInterface.php +++ b/src/Services/FormatterServiceInterface.php @@ -9,7 +9,21 @@ */ interface FormatterServiceInterface { + /** + * Convert an amount to an integer value with given decimal places. + * + * @param string|int|float $amount The amount to convert. + * @param int $decimalPlaces The number of decimal places. + * @return string The integer value of the amount. + */ public function intValue(string|int|float $amount, int $decimalPlaces): string; + /** + * Convert an amount to a float value with given decimal places. + * + * @param string|int|float $amount The amount to convert. + * @param int $decimalPlaces The number of decimal places. + * @return string The float value of the amount. + */ public function floatValue(string|int|float $amount, int $decimalPlaces): string; } diff --git a/src/Services/PrepareServiceInterface.php b/src/Services/PrepareServiceInterface.php index bf5f90701..c8892c0d9 100644 --- a/src/Services/PrepareServiceInterface.php +++ b/src/Services/PrepareServiceInterface.php @@ -17,9 +17,15 @@ interface PrepareServiceInterface { /** - * @param null|array $meta + * Deposit the specified amount of money into the wallet. * - * @throws AmountInvalid + * @param float|int|string $amount The amount to deposit. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @param null|string $uuid The UUID of the transaction. + * @return TransactionDtoInterface The created transaction. + * + * @throws AmountInvalid If the amount is invalid. */ public function deposit( Wallet $wallet, @@ -30,9 +36,15 @@ public function deposit( ): TransactionDtoInterface; /** - * @param null|array $meta + * Withdraw the specified amount of money from the wallet. + * + * @param float|int|string $amount The amount to withdraw. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @param null|string $uuid The UUID of the transaction. + * @return TransactionDtoInterface The created transaction. * - * @throws AmountInvalid + * @throws AmountInvalid If the amount is invalid. */ public function withdraw( Wallet $wallet, @@ -43,9 +55,19 @@ public function withdraw( ): TransactionDtoInterface; /** - * @param ExtraDtoInterface|array|null $meta + * Transfer funds from one wallet to another. + * + * This function is a shortcut for the transferExtraLazy method. + * It does not allow to specify the wallets models explicitly. * - * @throws AmountInvalid + * @param Wallet $from The wallet from which funds are transferred. + * @param Wallet $to The wallet to which funds are transferred. + * @param string $status The status of the transfer. + * @param float|int|string $amount The amount of funds to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transfer. + * @return TransferLazyDtoInterface The transfer DTO. + * + * @throws AmountInvalid If the amount is invalid. */ public function transferLazy( Wallet $from, @@ -56,9 +78,18 @@ public function transferLazy( ): TransferLazyDtoInterface; /** - * @param ExtraDtoInterface|array|null $meta + * Transfer funds from one wallet to another with extra data. + * + * @param Wallet $from The wallet from which funds are transferred. + * @param WalletModel $fromWallet The model of the wallet from which funds are transferred. + * @param Wallet $to The wallet to which funds are transferred. + * @param WalletModel $toWallet The model of the wallet to which funds are transferred. + * @param string $status The status of the transfer. + * @param float|int|string $amount The amount of funds to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transfer. + * @return TransferLazyDtoInterface The transfer lazy DTO. * - * @throws AmountInvalid + * @throws AmountInvalid If the amount is invalid. */ public function transferExtraLazy( Wallet $from, diff --git a/src/Services/PurchaseServiceInterface.php b/src/Services/PurchaseServiceInterface.php index 5481b1352..be0d16634 100644 --- a/src/Services/PurchaseServiceInterface.php +++ b/src/Services/PurchaseServiceInterface.php @@ -14,7 +14,15 @@ interface PurchaseServiceInterface { /** - * @return Transfer[] + * Retrieve an array of already purchased transfers for a given customer and basket. + * + * This method retrieves an array of already purchased transfers for a given customer and basket. + * The customer and basket are defined by the Customer and BasketDtoInterface objects respectively. + * + * @param Customer $customer The customer to retrieve transfers for. + * @param BasketDtoInterface $basketDto The basket to retrieve transfers for. + * @param bool $gifts [optional] Whether to only retrieve gift transfers or not. Default is false. + * @return Transfer[] An array of already purchased transfers. */ public function already(Customer $customer, BasketDtoInterface $basketDto, bool $gifts = false): array; } diff --git a/src/Services/RegulatorServiceInterface.php b/src/Services/RegulatorServiceInterface.php index 214942d60..7014c28b8 100644 --- a/src/Services/RegulatorServiceInterface.php +++ b/src/Services/RegulatorServiceInterface.php @@ -11,21 +11,88 @@ */ interface RegulatorServiceInterface { + /** + * Forget the stored value for the given wallet. + * + * This method removes the stored value associated with the provided wallet from the storage. + * + * @param Wallet $wallet The wallet to forget. + * @return bool True if the value was successfully forgotten, false otherwise. + */ public function forget(Wallet $wallet): bool; + /** + * Calculate the difference between the current balance and the given value. + * + * This method subtracts the given value from the current balance and returns the result. + * + * @param Wallet $wallet The wallet to calculate the difference for. + * @return non-empty-string The difference, formatted as a string with the same decimal places as the wallet. + */ public function diff(Wallet $wallet): string; + /** + * Get the current balance of the wallet. + * + * This method retrieves the current balance of the wallet from the storage. + * + * @param Wallet $wallet The wallet to get the balance for. + * @return non-empty-string The current balance, formatted as a string with the same decimal places as the wallet. + */ public function amount(Wallet $wallet): string; + /** + * Synchronize the stored value for the given wallet with the given value. + * + * This method updates the stored value associated with the provided wallet with the given value. + * If the value does not exist, it will be created. If the value already exists, it will be updated. + * + * @param Wallet $wallet The wallet to synchronize. + * @param float|int|non-empty-string $value The value to synchronize. + * @return non-empty-string True if the synchronization was successful, false otherwise. + */ public function sync(Wallet $wallet, float|int|string $value): bool; + /** + * Increase the stored value for the given wallet by the given amount. + * + * This method increases the stored value associated with the provided wallet by the given amount. + * + * @param Wallet $wallet The wallet to increase. + * @param float|int|non-empty-string $value The amount to increase the stored value by. + * @return non-empty-string The updated stored value, formatted as a string with the same decimal places as the wallet. + */ public function increase(Wallet $wallet, float|int|string $value): string; + /** + * Decrease the stored value for the given wallet by the given amount. + * + * This method decreases the stored value associated with the provided wallet by the given amount. + * + * @param Wallet $wallet The wallet to decrease. + * @param float|int|string $value The amount to decrease the stored value by. + * @return string The updated stored value, formatted as a string with the same decimal places as the wallet. + */ public function decrease(Wallet $wallet, float|int|string $value): string; + /** + * Start committing a transaction. + * + * This method starts a transaction and locks the wallet for the duration of the transaction. + */ public function committing(): void; + /** + * Commit the transaction. + * + * This method commits the transaction and unlocks the wallet. + */ public function committed(): void; + /** + * Purge the stored values for all wallets. + * + * This method removes all stored values from the storage. + */ public function purge(): void; } diff --git a/src/Services/TaxServiceInterface.php b/src/Services/TaxServiceInterface.php index 694b9a649..fdb322e58 100644 --- a/src/Services/TaxServiceInterface.php +++ b/src/Services/TaxServiceInterface.php @@ -11,5 +11,19 @@ */ interface TaxServiceInterface { + /** + * Calculates the fee for a given wallet and amount. + * + * This method calculates the fee for a given wallet and amount. + * The fee is determined by the wallet's getFeePercent() method. + * The amount is then multiplied by the fee percentage and divided by 100. + * The result is then rounded to the wallet's decimal places and formatted as a string. + * + * @param Wallet $wallet The wallet to calculate the fee for. + * @param float|int|string $amount The amount to calculate the fee for. + * @return string The fee, formatted as a string with the same decimal places as the wallet. + * + * @see \Bavix\Wallet\Interfaces\Taxable::getFeePercent() + */ public function getFee(Wallet $wallet, float|int|string $amount): string; } diff --git a/src/Services/TransactionServiceInterface.php b/src/Services/TransactionServiceInterface.php index b4991a29c..8e5dd72c5 100644 --- a/src/Services/TransactionServiceInterface.php +++ b/src/Services/TransactionServiceInterface.php @@ -15,9 +15,16 @@ interface TransactionServiceInterface { /** - * @param null|array $meta + * Create a new transaction. * - * @throws RecordNotFoundException + * @param Wallet $wallet The wallet associated with the transaction. + * @param string $type The type of the transaction. + * @param float|int|string $amount The amount of the transaction. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws RecordNotFoundException If the wallet is not found. */ public function makeOne( Wallet $wallet, @@ -28,11 +35,16 @@ public function makeOne( ): Transaction; /** - * @param non-empty-array $wallets - * @param non-empty-array $objects - * @return non-empty-array + * Apply multiple transactions to multiple wallets. + * + * This method applies multiple transactions to multiple wallets. It takes an array of wallets and an array of + * transaction objects as input. It returns an array of transactions. + * + * @param non-empty-array $wallets An array of wallets to apply the transactions to. + * @param non-empty-array $objects An array of transaction objects. + * @return non-empty-array An array of transactions. * - * @throws RecordNotFoundException + * @throws RecordNotFoundException If any of the wallets are not found. */ public function apply(array $wallets, array $objects): array; } diff --git a/src/Services/TransferServiceInterface.php b/src/Services/TransferServiceInterface.php index a50a453c6..21d027854 100644 --- a/src/Services/TransferServiceInterface.php +++ b/src/Services/TransferServiceInterface.php @@ -17,18 +17,32 @@ interface TransferServiceInterface { /** - * @param int[] $ids + * Updates the status of transfers identified by their IDs. + * + * This method updates the status field of transfers in the repository + * to the provided status, for all transfers whose IDs are included + * in the provided array. The method returns `true` if at least one + * transfer was updated, or `false` if no transfers were updated. + * + * @param string $status The new status to set for the specified transfers. + * @param int[] $ids A non-empty array of transfer IDs to update. + * @return bool `true` if at least one transfer was updated, `false` otherwise. */ public function updateStatusByIds(string $status, array $ids): bool; /** - * @param non-empty-array $objects - * @return non-empty-array + * Applies a set of transfer operations in a single database transaction. + * + * This method takes an array of transfer objects and applies them, + * creating transfers and corresponding transactions. + * + * @param non-empty-array $objects The array of transfer operations to apply. + * @return non-empty-array An array of created transfers, indexed by their IDs. * - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws RecordNotFoundException If a wallet referenced in the transfer operations is not found. + * @throws RecordsNotFoundException If a wallet referenced in the transfer operations is not found. + * @throws TransactionFailedException If the transaction fails for any reason. + * @throws ExceptionInterface If an unexpected error occurs. */ public function apply(array $objects): array; } diff --git a/src/Services/WalletServiceInterface.php b/src/Services/WalletServiceInterface.php index be61beb7f..f98d281b6 100644 --- a/src/Services/WalletServiceInterface.php +++ b/src/Services/WalletServiceInterface.php @@ -14,34 +14,73 @@ interface WalletServiceInterface { /** + * Create a new wallet for the given model. + * + * @param Model $model The model the wallet belongs to. * @param array{ * name: string, * slug?: string, * description?: string, * meta?: array|null, * decimal_places?: positive-int, - * } $data + * } $data The data for the new wallet. + * @return Wallet The newly created wallet. */ public function create(Model $model, array $data): Wallet; + /** + * Find a wallet by slug for the given model. + * + * @param Model $model The model the wallet belongs to. + * @param string $slug The slug of the wallet. + * @return Wallet|null The wallet with the given slug if found, otherwise null. + */ public function findBySlug(Model $model, string $slug): ?Wallet; + /** + * Find a wallet by UUID. + * + * @param string $uuid The UUID of the wallet. + * @return Wallet|null The wallet with the given UUID if found, otherwise null. + */ public function findByUuid(string $uuid): ?Wallet; + /** + * Find a wallet by ID. + * + * @param int $id The ID of the wallet. + * @return Wallet|null The wallet with the given ID if found, otherwise null. + */ public function findById(int $id): ?Wallet; /** - * @throws ModelNotFoundException + * Get a wallet by slug for the given model. + * + * @param Model $model The model the wallet belongs to. + * @param string $slug The slug of the wallet. + * @return Wallet The wallet with the given slug. + * + * @throws ModelNotFoundException If the wallet with the given slug is not found. */ public function getBySlug(Model $model, string $slug): Wallet; /** - * @throws ModelNotFoundException + * Get a wallet by UUID. + * + * @param string $uuid The UUID of the wallet. + * @return Wallet The wallet with the given UUID. + * + * @throws ModelNotFoundException If the wallet with the given UUID is not found. */ public function getByUuid(string $uuid): Wallet; /** - * @throws ModelNotFoundException + * Get a wallet by its ID. + * + * @param int $id The ID of the wallet. + * @return Wallet The wallet with the given ID. + * + * @throws ModelNotFoundException If the wallet with the given ID is not found. */ public function getById(int $id): Wallet; } diff --git a/src/Traits/CanConfirm.php b/src/Traits/CanConfirm.php index e49377d71..f21d6e3af 100644 --- a/src/Traits/CanConfirm.php +++ b/src/Traits/CanConfirm.php @@ -27,36 +27,69 @@ trait CanConfirm { /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws ConfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Confirm transaction. + * + * This method confirms the given transaction if it is not already confirmed. + * + * @param Transaction $transaction The transaction to confirm. + * @return bool Returns true if the transaction was confirmed, false otherwise. + * + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If there are insufficient funds. + * @throws ConfirmedInvalid If the transaction is already confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function confirm(Transaction $transaction): bool { + // Check if the wallet has enough money return app(AtomicServiceInterface::class)->block($this, function () use ($transaction): bool { if ($transaction->type === Transaction::TYPE_WITHDRAW) { + // Check if the wallet has enough money app(ConsistencyServiceInterface::class)->checkPotential( + // Get the wallet app(CastServiceInterface::class)->getWallet($this), + // Negative amount app(MathServiceInterface::class)->negative($transaction->amount) ); } + // Force confirm the transaction return $this->forceConfirm($transaction); }); } + /** + * Force confirm the transaction. + * + * This method forces the confirmation of the given transaction even if it is already confirmed. + * If the transaction is already confirmed, a `ConfirmedInvalid` exception will be thrown. + * If the transaction does not belong to the wallet, a `WalletOwnerInvalid` exception will be thrown. + * If the transaction was not found, a `RecordNotFoundException` will be thrown. + * + * @param Transaction $transaction The transaction to confirm. + * @return bool Returns true if the transaction was confirmed, false otherwise. + * + * @throws ConfirmedInvalid If the transaction is already confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. + */ public function safeConfirm(Transaction $transaction): bool { try { + // Attempt to confirm the transaction return $this->confirm($transaction); - } catch (ConfirmedInvalid) { + } catch (ConfirmedInvalid $e) { + // If the transaction is already confirmed, return true return true; - } catch (ExceptionInterface) { + } catch (ExceptionInterface $e) { + // If an exception occurred, return false return false; } } @@ -64,61 +97,104 @@ public function safeConfirm(Transaction $transaction): bool /** * Removal of confirmation (forced), use at your own peril and risk. * - * @throws UnconfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method is used to remove the confirmation from a transaction. + * If the transaction is already confirmed, a `UnconfirmedInvalid` exception will be thrown. + * If the transaction does not belong to the wallet, a `WalletOwnerInvalid` exception will be thrown. + * If the transaction was not found, a `RecordNotFoundException` will be thrown. + * If an exception occurred, a `TransactionFailedException` or `ExceptionInterface` will be thrown. + * + * @param Transaction $transaction The transaction to reset. + * @return bool Returns true if the confirmation was reset, false otherwise. + * + * @throws UnconfirmedInvalid If the transaction is not confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function resetConfirm(Transaction $transaction): bool { + // Reset the confirmation of the transaction in a single database transaction return app(AtomicServiceInterface::class)->block($this, function () use ($transaction) { + // Check if the transaction is already confirmed if (! $transaction->confirmed) { throw new UnconfirmedInvalid( + // If the transaction is not confirmed, throw an `UnconfirmedInvalid` exception app(TranslatorServiceInterface::class)->get('wallet::errors.unconfirmed_invalid'), + // The code of the exception ExceptionInterface::UNCONFIRMED_INVALID ); } + // Check if the transaction belongs to the wallet $wallet = app(CastServiceInterface::class)->getWallet($this); if ($wallet->getKey() !== $transaction->wallet_id) { throw new WalletOwnerInvalid( + // If the transaction does not belong to the wallet, throw a `WalletOwnerInvalid` exception app(TranslatorServiceInterface::class)->get('wallet::errors.owner_invalid'), + // The code of the exception ExceptionInterface::WALLET_OWNER_INVALID ); } + // Decrease the amount of the wallet app(RegulatorServiceInterface::class)->decrease($wallet, $transaction->amount); + // Reset the confirmation of the transaction return $transaction->update([ 'confirmed' => false, ]); }); } + /** + * Safely reset the confirmation of the transaction. + * + * This method attempts to reset the confirmation of the given transaction. If an exception occurs during the + * confirmation process, it will be caught and handled. If the confirmation is successfully reset, true will be + * returned. If an exception occurs, false will be returned. + * + * @param Transaction $transaction The transaction to reset. + * @return bool Returns true if the confirmation was reset, false otherwise. + */ public function safeResetConfirm(Transaction $transaction): bool { try { + // Attempt to reset the confirmation of the transaction return $this->resetConfirm($transaction); - } catch (UnconfirmedInvalid) { + } catch (UnconfirmedInvalid $e) { + // If the transaction is not confirmed, simply return true return true; - } catch (ExceptionInterface) { + } catch (ExceptionInterface $e) { + // If an exception occurs, return false return false; } } /** - * @throws ConfirmedInvalid - * @throws WalletOwnerInvalid - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Forces the confirmation of a transaction. + * + * This method attempts to confirm a transaction by decreasing the wallet's balance by the transaction's amount. + * If the transaction is already confirmed or does not belong to the wallet, an exception will be thrown. + * If the confirmation is successfully reset, true will be returned. If an exception occurs, false will be + * returned. + * + * @param Transaction $transaction The transaction to confirm. + * @return bool Returns true if the confirmation was reset, false otherwise. + * + * @throws ConfirmedInvalid If the transaction is already confirmed. + * @throws WalletOwnerInvalid If the transaction does not belong to the wallet. + * @throws RecordNotFoundException If the transaction was not found. + * @throws RecordsNotFoundException If no transactions were found. + * @throws TransactionFailedException If the transaction failed. + * @throws ExceptionInterface If an exception occurred. */ public function forceConfirm(Transaction $transaction): bool { + // Attempt to confirm the transaction in a single database transaction return app(AtomicServiceInterface::class)->block($this, function () use ($transaction) { + // Check if the transaction is already confirmed if ($transaction->confirmed) { throw new ConfirmedInvalid( app(TranslatorServiceInterface::class)->get('wallet::errors.confirmed_invalid'), @@ -126,6 +202,7 @@ public function forceConfirm(Transaction $transaction): bool ); } + // Check if the transaction belongs to the wallet $wallet = app(CastServiceInterface::class)->getWallet($this); if ($wallet->getKey() !== $transaction->wallet_id) { throw new WalletOwnerInvalid( @@ -134,8 +211,10 @@ public function forceConfirm(Transaction $transaction): bool ); } + // Increase the balance of the wallet app(RegulatorServiceInterface::class)->increase($wallet, $transaction->amount); + // Confirm the transaction return $transaction->update([ 'confirmed' => true, ]); diff --git a/src/Traits/CanExchange.php b/src/Traits/CanExchange.php index 5433928df..e356dfea5 100644 --- a/src/Traits/CanExchange.php +++ b/src/Traits/CanExchange.php @@ -30,47 +30,133 @@ trait CanExchange { /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Exchange currency from this wallet to another wallet. + * + * This method attempts to exchange currency from this wallet to another wallet. + * It uses the AtomicServiceInterface to ensure atomicity and consistency of the exchange. + * The ConsistencyServiceInterface is used to check if the exchange is possible before attempting it. + * + * @param Wallet $to The wallet to exchange the currency to. + * @param int|string $amount The amount to exchange. + * @param ExtraDtoInterface|array|null $meta The extra data for the transaction. + * @return Transfer The created transfer. + * + * @throws BalanceIsEmpty if the wallet does not have enough funds to make the exchange. + * @throws InsufficientFunds if the wallet does not have enough funds to make the exchange. + * @throws RecordNotFoundException if the wallet does not exist. + * @throws RecordsNotFoundException if the wallet does not exist. + * @throws TransactionFailedException if the transaction fails. + * @throws ExceptionInterface if an unexpected error occurs. */ public function exchange(Wallet $to, int|string $amount, ExtraDtoInterface|array|null $meta = null): Transfer { + // Execute the exchange operation atomically + // AtomicServiceInterface ensures that the exchange operation is performed as a single, indivisible action + // to prevent race conditions and ensure consistency. return app(AtomicServiceInterface::class)->block($this, function () use ($to, $amount, $meta): Transfer { + // Check if the exchange is possible before attempting it + // ConsistencyServiceInterface checks if the exchange is possible before attempting it. + // This helps to avoid unnecessary failures and ensures that the exchange is valid. app(ConsistencyServiceInterface::class)->checkPotential($this, $amount); + // Perform the exchange + // forceExchange is called to perform the actual exchange of currency. + // If the exchange is not possible, an exception is thrown. return $this->forceExchange($to, $amount, $meta); }); } + /** + * Safely attempts to exchange currency from this wallet to another wallet. + * + * This method attempts to exchange currency from this wallet to another wallet. + * If an error occurs during the process, null is returned. + * + * @param Wallet $to The wallet to exchange the currency to. + * @param int|string $amount The amount to exchange. + * @param ExtraDtoInterface|array|null $meta The extra data for the transaction. + * @return null|Transfer The created transfer, or null if an error occurred. + * + * @throws ExceptionInterface If an unexpected error occurs. + */ public function safeExchange(Wallet $to, int|string $amount, ExtraDtoInterface|array|null $meta = null): ?Transfer { try { + // Execute the exchange operation and return the created transfer. + // If an error occurs during the process, an exception is thrown. return $this->exchange($to, $amount, $meta); - } catch (ExceptionInterface) { + } catch (ExceptionInterface $e) { + // If an exception occurs during the exchange process, return null. return null; } } /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Force exchange currency from this wallet to another wallet. + * + * This method will throw an exception if the exchange is not possible. + * + * @param Wallet $to The wallet to exchange the currency to. + * @param int|string $amount The amount to exchange. + * @param ExtraDtoInterface|array|null $meta The extra data for the transaction. + * @return Transfer The created transfer. + * + * @throws RecordNotFoundException If the wallet does not exist. + * @throws RecordsNotFoundException If the wallet does not exist. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an unexpected error occurs. */ public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface|array|null $meta = null): Transfer { - return app(AtomicServiceInterface::class)->block($this, function () use ($to, $amount, $meta): Transfer { - $extraAssembler = app(ExtraDtoAssemblerInterface::class); - $prepareService = app(PrepareServiceInterface::class); - $mathService = app(MathServiceInterface::class); - $castService = app(CastServiceInterface::class); - $taxService = app(TaxServiceInterface::class); + // Get the atomic service to execute the exchange operation as a single, indivisible action. + $atomicService = app(AtomicServiceInterface::class); + + // Get the extra assembler to assemble the extra data for the transaction. + $extraAssembler = app(ExtraDtoAssemblerInterface::class); + + // Get the prepare service to prepare the transfer operation. + $prepareService = app(PrepareServiceInterface::class); + + // Get the math service to perform mathematical operations. + $mathService = app(MathServiceInterface::class); + + // Get the cast service to cast the wallet to the correct type. + $castService = app(CastServiceInterface::class); + + // Get the tax service to calculate the tax fee. + $taxService = app(TaxServiceInterface::class); + + // Get the exchange service to convert the currency. + $exchangeService = app(ExchangeServiceInterface::class); + + // Get the transfer lazy DTO assembler to assemble the transfer lazy DTO. + $transferLazyDtoAssembler = app(TransferLazyDtoAssemblerInterface::class); + + // Get the transfer service to apply the transfer operation. + $transferService = app(TransferServiceInterface::class); + + // Execute the exchange operation as a single, indivisible action. + return $atomicService->block($this, function () use ( + $to, + $amount, + $meta, + $extraAssembler, + $prepareService, + $mathService, + $castService, + $taxService, + $exchangeService, + $transferLazyDtoAssembler, + $transferService + ): Transfer { + // Assemble the extra data for the transaction. + $extraDto = $extraAssembler->create($meta); + + // Calculate the tax fee. $fee = $taxService->getFee($to, $amount); - $rate = app(ExchangeServiceInterface::class)->convertTo( + + // Convert the currency to the target wallet currency. + $rate = $exchangeService->convertTo( $castService->getWallet($this) ->getCurrencyAttribute(), $castService->getWallet($to) @@ -78,9 +164,10 @@ public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface| 1 ); - $extraDto = $extraAssembler->create($meta); + // Get the withdraw option from the extra data. $withdrawOption = $extraDto->getWithdrawOption(); - $depositOption = $extraDto->getDepositOption(); + + // Prepare the withdraw operation. $withdrawDto = $prepareService->withdraw( $this, $mathService->add($amount, $fee), @@ -88,6 +175,11 @@ public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface| $withdrawOption->isConfirmed(), $withdrawOption->getUuid(), ); + + // Get the deposit option from the extra data. + $depositOption = $extraDto->getDepositOption(); + + // Prepare the deposit operation. $depositDto = $prepareService->deposit( $to, $mathService->floor($mathService->mul($amount, $rate, 1)), @@ -95,7 +187,9 @@ public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface| $depositOption->isConfirmed(), $depositOption->getUuid(), ); - $transferLazyDto = app(TransferLazyDtoAssemblerInterface::class)->create( + + // Assemble the transfer lazy DTO. + $transferLazyDto = $transferLazyDtoAssembler->create( $this, $to, 0, @@ -107,8 +201,10 @@ public function forceExchange(Wallet $to, int|string $amount, ExtraDtoInterface| $extraDto->getExtra() ); - $transfers = app(TransferServiceInterface::class)->apply([$transferLazyDto]); + // Apply the transfer operation. + $transfers = $transferService->apply([$transferLazyDto]); + // Return the created transfer. return current($transfers); }); } diff --git a/src/Traits/CanPay.php b/src/Traits/CanPay.php index 52d07d3d9..2df9ead5b 100644 --- a/src/Traits/CanPay.php +++ b/src/Traits/CanPay.php @@ -27,109 +27,299 @@ trait CanPay use CartPay; /** - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Perform a free payment for a product. + * + * @param ProductInterface $product The product for which the payment should be made. + * @return Transfer The Transfer instance representing the successfully paid item. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + * + * @see \Bavix\Wallet\Interfaces\Customer::payFreeCart */ public function payFree(ProductInterface $product): Transfer { - return current($this->payFreeCart(app(Cart::class)->withItem($product))); + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Pay for the items in the cart without any payment. + // Return the first transfer from the result of the payment. + return current($this->payFreeCart($cart)); } + /** + * Safely pays for a product. + * + * Attempts to pay for the specified product. If the payment is successful, + * the method returns the first Transfer instance from the result of the payment. + * If the payment fails, the method returns null. + * + * @param ProductInterface $product The product for which the payment should be made. + * @param bool $force Whether to force the purchase. Defaults to false. + * @return Transfer|null The first Transfer instance representing the successfully paid item, or null if the payment failed. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + * + * @see \Bavix\Wallet\Interfaces\Customer::safePayCart + */ public function safePay(ProductInterface $product, bool $force = false): ?Transfer { - return current($this->safePayCart(app(Cart::class)->withItem($product), $force)) ?: null; + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Safely pay for the items in the cart. + // Return the first transfer from the result of the payment, or null if the payment failed. + return current($this->safePayCart($cart, $force)) ?: null; } /** - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Pays for the given product. + * + * This method is used to pay for a specific product. It creates a cart with the given product and + * then pays for the items in the cart. If the payment is successful, the method returns the + * transfer object representing the payment. If the payment fails due to insufficient funds or + * an empty balance, it throws an exception. If the payment fails due to a reason other than the + * above, it throws a more specific exception. + * + * @param ProductInterface $product The product to pay for. + * @param bool $force [optional] Whether to force the payment. Defaults to false. + * @return Transfer The transfer object representing the payment. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function pay(ProductInterface $product, bool $force = false): Transfer { - return current($this->payCart(app(Cart::class)->withItem($product), $force)); + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Pay for the items in the cart. + // Return the first transfer from the result of the payment. + return current($this->payCart($cart, $force)); } /** - * @throws ProductEnded - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Forces the payment of the given product. + * + * This method forcefully attempts to pay the given product. If the payment is successful, + * the method returns the transfer object. If the payment fails due to insufficient funds or + * empty balance, it throws an exception. If the payment fails due to a reason + * other than the above, it throws a more specific exception. + * + * @param ProductInterface $product The product for which the payment should be made. + * @return Transfer The transfer object representing the successfully paid item. + * + * @throws ProductEnded If the product is ended. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If the payment transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forcePay(ProductInterface $product): Transfer { - return current($this->forcePayCart(app(Cart::class)->withItem($product))); + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Forcefully pay for the items in the cart. + // Return the first transfer from the result of the payment. + return current($this->forcePayCart($cart)); } + /** + * Safely refunds the given product. + * + * This method attempts to refund the given product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or empty balance, + * it returns false. If the refund fails due to a reason other than the above, it throws + * a more specific exception. + * + * @param ProductInterface $product The product for which the refund should be made. + * @param bool $force Whether to force the refund even if the balance is empty. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + * + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If an exception occurs. + */ public function safeRefund(ProductInterface $product, bool $force = false, bool $gifts = false): bool { - return $this->safeRefundCart(app(Cart::class)->withItem($product), $force, $gifts); + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Safely refund the items in the cart. + // Return the result of the refund. + return $this->safeRefundCart($cart, $force, $gifts); } /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds the given product. + * + * This method attempts to refund the given product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or empty balance, + * it returns false. If the refund fails due to a reason other than the above, it throws + * a more specific exception. + * + * @param ProductInterface $product The product for which the refund should be made. + * @param bool $force Whether to force the refund even if the balance is empty. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If an exception occurs. */ public function refund(ProductInterface $product, bool $force = false, bool $gifts = false): bool { - return $this->refundCart(app(Cart::class)->withItem($product), $force, $gifts); + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Refund the items in the cart. + // Return the result of the refund. + return $this->refundCart($cart, $force, $gifts); } /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Forcefully refunds the given product. + * + * This method forcefully attempts to refund the given product. If the refund is successful, + * the method returns true. If the refund fails due to insufficient funds or empty balance, + * it throws an exception. If the refund fails due to a reason other than the above, it throws + * a more specific exception. + * + * @param ProductInterface $product The product for which the refund should be made. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If an exception occurs. */ public function forceRefund(ProductInterface $product, bool $gifts = false): bool { - return $this->forceRefundCart(app(Cart::class)->withItem($product), $gifts); + // Create a cart with the specified product. + $cart = app(Cart::class)->withItem($product); + + // Forcefully refund the items in the cart. + // Return the result of the refund. + return $this->forceRefundCart($cart, $gifts); } + /** + * Safely refunds a gift product. + * + * This method safely attempts to refund the given gift product. If the refund is successful, + * it returns true. If the refund fails due to insufficient funds or empty balance, it returns false. + * + * @param ProductInterface $product The gift product to be refunded. + * @param bool $force Whether to force the refund even if the balance is empty. + * @return bool True if the refund is successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If an exception occurs. + */ public function safeRefundGift(ProductInterface $product, bool $force = false): bool { - return $this->safeRefundGiftCart(app(Cart::class)->withItem($product), $force); + // Create a cart with the specified gift product. + $cart = app(Cart::class)->withItem($product); + + // Safely refund the items in the cart. + // Return the result of the refund. + return $this->safeRefundGiftCart($cart, $force); } /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds a gift product. + * + * This method attempts to refund the given gift product. If the refund is successful, + * it returns true. If the refund fails due to insufficient funds or empty balance, it returns false. + * + * @param ProductInterface $product The gift product to be refunded. + * @param bool $force Whether to force the refund even if the balance is empty. + * @return bool True if the refund is successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty. + * @throws InsufficientFunds If there are not enough funds in the wallet. + * @throws RecordNotFoundException If the gift product cannot be found. + * @throws RecordsNotFoundException If no transfers are found for the gift product. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If the refund fails for any other reason. */ public function refundGift(ProductInterface $product, bool $force = false): bool { - return $this->refundGiftCart(app(Cart::class)->withItem($product), $force); + // Create a cart with the specified gift product. + $cart = app(Cart::class)->withItem($product); + + // Attempt to refund the items in the cart. + // Return the result of the refund. + return $this->refundGiftCart($cart, $force); } /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Forcefully refunds a gift product. + * + * This method is designed to handle situations where a normal refund operation might not be possible, + * such as when a customer's wallet balance is insufficient or the item's transaction records cannot be found. + * It forcefully attempts to refund the given gift product by creating a special cart for the refund operation + * and then processing the refund through this cart. This approach ensures that even in cases where standard + * refund mechanisms fail, there is a fallback option to attempt the refund, thus providing a robust solution + * for handling refunds in exceptional circumstances. + * + * @param ProductInterface $product The gift product to be refunded. + * @return bool True if the refund is successful, false otherwise. + * + * @throws BalanceIsEmpty Exception thrown if the customer's balance is empty, indicating + * that there are no funds available for a refund. + * @throws InsufficientFunds Exception thrown if the wallet does not have enough funds to cover the refund. + * @throws RecordNotFoundException Exception thrown if the specified gift product cannot be located. + * @throws RecordsNotFoundException Exception thrown if no transfer records can be found for the gift product, + * indicating that the product has not been properly processed for a refund. + * @throws TransactionFailedException Exception thrown if the refund transaction fails to process correctly. + * @throws ModelNotFoundException Exception thrown if the model related to the refund operation cannot be found. + * @throws ExceptionInterface General exception thrown for any other errors encountered during the refund process. */ public function forceRefundGift(ProductInterface $product): bool { - return $this->forceRefundGiftCart(app(Cart::class)->withItem($product)); + // Initialize a cart specifically for the refund operation. + // This cart is populated with the product that needs to be refunded. + $cart = app(Cart::class)->withItem($product); + + // Attempt to refund the product by processing the refund through the initialized cart. + // The forceRefundGiftCart method encapsulates the logic to forcefully execute the refund. + // It returns true if the refund is successful, otherwise it will throw the appropriate exceptions + // as defined above to indicate the nature of the failure. + return $this->forceRefundGiftCart($cart); } } diff --git a/src/Traits/CanPayFloat.php b/src/Traits/CanPayFloat.php index 23e2c07f4..746a5711f 100644 --- a/src/Traits/CanPayFloat.php +++ b/src/Traits/CanPayFloat.php @@ -4,6 +4,18 @@ namespace Bavix\Wallet\Traits; +use Bavix\Wallet\Exceptions\AmountInvalid; +use Bavix\Wallet\Exceptions\BalanceIsEmpty; +use Bavix\Wallet\Exceptions\InsufficientFunds; +use Bavix\Wallet\Interfaces\Customer; +use Bavix\Wallet\Interfaces\Wallet; +use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; +use Bavix\Wallet\Internal\Exceptions\TransactionFailedException; +use Bavix\Wallet\Models\Transaction; +use Bavix\Wallet\Models\Transfer; +use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\RecordsNotFoundException; + /** * @psalm-require-extends \Illuminate\Database\Eloquent\Model * @@ -11,17 +23,141 @@ */ trait CanPayFloat { + /** + * Trait `HasWalletFloat` conflicts with `CanPay` methods. + * We need to exclude these methods from `HasWalletFloat`. + * + * @see HasWalletFloat + * @see CanPay + * + * The methods are "excluded" using the `insteadof` keyword. + * This allows us to use the `CanPay` trait without causing conflicts. + */ use HasWalletFloat, CanPay { + /** + * Deposit a float amount of money into the wallet. + * + * @param float|int|string $amount The amount to deposit. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + */ CanPay::deposit insteadof HasWalletFloat; + + /** + * Withdraw the specified float amount of money from the wallet. + * + * @param float|int|string $amount The amount to withdraw. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + */ CanPay::withdraw insteadof HasWalletFloat; + + /** + * Check if the wallet can be withdrawn. + * + * @param float|int|string $amount The amount to check. + * @return bool Whether the wallet can be withdrawn or not. + */ CanPay::canWithdraw insteadof HasWalletFloat; + + /** + * Forced to withdraw funds from the wallet. + * + * @param float|int|string $amount The amount to withdraw. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + */ CanPay::forceWithdraw insteadof HasWalletFloat; + + /** + * Transfer the specified float amount of money from the wallet to a customer. + * + * @param Customer $customer The customer to transfer to. + * @param float|int|string $amount The amount to transfer. + * @param null|array $meta Additional information for the transaction. + * @return Transfer The created transfer. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + */ CanPay::transfer insteadof HasWalletFloat; + + /** + * Try to transfer the specified float amount of money from the wallet to a customer. + * + * @param Customer $customer The customer to transfer to. + * @param float|int|string $amount The amount to transfer. + * @param null|array $meta Additional information for the transaction. + * @return Transfer|null The created transfer or null if the transfer fails. + */ CanPay::safeTransfer insteadof HasWalletFloat; + + /** + * Forced to transfer the specified float amount of money from the wallet to a customer. + * + * @param Customer $customer The customer to transfer to. + * @param float|int|string $amount The amount to transfer. + * @param null|array $meta Additional information for the transaction. + * @return Transfer The created transfer. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + */ CanPay::forceTransfer insteadof HasWalletFloat; + + /** + * Get the transactions of the wallet. + * + * @return MorphMany The transactions of the wallet. + */ CanPay::transactions insteadof HasWalletFloat; + + /** + * Get the transfers of the wallet. + * + * @return MorphMany The transfers of the wallet. + */ CanPay::transfers insteadof HasWalletFloat; + + /** + * Get or set the wallet. + * + * @param Wallet|null $wallet The wallet to set or get. + * @return Wallet The wallet. + */ CanPay::wallet insteadof HasWalletFloat; + + /** + * Get the balance of the wallet as a float. + * + * @return float The balance of the wallet. + */ CanPay::getBalanceAttribute insteadof HasWalletFloat; } } diff --git a/src/Traits/CartPay.php b/src/Traits/CartPay.php index 2c6c9db94..aa4d0f089 100644 --- a/src/Traits/CartPay.php +++ b/src/Traits/CartPay.php @@ -39,100 +39,188 @@ trait CartPay use HasWallet; /** - * @return non-empty-array + * Pay for all products in the cart without any payment. * - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method performs the payment for all products in the cart without any payment. + * It returns an array of Transfer instances representing the successfully paid items. + * + * @param CartInterface $cart The cart containing the products to be paid. + * @return non-empty-array An array of Transfer instances representing the successfully paid items. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function payFreeCart(CartInterface $cart): array { - return app(AtomicServiceInterface::class)->block($this, function () use ($cart) { + $atomicService = app(AtomicServiceInterface::class); + $eagerLoaderService = app(EagerLoaderServiceInterface::class); + $basketService = app(BasketServiceInterface::class); + $availabilityAssembler = app(AvailabilityDtoAssemblerInterface::class); + $translator = app(TranslatorServiceInterface::class); + $consistencyService = app(ConsistencyServiceInterface::class); + $castService = app(CastServiceInterface::class); + $prepareService = app(PrepareServiceInterface::class); + $assistantService = app(AssistantServiceInterface::class); + $transferService = app(TransferServiceInterface::class); + + // Perform the payment for all products in the cart without any payment. + return $atomicService->block($this, function () use ( + $cart, + $eagerLoaderService, + $basketService, + $availabilityAssembler, + $translator, + $consistencyService, + $castService, + $prepareService, + $assistantService, + $transferService + ) { + // Get the basket DTO containing the products in the cart. $basketDto = $cart->getBasketDto(); - $basketService = app(BasketServiceInterface::class); - $availabilityAssembler = app(AvailabilityDtoAssemblerInterface::class); - app(EagerLoaderServiceInterface::class)->loadWalletsByBasket($this, $basketDto); + + // Load the wallets for the products in the cart. + $eagerLoaderService->loadWalletsByBasket($this, $basketDto); + + // Check if the products are available. if (! $basketService->availability($availabilityAssembler->create($this, $basketDto, false))) { throw new ProductEnded( - app(TranslatorServiceInterface::class)->get('wallet::errors.product_stock'), + $translator->get('wallet::errors.product_stock'), ExceptionInterface::PRODUCT_ENDED ); } - app(ConsistencyServiceInterface::class)->checkPotential($this, 0, true); + // Check if the wallet has sufficient funds. + $consistencyService->checkPotential($this, 0, true); + // Prepare the transfers for the products in the cart. $transfers = []; - $castService = app(CastServiceInterface::class); - $prepareService = app(PrepareServiceInterface::class); - $assistantService = app(AssistantServiceInterface::class); + + // Iterate over the items in the cart. foreach ($basketDto->items() as $item) { + // Iterate over the products in the item. foreach ($item->getItems() as $product) { + // Prepare a transfer for the product. $transfers[] = $prepareService->transferExtraLazy( - $this, - $castService->getWallet($this), - $product, - $castService->getWallet($item->getReceiving() ?? $product), - Transfer::STATUS_PAID, - 0, - $assistantService->getMeta($basketDto, $product) + $this, // The customer who is paying for the product. + $castService->getWallet($this), // The customer's wallet. + $product, // The product to be paid. + $castService->getWallet( + $item->getReceiving() ?? $product + ), // The wallet to receive the payment. + Transfer::STATUS_PAID, // The status of the transfer. + 0, // The amount of the transfer. + $assistantService->getMeta($basketDto, $product) // The metadata of the transfer. ); } } - assert($transfers !== []); - - return app(TransferServiceInterface::class)->apply($transfers); + // Ensure that at least one transfer was prepared. + // If the above code executes without throwing an exception, + // we can safely assume that at least one transfer was prepared. + // This assertion is used to ensure that this assumption holds true. + // If the assertion fails, it means that the assumption is incorrect, + // which means that the code execution path leading to this assertion + // is incorrect and should be investigated. + assert($transfers !== [], 'At least one transfer must be prepared.'); + + // Apply the transfers. + return $transferService->apply($transfers); }); } /** - * @return Transfer[] + * Safely pays for the items in the given cart. + * + * This method attempts to pay for all items in the provided cart. If the payment is successful, + * the method returns an array of Transfer instances. If the payment fails, an empty array is returned. + * + * @param CartInterface $cart The cart containing the items to be purchased. + * @param bool $force Whether to force the purchase. Defaults to false. + * @return Transfer[] An array of Transfer instances representing the successfully paid items, or an empty array if the payment failed. */ public function safePayCart(CartInterface $cart, bool $force = false): array { + // Attempt to pay for all items in the provided cart. try { + // If the payment is successful, return the array of Transfer instances. return $this->payCart($cart, $force); - } catch (ExceptionInterface) { + } catch (ExceptionInterface $exception) { + // If the payment fails, return an empty array. return []; } } /** - * @return non-empty-array + * Pays for the items in the given cart. + * + * This method pays for all items in the provided cart. If the payment is successful, + * the method returns an array of Transfer instances. If the payment fails, the method throws an exception. * - * @throws ProductEnded - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @param CartInterface $cart The cart containing the items to be purchased. + * @param bool $force Whether to force the purchase. Defaults to false. + * @return non-empty-array An array of Transfer instances representing the successfully paid items. + * + * @throws ProductEnded If the product is ended. + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordNotFoundException If the record is not found. + * @throws RecordsNotFoundException If the records are not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function payCart(CartInterface $cart, bool $force = false): array { - return app(AtomicServiceInterface::class)->block($this, function () use ($cart, $force) { + $atomicService = app(AtomicServiceInterface::class); + $basketService = app(BasketServiceInterface::class); + $availabilityAssembler = app(AvailabilityDtoAssemblerInterface::class); + $eagerLoaderService = app(EagerLoaderServiceInterface::class); + $castService = app(CastServiceInterface::class); + $prepareService = app(PrepareServiceInterface::class); + $assistantService = app(AssistantServiceInterface::class); + $transferService = app(TransferServiceInterface::class); + $translator = app(TranslatorServiceInterface::class); + $consistencyService = app(ConsistencyServiceInterface::class); + + // Wrap the code in an atomic block to ensure consistency. + return $atomicService->block($this, function () use ( + $cart, + $force, + $basketService, + $availabilityAssembler, + $eagerLoaderService, + $castService, + $prepareService, + $assistantService, + $transferService, + $translator, + $consistencyService + ) { + // Get the items in the cart. $basketDto = $cart->getBasketDto(); - $basketService = app(BasketServiceInterface::class); - $availabilityAssembler = app(AvailabilityDtoAssemblerInterface::class); - app(EagerLoaderServiceInterface::class)->loadWalletsByBasket($this, $basketDto); + + // Load the wallets for the products in the cart. + $eagerLoaderService->loadWalletsByBasket($this, $basketDto); + + // Check if the products are available. if (! $basketService->availability($availabilityAssembler->create($this, $basketDto, $force))) { throw new ProductEnded( - app(TranslatorServiceInterface::class)->get('wallet::errors.product_stock'), + $translator->get('wallet::errors.product_stock'), ExceptionInterface::PRODUCT_ENDED ); } - $prices = []; - $transfers = []; - $castService = app(CastServiceInterface::class); - $prepareService = app(PrepareServiceInterface::class); - $assistantService = app(AssistantServiceInterface::class); + // Prepare the transfers. + $prices = []; // Store the prices of products. + $transfers = []; // Store the prepared transfers. foreach ($cart->getBasketDto()->items() as $item) { foreach ($item->getItems() as $product) { + // Get the price of the product. $productId = $product::class.':'.$castService->getModel($product)->getKey(); $pricePerItem = $item->getPricePerItem(); if ($pricePerItem === null) { @@ -140,66 +228,137 @@ public function payCart(CartInterface $cart, bool $force = false): array $pricePerItem = $prices[$productId]; } + // Prepare the transfer. $transfers[] = $prepareService->transferExtraLazy( $this, - $castService->getWallet($this), - $product, - $castService->getWallet($item->getReceiving() ?? $product), - Transfer::STATUS_PAID, - $pricePerItem, - $assistantService->getMeta($basketDto, $product) + $castService->getWallet($this), // The customer's wallet. + $product, // The product to be paid. + $castService->getWallet( + $item->getReceiving() ?? $product + ), // The wallet to receive the payment. + Transfer::STATUS_PAID, // The status of the transfer. + $pricePerItem, // The amount of the transfer. + $assistantService->getMeta($basketDto, $product) // The metadata of the transfer. ); } } + // Check that the transfers are consistent if the payment is not forced. if (! $force) { - app(ConsistencyServiceInterface::class)->checkTransfer($transfers); + $consistencyService->checkTransfer($transfers); } + // Assert that the $transfers array is not empty. + // This is necessary to avoid a potential PHP warning + // when calling $transferService->apply() with an empty array. + assert($transfers !== [], 'The $transfers array must not be empty.'); - assert($transfers !== []); - - return app(TransferServiceInterface::class)->apply($transfers); + // Apply the transfers. + return $transferService->apply($transfers); }); } /** - * @return non-empty-array + * Forcefully pays for the items in the given cart. + * + * This method attempts to pay for all items in the provided cart by calling the payCart method with force set to true. + * If the payment is successful, an array of Transfer instances is returned. + * If the payment fails, appropriate exceptions are thrown. + * + * @param CartInterface $cart The cart to pay for. + * @return non-empty-array Array of Transfer instances if payment is successful. * - * @throws ProductEnded - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws ProductEnded If a product has ended. + * @throws RecordNotFoundException If a record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If a transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forcePayCart(CartInterface $cart): array { + // Call the payCart method with force set to true. + // This method attempts to pay for all items in the provided cart. + // If the payment is successful, an array of Transfer instances is returned. + // If the payment fails, appropriate exceptions are thrown. return $this->payCart($cart, true); } + /** + * Safely attempts to refund all items in the given cart. + * + * This method safely attempts to refund all items in the provided cart. + * If the refund is successful, the method returns true. + * If the refund fails, the method returns false instead of throwing an exception. + * + * @param CartInterface $cart The cart to refund items from. + * @param bool $force Whether to force the refund even if the balance is empty. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + */ public function safeRefundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool { try { + // Try to refund all items in the provided cart. return $this->refundCart($cart, $force, $gifts); - } catch (ExceptionInterface) { + } catch (ExceptionInterface $e) { + // Return false if an exception occurs during the refund process. + // This is a safe refund method, so we do not rethrow the exception. return false; } } /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Safely refunds all items in the given cart. + * + * This method safely attempts to refund all items in the provided cart. + * If the refund is successful, it returns true. + * If the refund fails, it returns false instead of throwing an exception. + * + * @param CartInterface $cart The cart to refund items from. + * @param bool $force Whether to force the refund even if the balance is empty. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of a wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in a wallet. + * @throws RecordNotFoundException If a record is not found. + * @throws RecordsNotFoundException If multiple records are not found. + * @throws TransactionFailedException If a transaction fails. + * @throws ModelNotFoundException If a model is not found. + * @throws ExceptionInterface If an exception occurs. */ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts = false): bool { - return app(AtomicServiceInterface::class)->block($this, function () use ($cart, $force, $gifts) { - $basketDto = $cart->getBasketDto(); - app(EagerLoaderServiceInterface::class)->loadWalletsByBasket($this, $basketDto); - $transfers = app(PurchaseServiceInterface::class)->already($this, $basketDto, $gifts); + // Get the required services. + $atomicService = app(AtomicServiceInterface::class); + $basketDto = $cart->getBasketDto(); + $eagerLoaderService = app(EagerLoaderServiceInterface::class); + $purchaseService = app(PurchaseServiceInterface::class); + $castService = app(CastServiceInterface::class); + $prepareService = app(PrepareServiceInterface::class); + $assistantService = app(AssistantServiceInterface::class); + $consistencyService = app(ConsistencyServiceInterface::class); + $transferService = app(TransferServiceInterface::class); + + // Wrap the code in an atomic block to ensure consistency. + return $atomicService->block($this, function () use ( + $force, + $gifts, + $basketDto, + $eagerLoaderService, + $purchaseService, + $castService, + $prepareService, + $assistantService, + $consistencyService, + $transferService + ) { + // Load wallets by basket. + $eagerLoaderService->loadWalletsByBasket($this, $basketDto); + + // Get already processed transfers. + $transfers = $purchaseService->already($this, $basketDto, $gifts); + + // Check if the count of transfers is equal to the total items in the basket. if (count($transfers) !== $basketDto->total()) { throw new ModelNotFoundException( "No query results for model [{$this->transfers() @@ -209,13 +368,12 @@ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts ); } + // Prepare transfers for refund. $index = 0; - $objects = []; - $transferIds = []; - $transfers = array_values($transfers); - $castService = app(CastServiceInterface::class); - $prepareService = app(PrepareServiceInterface::class); - $assistantService = app(AssistantServiceInterface::class); + $objects = []; // Array to store the prepared transfers. + $transferIds = []; // Array to store the IDs of the transfers. + $transfers = array_values($transfers); // Convert the transfers array to indexed array. + foreach ($basketDto->items() as $itemDto) { foreach ($itemDto->getItems() as $product) { $transferIds[] = $transfers[$index]->getKey(); @@ -233,80 +391,169 @@ public function refundCart(CartInterface $cart, bool $force = false, bool $gifts } } + // Perform consistency check if force is false. if (! $force) { - app(ConsistencyServiceInterface::class)->checkTransfer($objects); + $consistencyService->checkTransfer($objects); } - assert($objects !== []); - - $transferService = app(TransferServiceInterface::class); + // Ensure there are prepared transfers. + // Assert that the array of prepared transfers is not empty. + // If the array is empty, it means that there are no transfers to be refunded, + // which is not expected and should not happen. + // The assertion is added to ensure that the refund process is not executed + // without any transfers to be refunded. + assert($objects !== [], 'Array of prepared transfers is empty. There are no transfers to be refunded.'); + // Apply refunds. $transferService->apply($objects); + // Update transfer status to refund. return $transferService ->updateStatusByIds(Transfer::STATUS_REFUND, $transferIds); }); } /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Forcefully refunds all items in the cart. + * + * This method forcefully attempts to refund all items in the provided cart. + * If the refund is successful, it returns true. If the refund fails due to + * insufficient funds or empty balance, it throws an exception. If the refund + * fails due to a reason other than the above, it throws a more specific + * exception. + * + * @param CartInterface $cart The cart containing the items to be refunded. + * @param bool $gifts Whether to refund gifts as well. + * @return bool True if the refund is successful, false otherwise. + * + * @throws RecordNotFoundException If the cart or its items are not found. + * @throws RecordsNotFoundException If the records for the refund are not found. + * @throws TransactionFailedException If the transaction fails for any reason. + * @throws ModelNotFoundException If the wallet or the transfer is not found. + * @throws ExceptionInterface If the refund fails for any other reason. */ public function forceRefundCart(CartInterface $cart, bool $gifts = false): bool { + // Set the force flag to true and perform the refund. + // The refundCart method handles the actual refund logic. + // By calling it with the force flag set to true, we ensure that + // the refund is performed even if the balance is empty. return $this->refundCart($cart, true, $gifts); } + /** + * Safely refunds all gifts in the given cart. + * + * This method attempts to refund all gifts in the provided cart safely. + * If the refund is successful, the method returns true. If the refund + * fails, the method returns false. + * + * @param CartInterface $cart The cart containing the gifts to be refunded. + * @param bool $force Whether to force the refund even if the balance is empty. + * @return bool True if the refund is successful, false otherwise. + */ public function safeRefundGiftCart(CartInterface $cart, bool $force = false): bool { try { + // Attempt to refund all gifts in the provided cart. return $this->refundGiftCart($cart, $force); - } catch (ExceptionInterface) { + } catch (ExceptionInterface $e) { + // Return false if an exception occurs during the refund process. + // This is a safe refund method, so we do not rethrow the exception. return false; } } /** - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Refunds all gifts in the given cart. + * + * This method attempts to refund all gifts in the provided cart. + * If the refund is successful, the method returns true. If the refund + * fails, the method throws an exception. + * + * @param CartInterface $cart The cart containing the gifts to be refunded. + * @param bool $force Whether to force the refund even if the balance is empty. + * Defaults to false. + * @return bool True if the refund was successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty and $force is false. + * @throws InsufficientFunds If the balance of the customer is insufficient to cover the refund. + * @throws RecordNotFoundException If the cart or its items are not found. + * @throws RecordsNotFoundException If the records for the refund are not found. + * @throws TransactionFailedException If the transaction fails for any reason. + * @throws ModelNotFoundException If the wallet or the transfer is not found. + * @throws ExceptionInterface If any other exception occurs during the refund process. */ public function refundGiftCart(CartInterface $cart, bool $force = false): bool { + // Attempt to refund all gifts in the cart by calling the refundCart method + // with the force flag set to the provided value and the gifts flag set to true. + // The refundCart method handles the actual refund logic. return $this->refundCart($cart, $force, true); } /** - * @throws RecordNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ModelNotFoundException - * @throws ExceptionInterface + * Forcefully refunds all gifts in the cart. + * + * This method forcefully attempts to refund all gifts in the provided cart. + * If the refund is successful, it returns true. If the refund fails due to + * insufficient funds or empty balance, it throws an exception. If the refund + * fails due to a reason other than the above, it throws a more specific + * exception. + * + * This method is a convenience method that calls the refundGiftCart method + * with the force flag set to true. This allows the caller to not have to + * worry about the force flag and just call a single method to perform the + * refund. + * + * @param CartInterface $cart The cart containing the gifts to be refunded. + * @return bool True if the gift refund was successful, false otherwise. + * + * @throws BalanceIsEmpty If the balance of the customer is empty and the force flag is false. + * @throws InsufficientFunds If the balance of the customer is insufficient to cover the refund. + * @throws RecordNotFoundException If the cart or its items are not found. + * @throws RecordsNotFoundException If the records for the refund are not found. + * @throws TransactionFailedException If the refund transaction fails. + * @throws ModelNotFoundException If the model used in the refund is not found. + * @throws ExceptionInterface If the refund fails for any other reason. */ public function forceRefundGiftCart(CartInterface $cart): bool { + // Call the refundGiftCart method with the force flag set to true. + // This allows the refund to be performed even if the balance is empty. return $this->refundGiftCart($cart, true); } /** - * Checks acquired product your wallet. + * Checks if the given product has been acquired by the customer's wallet. * - * @deprecated The method is slow and will be removed in the future + * This method is a convenience method that wraps a call to the PurchaseServiceInterface + * to check if the given product has been acquired by the customer's wallet. + * + * @param ProductInterface $product The product to check. + * @param bool $gifts Whether to include gifts in the search. + * @return Transfer|null The associated Transfer object, or null if none exists. + * + * @deprecated The method is slow and will be removed in the future. * @see PurchaseServiceInterface */ public function paid(ProductInterface $product, bool $gifts = false): ?Transfer { + // Retrieve the cart with the given product. + // The withItem method adds the given product to the cart. $cart = app(Cart::class)->withItem($product); + + // Use the PurchaseServiceInterface to find the associated Transfer object. + // The PurchaseServiceInterface is responsible for finding the transfers associated + // with a given basket. + // The already method is used to find the transfers that are already done. + // The basket is obtained from the cart. + // The $gifts parameter is used to specify whether to include gifts in the search. $purchases = app(PurchaseServiceInterface::class) ->already($this, $cart->getBasketDto(), $gifts); + // Return the first Transfer object in the array of purchases, or null if the array is empty. + // This is a convenience method and will be removed in the future. return current($purchases) ?: null; } } diff --git a/src/Traits/HasGift.php b/src/Traits/HasGift.php index 9063cc1f0..395a8b020 100644 --- a/src/Traits/HasGift.php +++ b/src/Traits/HasGift.php @@ -32,52 +32,104 @@ trait HasGift { /** - * Give the goods safely. + * Safely gives the goods to the specified wallet. + * + * This method attempts to give the goods to the specified wallet without throwing an exception. + * If an exception occurs during the process, `null` is returned. + * + * @param Wallet $to The wallet to which the goods will be given. + * @param ProductInterface $product The goods to be given. + * @param bool $force [optional] Whether to force the gift. Defaults to `false`. + * @return Transfer|null The transfer object representing the gift, or `null` if the gift fails. + * + * @throws ExceptionInterface If an exception occurs during the process of giving the goods. */ public function safeGift(Wallet $to, ProductInterface $product, bool $force = false): ?Transfer { try { + // Attempt to give the goods to the specified wallet return $this->gift($to, $product, $force); - } catch (ExceptionInterface) { + } catch (ExceptionInterface $exception) { + // If an exception occurs, return null return null; } } /** - * From this moment on, each user (wallet) can give the goods to another user (wallet). This functionality can be - * organized for gifts. + * Give the goods to another user (wallet). * - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method attempts to give a product to another user's wallet. If the gift is successful, the method + * returns the transfer object. If the gift fails due to a reason other than the above, it throws a more + * specific exception. + * + * @param Wallet $to The wallet to which the goods will be given. + * @param ProductInterface $product The goods to be given. + * @param bool $force [optional] Whether to force the gift. Defaults to `false`. + * @return Transfer The transfer object representing the gift. + * + * @throws BalanceIsEmpty If the balance of the wallet is empty. + * @throws InsufficientFunds If there are insufficient funds in the wallet. + * @throws RecordsNotFoundException If the record is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function gift(Wallet $to, ProductInterface $product, bool $force = false): Transfer { - return app(AtomicServiceInterface::class)->block($this, function () use ($to, $product, $force): Transfer { - $mathService = app(MathServiceInterface::class); - $discount = app(DiscountServiceInterface::class)->getDiscount($this, $product); + // Execute the gift operation atomically + $atomicService = app(AtomicServiceInterface::class); + $mathService = app(MathServiceInterface::class); + $discountService = app(DiscountServiceInterface::class); + $taxService = app(TaxServiceInterface::class); + $transactionService = app(TransactionServiceInterface::class); + $castService = app(CastServiceInterface::class); + $consistencyService = app(ConsistencyServiceInterface::class); + $atmService = app(AtmServiceInterface::class); + $transferDtoAssembler = app(TransferDtoAssemblerInterface::class); + + return $atomicService->block($this, function () use ( + $to, + $product, + $force, + $mathService, + $discountService, + $taxService, + $transactionService, + $castService, + $consistencyService, + $atmService, + $transferDtoAssembler + ): Transfer { + // Get the discount for the product + $discount = $discountService->getDiscount($this, $product); + + // Calculate the amount to be transferred after applying the discount $amount = $mathService->sub($product->getAmountProduct($this), $discount); - $fee = app(TaxServiceInterface::class)->getFee($product, $amount); + // Get the fee for the transaction + $fee = $taxService->getFee($product, $amount); + + // Check if the gift can be forced without checking the balance if (! $force) { - app(ConsistencyServiceInterface::class)->checkPotential($this, $mathService->add($amount, $fee)); + // Check the consistency of the potential transaction + $consistencyService->checkPotential($this, $mathService->add($amount, $fee)); } - $transactionService = app(TransactionServiceInterface::class); - $metaProduct = $product->getMetaProduct(); + // Create withdraw and deposit transactions $withdraw = $transactionService->makeOne( $this, Transaction::TYPE_WITHDRAW, $mathService->add($amount, $fee), - $metaProduct + $product->getMetaProduct() + ); + $deposit = $transactionService->makeOne( + $product, + Transaction::TYPE_DEPOSIT, + $amount, + $product->getMetaProduct() ); - $deposit = $transactionService->makeOne($product, Transaction::TYPE_DEPOSIT, $amount, $metaProduct); - - $castService = app(CastServiceInterface::class); - $transfer = app(TransferDtoAssemblerInterface::class)->create( + // Create a transfer object + $transfer = $transferDtoAssembler->create( $deposit->getKey(), $withdraw->getKey(), Transfer::STATUS_GIFT, @@ -89,21 +141,32 @@ public function gift(Wallet $to, ProductInterface $product, bool $force = false) null ); - $transfers = app(AtmServiceInterface::class)->makeTransfers([$transfer]); + // Create the transfer using the atm service + $transfers = $atmService->makeTransfers([$transfer]); + // Return the created transfer return current($transfers); }); } /** - * Santa without money gives a gift. + * Force a gift without checking balance. + * + * This method attempts to gift a product to another user's wallet without checking the balance. + * If the gift is successful, the method returns the transfer object. If the gift fails due + * to a reason other than the above, it throws a more specific exception. + * + * @param Wallet $to The wallet to which the gift will be given. + * @param ProductInterface $product The product to be given. + * @return Transfer The transfer object representing the gift. * - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @throws RecordsNotFoundException If the record is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forceGift(Wallet $to, ProductInterface $product): Transfer { + // Call the gift method with force true return $this->gift($to, $product, true); } } diff --git a/src/Traits/HasWallet.php b/src/Traits/HasWallet.php index 0dcff74cc..e03858654 100644 --- a/src/Traits/HasWallet.php +++ b/src/Traits/HasWallet.php @@ -44,71 +44,180 @@ trait HasWallet use MorphOneWallet; /** - * The input means in the system. + * Deposit funds into the wallet. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method executes the deposit transaction within an atomic block to ensure data consistency. + * + * @param int|string $amount The amount to deposit. + * @param array|null $meta Additional metadata for the transaction. This can be used to store + * information about the type of deposit, the source of the funds, or any other relevant details. + * @param bool $confirmed Whether the transaction is confirmed. This can be used to indicate whether the + * transaction has been verified and is considered final. Defaults to true. + * @return Transaction The transaction object representing the deposit. + * + * @throws AmountInvalid If the amount is invalid (e.g. negative, not a number, too large). + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails for any reason (e.g. network issues, insufficient funds). + * @throws ExceptionInterface If an exception occurs during the transaction process. */ public function deposit(int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction { + // Execute the deposit transaction within an atomic block to ensure data consistency. return app(AtomicServiceInterface::class)->block( $this, + // Create a new deposit transaction. fn () => app(TransactionServiceInterface::class) ->makeOne($this, Transaction::TYPE_DEPOSIT, $amount, $meta, $confirmed) ); } /** - * Magic laravel framework method, makes it possible to call property balance. + * Magic Laravel framework method that makes it possible to call property balance. + * + * This method is called by Laravel's magic getter when the `balance` property is accessed. + * It returns the current balance of the wallet as a string. + * + * @return non-empty-string The current balance of the wallet as a string. + * + * @throws \Bavix\Wallet\Internal\Exceptions\ModelNotFoundException If the wallet does not exist and `$save` is set to `false`. + * + * @see Wallet + * @see WalletModel */ public function getBalanceAttribute(): string { - /** @var Wallet $this */ - return app(RegulatorServiceInterface::class)->amount(app(CastServiceInterface::class)->getWallet($this, false)); + // Get the wallet object from the model. + // This method uses the CastServiceInterface to retrieve the wallet object from the model. + // The second argument, `$save = false`, prevents the service from saving the wallet if it does not exist. + // This is useful to avoid unnecessary database queries when retrieving the balance. + $wallet = app(CastServiceInterface::class)->getWallet($this, false); + + // Get the current balance of the wallet using the Regulator service. + // This method uses the RegulatorServiceInterface to retrieve the current balance of the wallet. + // The Regulator service is responsible for calculating the balance of the wallet based on the transactions. + // The balance is always returned as a string to preserve the accuracy of the decimal value. + $balance = app(RegulatorServiceInterface::class)->amount($wallet); + + // Return the balance as a string. + return $balance; } + /** + * Returns the balance of the wallet as an integer. + * + * This method is called by Laravel's magic getter when the `balanceInt` property is accessed. + * It retrieves the current balance of the wallet as a string using the `getBalanceAttribute` method. + * The decimal value is preserved by converting the string to an integer. + * + * @return int The current balance of the wallet as an integer. + * + * @throws \Bavix\Wallet\Internal\Exceptions\ModelNotFoundException If the wallet does not exist and `$save` is set to `false`. + * + * @see Wallet + * @see WalletModel + * @see HasWallet::getBalanceAttribute + */ public function getBalanceIntAttribute(): int { - return (int) $this->getBalanceAttribute(); + // Get the current balance of the wallet as a string. + // This is done using the `getBalanceAttribute` method. + $balanceString = $this->getBalanceAttribute(); + + // Convert the balance string to an integer. + // This ensures that the decimal value is preserved while converting it to an integer. + $balanceInt = (int) $balanceString; + + // Return the balance as an integer. + return $balanceInt; } /** - * We receive transactions of the selected wallet. + * Returns all transactions related to the wallet. + * + * This method retrieves all transactions associated with the wallet. + * It uses the `getWallet` method of the `CastServiceInterface` to retrieve the wallet instance. + * The `false` parameter indicates that the wallet should not be saved if it does not exist. + * The method then uses the `hasMany` method on the wallet instance to retrieve all transactions related to the wallet. + * The transaction model class is retrieved from the configuration using `config('wallet.transaction.model', Transaction::class)`. + * The relationship is defined using the `wallet_id` foreign key. * - * @return HasMany + * @return HasMany Returns a `HasMany` relationship of transactions related to the wallet. */ public function walletTransactions(): HasMany { - return app(CastServiceInterface::class) - ->getWallet($this, false) - ->hasMany(config('wallet.transaction.model', Transaction::class), 'wallet_id'); + // Retrieve the wallet instance using the `getWallet` method of the `CastServiceInterface`. + // The `false` parameter indicates that the wallet should not be saved if it does not exist. + $wallet = app(CastServiceInterface::class)->getWallet($this, false); + + // Retrieve all transactions related to the wallet using the `hasMany` method on the wallet instance. + // The transaction model class is retrieved from the configuration using `config('wallet.transaction.model', Transaction::class)`. + // The relationship is defined using the `wallet_id` foreign key. + $transactions = $wallet->hasMany(config('wallet.transaction.model', Transaction::class), 'wallet_id'); + + return $transactions; } /** - * all user actions on wallets will be in this method. + * Retrieves all user actions related to the wallet. + * + * This method returns a `MorphMany` relationship object that represents all transactions and transfers + * associated with the wallet. It fetches the wallet instance using the `getWallet` method of the + * `CastServiceInterface` and defines the relationship using the `morphMany` method on the wallet instance. + * The transaction model class is retrieved from the configuration using `config('wallet.transaction.model', Transaction::class)`. + * The relationship is defined using the `payable` foreign key. * - * @return MorphMany + * @return MorphMany The `MorphMany` relationship object representing all user actions on the wallet. */ public function transactions(): MorphMany { - return app(CastServiceInterface::class) - ->getHolder($this) - ->morphMany(config('wallet.transaction.model', Transaction::class), 'payable'); + // Fetch the wallet instance using the `getWallet` method of the `CastServiceInterface`. + // The `getWallet` method is responsible for retrieving the wallet instance associated with the current model. + $wallet = app(CastServiceInterface::class)->getHolder($this); + + // Define the relationship between the wallet and the transactions using the `morphMany` method. + // The `morphMany` method is used to define a polymorphic one-to-many relationship. + // In this case, it represents the relationship between the wallet and the transactions. + // The transaction model class is retrieved from the configuration using `config('wallet.transaction.model', Transaction::class)`. + // The relationship is defined using the `payable` foreign key. + // The `payable` foreign key is used to associate the transactions with the wallet. + return $wallet->morphMany( + config('wallet.transaction.model', Transaction::class), + 'payable' // The name of the polymorphic relation column. + ); } /** - * This method ignores errors that occur when transferring funds. + * Safely transfers funds from this wallet to another. + * + * This method attempts to transfer funds from this wallet to another wallet. + * If an error occurs during the process, null is returned. + * + * @param Wallet $wallet The wallet to transfer funds to. + * @param int|string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return null|Transfer The created transaction, or null if an error occurred. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function safeTransfer( Wallet $wallet, int|string $amount, ExtraDtoInterface|array|null $meta = null ): ?Transfer { + // Attempt to transfer the funds from this wallet to the specified wallet. try { + // Use the `transfer` method to transfer the funds. + // The `transfer` method is responsible for performing the actual transfer of funds. + // If an error occurs during the process, an exception is thrown. return $this->transfer($wallet, $amount, $meta); - } catch (ExceptionInterface) { + } catch (ExceptionInterface $e) { return null; } } @@ -116,97 +225,197 @@ public function safeTransfer( /** * A method that transfers funds from host to host. * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method attempts to transfer funds from the host wallet to another wallet. + * It uses the `AtomicServiceInterface` to ensure atomicity and consistency of the transfer. + * The `ConsistencyServiceInterface` is used to check if the transfer is possible before attempting it. + * + * @param Wallet $wallet The wallet to transfer funds to. + * @param int|string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return Transfer The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + * + * @see AtomicServiceInterface + * @see ConsistencyServiceInterface + * @see TransactionFailedException + * @see AmountInvalid + * @see BalanceIsEmpty + * @see InsufficientFunds + * @see RecordsNotFoundException */ public function transfer(Wallet $wallet, int|string $amount, ExtraDtoInterface|array|null $meta = null): Transfer { + // Wrap the transfer in an atomic block to ensure consistency and prevent race conditions. return app(AtomicServiceInterface::class)->block($this, function () use ($wallet, $amount, $meta): Transfer { /** @var Wallet $this */ + // Check if the transfer is possible before attempting it. app(ConsistencyServiceInterface::class)->checkPotential($this, $amount); + // Perform the transfer. return $this->forceTransfer($wallet, $amount, $meta); }); } /** - * Withdrawals from the system. - * - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Withdraw funds from the system. + * + * This method wraps the withdrawal in an atomic block to ensure atomicity and consistency of the withdrawal. + * It checks if the withdrawal is possible before attempting it. + * + * @param int|string $amount The amount to withdraw. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the withdrawal is confirmed. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. + * + * @see AtomicServiceInterface + * @see ConsistencyServiceInterface + * @see TransactionFailedException + * @see AmountInvalid + * @see BalanceIsEmpty + * @see InsufficientFunds + * @see RecordsNotFoundException */ public function withdraw(int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction { + // Wrap the withdrawal in an atomic block to ensure consistency and prevent race conditions. return app(AtomicServiceInterface::class)->block($this, function () use ( $amount, $meta, $confirmed ): Transaction { /** @var Wallet $this */ + // Check if the withdrawal is possible before attempting it. app(ConsistencyServiceInterface::class)->checkPotential($this, $amount); + // Perform the withdrawal. return $this->forceWithdraw($amount, $meta, $confirmed); }); } /** - * Checks if you can withdraw funds. + * Checks if the user can withdraw funds based on the provided amount. + * + * This method retrieves the math service instance and calculates the total balance of the wallet. + * It then checks if the withdrawal is possible using the consistency service. + * + * @param int|string $amount The amount to be withdrawn. + * @param bool $allowZero Flag to allow zero balance for withdrawal. Defaults to false. + * @return bool Returns true if the withdrawal is possible; otherwise, false. */ public function canWithdraw(int|string $amount, bool $allowZero = false): bool { + // Get the math service instance. $mathService = app(MathServiceInterface::class); + + // Get the wallet and calculate the total balance. $wallet = app(CastServiceInterface::class)->getWallet($this); $balance = $mathService->add($this->getBalanceAttribute(), $wallet->getCreditAttribute()); - return app(ConsistencyServiceInterface::class)->canWithdraw($balance, $amount, $allowZero); + // Check if the withdrawal is possible. + return app(ConsistencyServiceInterface::class) + ->canWithdraw($balance, $amount, $allowZero); } /** - * Forced to withdraw funds from system. + * Forced to withdraw funds from the system. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * This method creates a new withdrawal transaction and returns it. It wraps the transaction creation + * in an atomic block to ensure atomicity and consistency. + * + * @param int|string $amount The amount to withdraw. + * @param array|null $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed. Defaults to true. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ - public function forceWithdraw( - int|string $amount, - array|null $meta = null, - bool $confirmed = true - ): Transaction { + public function forceWithdraw(int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction + { + // Wrap the transaction creation in an atomic block to ensure atomicity and consistency. + // The atomic block ensures that the creation of the transaction is atomic, + // meaning that either the entire transaction is created or none of it is. return app(AtomicServiceInterface::class)->block( + // The wallet instance $this, - fn () => app(TransactionServiceInterface::class) - ->makeOne($this, Transaction::TYPE_WITHDRAW, $amount, $meta, $confirmed) + function () use ($amount, $meta, $confirmed): Transaction { + // Create a new withdrawal transaction. + return app(TransactionServiceInterface::class)->makeOne( + // The wallet instance + $this, + // The transaction type + Transaction::TYPE_WITHDRAW, + // The amount to withdraw + $amount, + // Additional information for the transaction + $meta, + // Whether the transaction is confirmed + $confirmed + ); + } ); } /** - * the forced transfer is needed when the user does not have the money, and we drive it. Sometimes you do. Depends - * on business logic. + * Forces a transfer of funds from this wallet to another, bypassing certain safety checks. + * + * This method is intended for use in scenarios where a transfer must be completed regardless of + * the usual validation checks (e.g., sufficient funds, wallet status). It is critical to use this + * method with caution as it can result in negative balances or other unintended consequences. * - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * @param Wallet $wallet The wallet to transfer funds to. + * @param int|string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return Transfer The created transfer. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forceTransfer( Wallet $wallet, int|string $amount, ExtraDtoInterface|array|null $meta = null ): Transfer { + // Wrap the transfer creation in an atomic block to ensure atomicity and consistency. + // The atomic block ensures that the creation of the transfer is atomic, + // meaning that either the entire transfer is created or none of it is. return app(AtomicServiceInterface::class)->block($this, function () use ($wallet, $amount, $meta): Transfer { + // Create a new transfer transaction. + // The transfer transaction is created using the PrepareServiceInterface. + // The transfer status is set to Transfer::STATUS_TRANSFER. + // The additional information for the transaction is passed as an argument. + // The created transfer transaction is stored in the $transferLazyDto variable. $transferLazyDto = app(PrepareServiceInterface::class) ->transferLazy($this, $wallet, Transfer::STATUS_TRANSFER, $amount, $meta); + // Apply the transfer transaction. + // The transfer transaction is applied using the TransferServiceInterface. + // The created transfer is returned. + // The $transferLazyDto is passed as an array to the apply method + // to create the transfer transaction. + // The applied transfer transaction is stored in the $transfers variable. + // The current transfer transaction is returned. $transfers = app(TransferServiceInterface::class)->apply([$transferLazyDto]); return current($transfers); @@ -214,27 +423,72 @@ public function forceTransfer( } /** - * the transfer table is used to confirm the payment this method receives all transfers. + * Retrieves all transfers related to the wallet. + * + * This method retrieves all transfers associated with the wallet. + * It uses the `getWallet` method of the `CastServiceInterface` to retrieve the wallet instance. + * The `false` parameter indicates that the wallet should not be saved if it does not exist. + * The method then uses the `hasMany` method on the wallet instance to retrieve all transfers related to the wallet. + * The transfer model class is retrieved from the configuration using `config('wallet.transfer.model', Transfer::class)`. + * The relationship is defined using the `from_id` foreign key. * - * @return HasMany + * @return HasMany The `HasMany` relationship object representing all transfers related to the wallet. */ public function transfers(): HasMany { - /** @var Wallet $this */ - return app(CastServiceInterface::class) - ->getWallet($this, false) - ->hasMany(config('wallet.transfer.model', Transfer::class), 'from_id'); + // Retrieve the wallet instance associated with the current model. + // The `getWallet` method of the `CastServiceInterface` is used to retrieve the wallet instance. + // The `false` parameter indicates that the wallet should not be saved if it does not exist. + $wallet = app(CastServiceInterface::class) + ->getWallet($this, false); + + // Retrieve all transfers associated with the wallet. + // The `hasMany` method is used on the wallet instance to retrieve all transfers related to the wallet. + // The transfer model class is retrieved from the configuration using `config('wallet.transfer.model', Transfer::class)`. + // The relationship is defined using the `from_id` foreign key. + return $wallet + ->hasMany( + // Retrieve the transfer model class from the configuration. + // The default value is `Transfer::class`. + config('wallet.transfer.model', Transfer::class), + // Define the foreign key for the relationship. + // The foreign key is `from_id`. + 'from_id' + ); } /** - * returns all the receiving transfers to this wallet. + * Retrieves all the receiving transfers to this wallet. * - * @return HasMany + * This method retrieves all receiving transfers associated with the wallet. + * It uses the `getWallet` method of the `CastServiceInterface` to retrieve the wallet instance. + * The `false` parameter indicates that the wallet should not be saved if it does not exist. + * The method then uses the `hasMany` method on the wallet instance to retrieve all receiving transfers related to the wallet. + * The transfer model class is retrieved from the configuration using `config('wallet.transfer.model', Transfer::class)`. + * The relationship is defined using the `to_id` foreign key. + * + * @return HasMany The `HasMany` relationship object representing all receiving transfers related to the wallet. */ public function receivedTransfers(): HasMany { - return app(CastServiceInterface::class) - ->getWallet($this, false) - ->hasMany(config('wallet.transfer.model', Transfer::class), 'to_id'); + // Retrieve the wallet instance associated with the current model. + // The `getWallet` method of the `CastServiceInterface` is used to retrieve the wallet instance. + // The `false` parameter indicates that the wallet should not be saved if it does not exist. + $wallet = app(CastServiceInterface::class) + ->getWallet($this, false); + + // Retrieve all receiving transfers associated with the wallet. + // The `hasMany` method is used on the wallet instance to retrieve all receiving transfers related to the wallet. + // The transfer model class is retrieved from the configuration using `config('wallet.transfer.model', Transfer::class)`. + // The relationship is defined using the `to_id` foreign key. + return $wallet + ->hasMany( + // Retrieve the transfer model class from the configuration. + // The default value is `Transfer::class`. + config('wallet.transfer.model', Transfer::class), + // Define the foreign key for the relationship. + // The foreign key is `to_id`. + 'to_id' + ); } } diff --git a/src/Traits/HasWalletFloat.php b/src/Traits/HasWalletFloat.php index 91ccbeec9..adb8decf8 100644 --- a/src/Traits/HasWalletFloat.php +++ b/src/Traits/HasWalletFloat.php @@ -33,133 +33,325 @@ trait HasWalletFloat use HasWallet; /** - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Withdraw funds from the wallet without checking the balance. + * + * @param float|int|string $amount The amount to withdraw. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function forceWithdrawFloat( float|int|string $amount, ?array $meta = null, bool $confirmed = true ): Transaction { + // Get the math service. $math = app(MathServiceInterface::class); - $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Get the decimal places value from the wallet. + $decimalPlacesValue = app(CastServiceInterface::class) + ->getWallet($this) + ->decimal_places; + + // Get the decimal places. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Perform the withdrawal. return $this->forceWithdraw($result, $meta, $confirmed); } /** - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Deposit funds into the wallet. + * + * This method takes a float or int amount and deposits it into the wallet. + * It uses the math service to convert the amount to the wallet's decimal format, + * and then performs the deposit. + * + * @param float|int|string $amount The amount to deposit. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function depositFloat(float|int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction { + // Get the math service. $math = app(MathServiceInterface::class); - $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Get the decimal places value from the wallet. + $decimalPlacesValue = app(CastServiceInterface::class) + ->getWallet($this) + ->decimal_places; + + // Get the decimal places. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. + // Rounding is needed to avoid issues with floats. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Perform the deposit. return $this->deposit($result, $meta, $confirmed); } /** - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Withdraw the specified float amount of money from the wallet. + * + * This method takes a float or int amount and withdraws it from the wallet. + * It uses the math service to convert the amount to the wallet's decimal format, + * and then performs the withdrawal. + * + * @param float|int|string $amount The amount to withdraw. + * @param null|array $meta Additional information for the transaction. + * @param bool $confirmed Whether the transaction is confirmed or not. + * @return Transaction The created transaction. + * + * @throws AmountInvalid If the amount is invalid. + * @throws BalanceIsEmpty If the balance is empty. + * @throws InsufficientFunds If the amount exceeds the balance. + * @throws RecordsNotFoundException If the wallet is not found. + * @throws TransactionFailedException If the transaction fails. + * @throws ExceptionInterface If an exception occurs. */ public function withdrawFloat(float|int|string $amount, ?array $meta = null, bool $confirmed = true): Transaction { + // Get the math service. $math = app(MathServiceInterface::class); - $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Get the decimal places value from the wallet. + $decimalPlacesValue = app(CastServiceInterface::class) + ->getWallet($this) + ->decimal_places; + + // Get the decimal places. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. + // Rounding is needed to avoid issues with floats. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Perform the withdrawal. return $this->withdraw($result, $meta, $confirmed); } + /** + * Checks if the user can safely withdraw the specified amount of funds. + * + * @param float|int|string $amount The amount to withdraw. Can be specified as a float, int, or string. + * @return bool Returns TRUE if the withdrawal is possible, FALSE otherwise. + * + * @throws AmountInvalid If the amount is invalid (e.g., negative values). + */ public function canWithdrawFloat(float|int|string $amount): bool { + // Get the math service. $math = app(MathServiceInterface::class); - $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Get the decimal places value from the wallet. + $decimalPlacesValue = app(CastServiceInterface::class) + ->getWallet($this) + ->decimal_places; + + // Get the decimal places. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. + // Rounding is needed to avoid issues with floats. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Check if the user can withdraw the specified amount. return $this->canWithdraw($result); } /** - * @throws AmountInvalid - * @throws BalanceIsEmpty - * @throws InsufficientFunds - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Transfers a specific amount of funds from this wallet to another. + * + * This method transfers the specified amount of funds from this wallet to another wallet. The amount can be + * specified as a float, int, or string. The transferred amount is rounded to the decimal places specified in the + * wallet's configuration. + * + * @param Wallet $wallet The wallet instance to which funds will be transferred. + * @param float|int|string $amount The amount of funds to transfer. Can be specified as a float, int, or string. + * @param ExtraDtoInterface|array|null $meta Additional metadata associated with the transfer. This can be + * used to store extra information about the transaction, such as + * reasons for the transfer or identifiers linking to other systems. + * @return Transfer Returns a Transfer object representing the completed transaction. + * + * @throws AmountInvalid If the amount specified is invalid (e.g., negative values). + * @throws BalanceIsEmpty If the balance of this wallet is empty. + * @throws InsufficientFunds If the amount to be transferred exceeds the available balance in this wallet. + * @throws RecordsNotFoundException If the target wallet cannot be found. + * @throws TransactionFailedException If the transfer could not be completed due to a failure + * in the underlying transaction system. + * @throws ExceptionInterface A generic exception interface catch-all for any other exceptions that + * might occur during the execution of the transfer. */ public function transferFloat( Wallet $wallet, float|int|string $amount, ExtraDtoInterface|array|null $meta = null ): Transfer { + // Get the math service. $math = app(MathServiceInterface::class); + + // Get the decimal places value from the wallet. $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Get the decimal places. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. + // Rounding is needed to avoid issues with floats. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Perform the transfer. return $this->transfer($wallet, $result, $meta); } + /** + * Safely transfers funds from this wallet to another. + * + * This method will not throw an exception if the transfer fails. Instead, it will return null. + * + * @param Wallet $wallet The wallet to transfer funds to. + * @param float|int|string $amount The amount to transfer. + * @param ExtraDtoInterface|array|null $meta Additional information for the transaction. + * This can be an instance of an ExtraDtoInterface + * or an array of arbitrary data. + * @return Transfer|null The created transaction, or null if the transfer fails. + * + * @throws AmountInvalid If the amount is invalid. + */ public function safeTransferFloat( Wallet $wallet, float|int|string $amount, ExtraDtoInterface|array|null $meta = null ): ?Transfer { + // Get the math service. $math = app(MathServiceInterface::class); + + // Get the decimal places value from the wallet. + // This value is used to convert the amount to the correct decimal format. $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Calculate the decimal places value as a power of ten. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. + // Rounding is needed to avoid issues with floats. + // The result is the amount in the correct decimal format. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Perform the transfer. + // This method will not throw an exception if the transfer fails. Instead, it will return null. return $this->safeTransfer($wallet, $result, $meta); } /** - * @throws AmountInvalid - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface + * Forces a transfer of funds from this wallet to another, bypassing certain safety checks. + * + * This method is intended for use in scenarios where a transfer must be completed regardless of + * the usual validation checks (e.g., sufficient funds, wallet status). It is critical to use this + * method with caution as it can result in negative balances or other unintended consequences. + * + * @param Wallet $wallet The wallet instance to which funds will be transferred. + * @param float|int|string $amount The amount of funds to transfer. Can be specified as a float, int, or string. + * @param ExtraDtoInterface|array|null $meta Additional metadata associated with the transfer. This + * can be used to store extra information about the transaction, such as reasons for the transfer or + * identifiers linking to other systems. + * @return Transfer Returns a Transfer object representing the completed transaction. + * + * @throws AmountInvalid If the amount specified is invalid (e.g., negative values). + * @throws RecordsNotFoundException If the target wallet cannot be found. + * @throws TransactionFailedException If the transfer could not be completed due to a failure + * in the underlying transaction system. + * @throws ExceptionInterface A generic exception interface catch-all for any other exceptions that + * might occur during the execution of the transfer. */ public function forceTransferFloat( Wallet $wallet, float|int|string $amount, ExtraDtoInterface|array|null $meta = null ): Transfer { + // Get the math service. $math = app(MathServiceInterface::class); + + // Get the decimal places value from the wallet. + // This value is used to convert the amount to the correct decimal format. $decimalPlacesValue = app(CastServiceInterface::class)->getWallet($this)->decimal_places; + + // Calculate the decimal places value as a power of ten. $decimalPlaces = $math->powTen($decimalPlacesValue); + + // Convert the amount to the decimal format. + // Rounding is needed to avoid issues with floats. + // The result is the amount in the correct decimal format. $result = $math->round($math->mul($amount, $decimalPlaces, $decimalPlacesValue)); + // Perform the transfer. + // This method will not throw an exception if the transfer fails. Instead, it will return null. return $this->forceTransfer($wallet, $result, $meta); } + /** + * Get the balance of the wallet as a float. + * + * This method returns the balance of the wallet as a string, and then formats it as a float with the + * correct number of decimal places. The number of decimal places is obtained from the wallet's + * `decimal_places` attribute. + * + * @return non-empty-string The balance of the wallet as a string, formatted as a float. + */ public function getBalanceFloatAttribute(): string { + // Get the wallet. $wallet = app(CastServiceInterface::class)->getWallet($this); + // Get the wallet balance. + /** @var non-empty-string $balance */ + $balance = $wallet->getBalanceAttribute(); + + // Get the decimal places value from the wallet. + $decimalPlacesValue = $wallet->decimal_places; + + // Use the formatter service to format the balance as a float. return app(FormatterServiceInterface::class)->floatValue( - $wallet->getBalanceAttribute(), - $wallet->decimal_places, + // The balance of the wallet. + $balance, + + // The number of decimal places for the wallet. + $decimalPlacesValue, ); } + /** + * Get the balance of the wallet as a float number. + * + * This method is used to get the balance of the wallet as a float number. The number of decimal + * places is obtained from the wallet's `decimal_places` attribute. This method is useful when you + * need to perform calculations or formatting on the balance. + * + * @return float The balance of the wallet as a float number. + */ public function getBalanceFloatNumAttribute(): float { + // Get the balance of the wallet as a float number. + // The number of decimal places is obtained from the wallet's `decimal_places` attribute. + // This method is useful when you need to perform calculations or formatting on the balance. + + // Return the balance of the wallet as a float. return (float) $this->getBalanceFloatAttribute(); } } diff --git a/src/Traits/HasWallets.php b/src/Traits/HasWallets.php index 2628c53ca..0c094e8e4 100644 --- a/src/Traits/HasWallets.php +++ b/src/Traits/HasWallets.php @@ -22,27 +22,29 @@ trait HasWallets { /** - * The variable is used for the cache, so as not to request wallets many times. WalletProxy keeps the money wallets - * in the memory to avoid errors when you purchase/transfer, etc. + * Cache for the wallets to avoid requesting them multiple times. WalletProxy stores the money wallets + * in memory to avoid errors when you purchase/transfer, etc. * - * @var WalletModel[] + * @var array */ private array $_wallets = []; /** * Get wallet by slug. * - * $user->wallet->balance // 200 or short recording $user->balance; // 200 + * @param string $slug The slug of the wallet. + * @return WalletModel|null The wallet with the given slug, or null if not found. * - * $defaultSlug = config('wallet.wallet.default.slug'); $user->getWallet($defaultSlug)->balance; // 200 - * - * $user->getWallet('usd')->balance; // 50 $user->getWallet('rub')->balance; // 100 + * This method is a wrapper around the getWalletOrFail method. It catches the ModelNotFoundException + * and returns null instead of throwing it. */ public function getWallet(string $slug): ?WalletModel { + // Try to get the wallet with the given slug. try { return $this->getWalletOrFail($slug); - } catch (ModelNotFoundException) { + } catch (ModelNotFoundException $exception) { + // If the wallet is not found, return null. return null; } } @@ -50,68 +52,124 @@ public function getWallet(string $slug): ?WalletModel /** * Get wallet by slug. * - * $user->wallet->balance // 200 or short recording $user->balance; // 200 - * - * $defaultSlug = config('wallet.wallet.default.slug'); $user->getWallet($defaultSlug)->balance; // 200 + * This method loads wallets from the database if they are not loaded yet. + * Then it retrieves the wallet with the given slug from the cache. + * If the wallet is not found in the cache, it retrieves it from the database, + * stores it in the cache, and returns it. * - * $user->getWallet('usd')->balance; // 50 $user->getWallet('rub')->balance; // 100 + * @param string $slug The slug of the wallet. + * @return WalletModel The wallet with the given slug. * - * @throws ModelNotFoundException + * @throws ModelNotFoundException If the wallet with the given slug is not found. */ public function getWalletOrFail(string $slug): WalletModel { + // Check if wallets are loaded. + // Load wallets if they are not loaded yet. if ($this->_wallets === [] && $this->relationLoaded('wallets')) { /** @var Collection $wallets */ $wallets = $this->getRelation('wallets'); + // Load the wallets into the cache. foreach ($wallets as $wallet) { $wallet->setRelation('holder', $this->withoutRelations()); $this->_wallets[$wallet->slug] = $wallet; } } + // Check if the wallet is not found in the cache. if (! array_key_exists($slug, $this->_wallets)) { + // Retrieve the wallet from the database if it is not found in the cache. $wallet = app(WalletServiceInterface::class)->getBySlug($this, $slug); $wallet->setRelation('holder', $this->withoutRelations()); + // Store the wallet in the cache. $this->_wallets[$slug] = $wallet; } + // Return the wallet from the cache. return $this->_wallets[$slug]; } /** - * method of obtaining all wallets. + * Method for obtaining all wallets. + * + * This method returns a MorphMany relationship object. The relationship is + * defined between the current model (the "holder") and the wallet model. + * The wallet model is specified in the configuration file under the + * 'wallet.model' key. If the key is not found, the default wallet model is + * used. * - * @return MorphMany + * @return MorphMany The MorphMany relationship object. */ public function wallets(): MorphMany { - return $this->morphMany(config('wallet.wallet.model', WalletModel::class), 'holder'); + // Define a MorphMany relationship between the current model (the "holder") + // and the wallet model. + return $this + ->morphMany( + // Get the wallet model from the configuration. + config('wallet.wallet.model', WalletModel::class), + // Specify the name of the polymorphic relation. + 'holder' + ); } /** + * Creates a new wallet for the current model. + * + * This method creates a new wallet with the given data and associates it + * with the current model. The current model is referred to as the "holder" + * of the wallet. + * + * The method can be used to create a new wallet with the following data: + * + * - name: The name of the wallet. + * - slug: The slug of the wallet. If not specified, the slug is generated + * automatically. + * - description: The description of the wallet. + * - meta: The meta data for the wallet. The meta data is an array of + * key-value pairs. + * - decimal_places: The number of decimal places for the wallet. If not + * specified, the default value is 2. + * * @param array{ * name: string, * slug?: string, * description?: string, * meta?: array|null, * decimal_places?: positive-int, - * } $data + * } $data The data for the new wallet. + * @return WalletModel The new wallet object. */ public function createWallet(array $data): WalletModel { + // Create the wallet with the given data. $wallet = app(WalletServiceInterface::class)->create($this, $data); + + // Cache the wallet. $this->_wallets[$wallet->slug] = $wallet; + + // Set the relation between the wallet and the current model. $wallet->setRelation('holder', $this->withoutRelations()); return $wallet; } /** - * The method checks the existence of the wallet. + * Checks the existence of a wallet. + * + * This method checks if a wallet with the given slug exists for the current model. + * + * @param string $slug The slug of the wallet. + * @return bool Returns true if the wallet exists, false otherwise. */ public function hasWallet(string $slug): bool { + // Check if the wallet exists by calling the getWallet() method. + // The getWallet() method returns the wallet object if it exists, + // or null if it does not exist. + // Casting the result to a boolean converts null to false and the wallet + // object to true. return (bool) $this->getWallet($slug); } } diff --git a/src/Traits/MorphOneWallet.php b/src/Traits/MorphOneWallet.php index b3b991aea..6b239cf4e 100644 --- a/src/Traits/MorphOneWallet.php +++ b/src/Traits/MorphOneWallet.php @@ -18,49 +18,78 @@ trait MorphOneWallet { /** - * Get default Wallet this method is used for Eager Loading. + * Get default Wallet. This method is used for Eager Loading. * * @return MorphOne */ public function wallet(): MorphOne { + // Get the CastService instance from the application container. $castService = app(CastServiceInterface::class); - /** @var class-string $related */ + /** + * Get the related wallet model class name from the configuration. + * If not found, use the default wallet model class name. + * + * @var class-string $related + */ $related = config('wallet.wallet.model', WalletModel::class); + // Eager load the wallet for the related model. return $castService - ->getHolder($this) - ->morphOne($related, 'holder') - ->withTrashed() - ->where('slug', config('wallet.wallet.default.slug', 'default')) - ->withDefault(static function (WalletModel $wallet, object $holder) use ($castService) { + ->getHolder($this) // Get the related holder model. + ->morphOne($related, 'holder') // Define the Eloquent relationship. + ->withTrashed() // Include soft deleted wallets. + ->where('slug', config('wallet.wallet.default.slug', 'default')) // Filter by the default wallet slug. + ->withDefault(static function (WalletModel $wallet, object $holder) use ( + $castService + ) { // Define the default wallet values. + // Get the related model. $model = $castService->getModel($holder); + // Get the dynamic default slug from the related model, if available. + // Otherwise, use the default slug from the configuration. + /** @var string $slug */ $slug = method_exists($model, 'getDynamicDefaultSlug') ? $model->getDynamicDefaultSlug() : config('wallet.wallet.default.slug', 'default'); + // Fill the default wallet attributes. $wallet->forceFill(array_merge(config('wallet.wallet.creating', []), [ - 'name' => config('wallet.wallet.default.name', 'Default Wallet'), - 'slug' => $slug, - 'meta' => config('wallet.wallet.default.meta', []), - 'balance' => 0, + 'name' => config('wallet.wallet.default.name', 'Default Wallet'), // Default wallet name. + 'slug' => $slug, // Default wallet slug. + 'meta' => config('wallet.wallet.default.meta', []), // Default wallet metadata. + 'balance' => 0, // Default wallet balance. ])); if ($model->exists) { + // Set the related model on the wallet. $wallet->setRelation('holder', $model->withoutRelations()); } }); } + /** + * Get the wallet attribute. + * + * @return WalletModel|null The wallet model associated with the related model. + */ public function getWalletAttribute(): ?WalletModel { - /** @var WalletModel|null $wallet */ + /** + * Retrieve the wallet model associated with the related model. + * + * @var WalletModel|null $wallet + */ $wallet = $this->getRelationValue('wallet'); + // If the wallet model exists and the 'holder' relationship is not loaded, + // associate the related model with the wallet. if ($wallet && ! $wallet->relationLoaded('holder')) { + // Get the related holder model. $holder = app(CastServiceInterface::class)->getHolder($this); + + // Associate the related model with the wallet. $wallet->setRelation('holder', $holder->withoutRelations()); } diff --git a/src/WalletConfigure.php b/src/WalletConfigure.php index 4ab44a270..6d913e124 100644 --- a/src/WalletConfigure.php +++ b/src/WalletConfigure.php @@ -9,7 +9,10 @@ final class WalletConfigure private static bool $runsMigrations = true; /** - * Needed for class testing. + * Reset the static property runsMigrations to true. + * + * This method is used to reset the static property runsMigrations to its default value of true. + * It is typically used after using the `ignoreMigrations` method to ignore the package migrations. */ public static function reset(): void { @@ -18,17 +21,29 @@ public static function reset(): void /** * Configure Wallet to not register its migrations. + * + * This method is used to prevent the package migrations from being registered. + * It is typically used in cases where you want to manage your own migrations. */ public static function ignoreMigrations(): void { + // Set the static property runsMigrations to false + // This prevents the package migrations from being registered self::$runsMigrations = false; } /** * Indicates if Wallet migrations will be run. + * + * @return bool + * True if the migrations will be run, false if they will be ignored. */ public static function isRunsMigrations(): bool { + // Returns the value of the $runsMigrations property. + // This property is used to determine whether or not the package migrations + // will be registered. If it is true, the migrations will be run. If it is + // false, the migrations will be ignored. return self::$runsMigrations; } } diff --git a/tests/Infra/Models/UserDynamic.php b/tests/Infra/Models/UserDynamic.php index ca1849999..5d4b076ba 100644 --- a/tests/Infra/Models/UserDynamic.php +++ b/tests/Infra/Models/UserDynamic.php @@ -28,6 +28,9 @@ public function getTable(): string return 'users'; } + /** + * @return non-empty-string + */ public function getDynamicDefaultSlug(): string { return 'default-'.$this->email;