A Processor-Neutral JSON Schema for Auditing Payment Processing Statements
Merchant processing statements are built for billing, not comparison. Two statements can describe the same monthly card volume with different labels, subtotals, credits, and pricing structures. That makes the basic question—“what did accepting cards actually cost?”—harder to answer than it should be.
We published version 1.1.3 of the Lifted Payments Payment Statement Audit Model to make that calculation explicit, reproducible, and independently verifiable. The release includes:
- a spreadsheet-ready CSV template;
- a JSON Schema Draft 2020-12 contract;
- a Decimal-safe companion validator;
- exact methodology and field definitions;
- two valid and fourteen deliberately invalid synthetic test vectors;
- a deterministic validation report and SHA-256 inventory; and
- a fail-closed publication gate.
The maintained guide and downloads live at liftedpayments.com/payment-processing-statement-audit. The exact release is archived as doi:10.5281/zenodo.21764642 and published from GitHub v1.1.3.
The canonical release archive has SHA-256 digest b1d398ada2bb456de0fffc5278b68a48554ffd5d80232d654416102ad87d5897. That digest is the simplest way to confirm that a downloaded copy is the release described here.
Start with one declared calculation basis
Version 1.1.3 uses gross settled purchase volume and net processing fees:
gross_processing_fees = exact sum(fee_groups.amount)
total_processing_fees = gross_processing_fees - statement_credits
effective_rate = round_half_up(total_processing_fees / card_volume, 6)
average_ticket = round_half_up(card_volume / transaction_count, 2)
Enter fullscreen mode Exit fullscreen mode
For the synthetic example:
$2,864.75 / $125,000.00 = 0.022918 = 2.2918%
Enter fullscreen mode Exit fullscreen mode
The JSON value is 0.022918, not 2.2918. Multiply by 100 only for display.
The denominator is gross settled purchase volume with its matching settled purchase count. It excludes declines, authorization-only events, voids, refunds, chargebacks, reserves, and funding adjustments. The numerator is net processing fees after gross fee groups reconcile exactly and processing-fee credits are reported separately.
That basis matters. Dividing fees by deposits, net sales after refunds, or authorization volume can produce a plausible-looking percentage that is not comparable.
The record makes its assumptions machine-readable
A shortened v1.1.3 record looks like this:
{
"schema_version": "1.1.0",
"calculation_basis": "gross_settled_purchase_volume_and_net_processing_fees",
"statement_period": {
"start": "2026-06-01",
"end": "2026-06-30"
},
"currency": "USD",
"card_volume": 125000.00,
"transaction_count": 1860,
"gross_processing_fees": 2864.75,
"statement_credits": 0.00,
"total_processing_fees": 2864.75,
"effective_rate": 0.022918,
"average_ticket": 67.20,
"pricing_model": "interchange_plus",
"fee_groups": []
}
Enter fullscreen mode Exit fullscreen mode
The published example includes the complete fee-group array. Categories are unique, and their exact cent sum must equal gross_processing_fees.
pricing_model uses a bounded set:
interchange_plus
flat_rate
tiered
subscription
dual_pricing
unknown
Enter fullscreen mode Exit fullscreen mode
unknown is intentional. A parser should preserve uncertainty rather than infer a pricing model from one line item.
Normalize labels into stable fee groups
Statement labels vary, but the economic roles are more stable:
-
interchange— issuer/interchange charges; -
assessments— network assessment, access, and brand charges; -
processor_markup— percentage, basis-point, per-item, or service markup; -
authorization— authorization, capture, AVS, gateway, and batch items; -
monthly— account, statement, minimum, and recurring platform charges; -
pci— compliance-program or non-validation fees; -
equipment— terminal rental, device service, or connectivity charges; -
chargebacks— dispute-administration fees, not transaction principal; and -
other— ambiguous processing-related charges, with a note.
The model does not assume every fee is avoidable. It makes the statement explainable. If a processing charge cannot be classified confidently, put it in other and document why; do not omit it to make a rate look better.
JSON Schema is necessary, but not sufficient
The schema handles types, required fields, enums, bounds, and unknown-field rejection. Cross-field accounting rules need a semantic validator.
Install the pinned dependencies and run both a record and the public corpus:
python -m pip install -r requirements-validation.txt
python tools/validate_audit.py examples/payment-statement-audit-example.json
python tools/validate_audit.py --corpus
python tools/publication_gate.py --mode package
python tools/publication_gate.py --mode candidate
python tools/publication_gate.py --mode published
Enter fullscreen mode Exit fullscreen mode
The validator loads JSON numbers directly as Decimal. It also rejects:
- duplicate JSON keys and non-finite numbers;
- invalid or reversed dates and periods longer than 62 days;
- fractional cents and over-precise rates;
- fee totals, credits, effective rates, or average tickets that do not reconcile;
- ambiguous zero-volume and zero-count combinations;
- duplicate fee categories and unknown fields;
- excessive input size or nesting depth; and
- likely payment, banking, identity, email, or credential data in notes.
Validation errors return a rule code, JSON Pointer path, and fixed message without echoing the submitted value.
The exact merge worktree passed 81 automated tests. A fresh copy unpacked from the published ZIP passed the same 81-test suite, with one Git-history-only immutability regression skipped as designed. Package mode passed 14/14 internal checks, the clean tagged candidate passed 15/15 readiness checks, and published mode passed 16/16 only after GitHub, Zenodo, DOI resolution, Hugging Face, and Software Heritage matched the reviewed commit and archive bytes. The deterministic report accepts two valid synthetic records and rejects fourteen adversarial records for their expected rules.
Those results demonstrate the declared contract, package integrity, rejection behavior, repository provenance, and public artifact identity. They do not prove that an arbitrary source statement was transcribed correctly, and they are not a security guarantee.
Version 1.1.3 keeps the 1.1.0 record contract unchanged while separating portable package validity from candidate readiness and published attestation. Before candidate Python can run, the gate checks the complete manifest, canonical paths, bounded sizes, UTF-8 and LF portability, SHA-256 coverage, credential signatures, release identity, clean committed tree, authoritative origin, complete remote tag inventory, exact tag alignment, and monotonic version. The public attestor is bounded and non-executing; it rejects unsafe redirects, oversized responses, archive traversal, case-fold aliases, symlinks, undeclared members, digest drift, and mismatched public identities.
The gate is not an accounting opinion, legal review, interchange-pricing guarantee, or malware sandbox. Automated screening cannot prove arbitrary prose contains no confidential information, so human review remains mandatory before public release.
The release was also exercised against 1,000 deterministic hostile JSON-native and direct-value records. Every case returned structured validation results rather than crashing the validator or leaking submitted values into error messages.
Keep the public-data boundary strict
This model needs statement-level aggregates. It has no field for card numbers, cardholder identity, bank details, credentials, or merchant-owner information.
Never put these into a public repository, prompt, issue, or dataset mirror:
- full or partial card numbers, expiration dates, or security codes;
- track, PIN, or PIN-block data;
- bank account or routing numbers;
- passwords, API keys, tokens, or authentication values;
- tax IDs, government identifiers, or owner Social Security numbers;
- merchant-identifying notes; or
- an unredacted real statement.
Automated screening reduces accidental disclosure. It cannot prove arbitrary prose is safe, so a human still has to review every public record.
What the model cannot tell you
An effective rate is an all-in normalization of the included statement costs. It can support consistent month-to-month or provider-to-provider comparisons when records use the same basis.
It cannot:
- prove the source statement was transcribed correctly;
- decide whether a fee is contractually permitted;
- determine whether interchange was qualified correctly;
- predict a future statement;
- replace legal, tax, or accounting advice; or
- guarantee savings.
Card mix, ticket size, acceptance channel, rewards mix, disputes, seasonality, and statement timing can move the result without a pricing change. Present the rate with the underlying dollars, volume, count, fee groups, and period.
Cite or reuse the exact release
The model is licensed under CC BY 4.0. The requested citation is:
Lifted Payments. (2026). Lifted Payments Payment Statement Audit Model (Version 1.1.3). Zenodo. https://doi.org/10.5281/zenodo.21764642
Public distribution surfaces:
- Canonical methodology and downloads
- GitHub v1.1.3 release
- Zenodo version record
- Exact Hugging Face v1.1.3 commit
- Software Heritage snapshot
GitHub is the versioned source, Zenodo is the persistent DOI archive, and Software Heritage preserves the exact source history. The Hugging Face catalog copy exposes the same v1.1.3 ZIP; an anonymous download matched the canonical archive digest above.
For a quick manual check, the free effective-rate calculator performs the core math in the browser without transmitting the values entered.
If you want a payments team to review a private statement and design processing technology around how the business actually operates, start the Lifted Payments merchant application.
답글 남기기