fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
for &byte in data {
crc ^= u16::from(byte) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x1021
} else {
crc << 1
};
}
}
crc
}
fn tlv(id: &str, value: &str) -> String {
format!("{id}{:02}{value}", value.len())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MerchantAccount {
tag: u8,
guid: String,
merchant_id: Option<String>,
}
impl MerchantAccount {
#[must_use]
pub fn new(tag: u8, guid: impl Into<String>) -> Self {
MerchantAccount {
tag: tag.clamp(26, 51),
guid: guid.into(),
merchant_id: None,
}
}
#[must_use]
pub fn merchant_id(mut self, id: impl Into<String>) -> Self {
self.merchant_id = Some(id.into());
self
}
fn to_tlv(&self) -> String {
let mut inner = tlv("00", &self.guid);
if let Some(id) = &self.merchant_id {
inner.push_str(&tlv("01", id));
}
tlv(&format!("{:02}", self.tag), &inner)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MerchantPayment {
account: MerchantAccount,
category_code: String,
currency: String,
amount: Option<String>,
country_code: String,
merchant_name: String,
merchant_city: String,
}
impl MerchantPayment {
#[must_use]
pub fn new(
account: MerchantAccount,
currency: impl Into<String>,
country_code: impl Into<String>,
merchant_name: impl Into<String>,
merchant_city: impl Into<String>,
) -> Self {
MerchantPayment {
account,
category_code: "0000".to_string(),
currency: currency.into(),
amount: None,
country_code: country_code.into(),
merchant_name: merchant_name.into(),
merchant_city: merchant_city.into(),
}
}
#[must_use]
pub fn category_code(mut self, mcc: impl Into<String>) -> Self {
self.category_code = mcc.into();
self
}
#[must_use]
pub fn amount(mut self, amount: impl Into<String>) -> Self {
self.amount = Some(amount.into());
self
}
#[must_use]
pub fn to_emvco(&self) -> String {
let mut s = String::new();
s.push_str(&tlv("00", "01")); s.push_str(&tlv("01", if self.amount.is_some() { "12" } else { "11" }));
s.push_str(&self.account.to_tlv());
s.push_str(&tlv("52", &self.category_code));
s.push_str(&tlv("53", &self.currency));
if let Some(amount) = &self.amount {
s.push_str(&tlv("54", amount));
}
s.push_str(&tlv("58", &self.country_code));
s.push_str(&tlv("59", &self.merchant_name));
s.push_str(&tlv("60", &self.merchant_city));
s.push_str("6304");
s.push_str(&format!("{:04X}", crc16(s.as_bytes())));
s
}
}
impl core::fmt::Display for MerchantPayment {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.to_emvco())
}
}