Complete documentation of vendor payment workflow including payment creation, multi-invoice allocation, early payment discounts, credit/debit note handling, and payment voiding procedures.
Payments are processed after invoices pass three-way matching and approval. A single payment can cover multiple invoices, and partial payments are supported.
| Method | Description | Processing Time | Use Case |
|---|---|---|---|
| CHECK | Physical check issued to vendor | 3-5 business days | Traditional vendors, large amounts |
| WIRE | Bank wire transfer | Same day - 2 days | International payments, urgent needs |
| ACH | Automated Clearing House transfer | 1-3 business days | Domestic recurring payments |
| CREDIT_CARD | Corporate credit card payment | Immediate | Small purchases, vendor accepts cards |
| CASH | Cash payment (petty cash) | Immediate | Small local purchases |
id, payment_number - Primary key and unique referencecompany_id, vendor_id - Key relationshipspayment_date - Date of paymentpayment_method - CHECK, WIRE, ACH, CREDIT_CARD, CASHpayment_reference - Check number, wire reference, etc.bank_account_id - Source bank accounttotal_amount, currency_code, exchange_ratebank_name, bank_account_number, bank_routing_number - Snapshotstatus - PENDING, APPROVED, PROCESSED, COMPLETED, VOIDEDapproved_by, approved_atprocessed_at - When payment was executedvoided_at, void_reason - If voidedpayment_id - Parent payment referenceinvoice_id - Invoice being paidallocated_amount - Amount applied to this invoicediscount_taken - Early payment discount capturedcredit_note_number, vendor_id, invoice_idcredit_note_datereason_code - RETURN, PRICE_ADJUSTMENT, QUALITY_ISSUE, SHORT_SHIPMENTreason_descriptionsubtotal, tax_amount, total_amountapplied_amount - Amount applied against invoicesbalance_amount - Remaining credit balancestatus - DRAFT, APPROVED, APPLIEDdebit_note_number, vendor_id, invoice_iddebit_note_datereason_code - Additional charges, correctionsreason_descriptionsubtotal, tax_amount, total_amountstatus - DRAFT, APPROVEDSelect approved, unpaid invoices for a vendor. System shows due dates and available early payment discounts.
-- Get payable invoices for vendor SELECT vi.id, vi.invoice_number, vi.vendor_invoice_number, vi.total_amount, vi.paid_amount, vi.total_amount - vi.paid_amount AS balance_due, vi.due_date, pt.discount_percent, pt.discount_days, CASE WHEN vi.invoice_date + pt.discount_days >= CURRENT_DATE THEN ROUND((vi.total_amount - vi.paid_amount) * pt.discount_percent / 100, 2) ELSE 0 END AS available_discount FROM finance.vendor_invoices vi LEFT JOIN master.payment_terms pt ON vi.payment_terms_id = pt.id WHERE vi.vendor_id = :vendor_id AND vi.status = 'APPROVED' AND vi.payment_status IN ('UNPAID', 'PARTIAL') ORDER BY vi.due_date;
Create payment header and allocate amounts to selected invoices. Apply credit notes if available.
-- Create payment INSERT INTO finance.payments ( payment_number, company_id, vendor_id, payment_date, payment_method, total_amount, currency_code, status ) VALUES ( :payment_number, :company_id, :vendor_id, CURRENT_DATE, :payment_method, :total_amount, 'USD', 'PENDING' ) RETURNING id; -- Create allocations INSERT INTO finance.payment_allocations ( payment_id, invoice_id, allocated_amount, discount_taken ) VALUES (:payment_id, :invoice_id_1, :amount_1, :discount_1), (:payment_id, :invoice_id_2, :amount_2, :discount_2);
Payments above threshold require approval. Amount-based approval matrix determines required approvers.
The payment creator cannot be the payment approver. System enforces this control automatically.
Execute the payment through banking system. Update payment status and record processing timestamp.
-- Process payment UPDATE finance.payments SET status = 'PROCESSED', processed_at = CURRENT_TIMESTAMP, payment_reference = :bank_reference WHERE id = :payment_id;
Update invoice paid amounts and payment status based on allocations.
-- Update invoice after payment UPDATE finance.vendor_invoices vi SET paid_amount = ( SELECT COALESCE(SUM(pa.allocated_amount + pa.discount_taken), 0) FROM finance.payment_allocations pa JOIN finance.payments p ON pa.payment_id = p.id WHERE pa.invoice_id = vi.id AND p.status IN ('PROCESSED', 'COMPLETED') ), payment_status = CASE WHEN paid_amount >= total_amount THEN 'PAID' WHEN paid_amount > 0 THEN 'PARTIAL' ELSE 'UNPAID' END, balance_amount = total_amount - paid_amount WHERE vi.id IN ( SELECT invoice_id FROM finance.payment_allocations WHERE payment_id = :payment_id );
Capture early payment discounts based on payment terms (e.g., 2/10 Net 30 = 2% discount if paid within 10 days):
discount_percent from payment_termsdiscount_days from invoice_datepayment_allocations.discount_takenReduce amount owed to vendor:
Credit balance can be applied to future payments.
Increase amount owed by vendor:
Debit notes added to vendor's payable balance.
Payments can only be voided if: (1) Check not yet cashed, (2) Wire can be recalled, (3) Within void window. Voiding requires approval and reason.
-- Void payment and reverse allocations BEGIN; -- Void the payment UPDATE finance.payments SET status = 'VOIDED', voided_at = CURRENT_TIMESTAMP, void_reason = :reason WHERE id = :payment_id; -- Reverse invoice paid amounts UPDATE finance.vendor_invoices vi SET paid_amount = paid_amount - pa.allocated_amount - pa.discount_taken, payment_status = CASE WHEN paid_amount - pa.allocated_amount - pa.discount_taken = 0 THEN 'UNPAID' ELSE 'PARTIAL' END FROM finance.payment_allocations pa WHERE pa.payment_id = :payment_id AND pa.invoice_id = vi.id; COMMIT;
SELECT v.vendor_name, COUNT(DISTINCT vi.id) AS invoices_paid, SUM(pa.allocated_amount) AS total_paid, SUM(pa.discount_taken) AS discounts_captured, MAX(p.payment_date) AS last_payment_date FROM finance.payments p JOIN finance.payment_allocations pa ON p.id = pa.payment_id JOIN finance.vendor_invoices vi ON pa.invoice_id = vi.id JOIN vendor.vendors v ON p.vendor_id = v.id WHERE p.status = 'COMPLETED' AND p.payment_date >= :start_date GROUP BY v.id, v.vendor_name ORDER BY total_paid DESC;
SELECT v.vendor_name, vi.invoice_number, vi.total_amount - vi.paid_amount AS balance_due, vi.due_date, CURRENT_DATE - vi.due_date AS days_overdue, CASE WHEN CURRENT_DATE <= vi.due_date THEN 'CURRENT' WHEN CURRENT_DATE - vi.due_date <= 30 THEN '1-30 DAYS' WHEN CURRENT_DATE - vi.due_date <= 60 THEN '31-60 DAYS' WHEN CURRENT_DATE - vi.due_date <= 90 THEN '61-90 DAYS' ELSE '90+ DAYS' END AS aging_bucket FROM finance.vendor_invoices vi JOIN vendor.vendors v ON vi.vendor_id = v.id WHERE vi.payment_status IN ('UNPAID', 'PARTIAL') AND vi.status = 'APPROVED' ORDER BY vi.due_date;
Process payments to capture early payment discounts. Monitor discount expiry dates and prioritize accordingly.
Regularly reconcile payment records with bank statements. Investigate and resolve any discrepancies promptly.
Verify vendor bank details before processing. Watch for duplicate payments. Implement segregation of duties.