├── CHANGELOG.md ├── Enums └── WalletEnums.php ├── LICENSE.md ├── README.md ├── composer.json ├── config └── pay-pocket.php ├── database ├── factories │ └── ModelFactory.php └── migrations │ ├── add_notes_and_reference_columns_to_wallets_logs_table.php.stub │ ├── create_wallets_logs_table.php.stub │ └── create_wallets_table.php.stub ├── resources └── views │ └── .gitkeep └── src ├── Commands └── LaravelPayPocketCommand.php ├── Exceptions ├── InsufficientBalanceException.php ├── InvalidDepositException.php ├── InvalidValueException.php ├── InvalidWalletTypeException.php └── WalletNotFoundException.php ├── Facades └── LaravelPayPocket.php ├── Interfaces └── WalletOperations.php ├── LaravelPayPocketServiceProvider.php ├── Models ├── Wallet.php └── WalletsLog.php ├── Services └── PocketServices.php └── Traits ├── BalanceOperation.php ├── GetWallets.php ├── HandlesDeposit.php ├── HandlesPayment.php ├── HasWallet.php ├── Loggable.php └── ManagesWallet.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-pay-pocket` will be documented in this file. 4 | 5 | ## 2.2.0 - 2024-06-27 6 | 7 | ### What's Changed 8 | 9 | * Update: Change log_reference_length to log_reference_params by @3m1n3nc3 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/34 10 | * Modify comments by @HPWebdeveloper in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/36 11 | 12 | **Full Changelog**: https://github.com/HPWebdeveloper/laravel-pay-pocket/compare/2.1.0...2.2.0 13 | 14 | ## 2.1.0 - 2024-06-27 15 | 16 | ### What's Changed 17 | 18 | * Bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/26 19 | * Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/27 20 | * Add check Laravel 11 compatibility by @HPWebdeveloper in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/28 21 | * Test over the correct php version by @HPWebdeveloper in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/29 22 | * Support Laravel 11 by @samehdoush in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/25 23 | 24 | ### New Contributors 25 | 26 | * @samehdoush made their first contribution in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/25 27 | 28 | **Full Changelog**: https://github.com/HPWebdeveloper/laravel-pay-pocket/compare/2.0.3...2.1.0 29 | 30 | ## 2.0.3 - 2024-04-12 31 | 32 | ### What's Changed 33 | 34 | * Update create_wallets_logs_table.php.stub by @josecarpio99 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/21 35 | 36 | ### New Contributors 37 | 38 | * @josecarpio99 made their first contribution in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/21 39 | 40 | **Full Changelog**: https://github.com/HPWebdeveloper/laravel-pay-pocket/compare/2.0.2...2.0.3 41 | 42 | ## 2.0.2 - 2024-04-12 43 | 44 | ### What's Changed 45 | 46 | * Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/20 47 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/23 48 | 49 | **Full Changelog**: https://github.com/HPWebdeveloper/laravel-pay-pocket/compare/2.0.1...2.0.2 50 | 51 | ## 2.0.1 - 2024-04-12 52 | 53 | ### What's Changed 54 | 55 | * Update README.md by @3m1n3nc3 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/15 56 | * Fix/style fix by @SSEsmaeeli in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/17 57 | * Types by @HPWebdeveloper in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/16 58 | 59 | **Full Changelog**: https://github.com/HPWebdeveloper/laravel-pay-pocket/compare/2.0.0...2.0.1 60 | 61 | ## v2.0.0 - 2024-01-12 62 | 63 | - [2.x] Add Transaction note by @3m1n3nc3 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/10 64 | 65 | ## v1.0.3 - 2023-12-15 66 | 67 | - [1.x] Fix problematic class references by @imanghafoori1 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/4 68 | 69 | ## v1.0.2 - 2023-12-15 70 | 71 | - [1.x] Create check_imports.yml by @imanghafoori1 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/3 72 | - [1.x] Update composer.json by @imanghafoori1 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/3 73 | - [1.x] Update README.md by @imanghafoori1 in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/3 74 | 75 | ## v1.0.1 - 2023-12-12 76 | 77 | - [1.x]Optimize wallet retrieval to single query by @SSEsmaeeli in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/2 78 | - [1.x] Configure Github Actions for PHP 8.3 testing by @SSEsmaeeli in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/2 79 | - [1.x] fix PHPStan issues by @SSEsmaeeli in https://github.com/HPWebdeveloper/laravel-pay-pocket/pull/2 80 | 81 | ## Initial Release - 2023-12-01 82 | 83 | - Initial release 84 | -------------------------------------------------------------------------------- /Enums/WalletEnums.php: -------------------------------------------------------------------------------- 1 | value === $type) { 17 | return true; 18 | } 19 | } 20 | 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Hamed Panjeh 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Laravel Pay Pocket](https://github.com/HPWebdeveloper/laravel-pay-pocket/assets/16323354/8e8ebcf6-f8d4-4811-b97c-fb6362e3f019) 2 | 3 | # Laravel Pay Pocket 4 | 5 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/hpwebdeveloper/laravel-pay-pocket.svg?style=flat-square)](https://packagist.org/packages/hpwebdeveloper/laravel-pay-pocket) 6 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/hpwebdeveloper/laravel-pay-pocket/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/hpwebdeveloper/laravel-pay-pocket/actions?query=workflow%3Arun-tests+branch%3Amain) 7 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/hpwebdeveloper/laravel-pay-pocket/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/hpwebdeveloper/laravel-pay-pocket/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 8 | [![Imports](https://github.com/HPWebdeveloper/laravel-pay-pocket/actions/workflows/check_imports.yml/badge.svg?branch=main)](https://github.com/HPWebdeveloper/laravel-pay-pocket/actions/workflows/check_imports.yml) 9 | 10 | **Laravel Pay Pocket** is a package designed for Laravel applications, offering the flexibility to manage multiple wallet types within two dedicated database tables, `wallets` and `wallets_logs`. 11 | 12 | **Demo:** https://github.com/HPWebdeveloper/demo-pay-pocket 13 | 14 | **Videos:** 15 | 16 | - [Laravel Pay Pocket Package: Virtual Wallets in Your Project](https://www.youtube.com/watch?v=KoQyURiwsA4) 17 | 18 | - [Laravel Exceptions: Why and How to Use? Practical Example.](https://www.youtube.com/watch?v=-Sr18w91v8Q) 19 | 20 | - [PHP Enums in Laravel: Practical Example from Package](https://www.youtube.com/watch?v=iUOb-3HQtK8) 21 | 22 | 23 | **Note:** This package does not handle payments from payment platforms, but instead offers the concept of virtual money, deposit, and withdrawal. 24 | 25 | - **Author**: Hamed Panjeh 26 | - **Vendor**: hpwebdeveloper 27 | - **Package**: laravel-pay-pocket 28 | - **Alias name**: Laravel PPP (Laravel Pay Pocket Package) 29 | - **Version**: `2.x` 30 | - **PHP Version**: 8.1+ 31 | - **Laravel Version**: `10.x`, `11.x` 32 | - **[Composer](https://getcomposer.org/):** `composer require hpwebdeveloper/laravel-pay-pocket` 33 | 34 | ### Support Policy 35 | 36 | | Version | Laravel | PHP | Release date | End of improvements | End of support | 37 | |-------------------------------------------------|--------------|-------------|---------------|---------------------| -------------- | 38 | | 1.x | ^10.0 | 8.1, 8.2, 8.3 | Nov 30, 2023 | Mar 1, 2024 | | 39 | | 2.x | ^10.0, ^11.0 |8.2, 8.3| June 27, 2024 | January 30, 2025 | | 40 | | 3.x (atomic operations and restricted wallets) | ^11.0 |8.2, 8.3| comming soon | | | 41 | 42 | ## Installation: 43 | 44 | - **Step 1:** You can install the package via composer: 45 | 46 | ```bash 47 | composer require hpwebdeveloper/laravel-pay-pocket 48 | ``` 49 | 50 | - **Step 2:** Publish and run the migrations with: 51 | 52 | ```bash 53 | php artisan vendor:publish --tag="pay-pocket-migrations" 54 | php artisan migrate 55 | ``` 56 | 57 | You have successfully added two dedicated database tables, `wallets` and `wallets_logs`, without making any modifications to the `users` table. 58 | 59 | - **Step 3:** Publish the wallet types using 60 | 61 | ```bash 62 | php artisan vendor:publish --tag="pay-pocket-wallets" 63 | php artisan vendor:publish --tag="config" 64 | ``` 65 | 66 | This command will automatically publish the `pay-pocket.php` config file and also `WalletEnums.php` file into your application's `config` and `app/Enums` directories respectively. 67 | 68 | ## Updating 69 | 70 | If updating to version `^2.0.0`, new migration and config files have been added to support the new [Transaction Notes Feature](#transaction-notes-8) 71 | 72 | Follow the [Installation](#installation) Steps 2 and 3 to update your migrations. 73 | 74 | ## Preparation 75 | 76 | ### Prepare User Model 77 | 78 | To use this package you need to implement the `WalletOperations` into `User` model and utilize the `ManagesWallet` trait. 79 | 80 | ```php 81 | 82 | use HPWebdeveloper\LaravelPayPocket\Interfaces\WalletOperations; 83 | use HPWebdeveloper\LaravelPayPocket\Traits\ManagesWallet; 84 | 85 | class User extends Authenticatable implements WalletOperations 86 | { 87 | use ManagesWallet; 88 | } 89 | ``` 90 | 91 | ### Prepare Wallets 92 | 93 | In Laravel Pay Pocket, you have the flexibility to define the order in which wallets are prioritized for payments through the use of Enums. The order of wallets in the Enum file determines their priority level. The first wallet listed has the highest priority and will be used first for deducting order values. 94 | 95 | For example, consider the following wallet types defined in the Enum class (published in step 3 of installation): 96 | 97 | ```php 98 | namespace App\Enums; 99 | 100 | enum WalletEnums: string 101 | { 102 | case WALLET1 = 'wallet_1'; 103 | case WALLET2 = 'wallet_2'; 104 | } 105 | 106 | ``` 107 | 108 | **You have complete freedom to name your wallets as per your requirements and even add more wallet types to the Enum list.** 109 | 110 | In this particular setup, `wallet_1` (`WALLET1`) is given the **highest priority**. When an order payment is processed, the system will first attempt to use `wallet_1` to cover the cost. If `wallet_1` does not have sufficient funds, `wallet_2` (`WALLET2`) will be used next. 111 | 112 | ### Example: 113 | 114 | If the balance in `wallet_1` is 10 and the balance in `wallet_2` is 20, and you need to pay an order value of 15, the payment process will first utilize the entire balance of `wallet_1`. Since `wallet_1`'s balance is insufficient to cover the full amount, the remaining 5 will be deducted from `wallet_2`. After the payment, `wallet_2` will have a remaining balance of 15." 115 | 116 | ## Usage, APIs and Operations: 117 | 118 | ### Deposit 119 | 120 | ```php 121 | deposit(type: 'wallet_1', amount: 123.45, notes: null) 122 | ``` 123 | 124 | Deposit funds into `wallet_1` 125 | 126 | ```php 127 | $user = auth()->user(); 128 | $user->deposit('wallet_1', 123.45); 129 | ``` 130 | 131 | Deposit funds into `wallet_2` 132 | 133 | ```php 134 | $user = auth()->user(); 135 | $user->deposit('wallet_2', 67.89); 136 | ``` 137 | 138 | Or using provided facade 139 | 140 | ```php 141 | use HPWebdeveloper\LaravelPayPocket\Facades\LaravelPayPocket; 142 | 143 | $user = auth()->user(); 144 | LaravelPayPocket::deposit($user, 'wallet_1', 123.45); 145 | 146 | ``` 147 | 148 | Note: `wallet_1` and `wallet_2` must already be defined in the `WalletEnums`. 149 | 150 | #### Transaction Info ([#8][i8]) 151 | 152 | When you need to add descriptions for a specific transaction, the `$notes` parameter enables you to provide details explaining the reason behind the transaction. 153 | 154 | ```php 155 | $user = auth()->user(); 156 | $user->deposit('wallet_1', 67.89, 'You ordered pizza.'); 157 | ``` 158 | 159 | ### Pay 160 | 161 | ```php 162 | pay(amount: 12.34, notes: null) 163 | ``` 164 | 165 | Pay the value using the total combined balance available across all allowed wallets 166 | 167 | ```php 168 | $user = auth()->user(); 169 | $user->pay(12.34); 170 | ``` 171 | 172 | Or using provided facade 173 | 174 | ```php 175 | use HPWebdeveloper\LaravelPayPocket\Facades\LaravelPayPocket; 176 | 177 | $user = auth()->user(); 178 | LaravelPayPocket::pay($user, 12.34); 179 | ``` 180 | 181 | ### Balance 182 | 183 | - **Wallets** 184 | 185 | ```php 186 | $user->walletBalance // Total combined balance available across all wallets 187 | 188 | // Or using provided facade 189 | 190 | LaravelPayPocket::checkBalance($user); 191 | ``` 192 | 193 | - **Particular Wallet** 194 | 195 | ```php 196 | $user->getWalletBalanceByType('wallet_1') // Balance available in wallet_1 197 | $user->getWalletBalanceByType('wallet_2') // Balance available in wallet_2 198 | 199 | // Or using provided facade 200 | 201 | LaravelPayPocket::walletBalanceByType($user, 'wallet_1'); 202 | ``` 203 | 204 | ### Exceptions 205 | 206 | Upon examining the `src/Exceptions` directory within the source code, 207 | you will discover a variety of exceptions tailored to address each scenario of invalid entry. Review the [demo](https://github.com/HPWebdeveloper/demo-pay-pocket) that accounts for some of the exceptions. 208 | 209 | ### Log 210 | 211 | A typical `wallets_logs` table. 212 | ![Laravel Pay Pocket Log](https://github.com/HPWebdeveloper/laravel-pay-pocket/assets/16323354/0d7f2237-88e1-4ac0-a4f2-ac200bad9273) 213 | 214 | ## Testing 215 | 216 | ```bash 217 | composer install 218 | 219 | composer test 220 | 221 | 222 | // Or 223 | 224 | ./vender/bin/pest 225 | ``` 226 | 227 | ## Changelog 228 | 229 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 230 | 231 | ## Contributing 232 | 233 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 234 | 235 | ## Security Vulnerabilities 236 | 237 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 238 | 239 | ## Credits 240 | 241 | - [Hamed Panjeh](https://github.com/HPWebdeveloper) 242 | - [All Contributors](../../contributors) 243 | - Icon in the above image: pocket by Creative Mahira from [Noun Project](https://thenounproject.com/browse/icons/term/pocket/) (CC BY 3.0) 244 | 245 | ## License 246 | 247 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 248 | 249 | [i8]: https://github.com/HPWebdeveloper/laravel-pay-pocket/releases/tag/2.0.0 250 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hpwebdeveloper/laravel-pay-pocket", 3 | "description": "Laravel Pay Pocket", 4 | "keywords": [ 5 | "Hamed Panjeh", 6 | "laravel", 7 | "laravel-pay-pocket" 8 | ], 9 | "homepage": "https://github.com/hpwebdeveloper/laravel-pay-pocket", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Hamed Panjeh", 14 | "email": "panjeh@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "spatie/laravel-package-tools": "^1.14.0", 21 | "illuminate/contracts": "^10.0 || ^11.0" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.0", 25 | "nunomaduro/collision": "^7.8", 26 | "nunomaduro/larastan": "^2.0.1", 27 | "orchestra/testbench": "^8.8", 28 | "pestphp/pest": "^2.20", 29 | "pestphp/pest-plugin-arch": "^2.0", 30 | "pestphp/pest-plugin-laravel": "^2.0", 31 | "phpstan/extension-installer": "^1.1", 32 | "phpstan/phpstan-deprecation-rules": "^1.0", 33 | "phpstan/phpstan-phpunit": "^1.0", 34 | "imanghafoori/php-imports-analyzer": "1.0.*", 35 | "spatie/laravel-ray": "^1.26" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "HPWebdeveloper\\LaravelPayPocket\\": "src/", 40 | "HPWebdeveloper\\LaravelPayPocket\\Database\\Factories\\": "database/factories/", 41 | "HPWebdeveloper\\LaravelPayPocket\\Tests\\Database\\Factories\\": "tests/database/factories" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "HPWebdeveloper\\LaravelPayPocket\\Tests\\": "tests/", 47 | "App\\Enums\\": "Enums/", 48 | "Workbench\\App\\": "workbench/app/" 49 | } 50 | }, 51 | "scripts": { 52 | "post-autoload-dump": "@composer run prepare", 53 | "clear": "@php vendor/bin/testbench package:purge-laravel-pay-pocket --ansi", 54 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 55 | "build": [ 56 | "@composer run prepare", 57 | "@php vendor/bin/testbench workbench:build --ansi" 58 | ], 59 | "start": [ 60 | "Composer\\Config::disableProcessTimeout", 61 | "@composer run build", 62 | "@php vendor/bin/testbench serve" 63 | ], 64 | "analyse": "vendor/bin/phpstan analyse", 65 | "test": "vendor/bin/pest", 66 | "test-coverage": "vendor/bin/pest --coverage", 67 | "format": "vendor/bin/pint" 68 | }, 69 | "config": { 70 | "sort-packages": true, 71 | "allow-plugins": { 72 | "pestphp/pest-plugin": true, 73 | "phpstan/extension-installer": true 74 | } 75 | }, 76 | "extra": { 77 | "laravel": { 78 | "providers": [ 79 | "HPWebdeveloper\\LaravelPayPocket\\LaravelPayPocketServiceProvider" 80 | ], 81 | "aliases": { 82 | "LaravelPayPocket": "HPWebdeveloper\\LaravelPayPocket\\Facades\\LaravelPayPocket" 83 | } 84 | } 85 | }, 86 | "minimum-stability": "dev", 87 | "prefer-stable": true 88 | } 89 | -------------------------------------------------------------------------------- /config/pay-pocket.php: -------------------------------------------------------------------------------- 1 | [12], 26 | 'log_reference_prefix' => '', 27 | 'log_reference_generator_class' => Illuminate\Support\Str::class, 28 | 'log_reference_generator_method' => 'random', 29 | ]; 30 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | string('notes')->nullable()->after('status'); 17 | } 18 | if (!Schema::hasColumn('wallets_logs', 'reference')) { 19 | $table->string('reference')->nullable()->after('ip'); 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::table('wallets_logs', function (Blueprint $table) { 30 | if (Schema::hasColumn('wallets_logs', 'notes')) { 31 | $table->dropColumn('notes'); 32 | } 33 | if (Schema::hasColumn('wallets_logs', 'reference')) { 34 | $table->dropColumn('reference'); 35 | } 36 | }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /database/migrations/create_wallets_logs_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->morphs('loggable'); 16 | $table->string('wallet_name'); 17 | $table->decimal('value', 16, 2); 18 | $table->decimal('from', 16, 2); 19 | $table->decimal('to', 16, 2); 20 | $table->string('type'); 21 | $table->string('status')->default('Pending'); 22 | $table->ipAddress('ip'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('wallets_logs'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/create_wallets_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->morphs('owner'); 16 | $table->string('type', 20); // add validation 17 | $table->decimal('balance', 16, 2)->default(0); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('wallets'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HPWebdeveloper/laravel-pay-pocket/291b013b5c0690b62c838373513381ae17a4ef90/resources/views/.gitkeep -------------------------------------------------------------------------------- /src/Commands/LaravelPayPocketCommand.php: -------------------------------------------------------------------------------- 1 | comment('All done'); 16 | 17 | return self::SUCCESS; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Exceptions/InsufficientBalanceException.php: -------------------------------------------------------------------------------- 1 | name('laravel-pay-pocket') 19 | ->hasConfigFile() 20 | ->hasViews() 21 | ->hasMigrations( 22 | 'create_wallets_logs_table', 23 | 'create_wallets_table', 24 | 'add_notes_and_reference_columns_to_wallets_logs_table' 25 | ); 26 | } 27 | 28 | public function bootingPackage() 29 | { 30 | $this->publishes([ 31 | __DIR__.'/../Enums/' => app_path('Enums'), 32 | ], 'pay-pocket-wallets'); 33 | 34 | $this->publishes([ 35 | __DIR__.'/../config/pay-pocket.php' => config_path('pay-pocket.php'), 36 | ], 'config'); 37 | } 38 | 39 | public function registeringPackage() 40 | { 41 | // Automatically apply the package configuration 42 | $this->mergeConfigFrom(__DIR__.'/../config/pay-pocket.php', 'pay-pocket'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Models/Wallet.php: -------------------------------------------------------------------------------- 1 | WalletEnums::class, 28 | ]; 29 | 30 | public function owner(): MorphTo 31 | { 32 | return $this->morphTo(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Models/WalletsLog.php: -------------------------------------------------------------------------------- 1 | morphTo(); 33 | } 34 | 35 | public function changeStatus($status) 36 | { 37 | $this->status = $status; 38 | 39 | return $this->save(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Services/PocketServices.php: -------------------------------------------------------------------------------- 1 | deposit($type, $amount, $notes); 16 | } 17 | 18 | /** 19 | * Pay the order value from the user's wallets. 20 | * 21 | * @throws InsufficientBalanceException 22 | */ 23 | public function pay(WalletOperations $user, int|float $orderValue, ?string $notes = null): void 24 | { 25 | $user->pay($orderValue, $notes); 26 | } 27 | 28 | /** 29 | * Get the balance of the user. 30 | */ 31 | public function checkBalance(WalletOperations $user): int|float 32 | { 33 | return $user->getWalletBalance(); 34 | } 35 | 36 | /** 37 | * Get the balance of a specific wallet type. 38 | */ 39 | public function walletBalanceByType(WalletOperations $user, string $type): int|float 40 | { 41 | return $user->getWalletBalanceByType($type); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Traits/BalanceOperation.php: -------------------------------------------------------------------------------- 1 | balance > 0; 18 | } 19 | 20 | /** 21 | * Decrement Balance and create a log entry. 22 | */ 23 | public function decrementAndCreateLog(int|float $value, ?string $notes = null): void 24 | { 25 | $this->createLog('dec', $value, $notes); 26 | $this->decrement('balance', $value); 27 | } 28 | 29 | /** 30 | * Increment Balance and create a log entry. 31 | */ 32 | public function incrementAndCreateLog(int|float $value, ?string $notes = null): void 33 | { 34 | $this->createLog('inc', $value, $notes); 35 | $this->increment('balance', $value); 36 | } 37 | 38 | /** 39 | * Create a new log record 40 | */ 41 | protected function createLog(string $logType, int|float $value, ?string $notes = null): void 42 | { 43 | $currentBalance = $this->balance ?? 0; 44 | 45 | $newBalance = $logType === 'dec' ? $currentBalance - $value : $currentBalance + $value; 46 | 47 | $this->createdLog = $this->logs()->create([ 48 | 'wallet_name' => $this->type->value, 49 | 'from' => $currentBalance, 50 | 'to' => $newBalance, 51 | 'type' => $logType, 52 | 'ip' => request()->ip(), 53 | 'value' => $value, 54 | 'notes' => $notes, 55 | 'reference' => $this->generateReference(), 56 | ]); 57 | 58 | $this->createdLog->changeStatus('Done'); 59 | } 60 | 61 | /** 62 | * @throws InvalidArgumentException 63 | */ 64 | protected function generateReference(): string 65 | { 66 | $className = config('pay-pocket.log_reference_generator_class'); 67 | $methodName = config('pay-pocket.log_reference_generator_method'); 68 | $params = (array) config('pay-pocket.log_reference_params', [12]); 69 | $prefix = config('pay-pocket.log_reference_prefix'); 70 | 71 | if (! is_callable([$className, $methodName])) { 72 | throw new InvalidArgumentException('Invalid configuration: The combination of log_reference_generator_class and log_reference_generator_method is not callable.'); 73 | } 74 | 75 | $reference = call_user_func([$className, $methodName], ...$params); 76 | 77 | return $prefix.$reference; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Traits/GetWallets.php: -------------------------------------------------------------------------------- 1 | $enumCase->value, 13 | WalletEnums::cases() 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Traits/HandlesDeposit.php: -------------------------------------------------------------------------------- 1 | getDepositableTypes(); 23 | 24 | if (! $this->isRequestValid($type, $depositable)) { 25 | throw new InvalidDepositException('Invalid deposit request.'); 26 | } 27 | 28 | if ($amount <= 0) { 29 | throw new InvalidValueException(); 30 | } 31 | 32 | DB::transaction(function () use ($type, $amount, $notes) { 33 | $type = WalletEnums::tryFrom($type); 34 | $wallet = $this->wallets()->firstOrCreate(['type' => $type]); 35 | $wallet->incrementAndCreateLog($amount, $notes); 36 | }); 37 | 38 | return true; 39 | } 40 | 41 | /** 42 | * Get depositable types from WalletEnums. 43 | */ 44 | private function getDepositableTypes(): array 45 | { 46 | $depositableTypes = []; 47 | foreach (WalletEnums::cases() as $enumCase) { 48 | $depositableTypes[$enumCase->value] = strtolower($enumCase->name); 49 | } 50 | 51 | return $depositableTypes; 52 | } 53 | 54 | /** 55 | * Check if the given type is valid. 56 | * 57 | * @throws InvalidWalletTypeException 58 | */ 59 | private function isRequestValid($type, array $depositable): bool 60 | { 61 | if (! array_key_exists($type, $depositable)) { 62 | throw new InvalidWalletTypeException('Invalid deposit type.'); 63 | } 64 | 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Traits/HandlesPayment.php: -------------------------------------------------------------------------------- 1 | hasSufficientBalance($orderValue)) { 18 | throw new InsufficientBalanceException('Insufficient balance to cover the order.'); 19 | } 20 | 21 | DB::transaction(function () use ($orderValue, $notes) { 22 | $remainingOrderValue = $orderValue; 23 | 24 | /** 25 | * @var \Illuminate\Support\Collection 26 | */ 27 | $walletsInOrder = $this->wallets()->whereIn('type', $this->walletsInOrder())->get(); 28 | 29 | foreach ($walletsInOrder as $wallet) { 30 | if (! $wallet || ! $wallet->hasBalance()) { 31 | continue; 32 | } 33 | 34 | $amountToDeduct = min($wallet->balance, $remainingOrderValue); 35 | $wallet->decrementAndCreateLog($amountToDeduct, $notes); 36 | $remainingOrderValue -= $amountToDeduct; 37 | 38 | if ($remainingOrderValue <= 0) { 39 | break; 40 | } 41 | } 42 | 43 | if ($remainingOrderValue > 0) { 44 | throw new InsufficientBalanceException('Insufficient total wallet balance to cover the order.'); 45 | } 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Traits/HasWallet.php: -------------------------------------------------------------------------------- 1 | morphMany(Wallet::class, 'owner'); 23 | } 24 | 25 | /** 26 | * Get User's Wallet Balance 27 | */ 28 | public function getWalletBalanceAttribute(): int|float 29 | { 30 | $totalBalance = 0; 31 | 32 | foreach ($this->walletsInOrder() as $walletInOrder) { 33 | $walletEnumType = WalletEnums::tryFrom($walletInOrder); 34 | $wallet = $this->wallets()->where('type', $walletEnumType)->first(); 35 | 36 | if ($wallet) { 37 | $totalBalance += $wallet->balance; 38 | } 39 | } 40 | 41 | return $totalBalance; 42 | } 43 | 44 | /** 45 | * Check if User's wallet balance is more than given value 46 | */ 47 | public function hasSufficientBalance(int|float $value): bool 48 | { 49 | return (int) $this->walletBalance >= (int) $value; 50 | } 51 | 52 | /** 53 | * Get the balance of a specific wallet type. 54 | * 55 | * @throws InvalidWalletTypeException|WalletNotFoundException 56 | */ 57 | public function getWalletBalanceByType(string $walletType): int|float 58 | { 59 | if (! WalletEnums::isValid($walletType)) { 60 | throw new InvalidWalletTypeException("Invalid wallet type '{$walletType}'."); 61 | } 62 | 63 | $wallet = $this->wallets()->where('type', $walletType)->first(); 64 | 65 | if (! $wallet) { 66 | throw new WalletNotFoundException("Wallet of type '{$walletType}' not found."); 67 | } 68 | 69 | return $wallet->balance; 70 | } 71 | 72 | public function getWalletBalance(): int|float 73 | { 74 | return $this->walletBalance; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Traits/Loggable.php: -------------------------------------------------------------------------------- 1 | morphMany(WalletsLog::class, 'loggable'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Traits/ManagesWallet.php: -------------------------------------------------------------------------------- 1 |