use super::AllowanceTerms;
use crate::{PaymentAmount, PaymentEndpointIdentifier, PaymentRequestTerms, Result};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum AllowanceEvaluationBlock {
#[error("Payment Request terms are invalid")]
InvalidRequest,
#[error("Payment Request asset does not match the Allowance")]
AssetMismatch,
#[error("Payment Request amount is outside the Allowance range")]
AmountOutsideRange,
#[error("No Payment Endpoint Identifier is allowed by both request and Allowance")]
NoEligibleEndpoint,
#[error("Allowance is not active yet")]
NotActive,
#[error("Allowance has expired")]
Expired,
#[error("Allowance evaluation time is earlier than its watermark")]
ClockRollback,
#[error("Allowance usage contains a future admission time")]
FutureUsage,
#[error("Allowance usage asset does not match its terms")]
UsageAssetMismatch,
#[error("Allowance period {index} amount ceiling would be exceeded")]
PeriodAmountLimit {
index: usize,
},
#[error("Allowance period {index} payment count would be exceeded")]
PeriodCountLimit {
index: usize,
},
#[error("Allowance lifetime amount ceiling would be exceeded")]
LifetimeAmountLimit,
#[error("Allowance arithmetic or UTC boundary cannot be represented")]
ArithmeticOverflow,
}
impl AllowanceEvaluationBlock {
pub fn code(&self) -> &'static str {
match self {
Self::InvalidRequest => "invalid_request",
Self::AssetMismatch => "asset_mismatch",
Self::AmountOutsideRange => "amount_outside_range",
Self::NoEligibleEndpoint => "no_eligible_endpoint",
Self::NotActive => "not_active",
Self::Expired => "expired",
Self::ClockRollback => "clock_rollback",
Self::FutureUsage => "future_usage",
Self::UsageAssetMismatch => "usage_asset_mismatch",
Self::PeriodAmountLimit { .. } => "period_amount_limit",
Self::PeriodCountLimit { .. } => "period_count_limit",
Self::LifetimeAmountLimit => "lifetime_amount_limit",
Self::ArithmeticOverflow => "arithmetic_overflow",
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct AllowanceUsageEntry {
amount: PaymentAmount,
admitted_at: DateTime<Utc>,
}
impl std::fmt::Debug for AllowanceUsageEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AllowanceUsageEntry(<redacted>)")
}
}
impl AllowanceUsageEntry {
pub fn new(amount: PaymentAmount, admitted_at: DateTime<Utc>) -> Result<Self> {
amount.validate_with_label("Allowance usage amount")?;
if admitted_at.timestamp_subsec_nanos() >= 1_000_000_000 {
return Err(crate::PaykitError::Validation(
"Allowance usage time cannot represent a leap second".into(),
));
}
Ok(Self {
amount,
admitted_at,
})
}
pub fn amount(&self) -> &PaymentAmount {
&self.amount
}
pub fn admitted_at(&self) -> DateTime<Utc> {
self.admitted_at
}
}
pub struct AllowanceEvaluationInput<'a> {
pub terms: &'a AllowanceTerms,
pub request: &'a PaymentRequestTerms,
pub trusted_time: DateTime<Utc>,
pub watermark: DateTime<Utc>,
pub usage: &'a [AllowanceUsageEntry],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AllowanceEvaluation {
pub eligible_payment_endpoint_identifiers: Vec<PaymentEndpointIdentifier>,
}