Payment Behaviors
Overview
Payments represent disbursements to vendors for invoices. The payment system supports multiple payment types (check, cash, debit, balance credit) and tracks payments through statuses (pending, cleared, voided). Payments are created through the invoice payment flow and managed through a searchable grid interface.
Testing Philosophy
- Prefer unit tests for pure business logic (calculations, validations, transformations)
- Use integration tests for database interactions and cross-system flows
- Use UI tests only for end-to-end happy paths that touch multiple pages
- Every behavior must be user-visible; no tests for implementation details
Testing Patterns
Pattern: Grid Page Behaviors
Most list pages in Integreat follow the same pattern:
- Fetch IDs via Datomic query with filters
- Hydrate results via
pull-many
- Render table with sortable columns
- Support selection (individual / all / all-filtered)
- Action buttons appear conditionally based on permissions and selection state
Test implications: Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
Pattern: Permission Gates
Every mutating operation checks:
assert-can-see-client — user has access to the client
assert-not-locked — payment date >= client locked-until
can? — user has the specific permission for the activity
Test implications: Integration test each gate independently. UI tests only verify the happy path with a permitted user.
Pattern: Check Generation Behaviors
Check printing involves:
- Validation of invoices and bank account
- Sequential check number assignment
- PDF generation with MICR encoding
- S3 upload and storage
- Transaction creation (for cash payments)
Test implications: Integration test the full flow once. Unit test validation logic and PDF content generation.
Payment List Page
Display Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 1.1 |
It should display a table with columns: Client, Vendor, Bank Account, Check #, Status, Date, Amount, Links |
UI |
[ ] |
| 1.2 |
It should show the Client column only when viewing payments for multiple clients |
Integration |
[ ] |
| 1.3 |
It should hide the Bank Account and Date columns on smaller viewports |
UI |
[ ] |
| 1.4 |
It should show "Cleared" status as a primary-colored pill |
UI |
[ ] |
| 1.5 |
It should show "Pending" status as a secondary-colored pill |
UI |
[ ] |
| 1.6 |
It should show "Voided" status as a red-colored pill |
UI |
[ ] |
| 1.7 |
It should render check numbers as links to S3 PDFs when an s3-url exists |
UI |
[ ] |
| 1.8 |
It should show plain check number text for payments without an s3-url |
UI |
[ ] |
| 1.9 |
It should display a links dropdown showing associated invoices and transactions |
UI |
[ ] |
| 1.10 |
It should display checkboxes for bulk selection on each row |
UI |
[ ] |
| 1.11 |
It should show no check number for non-check payment types |
UI |
[ ] |
Filtering Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 2.1 |
It should filter payments by vendor typeahead selection |
Integration |
[ ] |
| 2.2 |
It should filter payments by date range |
Integration |
[ ] |
| 2.3 |
It should filter payments by check number (exact match or partial text) |
Integration |
[ ] |
| 2.4 |
It should filter payments by invoice number (exact match) |
Integration |
[ ] |
| 2.5 |
It should filter payments by amount range (min/max) |
Integration |
[ ] |
| 2.6 |
It should filter payments by payment type via radio cards (All, Cash, Check, Debit) |
Integration |
[ ] |
| 2.7 |
It should support exact-match navigation to a specific payment by ID, bypassing other filters |
Integration |
[ ] |
| 2.8 |
It should filter payments by status via route (/payments/pending, /payments/cleared, /payments/voided) |
Integration |
[ ] |
| 2.9 |
It should apply all filters via HTMX with debounced triggers |
Integration |
[ ] |
| 2.10 |
It should combine all filters with AND logic |
Integration |
[ ] |
| 2.11 |
It should use efficient time-bounded queries for date range filtering |
Integration |
[ ] |
| 2.12 |
It should parse check number search as Long when possible, falling back to exact string match |
Unit + Integration |
[ ] |
| 2.13 |
Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set |
Integration |
[ ] |
| 2.14 |
It should bypass all other filters when exact-match ID is provided |
Integration |
[ ] |
Sorting Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 3.1 |
It should sort by client name ascending/descending |
Integration |
[ ] |
| 3.2 |
It should sort by vendor name ascending/descending |
Integration |
[ ] |
| 3.3 |
It should sort by bank account ascending/descending |
Integration |
[ ] |
| 3.4 |
It should sort by check number ascending/descending |
Integration |
[ ] |
| 3.5 |
It should sort by date ascending/descending |
Integration |
[ ] |
| 3.6 |
It should sort by amount ascending/descending |
Integration |
[ ] |
| 3.7 |
It should sort by status ascending/descending |
Integration |
[ ] |
| 3.8 |
Given the user clicks a column header twice, then the sort direction should toggle |
Integration |
[ ] |
| # |
Behavior |
Test Strategy |
Status |
| 4.1 |
It should display 25 payments per page by default |
Integration |
[ ] |
| 4.2 |
It should allow changing the per-page count |
Integration |
[ ] |
| 4.3 |
It should calculate the total visible float and total float across all matching payments, not just the current page |
Unit |
[ ] |
Selection Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 5.1 |
It should allow selecting individual payments via checkboxes |
UI |
[ ] |
| 5.2 |
It should allow selecting all visible payments via a header checkbox |
UI |
[ ] |
| 5.3 |
It should allow selecting all filtered payments (up to 250) for bulk operations |
Integration |
[ ] |
| 5.4 |
Given payments are selected, when the user applies a filter, then the selection should be cleared |
Integration |
[ ] |
Row Action Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 6.1 |
It should show a trash icon on each row unless the payment status is already voided |
UI |
[ ] |
| 6.2 |
It should prompt for confirmation when clicking the trash icon ("Are you sure you want to void this payment?") |
UI |
[ ] |
| 6.3 |
Given confirmation, when voiding a payment, then the row should be removed from the table with animation |
UI |
[ ] |
| 6.4 |
It should block voiding cleared check payments |
Integration |
[ ] |
Float Display Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 7.1 |
It should display a "Visible in float" pill showing the sum of pending payment amounts in the current filter view |
Unit |
[ ] |
| 7.2 |
It should display a "Total in float" pill showing the sum of all pending payments for the selected client(s) |
Unit |
[ ] |
| 7.3 |
It should exclude voided payments from float calculations |
Unit |
[ ] |
| 7.4 |
It should include only pending status payments in float calculations |
Unit |
[ ] |
Bulk Void
| # |
Behavior |
Test Strategy |
Status |
| 8.1 |
It should show a confirmation modal with warning icon and count of payments to be voided |
UI |
[ ] |
| 8.2 |
It should support "Selected only" mode to void only checkboxed payments |
UI |
[ ] |
| 8.3 |
It should support "All selected" mode to void all payments matching current filters (up to 250) |
Integration |
[ ] |
| 8.4 |
It should require admin permission for bulk void operations |
Integration |
[ ] |
| 8.5 |
Given confirmation, when voiding, then the modal should close and a notification should show "Successfully voided X of Y payments" |
Integration |
[ ] |
| 8.6 |
It should skip payments that already have transactions and skip already-voided payments |
Integration |
[ ] |
Check Printing
| # |
Behavior |
Test Strategy |
Status |
| 9.1 |
It should generate physical check PDFs with MICR encoding at the bottom |
Integration |
[ ] |
| 9.2 |
It should include payee, amount in numbers and words, date, memo, bank info, and client signature image |
Integration |
[ ] |
| 9.3 |
It should generate voucher copies with full invoice details below the check |
Integration |
[ ] |
| 9.4 |
It should store check PDFs in S3 under checks/{uuid}.pdf |
Integration |
[ ] |
| 9.5 |
It should assign check numbers sequentially from the bank account's check number |
Integration |
[ ] |
| 9.6 |
It should increment the bank account's check number by the number of vendors paid |
Integration |
[ ] |
| 9.7 |
It should validate that the bank account has a starting check number |
Integration |
[ ] |
| 9.8 |
It should merge multiple checks into a single PDF at merged-checks/{uuid}.pdf |
Integration |
[ ] |
| 9.9 |
It should group invoices by vendor, creating one check per vendor per batch |
Integration |
[ ] |
| 9.10 |
It should validate that all invoices belong to the same client and the selected bank account belongs to the same client |
Integration |
[ ] |
| 9.11 |
It should reject check creation if the total amount is <= $0.00 |
Integration |
[ ] |
ACH Payments
| # |
Behavior |
Test Strategy |
Status |
| 10.1 |
It should create pending payments with payment-type/debit |
Integration |
[ ] |
| 10.2 |
It should not generate check PDFs for ACH payments |
Integration |
[ ] |
| 10.3 |
It should not create transactions for ACH payments |
Integration |
[ ] |
Balance Credit Payments
| # |
Behavior |
Test Strategy |
Status |
| 11.1 |
It should allow paying invoices from existing vendor credit with payment-type/balance-credit |
Integration |
[ ] |
| 11.2 |
It should block balance credit payments when multiple vendors are selected |
Integration |
[ ] |
| 11.3 |
It should offset positive-balance invoices against negative-balance invoices |
Integration |
[ ] |
| 11.4 |
It should create a single cleared payment for the net amount, consuming credit invoices first-in |
Integration |
[ ] |
Cash Payments
| # |
Behavior |
Test Strategy |
Status |
| 12.1 |
It should create payments with payment-type/cash automatically marked as cleared |
Integration |
[ ] |
| 12.2 |
It should create an associated transaction with POSTED status |
Integration |
[ ] |
| 12.3 |
It should use the account with numeric code 21000 for cash payment transactions |
Integration |
[ ] |
| 12.4 |
It should set the payment date to the latest invoice date |
Integration |
[ ] |
Cross-Cutting Behaviors
Voiding Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 13.1 |
It should allow voiding pending payments |
Integration |
[ ] |
| 13.2 |
It should allow voiding cash, debit, and balance-credit payments even when cleared |
Integration |
[ ] |
| 13.3 |
It should block voiding cleared check payments |
Integration |
[ ] |
| 13.4 |
It should set the payment amount to 0.0 when voided |
Integration |
[ ] |
| 13.5 |
It should set the payment status to voided |
Integration |
[ ] |
| 13.6 |
It should remove all invoice-payment links when voiding |
Integration |
[ ] |
| 13.7 |
It should restore invoice outstanding balances by adding back the invoice-payment amount |
Integration |
[ ] |
| 13.8 |
It should revert invoice status to unpaid when restored balance becomes non-zero |
Integration |
[ ] |
| 13.9 |
It should unlink associated transactions when voiding |
Integration |
[ ] |
Permission Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 14.1 |
It should require client visibility for viewing payments |
Integration |
[ ] |
| 14.2 |
It should require client visibility for voiding individual payments |
Integration |
[ ] |
| 14.3 |
It should require admin permission for bulk voiding payments |
Integration |
[ ] |
| 14.4 |
It should allow viewing S3 check PDFs to all users who can see the payment |
Integration |
[ ] |
Lock Date Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 15.1 |
It should block voiding payments dated before the client's locked-until date |
Integration |
[ ] |
| 15.2 |
It should check lock dates on individual void operations |
Integration |
[ ] |
| 15.3 |
It should check lock dates on bulk void operations |
Integration |
[ ] |
| 15.4 |
It should exclude locked payments from bulk void results |
Integration |
[ ] |
| 15.5 |
It should show a warning when some selected payments are locked |
UI |
[ ] |
Test Data Requirements
| Entity |
Requirements |
| Clients |
Multiple clients with different locations; some with locked-until dates |
| Vendors |
With/without default accounts; with/without terms and autopay settings |
| Bank Accounts |
Check, cash, and credit types; with/without starting check numbers |
| Invoices |
Various statuses, dates, amounts; positive and negative balances |
| Payments |
Check, cash, debit, and balance-credit types; pending, cleared, and voided statuses |
| Transactions |
Linked to cash payments with POSTED status |
| Files |
Check PDFs stored in S3 |
Existing Tests to Preserve
test/clj/auto_ap/integration/graphql/checks.clj — Check/payment GraphQL operations
Dependencies
- Datomic for payment/invoice/transaction persistence
- S3 for check PDF storage and retrieval
- Solr for index updates after payment mutations
- HTMX + Alpine.js for interactive grid behavior
clj-pdf for check PDF generation
clj-time for date parsing and coercion
auto-ap.datomic/scan-payments for efficient date-range queries
auto-ap.permissions/can? for permission checks
auto-ap.datomic/audit-transact for all mutations