GitHub

Every number about money traces to a row.

Payments are an append-only ledger, refunds are capped by the database, and a mismatch raises an alert instead of correcting itself.

PostgreSQLRedisS3StripeResend
admin.yourstore.com/orders/4192
Order #4192Reconciled
Payment captured
$248.00
Refund issued
−$40.00
Net
$208.00
Base currency, stored at purchase
GBP

The expensive bugs are quiet disagreements.

Two places that were meant to agree, and did not. A refund total that drifted. A cached figure nobody compared to its source. Nothing crashed, so nothing was noticed.

Nothing edits a row that already exists.

A correction adds a row rather than changing one. The history of an order's money stays complete, so reconciliation is a read rather than an investigation.

Ledger

Append-only
Refund approved · Dana Feld−$40.00

PaymentTransaction

newest
Order status
Derived

Stored column

paidshippeddelivered

From events

paidshippeddelivered
Jobs to run
0
Can go stale
no
Sources
1

Orders

Every order carries its payment, fulfilment and refund history on one record.

#DateStatusCustomerRevenue
112 MarPaid
Maya Okonkwo
$182.40
212 MarRefunded
Leo Martins
$96.00
311 MarPaid
Priya Shah
$248.10
411 MarDisputed
Dana Feld
$74.00
Showing 4 rowsRead straight from your database

Four things the database will not allow.

Refunds past the order total. Money going negative. A ledger currency that does not match the order. A refund request reaching two terminal states.

sum(refunds) <= amountTotalamount >= 0ledger.currency = order.currencyapproved | rejected | cancelled

Last quarter is the same number today.

Each order keeps its total converted to your base currency as it was on the day of purchase, so reporting does not move when exchange rates do.

Payments against refunds

8 months
PaymentsRefunds

A mismatch raises an alert instead of being rounded away.

Cached figures are checked against the rows behind them. Nothing self-corrects quietly — if a projection disagrees with its source, somebody finds out.

Drift alarms0 open
OrderMoneyDrift
clear
CustomerMoneyDrift
clear
InventoryLedgerDrift
clear

Four steps, four records.

Litestore never sits between you and the money. There is no platform balance to withdraw from.

Money in

  1. 1Checkout quotes discounts, shipping and taxone server pass
  2. 2Stripe captures into your own accountPaymentTransaction
  3. 3Stock moves for every lineStockMovement
  4. 4The order and its receipt are writtenOrder · EmailLog

A stored total is a promise nobody kept.

Where a figure has to be cached for speed, it is compared against its source and any gap becomes a row you can act on.

Stored against derived

Stored

A total column has to be updated by a job. When that misses, the screen is wrong and nobody knows.

Derived

Returns, reviews and payouts compute status from their own events, so there is nothing to keep up to date.

What ships with it.

29

kinds of task the store spots for you

10

block types to build any page from

5 min

and the storefront reorders itself

1,517

tests keeping the money right

70

tables, all yours to query

1

database — nothing to sync

1,517Tests

Across 151 files

25Architecture checks

Run in CI on every change

70Data models

One Prisma schema

0Open drift

Money, stock and customers

The guarantees are written down in the repository, and the database is what enforces them.

Corrections are new rows

Payment rows are never edited, so an order keeps its full payment history.

Stored goes stale

Status is worked out from the events behind it, so nothing has to be kept up to date.

Reporting from your own tables

Revenue and conversion are computed from the rows that fulfilled the orders.

Reconcile from the rows themselves.

Payments settle into your own Stripe account and the ledger is yours to query.

MIT licensedYour Stripe accountDirect SQL access