pix_brcode_parser/
types.rs

1//! Type definitions for PIX BR Code data structures
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// Main BR Code structure representing a PIX QR code
7#[derive(Debug, Clone, PartialEq)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9pub struct BRCode {
10    /// Payload format indicator (always "01" for EMV QRCPS)
11    pub payload_format_indicator: String,
12    
13    /// Point of initiation method ("11" for static, "12" for dynamic)
14    pub point_of_initiation_method: Option<String>,
15    
16    /// Merchant account information containing PIX key
17    pub merchant_account_info: MerchantAccountInfo,
18    
19    /// Merchant category code (MCC)
20    pub merchant_category_code: String,
21    
22    /// Transaction currency code ("986" for BRL)
23    pub transaction_currency: String,
24    
25    /// Transaction amount (optional for static QR codes)
26    pub transaction_amount: Option<String>,
27    
28    /// Country code ("BR" for Brazil)
29    pub country_code: String,
30    
31    /// Merchant name (max 25 characters)
32    pub merchant_name: String,
33    
34    /// Merchant city (max 15 characters)
35    pub merchant_city: String,
36    
37    /// Additional data field template
38    pub additional_data: Option<AdditionalData>,
39    
40    /// CRC16 checksum for validation
41    pub crc16: String,
42}
43
44/// Merchant account information specific to PIX
45#[derive(Debug, Clone, PartialEq)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub struct MerchantAccountInfo {
48    /// Globally Unique Identifier ("br.gov.bcb.pix")
49    pub gui: String,
50    
51    /// PIX key (UUID, email, phone, CPF, CNPJ, or random key)
52    pub pix_key: String,
53    
54    /// Optional description for the payment
55    pub description: Option<String>,
56    
57    /// URL for dynamic QR codes
58    pub url: Option<String>,
59}
60
61/// Additional data field template
62#[derive(Debug, Clone, PartialEq)]
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64pub struct AdditionalData {
65    /// Bill number
66    pub bill_number: Option<String>,
67    
68    /// Mobile number
69    pub mobile_number: Option<String>,
70    
71    /// Store label
72    pub store_label: Option<String>,
73    
74    /// Loyalty number
75    pub loyalty_number: Option<String>,
76    
77    /// Reference label
78    pub reference_label: Option<String>,
79    
80    /// Customer label
81    pub customer_label: Option<String>,
82    
83    /// Terminal label
84    pub terminal_label: Option<String>,
85    
86    /// Purpose of transaction
87    pub purpose_of_transaction: Option<String>,
88    
89    /// Additional consumer data request
90    pub additional_consumer_data_request: Option<String>,
91}
92
93/// PIX key types
94#[derive(Debug, Clone, PartialEq)]
95#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
96pub enum PixKeyType {
97    /// UUID format key
98    Uuid,
99    /// Email address
100    Email,
101    /// Phone number (+5511999999999)
102    Phone,
103    /// CPF (individual taxpayer number)
104    Cpf,
105    /// CNPJ (company taxpayer number)  
106    Cnpj,
107    /// Random key (alphanumeric)
108    Random,
109}
110
111/// EMV data field
112#[derive(Debug, Clone, PartialEq)]
113pub struct EmvField {
114    /// Field tag identifier
115    pub tag: String,
116    /// Field length
117    pub length: usize,
118    /// Field value
119    pub value: String,
120}
121
122impl BRCode {
123    /// Check if this is a static QR code
124    pub fn is_static(&self) -> bool {
125        self.point_of_initiation_method.as_deref() == Some("11")
126    }
127    
128    /// Check if this is a dynamic QR code
129    pub fn is_dynamic(&self) -> bool {
130        self.point_of_initiation_method.as_deref() == Some("12")
131    }
132    
133    /// Get the PIX key type
134    pub fn pix_key_type(&self) -> PixKeyType {
135        classify_pix_key(&self.merchant_account_info.pix_key)
136    }
137    
138    /// Check if amount is specified
139    pub fn has_amount(&self) -> bool {
140        self.transaction_amount.is_some()
141    }
142}
143
144/// Classify a PIX key by its format
145pub fn classify_pix_key(key: &str) -> PixKeyType {
146    // UUID format: 8-4-4-4-12 hex digits
147    if key.len() == 36 && key.chars().nth(8) == Some('-') && key.chars().nth(13) == Some('-') {
148        return PixKeyType::Uuid;
149    }
150    
151    // Email format
152    if key.contains('@') && key.contains('.') {
153        return PixKeyType::Email;
154    }
155    
156    // Phone format: +5511999999999
157    if key.starts_with("+55") && key.len() >= 13 {
158        return PixKeyType::Phone;
159    }
160    
161    // CPF: 11 digits
162    if key.len() == 11 && key.chars().all(|c| c.is_ascii_digit()) {
163        return PixKeyType::Cpf;
164    }
165    
166    // CNPJ: 14 digits
167    if key.len() == 14 && key.chars().all(|c| c.is_ascii_digit()) {
168        return PixKeyType::Cnpj;
169    }
170    
171    // Default to random key
172    PixKeyType::Random
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_pix_key_classification() {
181        assert_eq!(classify_pix_key("123e4567-e89b-12d3-a456-426614174000"), PixKeyType::Uuid);
182        assert_eq!(classify_pix_key("user@example.com"), PixKeyType::Email);
183        assert_eq!(classify_pix_key("+5511999999999"), PixKeyType::Phone);
184        assert_eq!(classify_pix_key("12345678901"), PixKeyType::Cpf);
185        assert_eq!(classify_pix_key("12345678000195"), PixKeyType::Cnpj);
186        assert_eq!(classify_pix_key("randomkey123"), PixKeyType::Random);
187    }
188}