|
| 1 | +-- This file contains the sql queries plus their output, but we set the filetype to sql for better syntax highlighting |
| 2 | +-- vim: set filetype=sql: |
| 3 | + |
| 4 | +-- This is a fully working example script that shows how to use pgledger |
| 5 | +-- |
| 6 | +-- Note that it uses `\gset` to store sql responses as variables. For example, |
| 7 | +-- `\gset foo_` creates variables for each column in the response like |
| 8 | +-- `foo_col1`, `foo_col2`, etc. These variables can then be used like |
| 9 | +-- `:'foo1_col`. |
| 10 | +-- The entire script can be passed to psql. If you are running postgres via the |
| 11 | +-- pgledger docker compose, you can run this script with: |
| 12 | +-- |
| 13 | +-- cat basic-example.sql | \ |
| 14 | +-- docker compose exec --no-TTY postgres psql -U pgledger --echo-queries --no-psqlrc |
| 15 | +-- |
| 16 | +-- We're going to simulate a simple payment flow. First, we create our accounts: |
| 17 | +SELECT id FROM pgledger_create_account('user1.external', 'USD') \gset user1_external_ |
| 18 | +SELECT id FROM pgledger_create_account('user1.receivables', 'USD') \gset user1_receivables_ |
| 19 | +SELECT id FROM pgledger_create_account('user1.available', 'USD') \gset user1_available_ |
| 20 | +SELECT id FROM pgledger_create_account('user1.pending_outbound', 'USD') \gset user1_pending_outbound_ |
| 21 | +-- We can query an account to see what it looks like at the beginning. |
| 22 | +SELECT * FROM pgledger_accounts_view |
| 23 | +WHERE id =:'user1_external_id'; |
| 24 | + id | name | currency | balance | version | allow_negative_balance | allow_positive_balance | created_at | updated_at |
| 25 | +---------------------------------+----------------+----------+---------+---------+------------------------+------------------------+-------------------------------+------------------------------- |
| 26 | + pgla_01K398HBM8EAPBGD23PGR118JA | user1.external | USD | 0 | 0 | t | t | 2025-08-22 16:07:09.702979+00 | 2025-08-22 16:07:09.702979+00 |
| 27 | +(1 row) |
| 28 | + |
| 29 | +-- The first step in the flow is a $50 payment is created and we are waiting for funds to arrive: |
| 30 | +SELECT * FROM pgledger_create_transfer(:'user1_external_id',:'user1_receivables_id', 50.00); |
| 31 | + id | from_account_id | to_account_id | amount | created_at | event_at |
| 32 | +---------------------------------+---------------------------------+---------------------------------+--------+-------------------------------+------------------------------- |
| 33 | + pglt_01K398HBMCECNRPT2S9M0MCE42 | pgla_01K398HBM8EAPBGD23PGR118JA | pgla_01K398HBM9EYB8WNG5B2N031VS | 50.00 | 2025-08-22 16:07:09.706692+00 | 2025-08-22 16:07:09.706692+00 |
| 34 | +(1 row) |
| 35 | + |
| 36 | +-- Next, the funds arrive in our account, so we remove them from receivables and make them available: |
| 37 | +SELECT * FROM pgledger_create_transfer(:'user1_receivables_id',:'user1_available_id', 50.00); |
| 38 | + id | from_account_id | to_account_id | amount | created_at | event_at |
| 39 | +---------------------------------+---------------------------------+---------------------------------+--------+-------------------------------+------------------------------- |
| 40 | + pglt_01K398HBMDFGTRV884F9P83FF4 | pgla_01K398HBM9EYB8WNG5B2N031VS | pgla_01K398HBM9FRTVBHCAXZ37RB85 | 50.00 | 2025-08-22 16:07:09.709565+00 | 2025-08-22 16:07:09.709565+00 |
| 41 | +(1 row) |
| 42 | + |
| 43 | +-- Now, we can query the accounts and see the balances. We aren't waiting on |
| 44 | +-- any more funds, so the receivables balance is 0: |
| 45 | +SELECT balance FROM pgledger_accounts_view |
| 46 | +WHERE id =:'user1_receivables_id'; |
| 47 | + balance |
| 48 | +--------- |
| 49 | + 0.00 |
| 50 | +(1 row) |
| 51 | + |
| 52 | +-- And we can see the entries for the receivables account: |
| 53 | +SELECT * FROM pgledger_entries_view |
| 54 | +WHERE account_id =:'user1_receivables_id' |
| 55 | +ORDER BY account_version; |
| 56 | + id | account_id | transfer_id | amount | account_previous_balance | account_current_balance | account_version | created_at | event_at |
| 57 | +---------------------------------+---------------------------------+---------------------------------+--------+--------------------------+-------------------------+-----------------+-------------------------------+------------------------------- |
| 58 | + pgle_01K398HBMDEFFVVH0981XGBTDA | pgla_01K398HBM9EYB8WNG5B2N031VS | pglt_01K398HBMCECNRPT2S9M0MCE42 | 50.00 | 0.00 | 50.00 | 1 | 2025-08-22 16:07:09.706692+00 | 2025-08-22 16:07:09.706692+00 |
| 59 | + pgle_01K398HBMDFSDBJ43MKBX8FADZ | pgla_01K398HBM9EYB8WNG5B2N031VS | pglt_01K398HBMDFGTRV884F9P83FF4 | -50.00 | 50.00 | 0.00 | 2 | 2025-08-22 16:07:09.709565+00 | 2025-08-22 16:07:09.709565+00 |
| 60 | +(2 rows) |
| 61 | + |
| 62 | +-- Continuing the example, let's issue a partial refund of the payment. When we |
| 63 | +-- issue the refund, we move the money into the pending_outbound account to |
| 64 | +-- hold it until we get confirmation that it was sent |
| 65 | +SELECT * FROM pgledger_create_transfer(:'user1_available_id',:'user1_pending_outbound_id', 20.00); |
| 66 | + id | from_account_id | to_account_id | amount | created_at | event_at |
| 67 | +---------------------------------+---------------------------------+---------------------------------+--------+-------------------------------+------------------------------- |
| 68 | + pglt_01K398HBMEFHQRQ2CKPRET4M9V | pgla_01K398HBM9FRTVBHCAXZ37RB85 | pgla_01K398HBMAED49QW4QJ6HM4M0B | 20.00 | 2025-08-22 16:07:09.710622+00 | 2025-08-22 16:07:09.710622+00 |
| 69 | +(1 row) |
| 70 | + |
| 71 | +-- Once we get confirmation that that refund was sent, We can move the money |
| 72 | +-- back to the user's external account (e.g. their credit/debit card). Often, |
| 73 | +-- this confirmation will come as a webhook or bank file or similar, so we can |
| 74 | +-- record the event time in the confirmation separately from the time we record |
| 75 | +-- the ledger transfer (event_at vs created_at): |
| 76 | +SELECT * |
| 77 | +FROM |
| 78 | + pgledger_create_transfer( |
| 79 | + :'user1_pending_outbound_id', |
| 80 | + :'user1_external_id', |
| 81 | + 20.00, |
| 82 | + event_at => '2025-07-21T12:45:54.123Z' |
| 83 | + ); |
| 84 | + id | from_account_id | to_account_id | amount | created_at | event_at |
| 85 | +---------------------------------+---------------------------------+---------------------------------+--------+-------------------------------+---------------------------- |
| 86 | + pglt_01K398HBMFETR9XDE61HQ2NH8Q | pgla_01K398HBMAED49QW4QJ6HM4M0B | pgla_01K398HBM8EAPBGD23PGR118JA | 20.00 | 2025-08-22 16:07:09.711208+00 | 2025-07-21 12:45:54.123+00 |
| 87 | +(1 row) |
| 88 | + |
| 89 | +-- Now, we can query the current state. The external account has -$30 ($50 |
| 90 | +-- payment minus $20 refund) and our account for the user has $30. Nothing is |
| 91 | +-- in flight, so the receivables and pending accounts are 0. |
| 92 | +SELECT |
| 93 | + name, |
| 94 | + balance |
| 95 | +FROM pgledger_accounts_view |
| 96 | +WHERE id IN (:'user1_external_id',:'user1_receivables_id',:'user1_available_id',:'user1_pending_outbound_id'); |
| 97 | + name | balance |
| 98 | +------------------------+--------- |
| 99 | + user1.external | -30.00 |
| 100 | + user1.receivables | 0.00 |
| 101 | + user1.available | 30.00 |
| 102 | + user1.pending_outbound | 0.00 |
| 103 | +(4 rows) |
| 104 | + |
| 105 | +-- Next, we can simulate an unexpected case. Let's say we initiate a payment |
| 106 | +-- for $10 but we only receive $8 (e.g. due to unexpected fees): |
| 107 | +SELECT * FROM pgledger_create_transfer(:'user1_external_id',:'user1_receivables_id', 10.00); |
| 108 | + id | from_account_id | to_account_id | amount | created_at | event_at |
| 109 | +---------------------------------+---------------------------------+---------------------------------+--------+-------------------------------+------------------------------- |
| 110 | + pglt_01K398HBMGE7FSH6000VVTHK80 | pgla_01K398HBM8EAPBGD23PGR118JA | pgla_01K398HBM9EYB8WNG5B2N031VS | 10.00 | 2025-08-22 16:07:09.711938+00 | 2025-08-22 16:07:09.711938+00 |
| 111 | +(1 row) |
| 112 | + |
| 113 | +SELECT * FROM pgledger_create_transfer(:'user1_receivables_id',:'user1_available_id', 8.00); |
| 114 | + id | from_account_id | to_account_id | amount | created_at | event_at |
| 115 | +---------------------------------+---------------------------------+---------------------------------+--------+-------------------------------+------------------------------- |
| 116 | + pglt_01K398HBMGFNWTB8RPDCXDZQAW | pgla_01K398HBM9EYB8WNG5B2N031VS | pgla_01K398HBM9FRTVBHCAXZ37RB85 | 8.00 | 2025-08-22 16:07:09.712689+00 | 2025-08-22 16:07:09.712689+00 |
| 117 | +(1 row) |
| 118 | + |
| 119 | +-- Now, we can see that our receivables balance is not $0 like we expect: |
| 120 | +SELECT balance FROM pgledger_accounts_view |
| 121 | +WHERE id =:'user1_receivables_id'; |
| 122 | + balance |
| 123 | +--------- |
| 124 | + 2.00 |
| 125 | +(1 row) |
| 126 | + |
| 127 | +-- And we can look at the entries to figure out what happened: |
| 128 | +SELECT * FROM pgledger_entries_view |
| 129 | +WHERE account_id =:'user1_receivables_id' |
| 130 | +ORDER BY account_version; |
| 131 | + id | account_id | transfer_id | amount | account_previous_balance | account_current_balance | account_version | created_at | event_at |
| 132 | +---------------------------------+---------------------------------+---------------------------------+--------+--------------------------+-------------------------+-----------------+-------------------------------+------------------------------- |
| 133 | + pgle_01K398HBMDEFFVVH0981XGBTDA | pgla_01K398HBM9EYB8WNG5B2N031VS | pglt_01K398HBMCECNRPT2S9M0MCE42 | 50.00 | 0.00 | 50.00 | 1 | 2025-08-22 16:07:09.706692+00 | 2025-08-22 16:07:09.706692+00 |
| 134 | + pgle_01K398HBMDFSDBJ43MKBX8FADZ | pgla_01K398HBM9EYB8WNG5B2N031VS | pglt_01K398HBMDFGTRV884F9P83FF4 | -50.00 | 50.00 | 0.00 | 2 | 2025-08-22 16:07:09.709565+00 | 2025-08-22 16:07:09.709565+00 |
| 135 | + pgle_01K398HBMGEKERD38DNF6C7RKA | pgla_01K398HBM9EYB8WNG5B2N031VS | pglt_01K398HBMGE7FSH6000VVTHK80 | 10.00 | 0.00 | 10.00 | 3 | 2025-08-22 16:07:09.711938+00 | 2025-08-22 16:07:09.711938+00 |
| 136 | + pgle_01K398HBMGFYVBCTM6YBM8BXN1 | pgla_01K398HBM9EYB8WNG5B2N031VS | pglt_01K398HBMGFNWTB8RPDCXDZQAW | -8.00 | 10.00 | 2.00 | 4 | 2025-08-22 16:07:09.712689+00 | 2025-08-22 16:07:09.712689+00 |
| 137 | +(4 rows) |
| 138 | + |
0 commit comments