Back to Blog
Printed tax forms, a calculator, a pen, and an envelope arranged on a desk

AI Invoice Processing With Duplicate Payment Controls

10 min read

AI invoice processing fails when a clean extraction is mistaken for a safe accounting action. An OCR service can return an invoice number, supplier, total, tax, and line items while one value is wrong. The same document can also arrive twice through email, upload, or a retry. If either result goes straight into the finance system, the workflow can create an incorrect voucher or repeat a payment request.

Separate extraction from validation, approval, and posting. Keep the source evidence, check the financial relationships, search for duplicates before review, bind approval to the reviewed version, and make the final write idempotent. The result is a control path that can stop a bad or repeated invoice before it becomes an accounting action.

Why invoice extraction is not approval

Invoice parsers solve a document interpretation problem. They do not decide whether the document is legitimate, new, internally consistent, approved, or safe to post.

Current services already return broad sets of useful fields. Microsoft Document Intelligence extracts invoice identifiers, dates, totals, addresses, and line items, along with structured result data and confidence information. Google Cloud's Invoice Parser covers supplier details, invoice number, amounts, dates, and line-item values. Amazon Textract AnalyzeExpense returns normalized summary fields and line-item groups with confidence values.

Those outputs are evidence, not authorization. A high confidence score tells you how strongly the extractor matched a field. It does not prove the supplier exists in your vendor master, the arithmetic balances, the invoice was not submitted before, or the purchase was approved.

Treat the parser response as an untrusted proposal. Store it beside the original file, then move it through deterministic checks before a human or downstream system can accept it.

Define a canonical invoice record

Do not pass each provider's response shape through the rest of the workflow. Map it into one internal record that preserves both normalized values and source evidence.

Store these fields in the record:

  • The source document identifier, file hash, ingestion channel, and received time.
  • Supplier name as extracted, resolved vendor identifier, and resolution status.
  • Invoice number as extracted, a normalized comparison form, and its confidence.
  • Invoice date, currency, subtotal, tax, total, and amount due.
  • Line items with description, quantity, unit price, tax, amount, page, and bounding region when available.
  • The parser name and version, raw extraction payload location, and per-field confidence.
  • Validation findings, duplicate candidates, review status, record version, and posting status.

Keep the extracted and normalized values separate. If an invoice says INV 0042-A, retain that exact string for display and audit. A normalized value such as INV0042A may help with comparison, but it must not replace the source value.

The invoice number deserves special handling because it is a business identifier supplied by the seller. The Peppol invoice rule defines the invoice identifier as one assigned by the seller. That makes supplier identity part of the comparison. Two suppliers can legitimately use the same invoice number, while two invoices from one supplier with punctuation differences may be the same document.

Validate fields before duplicate matching

Run deterministic validation before searching for duplicates. Bad normalization creates bad matches, and malformed totals should become an exception even when no duplicate exists.

Start with required fields. Your posting contract may require a resolved supplier, invoice number, invoice date, currency, and total. Missing data should not be silently filled by the model. Route it to correction or reject the document according to policy.

Then reconcile the financial relationships. Use currency-specific decimal rules and a documented rounding tolerance. Check whether line amounts reconcile to the subtotal, whether tax plus subtotal reconciles to total, and whether amount due is plausible given payments or credits shown on the document. A mismatch is a finding for review, not an invitation to change the source numbers until they balance.

Validate line items individually. Negative quantities, unsupported currencies, impossible dates, and inconsistent tax treatment need typed findings. Keep the parser confidence beside each finding so reviewers can see whether the problem came from a weak extraction or from the invoice itself.

Confidence thresholds should vary by field and consequence. A low confidence description may be acceptable if it does not affect coding. A low confidence invoice number or total should block automatic posting because those fields drive duplicate detection and financial value. Set thresholds from labeled invoices and observed correction rates rather than adopting one global number.

Detect exact and near duplicates

Duplicate detection should produce candidates with reasons. It should not delete or reject a document based on one fuzzy similarity score.

Use several layers:

  1. Match the source file hash. This catches the same bytes arriving through another channel.
  2. Match resolved supplier plus normalized invoice number. This is the strongest business identity when both fields are reliable.
  3. Match supplier, total, currency, and invoice date within a narrow policy window. This catches number extraction errors and resubmitted scans.
  4. Compare document text or image fingerprints only as supporting evidence. Similar templates and recurring amounts can otherwise create false positives.

Keep a unique constraint for the strongest accepted identity, such as tenant, supplier, normalized invoice number, and document type. The application check gives the reviewer useful context. The storage constraint closes the race where two workers check at the same time and both see no existing invoice.

Near-duplicate matching needs an explicit outcome. A candidate can be the same invoice, a legitimate recurring invoice, a credit or correction, or an unrelated bill with coincidentally similar values. Show the reviewer the existing record, relevant fields, source previews, and current posting state. Do not ask them to decide from a percentage alone.

A current practitioner report illustrates the missing production controls. Khata's invoice OCR issue asks for field confidence, line-item review, duplicate checks, VAT cross-checks, explicit approval before voucher creation, and malformed-extraction tests. Treat this as one implementation report, not proof that every invoice system fails the same way.

Route exceptions instead of every invoice

A review queue works best when deterministic checks explain why an invoice needs attention. Use typed reasons such as:

  • Required field missing.
  • Supplier unresolved or ambiguous.
  • Invoice identity matches an existing record.
  • Financial reconciliation failed.
  • Consequential field is below its confidence threshold.
  • Purchase order, receiving record, or policy check failed.
  • Posting result is uncertain after a timeout.

The review screen should display the source region beside each extracted value. Show the duplicate candidate and its lifecycle state. If the existing invoice was rejected, that differs from an invoice already posted and paid.

Record edits as patches, not a replacement of the extraction record. Keep who changed each field, the old and new values, the reason, and the time. Increment the invoice record version after any edit or new duplicate finding.

Approval must bind to that version. If the supplier, invoice number, total, currency, line items, duplicate disposition, or supporting purchase order changes after review, invalidate the approval. A note or display-only correction may not need reapproval, but that rule belongs in policy rather than in the model prompt.

Make posting safe to resume

Posting is a separate state transition. The workflow may time out after the finance system accepted a request but before your application stored the response. Retrying without a stable request identity can create another voucher or payment action.

Create one posting command for the approved invoice version. Give it a stable command identifier and persist a state such as pending, submitted, confirmed, failed, or reconciliation_required. Derive the idempotency key from the internal invoice identity, approved version, target system, and operation type. Do not generate a new key for each network attempt.

Where a downstream API supports it, send that stable key. Stripe documents idempotent requests as a way to replay an API request without repeating the original operation. For an accounting API without native idempotency, put the command identifier in a unique external-reference field or build an adapter that records the command and result atomically around the remote operation where possible.

Never assume a timeout means failure. Query the target by command identifier or external reference before retrying. If the system cannot answer whether the operation succeeded, stop automatic retries and send the item to reconciliation.

The execution boundary can follow this shape:

def post_approved_invoice(invoice_id, approved_version, command_id):
    invoice = store.load(invoice_id)

    policy.require_approved_version(invoice, approved_version)
    policy.require_no_open_duplicate(invoice)
    policy.require_balanced_amounts(invoice)

    prior = commands.find(command_id)
    if prior and prior.status == "confirmed":
        return prior.result

    commands.mark_submitted(command_id, invoice_id, approved_version)

    try:
        result = accounting.create_voucher(
            invoice=invoice.to_posting_payload(),
            idempotency_key=command_id,
        )
    except UnknownRemoteOutcome:
        commands.mark_reconciliation_required(command_id)
        raise

    commands.mark_confirmed(command_id, result.external_id)
    return result

The example is an implementation recommendation. The exact transaction boundary depends on the accounting system. What matters is a stable command identity, current approval checks, another duplicate check immediately before posting, and an explicit state for an uncertain result.

Handle corrections and credit documents deliberately

A supplier may correct an invoice after submission. Do not overwrite the approved record and reuse its posting identity. Create a new version or linked correction with its own review and posting decision.

Define how the workflow treats credit notes, cancellations, and replacement invoices. A similar supplier, number, and amount can indicate a duplicate, but a negative amount or document type may represent a legitimate reversal. Preserve links such as corrects_invoice_id or credits_invoice_id so the accounting action follows the business relationship.

Supplier resolution changes need the same discipline. If a reviewer merges two vendor records, rerun duplicate matching for unposted invoices under the surviving vendor identity. A document that looked unique under an unresolved supplier may collide once the supplier is resolved.

Verify the complete workflow

Unit tests for the parser adapter are not enough. Exercise each boundary with representative documents and controlled failures.

Build a fixture set containing clear digital invoices, scans, rotated pages, repeated templates, multi-page line items, credits, tax variations, and documents with handwritten or low-quality fields where those appear in your workload. Record expected source fields, normalized values, validation findings, duplicate relationships, and review outcomes.

Then run these negative paths:

  • Upload identical bytes twice through different channels and assert only one invoice can post.
  • Change punctuation in the invoice number and assert the normalized match is explained to a reviewer.
  • Use the same number for two different suppliers and assert they do not collide.
  • Change the total after approval and assert the old approval cannot post.
  • Force two workers to pass the application duplicate check concurrently and assert the storage constraint rejects one.
  • Time out after the downstream system accepts the command and assert the workflow queries by stable identity instead of issuing a new command.
  • Return a low confidence total with a high confidence description and assert only the consequential field blocks posting.
  • Correct a vendor mapping and assert duplicate detection runs again.

Track extraction correction rates by field, exception reasons, reviewer changes, duplicate dispositions, posting retries, and reconciliation outcomes. Do not optimize for straight-through processing alone. A workflow that automatically posts more invoices by weakening controls has not improved.

Common implementation mistakes

One confidence threshold for every field ignores consequence. Tune thresholds by field and route type.

A duplicate check based only on file hash misses rescans and reformatted copies. A fuzzy check without supplier and business identity creates false positives. Use layered evidence and make the decision inspectable.

Approval stored as a boolean becomes stale after a correction. Bind it to an invoice version and the reviewed duplicate disposition.

A random idempotency key per retry defeats idempotency. Persist one posting command and reuse its identity until the result is confirmed or sent to reconciliation.

Keep the source extraction after a reviewer edits it. You need both versions to audit the decision, improve thresholds, reproduce parser changes, and explain why the posted record differs from the document.

Put one invoice path under control

Start with one supplier group and one target accounting action. Define the canonical invoice record, required fields, financial checks, duplicate layers, review reasons, approval invalidation rules, and posting command identity in writing. Run the negative tests before enabling automatic posting.

Before rollout, prove that every accepted invoice maps to one approved version and one confirmed posting command. Duplicates, corrections, and uncertain remote outcomes must remain visible instead of becoming silent financial actions.

References


About Fire In Belly: Independent senior engineering from Tallinn, Estonia. We design and build AI workflow automation with the validation gates, duplicate controls, and idempotent posting described above, at published fixed prices. Schedule a call to discuss your next project.