Skip to main content

paykit_lib/allowance/
evaluation_types.rs

1//! Inputs and outcomes for stateless Allowance evaluation.
2
3use super::AllowanceTerms;
4use crate::{PaymentAmount, PaymentEndpointIdentifier, PaymentRequestTerms, Result};
5use chrono::{DateTime, Utc};
6
7/// Why the common Allowance rules did not admit a candidate payment.
8#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
9pub enum AllowanceEvaluationBlock {
10    /// The caller supplied structurally invalid Payment Request terms.
11    #[error("Payment Request terms are invalid")]
12    InvalidRequest,
13    /// Request and Allowance assets differ, including case.
14    #[error("Payment Request asset does not match the Allowance")]
15    AssetMismatch,
16    /// Request amount is outside the inclusive per-payment range.
17    #[error("Payment Request amount is outside the Allowance range")]
18    AmountOutsideRange,
19    /// The request and Allowance have no common endpoint identifier.
20    #[error("No Payment Endpoint Identifier is allowed by both request and Allowance")]
21    NoEligibleEndpoint,
22    /// The inclusive active time has not been reached.
23    #[error("Allowance is not active yet")]
24    NotActive,
25    /// The exclusive expiry has been reached.
26    #[error("Allowance has expired")]
27    Expired,
28    /// Evaluation would move trusted time behind its durable watermark.
29    #[error("Allowance evaluation time is earlier than its watermark")]
30    ClockRollback,
31    /// Retained usage was admitted later than this evaluation time.
32    #[error("Allowance usage contains a future admission time")]
33    FutureUsage,
34    /// Accounting evidence uses a different asset from the Allowance.
35    #[error("Allowance usage asset does not match its terms")]
36    UsageAssetMismatch,
37    /// One configured period amount ceiling would be exceeded.
38    #[error("Allowance period {index} amount ceiling would be exceeded")]
39    PeriodAmountLimit {
40        /// Index into the original `period_limits` array.
41        index: usize,
42    },
43    /// One configured period count ceiling would be exceeded.
44    #[error("Allowance period {index} payment count would be exceeded")]
45    PeriodCountLimit {
46        /// Index into the original `period_limits` array.
47        index: usize,
48    },
49    /// The lifetime amount ceiling would be exceeded.
50    #[error("Allowance lifetime amount ceiling would be exceeded")]
51    LifetimeAmountLimit,
52    /// A time boundary, duration, or count cannot be represented safely.
53    #[error("Allowance arithmetic or UTC boundary cannot be represented")]
54    ArithmeticOverflow,
55}
56
57impl AllowanceEvaluationBlock {
58    /// Stable machine-readable code without private values.
59    pub fn code(&self) -> &'static str {
60        match self {
61            Self::InvalidRequest => "invalid_request",
62            Self::AssetMismatch => "asset_mismatch",
63            Self::AmountOutsideRange => "amount_outside_range",
64            Self::NoEligibleEndpoint => "no_eligible_endpoint",
65            Self::NotActive => "not_active",
66            Self::Expired => "expired",
67            Self::ClockRollback => "clock_rollback",
68            Self::FutureUsage => "future_usage",
69            Self::UsageAssetMismatch => "usage_asset_mismatch",
70            Self::PeriodAmountLimit { .. } => "period_amount_limit",
71            Self::PeriodCountLimit { .. } => "period_count_limit",
72            Self::LifetimeAmountLimit => "lifetime_amount_limit",
73            Self::ArithmeticOverflow => "arithmetic_overflow",
74        }
75    }
76}
77
78/// One committed automatic payment or unresolved automatic reservation.
79///
80/// Both consume one count and the exact original amount. Exclude released
81/// reservations and manual payments; the SDK must deduplicate payment keys.
82#[derive(Clone, PartialEq, Eq)]
83pub struct AllowanceUsageEntry {
84    amount: PaymentAmount,
85    admitted_at: DateTime<Utc>,
86}
87
88impl std::fmt::Debug for AllowanceUsageEntry {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.write_str("AllowanceUsageEntry(<redacted>)")
91    }
92}
93
94impl AllowanceUsageEntry {
95    /// Validate an amount and retain its original admission time and decimal text.
96    pub fn new(amount: PaymentAmount, admitted_at: DateTime<Utc>) -> Result<Self> {
97        amount.validate_with_label("Allowance usage amount")?;
98        if admitted_at.timestamp_subsec_nanos() >= 1_000_000_000 {
99            return Err(crate::PaykitError::Validation(
100                "Allowance usage time cannot represent a leap second".into(),
101            ));
102        }
103        Ok(Self {
104            amount,
105            admitted_at,
106        })
107    }
108
109    /// Exact original Payment Amount; no fee or refund adjustment is implied.
110    pub fn amount(&self) -> &PaymentAmount {
111        &self.amount
112    }
113
114    /// Original wallet admission time; settlement and retries do not change it.
115    pub fn admitted_at(&self) -> DateTime<Utc> {
116        self.admitted_at
117    }
118}
119
120/// Explicit inputs to common eligibility math, without lifecycle or payment authority.
121///
122/// `usage` must contain all committed and unresolved automatic usage for these
123/// exact Allowance Terms, once per semantic payment key. The SDK must establish
124/// complete, current accounting and watermark evidence before calling this API;
125/// an empty slice cannot prove that an Allowance has never been used.
126pub struct AllowanceEvaluationInput<'a> {
127    /// Immutable shared Allowance Terms.
128    pub terms: &'a AllowanceTerms,
129    /// Immutable Payment Request terms.
130    pub request: &'a PaymentRequestTerms,
131    /// Wallet-supplied trusted UTC time.
132    pub trusted_time: DateTime<Utc>,
133    /// Persisted nondecreasing evaluation-time watermark, never a guessed default.
134    pub watermark: DateTime<Utc>,
135    /// Complete committed usage and unresolved reservations, excluding the candidate.
136    pub usage: &'a [AllowanceUsageEntry],
137}
138
139/// Successful common-rule evaluation; this does not authorize or reserve payment.
140#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct AllowanceEvaluation {
142    /// Identifiers permitted by both request and Allowance, in request order.
143    pub eligible_payment_endpoint_identifiers: Vec<PaymentEndpointIdentifier>,
144}