Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Accounting for developers part III – building a lending marketplace (moderntreasury.com)
152 points by mattmcf on Oct 20, 2022 | hide | past | favorite | 16 comments


I still don't understand why they encourage double-entry accounting for tech. In my experience entries like:

| amount | payer_id | payee_id |

| 100 | 1 | 2 |

are more than sufficient. He uses Square, Uber, and Airbnb as examples in https://www.moderntreasury.com/journal/accounting-for-develo... but, as far as I can tell, only Square uses double-entry.

Double entry accounting was for when people were hand-writing a ledger and it helped avoid mistakes. It makes no sense in code and it just makes your DB queries more complicated.


What happens if you need to split that $100 from payer_id 1 into 2 payer_id 2's?

I.e. that $100 is actually:

$99 from 1 to 2 and $1(let's say in taxes) to payer_id 3.

The easy answer is, make it 2 transaction, $99 to 2 and $1 to 3, but then you can't tell that the $1 in taxes is from the $99 transaction. That leads to headaches when you need to balance your taxes.


This is just the ledger table. In your case you would have a payment event table with one row and a join table that connects that event with all the ledger rows.

That's what Airbnb seems to do: https://medium.com/airbnb-engineering/tracking-the-money-sca...


Or just go full double entry and allow multiple accounts per transaction, which is what anyone with an accounting degree would tell you to do.

The bonus of doing it this way, everyone with an accounting degree won't gasp in horror when they understand what it is you did.

The events table in your example is basically the same as a transaction table and dump the transaction_id into the ledger table. The bonus of doing it with a 'transaction_id' is accountants, auditors and the rest won't be confused.


We’ve built our own in house ledger and also use double entry accounting. When one transaction affects multiple accounts, you need to ensure the books balance, which the premise of double entry accounting.

It’s the standard for a reason. Even when using a software like Quickbooks, errors can come up.


One good technical reason to prefer double entry and relating to the "it helped avoid mistakes [in hand-writing ledgers]" is that double entry patterns encourage better database transaction discipline and provide helpful debugging clues when database transactions half-finish and accidentally commit rather than rollback. Atomic database transactions with single row inserts of accounting transactions are obviously simpler to build, but it also means you are less likely to test multi-row database transactions such as scenarios where you need to make sure an accounting transaction lands in an atomic commit with say a purchasing invoice row. Double entry accounting, especially when different types of accounts may be in different tables forces your codebase to use good database transactions and sometimes more thorough testing of your commit versus rollback code paths.

Also, in my experience double-entry tables are often much easier to DB query because the tables "naturally" look exactly like the ledgers accountants expect to see in reports. The math in aggregate queries is often just SUM and nothing else, no complicated case logic for "different transaction types" or complicated pivots from transactions to account views and back.


Not sure I follow: you're claiming that double entry forces you to write things with better transactions in mind but the only difference between the two is having 1 vs. 2 rows.

Say we have a hotel reservation system, it looks like this in my proposition:

1. Wrap a transaction around rows

2. Add reservation event

3. Add one ledger row for payment

4. Add one ledger row for tax

vs. double entry:

1. Wrap a transaction around rows:

2. Add reservation event

3. Add two ledger rows for payment

4. Add two ledger rows for tax


You can think of double entry as:

1. Create a reservation liability (invoice) [debit the reservation cost]

2. Create a tax liability (tax account)

The preceding may happen asynchronously ahead of the time. The following are generally wrapped in a single database transaction:

3. Credit a user's account for value of (total) payment

(3a. Depending on how deep your double-entry goes, Debit your Payment Processor)

4. Debit the user's account the reservation amount

5. Credit the reservation account the reservation amount (reservation invoice account should sum to 0 now: no assets, nothing liable)

6. Add reservation event for completed invoice

7. Debit the user's account the tax amount

8. Credit the tax account the tax amount (user's account should sum to 0 now: no assets, nothing liable)

It's been a while since I've worked on a system in this mindset, so apologies if I've got subtle things wrong or debit/credit backwards. That's still too easy to do even when you've done it a million times and even when you aren't rusty years later.

It's far more than "just two rows extra" for proper double entry with the right number of "accounts" and a lot more things form "accounts" than you think. Some of those accounts should read 0 after a proper transaction. Those become red flags if a transaction failed in the middle, and debugging tools when that happens (is it an extra credit or an extra debit, the account positive or negative/owed something or owing something, that can be very useful information to have on the failed database transaction).

It seems like a lot of extra steps, but each step should also reflect your domain model in useful ways: don't create new reservations until a reservation invoice has been filled/paid for. Even in cases where you are doing direct sales to a customer and don't think you need "proper" invoices and that the transactions existing in the first place are good enough, it's still sometimes a useful domain model to have that invoice concept and rather than "fire off" events like "Create Reservation" purely on the sales transaction, have a stronger "state model" with an "Invoice Completion" step. It's an abstraction that sometimes gains you some peace of mind that you don't accidentally make a new reservation from an over-payment or a payment intended for a different purpose.


Isn't your example double entry? It shows both accounts involved in the transaction and the direction of the transaction.


Double-entry is recording the credits and debits in a column and each account change happens on a row.

So if I change the ledger in my parent comment to double entry it would look like:

| account_id | credit_amount | debit_amount |

| 1 | 0 | 100 |

| 2 | 100 | 0 |


what do you mean dont use double entry and use a DB query instead?

if your customer is paying you, how will you record that transaction?


Having contributed to a project that needed to apply these principles in a previous role, it can’t be understated how important it is to understand these concepts.

One thing I found interesting though is that one feature of double entry accounting is creating a paper trail where some lost data can be reconstructed if necessary with redundancies. For example if you lose the physical pages detailing a transaction, you can figure out what happened based on data contained in another page. A problem we ran into was how to reconcile that methodology with how this would be solved in code (e.g. DB backups).


You should take a look at SAP's source code


Modern Treasury blogs are always full of gold nuggets. Is there RSS feed for this?


yep! https://www.moderntreasury.com/journal/rss.xml

You can also get automatic emails


Would love to see development for accountants so everytime marketing needs a new tool integrated to run a report they can see the time it takes to provision, implement, test, release, and then audit it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: