From dbf712ccfa73366f1568beb0b7fa93800bb7e5e6 Mon Sep 17 00:00:00 2001 From: Ayush <114248859+ayush22667@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:38:25 +0530 Subject: [PATCH] feat(installments): add InstallmentEntries struct for validated installment data --- crates/common_types/src/payments.rs | 71 ++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index 6fedf17280..8f5294ac8a 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -1249,7 +1249,7 @@ pub struct NetworkTransactionIdAndDecryptedWalletTokenDetails { } /// Billing frequency for a card installment plan -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum BillingFrequency { /// Monthly billing @@ -1396,6 +1396,72 @@ pub struct InstallmentOptionData { pub interest_rate: InstallmentInterestRate, } +/// A validated list of installment entries with no duplicate counts per billing frequency. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] +#[serde(try_from = "Vec")] +pub struct InstallmentEntries(Vec); + +impl InstallmentEntries { + fn validate_no_duplicate_counts_per_frequency( + installments: &[InstallmentOptionData], + ) -> Result<(), errors::ValidationError> { + installments + .iter() + .try_fold( + HashMap::<&BillingFrequency, HashSet>::new(), + |mut seen, entry| { + entry + .number_of_installments + .as_slice() + .iter() + .try_fold(&mut seen, |seen, &count| { + seen.entry(&entry.billing_frequency) + .or_default() + .insert(count) + .then_some(seen) + .ok_or_else(|| { + error_stack::report!( + errors::ValidationError::InvalidValue { + message: format!( + "installment count {count} appears in multiple entries with the same billing frequency." + ), + } + ) + }) + })?; + Ok(seen) + }, + ) + .map(|_| ()) + } +} + +impl TryFrom> for InstallmentEntries { + type Error = Report; + + fn try_from(entries: Vec) -> Result { + Self::validate_no_duplicate_counts_per_frequency(&entries)?; + Ok(Self(entries)) + } +} + +impl std::ops::Deref for InstallmentEntries { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl IntoIterator for InstallmentEntries { + type Item = InstallmentOptionData; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + /// Installment options grouped by payment method #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq)] pub struct InstallmentOption { @@ -1403,7 +1469,8 @@ pub struct InstallmentOption { #[schema(value_type = PaymentMethod)] pub payment_method: common_enums::PaymentMethod, /// List of available installment configurations - pub installments: Vec, + #[schema(value_type = Vec)] + pub installments: InstallmentEntries, } /// A list of installment options stored as a single JSONB column value.