open-standards/openpay/crates/op-openbanking/src/aisp.rs
Transaction Science 6ea90afbed ingest wave A4: 3 crates · smart-byte-atproto + op-openbanking + eoc-distributed
Smart Byte (1 crate, 2127 LOC, 35 tests)
  smart-byte-atproto    ATProto (Bluesky federation) adapter.
                        PLC + did:web resolution; XRPC client; CARv1 encode/decode
                        with CIDv1 (dag-cbor + raw, SHA-256); sketch MST with
                        order-independent BLAKE3 root; Repo + UnsignedCommit /
                        SignedCommit (Ed25519 over CBOR); firehose transport
                        trait + in-memory impl; AT URI ↔ smart-byte AID bridge
                        with SAID commitment.

OpenPay (1 crate, 2397 LOC, 44 tests)
  op-openbanking        UK OBIE R/W v3.1.10/3.1.11, Berlin Group NextGenPSD2
                        XS2A v1.3.13, STET PSD2 v1.7, Australia CDR Banking v1.31,
                        FDX v5/v6, SGFinDex shapes. AISP / PISP / PIIS / VRP
                        traits. FAPI 1.0 Advanced (PS256/ES256, mTLS, RFC 8705
                        certificate binding, JWK registration). Operators wire
                        their own JWS signer + HTTP transport — keeps the crate
                        KMS-agnostic.

EOC (1 crate, 2179 LOC, 32 tests)
  eoc-distributed       Distributed-inference scheduling. Worker trait with
                        Capability + Accelerator (CPU/GPU/TPU/NPU/Mixed) +
                        Load.busyness()/projected_micro_joules()/projected_g_co2e().
                        RoundRobin / LeastBusy / LatencyWeighted / JouleWeighted
                        (default) / CarbonWeighted routers. Work-stealing
                        scheduler. vLLM-style continuous batching with KV-block
                        budget. Ray-Serve-style auto-scaler with cooldown.
                        KV-cache-aware sticky-session routing.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-24 12:22:09 -04:00

219 lines
7.5 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Account Information Service Provider (AISP) surface.
//!
//! Implements the vendor-neutral version of:
//!
//! - UK Open Banking R/W v3.1 § Accounts/Balances/Transactions APIs
//! - Berlin Group NextGenPSD2 § 5.2–5.6 (account-information service)
//! - STET PSD2 § Accounts / Balances / Transactions
//! - Australia CDR `cds-au/v1/banking/*`
//! - FDX `accounts`, `transactions`, `balances`
//! - SGFinDex `Accounts` aggregation
//!
//! The shapes here are the union of those six; the per-standard
//! binding modules ([`crate::uk_ob`], [`crate::berlin_group`], etc.)
//! decide which subset of fields the wire payload carries.
use op_core::Money;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::fapi::OAuth2Token;
/// Opaque consent identifier minted by the ASPSP at consent creation.
///
/// UK OBIE: `Data.ConsentId`. Berlin Group: `consentId` header.
/// STET: `consentId`. CDR: `arrangementId`. FDX: `consentId`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ConsentId(pub String);
/// Account type per ISO 20022 `CashAccountType4Code`, narrowed to the
/// values open-banking standards actually use.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AccountType {
/// Personal current / checking account.
Personal,
/// Business current account.
Business,
/// Card (credit) account.
Card,
/// Loan / mortgage account.
Loan,
/// Savings / e-money account.
Savings,
}
/// Refinement of [`AccountType`] used by FDX and some CDR fields.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AccountSubtype {
/// FDX `CHECKING` / OBIE `CurrentAccount`.
Checking,
/// FDX `SAVINGS` / OBIE `Savings`.
Savings,
/// FDX `MONEY_MARKET`.
MoneyMarket,
/// FDX `CD` (certificate of deposit).
CertificateOfDeposit,
/// FDX `CREDIT_CARD`.
CreditCard,
/// FDX `MORTGAGE`.
Mortgage,
/// FDX `LOAN` (non-mortgage).
Loan,
/// Anything outside the enumerated subtypes; carry the wire string.
Other(String),
}
/// Balance type per ISO 20022 `BalanceType13Code`.
///
/// Open Banking standards expose a subset; this enum is that subset.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BalanceType {
/// `ClosingBooked` — most-recent booked balance at the close
/// of the previous business day.
ClosingBooked,
/// `InterimAvailable` — current available balance (booked +
/// authorised holds).
InterimAvailable,
/// `Expected` — booked + anticipated forward dated items.
Expected,
/// `OpeningBooked` — opening booked balance for the current period.
OpeningBooked,
}
/// Whether a balance / transaction line is a credit or debit.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransactionCredit {
/// Money into the account.
Credit,
/// Money out of the account.
Debit,
}
/// Posting status of a transaction. UK OBIE distinguishes
/// `Booked` from `Pending`; Berlin Group adds `Information`
/// (informational entries that do not change the balance).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransactionStatus {
/// Posted to the booked balance.
Booked,
/// Authorised hold, not yet booked.
Pending,
/// Informational only (e.g. fee preview).
Information,
}
/// A balance line for an account.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Balance {
/// Which balance flavour this line represents.
pub balance_type: BalanceType,
/// Signed amount, sign carried in [`Self::credit_debit`].
pub amount: Money,
/// Sign of the balance.
pub credit_debit: TransactionCredit,
/// RFC 3339 timestamp of the snapshot.
pub as_of: time::OffsetDateTime,
}
/// A transaction line from an account's ledger.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Transaction {
/// ASPSP-assigned transaction identifier.
pub id: String,
/// Posting status.
pub status: TransactionStatus,
/// Signed amount, sign in [`Self::credit_debit`].
pub amount: Money,
/// Credit or debit direction.
pub credit_debit: TransactionCredit,
/// RFC 3339 timestamp at which the transaction posted (or, for
/// pending, was authorised).
pub booking_date: time::OffsetDateTime,
/// Operator-facing description / merchant name.
pub description: String,
/// Optional remittance information (free-text reference printed
/// on the statement).
pub remittance: Option<String>,
}
/// An account exposed via the AISP service.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Account {
/// ASPSP-assigned, scoped to a single consent.
pub id: String,
/// IBAN / sort-code+account-no / routing+account, etc.
/// The wire format is binding-specific; we carry the raw string.
pub identifier: String,
/// Display nickname or product name.
pub nickname: Option<String>,
/// Account type (current / savings / card / loan).
pub account_type: AccountType,
/// Refined subtype for FDX-style classifications.
pub subtype: Option<AccountSubtype>,
/// Currency the account is denominated in.
pub currency: op_core::Currency,
}
/// AISP service trait — vendor-neutral account-information surface.
///
/// Implemented by each binding ([`crate::uk_ob`], [`crate::berlin_group`],
/// [`crate::stet`], [`crate::cdr`], [`crate::fdx`]). Each binding
/// translates the request into its standard's wire format and parses
/// the response back into these shapes.
///
/// **Async strategy.** This trait is intentionally synchronous: the
/// crate ships no HTTP client, and the bindings have no I/O of their
/// own. Operators wrap a real driver in an `async` wrapper at the
/// call site. Keeping the trait sync means it stays object-safe and
/// composable without an executor dependency.
pub trait AccountInfoService: Send + Sync {
/// List the accounts a consent covers.
fn accounts(&self, consent: &ConsentId, token: &OAuth2Token) -> Result<Vec<Account>>;
/// Fetch the current balance(s) for an account.
///
/// `account_id` is the ASPSP-scoped identifier returned by
/// [`Self::accounts`].
fn balances(
&self,
consent: &ConsentId,
token: &OAuth2Token,
account_id: &str,
) -> Result<Vec<Balance>>;
/// Fetch transactions for an account within an optional time range.
fn transactions(
&self,
consent: &ConsentId,
token: &OAuth2Token,
account_id: &str,
from: Option<time::OffsetDateTime>,
to: Option<time::OffsetDateTime>,
) -> Result<Vec<Transaction>>;
}
#[cfg(test)]
mod tests {
use super::*;
use op_core::Currency;
#[test]
fn balance_round_trips_through_serde() {
let b = Balance {
balance_type: BalanceType::InterimAvailable,
amount: Money::from_minor(12_345, Currency::GBP),
credit_debit: TransactionCredit::Credit,
as_of: time::OffsetDateTime::UNIX_EPOCH,
};
let json = serde_json::to_string(&b).expect("ser");
let back: Balance = serde_json::from_str(&json).expect("de");
assert_eq!(b, back);
}
#[test]
fn account_subtype_other_carries_string() {
let s = AccountSubtype::Other("LINE_OF_CREDIT".into());
let json = serde_json::to_string(&s).expect("ser");
assert!(json.contains("LINE_OF_CREDIT"));
}
}