---
title: "Log every staff action on money · Agent Wranglers"
url: https://agentwranglers.se/playbook/payments/invoices-and-records/money-audit-log
updated: 2026-10-20
licence: "All rights reserved"
---

# Log every staff action on money

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Invoices and records](https://agentwranglers.se/playbook/payments/invoices-and-records)
- Rule: Log every manual refund, credit, price change, discount and subscription change with who did it, when, why and the values before and after.
- How: Write the log row in the same transaction as the change. Changes made at the provider, such as a subscription's price, are logged when your admin screen sends them. Require a reason for refunds and credits. Require a second person for refunds above a set amount, such as 10,000 SEK. Keep the log as long as the payment records, in a table the app can only add to. The general rules are in [security logging](https://agentwranglers.se/playbook/adversarial-testing/supply-chain-and-setup/security-logging) and [access logging](https://agentwranglers.se/playbook/data/personal-data/access-logging).
- Check: Issue a refund from the admin screen. A log row with the staff ID, reason, amount, time and the values before and after appears. The app role cannot UPDATE or DELETE it.

Example:

```js
await db.tx(async t => {
  const before = await t.one('SELECT id, credit_minor FROM accounts WHERE id = $1 FOR UPDATE', [accountId])
  const after = await t.one('UPDATE accounts SET credit_minor = credit_minor + $1 WHERE id = $2 RETURNING id, credit_minor', [creditMinor, accountId])
  await t.none('INSERT INTO money_audit (staff_id, action, reason, before, after) VALUES ($1, $2, $3, $4, $5)',
    [staffId, 'account.credit', reason, before, after])
})
```

```python
from psycopg.rows import dict_row
from psycopg.types.json import Jsonb

with connection.transaction(), connection.cursor(row_factory=dict_row) as cursor:
    before = cursor.execute("SELECT id, credit_minor FROM accounts WHERE id = %s FOR UPDATE", (account_id,)).fetchone()
    after = cursor.execute(
        "UPDATE accounts SET credit_minor = credit_minor + %s WHERE id = %s RETURNING id, credit_minor",
        (credit_minor, account_id),
    ).fetchone()
    cursor.execute(
        "INSERT INTO money_audit (staff_id, action, reason, before, after) VALUES (%s, %s, %s, %s, %s)",
        (staff_id, "account.credit", reason, Jsonb(before), Jsonb(after)),
    )
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers payments playbook: https://agentwranglers.se/playbook/payments/invoices-and-records/money-audit-log`.

- Previous: [Keep payment records for the legal period](https://agentwranglers.se/playbook/payments/invoices-and-records/payment-record-retention)
- [Invoices and records](https://agentwranglers.se/playbook/payments/invoices-and-records)
