open-standards/openpay/crates/op-driver-sdk/tests/orchestrator_integration.rs
Transaction Science 70ecd6caa0 Initial import — three open standards under one shelf
Transaction Science open standards monorepo. Each subdirectory is a
self-contained reference protocol with its own README and licence.

- openpay/    Apache-2.0  payment-acceptance stack (Rust, 26 crates)
- smart-byte/ CC-BY-4.0   content-addressed value carrier (spec)
- eoc/        CC-BY-4.0   energy-optimised compute (spec)

The wire format and the right to fork are public. The optional hosted
services are the offer; the protocols are not.
2026-05-23 07:58:09 -04:00

286 lines
10 KiB
Rust

//! End-to-end integration: wire `DeterministicCardAcquirer` and
//! `DeterministicA2aGateway` into a real Orchestrator, prove the
//! sample-driver path works without any live PSP.
use std::sync::Arc;
use op_core::RailKind;
use op_core::{CryptoAddress, Currency, Money, PaymentMethod, VaultRef};
use op_driver_sdk::{
DeterministicA2aGateway, DeterministicCardAcquirer, DeterministicCryptoGateway, conformance,
};
use op_orchestrator::{
A2aAdapter, CardAdapter, CryptoAdapter, IdempotencyKey, MerchantBankProfile, Orchestrator,
PaymentIntent, PolicyRouter, TerminalStatus,
};
use op_rails_a2a::acquirer::{A2aStatus, ParticipantId};
use op_rails_card::acquirer::AuthStatus;
use op_rails_crypto::{CryptoStatus, StableToken};
#[test]
fn deterministic_card_routes_through_orchestrator_and_settles() {
let acquirer = Arc::new(DeterministicCardAcquirer::new());
let card = Arc::new(CardAdapter::new("det-card", acquirer.clone()));
let mut orch = Orchestrator::new().with_router(Box::new(PolicyRouter::new(
vec!["det-card".to_owned()],
vec![],
)));
orch.register_adapter(card);
let intent = PaymentIntent::new(
IdempotencyKey::new("e2e-card-1"),
Money::from_minor(2500, Currency::USD),
PaymentMethod::Vault(VaultRef::new("tok_v7_e2e")),
);
let outcome = orch.run(&intent).unwrap();
assert_eq!(outcome.terminal_status, TerminalStatus::Approved);
assert_eq!(outcome.attempt_count(), 1);
assert!(outcome.psp_payment_id.is_some());
// Idempotency key flowed into the acquirer untouched.
let history = acquirer.auth_history();
assert_eq!(history.len(), 1);
assert_eq!(history[0].idempotency_key, "e2e-card-1");
}
#[test]
fn deterministic_card_with_key_override_declines() {
let acquirer = Arc::new(DeterministicCardAcquirer::new().with_key_override(
"e2e-decline",
AuthStatus::HardDecline,
Some("nsf".into()),
));
let card = Arc::new(CardAdapter::new("det-card", acquirer));
let mut orch = Orchestrator::new().with_router(Box::new(PolicyRouter::new(
vec!["det-card".to_owned()],
vec![],
)));
orch.register_adapter(card);
let intent = PaymentIntent::new(
IdempotencyKey::new("e2e-decline"),
Money::from_minor(2500, Currency::USD),
PaymentMethod::Vault(VaultRef::new("tok_v7_decline")),
);
let outcome = orch.run(&intent).unwrap();
assert_eq!(outcome.terminal_status, TerminalStatus::Declined);
}
#[test]
fn deterministic_a2a_routes_through_orchestrator() {
let gateway = Arc::new(DeterministicA2aGateway::new().with_name("det-a2a"));
let profile = MerchantBankProfile {
creditor_agent: ParticipantId::Aba("021000021".into()),
creditor_account: "1234567890".into(),
creditor_name: "ACME CORP".into(),
default_debtor_agent: ParticipantId::Aba("121000248".into()),
default_debtor_name: "Customer".into(),
};
let a2a = Arc::new(A2aAdapter::new("det-a2a", gateway.clone(), profile));
let mut orch = Orchestrator::new().with_router(Box::new(PolicyRouter::new(
vec![],
vec!["det-a2a".to_owned()],
)));
orch.register_adapter(a2a);
let intent = PaymentIntent::new(
IdempotencyKey::new("e2e-a2a-1"),
Money::from_minor(50_000, Currency::USD),
PaymentMethod::A2a(op_core::A2aKey::UsAch {
routing: "021000021".into(),
account: "9999".into(),
}),
);
let outcome = orch.run(&intent).unwrap();
assert_eq!(outcome.terminal_status, TerminalStatus::Approved);
assert!(outcome.uetr.is_some());
assert_eq!(gateway.transfer_history().len(), 1);
}
#[test]
fn deterministic_a2a_with_amount_rule_routes_to_pending() {
let gateway = Arc::new(
DeterministicA2aGateway::new()
.with_name("det-a2a")
.with_amount_ge(
Money::from_minor(1_000_000, Currency::USD),
A2aStatus::Pending,
None,
),
);
let profile = MerchantBankProfile {
creditor_agent: ParticipantId::Aba("021000021".into()),
creditor_account: "1234567890".into(),
creditor_name: "ACME CORP".into(),
default_debtor_agent: ParticipantId::Aba("121000248".into()),
default_debtor_name: "Customer".into(),
};
let a2a = Arc::new(A2aAdapter::new("det-a2a", gateway, profile));
let mut orch = Orchestrator::new().with_router(Box::new(PolicyRouter::new(
vec![],
vec!["det-a2a".to_owned()],
)));
orch.register_adapter(a2a);
let intent = PaymentIntent::new(
IdempotencyKey::new("e2e-big"),
Money::from_minor(2_000_000, Currency::USD),
PaymentMethod::A2a(op_core::A2aKey::UsAch {
routing: "021000021".into(),
account: "9999".into(),
}),
);
// Pending → orchestrator treats as soft failure. With no
// fallback rail this surfaces as either `Declined` or
// `Err(AllRailsExhausted)` depending on the engine config;
// both are correct "not approved" signals.
match orch.run(&intent) {
Ok(outcome) => {
assert_ne!(outcome.terminal_status, TerminalStatus::Approved);
}
Err(op_orchestrator::Error::AllRailsExhausted { .. }) => {}
Err(other) => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn deterministic_crypto_routes_through_orchestrator() {
let gateway = Arc::new(
DeterministicCryptoGateway::for_token(StableToken::UsdcBase).with_name("usdc-base"),
);
let adapter = Arc::new(CryptoAdapter::new("usdc-base", gateway.clone()));
let router =
PolicyRouter::new(vec![], vec![]).with_crypto_drivers(vec!["usdc-base".to_owned()]);
let mut orch = Orchestrator::new().with_router(Box::new(router));
orch.register_adapter(adapter);
let intent = PaymentIntent::new(
IdempotencyKey::new("crypto-e2e-1"),
Money::from_minor(50_000, Currency::USD),
PaymentMethod::Crypto(CryptoAddress::new(
"base",
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
)),
);
let outcome = orch.run(&intent).unwrap();
assert_eq!(outcome.terminal_status, TerminalStatus::Approved);
assert!(outcome.psp_payment_id.is_some(), "tx_hash expected");
let history = gateway.transfer_history();
assert_eq!(history.len(), 1);
assert_eq!(history[0].idempotency_key, "crypto-e2e-1");
}
#[test]
fn crypto_rejected_status_declines_terminally() {
let gateway = Arc::new(
DeterministicCryptoGateway::for_token(StableToken::UsdcSolana)
.with_name("usdc-solana")
.with_key_override("rev", CryptoStatus::Rejected, Some("revert".into())),
);
let adapter = Arc::new(CryptoAdapter::new("usdc-solana", gateway));
let router =
PolicyRouter::new(vec![], vec![]).with_crypto_drivers(vec!["usdc-solana".to_owned()]);
let mut orch = Orchestrator::new().with_router(Box::new(router));
orch.register_adapter(adapter);
let intent = PaymentIntent::new(
IdempotencyKey::new("rev"),
Money::from_minor(100, Currency::USD),
PaymentMethod::Crypto(CryptoAddress::new(
"solana",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
)),
);
let outcome = orch.run(&intent).unwrap();
assert_eq!(outcome.terminal_status, TerminalStatus::Declined);
}
#[test]
fn three_ds_challenge_then_resume_completes() {
// Stand up an orchestrator with a deterministic card acquirer
// that returns RequiresCustomerAction for a specific
// idempotency key. Then call resume() with the returned
// psp_payment_id and confirm the final outcome is Approved.
let acquirer = Arc::new(DeterministicCardAcquirer::new().with_key_override(
"3ds-pending",
AuthStatus::RequiresCustomerAction,
None,
));
let card = Arc::new(CardAdapter::new("det-card", acquirer.clone()));
let mut orch = Orchestrator::new().with_router(Box::new(PolicyRouter::new(
vec!["det-card".to_owned()],
vec![],
)));
orch.register_adapter(card);
let intent = PaymentIntent::new(
IdempotencyKey::new("3ds-pending"),
Money::from_minor(15_000, Currency::USD),
PaymentMethod::Vault(VaultRef::new("tok_v7_3ds")),
);
let outcome = orch.run(&intent).unwrap();
assert_eq!(
outcome.terminal_status,
TerminalStatus::RequiresCustomerAction
);
let psp_id = outcome.psp_payment_id.expect("psp id on challenge");
// Customer completes the challenge out-of-band; operator
// calls back into the orchestrator with the same intent +
// psp_payment_id.
let resumed = orch
.resume(&intent, RailKind::Card, "det-card", &psp_id)
.unwrap();
assert_eq!(resumed.terminal_status, TerminalStatus::Approved);
assert_eq!(resumed.psp_payment_id.as_deref(), Some(psp_id.as_str()));
}
#[test]
fn resume_against_unknown_driver_errors() {
let acquirer = Arc::new(DeterministicCardAcquirer::new());
let card = Arc::new(CardAdapter::new("det-card", acquirer));
let mut orch = Orchestrator::new().with_router(Box::new(PolicyRouter::new(
vec!["det-card".to_owned()],
vec![],
)));
orch.register_adapter(card);
let intent = PaymentIntent::new(
IdempotencyKey::new("missing"),
Money::from_minor(100, Currency::USD),
PaymentMethod::Vault(VaultRef::new("tok")),
);
let err = orch
.resume(&intent, RailKind::Card, "no-such-driver", "psp-x")
.unwrap_err();
assert!(matches!(err, op_orchestrator::Error::NoEligibleRail { .. }));
}
#[test]
fn all_three_deterministic_drivers_pass_conformance() {
let card = DeterministicCardAcquirer::new();
let card_report = conformance::run_card(&card);
assert!(
card_report.is_clean(),
"card conformance failed: {:?}",
card_report.failures
);
let a2a = DeterministicA2aGateway::new();
let a2a_report = conformance::run_a2a(&a2a);
assert!(
a2a_report.is_clean(),
"a2a conformance failed: {:?}",
a2a_report.failures
);
let crypto = DeterministicCryptoGateway::new();
let crypto_report = conformance::run_crypto(&crypto);
assert!(
crypto_report.is_clean(),
"crypto conformance failed: {:?}",
crypto_report.failures
);
}