Payment processes and batches
What it is
Payments to suppliers are released in batches (called "processes" in the routing and "fund transfer batches" in the UI). A batch is created with selection criteria, filled with the matching payments, reviewed and approved payment by payment, locked, submitted, and finally produces a payment file sent to the bank.
This is the highest-consequence area of the application: a mistake here moves real money.
Where it lives
| Concern | Location |
|---|---|
Routes |
|
Components |
|
Services |
|
Models |
|
Balance approval |
|
Routes
| Route | Component |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Route ordering hazard. In the Do not "tidy" either table without checking bookmarks, the sidebar, and any deep links in emails or runbooks. |
The sidebar’s Payments group links to /processes/supplier-payments/batches and
/proceses/approval. The first does not match any declared route path literally —
it resolves through :processKey/batches with processKey = 'supplier-payments', i.e.
a literal string used as a key. Worth knowing before changing the batches route.
Batch creation
SupplierPaymentNewBatchModel is the creation payload, and its fields are the
selection criteria:
| Field | Meaning |
|---|---|
|
Batch label. |
|
When the batch is intended to pay. |
|
Who must review it. |
|
Whether it starts locked. |
|
Include scheduled payments. |
|
Include Instant Access payments. |
|
Include proposed advance-line disbursements. |
|
This is a debit batch (money in) rather than credit (money out). |
|
Which payment types to include: ACH, EFS card, PPM, wire, LLP. |
|
Optional fee code applied to the batch. |
SupplierPaymentBatchModel adds the runtime view: paymentBatchKey,
paymentTypeCounts, paymentTotal, numPayments, numApproved, numCancelled,
numHold, numSentToBank, submitted, locked, internalTransfers.
PaymentBatchRepositoryService.getItems post-processes paymentTypeCounts into a
human-readable paymentTypes string by concatenating the type names.
Batch endpoints
All on internalApiUrl:
| Endpoint | Purpose |
|---|---|
|
List (with |
|
One batch. |
|
Create from |
|
Update. |
|
Perform a workflow action ( |
|
Unlock a locked batch. |
|
|
|
The credit payments in the batch. |
|
The debit payments — a different endpoint, selected by an |
|
Update one payment (hold, approve, amend). |
|
Remove a payment from the batch. |
|
The bank payment file ( |
saveItem on the repository is a stub returning null with a // [SM] TODO — batch
updates go through updateBatch, not the generic abstract-service method.
Serialisation quirk
Every write method calls SerializeKeysTo(CamelCase) before serialising — so batches
are sent in camelCase, unlike most of the API which expects snake_case. Because
the casing setting is global in cerialize, this leaves CamelCase active for
whatever serialises next. See
data modelling.
Batch lifecycle
create fill review
┌──────────────┐ ┌───────────────┐ ┌────────────────────────┐
│ POST │──►│ backend selects│──►│ per-payment approve / │
│ paymentBatches│ │ matching pmts │ │ hold / cancel / remove │
│ (criteria) │ │ by criteria │ │ PUT|DELETE …/payments │
└──────────────┘ └───────────────┘ └───────────┬────────────┘
│
lock ◄─────── unlock ──────────┤
│ POST …/unlock │
▼ ▼
┌────────────────┐ ┌───────────────────────┐
│ POST …/action │────────► │ submitted │
│ (approve/submit)│ │ numSentToBank grows │
└────────────────┘ └──────────┬────────────┘
▼
┌───────────────────────┐
│ GET …/paymentFile │
│ ?withAch=true │
│ → file-saver download │
└───────────────────────┘
The authoritative state is GET …/status; the counters on the batch model
(numApproved / numCancelled / numHold / numSentToBank) are the summary view.
The payment file
PaymentBatchDownloaderService builds
paymentBatches/:batchId/paymentFile?withAch=true and appends a timestamp
(addTimestampToUrl) to defeat caching, then hands the response to file-saver.
There is an abstract base (PaymentBatchDownloaderAbstractService) and a mock, so
specs can exercise the flow without downloading.
Pending review and pending edits
Two related-but-distinct things:
PendingReviewService-
GET pending_review/andPUT /pending_review/:supplierKeywith{ supplier_transfer_approval_ml_key, treasury_approved }. This is the balance approval queue — see balance approval. PendingEditComponent-
Routed at
/processes/:supKey, this is the pending-edit review — supplier changes flaggedpendApprovNeededthat require a second approver. Note it is also what/processes/approvalaccidentally resolves to.
|
|
Permissions
The /processes and /proceses tables use the standard deny clause
(except: [REST_UW_ACCESS, ADVANCE_AUDITORS]) — the second table without
NgxPermissionsGuard at all, only RouteAccessResolver and
FeatureFlagsGeneralGuard.
Meaningful control is by permission code enforced in components and the backend:
INT_PMT_ADMIN (administer payments), INT_PMT_REVIEW (review payments),
MANAGE_PMT, FINANCE. See
the permission catalogue.
|
|
Error handling
PaymentBatchRepositoryService is unusual in toasting from the service layer — each
method catches, toasts a specific message ("Cannot fetch Payment Batches", "Cannot
store Payment Batch", "Cannot update Payment Batch", "Cannot make Payment Batch
Action", "Cannot unlock Payment Batch", "Cannot update Payment", "Cannot delete
Payment", "Cannot fetch Payment status") and rethrows.
Combined with PendingInterceptorService, which toasts any 5xx, a server error during
a batch operation produces two toasts. Expected, not a bug. See
error handling.
Gotchas
| Issue | Detail |
|---|---|
|
The misspelling is load-bearing — it is how balance approval is reachable. |
|
The sidebar’s batches link relies on it. |
Debit vs credit are different endpoints |
|
|
Batch writes flip the global |
|
Returns |
Double slashes in URLs |
|
Fixture JSON in the module |
|