Two exports both say October. One is a merchant's monthly processing statement. The other is an activity report filtered by settlement date. Your importer gives them the same 2026-10 key, joins the totals, and raises a discrepancy.
The files may be correct. The comparison contract may be missing.
I work on BrowserAct. A guide we prepared about Worldpay iQ statements separates monthly processing statements, activity reports, and other financial records. For a developer, that distinction should survive ingestion. A human-readable month label cannot express which event date selected the underlying records.
This article proposes an application-side review gate for document metadata. The example uses fictional merchants and synthetic records. It does not access Worldpay, call a processor API, parse real statements, calculate fees, or establish that any customer's accounts reconcile.
Make the time axis an input field
The linked guide describes monthly statement retrieval in the applicable merchant account and treats activity exports separately. It also calls out transaction, settlement, bank-posting, and statement dates as different contexts. It does not establish one worldwide posting delay or a universal iQ export layout.
Our own metadata contract can therefore record basis, start, and end alongside the merchant reference. Those names are application fields, not a claimed processor response schema. Use an exclusive end date consistently in this example: October's calendar window is represented as October 1 through November 1.
The dates must come from a reviewed report definition or a verified extraction process. Do not assign settlement_date because a filename contains the word settlement, or convert an unknown period into calendar-month boundaries just to satisfy the schema.
| Input | What the application needs to know |
|---|---|
| Monthly processing statement | Merchant, currency, and stated statement period |
| Activity report | Merchant, currency, selected date basis, and date window |
| Bank record | Its own posting-date context; a separate artifact |
| Approved comparison mapping | Which statement and report windows may be reviewed together |
An unknown basis is a request for clarification, not an empty string to normalize away.
Require a mapping even when the month labels agree
The following gate checks scope and looks for an explicit approved window pair. It deliberately does not contain a default mapping from “October statement” to “October settlements.” An accounting owner must define the applicable comparison; this code cannot derive that policy from the presence of two files.
from datetime import date FIELDS = ("company", "merchant", "currency", "kind", "basis", "start", "end") def validate(record): for field in FIELDS: value = record.get(field) if not isinstance(value, str) or not value.strip(): raise ValueError(f"Missing or invalid field: {field}") start = date.fromisoformat(record["start"]) end = date.fromisoformat(record["end"]) if start >= end: raise ValueError("Expected a nonempty window with an exclusive end") def mapping_key(statement, activity): return ( statement["company"], statement["merchant"], statement["currency"], statement["start"], statement["end"], activity["basis"], activity["start"], activity["end"], ) def prepare_review(statement, activity, approved_mappings): validate(statement) validate(activity) if (statement["kind"] != "monthly_processing_statement" or activity["kind"] != "activity_report"): return "wrong_document_type" if any(statement[k] != activity[k] for k in ("company", "merchant")): return "merchant_scope_mismatch" if statement["currency"] != activity["currency"]: return "currency_mismatch" if statement["basis"] != "statement_period": return "statement_basis_invalid" if activity["basis"] not in {"transaction_date", "settlement_date"}: return "unsupported_activity_basis" if mapping_key(statement, activity) not in approved_mappings: return "mapping_approval_required" return "ready_for_accounting_review" The result name matters. ready_for_accounting_review means that these metadata checks passed against a supplied mapping. It does not mean reconciled, paid, mathematically equal, or financially correct. There are no amount fields in this example.
Validation errors should stop the affected input at the application boundary. Keep the error reason and source-record reference. A caller that catches ValueError and returns success would undo the purpose of the gate.
Exercise the false matches
The local synthetic checks covered a permitted pair as well as the cases below. No supplier document or authenticated merchant account was involved.
| Synthetic case | Gate outcome |
|---|---|
| Same month and merchant, no approved mapping | mapping_approval_required |
| Same merchant identifier under another company | merchant_scope_mismatch |
| Statement and report use different currency labels | currency_mismatch |
| Tax form supplied in the statement slot | wrong_document_type |
| Bank-posting date supplied as activity basis | unsupported_activity_basis |
| Report window changes after a pair was approved | mapping_approval_required |
Notice that equal windows still require approval. Conversely, different windows are not automatically wrong: the caller can supply an explicitly reviewed mapping. The application must preserve who approved that relationship and which policy revision applied. This minimal function only receives the approved key set; an actual service needs the approval record and access controls around that set.
When a window or basis changes, generate a new comparison request. Do not silently reuse an approval for the old report because the merchant and display month happen to match.
Keep availability and accounting review independent
A source statement can exist while an integration has not imported it. A file can be saved while its merchant or period is unverified. Both files can be reviewed while the accounting comparison remains unresolved.
Represent those as separate facts in the job record. For example, a statement that was not delivered calls for collection investigation; a missing period mapping calls for an accounting decision. Repeating the download does not supply the missing policy.
The same separation prevents a transaction report from standing in for an absent monthly processing statement. The supporting rows might be useful, but their presence does not prove delivery of the requested statement artifact.
Keep the implementation boundary visible
This design can sit behind manual file delivery, an existing integration, or a separately permitted collection workflow. It does not validate portal permissions or create a Worldpay collector. A browser job would still need its own authorized access, document identity, and file-delivery acceptance checks.
The useful engineering change is small: replace an ambiguous month join with a named date basis and an approved period relationship. You can then investigate a mismatch without asking the collector to keep changing date filters until the numbers happen to agree.
Source and native statement context: Download Worldpay iQ statements for reconciliation.

