use serde::{Deserialize, Serialize};
use crate::error::{MppError, Result};
#[cfg(feature = "evm")]
use crate::evm::U256;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ChargeRequest {
pub amount: String,
pub currency: String,
#[serde(skip)]
pub decimals: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recipient: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "externalId", skip_serializing_if = "Option::is_none")]
pub external_id: Option<String>,
#[serde(rename = "methodDetails", skip_serializing_if = "Option::is_none")]
pub method_details: Option<serde_json::Value>,
}
impl ChargeRequest {
pub fn with_base_units(mut self) -> Result<Self> {
if let Some(decimals) = self.decimals {
self.amount = super::parse_units(&self.amount, decimals)?;
self.decimals = None;
}
Ok(self)
}
pub fn parse_amount(&self) -> Result<u128> {
self.amount
.parse()
.map_err(|_| MppError::InvalidAmount(format!("Invalid amount: {}", self.amount)))
}
#[cfg(feature = "evm")]
pub fn parse_amount_u256(&self) -> Result<U256> {
crate::evm::parse_amount(&self.amount)
}
pub fn validate_max_amount(&self, max_amount: &str) -> Result<()> {
let amount = self.parse_amount()?;
let max: u128 = max_amount
.parse()
.map_err(|_| MppError::InvalidAmount(format!("Invalid max amount: {}", max_amount)))?;
if amount > max {
return Err(MppError::AmountExceedsMax {
required: amount,
max,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_charge_request_serialization() {
let req = ChargeRequest {
amount: "10000".to_string(),
currency: "0x123".to_string(),
recipient: Some("0x456".to_string()),
description: None,
external_id: None,
method_details: Some(serde_json::json!({
"chainId": 42431,
"feePayer": true
})),
..Default::default()
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"amount\":\"10000\""));
assert!(json.contains("\"methodDetails\""));
assert!(json.contains("\"chainId\":42431"));
let parsed: ChargeRequest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.amount, "10000");
}
#[test]
fn test_parse_amount() {
let req = ChargeRequest {
amount: "1000000".to_string(),
..Default::default()
};
assert_eq!(req.parse_amount().unwrap(), 1_000_000u128);
let invalid = ChargeRequest {
amount: "not-a-number".to_string(),
..Default::default()
};
assert!(invalid.parse_amount().is_err());
}
#[cfg(feature = "evm")]
#[test]
fn test_parse_amount_u256() {
let req = ChargeRequest {
amount: "340282366920938463463374607431768211456".to_string(), ..Default::default()
};
let parsed = req.parse_amount_u256().unwrap();
assert_eq!(
parsed.to_string(),
"340282366920938463463374607431768211456"
);
}
#[test]
fn test_with_base_units() {
let req = ChargeRequest {
amount: "1.5".to_string(),
currency: "0x123".to_string(),
decimals: Some(6),
..Default::default()
};
let converted = req.with_base_units().unwrap();
assert_eq!(converted.amount, "1500000");
assert!(converted.decimals.is_none());
}
#[test]
fn test_with_base_units_no_decimals() {
let req = ChargeRequest {
amount: "1000000".to_string(),
currency: "0x123".to_string(),
..Default::default()
};
let converted = req.with_base_units().unwrap();
assert_eq!(converted.amount, "1000000");
}
#[test]
fn test_validate_max_amount() {
let req = ChargeRequest {
amount: "1000".to_string(),
..Default::default()
};
assert!(req.validate_max_amount("2000").is_ok());
assert!(req.validate_max_amount("1000").is_ok());
assert!(req.validate_max_amount("500").is_err());
}
}