OpenPay (1 crate, ~2940 LOC, 40 tests)
op-statements Statement generation + reconciliation + fee accounting.
Render targets: PDF (template), CSV, JSON, 94-char
NACHA fixed-width. ISO 20022 camt.053 with full
BkToCstmrStmt envelope (GrpHdr/Stmt/Bal/Ntry). BAI2
with 01/02/03/16/49/98/99 records. MT940/MT942
SWIFT with :20:/:25:/:28C:/:60F:/:61:/:86:/:62F:
+ :90C:/:90D: counters. FeeBucket (interchange/scheme/
acquirer/fx/settlement-network/bnpl) + FeeSchedule
with bps + flat rules. Cadence (daily/weekly/monthly/
custom). Two-tier Reconciler (external_id → window
heuristic).
Smart Byte (1 crate, 3032 LOC, 39 tests)
smart-byte-activitypub ActivityPub W3C Rec (2018). AS2 vocab + Public
addressing; Actor (Person/Application/Group/
Organization/Service) with publicKey + shared-inbox;
Object (Note/Article/Image/Video/Tombstone) with
Mastodon extensions (sensitive, CW summary,
conversation, blurhash); 12 Activity types
(Create/Update/Delete/Follow/Accept/Reject/Announce/
Like/Undo/Move/Block/Flag) with side-effect rules;
OrderedCollection + Page; Webfinger (RFC 7033);
HTTP Signatures (cavage draft-12 + RFC 9421);
SHA-256 Digest; Ed25519 sign/verify.
EOC (1 crate, 2759 LOC, 20 tests)
eoc-observability OpenTelemetry-compatible tracing + metrics + logs.
W3C tracecontext + baggage. Span / SpanKind / Status.
Samplers: AlwaysOn/Off/RatioBased/ParentBased. Sync
BatchSpanProcessor + Exporter trait. Counter / Gauge /
Histogram. Prometheus text exposition (HELP/TYPE/
_bucket/_sum/_count/+Inf). StatsD / DogStatsD lines.
GenAI semconv (gen_ai.{operation,system,request.*,
response.*,usage.*}) + EOC extensions (eoc.joules.*,
eoc.co2e_grams, eoc.cost.usd). USD + gCO2e attribution
with PUE. LangSmith + LangFuse export adapters.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
59 lines
2.2 KiB
Rust
59 lines
2.2 KiB
Rust
//! Integration: build a fee schedule mirroring a Stripe-classic
|
|
//! 2.9% + 30c card price plus a 0.13% scheme assessment, and assert
|
|
//! both lines accrue at the expected magnitudes.
|
|
|
|
use op_core::{Currency, Money};
|
|
use op_statements::{FeeBucket, FeeRule, FeeSchedule};
|
|
|
|
#[test]
|
|
fn stripe_classic_plus_scheme_accrual() {
|
|
let sched = FeeSchedule::new(Currency::USD)
|
|
.with_rule(
|
|
FeeRule::new(FeeBucket::Acquirer, 290, 30) // 2.90% + 30c
|
|
.with_code("stripe-standard"),
|
|
)
|
|
.with_rule(
|
|
FeeRule::new(FeeBucket::Scheme, 13, 0) // 0.13%
|
|
.with_code("visa-fanf"),
|
|
)
|
|
.with_rule(
|
|
FeeRule::new(FeeBucket::Interchange, 165, 10) // 1.65% + 10c — Visa CPS retail
|
|
.with_code("cps-retail"),
|
|
);
|
|
|
|
let fees = sched
|
|
.accrue(Money::from_minor(10_000, Currency::USD), Some("ord-1"))
|
|
.expect("accrue");
|
|
assert_eq!(fees.len(), 3);
|
|
|
|
// Acquirer: 10000 * 290 / 10000 + 30 = 290 + 30 = 320
|
|
assert_eq!(fees[0].bucket, FeeBucket::Acquirer);
|
|
assert_eq!(fees[0].amount.minor_units, 320);
|
|
assert_eq!(fees[0].code.as_deref(), Some("stripe-standard"));
|
|
assert_eq!(fees[0].against_external_id.as_deref(), Some("ord-1"));
|
|
|
|
// Scheme: 10000 * 13 / 10000 = 13
|
|
assert_eq!(fees[1].bucket, FeeBucket::Scheme);
|
|
assert_eq!(fees[1].amount.minor_units, 13);
|
|
|
|
// Interchange: 10000 * 165 / 10000 + 10 = 165 + 10 = 175
|
|
assert_eq!(fees[2].bucket, FeeBucket::Interchange);
|
|
assert_eq!(fees[2].amount.minor_units, 175);
|
|
|
|
let grouped = FeeSchedule::group_by_bucket(&fees);
|
|
assert_eq!(grouped.len(), 3);
|
|
let total_minor: i64 = grouped.iter().map(|(_, m)| *m).sum();
|
|
assert_eq!(total_minor, 320 + 13 + 175);
|
|
}
|
|
|
|
#[test]
|
|
fn jpy_zero_decimal_accrual() {
|
|
// JPY has 0 decimal places; flat amounts are whole yen.
|
|
let sched = FeeSchedule::new(Currency::JPY).with_rule(FeeRule::new(FeeBucket::Acquirer, 290, 50));
|
|
let fees = sched
|
|
.accrue(Money::from_minor(10_000, Currency::JPY), None)
|
|
.expect("accrue");
|
|
// 10000 * 290 / 10000 + 50 = 290 + 50 = 340 yen
|
|
assert_eq!(fees.len(), 1);
|
|
assert_eq!(fees[0].amount.minor_units, 340);
|
|
}
|