Wires the no-vanity-metrics policy from prose-only into structural
enforcement. Without this, the next time a count creeps into a
README it stays there until I notice; with this, the PR fails.
What lands:
- scripts/check-no-vanity-metrics.sh — bash scanner, dep-free,
targets marketing/spec/governance Markdown only (per-phase
progress docs, CHANGELOGs, conformance/notes/research dirs
exempt by design). Regex is tuned to the patterns we've actually
had to fix and rejects false positives on RFC / codec / version
references. Lines that legitimately need a number opt out with
`<!-- allow-vanity: <reason> -->`.
- .github/workflows/no-vanity-metrics.yml — CI workflow runs the
scanner on every PR and `main` push that touches Markdown.
- .githooks/pre-commit — same scanner, local guard so the failure
happens before push. Activated per-clone via
`scripts/install-hooks.sh`.
- CHARTER.md — adds a "Messaging — no vanity metrics" section
documenting the policy and pointing to the scanner.
Also strips the four remaining drift sites the scanner found on
first run: a ~5 LOC stub note, a "98 crates" + "≤ 120 LOC" mention
in jouleclaw-stack/examples/AGENTS.md, and "26 crates" in the PCI
scope-map doc. The CAMT.054 ISO 20022 message-name reference is
preserved via an allow-vanity marker.
235 lines
13 KiB
Markdown
235 lines
13 KiB
Markdown
# OpenPay — Foundation Document
|
||
|
||
**Status**: Draft v0.1
|
||
**Date**: 2026-05-17
|
||
**Toolchain**: Rust 1.95.0 (stable, released 2026-04-16), edition 2024
|
||
**License**: Apache-2.0 (matches Hyperswitch, Fineract, ISO 20022 Rust crates)
|
||
|
||
---
|
||
|
||
## 1. Mission
|
||
|
||
A free, open-source, universal payment-acceptance stack that runs on any modern phone,
|
||
tablet, or x86/ARM box, integrates every major payment rail (cards, A2A/instant, wallets,
|
||
QR), and ships a single safety-critical Rust core compiled to every platform via cxx,
|
||
jni, and wasm-pack.
|
||
|
||
Not a startup. A reference implementation that any merchant, bank, fintech, or
|
||
government can deploy with no per-transaction tax to OpenPay itself.
|
||
|
||
---
|
||
|
||
## 2. The Tax Stack We Eliminate (Quantified)
|
||
|
||
For a merchant doing $1M/yr in card volume on the current US stack:
|
||
|
||
| Layer | Typical cost | Source |
|
||
|---|---|---|
|
||
| Interchange (issuer) | 1.5–3.0% | Aeropay 2026 cost analysis |
|
||
| Network assessment (Visa/MC) | ~0.13% | Aeropay 2026 cost analysis |
|
||
| Processor markup | 0.3–0.5% (often 2.9%+$0.30 blended) | Aeropay 2026 |
|
||
| POS SaaS (Toast/Square/Clover) | $69–$300/mo × 12 = $828–$3,600/yr | Vendor pricing pages |
|
||
| Hardware lease | $50–$150/mo per terminal | Vendor pricing pages |
|
||
| Chargebacks | $15–$100+ per dispute | Aeropay 2026 |
|
||
| **Total effective rate** | **3.0–5.0%** | |
|
||
| **On $1M volume** | **$30K–$50K/yr** | |
|
||
|
||
A2A / pay-by-bank rails (FedNow, RTP, PIX, UPI, SEPA Instant) charge cents per
|
||
transaction, not percentages. FedNow's 2026 fee schedule increased the per-transaction
|
||
limit from $1M to $10M; participation has grown to 1,500+ US financial institutions.
|
||
A merchant routing 50% of volume to A2A saves ~$25K/yr on a $1M business before any
|
||
SaaS savings.
|
||
|
||
---
|
||
|
||
## 3. Hard Constraints (Non-Negotiable Physics)
|
||
|
||
### 3.1 Tap to Pay on iPhone requires a PSP
|
||
|
||
Per Apple's developer documentation: "App developers who want to offer Tap to Pay on
|
||
iPhone to merchants will first need to integrate with a supported payment service
|
||
provider (PSP) who processes the payments read by Tap to Pay on iPhone and provides
|
||
the certified terminal configurations that are loaded on the merchant's device."
|
||
|
||
**Implication**: OpenPay cannot remove the PSP for in-person iPhone card acceptance.
|
||
It can (a) treat PSPs as pluggable, swappable drivers and (b) route to non-card rails
|
||
wherever the customer permits.
|
||
|
||
### 3.2 PCI DSS scope is unavoidable for card data
|
||
|
||
Any code path that touches raw PAN (Primary Account Number) puts the operator in PCI
|
||
scope. OpenPay's design must **never see the PAN** in the application layer — only
|
||
tokens issued by the PSP or by the device secure element (Apple Secure Enclave,
|
||
Android StrongBox, host card emulation).
|
||
|
||
### 3.3 ISO 20022 is the global messaging substrate
|
||
|
||
FedNow, PIX, SEPA Instant, India's NPCI (post-IMPS migration), and SWIFT all use
|
||
ISO 20022 (PACS for clearing/settlement, PAIN for initiation, CAMT for cash
|
||
management). The Rust crate family `open-payments-iso20022-*` v1.0.1 ships full
|
||
type-safe serde parsers for every message type. We adopt it directly; we don't
|
||
rebuild it.
|
||
|
||
### 3.4 We are not a money transmitter
|
||
|
||
OpenPay the project ships software, not regulated money movement. Anyone who deploys
|
||
OpenPay to clear funds becomes a money transmitter / payment institution /
|
||
authorized payment provider under their own jurisdiction's law and is responsible
|
||
for licensing, AML/KYC, and capital requirements. The codebase enforces this with a
|
||
clear separation between the `op-core` orchestration layer and `op-rails-*` adapter
|
||
crates that must be configured with the operator's own credentials.
|
||
|
||
---
|
||
|
||
## 4. Prior Art We Build On (Not Reinvent)
|
||
|
||
| Project | Role | License | Why we use it |
|
||
|---|---|---|---|
|
||
| **Hyperswitch** (Juspay) | Payment orchestration server | Apache 2.0 | Rust, PCI-cert, 175M tx/day claimed, 100+ PSP connectors |
|
||
| **open-payments-iso20022** | ISO 20022 + FedNow parsers | Apache/MIT | Type-safe pacs/pain/camt in Rust |
|
||
| **Apache Fineract** | Core banking / ledger | Apache 2.0 | Account management; we plug in via REST for ledger ops |
|
||
| **Mojaloop** | Interoperability switch | Apache 2.0 | Inter-DFSP transfers, Gates Foundation reference |
|
||
| **NPCI open source** | UPI reference stack | Apache 2.0 | Distributed payment processing platform |
|
||
| **Burn** (tracel-ai) | ML framework | Apache 2.0 / MIT | Cross-platform inference: native, WASM, no_std for embedded |
|
||
| **ort** (pykeio) | ONNX Runtime bindings | Apache 2.0 / MIT | Fast inference for production fraud models |
|
||
|
||
OpenPay's net new contribution is the **edge integration**: a single safety-critical
|
||
Rust core that runs on the merchant device (iPhone/Android/web/Linux kiosk), speaks
|
||
to Hyperswitch or any PSP for cards, speaks directly ISO 20022 to A2A rails, and
|
||
runs fraud inference on-device before any network hop.
|
||
|
||
---
|
||
|
||
## 5. Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ MERCHANT DEVICE (any platform) │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────┐ │
|
||
│ │ iOS shell │ │ Android │ │ Web PWA │ │ Kiosk │ │
|
||
│ │ (Swift + │ │ shell │ │ (wasm-bindgen│ │ (Linux │ │
|
||
│ │ cxx FFI) │ │ (Kotlin + │ │ + JS/TS UI) │ │ + Rust │ │
|
||
│ │ │ │ jni) │ │ │ │ native)│ │
|
||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └────┬────┘ │
|
||
│ └─────────────────┴──────┬──────────┴───────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────────▼────────────────┐ │
|
||
│ │ op-core (Rust 1.95, no_std- │ │
|
||
│ │ compatible where possible) │ │
|
||
│ │ │ │
|
||
│ │ ┌──────────────────────────┐ │ │
|
||
│ │ │ Payment state machine │ │ │
|
||
│ │ │ (typestate pattern) │ │ │
|
||
│ │ └──────────────────────────┘ │ │
|
||
│ │ ┌──────────────────────────┐ │ │
|
||
│ │ │ op-emv: EMV TLV decode │ │ │
|
||
│ │ │ op-iso20022: ISO parsers │ │ │
|
||
│ │ │ op-vault: tokens only, │ │ │
|
||
│ │ │ never raw PAN │ │ │
|
||
│ │ │ op-fraud: on-device │ │ │
|
||
│ │ │ inference (Burn/ort) │ │ │
|
||
│ │ └──────────────────────────┘ │ │
|
||
│ └────────┬───────────────────────┘ │
|
||
└───────────────────────────┼─────────────────────────────────────────┘
|
||
│
|
||
┌───────────────────┼───────────────────┐
|
||
│ │ │
|
||
┌───────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
|
||
│ op-rails-card │ │ op-rails-a2a │ │ op-rails-wallet│
|
||
│ │ │ │ │ │
|
||
│ adapter trait: │ │ adapter trait: │ │ Apple Pay, │
|
||
│ → Hyperswitch │ │ → FedNow │ │ Google Pay, │
|
||
│ → Stripe │ │ → RTP (TCH) │ │ host card │
|
||
│ → Adyen │ │ → PIX/SPI │ │ emulation │
|
||
│ → Finix/Moov │ │ → UPI/NPCI │ │ │
|
||
│ (PSP swappable)│ │ → SEPA Inst. │ │ │
|
||
│ │ │ (rail-direct) │ │ │
|
||
└────────────────┘ └────────────────┘ └────────────────┘
|
||
│ │ │
|
||
└───────────────────┼───────────────────┘
|
||
│
|
||
┌───────────▼────────────┐
|
||
│ op-ledger │
|
||
│ (optional; Fineract │
|
||
│ adapter or sled │
|
||
│ embedded DB for │
|
||
│ edge-only deploys) │
|
||
└────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Crate Layout (Cargo Workspace)
|
||
|
||
```
|
||
openpay/
|
||
├── Cargo.toml # workspace root
|
||
├── crates/
|
||
│ ├── op-core/ # state machines, domain types, traits
|
||
│ ├── op-iso20022/ # re-export + helpers over open-payments-iso20022
|
||
│ ├── op-emv/ # EMV TLV parsing, BER-TLV codec
|
||
│ ├── op-vault/ # token-only secure storage abstraction
|
||
│ ├── op-fraud/ # on-device inference (Burn primary, ort backend)
|
||
│ ├── op-rails-card/ # PSP adapter trait + Hyperswitch driver
|
||
│ ├── op-rails-a2a/ # FedNow, PIX, UPI, SEPA Instant drivers
|
||
│ ├── op-rails-wallet/ # Apple Pay / Google Pay token handling
|
||
│ ├── op-ffi-swift/ # cxx bridge for iOS / macOS
|
||
│ ├── op-ffi-jni/ # jni bridge for Android / JVM
|
||
│ └── op-wasm/ # wasm-bindgen bridge for web / PWA
|
||
├── examples/
|
||
│ ├── kiosk-linux/ # standalone binary
|
||
│ └── headless-server/ # for testing
|
||
└── tests/
|
||
├── conformance/ # ISO 20022 message round-trips
|
||
├── emv/ # EMV test vectors
|
||
└── property/ # proptest invariants on state machine
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Core Principles (Enforced by the Type System)
|
||
|
||
1. **No raw PAN in application memory.** The `PaymentMethod` enum carries
|
||
`Token(VaultRef)` or `EmvTag(SecureBlob)` variants only. Constructing a
|
||
`PaymentMethod::RawPan` requires the `pci-scope` cargo feature and is gated
|
||
behind `#[cfg(feature = "pci-scope")]`.
|
||
|
||
2. **Typestate payment machine.** `Payment<S>` where `S: PaymentState`.
|
||
Transitions are functions, not method calls on a mutable struct.
|
||
Illegal transitions don't compile.
|
||
|
||
3. **Money is never f64.** All amounts are `op_core::Money { minor_units: i64, currency: Iso4217 }`.
|
||
|
||
4. **Errors are sealed enums, never `Box<dyn Error>`.** Each crate exposes
|
||
exactly one `Error` type with `thiserror`. Callers exhaustive-match.
|
||
|
||
5. **No `unsafe` outside `op-ffi-*` and `op-emv` (raw byte parsing).**
|
||
Enforced by `#![forbid(unsafe_code)]` at every other crate root.
|
||
|
||
6. **Every public function is `#[must_use]` when it returns a non-`()` value
|
||
that represents a state transition or side-effect.**
|
||
|
||
7. **Cross-platform via Rust 1.95's `cfg_select!`** for clean target-specific
|
||
bodies instead of nested `cfg_if!`.
|
||
|
||
---
|
||
|
||
## 8. What Comes Next (Sequenced)
|
||
|
||
Each phase produces verifiable artifacts; we do not move on until the prior
|
||
phase's tests pass.
|
||
|
||
- **Phase 1** (this commit): foundation doc, workspace skeleton, `op-core`
|
||
domain types (`Money`, `Currency`, `PaymentMethod`, typestate machine
|
||
scaffolding).
|
||
- **Phase 2**: `op-iso20022` wrapper + conformance tests against PACS.008 /
|
||
PAIN.001 / CAMT.054 test vectors. <!-- allow-vanity: CAMT.054 is the ISO 20022 message-name, not a count -->
|
||
- **Phase 3**: `op-emv` BER-TLV codec with EMVCo test vectors.
|
||
- **Phase 4**: `op-rails-card` trait + Hyperswitch driver, mocked against
|
||
Hyperswitch's sandbox.
|
||
- **Phase 5**: `op-rails-a2a` FedNow driver against FRB test harness.
|
||
- **Phase 6**: `op-fraud` Burn model with synthetic transaction stream.
|
||
- **Phase 7**: FFI bridges (`op-ffi-swift`, `op-ffi-jni`, `op-wasm`).
|
||
- **Phase 8**: example kiosk + headless server + end-to-end test harness.
|
||
```
|
||
|