use std::sync::Arc;
use crate::errors::AppError;
use crate::services::{DepositFeeService, FeeConfig, SolPriceService};
const USD_MINOR_UNITS: f64 = 1_000_000.0;
#[derive(Debug, Clone)]
pub struct CreditParams {
pub deposit_amount: i64,
pub deposit_currency: String,
pub has_swap: bool,
pub has_privacy: bool,
}
#[derive(Debug, Clone)]
pub struct CreditResult {
pub amount: i64,
pub currency: String,
pub fee_deducted: i64,
pub conversion_rate: Option<f64>,
}
pub struct DepositCreditService {
sol_price_service: Arc<SolPriceService>,
fee_service: Arc<DepositFeeService>,
company_currency: String,
}
impl DepositCreditService {
pub fn new(
sol_price_service: Arc<SolPriceService>,
fee_service: Arc<DepositFeeService>,
company_currency: String,
) -> Self {
Self {
sol_price_service,
fee_service,
company_currency,
}
}
fn credit_currency(&self) -> &'static str {
match self.company_currency.to_uppercase().as_str() {
"SOL" => "SOL",
"USDC" | "USDT" => "USD",
"EURC" => "EUR",
_ => "USD", }
}
fn is_sol_company(&self) -> bool {
self.company_currency.to_uppercase() == "SOL"
}
pub async fn calculate(&self, params: CreditParams) -> Result<CreditResult, AppError> {
let fee_config = self.fee_service.get_config().await?;
let (amount_in_company_currency, conversion_rate) = self
.convert_to_company_currency(params.deposit_amount, ¶ms.deposit_currency)
.await?;
let amount_lamports = if params.deposit_currency == "SOL" {
params.deposit_amount as u64
} else {
let usd = params.deposit_amount as f64 / USD_MINOR_UNITS;
self.sol_price_service.usd_to_lamports(usd).await?
};
let fees = self.fee_service.calculate_fees(
amount_lamports,
params.has_swap,
params.has_privacy,
&fee_config,
);
let fee_deduction_lamports = self.fee_service.user_deduction(&fees, fee_config.policy);
let fee_deduction = self
.convert_lamports_to_company_currency(fee_deduction_lamports)
.await?;
let final_amount = amount_in_company_currency.saturating_sub(fee_deduction);
Ok(CreditResult {
amount: final_amount.max(0), currency: self.credit_currency().to_string(),
fee_deducted: fee_deduction,
conversion_rate,
})
}
async fn convert_to_company_currency(
&self,
amount: i64,
deposit_currency: &str,
) -> Result<(i64, Option<f64>), AppError> {
match (
deposit_currency.to_uppercase().as_str(),
self.is_sol_company(),
) {
("SOL", true) => Ok((amount, None)),
("SOL", false) => {
let usd = self
.sol_price_service
.lamports_to_usd(amount as u64)
.await?;
let price = self.sol_price_service.get_sol_price_usd().await?;
Ok(((usd * USD_MINOR_UNITS) as i64, Some(price)))
}
("USD", true) => {
let usd = amount as f64 / USD_MINOR_UNITS;
let lamports = self.sol_price_service.usd_to_lamports(usd).await?;
let price = self.sol_price_service.get_sol_price_usd().await?;
Ok((lamports as i64, Some(price)))
}
("USD", false) => Ok((amount, None)),
(_, false) => Ok((amount, None)),
(_, true) => {
let usd = amount as f64 / USD_MINOR_UNITS;
let lamports = self.sol_price_service.usd_to_lamports(usd).await?;
let price = self.sol_price_service.get_sol_price_usd().await?;
Ok((lamports as i64, Some(price)))
}
}
}
async fn convert_lamports_to_company_currency(&self, lamports: i64) -> Result<i64, AppError> {
if self.is_sol_company() {
Ok(lamports)
} else {
let usd = self
.sol_price_service
.lamports_to_usd(lamports as u64)
.await?;
Ok((usd * USD_MINOR_UNITS) as i64)
}
}
pub async fn get_fee_config(&self) -> Result<FeeConfig, AppError> {
self.fee_service.get_config().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::repositories::InMemorySystemSettingsRepository;
use crate::services::SettingsService;
fn create_test_service(company_currency: &str) -> DepositCreditService {
let settings_repo = Arc::new(InMemorySystemSettingsRepository::new());
let settings_service = Arc::new(SettingsService::new(settings_repo));
let sol_price_service = Arc::new(SolPriceService::new());
let fee_service = Arc::new(DepositFeeService::new(settings_service));
DepositCreditService::new(sol_price_service, fee_service, company_currency.to_string())
}
#[test]
fn test_credit_currency() {
let service = create_test_service("USDC");
assert_eq!(service.credit_currency(), "USD");
let service = create_test_service("USDT");
assert_eq!(service.credit_currency(), "USD");
let service = create_test_service("SOL");
assert_eq!(service.credit_currency(), "SOL");
let service = create_test_service("EURC");
assert_eq!(service.credit_currency(), "EUR");
}
#[test]
fn test_is_sol_company() {
let service = create_test_service("SOL");
assert!(service.is_sol_company());
let service = create_test_service("USDC");
assert!(!service.is_sol_company());
}
}