Procure to pay (AP)
The payables stack from requisition to reconciliation: requisition → PO → receipt → bill → approval → payment → reconciliation.
AP is where fraud happens, so it's where controls matter most:
Every AP screen is a control surface. Approval, segregation of duties and duplicate detection are why the form exists in the first place.
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. A spend view shows booked, accrued, and committed as three distinct series and never sums them 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's passing automatically.
- A variance requires a disposition: accept it (with a reason), request a vendor credit, partial-pay, or reject.
- The three documents sit side by side, line by line, with differences highlighted on the lines as well as the header. Overbilling often hides under a header total that matches while the lines don't.
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 payments are among the most common AP losses and among the easiest to prevent. The check is a blocking warning that shows the suspected duplicate side by side. - The source document is required. A bill with no attachment can't be approved. It's the evidence an auditor asks for.
- The expense account and full dimensions are per line, not per bill. A single invoice legitimately hits four departments.
- Default coding is suggested from this vendor's last bills, and it looks like a suggestion, with the confidence and the basis shown.
- Early-payment discounts are surfaced as a decision. "Pay by Oct 12 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. The person who created a bill can't approve it, and no setting changes that.
- The approval chain is visible before submission: who has to approve, in what order, and what's already done. When approvals vanish into a queue nobody can see, AP ends up chasing 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, so 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. Editing a bill after approval without a trace 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 number that matters most is the cash left afterwards. A run that overdraws the account is an 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, has to be re-verified through an out-of-band channel, and shows prominently in the run preview. Vendor-impersonation fraud runs through exactly this change, 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. When 40 of 42 payments settle, the run hasn't failed. The UI shows which two didn't and what to do about them.
- Positive pay files and check numbering are tracked; voided checks are recorded, never deleted.
- The cash requirements forecast, what's due in the next 7/14/30 days against available cash, is the view AP managers 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 January 1, 2026 (One Big Beautiful Bill Act, indexed for inflation from 2027), so 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 people photograph receipts on their phones.
UI rules:
- Receipt capture is camera-first. OCR pre-fills amount, date and merchant as editable suggestions, with each extracted value 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 which one applies.
- Mileage and per diem use rate tables with effective dates. The trip date picks the rate, not today's date.
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 and serves as its evidence.- 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. The UI keeps those two treatments apart.
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 low, and cheap to fix for the return |
| 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 better cash management and may mean vendor relationships are souring. Lopifi renders it with polarity neutral and always puts the on-time payment rate next to it, because either number alone misleads.
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 |