settleup/ledger
最新稳定版本:0.2.0
Composer 安装命令:
composer require settleup/ledger
包简介
A general-purpose, double-entry, append-only financial ledger for Laravel.
README 文档
README
A general-purpose, double-entry, append-only financial ledger for Laravel.
Introduction
Ledger is a financial record-keeping package for Laravel applications that need a reliable audit trail of monetary events. It implements double-entry bookkeeping: every transaction contains debit and credit entries that must balance, ensuring your books always add up.
The ledger is append-only. Entries are never modified or deleted. To correct a mistake, you post a new reversing transaction. This gives you a complete, tamper-evident history of every financial event in your system.
Ledger provides the accounting infrastructure -- accounts, transactions, entries, balances, and fiscal periods -- while your application defines the specific accounts and business logic. It ships with a hierarchical chart of accounts: five standard categories (asset, liability, equity, revenue, expense) organised into group and detail accounts using numeric codes. Multi-currency is supported out of the box, with each entry storing both the original currency amount and a base currency equivalent.
Installation
Install the package via Composer:
composer require trysettleup/ledger
Publish and run the migrations:
php artisan vendor:publish --tag="ledger-migrations"
php artisan migrate
Publish the config file:
php artisan vendor:publish --tag="ledger-config"
Configuration
The published config file lives at config/ledger.php:
return [ // The base currency for balance calculations. All entry amounts are // converted to this currency using the entry's exchange rate. 'base_currency' => 'USD', // Default exchange rate applied when none is specified on an entry. 'default_exchange_rate' => 1.0, // Number of characters per hierarchy level in account codes. Codes must // be numeric strings whose length is a positive multiple of this value. // With code_depth of 2: '10' = level 1, '1010' = level 2, '101010' = level 3. 'code_depth' => 2, ];
Usage
Account Hierarchy
Ledger uses a hierarchical chart of accounts with two account types:
- Group accounts organise the chart into a tree. They cannot receive entries -- their balance is computed by rolling up descendant detail accounts.
- Detail accounts are the leaf nodes. They receive entries and maintain a denormalized running balance.
Account codes are numeric strings where the hierarchy level is determined by the configured code_depth. With the default code_depth of 2:
10 Assets (group, level 1)
1010 Current Assets (group, level 2)
101010 Cash (detail, level 3)
101020 Accounts Receivable (detail, level 3)
1020 Fixed Assets (group, level 2)
102010 Equipment (detail, level 3)
Creating Accounts
Create accounts using the Ledger facade. Every account belongs to one of five categories and is either a group or detail account:
use Money\Currency; use SettleUp\Ledger\Enums\AccountCategory; use SettleUp\Ledger\Facades\Ledger; // Create a group account to organise detail accounts beneath it $assets = Ledger::createGroup( code: '10', name: 'Assets', category: AccountCategory::Asset, currency: new Currency('USD'), ); $currentAssets = Ledger::createGroup( code: '1010', name: 'Current Assets', category: AccountCategory::Asset, currency: new Currency('USD'), ); // Create detail (posting) accounts -- these receive entries $cashAccount = Ledger::createDetail( code: '101010', name: 'Cash', category: AccountCategory::Asset, currency: new Currency('USD'), ); $revenueAccount = Ledger::createDetail( code: '4010', name: 'Service Revenue', category: AccountCategory::Revenue, currency: new Currency('USD'), );
The five account categories are: Asset, Liability, Equity, Revenue, and Expense. Asset and Expense accounts have normal debit balances; Liability, Equity, and Revenue accounts have normal credit balances.
Fiscal Periods
Transactions can only be posted within an open fiscal period. Create periods before posting:
use SettleUp\Ledger\Facades\Ledger; $period = Ledger::createPeriod( name: 'Q1 2025', startDate: now()->startOfQuarter(), endDate: now()->endOfQuarter(), ); // Close a period to prevent further posting Ledger::closePeriod($period); // Reopen if needed Ledger::reopenPeriod($period);
Periods cannot overlap. All period status changes are audited with timestamps and an optional actor.
Posting Transactions
Post a balanced transaction using EntryLineItem objects. Debits must equal credits in base currency:
use Money\Money; use SettleUp\Ledger\Facades\Ledger; use SettleUp\Ledger\Support\EntryLineItem; $transaction = Ledger::post( description: 'Invoice #1042 payment received', date: now(), entries: [ EntryLineItem::debit($cashAccount, Money::USD(5000)), EntryLineItem::credit($revenueAccount, Money::USD(5000)), ], ); // $transaction->uuid -- unique identifier // $transaction->entries -- collection of Entry models
You can attach arbitrary metadata and an actor (the authenticated user who performed the action):
$transaction = Ledger::post( description: 'Subscription renewal', date: now(), entries: [ EntryLineItem::debit($cashAccount, Money::USD(9900)), EntryLineItem::credit($revenueAccount, Money::USD(9900)), ], metadata: ['invoice_id' => 1042, 'plan' => 'pro'], actor: $user, );
Multi-Currency
For entries in a currency other than the base currency, provide an exchange rate. The base currency amount is computed automatically:
use Money\Money; use SettleUp\Ledger\Facades\Ledger; use SettleUp\Ledger\Support\EntryLineItem; // Record a EUR 100.00 payment when EUR/USD = 1.08 $transaction = Ledger::post( description: 'Payment from EU customer', date: now(), entries: [ EntryLineItem::debit($cashEurAccount, Money::EUR(10000), exchangeRate: 1.08), EntryLineItem::credit($revenueAccount, Money::USD(10800)), ], );
Each entry stores the original currency amount, the exchange rate, and the computed base currency equivalent. The entry currency must match the account's currency.
Reversals
Since the ledger is append-only, corrections are made by reversing a transaction. This posts a new transaction with opposite entry types:
use SettleUp\Ledger\Facades\Ledger; $reversal = Ledger::reverse($transaction, 'Reverse: duplicate payment'); // The reversal is a new Transaction with entries mirroring the original // but with debits and credits swapped.
Rollup Balances
Query the rolled-up balance of any account. For group accounts, this sums all descendant detail account balances. For detail accounts, it returns the account's own balance:
use SettleUp\Ledger\Facades\Ledger; // Get the rolled-up balance for a group account (sums all descendants) $assetsBalance = Ledger::rollupBalance($assets); // Money object in base currency // Get a detail account's balance $cashBalance = Ledger::rollupBalance($cashAccount);
Rebuilding Balances
If you ever need to recompute account balances from scratch (e.g., after a data repair):
use SettleUp\Ledger\Facades\Ledger; // Rebuild a single account Ledger::rebuildBalance($cashAccount); // Rebuild all accounts Ledger::rebuildBalances();
Account Lifecycle
Accounts go through three statuses: active, inactive, and archived. Only active accounts can receive entries.
use SettleUp\Ledger\Facades\Ledger; // Deactivate an account (prevents new entries) Ledger::deactivateAccount($account); // Re-activate it Ledger::activateAccount($account); // Archive permanently Ledger::archiveAccount($account);
When deactivating or archiving a group account, the status change cascades to all descendants. Activation does not cascade -- children must be re-activated individually.
Polymorphic References
Attach transactions to any Eloquent model by implementing the LedgerReferenceable interface and adding the HasLedgerTransactions trait:
use Illuminate\Database\Eloquent\Model; use SettleUp\Ledger\Concerns\HasLedgerTransactions; use SettleUp\Ledger\Contracts\LedgerReferenceable; class Invoice extends Model implements LedgerReferenceable { use HasLedgerTransactions; }
Then pass the model when posting:
$invoice = Invoice::find(1); $transaction = Ledger::post( description: 'Invoice #1 payment', date: now(), entries: [ EntryLineItem::debit($cashAccount, Money::USD(5000)), EntryLineItem::credit($revenueAccount, Money::USD(5000)), ], referenceable: $invoice, ); // Later, query all ledger transactions for an invoice: $invoice->ledgerTransactions;
Account Ownership
Assign accounts to Eloquent models (e.g., customers or vendors) by implementing the LedgerAccountOwner interface:
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphMany; use SettleUp\Ledger\Contracts\LedgerAccountOwner; use SettleUp\Ledger\Models\Account; class Customer extends Model implements LedgerAccountOwner { public function ledgerAccounts(): MorphMany { return $this->morphMany(Account::class, 'owner'); } }
Then pass the owner when creating accounts:
$account = Ledger::createDetail( code: '101010', name: 'Customer Receivable', category: AccountCategory::Asset, currency: new Currency('USD'), owner: $customer, );
Events
The package dispatches events at key points so you can hook into the lifecycle:
| Event | When it fires |
|---|---|
AccountDetailCreated |
After a detail account is created |
AccountGroupCreated |
After a group account is created |
AccountStatusChanged |
After an account status transitions (active/inactive/archived) |
TransactionPosted |
After a transaction is posted and balances are updated |
TransactionReversed |
After a transaction is reversed (fires in addition to TransactionPosted) |
PeriodCreated |
After a fiscal period is created |
PeriodClosed |
After a fiscal period is closed |
PeriodReopened |
After a fiscal period is reopened |
All events are in the SettleUp\Ledger\Events namespace.
Testing
Using the Fake
Swap the ledger for an in-memory fake in your tests. The fake validates entries the same way (balanced transactions, active accounts, period constraints, currency matching) but does not touch the database:
use SettleUp\Ledger\Facades\Ledger; // In your test setUp or at the start of a test: $fake = Ledger::fake(); // ... run your application code that posts transactions ... // Assert a transaction was posted $fake->assertPosted(); // Assert with a callback $fake->assertPosted(fn ($transaction) => $transaction->description === 'Invoice #1042 payment received' ); // Assert nothing was posted $fake->assertNothingPosted(); // Assert exact count $fake->assertPostedCount(2); // Assert a reversal occurred $fake->assertReversed(); $fake->assertReversed(fn ($original, $reversal) => $original->description === 'Invoice #1042 payment received' ); // Assert no reversals $fake->assertNotReversed(); // Assert account creation $fake->assertAccountCreated(); $fake->assertAccountCreated(fn ($account) => $account->code === '101010'); $fake->assertNotAccountCreated(); // You can also query balances on the fake $balance = Ledger::rollupBalance($account);
Running Package Tests
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
settleup/ledger 适用场景与选型建议
settleup/ledger 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 0 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 14 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「laravel」 「ledger」 「SettleUp」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 settleup/ledger 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 settleup/ledger 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 settleup/ledger 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
General Ledger and Journal Accounting Package for Laravel with JSON API
PHP SDK for Ledga.io - Programmatic double-entry ledgers for finance, gaming and multi-tenant SaaS
ledger implementation for laravel
Alfabank REST API integration
Accounting library
Sie5 SDK, based on Sie5 XSD schema
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 28
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-14