Giter VIP home page Giter VIP logo

balance's Introduction

Balance Accounting System extension for Laravel


This extension provides basic support for balance accounting (bookkeeping) system based on debit and credit principle.

For license information check the LICENSE-file.

Latest Stable Version Total Downloads Build Status

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist illuminatech/balance

or add

"illuminatech/balance": "*"

to the require section of your composer.json.

Usage

This extension provides basic support for balance accounting (bookkeeping) system based on debit and credit principle. Balance system is usually used for the accounting (bookkeeping) and money operations. However, it may also be used for any resource transferring from one location to another. For example: transferring goods from storehouse to the shop and so on.

There 2 main terms related to the balance system:

  • account - virtual storage of the resources, which have some logical meaning.
  • transaction - represents actual transfer of the resources to or from the particular account.

Lets assume we have a system, which provides virtual money balance for the user. Money on the balance can be used for the goods purchasing, user can top up his balance via some payment gateway. In such example, each user should have 3 virtual balance accounts: 'virtual-money', 'payment-gateway' and 'purchases'. When user tops up his virtual balance, our system should remove money from 'payment-gateway' and add them to 'virtual-money'. When user purchases an item, our system should remove money from 'virtual-money' and add them to 'purchases'. The trick is: if you sum current amount over all user related accounts ('payment-gateway' + 'virtual-money' + 'purchases'), it will always be equal to zero. Such check allows you to verify is something went wrong any time.

This extension introduces term 'balance manager' as a service, which should handle all balance transactions. Public contract for such manager is determined by \Illuminatech\Balance\BalanceContract interface. Following particular implementations are provided:

Please refer to the particular manager class for more details.

This extension provides \Illuminatech\Balance\BalanceServiceProvider service provider, which binds \Illuminatech\Balance\BalanceContract as a singleton in DI container. Thus you can get balance manager via automatic DI injections or via container instance. For example:

<?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);
        
        // ...
    }
    
    // ...
}

You may as well use \Illuminatech\Balance\Facades\Balance facade. For example:

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::increase($accountId, $amount);

In these documentation facade is used in code snippets for simplicity.

Application configuration

This extension uses illuminatech/array-factory for configuration. Make sure you are familiar with 'array factory' concept before configuring this extension. Configuration is stored at 'config/balance.php' file.

You can publish predefined configuration file using following console command:

php artisan vendor:publish --provider="Illuminatech\Balance\BalanceServiceProvider" --tag=config

In case you are using \Illuminatech\Balance\BalanceDb, you can publish predefined database migration for it using following console command:

php artisan vendor:publish --provider="Illuminatech\Balance\BalanceServiceProvider" --tag=migrations

Basic operations

In order to increase (debit) balance at particular account, \Illuminatech\Balance\BalanceContract::increase() method is used:

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::increase($accountId, 500); // add 500 credits to account

In order to decrease (credit) balance at particular account, \Illuminatech\Balance\BalanceContract:decrease() method is used:

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::decrease($accountId, 100); // remove 100 credits from account

Tip: actually, method decrease() is redundant, you can call increase() with negative amount in order to achieve the same result.

It is unlikely you will use plain increase() and decrease() methods in your application. In most cases there is a need to transfer money from one account to another at once. Method \Illuminatech\Balance\BalanceContract::transfer() can be used for this:

<?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

Note that method transfer() creates 2 separated transactions: one per each affected account. Thus you can easily fetch all money transfer history for particular account, simply selecting all transactions linked to it. 'Debit' transactions will have positive amount, while 'credit' ones - negative.

Note: If you wish each transaction created by transfer() remember another account involved in the process, you'll need to setup \Illuminatech\Balance\Balance::$extraAccountLinkAttribute.

You may revert particular transaction using \Illuminatech\Balance\BalanceContract::revert() method:

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::revert($transactionId);

This method will not remove original transaction, but create a new one, which compensates it.

Querying accounts

Using account IDs for the balance manager is not very practical. In our above example, each system user have 3 virtual accounts, each of which has its own unique ID. However, while performing purchase, we operate user ID and account type, so we need to query actual account ID before using balance manager. Thus there is an ability to specify account for the balance manager methods using their attributes set. For example:

<?php

use Illuminatech\Balance\Facades\Balance;

$user = request()->user();

Balance::transfer(
    [
        'userId' => $user->id,
        'type' => 'virtual-money',
    ],
    [
        'userId' => $user->id,
        'type' => 'purchases',
    ],
    500
);

In this example balance manager will find ID of the affected accounts automatically, using provided attributes as a filter.

You may enable \Illuminatech\Balance\Balance::$autoCreateAccount, allowing automatic creation of the missing accounts, if they are specified as attributes set. This allows accounts creation on the fly, by demand only, eliminating necessity of their pre-creation.

Heads up! Actually 'account' entity is redundant at balance system, and its usage can be avoided. However, its presence provides more flexibility and saves performance. Storing of account data is not mandatory for this extension, you can configure your balance manager in the way it is not used.

Finding account current balance

Current money amount at particular account can always be calculated as a sum of amounts over related transactions. You can use \Illuminatech\Balance\BalanceContract::calculateBalance() method for that:

<?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

However, calculating current balance each time you need it, is not efficient. Thus you can specify an attribute of account entity, which will be used to store current account balance. This can be done via \Illuminatech\Balance\Balance::$accountBalanceAttribute. Each time balance manager performs a transaction, it will update this attribute accordingly:

<?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

Saving extra transaction data

Usually there is a necessity to save extra information along with the transaction. For example: we may need to save payment ID received from payment gateway. This can be achieved in following way:

<?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',
    ]
);

The way extra attributes are stored in the data storage depends on particular balance manager implementation. For example: \Illuminatech\Balance\BalanceDb will try to store extra data inside transaction table columns, if their name equals the parameter name. You may as well setup special data field via \Illuminatech\Balance\BalanceDb::$dataAttribute, which will store all extra parameters, which have no matching column, in serialized state.

Note: watch for the keys you use in transaction data: make sure they do not conflict with columns, which are reserved for other purposes, like primary keys.

Saving balance amount per transaction

There is a common accounting (bookkeeping) practice to record new balance amount per each performed transaction. Such approach simplifies recreation of the balance transfers dynamics and search for possible errors. You can achieve such behavior by setting \Illuminatech\Balance\Balance::$newBalanceAttribute value with the name of transaction entity attribute, which should store account balance, which appears after this transaction has been performed. For example:

<?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

Events

\Illuminatech\Balance\Balance provides several events, which can be handled via event listener:

For example:

<?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

balance's People

Contributors

amaelftah avatar klimov-paul avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

balance's Issues

Managing multiple currency

Can this package support multiple currencies?

Should I use the account type eg 'gbp:virtual-money'? Or add a column to both tables of currency, and modify the core logic to check ->where('currency', $currency) etc?

Additional info

Q A
This Package Version 1.2.2
Laravel Framework Version 8.8.0
PHP version 7.3
Operating system Windows

Balance_accounts balance not updating

What steps will reproduce the problem?

Increase to an account with:

Balance::increase(
    [
        'user_id' => 3,
        'type' => 'virtual-money',
    ],
    100
);

What is the expected result?

The below should equal 100, But it equals 0. I can see the database table shows 0.

echo $currentBalance = \DB::table('balance_accounts')
    ->select(['balance'])
    ->where(['id' => 1])
    ->value('balance');

What do you get instead?

0 balance on accounts.

Additional info

Q A
This Package Version 1.2.2
Laravel Framework Version 8.8.0
PHP version 7.3
Operating system Windows

Multiple balance service.

Hello all,

How to use multiple balance service in 1 application?

For example:

Default is balance_accounts & balance_transactions during installation.
And I need to add another table like stock_accounts & stock_transactions and wanna use the same service.

Is it possible ?

Thank You.

Usage with Database Transaction

What steps will reproduce the problem?

Put the balance increase call in between DB::beginTransaction(); and DB::commit();

What is the expected result?

Transaction is not committed until DB::commit(); is called and is rolled back when DB::rollBack(); is called.

What do you get instead?

The transaction is committed even if an exception is thrown.

Additional info

Q A
This Package Version ^1.2
Laravel Framework Version ^8.40
PHP version 7.4.9
Operating system Windows 10

Creating balance_accounts and attempting to increase their balances does nothing

What steps will reproduce the problem?

i created balance accounts through the seeder and I tried to increase their balance without artisan tinker, I get returned a transaction ID but the table remains empty and the balance unchanged. I did it with the Balance facade and it's ::increase method

What is the expected result?

seeing transactions in the database and balance changed

What do you get instead?

balance remains 0, transactions table auto-increment increments but no existing rows remain + balance doesn't get recalculated

Additional info

Q A
This Package Version 1.3.0
Laravel Framework Version 9.7.0
PHP version 8.1
Operating system Mac OS

Auto account creation

What steps will reproduce the problem?

\Illuminatech\Balance\Facades\Balance::increase($user->id, 100);

What is the expected result?

Balance increase

What do you get instead?

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (tracking.backend.balance_transactions, CONSTRAINT balance_transactions_account_id_foreign FOREIGN KEY (account_id) REFERENCES balance_accounts (id) ON DELETE CASCADE ON UPDATE CASC) (SQL: insert into balance_transactions (created_at, amount, account_id) values (2020-08-28 11:45:47, 100, 1))

Additional info

Code increase balance is

public function increase($account, $amount, $data = [])
{
        $accountId = $this->fetchAccountId($account);
        ...
}

And if the variable account is an integer, then there is no automatic creation of an account if it does not exist

protected function fetchAccountId($idOrFilter)
{
        if (is_array($idOrFilter)) {
            $accountId = $this->findAccountId($idOrFilter);
            if ($accountId === null) {
                if ($this->autoCreateAccount) {
                    $accountId = $this->createAccount($idOrFilter);
                } else {
                    throw new InvalidArgumentException('Unable to find account matching filter: ' . print_r($idOrFilter, true));
                }
            }
        } else {
            $accountId = $idOrFilter; // Here
        }

        return $accountId;
}

There are no methods for obtaining an account, where to get an ID from or how to create one.
Perhaps I did not understand something about how to use it correctly, thanks

Q A
This Package Version 1.1.1
Laravel Framework Version 7.26.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.