Skip to main content

promptparse/generate/
any_id.rs

1use crate::tlv::{encode, tag, with_crc_tag};
2use crate::Result;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ProxyType {
6    /// Mobile number
7    Msisdn,
8    /// National ID or Tax ID
9    NatId,
10    /// E-Wallet ID
11    EWalletId,
12    /// Bank Account (Reserved)
13    BankAcc,
14}
15
16impl ProxyType {
17    pub(crate) fn to_code(self) -> &'static str {
18        match self {
19            ProxyType::Msisdn => "01",
20            ProxyType::NatId => "02",
21            ProxyType::EWalletId => "03",
22            ProxyType::BankAcc => "04",
23        }
24    }
25}
26
27#[derive(Debug, Clone)]
28pub struct AnyIdConfig {
29    /// Proxy type
30    pub proxy_type: ProxyType,
31    /// Recipient number
32    pub target: String,
33    /// Transaction amount
34    pub amount: Option<f64>,
35}
36
37/// Generate PromptPay AnyID (Tag 29) QR Code
38pub fn any_id(config: AnyIdConfig) -> Result<String> {
39    let mut target = config.target;
40
41    if matches!(config.proxy_type, ProxyType::Msisdn) {
42        // Convert mobile number format: remove leading 0, add 66, pad to 13 digits
43        if target.starts_with('0') {
44            target = format!("66{}", &target[1..]);
45        }
46        target = format!("{target:0>13}");
47    }
48
49    let tag29_data = vec![
50        tag("00", "A000000677010111"),
51        tag(config.proxy_type.to_code(), &target),
52    ];
53
54    let mut payload = vec![
55        tag("00", "01"),
56        tag("01", if config.amount.is_none() { "11" } else { "12" }),
57        tag("29", &encode(&tag29_data)),
58        tag("53", "764"),
59        tag("58", "TH"),
60    ];
61
62    if let Some(amount) = config.amount {
63        payload.push(tag("54", &format!("{amount:.2}")));
64    }
65
66    Ok(with_crc_tag(&encode(&payload), "63", true))
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_any_id_msisdn() {
75        let config = AnyIdConfig {
76            proxy_type: ProxyType::Msisdn,
77            target: "0812223333".to_string(),
78            amount: None,
79        };
80        let result = any_id(config).unwrap();
81        assert_eq!(
82            result,
83            "00020101021129370016A0000006770101110113006681222333353037645802TH63041DCF"
84        );
85    }
86
87    #[test]
88    fn test_any_id_msisdn_with_amount() {
89        let config = AnyIdConfig {
90            proxy_type: ProxyType::Msisdn,
91            target: "0812223333".to_string(),
92            amount: Some(30.0),
93        };
94        let result = any_id(config).unwrap();
95        assert_eq!(
96            result,
97            "00020101021229370016A0000006770101110113006681222333353037645802TH540530.0063043CAD"
98        );
99    }
100
101    #[test]
102    fn test_any_id_natid() {
103        let config = AnyIdConfig {
104            proxy_type: ProxyType::NatId,
105            target: "1234567890123".to_string(),
106            amount: None,
107        };
108        let result = any_id(config).unwrap();
109        // Just verify it doesn't error and produces a result
110        assert!(!result.is_empty());
111    }
112}