Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::banking_faster_payments_limit_data::{SCHEME_LIMITS, SCHEME_LIMITS_HISTORY}; ← this capability’s own data, compiled from data/scheme-limits.json into the same file by fune build
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
fn is_iso_date(value: &str) -> bool {
let bytes = value.as_bytes();
bytes.len() == 10
&& bytes[4] == b'-'
&& bytes[7] == b'-'
&& bytes
.iter()
.enumerate()
.all(|(i, b)| i == 4 || i == 7 || b.is_ascii_digit())
}
/// The scheme maximum per payment in force on a date, in pence.
fn scheme_limit_on(on_date: &str) -> i64 {
let mut best: Option<(i64, &'static str)> = None;
for row in SCHEME_LIMITS {
if on_date < row.valid_from {
continue;
}
if let Some(valid_to) = row.valid_to {
if on_date > valid_to {
continue;
}
}
if best.map_or(true, |(_, from)| row.valid_from > from) {
best = Some((row.limit_minor, row.valid_from));
}
}
if let Some((limit, _)) = best {
return limit;
}
let mut earliest: Option<&'static str> = None;
for row in SCHEME_LIMITS {
if earliest.map_or(true, |e| row.valid_from < e) {
earliest = Some(row.valid_from);
}
}
let earliest = earliest.unwrap_or("");
// A build installed with history=current only carries today's limit, and
// answering an old date with it would be exactly the wrong answer.
if SCHEME_LIMITS_HISTORY != "full" && !earliest.is_empty() && on_date < earliest {
panic!(
"no Faster Payments scheme limit on {}: this build was installed with history={}, so it only carries limits from {}. Reinstall with history=full to check older payments.",
on_date, SCHEME_LIMITS_HISTORY, earliest
);
}
panic!("no Faster Payments scheme limit on {}: the scheme opened on {}", on_date, earliest)
}
/// Check a Faster Payment against three limits, in order: the scheme maximum
/// on the payment date, the bank's per-payment limit, and what is left of the
/// bank's daily limit. The decision names the first one broken.
///
/// # Panics
/// Panics on a malformed date, a date before the scheme opened, a currency
/// other than GBP, an amount of zero or less, or a negative limit.
pub fn faster_payments_check(
amount: &Money,
sent_today: &Money,
per_transaction_limit: &Money,
daily_limit: &Money,
on_date: &str,
) -> FasterPaymentsCheck {
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
for m in [amount, sent_today, per_transaction_limit, daily_limit] {
if m.currency != "GBP" {
panic!("Faster Payments is a sterling scheme: received {}", m.currency);
}
}
if amount.minor <= 0 {
panic!("amount must be greater than zero, received {}", amount.minor);
}
if sent_today.minor < 0 || per_transaction_limit.minor < 0 || daily_limit.minor < 0 {
panic!("sentToday and the limits must not be negative");
}
let scheme = scheme_limit_on(on_date);
let remaining = (daily_limit.minor - sent_today.minor).max(0);
let decision = if amount.minor > scheme {
"over-scheme-limit"
} else if amount.minor > per_transaction_limit.minor {
"over-transaction-limit"
} else if amount.minor > remaining {
"over-daily-limit"
} else {
"allowed"
};
FasterPaymentsCheck {
decision: decision.to_string(),
allowed: decision == "allowed",
scheme_limit: money(scheme, "GBP"),
remaining_today: money(remaining, "GBP"),
}
}
pub fn faster_payments_check_to_value(check: &FasterPaymentsCheck) -> Value {
Value::obj(vec![
("decision", Value::str(&check.decision)),
("allowed", Value::Bool(check.allowed)),
("schemeLimit", money_to_value(&check.scheme_limit)),
("remainingToday", money_to_value(&check.remaining_today)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
faster_payments_check_to_value(&faster_payments_check(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
&money_from_value(&args[2]),
&money_from_value(&args[3]),
args[4].as_str(),
))
}