Procure to pay (AP)
The complete payables stack: requisition → PO → receipt → bill → approval → payment → reconciliation.
AP is where fraud happens and where controls matter most. The design consequence:
Every AP screen is a control surface. Approval, segregation of duties, and duplicate detection are not features bolted onto a form — they are the reason the form exists.
1. The lifecycle
Requisition → PO → Goods receipt → Bill → Match → Approve → Schedule → Pay → Reconcile
│ │ │ │ │ │ │
│ │ │ │ └────────┴─ the control gate │
│ │ │ └─ Dr GR/IR or Expense / Cr AP │
│ │ └─ Dr Inventory/Expense / Cr GR/IR (if used) │
│ └─ commitment; no GL impact Dr AP / Cr Cash
└─ no GL impact
No GL impact from requisitions or POs. The GL moves at goods receipt (if GR/IR is used) or at the bill. POs are commitments. Showing committed spend in an expense figure overstates it; showing only booked spend understates the obligation. Both matter — a spend view renders booked, accrued, and committed as three distinct series, never summed into one "spend" number.
2. Three-way match
The core AP control. Three documents must agree before payment.
Purchase order 100 units @ $12.00 = $1,200.00
Goods receipt 98 units received
Vendor invoice 100 units @ $12.00 = $1,200.00
─────────────────────────────────
Quantity variance 2 units short ($24.00) ⚠ requires disposition
| Match type | Compares | Used for |
|---|---|---|
| Two-way | PO ↔ invoice | Services, no physical receipt |
| Three-way | PO ↔ receipt ↔ invoice | Goods |
| Four-way | + inspection/quality | Regulated or high-value goods |
UI rules:
- Tolerances are configurable and displayed. "Matching within $50 or 2%" — a reviewer needs to know what is auto-passing.
- A variance requires a disposition, never a silent pass: accept (with reason), request a vendor credit, partial-pay, or reject.
- The three documents are shown side by side, line by line, with differences highlighted at the line level, not just the header. A header total that matches while lines don't is a common way overbilling hides.
MatchExceptionQueueis the AP clerk's worklist, sorted by age and amount.
3. The bill
type Bill = {
vendorId: string;
vendorInvoiceNumber: string; // THEIR number — the duplicate-detection key
billDate: Date;
dueDate: Date;
terms: PaymentTerms;
currency: string;
lines: BillLine[]; // each with account + full dimensions
taxes: TaxLine[];
total: Money;
poReference?: string;
approvals: Approval[];
status: BillStatus;
paymentSchedule?: Date;
attachments: Document[]; // the source invoice — required
};
UI rules:
- Duplicate detection runs before the form is saved, keyed on
vendor + vendorInvoiceNumber, with a secondary fuzzy check onvendor + amount + date ± 5 days. Duplicate payment is one of the most common and most preventable AP losses, and the check must be a blocking warning showing the suspected duplicate side by side. - The source document is required. A bill with no attachment cannot be approved. This is the evidence an auditor asks for and it should never be optional.
- The expense account and full dimensions are per line, not per bill. A single invoice legitimately hits four departments.
- Default coding is suggested from history — this vendor's last bills — and the suggestion is visibly a suggestion, with the confidence and the basis shown.
- Early-payment discounts are surfaced as a decision. "Pay by 12 Oct to save $240 (2%)" with the annualized return, because 2/10 net 30 is a ~37% annualized return and finance teams miss it constantly.
4. Approval
type ApprovalPolicy = {
thresholds: { upTo: Money; approvers: Role[]; requireAll: boolean }[];
requireSeparateApprover: true; // preparer ≠ approver. Never configurable to false.
dimensionApprovers?: Record<string, Role>; // department owner must approve their spend
delegations: { from: string; to: string; until: Date }[];
};
UI rules:
- Segregation of duties is enforced, not advisory. The person who created a bill cannot approve it. This is not a setting.
- The approval chain is visible before submission — who has to approve, in what order, and what's already done. An approval that disappears into a queue with no visibility is the reason AP teams chase people on Slack.
- Threshold escalation is shown at entry. "Bills over $10,000 require CFO approval" appears while typing the amount, not after submitting.
- Delegation has an expiry. Permanent delegation is a control failure and the UI requires an end date.
- Rejection requires a reason and routes back to the preparer with it visible.
- Approval is on a specific version. If a bill is edited after approval, approvals are voided and re-requested — with the change shown. Silent post-approval editing is a fraud vector.
5. Payment runs
type PaymentRun = {
runDate: Date;
paymentDate: Date;
method: "ach" | "wire" | "check" | "card" | "rtp";
bills: string[];
totalAmount: Money;
fundingAccount: string;
status: "draft" | "approved" | "submitted" | "settled" | "partially-failed";
};
UI rules:
- The run is previewed in full before submission: vendor, amount, method, bank details, and the resulting cash balance after the run. The most important number is the cash left afterwards — a payment run that overdraws is a very expensive UI failure.
- Bank detail changes are the highest-risk event in AP. A vendor bank change within N days of a payment is flagged, requires re-verification through an out-of-band channel, and is displayed prominently in the run preview. This is how vendor-impersonation fraud works and the UI is the last line of defense.
- Payment runs use
HoldToConfirm. They are irreversible movements of money, often large. - Partial failures are a first-class state. A run where 40 of 42 payments settled is not "failed"; the UI shows exactly which two and what to do.
- Positive pay files and check numbering are tracked; voided checks are recorded, never deleted.
- Cash requirements forecast — what's due in the next 7/14/30 days versus available cash — is the view AP managers actually plan from.
6. Vendor master
type Vendor = {
id: string; name: string; legalName: string;
taxId?: string; // W-9 / TIN
form1099?: { required: boolean; box: string };
bankDetails: BankDetail[]; // versioned, never overwritten
defaultTerms: PaymentTerms;
defaultAccount?: string;
currency: string;
status: "active" | "on-hold" | "inactive";
approvedBy?: string; // vendor onboarding is itself an approval
};
UI rules:
- Bank details are versioned, never overwritten. The history — who changed them, when, and what verification was performed — is the audit trail that matters most in AP.
- Vendor onboarding requires approval and is separate from bill entry. Whoever can create a vendor should not be able to pay one.
- 1099 tracking flags missing W-9s before year-end, when it's still fixable. The Form 1099-NEC / 1099-MISC threshold is $600 for payments made through 2025 and $2,000 for payments made from 1 January 2026 (One Big Beautiful Bill Act, indexed for inflation from 2027) — the threshold is a dated table, not a constant. Payments by card or a third-party network are reported by the processor on Form 1099-K, not by the payer. A payee who will not provide a TIN triggers 24% backup withholding.
- Duplicate vendor detection on name similarity and tax ID — duplicate vendors are how duplicate payments survive duplicate-invoice checks.
- Sanctions/OFAC screening status is displayed where the integration exists. Paying a sanctioned entity is a legal event, not an accounting one.
7. Expense management
Employee-initiated spend. Same control model, different ergonomics — it's mobile-first, because receipts are photographed on phones.
UI rules:
- Receipt capture is camera-first, with OCR pre-filling amount, date, and merchant — all shown as editable suggestions with the extracted value visible next to the image.
- Policy violations are shown at entry, not at approval. "Meals over $75 require a business purpose" appears while typing, so the fix is cheap.
- Corporate card transactions are matched to receipts, not re-entered. The unmatched-receipt and unmatched-transaction queues are both first-class.
- Personal vs. reimbursable vs. corporate-card-paid are three different settlement paths and the UI never guesses.
- Mileage and per diem use rate tables with effective dates — the rate that applied on the trip date, not today's.
8. Accruals from AP
At period end, goods and services received but not invoiced must be accrued. This is the bridge from AP to the close.
The GR/IR (goods received / invoice received) clearing account holds the gap:
Receipt: Dr Inventory/Expense / Cr GR-IR clearing
Invoice: Dr GR-IR clearing / Cr Accounts payable
Balance in GR-IR = received not yet invoiced = the accrual
UI rules:
- The GR/IR balance is a schedule that must be aged and cleared. Old balances mean either a missing invoice or a receipt that never happened, and both are findings.
UninvoicedReceiptsReportdrives the period-end accrual entry — it is the evidence for it.- Open POs at period end are not accrued. Only significant unconditional or noncancelable purchase obligations are disclosed (ASC 440), and a loss on a firm commitment is accrued. Different treatment, and the UI must not conflate them.
9. AP metrics
| Metric | Formula | Watch out |
|---|---|---|
| DPO | (AP ÷ COGS) × days in period |
Higher isn't automatically better — it may mean you're late, not efficient |
| CCC | DSO + DIO − DPO |
The working-capital cycle in days |
| On-time payment rate | Bills paid by due date ÷ bills paid |
Vendor relationship health |
| Early-discount capture | Discounts taken ÷ discounts available |
Usually shockingly low; high-ROI to fix |
| Invoice cycle time | Receipt → approval → payment, in days | Where the bottleneck is |
| Cost per invoice | Processing cost ÷ invoice count | The automation business case |
| Duplicate rate | Duplicates caught ÷ invoices | The control's effectiveness |
DPO is the metric most likely to be misread. Rising DPO looks like improved cash management and may be deteriorating vendor relationships. Lopifi renders it with the on-time payment rate adjacent, always — one number without the other is misleading.
10. AP component set
| Component | Purpose |
|---|---|
BillForm |
Entry with duplicate detection, line coding, and dimension capture |
BillDetail |
Full bill with the source document, match, approvals, and audit trail |
BillRegister |
All bills, filterable, with aging |
ThreeWayMatch |
PO / receipt / invoice side by side, line-level variances |
MatchExceptionQueue |
The clerk's worklist |
ApprovalChain |
Who, in what order, what's done, what's blocking |
ApprovalInbox |
The approver's queue, mobile-first, with the document inline |
PaymentRunBuilder |
Select bills, preview, show the resulting cash balance |
PaymentRunPreview |
Final confirmation with hold-to-confirm |
CashRequirements |
Due in 7/14/30 days vs. available cash |
VendorMaster |
With versioned bank details and change history |
BankDetailChangeAlert |
The fraud-prevention surface |
ExpenseCapture |
Camera-first, OCR-assisted, mobile |
CardTransactionMatch |
Corporate card ↔ receipt matching |
UninvoicedReceiptsReport |
GR/IR aging, drives the accrual |
AgingTable |
AP aging, tied to the control account |