///|
/// Parsed six-digit processing code (DE3).
pub(all) struct ProcessingCode {
transaction_type : String
from_account : String
to_account : String
} derive(Eq, Debug)
///|
/// Common transaction-type meaning.
pub(all) enum TransactionType {
GoodsAndServices
CashWithdrawal
DebitAdjustment
CreditAdjustment
BalanceInquiry
AccountTransfer
Payment
Deposit
UnknownTransactionType(String)
} derive(Eq, Debug)
///|
/// Common account-type meaning.
pub(all) enum AccountType {
DefaultAccount
Savings
Checking
Credit
Universal
Investment
ElectronicPurse
UnknownAccountType(String)
} derive(Eq, Debug)
///|
/// Parse DE3 into three two-digit components.
pub fn parse_processing_code(
value : String,
) -> Result[ProcessingCode, IsoError] {
if value.length() != 6 {
return Err(InvalidProcessingCode(value))
}
match validate_numeric_text(3, value) {
Err(_) => return Err(InvalidProcessingCode(value))
Ok(_) => ()
}
Ok({
transaction_type: value[0:2].to_owned(),
from_account: value[2:4].to_owned(),
to_account: value[4:6].to_owned(),
})
}
///|
/// Build DE3 from validated two-digit components.
pub fn processing_code(
transaction_type : String,
from_account : String,
to_account : String,
) -> Result[String, IsoError] {
let value = transaction_type + from_account + to_account
match parse_processing_code(value) {
Ok(_) => Ok(value)
Err(error) => Err(error)
}
}
///|
/// Map a DE3 transaction type to a named meaning.
pub fn ProcessingCode::transaction(self : ProcessingCode) -> TransactionType {
match self.transaction_type {
"00" => GoodsAndServices
"01" => CashWithdrawal
"02" => DebitAdjustment
"20" => CreditAdjustment
"30" => BalanceInquiry
"40" => AccountTransfer
"50" => Payment
"21" => Deposit
other => UnknownTransactionType(other)
}
}
///|
/// Map the source-account component.
pub fn ProcessingCode::source_account(self : ProcessingCode) -> AccountType {
account_type(self.from_account)
}
///|
/// Map the destination-account component.
pub fn ProcessingCode::destination_account(
self : ProcessingCode,
) -> AccountType {
account_type(self.to_account)
}
///|
/// Decode one two-digit account type.
pub fn account_type(value : String) -> AccountType {
match value {
"00" => DefaultAccount
"10" => Savings
"20" => Checking
"30" => Credit
"40" => Universal
"50" => Investment
"60" => ElectronicPurse
other => UnknownAccountType(other)
}
}
///|
/// Human-readable transaction-type label.
pub fn TransactionType::label(self : TransactionType) -> String {
match self {
GoodsAndServices => "goods and services"
CashWithdrawal => "cash withdrawal"
DebitAdjustment => "debit adjustment"
CreditAdjustment => "credit adjustment"
BalanceInquiry => "balance inquiry"
AccountTransfer => "account transfer"
Payment => "payment"
Deposit => "deposit"
UnknownTransactionType(value) => "transaction type \{value}"
}
}
///|
/// Human-readable account-type label.
pub fn AccountType::label(self : AccountType) -> String {
match self {
DefaultAccount => "default"
Savings => "savings"
Checking => "checking"
Credit => "credit"
Universal => "universal"
Investment => "investment"
ElectronicPurse => "electronic purse"
UnknownAccountType(value) => "account type \{value}"
}
}
///|
/// Parsed unsigned minor-unit amount.
pub(all) struct MinorAmount {
minor_units : Int64
digits : String
scale : Int
} derive(Eq, Debug)
///|
/// Parse a fixed-width numeric amount in minor currency units.
pub fn parse_minor_amount(
field : Int,
value : String,
width : Int,
scale : Int,
) -> Result[MinorAmount, IsoError] {
if value.length() != width || width <= 0 || scale < 0 || scale > width {
return Err(InvalidAmount(value))
}
match validate_numeric_text(field, value) {
Err(_) => return Err(InvalidAmount(value))
Ok(_) => ()
}
let mut amount : Int64 = 0
for i = 0; i < value.length(); i = i + 1 {
amount = amount * 10 + (value[i].to_int() - 48).to_int64()
}
Ok({ minor_units: amount, digits: value, scale, })
}
///|
/// Format a non-negative minor-unit amount as fixed-width digits.
pub fn format_minor_amount(
value : Int64,
width : Int,
) -> Result[String, IsoError] {
if value < 0 || width <= 0 {
return Err(InvalidAmount(value.to_string()))
}
let text = value.to_string()
if text.length() > width {
return Err(InvalidAmount(text))
}
Ok(left_pad(text, width, '0'))
}
///|
/// Format a decimal display without floating-point arithmetic.
pub fn MinorAmount::display(self : MinorAmount) -> String {
if self.scale == 0 {
return self.minor_units.to_string()
}
let padded = left_pad(self.minor_units.to_string(), self.scale + 1, '0')
let split = padded.length() - self.scale
padded[0:split].to_owned() + "." + padded[split:].to_owned()
}
///|
/// ISO 4217 numeric code represented by DE49/50/51.
pub(all) struct CurrencyCode {
numeric : String
alpha : String?
minor_units : Int?
} derive(Eq, Debug)
///|
/// Parse a three-digit currency code with selected common metadata.
pub fn parse_currency(value : String) -> Result[CurrencyCode, IsoError] {
if value.length() != 3 {
return Err(InvalidCurrency(value))
}
match validate_numeric_text(49, value) {
Err(_) => return Err(InvalidCurrency(value))
Ok(_) => ()
}
let (alpha, minor) = match value {
"036" => (Some("AUD"), Some(2))
"124" => (Some("CAD"), Some(2))
"156" => (Some("CNY"), Some(2))
"344" => (Some("HKD"), Some(2))
"356" => (Some("INR"), Some(2))
"392" => (Some("JPY"), Some(0))
"410" => (Some("KRW"), Some(0))
"702" => (Some("SGD"), Some(2))
"826" => (Some("GBP"), Some(2))
"840" => (Some("USD"), Some(2))
"978" => (Some("EUR"), Some(2))
_ => (None, None)
}
Ok({ numeric: value, alpha, minor_units: minor, })
}
///|
/// Combine DE4 and DE49 into an inspectable amount description.
pub fn describe_transaction_amount(
amount : String,
currency : String,
) -> Result[String, IsoError] {
let code = match parse_currency(currency) {
Ok(value) => value
Err(error) => return Err(error)
}
let scale = code.minor_units.unwrap_or(2)
let parsed = match parse_minor_amount(4, amount, 12, scale) {
Ok(value) => value
Err(error) => return Err(error)
}
let name = code.alpha.unwrap_or(code.numeric)
Ok("\{name} \{parsed.display()}")
}