PHP code example of othmanhaba / ledger-core
1. Go to this page and download the library: Download othmanhaba/ledger-core library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
othmanhaba / ledger-core example snippets
return [
'tables' => [
'entities' => 'ledger_entities',
'accounts' => 'ledger_accounts',
'journal_entries' => 'journal_entries',
'journal_lines' => 'journal_lines',
'account_balances' => 'account_balances',
],
'models' => [
'entity' => LedgerEntity::class,
'account' => LedgerAccount::class,
'journal_entry' => JournalEntry::class,
'journal_line' => JournalLine::class,
'account_balance' => AccountBalance::class,
],
'currency' => [
'base_currency' => env('LEDGER_BASE_CURRENCY', 'USD'),
'
use LedgerCore\Data\CreateAccountData;
use LedgerCore\Data\JournalEntryData;
use LedgerCore\Data\JournalLineData;
use LedgerCore\Enums\AccountType;
use LedgerCore\Enums\NormalBalance;
use LedgerCore\Services\LedgerManager;
$ledger = app(LedgerManager::class);
$entity = $ledger->createEntity(
name: 'Main Book',
code: 'MAIN',
baseCurrency: 'USD',
);
$cash = $ledger->createAccount(new CreateAccountData(
ledgerEntityId: $entity->id,
name: 'Cash',
code: '1000',
type: AccountType::ASSET,
normalBalance: NormalBalance::DEBIT,
currency: 'USD',
));
$equity = $ledger->createAccount(new CreateAccountData(
ledgerEntityId: $entity->id,
name: 'Opening Balance Equity',
code: '3000',
type: AccountType::EQUITY,
normalBalance: NormalBalance::CREDIT,
currency: 'USD',
));
$entry = $ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'opening-cash:2026',
description: 'Opening cash balance',
lines: [
JournalLineData::debit($cash->id, '1000.00000000', 'USD'),
JournalLineData::credit($equity->id, '1000.00000000', 'USD'),
],
));
$balance = $ledger->getBalance($cash);
$entity = $ledger->createEntity(
name: 'Main Book',
code: 'MAIN',
type: 'operating',
baseCurrency: 'USD',
parentId: null,
metadata: [
'external_id' => 'book_123',
],
);
use LedgerCore\Data\CreateAccountData;
use LedgerCore\Enums\AccountType;
use LedgerCore\Enums\NormalBalance;
$bank = $ledger->createAccount(new CreateAccountData(
ledgerEntityId: $entity->id,
name: 'Bank Account',
code: '1010',
type: AccountType::ASSET,
normalBalance: NormalBalance::DEBIT,
currency: 'USD',
parentId: null,
isControlAccount: false,
isPostable: true,
allowNegative: false,
metadata: [
'note' => 'Primary settlement account',
],
));
$assets = $ledger->createAccount(new CreateAccountData(
ledgerEntityId: $entity->id,
name: 'Assets',
code: '1000',
type: AccountType::ASSET,
normalBalance: NormalBalance::DEBIT,
isControlAccount: true,
isPostable: false,
));
$cash = $ledger->createAccount(new CreateAccountData(
ledgerEntityId: $entity->id,
name: 'Cash',
code: '1010',
type: AccountType::ASSET,
normalBalance: NormalBalance::DEBIT,
parentId: $assets->id,
isPostable: true,
));
counterpartyType: User::class,
counterpartyId: $user->id,
use LedgerCore\Data\JournalEntryData;
use LedgerCore\Data\JournalLineData;
$entry = $ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'receipt:2026-000001',
referenceType: 'receipt',
referenceId: '2026-000001',
description: 'Receipt posted by host application',
lines: [
JournalLineData::debit(
accountId: $cashAccount->id,
amount: '250.00000000',
currency: 'USD',
memo: 'Cash received',
),
JournalLineData::credit(
accountId: $revenueAccount->id,
amount: '250.00000000',
currency: 'USD',
memo: 'Revenue recognized',
),
],
metadata: [
'source' => 'api',
],
postedAt: now(),
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'single-currency-example',
lines: [
JournalLineData::debit($cash->id, '100.00000000', 'USD'),
JournalLineData::credit($revenue->id, '100.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'split-entry-example',
lines: [
JournalLineData::debit($cash->id, '1000.00000000', 'USD'),
JournalLineData::credit($clearing->id, '980.00000000', 'USD'),
JournalLineData::credit($feeRevenue->id, '20.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'multi-currency-example',
lines: [
JournalLineData::debit(
accountId: $eurCash->id,
amount: '100.00000000',
currency: 'EUR',
baseAmount: '108.00000000',
exchangeRate: '1.080000000000',
),
JournalLineData::credit(
accountId: $usdClearing->id,
amount: '108.00000000',
currency: 'USD',
baseAmount: '108.00000000',
exchangeRate: '1.000000000000',
),
],
));
$ledger->postOpeningBalance(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'opening:cash:2026',
description: 'Opening cash balance',
lines: [
JournalLineData::debit($cash->id, '100000.00000000', 'USD'),
JournalLineData::credit($openingBalanceEquity->id, '100000.00000000', 'USD'),
],
));
$data = new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'external-event:' . $eventUuid,
lines: [
JournalLineData::debit($cash->id, '50.00000000', 'USD'),
JournalLineData::credit($revenue->id, '50.00000000', 'USD'),
],
);
$first = $ledger->post($data);
$second = $ledger->post($data);
// Same entry is returned when the payload is identical.
$first->id === $second->id;
use LedgerCore\Exceptions\IdempotencyConflictException;
try {
$ledger->post($differentPayloadWithSameKey);
} catch (IdempotencyConflictException $exception) {
report($exception);
}
$reversal = $ledger->reverse(
entry: $entry,
reason: 'Incorrect amount posted',
);
$balance = $ledger->getBalance($cash);
use LedgerCore\Services\BalanceService;
$balance = app(BalanceService::class)->getBalance($cash);
$account = $ledger->createAccount(new CreateAccountData(
ledgerEntityId: $entity->id,
name: 'Temporary Clearing',
type: AccountType::ASSET,
normalBalance: NormalBalance::DEBIT,
allowNegative: true,
));
$trialBalance = $ledger->getTrialBalance($entity->id);
$statement = $ledger->getStatement(
accountId: $cash->id,
from: now()->startOfMonth(),
to: now()->endOfMonth(),
);
use LedgerCore\Services\LedgerReportService;
$reports = app(LedgerReportService::class);
$trialBalance = $reports->trialBalance(
entityId: $entity->id,
from: now()->startOfYear(),
to: now(),
);
$generalLedger = $reports->generalLedger($entity->id, [
'account_id' => $cash->id,
'from' => now()->startOfMonth(),
'to' => now()->endOfMonth(),
'reference_type' => 'receipt',
'per_page' => 100,
]);
$statement = $reports->accountStatement(
accountId: $cash->id,
from: now()->subMonth(),
to: now(),
);
$balances = $reports->accountBalances($entity->id, [
'currency' => 'USD',
]);
use Filament\Panel;
use LedgerCore\LedgerCorePlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(LedgerCorePlugin::make());
}
'posting' => [
'allow_manual_entries_from_filament' => true,
],
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'cash-received:' . $event->uuid,
referenceType: 'cash_received',
referenceId: (string) $event->id,
description: 'Cash received',
lines: [
JournalLineData::debit($cashAccountId, '500.00000000', 'USD'),
JournalLineData::credit($clearingAccountId, '500.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'gross-receipt:' . $event->uuid,
referenceType: 'gross_receipt',
referenceId: (string) $event->id,
description: 'Gross receipt with fee split',
lines: [
JournalLineData::debit($cashAccountId, '1000.00000000', 'USD'),
JournalLineData::credit($clearingAccountId, '980.00000000', 'USD'),
JournalLineData::credit($feeRevenueAccountId, '20.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'expense:' . $expense->uuid,
referenceType: 'expense',
referenceId: (string) $expense->id,
description: 'Expense recognized',
lines: [
JournalLineData::debit($expenseAccountId, '75.00000000', 'USD'),
JournalLineData::credit($cashAccountId, '75.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'liability-settlement:' . $settlement->uuid,
referenceType: 'liability_settlement',
referenceId: (string) $settlement->id,
description: 'Liability settled',
lines: [
JournalLineData::debit($liabilityAccountId, '300.00000000', 'USD'),
JournalLineData::credit($cashAccountId, '300.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'currency-exchange:' . $exchange->uuid,
referenceType: 'currency_exchange',
referenceId: (string) $exchange->id,
description: 'Currency exchange executed',
lines: [
JournalLineData::debit(
accountId: $usdCashAccountId,
amount: '1000.00000000',
currency: 'USD',
baseAmount: '1000.00000000',
exchangeRate: '1.000000000000',
memo: 'USD received',
),
JournalLineData::credit(
accountId: $lydCashAccountId,
amount: '4800.00000000',
currency: 'LYD',
baseAmount: '990.00000000',
exchangeRate: '0.206250000000',
memo: 'LYD delivered',
),
JournalLineData::credit(
accountId: $exchangeRevenueAccountId,
amount: '10.00000000',
currency: 'USD',
baseAmount: '10.00000000',
exchangeRate: '1.000000000000',
memo: 'Exchange spread',
),
],
metadata: [
'rate_source' => 'host_application',
'quoted_rate' => '4.800000000000',
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'order-payment-captured:' . $order->uuid,
referenceType: 'order',
referenceId: (string) $order->id,
description: 'Order payment captured',
lines: [
JournalLineData::debit(
accountId: $paymentClearingAccountId,
amount: $order->total_amount,
currency: $order->currency,
),
JournalLineData::credit(
accountId: $customerDepositAccountId,
amount: $order->total_amount,
currency: $order->currency,
),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'order-fulfilled:' . $order->uuid,
referenceType: 'order',
referenceId: (string) $order->id,
description: 'Order fulfilled and revenue recognized',
lines: [
JournalLineData::debit(
accountId: $customerDepositAccountId,
amount: $order->total_amount,
currency: $order->currency,
),
JournalLineData::credit(
accountId: $salesRevenueAccountId,
amount: $order->total_amount,
currency: $order->currency,
),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'order-refund:' . $refund->uuid,
referenceType: 'order_refund',
referenceId: (string) $refund->id,
description: 'Order refund posted',
lines: [
JournalLineData::debit(
accountId: $salesReturnsAccountId,
amount: $refund->amount,
currency: $refund->currency,
),
JournalLineData::credit(
accountId: $paymentClearingAccountId,
amount: $refund->amount,
currency: $refund->currency,
),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'platform-collection:' . $collection->uuid,
referenceType: 'platform_collection',
referenceId: (string) $collection->id,
description: 'Platform collection with fee',
lines: [
JournalLineData::debit($cashAccountId, '1000.00000000', 'USD'),
JournalLineData::credit($counterpartyPayableAccountId, '950.00000000', 'USD'),
JournalLineData::credit($platformFeeRevenueAccountId, '50.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'platform-payout:' . $payout->uuid,
referenceType: 'platform_payout',
referenceId: (string) $payout->id,
description: 'Platform payout sent',
lines: [
JournalLineData::debit($counterpartyPayableAccountId, '950.00000000', 'USD'),
JournalLineData::credit($cashAccountId, '950.00000000', 'USD'),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'prepayment-received:' . $payment->uuid,
referenceType: 'prepayment',
referenceId: (string) $payment->id,
description: 'Customer prepayment received',
lines: [
JournalLineData::debit($cashAccountId, $payment->amount, $payment->currency),
JournalLineData::credit($customerDepositAccountId, $payment->amount, $payment->currency),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'prepayment-applied:' . $application->uuid,
referenceType: 'prepayment_application',
referenceId: (string) $application->id,
description: 'Customer prepayment applied',
lines: [
JournalLineData::debit($customerDepositAccountId, $application->amount, $application->currency),
JournalLineData::credit($revenueAccountId, $application->amount, $application->currency),
],
));
$ledger->post(new JournalEntryData(
ledgerEntityId: $entity->id,
idempotencyKey: 'reclassification:' . $adjustment->uuid,
referenceType: 'reclassification',
referenceId: (string) $adjustment->id,
description: 'Internal account reclassification',
lines: [
JournalLineData::debit($correctAccountId, '125.00000000', 'USD'),
JournalLineData::credit($originalAccountId, '125.00000000', 'USD'),
],
metadata: [
'approved_by' => $adjustment->approved_by,
],
));
namespace App\Services;
use App\Models\Receipt;
use LedgerCore\Data\JournalEntryData;
use LedgerCore\Data\JournalLineData;
use LedgerCore\Services\LedgerManager;
final readonly class ReceiptPostingService
{
public function __construct(private LedgerManager $ledger)
{
}
public function postReceipt(Receipt $receipt): void
{
$this->ledger->post(new JournalEntryData(
ledgerEntityId: $receipt->ledger_entity_id,
idempotencyKey: 'receipt:' . $receipt->uuid,
referenceType: 'receipt',
referenceId: (string) $receipt->id,
description: 'Receipt posted',
lines: [
JournalLineData::debit(
accountId: $receipt->cash_account_id,
amount: $receipt->amount,
currency: $receipt->currency,
),
JournalLineData::credit(
accountId: $receipt->clearing_account_id,
amount: $receipt->amount,
currency: $receipt->currency,
),
],
));
}
}
use Illuminate\Support\ServiceProvider;
use LedgerCore\Contracts\PostingValidatorContract;
final class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
PostingValidatorContract::class,
\App\Ledger\AppPostingValidator::class,
);
}
}
namespace App\Ledger;
use LedgerCore\Contracts\PostingValidatorContract;
use LedgerCore\Data\JournalEntryData;
use LedgerCore\Exceptions\LedgerException;
final class AppPostingValidator implements PostingValidatorContract
{
public function validate(JournalEntryData $entry): void
{
if ($entry->idempotencyKey === '') {
throw new LedgerException('Idempotency key is
use LedgerCore\Contracts\AccountResolverContract;
$this->app->bind(AccountResolverContract::class, AppAccountResolver::class);
use LedgerCore\Contracts\CurrencyConverterContract;
$this->app->bind(CurrencyConverterContract::class, AppCurrencyConverter::class);
'models' => [
'account' => App\Models\LedgerAccount::class,
],
use LedgerCore\Events\JournalEntryPosting;
Event::listen(JournalEntryPosting::class, function (JournalEntryPosting $event): void {
logger()->info('Posting journal entry', [
'idempotency_key' => $event->data->idempotencyKey,
]);
});
use LedgerCore\Events\JournalEntryPosted;
Event::listen(JournalEntryPosted::class, function (JournalEntryPosted $event): void {
logger()->info('Journal entry posted', [
'entry_id' => $event->entry->id,
]);
});
use LedgerCore\Events\JournalEntryReversed;
Event::listen(JournalEntryReversed::class, function (JournalEntryReversed $event): void {
logger()->info('Journal entry reversed', [
'original_id' => $event->original->id,
'reversal_id' => $event->reversal->id,
]);
});
use LedgerCore\Exceptions\AccountCurrencyMismatchException;
use LedgerCore\Exceptions\InsufficientBalanceException;
use LedgerCore\Exceptions\UnbalancedJournalEntryException;
try {
$ledger->post($entryData);
} catch (UnbalancedJournalEntryException $exception) {
// Show validation feedback or fail the job permanently.
} catch (AccountCurrencyMismatchException $exception) {
// Fix account mapping or currency selection.
} catch (InsufficientBalanceException $exception) {
// Stop the workflow or route it to review.
}
$account->balance()->update(['balance' => '100.00000000']);
$accounts = app(AccountMappingService::class)->forReceipt($receipt);
$ledger->post(new JournalEntryData(
ledgerEntityId: $receipt->ledger_entity_id,
idempotencyKey: 'receipt:' . $receipt->uuid,
lines: [
JournalLineData::debit($accounts->cash, $receipt->amount, $receipt->currency),
JournalLineData::credit($accounts->clearing, $receipt->amount, $receipt->currency),
],
));
DB::transaction(function () use ($receipt, $ledger): void {
$receipt->markAsPosted();
$ledger->post(new JournalEntryData(
ledgerEntityId: $receipt->ledger_entity_id,
idempotencyKey: 'receipt:' . $receipt->uuid,
lines: [
JournalLineData::debit($receipt->cash_account_id, $receipt->amount, $receipt->currency),
JournalLineData::credit($receipt->clearing_account_id, $receipt->amount, $receipt->currency),
],
));
});
referenceType: 'receipt',
referenceId: (string) $receipt->id,
$ledger->createEntity(...);
$ledger->createAccount(CreateAccountData $data);
$ledger->post(JournalEntryData $data);
$ledger->postOpeningBalance(JournalEntryData $data);
$ledger->reverse(JournalEntry $entry, ?string $reason = null);
$ledger->getBalance(LedgerAccount|int|string $account);
$ledger->getStatement($accountId, $from = null, $to = null);
$ledger->getTrialBalance($entityId, $from = null, $to = null);
$accounts->create(CreateAccountData $data);
$accounts->findOrFail($id);
$accounts->ensurePostable($account);
$accounts->ensureCurrencyCompatible($account, $currency);
$posting->post(JournalEntryData $data);
$balances->initializeBalance($account);
$balances->applyLine($line);
$balances->getBalance($account);
$balances->assertSufficientBalance($account, $amount);
$reversals->reverse($entry, $reason);
$reports->trialBalance($entityId, $from = null, $to = null);
$reports->generalLedger($entityId, $filters);
$reports->accountStatement($accountId, $from = null, $to = null);
$reports->accountBalances($entityId, $filters);
bash
php artisan migrate