use crate::channel::{Bank, Wallet};
use crate::money::Money;
use rustlavel_core::{Error, Json, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Account {
Bank { bank: Bank, number: String, holder: String },
Wallet { wallet: Wallet, phone: String, holder: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransferRequest {
pub reference: String,
pub amount: Money,
pub to: Account,
pub description: String,
}
impl TransferRequest {
pub fn new(reference: impl Into<String>, amount: Money, to: Account) -> TransferRequest {
TransferRequest { reference: reference.into(), amount, to, description: String::new() }
}
pub fn description(mut self, description: impl Into<String>) -> TransferRequest {
self.description = description.into();
self
}
pub fn validate(&self) -> Result<()> {
self.amount.positive()?;
if self.reference.trim().is_empty() {
return Err(Error::msg("a transfer needs a reference: it is what stops a retry paying twice"));
}
let number = match &self.to {
Account::Bank { number, .. } => number,
Account::Wallet { phone, .. } => phone,
};
if number.trim().is_empty() {
return Err(Error::msg("a transfer needs a destination account or number"));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransferStatus {
Pending,
Completed,
Failed,
}
impl TransferStatus {
pub fn as_str(self) -> &'static str {
match self {
TransferStatus::Pending => "pending",
TransferStatus::Completed => "completed",
TransferStatus::Failed => "failed",
}
}
pub fn parse(text: &str) -> Option<TransferStatus> {
Some(match text.trim().to_ascii_lowercase().as_str() {
"pending" | "processing" => TransferStatus::Pending,
"completed" | "success" | "settled" => TransferStatus::Completed,
"failed" => TransferStatus::Failed,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transfer {
pub id: String,
pub reference: String,
pub status: TransferStatus,
pub amount: Money,
pub failure: Option<String>,
}
impl Transfer {
pub fn to_json(&self) -> Json {
Json::object([
("id", Json::from(self.id.as_str())),
("reference", Json::from(self.reference.as_str())),
("status", Json::from(self.status.as_str())),
("amount", self.amount.to_json()),
("failure", self.failure.as_deref().map_or(Json::Null, Json::from)),
])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_transfer_needs_a_reference_and_a_destination() {
let to = Account::Bank { bank: Bank::Bca, number: "1234567890".into(), holder: "Ada".into() };
assert!(TransferRequest::new("payout-1", Money::idr(50_000), to.clone()).validate().is_ok());
assert!(TransferRequest::new("", Money::idr(50_000), to.clone()).validate().is_err());
let nowhere = Account::Wallet { wallet: Wallet::Dana, phone: " ".into(), holder: "Ada".into() };
assert!(TransferRequest::new("payout-2", Money::idr(50_000), nowhere).validate().is_err());
}
}