├── .codeclimate.yml
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── error-request.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── autofix.yaml
│ ├── changelog.yml
│ ├── deploy.yml
│ ├── deptrac.yaml
│ ├── lock.yml
│ ├── phpstan.yaml
│ ├── phpunits.yaml
│ ├── semgrep.yml
│ └── stale.yml
├── .gitignore
├── .phpstorm.meta.php
├── .whitesource
├── LICENSE
├── README.md
├── changelog.md
├── composer.json
├── config
└── config.php
├── database
├── 2018_11_06_222923_create_transactions_table.php
├── 2018_11_07_192923_create_transfers_table.php
├── 2018_11_15_124230_create_wallets_table.php
├── 2021_11_02_202021_update_wallets_uuid_table.php
├── 2023_12_30_113122_extra_columns_removed.php
├── 2023_12_30_204610_soft_delete.php
└── 2024_01_24_185401_add_extra_column_in_transfer.php
├── deptrac.yaml
├── docs
├── .vitepress
│ ├── .gitignore
│ ├── config.mts
│ └── theme
│ │ ├── custom.css
│ │ └── index.js
├── _include
│ ├── composer.md
│ ├── eager_loading.md
│ └── models
│ │ ├── user_simple.md
│ │ └── user_simple_float.md
├── guide
│ ├── additions
│ │ ├── swap.md
│ │ └── uuid.md
│ ├── cqrs
│ │ └── create-wallet.md
│ ├── db
│ │ ├── atomic-service.md
│ │ ├── race-condition.md
│ │ └── transaction.md
│ ├── events
│ │ ├── balance-updated-event.md
│ │ ├── customize.md
│ │ ├── transaction-created-event.md
│ │ └── wallet-created-event.md
│ ├── fractional
│ │ ├── deposit.md
│ │ ├── transfer.md
│ │ └── withdraw.md
│ ├── helpers
│ │ └── formatter.md
│ ├── high-performance
│ │ ├── batch-transactions.md
│ │ └── batch-transfers.md
│ ├── introduction
│ │ ├── basic-usage.md
│ │ ├── configuration.md
│ │ ├── index.md
│ │ ├── installation.md
│ │ └── upgrade.md
│ ├── multi
│ │ ├── new-wallet.md
│ │ ├── transaction-filter.md
│ │ └── transfer.md
│ ├── purchases
│ │ ├── cart.md
│ │ ├── commissions.md
│ │ ├── gift.md
│ │ ├── payment-free.md
│ │ ├── payment.md
│ │ ├── receiving.md
│ │ └── refund.md
│ └── single
│ │ ├── cancel.md
│ │ ├── confirm.md
│ │ ├── credit-limits.md
│ │ ├── deposit.md
│ │ ├── exchange.md
│ │ ├── refresh.md
│ │ ├── transfer.md
│ │ └── withdraw.md
├── icon.svg
└── index.md
├── ecs.php
├── infection.json.dist
├── package-lock.json
├── package.json
├── phpstan.common.neon
├── phpstan.src.baseline.neon
├── phpstan.src.neon
├── phpstan.tests.baseline.neon
├── phpstan.tests.neon
├── phpunit.xml
├── rector.php
├── resources
└── lang
│ ├── ar
│ └── errors.php
│ ├── en
│ └── errors.php
│ ├── es
│ └── errors.php
│ ├── fa
│ └── errors.php
│ ├── ru
│ └── errors.php
│ └── zh-CN
│ └── errors.php
├── src
├── Exceptions
│ ├── AmountInvalid.php
│ ├── BalanceIsEmpty.php
│ ├── ConfirmedInvalid.php
│ ├── InsufficientFunds.php
│ ├── ProductEnded.php
│ ├── UnconfirmedInvalid.php
│ └── WalletOwnerInvalid.php
├── External
│ ├── Api
│ │ ├── TransactionFloatQuery.php
│ │ ├── TransactionQuery.php
│ │ ├── TransactionQueryHandler.php
│ │ ├── TransactionQueryHandlerInterface.php
│ │ ├── TransactionQueryInterface.php
│ │ ├── TransferFloatQuery.php
│ │ ├── TransferQuery.php
│ │ ├── TransferQueryHandler.php
│ │ ├── TransferQueryHandlerInterface.php
│ │ └── TransferQueryInterface.php
│ ├── Contracts
│ │ ├── ExtraDtoInterface.php
│ │ └── OptionDtoInterface.php
│ └── Dto
│ │ ├── Extra.php
│ │ └── Option.php
├── Interfaces
│ ├── CartInterface.php
│ ├── Confirmable.php
│ ├── Customer.php
│ ├── Discount.php
│ ├── Exchangeable.php
│ ├── MaximalTaxable.php
│ ├── MinimalTaxable.php
│ ├── ProductInterface.php
│ ├── ProductLimitedInterface.php
│ ├── Taxable.php
│ ├── Wallet.php
│ └── WalletFloat.php
├── Internal
│ ├── Assembler
│ │ ├── AvailabilityDtoAssembler.php
│ │ ├── AvailabilityDtoAssemblerInterface.php
│ │ ├── BalanceUpdatedEventAssembler.php
│ │ ├── BalanceUpdatedEventAssemblerInterface.php
│ │ ├── ExtraDtoAssembler.php
│ │ ├── ExtraDtoAssemblerInterface.php
│ │ ├── OptionDtoAssembler.php
│ │ ├── OptionDtoAssemblerInterface.php
│ │ ├── TransactionCreatedEventAssembler.php
│ │ ├── TransactionCreatedEventAssemblerInterface.php
│ │ ├── TransactionDtoAssembler.php
│ │ ├── TransactionDtoAssemblerInterface.php
│ │ ├── TransactionQueryAssembler.php
│ │ ├── TransactionQueryAssemblerInterface.php
│ │ ├── TransferDtoAssembler.php
│ │ ├── TransferDtoAssemblerInterface.php
│ │ ├── TransferLazyDtoAssembler.php
│ │ ├── TransferLazyDtoAssemblerInterface.php
│ │ ├── TransferQueryAssembler.php
│ │ ├── TransferQueryAssemblerInterface.php
│ │ ├── WalletCreatedEventAssembler.php
│ │ └── WalletCreatedEventAssemblerInterface.php
│ ├── Decorator
│ │ └── StorageServiceLockDecorator.php
│ ├── Dto
│ │ ├── AvailabilityDto.php
│ │ ├── AvailabilityDtoInterface.php
│ │ ├── BasketDto.php
│ │ ├── BasketDtoInterface.php
│ │ ├── ItemDto.php
│ │ ├── ItemDtoInterface.php
│ │ ├── TransactionDto.php
│ │ ├── TransactionDtoInterface.php
│ │ ├── TransferDto.php
│ │ ├── TransferDtoInterface.php
│ │ ├── TransferLazyDto.php
│ │ └── TransferLazyDtoInterface.php
│ ├── Events
│ │ ├── BalanceUpdatedEvent.php
│ │ ├── BalanceUpdatedEventInterface.php
│ │ ├── EventInterface.php
│ │ ├── TransactionCreatedEvent.php
│ │ ├── TransactionCreatedEventInterface.php
│ │ ├── WalletCreatedEvent.php
│ │ └── WalletCreatedEventInterface.php
│ ├── Exceptions
│ │ ├── CartEmptyException.php
│ │ ├── ExceptionInterface.php
│ │ ├── InvalidArgumentExceptionInterface.php
│ │ ├── LogicExceptionInterface.php
│ │ ├── ModelNotFoundException.php
│ │ ├── RecordNotFoundException.php
│ │ ├── RuntimeExceptionInterface.php
│ │ ├── TransactionFailedException.php
│ │ ├── TransactionRollbackException.php
│ │ └── UnderflowExceptionInterface.php
│ ├── Listeners
│ │ ├── TransactionBeginningListener.php
│ │ ├── TransactionCommittedListener.php
│ │ ├── TransactionCommittingListener.php
│ │ └── TransactionRolledBackListener.php
│ ├── Observers
│ │ ├── TransactionObserver.php
│ │ └── TransferObserver.php
│ ├── Query
│ │ ├── TransactionQuery.php
│ │ ├── TransactionQueryInterface.php
│ │ ├── TransferQuery.php
│ │ └── TransferQueryInterface.php
│ ├── Repository
│ │ ├── TransactionRepository.php
│ │ ├── TransactionRepositoryInterface.php
│ │ ├── TransferRepository.php
│ │ ├── TransferRepositoryInterface.php
│ │ ├── WalletRepository.php
│ │ └── WalletRepositoryInterface.php
│ ├── Service
│ │ ├── ClockService.php
│ │ ├── ClockServiceInterface.php
│ │ ├── ConnectionService.php
│ │ ├── ConnectionServiceInterface.php
│ │ ├── DatabaseService.php
│ │ ├── DatabaseServiceInterface.php
│ │ ├── DispatcherService.php
│ │ ├── DispatcherServiceInterface.php
│ │ ├── IdentifierFactoryService.php
│ │ ├── IdentifierFactoryServiceInterface.php
│ │ ├── JsonService.php
│ │ ├── JsonServiceInterface.php
│ │ ├── LockService.php
│ │ ├── LockServiceInterface.php
│ │ ├── MathService.php
│ │ ├── MathServiceInterface.php
│ │ ├── StateService.php
│ │ ├── StateServiceInterface.php
│ │ ├── StorageService.php
│ │ ├── StorageServiceInterface.php
│ │ ├── TranslatorService.php
│ │ ├── TranslatorServiceInterface.php
│ │ ├── UuidFactoryService.php
│ │ └── UuidFactoryServiceInterface.php
│ └── Transform
│ │ ├── TransactionDtoTransformer.php
│ │ ├── TransactionDtoTransformerInterface.php
│ │ ├── TransferDtoTransformer.php
│ │ └── TransferDtoTransformerInterface.php
├── Models
│ ├── Transaction.php
│ ├── Transfer.php
│ └── Wallet.php
├── Objects
│ └── Cart.php
├── Services
│ ├── AssistantService.php
│ ├── AssistantServiceInterface.php
│ ├── AtmService.php
│ ├── AtmServiceInterface.php
│ ├── AtomicService.php
│ ├── AtomicServiceInterface.php
│ ├── BasketService.php
│ ├── BasketServiceInterface.php
│ ├── BookkeeperService.php
│ ├── BookkeeperServiceInterface.php
│ ├── CastService.php
│ ├── CastServiceInterface.php
│ ├── ConsistencyService.php
│ ├── ConsistencyServiceInterface.php
│ ├── DiscountService.php
│ ├── DiscountServiceInterface.php
│ ├── EagerLoaderService.php
│ ├── EagerLoaderServiceInterface.php
│ ├── ExchangeService.php
│ ├── ExchangeServiceInterface.php
│ ├── FormatterService.php
│ ├── FormatterServiceInterface.php
│ ├── PrepareService.php
│ ├── PrepareServiceInterface.php
│ ├── PurchaseService.php
│ ├── PurchaseServiceInterface.php
│ ├── RegulatorService.php
│ ├── RegulatorServiceInterface.php
│ ├── TaxService.php
│ ├── TaxServiceInterface.php
│ ├── TransactionService.php
│ ├── TransactionServiceInterface.php
│ ├── TransferService.php
│ ├── TransferServiceInterface.php
│ ├── WalletService.php
│ └── WalletServiceInterface.php
├── Traits
│ ├── CanConfirm.php
│ ├── CanExchange.php
│ ├── CanPay.php
│ ├── CanPayFloat.php
│ ├── CartPay.php
│ ├── HasGift.php
│ ├── HasWallet.php
│ ├── HasWalletFloat.php
│ ├── HasWallets.php
│ └── MorphOneWallet.php
├── WalletConfigure.php
└── WalletServiceProvider.php
└── tests
├── Infra
├── Exceptions
│ ├── PriceNotSetException.php
│ └── UnknownEventException.php
├── Factories
│ ├── BuyerFactory.php
│ ├── ItemDiscountFactory.php
│ ├── ItemDiscountTaxFactory.php
│ ├── ItemFactory.php
│ ├── ItemMaxTaxFactory.php
│ ├── ItemMetaFactory.php
│ ├── ItemMinTaxFactory.php
│ ├── ItemMultiPriceFactory.php
│ ├── ItemTaxFactory.php
│ ├── ItemWalletFactory.php
│ ├── ManagerFactory.php
│ ├── UserCashierFactory.php
│ ├── UserConfirmFactory.php
│ ├── UserDynamicFactory.php
│ ├── UserFactory.php
│ ├── UserFloatFactory.php
│ └── UserMultiFactory.php
├── Helpers
│ └── Config.php
├── Listeners
│ ├── BalanceUpdatedThrowDateListener.php
│ ├── BalanceUpdatedThrowIdListener.php
│ ├── BalanceUpdatedThrowUuidListener.php
│ ├── TransactionCreatedThrowListener.php
│ └── WalletCreatedThrowListener.php
├── Models
│ ├── Buyer.php
│ ├── Item.php
│ ├── ItemDiscount.php
│ ├── ItemDiscountTax.php
│ ├── ItemMaxTax.php
│ ├── ItemMeta.php
│ ├── ItemMinTax.php
│ ├── ItemMultiPrice.php
│ ├── ItemTax.php
│ ├── ItemWallet.php
│ ├── Manager.php
│ ├── User.php
│ ├── UserCashier.php
│ ├── UserConfirm.php
│ ├── UserDynamic.php
│ ├── UserFloat.php
│ └── UserMulti.php
├── PackageModels
│ ├── MyWallet.php
│ ├── Transaction.php
│ ├── TransactionMoney.php
│ ├── Transfer.php
│ └── Wallet.php
├── Services
│ ├── ClockFakeService.php
│ ├── ExchangeUsdToBtcService.php
│ └── MyExchangeService.php
├── TestCase.php
├── TestServiceProvider.php
├── Transform
│ └── TransactionDtoTransformerCustom.php
└── Values
│ └── Money.php
├── Units
├── Api
│ ├── TransactionHandlerTest.php
│ └── TransferHandlerTest.php
├── Domain
│ ├── BalanceTest.php
│ ├── BasketTest.php
│ ├── BlockTest.php
│ ├── CartReceivingTest.php
│ ├── CartTest.php
│ ├── ConfirmTest.php
│ ├── CreditWalletTest.php
│ ├── DiscountTaxTest.php
│ ├── DiscountTest.php
│ ├── EagerLoadingTest.php
│ ├── EventTest.php
│ ├── ExchangeTest.php
│ ├── ExtraTest.php
│ ├── GiftDiscountTaxTest.php
│ ├── GiftDiscountTest.php
│ ├── GiftTest.php
│ ├── MinTaxTest.php
│ ├── ModelTableTest.php
│ ├── MultiWalletGiftTest.php
│ ├── MultiWalletTest.php
│ ├── ProductTest.php
│ ├── SilentlyDiscardingTest.php
│ ├── SoftDeletesTest.php
│ ├── StateTest.php
│ ├── TaxTest.php
│ ├── TransactionAmountFloatAccessorTest.php
│ ├── TransactionsFilterTest.php
│ ├── WalletExtensionTest.php
│ ├── WalletFloatTest.php
│ └── WalletTest.php
├── Expand
│ └── WalletTest.php
└── Service
│ ├── AtomicServiceTest.php
│ ├── BookkeeperTest.php
│ ├── DatabaseTest.php
│ ├── DeferrableTest.php
│ ├── FormatterTest.php
│ ├── JsonServiceTest.php
│ ├── LockServiceTest.php
│ ├── MathTest.php
│ ├── SingletonTest.php
│ ├── StorageTest.php
│ ├── WalletConfigureTest.php
│ └── WalletTest.php
└── migrations
├── 2014_10_12_000000_create_users_table.php
├── 2018_11_08_214421_create_items_table.php
├── 2019_09_19_191432_alter_column_transaction_table.php
├── 2022_01_26_185311_create_managers_table.php
└── 2023_12_13_190445_create_cache_table.php
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | exclude_patterns:
2 | - "config/"
3 | - "database/"
4 | - "docs/"
5 | - "resources/"
6 | - "tests/"
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: laravel-wallet
2 | custom: ["https://www.tinkoff.ru/cf/1OkIPwTvTLf"]
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug, question
6 | assignees: rez1dent3
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Trace Error**
21 | Full error trace...
22 |
23 | **Expected behavior**
24 | A clear and concise description of what you expected to happen.
25 |
26 | **Screenshots**
27 | If applicable, add screenshots to help explain your problem.
28 |
29 | **Server:**
30 | - php version: [e.g. 8.1]
31 | - database: [e.g mysql 8.0]
32 | - wallet version [e.g. 8.2]
33 | - cache lock: [e.g. redis]
34 | - cache wallets: [e.g. redis]
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/error-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Error request
3 | about: An error occurred, please help
4 | title: ''
5 | labels: question
6 | assignees: rez1dent3
7 |
8 | ---
9 |
10 | **Describe your task**
11 | A clear and concise description of your task.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Trace Error**
21 | Full error trace... If there is
22 |
23 | **Expected behavior**
24 | A clear and concise description of what you expected to happen.
25 |
26 | **Screenshots**
27 | If applicable, add screenshots to help explain your problem.
28 |
29 | **Server:**
30 | - php version: [e.g. 8.1]
31 | - database: [e.g mysql 8.0]
32 | - wallet version [e.g. 8.2]
33 | - cache lock: [e.g. redis]
34 | - cache wallets: [e.g. redis]
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: rez1dent3
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | - package-ecosystem: npm
9 | directory: "/"
10 | schedule:
11 | interval: daily
12 | open-pull-requests-limit: 10
13 | - package-ecosystem: github-actions
14 | directory: "/"
15 | schedule:
16 | interval: daily
17 | open-pull-requests-limit: 10
18 |
--------------------------------------------------------------------------------
/.github/workflows/changelog.yml:
--------------------------------------------------------------------------------
1 | name: update changelog
2 |
3 | on:
4 | release:
5 | types: [released]
6 |
7 | permissions: {}
8 |
9 | jobs:
10 | changelog:
11 | permissions:
12 | contents: write
13 | secrets: inherit
14 | uses: bavix/.github/.github/workflows/changelog.yml@0.3.3
15 |
--------------------------------------------------------------------------------
/.github/workflows/deptrac.yaml:
--------------------------------------------------------------------------------
1 | name: deptrac
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | deptrac:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 |
17 | - name: Setup PHP
18 | uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: 8.2
21 | extensions: bcmath
22 | env:
23 | runner: self-hosted
24 |
25 | - name: Validate composer.json and composer.lock
26 | run: composer validate --strict
27 |
28 | - name: Cache Composer packages
29 | id: composer-cache
30 | uses: actions/cache@v4
31 | with:
32 | path: vendor
33 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
34 | restore-keys: |
35 | ${{ runner.os }}-php-
36 |
37 | - name: Install dependencies
38 | run: composer install --prefer-dist --no-progress
39 |
40 | - name: Install deptrac
41 | run: composer req --dev qossmic/deptrac-shim
42 | if: hashFiles('vendor/bin/deptrac') == ''
43 |
44 | - name: Run deptrac
45 | run: php vendor/bin/deptrac analyse
46 |
--------------------------------------------------------------------------------
/.github/workflows/lock.yml:
--------------------------------------------------------------------------------
1 | name: "Lock Threads"
2 |
3 | on:
4 | schedule:
5 | - cron: "0 1 * * *"
6 |
7 | permissions:
8 | issues: write
9 |
10 | jobs:
11 | action:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: dessant/lock-threads@v5
15 | with:
16 | github-token: ${{ secrets.GITHUB_TOKEN }}
17 | issue-inactive-days: "45"
18 | process-only: "issues"
19 |
--------------------------------------------------------------------------------
/.github/workflows/phpstan.yaml:
--------------------------------------------------------------------------------
1 | name: phpstan
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | phpstan:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 |
17 | - name: Setup PHP
18 | uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: 8.2
21 | extensions: bcmath
22 | env:
23 | runner: self-hosted
24 |
25 | - name: Validate composer.json and composer.lock
26 | run: composer validate --strict
27 |
28 | - name: Cache Composer packages
29 | id: composer-cache
30 | uses: actions/cache@v4
31 | with:
32 | path: vendor
33 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
34 | restore-keys: |
35 | ${{ runner.os }}-php-
36 |
37 | - name: Install dependencies
38 | run: composer install --prefer-dist --no-progress
39 |
40 | - name: Run phpstan
41 | run: composer phpstan
42 |
--------------------------------------------------------------------------------
/.github/workflows/semgrep.yml:
--------------------------------------------------------------------------------
1 | name: semgrep
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | semgrep:
11 | uses: bavix/.github/.github/workflows/php-semgrep.yml@0.3.3
12 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues
2 | on:
3 | schedule:
4 | - cron: "0 */8 * * *"
5 | jobs:
6 | stale:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/stale@v9
10 | with:
11 | repo-token: ${{ secrets.GITHUB_TOKEN }}
12 | stale-issue-message: 'This issue is stale because it has been open 7 days with no activity.'
13 | days-before-stale: 7
14 | days-before-close: 3
15 | exempt-issue-labels: 'bug,backlog,in-developing'
16 | exempt-pr-labels: 'bug,frozen,in-developing'
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.phar
2 | /vendor/
3 | composer.lock
4 | .idea/
5 | build/
6 | .php_cs_cache
7 | node_modules/
8 | .deptrac.cache
9 | .phpunit.cache/
10 |
--------------------------------------------------------------------------------
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 | '@',
6 | ]));
7 | }
8 |
--------------------------------------------------------------------------------
/.whitesource:
--------------------------------------------------------------------------------
1 | {
2 | "scanSettings": {
3 | "baseBranches": []
4 | },
5 | "checkRunSettings": {
6 | "vulnerableCheckRunConclusionLevel": "failure",
7 | "displayMode": "diff",
8 | "useMendCheckNames": true
9 | },
10 | "issueSettings": {
11 | "minSeverityLevel": "LOW",
12 | "issueType": "DEPENDENCY"
13 | }
14 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 bavix
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/database/2018_11_06_222923_create_transactions_table.php:
--------------------------------------------------------------------------------
1 | table(), static function (Blueprint $table) {
15 | $table->bigIncrements('id');
16 | $table->morphs('payable');
17 | $table->unsignedBigInteger('wallet_id');
18 | $table->enum('type', ['deposit', 'withdraw'])->index();
19 | $table->decimal('amount', 64, 0);
20 | $table->boolean('confirmed');
21 | $table->json('meta')
22 | ->nullable();
23 | $table->uuid('uuid')
24 | ->unique();
25 | $table->timestamps();
26 |
27 | $table->index(['payable_type', 'payable_id'], 'payable_type_payable_id_ind');
28 | $table->index(['payable_type', 'payable_id', 'type'], 'payable_type_ind');
29 | $table->index(['payable_type', 'payable_id', 'confirmed'], 'payable_confirmed_ind');
30 | $table->index(['payable_type', 'payable_id', 'type', 'confirmed'], 'payable_type_confirmed_ind');
31 | });
32 | }
33 |
34 | public function down(): void
35 | {
36 | Schema::dropIfExists($this->table());
37 | }
38 |
39 | private function table(): string
40 | {
41 | return (new Transaction())->getTable();
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/database/2021_11_02_202021_update_wallets_uuid_table.php:
--------------------------------------------------------------------------------
1 | table(), 'uuid')) {
17 | return;
18 | }
19 |
20 | // upgrade from 6.x
21 | Schema::table($this->table(), static function (Blueprint $table) {
22 | $table->uuid('uuid')
23 | ->after('slug')
24 | ->nullable()
25 | ->unique();
26 | });
27 |
28 | Wallet::query()->chunk(10000, static function (Collection $wallets) {
29 | $wallets->each(function (Wallet $wallet) {
30 | $wallet->uuid = app(IdentifierFactoryServiceInterface::class)->generate();
31 | $wallet->save();
32 | });
33 | });
34 |
35 | Schema::table($this->table(), static function (Blueprint $table) {
36 | $table->uuid('uuid')
37 | ->change();
38 | });
39 | }
40 |
41 | public function down(): void
42 | {
43 | Schema::table($this->table(), function (Blueprint $table) {
44 | if (Schema::hasColumn($this->table(), 'uuid')) {
45 | $table->dropIndex('wallets_uuid_unique');
46 | $table->dropColumn('uuid');
47 | }
48 | });
49 | }
50 |
51 | private function table(): string
52 | {
53 | return (new Wallet())->getTable();
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/database/2023_12_30_113122_extra_columns_removed.php:
--------------------------------------------------------------------------------
1 | table(), function (Blueprint $table) {
15 | $table->dropIndex(['from_type', 'from_id']);
16 | $table->dropIndex(['to_type', 'to_id']);
17 |
18 | $table->index('from_id');
19 | $table->index('to_id');
20 | });
21 |
22 | Schema::dropColumns($this->table(), ['from_type', 'to_type']);
23 | }
24 |
25 | public function down(): void
26 | {
27 | Schema::table($this->table(), static function (Blueprint $table) {
28 | $table->string('from_type')
29 | ->after('from_id');
30 | $table->string('to_type')
31 | ->after('to_id');
32 | });
33 | }
34 |
35 | private function table(): string
36 | {
37 | return (new Transfer())->getTable();
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/database/2023_12_30_204610_soft_delete.php:
--------------------------------------------------------------------------------
1 | getTable(), static function (Blueprint $table) {
17 | $table->softDeletesTz();
18 | });
19 | Schema::table((new Transfer())->getTable(), static function (Blueprint $table) {
20 | $table->softDeletesTz();
21 | });
22 | Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {
23 | $table->softDeletesTz();
24 | });
25 | }
26 |
27 | public function down(): void
28 | {
29 | Schema::table((new Wallet())->getTable(), static function (Blueprint $table) {
30 | $table->dropSoftDeletes();
31 | });
32 | Schema::table((new Transfer())->getTable(), static function (Blueprint $table) {
33 | $table->dropSoftDeletes();
34 | });
35 | Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {
36 | $table->dropSoftDeletes();
37 | });
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/database/2024_01_24_185401_add_extra_column_in_transfer.php:
--------------------------------------------------------------------------------
1 | table(), static function (Blueprint $table) {
15 | $table->json('extra')
16 | ->nullable()
17 | ->after('fee');
18 | });
19 | }
20 |
21 | public function down(): void
22 | {
23 | Schema::dropColumns($this->table(), ['extra']);
24 | }
25 |
26 | private function table(): string
27 | {
28 | return (new Transfer())->getTable();
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/docs/.vitepress/.gitignore:
--------------------------------------------------------------------------------
1 | cache/
2 | dist/
3 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --vp-home-hero-image-background-image: linear-gradient(-44deg, #ec9333 50%, #f1f09c 50%);
3 | --vp-home-hero-image-filter: blur(46px);
4 | }
5 |
6 | @media (min-width: 640px) {
7 | :root {
8 | --vp-home-hero-image-filter: blur(50px);
9 | }
10 | }
11 |
12 | @media (min-width: 960px) {
13 | :root {
14 | --vp-home-hero-image-filter: blur(75px);
15 | }
16 | }
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | import DefaultTheme from 'vitepress/theme'
2 | import './custom.css'
3 |
4 | export default DefaultTheme
5 |
--------------------------------------------------------------------------------
/docs/_include/composer.md:
--------------------------------------------------------------------------------
1 | ## Composer
2 |
3 | The recommended installation method is using [Composer](https://getcomposer.org/).
4 |
5 | In your project root just run:
6 |
7 | ```bash
8 | composer req bavix/laravel-wallet
9 | ```
10 |
11 | Ensure that you’ve set up your project to [autoload Composer-installed packages](https://getcomposer.org/doc/01-basic-usage.md#autoloading).
12 |
--------------------------------------------------------------------------------
/docs/_include/eager_loading.md:
--------------------------------------------------------------------------------
1 | ## Eager Loading
2 |
3 | When accessing Eloquent relationships as properties, the relationship data is "lazy loaded".
4 | This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem. To illustrate the N + 1 query problem, consider a `Wallet` model that is related to `User`:
5 |
6 | Add the `HasWallet` trait and `Wallet` interface to model.
7 | ```php
8 | use Bavix\Wallet\Traits\HasWallet;
9 | use Bavix\Wallet\Interfaces\Wallet;
10 |
11 | class User extends Model implements Wallet
12 | {
13 | use HasWallet; // public function wallet(): MorphOne...
14 | }
15 | ```
16 |
17 | Now, let's retrieve all wallets and their users:
18 |
19 | ```php
20 | $users = User::all();
21 |
22 | foreach ($users as $user) {
23 | // echo $user->wallet->balance;
24 | echo $user->balance; // Abbreviated notation
25 | }
26 | ```
27 |
28 | This loop will execute 1 query to retrieve all of the users on the table, then another query for each user to retrieve the wallet. So, if we have 25 users, the code above would run 26 queries: 1 for the original user, and 25 additional queries to retrieve the wallet of each user.
29 |
30 | Thankfully, we can use eager loading to reduce this operation to just 2 queries. When querying, you may specify which relationships should be eager loaded using the with method:
31 |
32 | ```php
33 | $users = User::with('wallet')->all();
34 |
35 | foreach ($users as $user) {
36 | // echo $user->wallet->balance;
37 | echo $user->balance; // Abbreviated notation
38 | }
39 | ```
40 |
41 | For this operation, only two queries will be executed.
42 |
--------------------------------------------------------------------------------
/docs/_include/models/user_simple.md:
--------------------------------------------------------------------------------
1 | It is necessary to expand the model that will have the wallet.
2 | This is done in two stages:
3 | - Add `Wallet` interface;
4 | - Add the `HasWallet` trait;
5 |
6 | Let's get started.
7 | ```php
8 | use Bavix\Wallet\Traits\HasWallet;
9 | use Bavix\Wallet\Interfaces\Wallet;
10 |
11 | class User extends Model implements Wallet
12 | {
13 | use HasWallet;
14 | }
15 | ```
16 |
17 | The model is prepared to work with a wallet.
18 |
--------------------------------------------------------------------------------
/docs/_include/models/user_simple_float.md:
--------------------------------------------------------------------------------
1 | It is necessary to expand the model that will have the wallet.
2 | This is done in two stages:
3 | - Add `Wallet` interface;
4 | - Add the `HasWalletFloat` trait;
5 |
6 | Let's get started.
7 | ```php
8 | use Bavix\Wallet\Traits\HasWalletFloat;
9 | use Bavix\Wallet\Interfaces\Wallet;
10 |
11 | class User extends Model implements Wallet
12 | {
13 | use HasWalletFloat;
14 | }
15 | ```
16 |
17 | The model is prepared to work with a wallet.
18 |
--------------------------------------------------------------------------------
/docs/guide/additions/swap.md:
--------------------------------------------------------------------------------
1 | # Laravel Wallet Swap
2 |
3 | ## Composer
4 |
5 | The recommended installation method is using [Composer](https://getcomposer.org/).
6 |
7 | In your project root just run:
8 |
9 | ```bash
10 | composer req bavix/laravel-wallet-swap
11 | ```
12 |
13 | ## User model
14 | We need a simple model with the ability to work multi-wallets.
15 |
16 | ```php
17 | use Bavix\Wallet\Interfaces\Wallet;
18 | use Bavix\Wallet\Traits\HasWallets;
19 | use Bavix\Wallet\Traits\HasWallet;
20 |
21 | class User extends Model implements Wallet
22 | {
23 | use HasWallet, HasWallets;
24 | }
25 | ```
26 |
27 | ## Simple example
28 | Let's create wallets with currency:
29 | ```php
30 | $usd = $user->createWallet([
31 | 'name' => 'My Dollars',
32 | 'slug' => 'usd',
33 | 'meta' => ['currency' => 'USD'],
34 | ]);
35 |
36 | $rub = $user->createWallet([
37 | 'name' => 'My Ruble',
38 | 'slug' => 'rub',
39 | 'meta' => ['currency' => 'RUB'],
40 | ]);
41 | ```
42 |
43 | Find wallets and exchange from one to another.
44 |
45 | ```php
46 | $rub = $user->getWallet('rub');
47 | $usd = $user->getWallet('usd');
48 |
49 | $usd->balance; // 200
50 | $rub->balance; // 0
51 |
52 | $usd->exchange($rub, 10);
53 | $usd->balance; // 190
54 | $rub->balance; // 622
55 | ```
56 |
57 | It's simple!
58 |
--------------------------------------------------------------------------------
/docs/guide/additions/uuid.md:
--------------------------------------------------------------------------------
1 | # Laravel Wallet UUID
2 |
3 | > Using uuid greatly reduces package performance. We recommend using int.
4 |
5 | Often there is a need to store an identifier in uuid/ulid. Since version 9.0 laravel-wallet supports string identifiers, you only need to perform the migration.
6 |
7 | To simplify the process, you can use a ready-made package.
8 |
9 | > Attention! It will not work to use UUID instead of ID in wallet models; there is a special uuid field for this.
10 |
11 | ## Composer
12 |
13 | The recommended installation method is using [Composer](https://getcomposer.org/).
14 |
15 | In your project root just run:
16 |
17 | ```bash
18 | composer req bavix/laravel-wallet-uuid
19 | ```
20 |
21 | Now you need to migrate!
22 |
23 | After migration, you can use the UUID in your models.
24 |
25 | You can find implementation examples in the package tests: https://github.com/bavix/laravel-wallet-uuid/tree/master/tests
26 |
27 | It's simple!
28 |
--------------------------------------------------------------------------------
/docs/guide/db/race-condition.md:
--------------------------------------------------------------------------------
1 | # Race Condition
2 |
3 | A common issue in the issue is about race conditions.
4 |
5 | If you have not yet imported the config into the project, then you need to do this.
6 | ```bash
7 | php artisan vendor:publish --tag=laravel-wallet-config
8 | ```
9 |
10 | Previously, there was a vacuum package, but now it is a part of the core. You just need to configure the lock service and the cache service in the package configuration `wallet.php`.
11 |
12 | ```php
13 | /**
14 | * A system for dealing with race conditions.
15 | */
16 | 'lock' => [
17 | 'driver' => 'array',
18 | 'seconds' => 1,
19 | ],
20 | ```
21 |
22 | To enable the fight against race conditions, you need to select a provider that supports work with locks. I recommend `redis`.
23 |
24 | There is a setting for storing the state of the wallet, I recommend choosing `redis` here too.
25 |
26 | ```php
27 | /**
28 | * Storage of the state of the balance of wallets.
29 | */
30 | 'cache' => ['driver' => 'array'],
31 | ```
32 |
33 | You need `redis-server` and `php-redis`.
34 |
35 | Redis is recommended but not required. You can choose whatever the [framework](https://laravel.com/docs/8.x/cache#introduction) offers you.
36 |
37 | It's simple!
38 |
--------------------------------------------------------------------------------
/docs/guide/db/transaction.md:
--------------------------------------------------------------------------------
1 | # Transaction
2 |
3 | You definitely need to know the feature of transactions. The wallet is automatically blocked from the moment it is used until the end of the transaction. Therefore, it is necessary to use the wallet closer to the end of the transaction.
4 |
5 | Very important! Almost all wallet transactions are blocking.
6 |
7 | ```php
8 | use Illuminate\Support\Facades\DB;
9 |
10 | DB::beginTransaction();
11 | $wallet->balanceInt; // now the wallet is blocked
12 | doingMagic(); // running for a long time.
13 | DB::commit(); // here will unlock the wallet
14 | ```
15 |
16 | The point is that you need to minimize operations within transactions as much as possible. The longer the transaction, the longer the wallet lock.
17 | The maximum wallet blocking time is set in the configuration. The longer the transaction takes, the more likely it is to get a race for the wallet.
18 |
19 | ---
20 | It's simple!
21 |
--------------------------------------------------------------------------------
/docs/guide/events/balance-updated-event.md:
--------------------------------------------------------------------------------
1 | # Tracking balance changes
2 |
3 | There are tasks when you urgently need to do something when the user's balance changes. A frequent case of transferring data via websockets to the front-end.
4 |
5 | Version 7.2 introduces an interface to which you can subscribe.
6 | This is done using standard Laravel methods.
7 | More information in the [documentation](https://laravel.com/docs/8.x/events).
8 |
9 | ```php
10 | use Bavix\Wallet\Internal\Events\BalanceUpdatedEventInterface;
11 |
12 | protected $listen = [
13 | BalanceUpdatedEventInterface::class => [
14 | MyBalanceUpdatedListener::class,
15 | ],
16 | ];
17 | ```
18 |
19 | And then we create a listener.
20 |
21 | ```php
22 | use Bavix\Wallet\Internal\Events\BalanceUpdatedEventInterface;
23 |
24 | class MyBalanceUpdatedListener
25 | {
26 | public function handle(BalanceUpdatedEventInterface $event): void
27 | {
28 | // And then the implementation...
29 | }
30 | }
31 | ```
32 |
33 | It's simple!
34 |
--------------------------------------------------------------------------------
/docs/guide/events/transaction-created-event.md:
--------------------------------------------------------------------------------
1 | # Tracking the creation of wallet transactions
2 |
3 | The events are similar to the events for updating the balance, only for the creation of a wallet. A frequent case of transferring data via websockets to the front-end.
4 |
5 | Version 9.1 introduces an interface to which you can subscribe.
6 | This is done using standard Laravel methods.
7 | More information in the [documentation](https://laravel.com/docs/8.x/events).
8 |
9 | ```php
10 | use Bavix\Wallet\Internal\Events\TransactionCreatedEventInterface;
11 |
12 | protected $listen = [
13 | TransactionCreatedEventInterface::class => [
14 | MyWalletTransactionCreatedListener::class,
15 | ],
16 | ];
17 | ```
18 |
19 | And then we create a listener.
20 |
21 | ```php
22 | use Bavix\Wallet\Internal\Events\TransactionCreatedEventInterface;
23 |
24 | class MyWalletTransactionCreatedListener
25 | {
26 | public function handle(TransactionCreatedEventInterface $event): void
27 | {
28 | // And then the implementation...
29 | }
30 | }
31 | ```
32 |
33 | It's simple!
34 |
--------------------------------------------------------------------------------
/docs/guide/events/wallet-created-event.md:
--------------------------------------------------------------------------------
1 | # Tracking the creation of wallets
2 |
3 | The events are similar to the events for updating the balance, only for the creation of a wallet. A frequent case of transferring data via websockets to the front-end.
4 |
5 | Version 7.3 introduces an interface to which you can subscribe.
6 | This is done using standard Laravel methods.
7 | More information in the [documentation](https://laravel.com/docs/8.x/events).
8 |
9 | ```php
10 | use Bavix\Wallet\Internal\Events\WalletCreatedEventInterface;
11 |
12 | protected $listen = [
13 | WalletCreatedEventInterface::class => [
14 | MyWalletCreatedListener::class,
15 | ],
16 | ];
17 | ```
18 |
19 | And then we create a listener.
20 |
21 | ```php
22 | use Bavix\Wallet\Internal\Events\WalletCreatedEventInterface;
23 |
24 | class MyWalletCreatedListener
25 | {
26 | public function handle(WalletCreatedEventInterface $event): void
27 | {
28 | // And then the implementation...
29 | }
30 | }
31 | ```
32 |
33 | It's simple!
34 |
--------------------------------------------------------------------------------
/docs/guide/fractional/deposit.md:
--------------------------------------------------------------------------------
1 | # Deposit float
2 |
3 | A deposit is a sum of money which is part of the full price of something,
4 | and which you pay when you agree to buy it.
5 |
6 | In this case, the Deposit is the replenishment of the wallet.
7 |
8 | ## User Model
9 |
10 |
11 |
12 | ## Make a Deposit
13 |
14 | Find user:
15 |
16 | ```php
17 | $user = User::first();
18 | ```
19 |
20 | As the user uses `HasWalletFloat`, he will have `balance` property.
21 | Check the user's balance.
22 |
23 | ```php
24 | $user->balance; // 0
25 | $user->balanceInt; // 0
26 | $user->balanceFloatNum; // 0
27 | ```
28 |
29 | The balance is zero, which is what we expected.
30 |
31 | ```php
32 | $user->depositFloat(10.1);
33 | $user->balance; // 1010
34 | $user->balanceInt; // 1010
35 | $user->balanceFloatNum; // 10.1
36 | ```
37 |
38 | Wow!
39 |
--------------------------------------------------------------------------------
/docs/guide/fractional/withdraw.md:
--------------------------------------------------------------------------------
1 | # Withdraw
2 |
3 | When there is enough money in the account, you can transfer/withdraw
4 | it or buy something in the system.
5 |
6 | Since the currency is virtual, you can buy any services on your website.
7 | For example, priority in search results.
8 |
9 | ## User Model
10 |
11 |
12 |
13 | ## Make a Withdraw
14 |
15 | Find user:
16 |
17 | ```php
18 | $user = User::first();
19 | ```
20 |
21 | As the user uses `HasWalletFloat`, he will have `balance` property.
22 | Check the user's balance.
23 |
24 | ```php
25 | $user->balance; // 10000
26 | $user->balanceInt; // 10000
27 | $user->balanceFloatNum; // 100.00
28 | ```
29 |
30 | The balance is not empty, so you can withdraw funds.
31 |
32 | ```php
33 | $user->withdrawFloat(10);
34 | $user->balance; // 9000
35 | $user->balanceInt; // 9000
36 | $user->balanceFloatNum; // 90.00
37 | ```
38 |
39 | It's simple!
40 |
41 | ## Force Withdraw
42 |
43 | Forced withdrawal is necessary for those cases when
44 | the user has no funds. For example, a fine for spam.
45 |
46 | ```php
47 | $user->balanceFloatNum; // 90.00
48 | $user->forceWithdrawFloat(101);
49 | $user->balanceFloatNum; // -11.00
50 | ```
51 |
52 | ## And what will happen if the money is not enough?
53 |
54 | There can be two situations:
55 |
56 | - The user's balance is zero, then we get an error
57 | `Bavix\Wallet\Exceptions\BalanceIsEmpty`
58 | - If the balance is greater than zero, but it is not enough
59 | `Bavix\Wallet\Exceptions\InsufficientFunds`
60 |
--------------------------------------------------------------------------------
/docs/guide/helpers/formatter.md:
--------------------------------------------------------------------------------
1 | # Formatter
2 |
3 | Sometimes you need to convert the balance to some format. A small and simple helper has appeared that will simplify the process a little.
4 |
5 | ## To fractional numbers
6 |
7 | ```php
8 | app(FormatterServiceInterface::class)->floatValue('12345', 2); // 123.45
9 | app(FormatterServiceInterface::class)->floatValue('12345', 3); // 12.345
10 | ```
11 |
12 | ## To whole numbers
13 |
14 | ```php
15 | app(FormatterServiceInterface::class)->intValue('12.345', 3); // 12345
16 | app(FormatterServiceInterface::class)->intValue('123.45', 2); // 12345
17 | ```
18 |
19 | ---
20 | It's simple!
21 |
--------------------------------------------------------------------------------
/docs/guide/introduction/index.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | ---
4 |
5 | 
6 |
7 | `laravel-wallet` - It's easy to work with a virtual wallet.
8 |
9 | ### Support
10 |
11 | Please ask questions on the [Github issues page](https://github.com/bavix/laravel-wallet/issues).
12 |
--------------------------------------------------------------------------------
/docs/guide/introduction/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 |
4 |
5 | ## Customize
6 |
7 | Sometimes it is useful...
8 |
9 | ### Run Migrations
10 | Publish the migrations with this artisan command:
11 | ```bash
12 | php artisan vendor:publish --tag=laravel-wallet-migrations
13 | ```
14 |
15 | ### Configuration
16 | You can publish the config file with this artisan command:
17 | ```bash
18 | php artisan vendor:publish --tag=laravel-wallet-config
19 | ```
20 |
21 | After installing the package, you can proceed to [use it](basic-usage) or [configure](configuration) it to suit your needs.
22 |
--------------------------------------------------------------------------------
/docs/guide/multi/new-wallet.md:
--------------------------------------------------------------------------------
1 | # New Wallet
2 |
3 | You can create an unlimited number of wallets, but the `slug` for each wallet should be unique.
4 |
5 | ## User Model
6 |
7 | Add the `HasWallets` trait's and `Wallet` interface to model.
8 |
9 | ```php
10 | use Bavix\Wallet\Traits\HasWallets;
11 | use Bavix\Wallet\Interfaces\Wallet;
12 |
13 | class User extends Model implements Wallet
14 | {
15 | use HasWallets;
16 | }
17 | ```
18 |
19 | ## Create a wallet
20 |
21 | Find user:
22 |
23 | ```php
24 | $user = User::first();
25 | ```
26 |
27 | Create a new wallet.
28 |
29 | ```php
30 | $user->hasWallet('my-wallet'); // bool(false)
31 | $wallet = $user->createWallet([
32 | 'name' => 'New Wallet',
33 | 'slug' => 'my-wallet',
34 | ]);
35 |
36 | $user->hasWallet('my-wallet'); // bool(true)
37 |
38 | $wallet->deposit(100);
39 | $wallet->balance; // 100
40 | $wallet->balanceFloatNum; // 1.00
41 | ```
42 |
43 | ## How to get the right wallet?
44 |
45 | ```php
46 | $myWallet = $user->getWallet('my-wallet');
47 | $myWallet->balance; // 100
48 | $myWallet->balanceFloatNum; // 1.00
49 | ```
50 |
51 | ## Default Wallet + MultiWallet
52 |
53 | Is it possible to use the default wallet and multi-wallets at the same time? Yes.
54 |
55 | ```php
56 | use Bavix\Wallet\Traits\HasWallet;
57 | use Bavix\Wallet\Traits\HasWallets;
58 | use Bavix\Wallet\Interfaces\Wallet;
59 |
60 | class User extends Model implements Wallet
61 | {
62 | use HasWallet, HasWallets;
63 | }
64 | ```
65 |
66 | How to get the default wallet?
67 |
68 | ```php
69 | $wallet = $user->wallet;
70 | $wallet->balance; // 10
71 | $wallet->balanceFloatNum; // 0.10
72 | ```
73 |
74 | It's simple!
75 |
--------------------------------------------------------------------------------
/docs/guide/multi/transaction-filter.md:
--------------------------------------------------------------------------------
1 | # Transaction Filter
2 |
3 | Often developers ask me about the `transactions` method.
4 | Yes, this method displays ALL transactions for the wallet owner.
5 | If you only need to filter one wallet at a time, now you can use the `walletTransactions` method.
6 |
7 | ```php
8 | /** @var \Bavix\Wallet\Models\Wallet $wallet */
9 |
10 | $query = $wallet->walletTransactions();
11 | ```
12 |
13 | Let's take a look at a livelier code example:
14 | ```php
15 | $user->transactions()->count(); // 0
16 |
17 | // Multi wallets and default wallet can be used together
18 | // default wallet
19 | $user->deposit(100);
20 | $user->wallet->deposit(200);
21 | $user->wallet->withdraw(1);
22 |
23 | // usd
24 | $usd = $user->createWallet(['name' => 'USD']);
25 | $usd->deposit(100);
26 |
27 | // eur
28 | $eur = $user->createWallet(['name' => 'EUR']);
29 | $eur->deposit(100);
30 |
31 | $user->transactions()->count(); // 5
32 | $user->wallet->transactions()->count(); // 5
33 | $usd->transactions()->count(); // 5
34 | $eur->transactions()->count(); // 5
35 | // the transactions method returns data relative to the owner of the wallet, for all transactions
36 |
37 | $user->walletTransactions()->count(); // 3. we get the default wallet
38 | $user->wallet->walletTransactions()->count(); // 3
39 | $usd->walletTransactions()->count(); // 1
40 | $eur->walletTransactions()->count(); // 1
41 | ```
42 |
43 | It's simple!
44 |
--------------------------------------------------------------------------------
/docs/guide/single/cancel.md:
--------------------------------------------------------------------------------
1 | # Cancel Transaction
2 |
3 | Sometimes you need to cancel a confirmed transaction. For example, money was received or debited by mistake. You can reset the confirmation of a specific transaction.
4 |
5 | ## User Model
6 |
7 | Add the `CanConfirm` trait and `Confirmable` interface to your User model.
8 |
9 | ```php
10 | use Bavix\Wallet\Interfaces\Confirmable;
11 | use Bavix\Wallet\Interfaces\Wallet;
12 | use Bavix\Wallet\Traits\CanConfirm;
13 | use Bavix\Wallet\Traits\HasWallet;
14 |
15 | class UserConfirm extends Model implements Wallet, Confirmable
16 | {
17 | use HasWallet, CanConfirm;
18 | }
19 | ```
20 |
21 | > You can only cancel the transaction with the wallet you paid with.
22 |
23 | ## To cancel
24 |
25 | ### Example:
26 |
27 | Created a transaction, and after resetting its confirmation.
28 |
29 | ```php
30 | $user->balance; // 0
31 | $transaction = $user->deposit(100); // confirmed transaction
32 | $transaction->confirmed; // bool(true)
33 | $user->balance; // 100
34 |
35 | $user->resetConfirm($transaction); // bool(true)
36 | $transaction->confirmed; // bool(false)
37 |
38 | $user->balance; // 0
39 | ```
40 |
41 | It's simple!
42 |
--------------------------------------------------------------------------------
/docs/guide/single/confirm.md:
--------------------------------------------------------------------------------
1 | # Confirm Transaction
2 |
3 | There are situations when it is necessary to create a transaction without crediting to the wallet or debiting. Laravel-wallet has such a mode of unconfirmed transactions.
4 |
5 | You create a transaction without confirmation, and a little later you confirm it.
6 |
7 | ## User Model
8 |
9 | Add the `CanConfirm` trait and `Confirmable` interface to your User model.
10 |
11 | ```php
12 | use Bavix\Wallet\Interfaces\Confirmable;
13 | use Bavix\Wallet\Interfaces\Wallet;
14 | use Bavix\Wallet\Traits\CanConfirm;
15 | use Bavix\Wallet\Traits\HasWallet;
16 |
17 | class UserConfirm extends Model implements Wallet, Confirmable
18 | {
19 | use HasWallet, CanConfirm;
20 | }
21 | ```
22 |
23 | > You can only confirm the transaction with the wallet you paid with.
24 |
25 | ## To confirmation
26 |
27 | ### Example:
28 |
29 | Sometimes you need to create an operation and confirm its field.
30 | That is what this trey does.
31 |
32 | ```php
33 | $user->balance; // 0
34 | $transaction = $user->deposit(100, null, false); // not confirm
35 | $transaction->confirmed; // bool(false)
36 | $user->balance; // 0
37 |
38 | $user->confirm($transaction); // bool(true)
39 | $transaction->confirmed; // bool(true)
40 |
41 | $user->balance; // 100
42 | ```
43 |
44 | It's simple!
45 |
--------------------------------------------------------------------------------
/docs/guide/single/credit-limits.md:
--------------------------------------------------------------------------------
1 | # Credit Limits
2 |
3 | If you need the ability to have wallets have a credit limit, then this functionality is for you.
4 |
5 | The functionality does nothing, it only allows you not to use "force" for most of the operations within the credit limit. You should write the logic for collecting interest, notifications on debts, etc.
6 |
7 | By default, the credit limit is zero.
8 |
9 | An example of working with a credit limit:
10 | ```php
11 | /**
12 | * @var \Bavix\Wallet\Interfaces\Customer $customer
13 | * @var \Bavix\Wallet\Models\Wallet $wallet
14 | * @var \Bavix\Wallet\Interfaces\ProductInterface $product
15 | */
16 | $wallet = $customer->wallet; // get default wallet
17 | $wallet->meta['credit'] = 10000; // credit limit
18 | $wallet->save(); // update credit limit
19 |
20 | $wallet->balanceInt; // 0
21 | $product->getAmountProduct($customer); // 500
22 |
23 | $wallet->pay($product); // success
24 | $wallet->balanceInt; // -500
25 | ```
26 |
27 | For multi-wallets when creating:
28 | ```php
29 | /** @var \Bavix\Wallet\Traits\HasWallets $user */
30 | $wallet = $user->createWallet([
31 | 'name' => 'My Wallet',
32 | 'meta' => ['credit' => 500],
33 | ]);
34 | ```
35 |
36 | It's simple!
37 |
--------------------------------------------------------------------------------
/docs/guide/single/deposit.md:
--------------------------------------------------------------------------------
1 | # Deposit
2 |
3 | A deposit is a sum of money which is part of the full price of something,
4 | and which you pay when you agree to buy it.
5 |
6 | In this case, the Deposit is the replenishment of the wallet.
7 |
8 | ## User Model
9 |
10 |
11 |
12 | ## Make a Deposit
13 |
14 | Find user:
15 |
16 | ```php
17 | $user = User::first();
18 | ```
19 |
20 | As the user uses `HasWallet`, he will have `balance` property.
21 | Check the user's balance.
22 |
23 | ```php
24 | $user->balance; // 0
25 | $user->balanceInt; // 0
26 | ```
27 |
28 | The balance is zero, which is what we expected.
29 | Put it on his 10 cents account.
30 |
31 | ```php
32 | $user->deposit(10);
33 | $user->balance; // 10
34 | $user->balanceInt; // 10
35 | ```
36 |
37 | Wow! The balance is 10 cents, the money is credited.
38 |
--------------------------------------------------------------------------------
/docs/guide/single/refresh.md:
--------------------------------------------------------------------------------
1 | # To refresh the balance
2 |
3 | There are situations when you create a lot of unconfirmed operations,
4 | and then abruptly confirm everything.
5 | In this case, the user's balance will not change.
6 | You must be forced to refresh the balance.
7 |
8 | ## User Model
9 |
10 |
11 |
12 | ## Get the current balance for your wallet
13 |
14 | Let's say the user's balance
15 |
16 | ```php
17 | $user->id; // 5
18 | $user->balance; // 27
19 | ```
20 |
21 | And he has unconfirmed transactions.
22 | Confirm all transactions.
23 |
24 | ```sql
25 | update transactions
26 | set confirmed=1
27 | where confirmed=0 and
28 | payable_type='App\Models\User' and
29 | payable_id=5;
30 | -- 212 rows affected in 54 ms
31 | ```
32 |
33 | Refresh the balance.
34 |
35 | ```php
36 | $user->balance; // 27
37 | $user->wallet->refreshBalance();
38 | $user->balance; // 42
39 | ```
40 |
41 | It's simple!
42 |
--------------------------------------------------------------------------------
/docs/guide/single/transfer.md:
--------------------------------------------------------------------------------
1 | # Transfer
2 |
3 | Transfer in our system are two well-known [Deposit](deposit.md) and [Withdraw](withdraw.md)
4 | operations that are performed in one transaction.
5 |
6 | The transfer takes place between wallets.
7 |
8 | ## User Model
9 |
10 |
11 |
12 | ### Example contract
13 |
14 | ```php
15 | $transfer = $user1->transfer(
16 | $user2,
17 | 511,
18 | new Extra(
19 | deposit: [
20 | 'type' => 'extra-deposit',
21 | ],
22 | withdraw: new Option(
23 | [
24 | 'type' => 'extra-withdraw',
25 | ],
26 | false // confirmed
27 | ),
28 | extra: [
29 | 'msg' => 'hello world',
30 | ],
31 | )
32 | );
33 | ```
34 |
35 | ## Make a Transfer
36 |
37 | Find user:
38 |
39 | ```php
40 | $first = User::first();
41 | $last = User::orderBy('id', 'desc')->first(); // last user
42 | $first->getKey() !== $last->getKey(); // true
43 | ```
44 |
45 | As the user uses `HasWallet`, he will have `balance` property.
46 | Check the user's balance.
47 |
48 | ```php
49 | $first->balance; // 100
50 | $last->balance; // 0
51 | ```
52 |
53 | The transfer will be from the first user to the second.
54 |
55 | ```php
56 | $first->transfer($last, 5);
57 | $first->balance; // 95
58 | $last->balance; // 5
59 | ```
60 |
61 | It's simple!
62 |
63 | ## Force Transfer
64 |
65 | Check the user's balance.
66 |
67 | ```php
68 | $first->balance; // 100
69 | $last->balance; // 0
70 | ```
71 |
72 | The transfer will be from the first user to the second.
73 |
74 | ```php
75 | $first->forceTransfer($last, 500);
76 | $first->balance; // -400
77 | $last->balance; // 500
78 | ```
79 |
80 | It's simple!
81 |
--------------------------------------------------------------------------------
/docs/guide/single/withdraw.md:
--------------------------------------------------------------------------------
1 | # Withdraw
2 |
3 | When there is enough money in the account, you can transfer/withdraw
4 | it or buy something in the system.
5 |
6 | Since the currency is virtual, you can buy any services on your website.
7 | For example, priority in search results.
8 |
9 | ## User Model
10 |
11 |
12 |
13 | ## Make a Withdraw
14 |
15 | Find user:
16 |
17 | ```php
18 | $user = User::first();
19 | ```
20 |
21 | As the user uses `HasWallet`, he will have `balance` property.
22 | Check the user's balance.
23 |
24 | ```php
25 | $user->balance; // 100
26 | $user->balanceInt; // 100
27 | ```
28 |
29 | The balance is not empty, so you can withdraw funds.
30 |
31 | ```php
32 | $user->withdraw(10);
33 | $user->balance; // 90
34 | $user->balanceInt; // 90
35 | ```
36 |
37 | It's simple!
38 |
39 | ## Force Withdraw
40 |
41 | Forced withdrawal is necessary for those cases when
42 | the user has no funds. For example, a fine for spam.
43 |
44 | ```php
45 | $user->balance; // 100
46 | $user->balanceInt; // 100
47 | $user->forceWithdraw(101);
48 | $user->balance; // -1
49 | $user->balanceInt; // -1
50 | ```
51 |
52 | ## And what will happen if the money is not enough?
53 |
54 | There can be two situations:
55 |
56 | - The user's balance is zero, then we get an error
57 | `Bavix\Wallet\Exceptions\BalanceIsEmpty`
58 | - If the balance is greater than zero, but it is not enough
59 | `Bavix\Wallet\Exceptions\InsufficientFunds`
60 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Laravel Wallet"
7 | text: It's simple!
8 | tagline: It's easy to work with a virtual wallet
9 | image:
10 | src: https://github.com/bavix/laravel-wallet/assets/5111255/24cf424a-0177-4712-b74c-52b4ba88c428
11 | alt: Laravel Wallet
12 | actions:
13 | - theme: brand
14 | text: Getting started
15 | link: /guide/introduction/
16 | - theme: alt
17 | text: Upgrade Guide
18 | link: /guide/introduction/upgrade
19 |
20 | features:
21 | - title: Default Wallet
22 | details: For simple projects when there is no need for multiple wallets.
23 | icon: 💰
24 | link: /guide/single/deposit
25 | - title: Multi wallets
26 | details: Many wallets for one model. Easy API.
27 | icon: 🎒
28 | link: /guide/multi/new-wallet
29 | - title: Purchases
30 | details: E-commerce. Create goods and buy them using wallets. There are also shopping carts, availability, taxes and fees.
31 | icon: 🛍️
32 | link: /guide/purchases/payment
33 | - title: Exchanges
34 | details: Exchanges between wallets. Convert currency from one wallet to another.
35 | icon: 💱
36 | link: /guide/single/exchange
37 | - title: Support UUID
38 | details: Models with UUID are supported.
39 | icon: ❄️
40 | link: /guide/additions/uuid
41 | - title: Events
42 | details: For more complex projects there are events and high performance API.
43 | icon: 📻
44 | link: /guide/events/balance-updated-event
45 | ---
46 |
47 |
--------------------------------------------------------------------------------
/ecs.php:
--------------------------------------------------------------------------------
1 | paths([
11 | __DIR__ . '/config',
12 | __DIR__ . '/database',
13 | __DIR__ . '/resources/lang',
14 | __DIR__ . '/src',
15 | __DIR__ . '/tests',
16 | ]);
17 |
18 | $config->skip([
19 | GeneralPhpdocAnnotationRemoveFixer::class,
20 | \PhpCsFixer\Fixer\Import\FullyQualifiedStrictTypesFixer::class,
21 | ]);
22 |
23 | $config->sets([
24 | SetList::CLEAN_CODE,
25 | SetList::SYMPLIFY,
26 | SetList::ARRAY,
27 | SetList::COMMON,
28 | SetList::PSR_12,
29 | SetList::CONTROL_STRUCTURES,
30 | SetList::NAMESPACES,
31 | SetList::STRICT,
32 | SetList::PHPUNIT,
33 | SetList::LARAVEL,
34 | ]);
35 | };
36 |
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "timeout": 2,
3 | "source": {
4 | "directories": [
5 | "src"
6 | ]
7 | },
8 | "logs": {
9 | "text": "build/infection.log",
10 | "stryker": {
11 | "badge": "master"
12 | }
13 | },
14 | "mutators": {
15 | "@default": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel-wallet",
3 | "version": "2.0.0",
4 | "description": "Package documentation",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/bavix/laravel-wallet.git"
8 | },
9 | "author": "Maxim Babichev",
10 | "license": "MIT",
11 | "bugs": {
12 | "url": "https://github.com/bavix/laravel-wallet/issues"
13 | },
14 | "homepage": "https://bavix.github.io/laravel-wallet/",
15 | "scripts": {
16 | "docs:dev": "vitepress dev docs",
17 | "docs:build": "vitepress build docs",
18 | "docs:preview": "vitepress preview docs",
19 | "build": "vitepress build docs"
20 | },
21 | "dependencies": {
22 | "vitepress": "^1.6.3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/phpstan.common.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/larastan/larastan/extension.neon
3 | - vendor/ergebnis/phpstan-rules/rules.neon
4 |
5 | parameters:
6 | level: 9
7 | reportUnmatchedIgnoredErrors: false
8 | fileExtensions:
9 | - php
10 |
--------------------------------------------------------------------------------
/phpstan.src.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan.common.neon
3 | - phpstan.src.baseline.neon
4 |
5 | parameters:
6 | level: 9
7 | fileExtensions:
8 | - php
9 | ergebnis:
10 | noParameterWithNullableTypeDeclaration:
11 | enabled: false
12 | noNullableReturnTypeDeclaration:
13 | enabled: false
14 | noParameterWithNullDefaultValue:
15 | enabled: false
16 | final:
17 | allowAbstractClasses: true
18 | classesNotRequiredToBeAbstractOrFinal:
19 | - Bavix\Wallet\Models\Wallet
20 | - Bavix\Wallet\Models\Transfer
21 | - Bavix\Wallet\Models\Transaction
22 | noExtends:
23 | classesAllowedToBeExtended:
24 | # laravel
25 | - Illuminate\Support\ServiceProvider
26 | - Illuminate\Database\Eloquent\Model
27 |
28 | # php exceptions
29 | - LogicException
30 | - RuntimeException
31 | - UnderflowException
32 | - UnexpectedValueException
33 | - InvalidArgumentException
34 | noParameterWithContainerTypeDeclaration:
35 | interfacesImplementedByContainers:
36 | - Psr\Container\ContainerInterface
37 | paths:
38 | - src/
--------------------------------------------------------------------------------
/phpstan.tests.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan.common.neon
3 | - phpstan.tests.baseline.neon
4 |
5 | parameters:
6 | level: 9
7 | ignoreErrors:
8 | - '#^Offset \d+ does not exist on.+Collection.+int.+$#'
9 | fileExtensions:
10 | - php
11 | ergebnis:
12 | noParameterWithNullableTypeDeclaration:
13 | enabled: false
14 | noNullableReturnTypeDeclaration:
15 | enabled: false
16 | noParameterWithNullDefaultValue:
17 | enabled: false
18 | final:
19 | allowAbstractClasses: false
20 | classesNotRequiredToBeAbstractOrFinal:
21 | - Bavix\Wallet\Models\Wallet
22 | - Bavix\Wallet\Models\Transfer
23 | - Bavix\Wallet\Models\Transaction
24 | noExtends:
25 | classesAllowedToBeExtended:
26 | # laravel
27 | - Illuminate\Support\ServiceProvider
28 | - Illuminate\Database\Eloquent\Model
29 | - Illuminate\Database\Migrations\Migration
30 | - Illuminate\Database\Eloquent\Factories\Factory
31 |
32 | # php exceptions
33 | - RuntimeException
34 | - InvalidArgumentException
35 |
36 | # phpunit
37 | - Orchestra\Testbench\TestCase
38 | - Bavix\Wallet\Test\Infra\TestCase
39 |
40 | # wallet
41 | - Bavix\Wallet\Models\Wallet
42 | - Bavix\Wallet\Models\Transfer
43 | - Bavix\Wallet\Models\Transaction
44 |
45 | noParameterWithContainerTypeDeclaration:
46 | interfacesImplementedByContainers:
47 | - Psr\Container\ContainerInterface
48 | paths:
49 | - tests/
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | tests
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ./src/
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | parallel();
14 | $config->paths([
15 | __DIR__ . '/src',
16 | __DIR__ . '/tests',
17 | ]);
18 |
19 | // remove it in next version
20 | $config->skip([
21 | MigrateToSimplifiedAttributeRector::class,
22 | MinutesToSecondsInCacheRector::class,
23 | RemoveDeadZeroAndOneOperationRector::class,
24 | ]);
25 |
26 | // Define what rule sets will be applied
27 | $config->import(LaravelLevelSetList::UP_TO_LARAVEL_110);
28 | $config->import(SetList::STRICT_BOOLEANS);
29 | $config->import(SetList::PRIVATIZATION);
30 | $config->import(SetList::EARLY_RETURN);
31 | $config->import(SetList::INSTANCEOF);
32 | $config->import(SetList::CODE_QUALITY);
33 | $config->import(SetList::DEAD_CODE);
34 | $config->import(SetList::PHP_82);
35 | };
36 |
--------------------------------------------------------------------------------
/resources/lang/ar/errors.php:
--------------------------------------------------------------------------------
1 | 'يجب أن يكون السعر موجبًا',
7 | 'product_stock' => 'المنتج غير متوفر',
8 | 'wallet_empty' => 'المحفظة فارغة',
9 | 'insufficient_funds' => 'الأموال غير الكافية',
10 | 'confirmed_invalid' => 'تم التأكيد بالفعل',
11 | 'unconfirmed_invalid' => 'التأكيد تمت إعادة تعيينه بالفعل',
12 | 'owner_invalid' => 'أنت لست مالك المحفظة',
13 | ];
14 |
--------------------------------------------------------------------------------
/resources/lang/en/errors.php:
--------------------------------------------------------------------------------
1 | 'The price should be positive',
7 | 'product_stock' => 'The product is out of stock',
8 | 'wallet_empty' => 'Wallet is empty',
9 | 'insufficient_funds' => 'Insufficient funds',
10 | 'confirmed_invalid' => 'The transaction has already been confirmed',
11 | 'unconfirmed_invalid' => 'Confirmation has already been reset',
12 | 'owner_invalid' => 'You are not the owner of the wallet',
13 | ];
14 |
--------------------------------------------------------------------------------
/resources/lang/es/errors.php:
--------------------------------------------------------------------------------
1 | 'El precio debe ser un valor positivo',
7 | 'product_stock' => 'El producto no esta disponible en el inventario',
8 | 'wallet_empty' => 'La cartera esta vacía',
9 | 'insufficient_funds' => 'Fondos Insuficientes',
10 | 'confirmed_invalid' => 'La transferencia ya fue confirmada',
11 | 'unconfirmed_invalid' => 'La confirmación ya fue restablecida',
12 | 'owner_invalid' => 'No eres el propietario de esta cartera',
13 | ];
14 |
--------------------------------------------------------------------------------
/resources/lang/fa/errors.php:
--------------------------------------------------------------------------------
1 | 'قیمت باید عددی مثبت باشد',
7 | 'product_stock' => 'کالا موجود نیست',
8 | 'wallet_empty' => 'کیف پول خالی است',
9 | 'insufficient_funds' => 'موجودی ناکافی',
10 | 'confirmed_invalid' => 'تراکنش قبلا تایید شده است',
11 | 'unconfirmed_invalid' => 'تاییدیه تراکنش لغو شده است',
12 | 'owner_invalid' => 'این کیف پول متعلق به شما نیست',
13 | ];
14 |
--------------------------------------------------------------------------------
/resources/lang/ru/errors.php:
--------------------------------------------------------------------------------
1 | 'Цена должна быть положительной',
7 | 'product_stock' => 'Товар отсутствует',
8 | 'wallet_empty' => 'Кошелёк пуст',
9 | 'insufficient_funds' => 'Недостаточно средств',
10 | 'confirmed_invalid' => 'Перевод уже подтвержден',
11 | 'unconfirmed_invalid' => 'Подтверждение уже сброшено',
12 | 'owner_invalid' => 'Вы не владелец кошелька',
13 | ];
14 |
--------------------------------------------------------------------------------
/resources/lang/zh-CN/errors.php:
--------------------------------------------------------------------------------
1 | '价格不能为负数',
7 | 'product_stock' => '库存不足',
8 | 'wallet_empty' => '钱包为空',
9 | 'insufficient_funds' => '余额不足',
10 | 'confirmed_invalid' => '翻译已经确认',
11 | 'unconfirmed_invalid' => '確認已被重置',
12 | 'owner_invalid' => '你不是钱包的主人',
13 | ];
14 |
--------------------------------------------------------------------------------
/src/Exceptions/AmountInvalid.php:
--------------------------------------------------------------------------------
1 | $objects
21 | * @return non-empty-array
22 | *
23 | * @throws ExceptionInterface
24 | */
25 | public function apply(array $objects): array;
26 | }
27 |
--------------------------------------------------------------------------------
/src/External/Api/TransactionQueryInterface.php:
--------------------------------------------------------------------------------
1 | |null
27 | */
28 | public function getMeta(): ?array;
29 |
30 | public function isConfirmed(): bool;
31 |
32 | public function getUuid(): ?string;
33 | }
34 |
--------------------------------------------------------------------------------
/src/External/Api/TransferFloatQuery.php:
--------------------------------------------------------------------------------
1 | |ExtraDtoInterface|null $meta
18 | */
19 | public function __construct(
20 | private Wallet $from,
21 | private Wallet $to,
22 | float|int|string $amount,
23 | private array|ExtraDtoInterface|null $meta
24 | ) {
25 | $walletModel = app(CastServiceInterface::class)->getWallet($from);
26 |
27 | $this->amount = app(FormatterServiceInterface::class)
28 | ->intValue($amount, $walletModel->decimal_places);
29 | }
30 |
31 | public function getFrom(): Wallet
32 | {
33 | return $this->from;
34 | }
35 |
36 | public function getTo(): Wallet
37 | {
38 | return $this->to;
39 | }
40 |
41 | public function getAmount(): string
42 | {
43 | return $this->amount;
44 | }
45 |
46 | /**
47 | * @return array|ExtraDtoInterface|null
48 | */
49 | public function getMeta(): array|ExtraDtoInterface|null
50 | {
51 | return $this->meta;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/External/Api/TransferQuery.php:
--------------------------------------------------------------------------------
1 | |ExtraDtoInterface|null $meta
14 | */
15 | public function __construct(
16 | private Wallet $from,
17 | private Wallet $to,
18 | private float|int|string $amount,
19 | private array|ExtraDtoInterface|null $meta
20 | ) {
21 | }
22 |
23 | public function getFrom(): Wallet
24 | {
25 | return $this->from;
26 | }
27 |
28 | public function getTo(): Wallet
29 | {
30 | return $this->to;
31 | }
32 |
33 | public function getAmount(): float|int|string
34 | {
35 | return $this->amount;
36 | }
37 |
38 | /**
39 | * @return array|ExtraDtoInterface|null
40 | */
41 | public function getMeta(): array|ExtraDtoInterface|null
42 | {
43 | return $this->meta;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/External/Api/TransferQueryHandler.php:
--------------------------------------------------------------------------------
1 | assistantService->getWallets(
30 | array_map(static fn (TransferQueryInterface $query): Wallet => $query->getFrom(), $objects),
31 | );
32 |
33 | $values = array_map(
34 | fn (TransferQueryInterface $query) => $this->prepareService->transferLazy(
35 | $query->getFrom(),
36 | $query->getTo(),
37 | Transfer::STATUS_TRANSFER,
38 | $query->getAmount(),
39 | $query->getMeta(),
40 | ),
41 | $objects
42 | );
43 |
44 | return $this->atomicService->blocks($wallets, fn () => $this->transferService->apply($values));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/External/Api/TransferQueryHandlerInterface.php:
--------------------------------------------------------------------------------
1 | $objects
21 | * @return non-empty-array
22 | *
23 | * @throws ExceptionInterface
24 | */
25 | public function apply(array $objects): array;
26 | }
27 |
--------------------------------------------------------------------------------
/src/External/Api/TransferQueryInterface.php:
--------------------------------------------------------------------------------
1 | |ExtraDtoInterface|null
20 | */
21 | public function getMeta(): array|ExtraDtoInterface|null;
22 | }
23 |
--------------------------------------------------------------------------------
/src/External/Contracts/ExtraDtoInterface.php:
--------------------------------------------------------------------------------
1 | |null
17 | */
18 | public function getExtra(): ?array;
19 | }
20 |
--------------------------------------------------------------------------------
/src/External/Contracts/OptionDtoInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | public function getMeta(): ?array;
13 |
14 | public function isConfirmed(): bool;
15 |
16 | public function getUuid(): ?string;
17 | }
18 |
--------------------------------------------------------------------------------
/src/External/Dto/Extra.php:
--------------------------------------------------------------------------------
1 | |null $deposit
18 | * @param OptionDtoInterface|array|null $withdraw
19 | * @param array|null $extra
20 | */
21 | public function __construct(
22 | OptionDtoInterface|array|null $deposit,
23 | OptionDtoInterface|array|null $withdraw,
24 | private ?string $uuid = null,
25 | private ?array $extra = null
26 | ) {
27 | $this->deposit = $deposit instanceof OptionDtoInterface ? $deposit : new Option($deposit);
28 | $this->withdraw = $withdraw instanceof OptionDtoInterface ? $withdraw : new Option($withdraw);
29 | }
30 |
31 | public function getDepositOption(): OptionDtoInterface
32 | {
33 | return $this->deposit;
34 | }
35 |
36 | public function getWithdrawOption(): OptionDtoInterface
37 | {
38 | return $this->withdraw;
39 | }
40 |
41 | public function getUuid(): ?string
42 | {
43 | return $this->uuid;
44 | }
45 |
46 | /**
47 | * @return array|null
48 | */
49 | public function getExtra(): ?array
50 | {
51 | return $this->extra;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/External/Dto/Option.php:
--------------------------------------------------------------------------------
1 | $meta
13 | */
14 | public function __construct(
15 | private ?array $meta,
16 | private bool $confirmed = true,
17 | private ?string $uuid = null
18 | ) {
19 | }
20 |
21 | /**
22 | * @return null|array
23 | */
24 | public function getMeta(): ?array
25 | {
26 | return $this->meta;
27 | }
28 |
29 | public function isConfirmed(): bool
30 | {
31 | return $this->confirmed;
32 | }
33 |
34 | public function getUuid(): ?string
35 | {
36 | return $this->uuid;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Interfaces/CartInterface.php:
--------------------------------------------------------------------------------
1 | |null The meta data for the product, or null if there is no meta data.
28 | *
29 | * @example
30 | * The return value should be an array with key-value pairs, for example:
31 | *
32 | * return [
33 | * 'title' => 'Product Title',
34 | * 'description' => 'Product Description',
35 | * 'images' => [
36 | * 'https://example.com/image1.jpg',
37 | * 'https://example.com/image2.jpg',
38 | * ],
39 | * ];
40 | */
41 | public function getMetaProduct(): ?array;
42 | }
43 |
--------------------------------------------------------------------------------
/src/Interfaces/ProductLimitedInterface.php:
--------------------------------------------------------------------------------
1 | getKey(),
23 | $wallet->uuid,
24 | $wallet->getOriginalBalanceAttribute(),
25 | $this->clockService->now()
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/BalanceUpdatedEventAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | optionDtoAssembler->create($data);
24 |
25 | return new Extra($option, $option, null);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/ExtraDtoAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | |null $data
15 | * The data to create ExtraDto from. Can be either ExtraDtoInterface object, array or null.
16 | * @return ExtraDtoInterface
17 | * The created ExtraDto.
18 | */
19 | public function create(ExtraDtoInterface|array|null $data): ExtraDtoInterface;
20 | }
21 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/OptionDtoAssembler.php:
--------------------------------------------------------------------------------
1 | |null $data The data to create the OptionDto from.
15 | * This can be null, in which case an empty
16 | * OptionDto object will be created.
17 | * @return OptionDtoInterface The created OptionDto object.
18 | */
19 | public function create(array|null $data): OptionDtoInterface;
20 | }
21 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransactionCreatedEventAssembler.php:
--------------------------------------------------------------------------------
1 | getKey(),
23 | $transaction->type,
24 | $transaction->wallet_id,
25 | $this->clockService->now(),
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransactionCreatedEventAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | identifierFactoryService->generate(),
32 | $payable->getMorphClass(),
33 | $payable->getKey(),
34 | $walletId,
35 | $type,
36 | $amount,
37 | $confirmed,
38 | $meta,
39 | $this->clockService->now(),
40 | $this->clockService->now(),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransactionDtoAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | $meta
16 | */
17 | public function create(
18 | Model $payable,
19 | int $walletId,
20 | string $type,
21 | float|int|string $amount,
22 | bool $confirmed,
23 | ?array $meta,
24 | ?string $uuid
25 | ): TransactionDtoInterface;
26 | }
27 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransactionQueryAssembler.php:
--------------------------------------------------------------------------------
1 | $uuids
14 | */
15 | public function create(array $uuids): TransactionQueryInterface
16 | {
17 | return new TransactionQuery($uuids);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransactionQueryAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | $uuids The uuids of the transactions.
15 | * @return TransactionQueryInterface The transaction query.
16 | */
17 | public function create(array $uuids): TransactionQueryInterface;
18 | }
19 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransferDtoAssembler.php:
--------------------------------------------------------------------------------
1 | |null $extra
23 | */
24 | public function create(
25 | int $depositId,
26 | int $withdrawId,
27 | string $status,
28 | Model $fromModel,
29 | Model $toModel,
30 | int $discount,
31 | string $fee,
32 | ?string $uuid,
33 | ?array $extra,
34 | ): TransferDtoInterface {
35 | return new TransferDto(
36 | $uuid ?? $this->identifierFactoryService->generate(),
37 | $depositId,
38 | $withdrawId,
39 | $status,
40 | $fromModel->getKey(),
41 | $toModel->getKey(),
42 | $discount,
43 | $fee,
44 | $extra,
45 | $this->clockService->now(),
46 | $this->clockService->now(),
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransferDtoAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | |null $extra Extra data of transfer
24 | */
25 | public function create(
26 | int $depositId,
27 | int $withdrawId,
28 | string $status,
29 | Model $fromModel,
30 | Model $toModel,
31 | int $discount,
32 | string $fee,
33 | ?string $uuid,
34 | ?array $extra,
35 | ): TransferDtoInterface;
36 | }
37 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransferLazyDtoAssembler.php:
--------------------------------------------------------------------------------
1 | |null $extra
16 | */
17 | public function create(
18 | Wallet $fromWallet,
19 | Wallet $toWallet,
20 | int $discount,
21 | string $fee,
22 | TransactionDtoInterface $withdrawDto,
23 | TransactionDtoInterface $depositDto,
24 | string $status,
25 | ?string $uuid,
26 | ?array $extra,
27 | ): TransferLazyDtoInterface {
28 | return new TransferLazyDto(
29 | $fromWallet,
30 | $toWallet,
31 | $discount,
32 | $fee,
33 | $withdrawDto,
34 | $depositDto,
35 | $status,
36 | $uuid,
37 | $extra,
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransferLazyDtoAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | |null $extra The extra data.
25 | * @return TransferLazyDtoInterface The transfer lazy DTO.
26 | */
27 | public function create(
28 | Wallet $fromWallet,
29 | Wallet $toWallet,
30 | int $discount,
31 | string $fee,
32 | TransactionDtoInterface $withdrawDto,
33 | TransactionDtoInterface $depositDto,
34 | string $status,
35 | ?string $uuid,
36 | ?array $extra,
37 | ): TransferLazyDtoInterface;
38 | }
39 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransferQueryAssembler.php:
--------------------------------------------------------------------------------
1 | $uuids
14 | */
15 | public function create(array $uuids): TransferQueryInterface
16 | {
17 | return new TransferQuery($uuids);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/TransferQueryAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | $uuids The UUIDs of the transfers.
15 | * @return TransferQueryInterface The newly created TransferQuery object.
16 | */
17 | public function create(array $uuids): TransferQueryInterface;
18 | }
19 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/WalletCreatedEventAssembler.php:
--------------------------------------------------------------------------------
1 | holder_type,
23 | $wallet->holder_id,
24 | $wallet->uuid,
25 | $wallet->getKey(),
26 | $this->clockService->now()
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Internal/Assembler/WalletCreatedEventAssemblerInterface.php:
--------------------------------------------------------------------------------
1 | basketDto;
22 | }
23 |
24 | public function getCustomer(): Customer
25 | {
26 | return $this->customer;
27 | }
28 |
29 | public function isForce(): bool
30 | {
31 | return $this->force;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Internal/Dto/AvailabilityDtoInterface.php:
--------------------------------------------------------------------------------
1 | $items
14 | * @param array $meta
15 | * @param array|null $extra
16 | */
17 | public function __construct(
18 | private array $items,
19 | private array $meta,
20 | private ?array $extra,
21 | ) {
22 | }
23 |
24 | public function meta(): array
25 | {
26 | return $this->meta;
27 | }
28 |
29 | public function count(): int
30 | {
31 | return count($this->items);
32 | }
33 |
34 | public function total(): int
35 | {
36 | return iterator_count($this->cursor());
37 | }
38 |
39 | /**
40 | * @return Generator
41 | */
42 | public function cursor(): Generator
43 | {
44 | foreach ($this->items as $item) {
45 | foreach ($item->getItems() as $product) {
46 | yield $product;
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * @return non-empty-array
53 | */
54 | public function items(): array
55 | {
56 | return $this->items;
57 | }
58 |
59 | /**
60 | * @return array|null
61 | */
62 | public function extra(): ?array
63 | {
64 | return $this->extra;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Internal/Dto/BasketDtoInterface.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | public function meta(): array;
24 |
25 | /**
26 | * Retrieve the extra data of the basket.
27 | *
28 | * @return array|null
29 | */
30 | public function extra(): ?array;
31 |
32 | /**
33 | * Retrieve the items of the basket.
34 | *
35 | * @return non-empty-array
36 | */
37 | public function items(): array;
38 |
39 | /**
40 | * Retrieve the generator for the items of the basket.
41 | *
42 | * @return Generator
43 | */
44 | public function cursor(): Generator;
45 | }
46 |
--------------------------------------------------------------------------------
/src/Internal/Dto/ItemDto.php:
--------------------------------------------------------------------------------
1 | quantity, $this->product);
27 | }
28 |
29 | public function getPricePerItem(): int|string|null
30 | {
31 | return $this->pricePerItem;
32 | }
33 |
34 | public function getProduct(): ProductInterface
35 | {
36 | return $this->product;
37 | }
38 |
39 | public function count(): int
40 | {
41 | return $this->quantity;
42 | }
43 |
44 | public function getReceiving(): ?Wallet
45 | {
46 | return $this->receiving;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Internal/Dto/ItemDtoInterface.php:
--------------------------------------------------------------------------------
1 |
56 | */
57 | public function getMeta(): ?array;
58 |
59 | /**
60 | * Get the created at timestamp of the transaction.
61 | */
62 | public function getCreatedAt(): DateTimeImmutable;
63 |
64 | /**
65 | * Get the updated at timestamp of the transaction.
66 | */
67 | public function getUpdatedAt(): DateTimeImmutable;
68 | }
69 |
--------------------------------------------------------------------------------
/src/Internal/Dto/TransferDtoInterface.php:
--------------------------------------------------------------------------------
1 | |null
59 | */
60 | public function getExtra(): ?array;
61 |
62 | /**
63 | * Get the created at timestamp of the transfer.
64 | */
65 | public function getCreatedAt(): DateTimeImmutable;
66 |
67 | /**
68 | * Get the updated at timestamp of the transfer.
69 | */
70 | public function getUpdatedAt(): DateTimeImmutable;
71 | }
72 |
--------------------------------------------------------------------------------
/src/Internal/Dto/TransferLazyDto.php:
--------------------------------------------------------------------------------
1 | |null $extra
14 | */
15 | public function __construct(
16 | private Wallet $fromWallet,
17 | private Wallet $toWallet,
18 | private int $discount,
19 | private string $fee,
20 | private TransactionDtoInterface $withdrawDto,
21 | private TransactionDtoInterface $depositDto,
22 | private string $status,
23 | private ?string $uuid,
24 | private ?array $extra,
25 | ) {
26 | }
27 |
28 | public function getFromWallet(): Wallet
29 | {
30 | return $this->fromWallet;
31 | }
32 |
33 | public function getToWallet(): Wallet
34 | {
35 | return $this->toWallet;
36 | }
37 |
38 | public function getDiscount(): int
39 | {
40 | return $this->discount;
41 | }
42 |
43 | public function getFee(): string
44 | {
45 | return $this->fee;
46 | }
47 |
48 | public function getWithdrawDto(): TransactionDtoInterface
49 | {
50 | return $this->withdrawDto;
51 | }
52 |
53 | public function getDepositDto(): TransactionDtoInterface
54 | {
55 | return $this->depositDto;
56 | }
57 |
58 | public function getStatus(): string
59 | {
60 | return $this->status;
61 | }
62 |
63 | public function getUuid(): ?string
64 | {
65 | return $this->uuid;
66 | }
67 |
68 | /**
69 | * @return array|null
70 | */
71 | public function getExtra(): ?array
72 | {
73 | return $this->extra;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Internal/Dto/TransferLazyDtoInterface.php:
--------------------------------------------------------------------------------
1 | |null
59 | */
60 | public function getExtra(): ?array;
61 | }
62 |
--------------------------------------------------------------------------------
/src/Internal/Events/BalanceUpdatedEvent.php:
--------------------------------------------------------------------------------
1 | walletId;
22 | }
23 |
24 | public function getWalletUuid(): string
25 | {
26 | return $this->walletUuid;
27 | }
28 |
29 | public function getBalance(): string
30 | {
31 | return $this->balance;
32 | }
33 |
34 | public function getUpdatedAt(): DateTimeImmutable
35 | {
36 | return $this->updatedAt;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Internal/Events/BalanceUpdatedEventInterface.php:
--------------------------------------------------------------------------------
1 | id;
22 | }
23 |
24 | public function getType(): string
25 | {
26 | return $this->type;
27 | }
28 |
29 | public function getWalletId(): int
30 | {
31 | return $this->walletId;
32 | }
33 |
34 | public function getCreatedAt(): DateTimeImmutable
35 | {
36 | return $this->createdAt;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Internal/Events/TransactionCreatedEventInterface.php:
--------------------------------------------------------------------------------
1 | holderType;
23 | }
24 |
25 | public function getHolderId(): int|string
26 | {
27 | return $this->holderId;
28 | }
29 |
30 | public function getWalletUuid(): string
31 | {
32 | return $this->walletUuid;
33 | }
34 |
35 | public function getWalletId(): int
36 | {
37 | return $this->walletId;
38 | }
39 |
40 | public function getCreatedAt(): DateTimeImmutable
41 | {
42 | return $this->createdAt;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Internal/Events/WalletCreatedEventInterface.php:
--------------------------------------------------------------------------------
1 | $missingKeys
13 | */
14 | public function __construct(
15 | string $message,
16 | int $code,
17 | private readonly array $missingKeys
18 | ) {
19 | parent::__construct($message, $code);
20 | }
21 |
22 | /**
23 | * @return non-empty-array
24 | */
25 | public function getMissingKeys(): array
26 | {
27 | return $this->missingKeys;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Internal/Exceptions/RuntimeExceptionInterface.php:
--------------------------------------------------------------------------------
1 | result;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Internal/Exceptions/UnderflowExceptionInterface.php:
--------------------------------------------------------------------------------
1 | get()->transactionLevel();
20 |
21 | // If the transaction level is 1, it means it is the top level of a transaction
22 | if ($transactionLevel === 1) {
23 | // Get the regulator service instance
24 | /** @var RegulatorServiceInterface $regulatorService */
25 | $regulatorService = app(RegulatorServiceInterface::class);
26 |
27 | // Purge all transactions and transfers
28 | // This method is called to ensure that all changes made to the database within the transaction
29 | // are reflected in the wallet's balance. It is important to note that this action is not reversible
30 | // and data loss is possible.
31 | $regulatorService->purge();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Internal/Query/TransactionQuery.php:
--------------------------------------------------------------------------------
1 | $uuids
16 | */
17 | public function __construct(
18 | private array $uuids
19 | ) {
20 | }
21 |
22 | /**
23 | * @return non-empty-array
24 | */
25 | public function getUuids(): array
26 | {
27 | return $this->uuids;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Internal/Query/TransactionQueryInterface.php:
--------------------------------------------------------------------------------
1 | An array of transaction UUIDs.
15 | */
16 | public function getUuids(): array;
17 | }
18 |
--------------------------------------------------------------------------------
/src/Internal/Query/TransferQuery.php:
--------------------------------------------------------------------------------
1 | $uuids
16 | */
17 | public function __construct(
18 | private array $uuids
19 | ) {
20 | }
21 |
22 | /**
23 | * @return non-empty-array
24 | */
25 | public function getUuids(): array
26 | {
27 | return $this->uuids;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Internal/Query/TransferQueryInterface.php:
--------------------------------------------------------------------------------
1 | An array of transfer UUIDs.
15 | */
16 | public function getUuids(): array;
17 | }
18 |
--------------------------------------------------------------------------------
/src/Internal/Repository/TransactionRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | $objects The array of transaction objects to insert.
17 | */
18 | public function insert(array $objects): void;
19 |
20 | /**
21 | * Inserts a single transaction into the repository.
22 | *
23 | * @param TransactionDtoInterface $dto The transaction object to insert.
24 | * @return Transaction The inserted transaction object.
25 | */
26 | public function insertOne(TransactionDtoInterface $dto): Transaction;
27 |
28 | /**
29 | * Retrieves transactions from the repository based on the given query.
30 | *
31 | * @param TransactionQueryInterface $query The query to filter the transactions.
32 | * @return Transaction[] An array of transactions that match the query.
33 | */
34 | public function findBy(TransactionQueryInterface $query): array;
35 | }
36 |
--------------------------------------------------------------------------------
/src/Internal/Service/ClockService.php:
--------------------------------------------------------------------------------
1 | connection = $connectionResolver->connection(config('wallet.database.connection'));
20 | }
21 |
22 | public function get(): ConnectionInterface
23 | {
24 | return $this->connection;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Internal/Service/ConnectionServiceInterface.php:
--------------------------------------------------------------------------------
1 | get();
30 | *
31 | * // Run a query on the "users" table.
32 | * * $connection->table('users')->where('id', 1)->update(['name' => 'Jane']);
33 | *
34 | * // Start a database transaction.
35 | * $connection->beginTransaction();
36 | *
37 | * try {
38 | * // Run queries...
39 | *
40 | * // Commit the transaction.
41 | * $connection->commit();
42 | * } catch (Exception $e) {
43 | * // Rollback the transaction.
44 | * $connection->rollback();
45 | *
46 | * // Rethrow the exception.
47 | * throw $e;
48 | * }
49 | */
50 | public function get(): ConnectionInterface;
51 | }
52 |
--------------------------------------------------------------------------------
/src/Internal/Service/DatabaseServiceInterface.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | private array $events = [];
16 |
17 | public function __construct(
18 | private readonly Dispatcher $dispatcher,
19 | private readonly ConnectionServiceInterface $connectionService
20 | ) {
21 | }
22 |
23 | public function dispatch(EventInterface $event): void
24 | {
25 | $this->events[$event::class] = true;
26 | $this->dispatcher->push($event::class, [$event]);
27 | }
28 |
29 | public function flush(): void
30 | {
31 | foreach (array_keys($this->events) as $event) {
32 | $this->dispatcher->flush($event);
33 | }
34 |
35 | $this->dispatcher->forgetPushed();
36 | $this->events = [];
37 | }
38 |
39 | public function forgot(): void
40 | {
41 | foreach (array_keys($this->events) as $event) {
42 | $this->dispatcher->forget($event);
43 | }
44 |
45 | $this->events = [];
46 | }
47 |
48 | public function lazyFlush(): void
49 | {
50 | if ($this->connectionService->get()->transactionLevel() === 0) {
51 | $this->flush();
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Internal/Service/DispatcherServiceInterface.php:
--------------------------------------------------------------------------------
1 | uuidFactory->uuid7($this->clockService->now())
41 | ->toString();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Internal/Service/IdentifierFactoryServiceInterface.php:
--------------------------------------------------------------------------------
1 | |null $data The data to encode. If null, returns null.
13 | * @return string|null The JSON encoded string, or null if the input is null.
14 | *
15 | * @note The input data is expected to be an array of mixed type data.
16 | * If the input is not an array, it will be converted to an array
17 | * before being encoded.
18 | *
19 | * @see https://www.php.net/manual/en/function.json-encode.php
20 | * @see https://www.php.net/manual/en/json.constants.php
21 | * @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
22 | */
23 | public function encode(?array $data): ?string;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Internal/Service/LockServiceInterface.php:
--------------------------------------------------------------------------------
1 | translator->get($key);
19 | assert(is_string($value));
20 |
21 | return $value;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Internal/Service/TranslatorServiceInterface.php:
--------------------------------------------------------------------------------
1 | uuidFactory->uuid4()
22 | ->toString();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Internal/Service/UuidFactoryServiceInterface.php:
--------------------------------------------------------------------------------
1 | $dto->getUuid(),
15 | 'payable_type' => $dto->getPayableType(),
16 | 'payable_id' => $dto->getPayableId(),
17 | 'wallet_id' => $dto->getWalletId(),
18 | 'type' => $dto->getType(),
19 | 'amount' => $dto->getAmount(),
20 | 'confirmed' => $dto->isConfirmed(),
21 | 'meta' => $dto->getMeta(),
22 | 'created_at' => $dto->getCreatedAt(),
23 | 'updated_at' => $dto->getUpdatedAt(),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Internal/Transform/TransactionDtoTransformerInterface.php:
--------------------------------------------------------------------------------
1 | |null,
22 | * created_at: DateTimeImmutable,
23 | * updated_at: DateTimeImmutable,
24 | * }
25 | */
26 | public function extract(TransactionDtoInterface $dto): array;
27 | }
28 |
--------------------------------------------------------------------------------
/src/Internal/Transform/TransferDtoTransformer.php:
--------------------------------------------------------------------------------
1 | $dto->getUuid(),
15 | 'deposit_id' => $dto->getDepositId(),
16 | 'withdraw_id' => $dto->getWithdrawId(),
17 | 'status' => $dto->getStatus(),
18 | 'from_id' => $dto->getFromId(),
19 | 'to_id' => $dto->getToId(),
20 | 'discount' => $dto->getDiscount(),
21 | 'fee' => $dto->getFee(),
22 | 'extra' => $dto->getExtra(),
23 | 'created_at' => $dto->getCreatedAt(),
24 | 'updated_at' => $dto->getUpdatedAt(),
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Internal/Transform/TransferDtoTransformerInterface.php:
--------------------------------------------------------------------------------
1 | |null,
23 | * created_at: DateTimeImmutable,
24 | * updated_at: DateTimeImmutable,
25 | * }
26 | */
27 | public function extract(TransferDtoInterface $dto): array;
28 | }
29 |
--------------------------------------------------------------------------------
/src/Services/BasketService.php:
--------------------------------------------------------------------------------
1 | getBasketDto();
18 | $customer = $availabilityDto->getCustomer();
19 | foreach ($basketDto->items() as $itemDto) {
20 | $product = $itemDto->getProduct();
21 | if ($product instanceof ProductLimitedInterface && ! $product->canBuy(
22 | $customer,
23 | $itemDto->count(),
24 | $availabilityDto->isForce()
25 | )) {
26 | return false;
27 | }
28 | }
29 |
30 | return true;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Services/BasketServiceInterface.php:
--------------------------------------------------------------------------------
1 | getPersonalDiscount($customer);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Services/DiscountServiceInterface.php:
--------------------------------------------------------------------------------
1 | power($decimalPlaces)
19 | ->multipliedBy(BigDecimal::of($amount))
20 | ->toScale(0, RoundingMode::DOWN);
21 | }
22 |
23 | public function floatValue(string|int|float $amount, int $decimalPlaces): string
24 | {
25 | return (string) BigDecimal::ofUnscaledValue($amount, $decimalPlaces);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Services/FormatterServiceInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class BuyerFactory extends Factory
14 | {
15 | protected $model = Buyer::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemDiscountFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemDiscountFactory extends Factory
14 | {
15 | protected $model = ItemDiscount::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(200, 700),
23 | 'quantity' => random_int(10, 100),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemDiscountTaxFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemDiscountTaxFactory extends Factory
14 | {
15 | protected $model = ItemDiscountTax::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => 250,
23 | 'quantity' => 90,
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemFactory extends Factory
14 | {
15 | protected $model = Item::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(1, 100),
23 | 'quantity' => random_int(0, 10),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemMaxTaxFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemMaxTaxFactory extends Factory
14 | {
15 | protected $model = ItemMaxTax::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(1, 100),
23 | 'quantity' => random_int(0, 10),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemMetaFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemMetaFactory extends Factory
14 | {
15 | protected $model = ItemMeta::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(1, 100),
23 | 'quantity' => random_int(0, 10),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemMinTaxFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemMinTaxFactory extends Factory
14 | {
15 | protected $model = ItemMinTax::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(1, 100),
23 | 'quantity' => random_int(0, 10),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemMultiPriceFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemMultiPriceFactory extends Factory
14 | {
15 | protected $model = ItemMultiPrice::class;
16 |
17 | public function definition(): array
18 | {
19 | $priceUsd = random_int(100, 700);
20 |
21 | return [
22 | 'name' => fake()
23 | ->domainName,
24 | 'price' => -1,
25 | 'quantity' => random_int(10, 100),
26 | 'prices' => [
27 | 'USD' => $priceUsd,
28 | ],
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemTaxFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemTaxFactory extends Factory
14 | {
15 | protected $model = ItemTax::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(1, 100),
23 | 'quantity' => random_int(0, 10),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ItemWalletFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class ItemWalletFactory extends Factory
14 | {
15 | protected $model = ItemWallet::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->domainName,
22 | 'price' => random_int(1, 100),
23 | 'quantity' => random_int(0, 10),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/ManagerFactory.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | final class ManagerFactory extends Factory
15 | {
16 | protected $model = Manager::class;
17 |
18 | public function definition(): array
19 | {
20 | return [
21 | 'id' => Uuid::uuid4()->toString(),
22 | 'name' => fake()
23 | ->name,
24 | 'email' => fake()
25 | ->unique()
26 | ->safeEmail,
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/UserCashierFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class UserCashierFactory extends Factory
14 | {
15 | protected $model = UserCashier::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/UserConfirmFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class UserConfirmFactory extends Factory
14 | {
15 | protected $model = UserConfirm::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/UserDynamicFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class UserDynamicFactory extends Factory
14 | {
15 | protected $model = UserDynamic::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/UserFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class UserFactory extends Factory
14 | {
15 | protected $model = User::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/UserFloatFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class UserFloatFactory extends Factory
14 | {
15 | protected $model = UserFloat::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Factories/UserMultiFactory.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | final class UserMultiFactory extends Factory
14 | {
15 | protected $model = UserMulti::class;
16 |
17 | public function definition(): array
18 | {
19 | return [
20 | 'name' => fake()
21 | ->name,
22 | 'email' => fake()
23 | ->unique()
24 | ->safeEmail,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Infra/Helpers/Config.php:
--------------------------------------------------------------------------------
1 | getUpdatedAt()
17 | ->format(DateTimeInterface::ATOM),
18 | (int) $balanceChangedEvent->getBalance()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Infra/Listeners/BalanceUpdatedThrowIdListener.php:
--------------------------------------------------------------------------------
1 | getWalletUuid(),
16 | (int) $balanceChangedEvent->getBalance()
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Infra/Listeners/BalanceUpdatedThrowUuidListener.php:
--------------------------------------------------------------------------------
1 | getWalletUuid(),
16 | ((int) $balanceChangedEvent->getBalance()) + $balanceChangedEvent->getWalletId(),
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Infra/Listeners/TransactionCreatedThrowListener.php:
--------------------------------------------------------------------------------
1 | getType();
15 | $createdAt = $transactionCreatedEvent->getCreatedAt()
16 | ->format(\DateTimeInterface::ATOM);
17 |
18 | $message = hash('sha256', $type.$createdAt);
19 |
20 | throw new UnknownEventException($message, $transactionCreatedEvent->getId());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Infra/Listeners/WalletCreatedThrowListener.php:
--------------------------------------------------------------------------------
1 | getHolderType();
16 | $uuid = $walletCreatedEvent->getWalletUuid();
17 | $createdAt = $walletCreatedEvent->getCreatedAt()
18 | ->format(DateTimeInterface::ATOM);
19 |
20 | $message = hash('sha256', $holderType.$uuid.$createdAt);
21 | $code = $walletCreatedEvent->getWalletId() + (int) $walletCreatedEvent->getHolderId();
22 | assert($code > 1);
23 |
24 | throw new UnknownEventException($message, $code);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Infra/Models/Buyer.php:
--------------------------------------------------------------------------------
1 |
29 | */
30 | protected $fillable = ['name', 'quantity', 'price'];
31 |
32 | public function getTable(): string
33 | {
34 | return 'items';
35 | }
36 |
37 | public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
38 | {
39 | $result = $this->quantity >= $quantity;
40 |
41 | if ($force) {
42 | return $result;
43 | }
44 |
45 | return $result && ! $customer->paid($this) instanceof \Bavix\Wallet\Models\Transfer;
46 | }
47 |
48 | public function getAmountProduct(Customer $customer): int
49 | {
50 | /** @var Wallet $wallet */
51 | $wallet = app(CastService::class)->getWallet($customer);
52 |
53 | return $this->price + (int) $wallet->holder_id;
54 | }
55 |
56 | /**
57 | * @return array{name: string, price: int}
58 | */
59 | public function getMetaProduct(): ?array
60 | {
61 | return [
62 | 'name' => $this->name,
63 | 'price' => $this->price,
64 | ];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Infra/Models/ItemWallet.php:
--------------------------------------------------------------------------------
1 |
29 | */
30 | protected $fillable = ['name', 'quantity', 'price'];
31 |
32 | public function getTable(): string
33 | {
34 | return 'items';
35 | }
36 |
37 | public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
38 | {
39 | $result = $this->quantity >= $quantity;
40 |
41 | if ($force) {
42 | return $result;
43 | }
44 |
45 | return $result && ! $customer->paid($this) instanceof \Bavix\Wallet\Models\Transfer;
46 | }
47 |
48 | public function getAmountProduct(Customer $customer): int
49 | {
50 | /** @var Wallet $wallet */
51 | $wallet = app(CastService::class)->getWallet($customer);
52 |
53 | return $this->price + (int) $wallet->holder_id;
54 | }
55 |
56 | public function getMetaProduct(): ?array
57 | {
58 | return null;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Infra/Models/Manager.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | protected $fillable = ['name', 'email'];
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Infra/Models/User.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | protected $fillable = ['name', 'email'];
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Infra/Models/UserCashier.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | protected $fillable = ['name', 'email'];
28 |
29 | public function getTable(): string
30 | {
31 | return 'users';
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Infra/Models/UserDynamic.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | protected $fillable = ['name', 'email'];
25 |
26 | public function getTable(): string
27 | {
28 | return 'users';
29 | }
30 |
31 | /**
32 | * @return non-empty-string
33 | */
34 | public function getDynamicDefaultSlug(): string
35 | {
36 | return 'default-'.$this->email;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Infra/Models/UserFloat.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | protected $fillable = ['name', 'email'];
26 |
27 | public function getTable(): string
28 | {
29 | return 'users';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Infra/Models/UserMulti.php:
--------------------------------------------------------------------------------
1 | fillable, ['bank_method']);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Infra/PackageModels/TransactionMoney.php:
--------------------------------------------------------------------------------
1 | currency ??= new Money($this->amount, $this->meta['currency'] ?? 'USD');
21 |
22 | return $this->currency;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Infra/PackageModels/Transfer.php:
--------------------------------------------------------------------------------
1 | >
14 | */
15 | private array $rates = [
16 | 'USD' => [
17 | 'BTC' => 0.004636,
18 | ],
19 | ];
20 |
21 | public function __construct(
22 | private readonly MathServiceInterface $mathService
23 | ) {
24 | }
25 |
26 | public function convertTo(string $fromCurrency, string $toCurrency, float|int|string $amount): string
27 | {
28 | return $this->mathService->mul($amount, $this->rates[$fromCurrency][$toCurrency] ?? 1.);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Infra/Services/MyExchangeService.php:
--------------------------------------------------------------------------------
1 | >
14 | */
15 | private array $rates = [
16 | 'USD' => [
17 | 'RUB' => 67.61,
18 | ],
19 | ];
20 |
21 | public function __construct(
22 | private readonly MathServiceInterface $mathService
23 | ) {
24 | foreach ($this->rates as $from => $rates) {
25 | foreach ($rates as $to => $rate) {
26 | $this->rates[$to][$from] ??= $this->mathService->div(1, $rate);
27 | }
28 | }
29 | }
30 |
31 | public function convertTo(string $fromCurrency, string $toCurrency, float|int|string $amount): string
32 | {
33 | return $this->mathService->mul($amount, $this->rates[$fromCurrency][$toCurrency] ?? 1.);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Infra/TestServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadMigrationsFrom([dirname(__DIR__).'/migrations']);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/Infra/Transform/TransactionDtoTransformerCustom.php:
--------------------------------------------------------------------------------
1 | getMeta() !== null) {
22 | $bankMethod = $dto->getMeta()['bank_method'] ?? null;
23 | }
24 |
25 | return array_merge($this->transactionDtoTransformer->extract($dto), [
26 | 'bank_method' => $bankMethod,
27 | ]);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Infra/Values/Money.php:
--------------------------------------------------------------------------------
1 | create();
22 | self::assertSame('wallet', $user->wallet->getTable());
23 | }
24 |
25 | public function testTransactionTableName(): void
26 | {
27 | /** @var User $user */
28 | $user = UserFactory::new()->create();
29 | $transaction = $user->deposit(100);
30 | self::assertSame('transaction', $transaction->getTable());
31 | }
32 |
33 | public function testTransferTableName(): void
34 | {
35 | /**
36 | * @var User $user1
37 | * @var User $user2
38 | */
39 | [$user1, $user2] = UserFactory::times(2)->create();
40 | $user1->deposit(1000);
41 | $transfer = $user1->transfer($user2, 1000);
42 | self::assertSame('transfer', $transfer->getTable());
43 |
44 | /** @var Manager $manager */
45 | $manager = ManagerFactory::new()->create();
46 | $user2->transfer($manager, 1000);
47 | self::assertSame(1000, $manager->balanceInt);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Units/Expand/WalletTest.php:
--------------------------------------------------------------------------------
1 | MyWallet::class,
21 | ]);
22 |
23 | /** @var Buyer $buyer */
24 | $buyer = BuyerFactory::new()->create();
25 |
26 | /** @var MyWallet $wallet */
27 | $wallet = $buyer->wallet;
28 |
29 | self::assertSame('hello world', $wallet->helloWorld());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Units/Service/DatabaseTest.php:
--------------------------------------------------------------------------------
1 | expectException(TransactionFailedException::class);
23 | $this->expectExceptionCode(ExceptionInterface::TRANSACTION_FAILED);
24 | $this->expectExceptionMessage('Transaction failed. Message: hello');
25 |
26 | app(DatabaseServiceInterface::class)->transaction(static function (): never {
27 | throw new \RuntimeException('hello');
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Units/Service/DeferrableTest.php:
--------------------------------------------------------------------------------
1 | resolveProvider(WalletServiceProvider::class);
20 |
21 | self::assertInstanceOf(DeferrableProvider::class, $walletServiceProvider);
22 | self::assertNotEmpty($walletServiceProvider->provides());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Units/Service/FormatterTest.php:
--------------------------------------------------------------------------------
1 | floatValue('12345', 3);
22 |
23 | self::assertSame('12.345', $result);
24 | }
25 |
26 | /**
27 | * @throws ExceptionInterface
28 | */
29 | public function testFloatValueDP2(): void
30 | {
31 | $result = app(FormatterServiceInterface::class)->floatValue('12345', 2);
32 |
33 | self::assertSame('123.45', $result);
34 | }
35 |
36 | /**
37 | * @throws ExceptionInterface
38 | */
39 | public function testIntValueDP3(): void
40 | {
41 | $result = app(FormatterServiceInterface::class)->intValue('12.345', 3);
42 |
43 | self::assertSame('12345', $result);
44 | }
45 |
46 | /**
47 | * @throws ExceptionInterface
48 | */
49 | public function testIntValueDP2(): void
50 | {
51 | $result = app(FormatterServiceInterface::class)->intValue('123.45', 2);
52 |
53 | self::assertSame('12345', $result);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Units/Service/JsonServiceTest.php:
--------------------------------------------------------------------------------
1 | encode(null));
19 | self::assertJson((string) $jsonService->encode([1]));
20 | }
21 |
22 | public function testJsonEncodeFailed(): void
23 | {
24 | $jsonService = app(JsonService::class);
25 | $array = [1];
26 | $array[] = &$array;
27 |
28 | self::assertNull($jsonService->encode($array));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Units/Service/WalletConfigureTest.php:
--------------------------------------------------------------------------------
1 | increments('id');
15 | $table->string('name');
16 | $table->string('email');
17 | $table->timestamps();
18 | });
19 | }
20 |
21 | public function down(): void
22 | {
23 | Schema::dropIfExists('users');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/tests/migrations/2018_11_08_214421_create_items_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
15 | $table->string('name');
16 | $table->integer('price');
17 | $table->json('prices')
18 | ->nullable();
19 | $table->unsignedSmallInteger('quantity');
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | public function down(): void
25 | {
26 | Schema::dropIfExists('items');
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/tests/migrations/2019_09_19_191432_alter_column_transaction_table.php:
--------------------------------------------------------------------------------
1 | getTable(), static function (Blueprint $table) {
15 | $table->string('bank_method')
16 | ->nullable();
17 | });
18 | }
19 |
20 | public function down(): void
21 | {
22 | Schema::table((new Transaction())->getTable(), static function (Blueprint $table) {
23 | $table->dropColumn('bank_method');
24 | });
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/tests/migrations/2022_01_26_185311_create_managers_table.php:
--------------------------------------------------------------------------------
1 | uuid('id')
15 | ->primary();
16 | $table->string('name');
17 | $table->string('email');
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | public function down(): void
23 | {
24 | Schema::dropIfExists('managers');
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/tests/migrations/2023_12_13_190445_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')
18 | ->primary();
19 | $table->mediumText('value');
20 | $table->integer('expiration');
21 | });
22 |
23 | Schema::create('cache_locks', static function (Blueprint $table) {
24 | $table->string('key')
25 | ->primary();
26 | $table->string('owner');
27 | $table->integer('expiration');
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | */
34 | public function down(): void
35 | {
36 | Schema::dropIfExists('cache');
37 | Schema::dropIfExists('cache_locks');
38 | }
39 | };
40 |
--------------------------------------------------------------------------------