illuminatech / balance
提供基于借记和贷记原则的Balance会计系统的支持
Requires
- illuminate/database: ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminatech/array-factory: ^1.2.5
Requires (Dev)
- illuminate/events: *
- phpunit/phpunit: ^7.5 || ^8.0 || ^9.3 || ^10.5
README
Balance会计系统Laravel扩展
此扩展提供了基于借记和贷记原则的平衡会计系统的基本支持。
有关许可证信息,请查看LICENSE文件。
安装
安装此扩展的首选方式是通过composer。
运行以下命令
php composer.phar require --prefer-dist illuminatech/balance
或者将以下内容添加到您的composer.json文件的require部分。
"illuminatech/balance": "*"
用法
此扩展提供了基于借记和贷记原则的平衡会计系统的基本支持。平衡系统通常用于会计(记账)和资金操作。然而,它也可以用于任何资源从一个位置转移到另一个位置。例如:将商品从仓库转移到商店等。
与平衡系统相关的两个主要术语
- 账户 - 具有某些逻辑意义的资源的虚拟存储。
- 交易 - 代表将资源转移到特定账户或从特定账户转移的实际转移。
假设我们有一个提供用户虚拟货币余额的系统。余额中的货币可以用于购买商品,用户可以通过某些支付网关为余额充值。在这种情况下,每个用户应该有3个虚拟余额账户:'virtual-money'、'payment-gateway'和'purchases'。当用户为虚拟余额充值时,我们的系统应从'payment-gateway'中扣除资金并添加到'virtual-money'。当用户购买商品时,我们的系统应从'virtual-money'中扣除资金并添加到'purchases'。关键是:如果你将所有与用户相关的账户的当前金额加起来('payment-gateway' + 'virtual-money' + 'purchases'),它总是等于零。这种检查允许你在任何时候验证是否有错误发生。
此扩展引入了“平衡管理器”术语作为服务,该服务应处理所有平衡交易。此类管理器的公共契约由\Illuminatech\Balance\BalanceContract
接口确定。以下是一些特定实现
- \Illuminatech\Balance\BalanceDb - 使用关系型数据库作为数据存储。
请参阅特定管理器类以获取更多详细信息。
此扩展提供了\Illuminatech\Balance\BalanceServiceProvider
服务提供者,该提供者将\Illuminatech\Balance\BalanceContract
绑定到DI容器中的单例。因此,您可以通过自动DI注入或通过容器实例获取平衡管理器。例如
<?php use Illuminate\Container\Container; use App\Http\Controllers\Controller; use Illuminatech\Balance\BalanceContract; class BalanceController extends Controller { public function increase(BalanceContract $balance, $accountId, $amount) { $balance->increase($accountId, $amount); // ... } public function decrease($accountId, $amount) { $balance = Container::getInstance()->get(BalanceContract::class); $balance->decrease($accountId, $amount); // ... } // ... }
您还可以使用\Illuminatech\Balance\Facades\Balance
外观。例如
<?php use Illuminatech\Balance\Facades\Balance; Balance::increase($accountId, $amount);
在这些文档中,外观用于代码片段以简化。
应用程序配置
此扩展使用illuminatech/array-factory进行配置。在配置此扩展之前,请确保您熟悉“数组工厂”概念。配置存储在'config/balance.php'文件中。
您可以使用以下控制台命令发布预定义的配置文件
php artisan vendor:publish --provider="Illuminatech\Balance\BalanceServiceProvider" --tag=config
如果您正在使用\Illuminatech\Balance\BalanceDb
,您可以使用以下控制台命令发布针对它的预定义数据库迁移
php artisan vendor:publish --provider="Illuminatech\Balance\BalanceServiceProvider" --tag=migrations
基本操作
为了增加特定账户的(借方)余额,使用 \Illuminatech\Balance\BalanceContract::increase()
方法
<?php use Illuminatech\Balance\Facades\Balance; Balance::increase($accountId, 500); // add 500 credits to account
为了减少特定账户的(贷方)余额,使用 \Illuminatech\Balance\BalanceContract:decrease()
方法
<?php use Illuminatech\Balance\Facades\Balance; Balance::decrease($accountId, 100); // remove 100 credits from account
提示:实际上,方法
decrease()
是多余的,你可以通过调用increase()
并传入负金额来达到相同的效果。
在您的应用中,不太可能直接使用 increase()
和 decrease()
方法。在大多数情况下,需要一次性将资金从某个账户转移到另一个账户。可以使用 \Illuminatech\Balance\BalanceContract::transfer()
方法来实现。
<?php use Illuminatech\Balance\Facades\Balance; $fromId = 1; $toId = 2; Balance::transfer($fromId, $toId, 100); // remove 100 credits from account 1 and add 100 credits to account 2
注意:方法 transfer()
会创建两个独立的交易:每个受影响的账户一个。因此,您可以轻松地获取特定账户的所有转账历史,只需选择与其相关的所有交易即可。借方交易将具有正金额,而贷方交易将具有负金额。
注意:如果您希望
transfer()
创建的交易记录另一个参与过程的账户,您需要设置\Illuminatech\Balance\Balance::$extraAccountLinkAttribute
。
您可以使用 \Illuminatech\Balance\BalanceContract::revert()
方法回滚特定交易
<?php use Illuminatech\Balance\Facades\Balance; Balance::revert($transactionId);
此方法不会删除原始交易,而是创建一个新的交易,以补偿它。
查询账户
使用账户 ID 对余额管理器来说并不实用。在我们的上述示例中,每个系统用户都有 3 个虚拟账户,每个账户都有其唯一的 ID。然而,在执行购买时,我们操作用户 ID 和账户类型,因此在使用余额管理器之前,我们需要查询实际的账户 ID。因此,可以通过设置它们的属性集来指定用于余额管理器方法的账户。例如
<?php use Illuminatech\Balance\Facades\Balance; $user = request()->user(); Balance::transfer( [ 'userId' => $user->id, 'type' => 'virtual-money', ], [ 'userId' => $user->id, 'type' => 'purchases', ], 500 );
在此示例中,余额管理器将自动使用提供的属性作为过滤器来找到受影响账户的 ID。
您可以通过启用 \Illuminatech\Balance\Balance::$autoCreateAccount
来允许自动创建缺失的账户,如果它们被指定为属性集。这允许在需要时动态创建账户,从而消除预先创建的需求。
请注意!实际上,'账户' 实体在余额系统中是多余的,其使用可以避免。然而,它的存在提供了更多的灵活性并节省了性能。存储账户数据对于此扩展不是必需的,您可以按照不使用它的方式配置您的余额管理器。
查找账户当前余额
特定账户的当前金额始终可以计算为相关交易金额的总和。您可以使用 \Illuminatech\Balance\BalanceContract::calculateBalance()
方法来实现。
<?php use Illuminatech\Balance\Facades\Balance; Balance::transfer($fromAccount, $toAccount, 100); // assume this is first time accounts are affected echo Balance::calculateBalance($fromAccount); // outputs: -100 echo Balance::calculateBalance($toAccount); // outputs: 100
然而,每次需要时都计算当前余额并不高效。因此,您可以指定账户实体的一个属性,该属性将用于存储当前账户余额。这可以通过 \Illuminatech\Balance\Balance::$accountBalanceAttribute
来完成。每次余额管理器执行交易时,它将相应地更新此属性。
<?php use Illuminate\Support\Facades\DB; use Illuminatech\Balance\Facades\Balance; Balance::transfer($fromAccountId, $toAccountId, 100); // assume this is first time accounts are affected $currentBalance = DB::table('balance_accounts') ->select(['balance']) ->where(['id' => $fromAccountId]) ->value('balance'); echo $currentBalance; // outputs: -100
保存额外的交易数据
通常有必要在交易中保存额外信息。例如:我们可能需要保存从支付网关接收到的支付 ID。这可以通过以下方式实现
<?php use Illuminatech\Balance\Facades\Balance; $user = request()->user(); // simple increase : Balance::increase( [ 'userId' => $user->id, 'type' => 'virtual-money', ], 100, // extra data associated with transaction : [ 'paymentGateway' => 'PayPal', 'paymentId' => 'abcxyzerft', ] ); // transfer : Balance::transfer( [ 'userId' => $user->id, 'type' => 'payment-gateway', ], [ 'userId' => $user->id, 'type' => 'virtual-money', ], 100, // extra data associated with transaction : [ 'paymentGateway' => 'PayPal', 'paymentId' => 'abcxyzerft', ] );
额外属性在数据存储中的存储方式取决于特定的余额管理器实现。例如:\Illuminatech\Balance\BalanceDb
将尝试将额外数据存储在交易表的列中,如果它们的名称等于参数名称。您也可以通过设置特殊的数据字段来设置 \Illuminatech\Balance\BalanceDb::$dataAttribute
,它将以序列化的状态存储所有没有匹配列的额外参数。
注意:注意您在交易数据中使用的键:确保它们不会与其他用途保留的列(如主键)冲突。
每笔交易保存余额金额
在会计(簿记)中,有一种常见的做法是记录每笔完成的交易的新的余额金额。这种方法简化了余额转移动态的重建和查找可能的错误。您可以通过设置\Illuminatech\Balance\Balance::$newBalanceAttribute
的值,使用交易实体的属性名称,该属性应在交易完成后存储账户余额,来实现这种行为。例如
<?php use Illuminate\Support\Facades\DB; use Illuminatech\Balance\Facades\Balance; $accountId = 1; $lastTransactionQuery = DB::table('balance_transactions') ->where(['account_id' => $accountId]) ->orderBy('id', 'DESC'); Balance::increase($accountId, 50); // assume this is first time accounts is affected $lastTransaction = $lastTransactionQuery->first(); echo $lastTransaction->new_balance; // outputs: 50 Balance::increase($accountId, 25); $lastTransaction = $lastTransactionQuery->first(); echo $lastTransaction->new_balance; // outputs: 75 Balance::decrease($accountId, 50); $lastTransaction = $lastTransactionQuery->first(); echo $lastTransaction->new_balance; // outputs: 25
事件
\Illuminatech\Balance\Balance
提供了一些事件,可以通过事件监听器来处理
- \Illuminatech\Balance\Events\CreatingTransaction - 在创建新交易之前触发。
- \Illuminatech\Balance\Events\TransactionCreated - 在创建新交易之后触发。
例如
<?php use Illuminate\Support\Facades\Event; use Illuminatech\Balance\Facades\Balance; use Illuminatech\Balance\Events\TransactionCreated; use Illuminatech\Balance\Events\CreatingTransaction; Event::listen(CreatingTransaction::class, function (CreatingTransaction $event) { $event->data['amount'] += 10; // you may adjust transaction data to be saved, including transaction amount $event->data['comment'] = 'adjusted by event handler'; }); Event::listen(TransactionCreated::class, function (TransactionCreated $event) { echo 'new transaction: '.$event->transactionId; // you may get newly created transaction ID }); Balance::increase(1, 100); // outputs: 'new transaction: 1' echo Balance::calculateBalance(1); // outputs: 110