Skip to main content

emv_3ds/message/
areq.rs

1use crate::types::device::{BrowserInfo, DeviceRenderOptions, SdkInfo};
2use crate::types::{
3    AccountInfo, AccountType, AuthenticationMethod, ChallengeIndicator, DeliveryTimeframe,
4    DeviceChannel, MessageCategory, MessageVersion, PreOrderPurchaseIndicator,
5    ReorderItemsIndicator, ShipIndicator, ThreeDsMethod,
6};
7use serde::{Deserialize, Serialize};
8
9/// EMV 3DS Authentication Request (AReq).
10///
11/// Sent by the 3DS Server to the Directory Server to initiate authentication.
12/// Fields marked `Option` are conditional or optional per the EMVCo spec.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct AuthenticationRequest {
16    // ── Message envelope ──────────────────────────────────────────────────
17    pub message_type: MessageType,
18    pub message_version: MessageVersion,
19    /// UUIDv4 assigned by the 3DS Server for this transaction.
20    #[serde(rename = "threeDSServerTransID")]
21    pub three_ds_server_trans_id: String,
22    pub device_channel: DeviceChannel,
23    pub message_category: MessageCategory,
24
25    // ── Requestor identity ────────────────────────────────────────────────
26    pub three_ds_requestor_id: String,
27    pub three_ds_requestor_name: String,
28    pub three_ds_requestor_url: String,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub three_ds_requestor_authentication_ind: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub three_ds_requestor_authentication_info: Option<AuthenticationInfo>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub three_ds_requestor_challenge_ind: Option<ChallengeIndicator>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub three_ds_requestor_prior_authentication_info: Option<PriorAuthenticationInfo>,
37
38    // ── Card / account ────────────────────────────────────────────────────
39    /// PAN — must be encrypted when transmitted outside the 3DS Server.
40    pub acct_number: String,
41    /// YYMM
42    pub card_expiry_date: String,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub acct_type: Option<AccountType>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub acct_info: Option<AccountInfo>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub acct_id: Option<String>,
49
50    // ── Purchase ──────────────────────────────────────────────────────────
51    /// Minor-unit purchase amount as a numeric string.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub purchase_amount: Option<String>,
54    /// ISO 4217 numeric currency code, zero-padded to 3 digits.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub purchase_currency: Option<String>,
57    /// Decimal exponent (e.g. "2" for cents).
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub purchase_exponent: Option<String>,
60    /// UTC datetime, format: YYYYMMDDHHMMSS.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub purchase_date: Option<String>,
63    /// Type of transaction: "01" goods/service, "03" cash-advance, etc.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub trans_type: Option<String>,
66    /// Recurring/instalment expiry date (YYYYMMDD).
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub recurring_expiry: Option<String>,
69    /// Days between recurring charges.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub recurring_frequency: Option<String>,
72    /// Instalments count.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub purchase_instal_data: Option<String>,
75
76    // ── Merchant ──────────────────────────────────────────────────────────
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub merchant_id: Option<String>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub mcc: Option<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub merchant_name: Option<String>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub merchant_country_code: Option<String>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub merchant_risk_indicator: Option<MerchantRiskIndicator>,
87
88    // ── Cardholder ────────────────────────────────────────────────────────
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub cardholder_name: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub email: Option<String>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub home_phone: Option<PhoneNumber>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub mobile_phone: Option<PhoneNumber>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub work_phone: Option<PhoneNumber>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub bill_addr_city: Option<String>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub bill_addr_country: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub bill_addr_line1: Option<String>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub bill_addr_line2: Option<String>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub bill_addr_line3: Option<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub bill_addr_post_code: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub bill_addr_state: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub ship_addr_city: Option<String>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub ship_addr_country: Option<String>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub ship_addr_line1: Option<String>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub ship_addr_line2: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub ship_addr_line3: Option<String>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub ship_addr_post_code: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub ship_addr_state: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub addr_match: Option<AddrMatch>,
129
130    // ── 3DS Method ────────────────────────────────────────────────────────
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub three_ds_comp_ind: Option<ThreeDsMethod>,
133    /// Notification URL where the ACS posts the CRes (browser channel).
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub notification_url: Option<String>,
136
137    // ── Browser channel ───────────────────────────────────────────────────
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub browser_info: Option<BrowserInfo>,
140
141    // ── App channel ───────────────────────────────────────────────────────
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub sdk_info: Option<SdkInfo>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub device_render_options: Option<DeviceRenderOptions>,
146}
147
148impl AuthenticationRequest {
149    pub fn message_type_value() -> &'static str {
150        "AReq"
151    }
152}
153
154/// Constant "AReq" literal for the messageType field.
155#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156pub enum MessageType {
157    #[default]
158    AReq,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct AuthenticationInfo {
164    pub three_ds_req_auth_method: AuthenticationMethod,
165    pub three_ds_req_auth_timestamp: String,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub three_ds_req_auth_data: Option<String>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct PriorAuthenticationInfo {
173    pub three_ds_req_prior_ref: String,
174    pub three_ds_req_prior_auth_method: String,
175    pub three_ds_req_prior_auth_timestamp: String,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub three_ds_req_prior_auth_data: Option<String>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct MerchantRiskIndicator {
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub delivery_email_address: Option<String>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub delivery_timeframe: Option<DeliveryTimeframe>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub gift_card_amount: Option<String>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub gift_card_count: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub gift_card_curr: Option<String>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub pre_order_date: Option<String>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub pre_order_purchase_ind: Option<PreOrderPurchaseIndicator>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub reorder_items_ind: Option<ReorderItemsIndicator>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub ship_indicator: Option<ShipIndicator>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct PhoneNumber {
206    pub cc: String,
207    pub subscriber: String,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
211pub enum AddrMatch {
212    #[serde(rename = "Y")]
213    Match,
214    #[serde(rename = "N")]
215    NoMatch,
216}