1use crate::EMVTag;
2use crate::error::{QRError, Result};
3use crate::tags;
4
5#[derive(Debug, Default, Clone)]
7pub struct AdditionalData {
8 pub bill_number: Option<String>,
10 pub mobile_number: Option<String>,
12 pub store_label: Option<String>,
14 pub loyalty_number: Option<String>,
16 pub reference_label: Option<String>,
18 pub customer_label: Option<String>,
20 pub terminal_number: Option<String>,
22 pub purpose: Option<String>,
24 pub additional_customer_data: Option<String>,
26 pub merchant_tax_id: Option<String>,
28 pub merchant_channel: Option<String>,
30 pub due_date: Option<String>,
32 pub amount_after_due_date: Option<String>,
34}
35
36impl AdditionalData {
37 #[must_use]
38 pub fn new() -> AdditionalData {
39 AdditionalData::default()
40 }
41
42 pub fn bill_number(mut self, bill_number: impl Into<String>) -> Self {
43 self.bill_number = Some(bill_number.into());
44 self
45 }
46
47 pub fn mobile_number(mut self, mobile_number: impl Into<String>) -> Self {
48 self.mobile_number = Some(mobile_number.into());
49 self
50 }
51
52 pub fn store_label(mut self, store_label: impl Into<String>) -> Self {
53 self.store_label = Some(store_label.into());
54 self
55 }
56
57 pub fn loyalty_number(mut self, loyalty_number: impl Into<String>) -> Self {
58 self.loyalty_number = Some(loyalty_number.into());
59 self
60 }
61
62 pub fn reference_label(mut self, reference_label: impl Into<String>) -> Self {
63 self.reference_label = Some(reference_label.into());
64 self
65 }
66
67 pub fn customer_label(mut self, customer_label: impl Into<String>) -> Self {
68 self.customer_label = Some(customer_label.into());
69 self
70 }
71
72 pub fn terminal_number(mut self, terminal_number: impl Into<String>) -> Self {
73 self.terminal_number = Some(terminal_number.into());
74 self
75 }
76
77 pub fn purpose(mut self, purpose: impl Into<String>) -> Self {
78 self.purpose = Some(purpose.into());
79 self
80 }
81
82 pub fn additional_customer_data(mut self, additional_customer_data: impl Into<String>) -> Self {
83 self.additional_customer_data = Some(additional_customer_data.into());
84 self
85 }
86
87 pub fn merchant_tax_id(mut self, merchant_tax_id: impl Into<String>) -> Self {
88 self.merchant_tax_id = Some(merchant_tax_id.into());
89 self
90 }
91
92 pub fn merchant_channel(mut self, merchant_channel: impl Into<String>) -> Self {
93 self.merchant_channel = Some(merchant_channel.into());
94 self
95 }
96
97 pub fn due_date(mut self, due_date: impl Into<String>) -> Self {
99 self.due_date = Some(due_date.into());
100 self
101 }
102
103 pub fn amount_after_due_date(mut self, amount_after_due_date: impl Into<String>) -> Self {
104 self.amount_after_due_date = Some(amount_after_due_date.into());
105 self
106 }
107
108 pub fn encode(&self) -> Option<EMVTag> {
110 let mut sub_tags = Vec::new();
111
112 if let Some(ref value) = self.bill_number {
113 sub_tags.push(EMVTag::new("01", value));
114 }
115 if let Some(ref value) = self.mobile_number {
116 sub_tags.push(EMVTag::new("02", value));
117 }
118 if let Some(ref value) = self.store_label {
119 sub_tags.push(EMVTag::new("03", value));
120 }
121 if let Some(ref value) = self.loyalty_number {
122 sub_tags.push(EMVTag::new("04", value));
123 }
124 if let Some(ref value) = self.reference_label {
125 sub_tags.push(EMVTag::new("05", value));
126 }
127 if let Some(ref value) = self.customer_label {
128 sub_tags.push(EMVTag::new("06", value));
129 }
130 if let Some(ref value) = self.terminal_number {
131 sub_tags.push(EMVTag::new("07", value));
132 }
133 if let Some(ref value) = self.purpose {
134 sub_tags.push(EMVTag::new("08", value));
135 }
136 if let Some(ref value) = self.additional_customer_data {
137 sub_tags.push(EMVTag::new("09", value));
138 }
139 if let Some(ref value) = self.merchant_tax_id {
140 sub_tags.push(EMVTag::new("10", value));
141 }
142 if let Some(ref value) = self.merchant_channel {
143 sub_tags.push(EMVTag::new("11", value));
144 }
145 if let Some(ref value) = self.due_date {
146 sub_tags.push(EMVTag::new("50", value));
147 }
148 if let Some(ref value) = self.amount_after_due_date {
149 sub_tags.push(EMVTag::new("51", value));
150 }
151
152 if sub_tags.is_empty() {
153 None
154 } else {
155 let value = sub_tags
156 .iter()
157 .map(super::EMVTag::encode)
158 .collect::<String>();
159 Some(EMVTag::new(tags::ADDITIONAL_DATA, value))
160 }
161 }
162}
163
164#[derive(Debug, Default, Clone)]
166pub struct ExtensionFields {
167 pub transaction_context: Option<String>,
169 pub discounts_loyalty: Option<String>,
171 pub offline_to_online: Option<String>,
173 pub ecommerce: Option<String>,
175 pub end_to_end_id: Option<String>,
177 pub transaction_type_code: Option<String>,
179}
180
181#[derive(Debug, Clone)]
183pub enum ConvenienceFee {
184 Prompt,
186 Fixed(String),
188 Percentage(String),
190}
191
192#[derive(Debug, Clone)]
194pub enum SchemeConfig {
195 Visa {
196 account_info: String,
197 },
198 Mastercard {
199 account_info: String,
200 },
201 UnionPay {
202 account_info: String,
203 },
204 IPSET {
205 guid: String,
206 bic: String,
207 account: String,
208 },
209}
210
211impl SchemeConfig {
212 pub fn ips_et(guid: &str, bic: &str, account: &str) -> Self {
214 SchemeConfig::IPSET {
215 guid: guid.to_string(),
216 bic: bic.to_string(),
217 account: account.to_string(),
218 }
219 }
220
221 pub fn visa(account_info: impl Into<String>) -> Self {
223 Self::Visa {
224 account_info: account_info.into(),
225 }
226 }
227
228 pub fn mastercard(account_info: impl Into<String>) -> Self {
230 Self::Mastercard {
231 account_info: account_info.into(),
232 }
233 }
234
235 pub fn tag_id(&self) -> &str {
237 match self {
238 SchemeConfig::Visa { .. } => tags::VISA,
239 SchemeConfig::Mastercard { .. } => tags::MASTERCARD,
240 SchemeConfig::UnionPay { .. } => tags::UNIONPAY,
241 SchemeConfig::IPSET { .. } => tags::IPS_ET,
242 }
243 }
244
245 pub fn encode(&self) -> Result<EMVTag> {
247 match self {
248 SchemeConfig::Visa { account_info } => Ok(EMVTag::new(tags::VISA, account_info)),
249 SchemeConfig::Mastercard { account_info } => {
250 Ok(EMVTag::new(tags::MASTERCARD, account_info))
251 }
252 SchemeConfig::UnionPay { account_info } => {
253 Ok(EMVTag::new(tags::UNIONPAY, account_info))
254 }
255 SchemeConfig::IPSET { guid, bic, account } => {
256 if guid.len() != 32 || !guid.chars().all(|c| c.is_ascii_alphanumeric()) {
258 return Err(QRError::InvalidValue {
259 field: "guid".to_string(),
260 value: guid.clone(),
261 });
262 }
263
264 if bic.len() != 8 && bic.len() != 11 {
266 return Err(QRError::InvalidValue {
267 field: "bic".to_string(),
268 value: bic.clone(),
269 });
270 }
271
272 if account.len() > 24 {
274 return Err(QRError::InvalidValue {
275 field: "account".to_string(),
276 value: account.clone(),
277 });
278 }
279
280 let sub_tag_00 = EMVTag::new("00", guid);
282 let sub_tag_01 = EMVTag::new("01", bic);
283 let sub_tag_02 = EMVTag::new("02", account);
284
285 let value = format!(
286 "{}{}{}",
287 sub_tag_00.encode(),
288 sub_tag_01.encode(),
289 sub_tag_02.encode()
290 );
291
292 Ok(EMVTag::new(tags::IPS_ET, value))
293 }
294 }
295 }
296}