Skip to main content

emv_3ds/message/
ares.rs

1use crate::types::{Eci, MessageVersion, TransStatus, TransStatusReason};
2use serde::{Deserialize, Serialize};
3
4/// EMV 3DS Authentication Response (ARes).
5///
6/// Returned by the Directory Server (relaying from the ACS) in response to an AReq.
7/// A `transStatus` of `C` means the ACS requires a challenge; `Y` or `A` means
8/// authentication succeeded frictionlessly.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct AuthenticationResponse {
12    pub message_type: MessageType,
13    pub message_version: MessageVersion,
14
15    // ── Transaction IDs ───────────────────────────────────────────────────
16    #[serde(rename = "threeDSServerTransID")]
17    pub three_ds_server_trans_id: String,
18    #[serde(rename = "acsTransID")]
19    pub acs_trans_id: String,
20    #[serde(rename = "dsTransID")]
21    pub ds_trans_id: String,
22
23    // ── Authentication outcome ────────────────────────────────────────────
24    pub trans_status: TransStatus,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub trans_status_reason: Option<TransStatusReason>,
27    /// Whether the ACS mandates a challenge regardless of requestor preference.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub acs_challenge_mandated: Option<AcsMandated>,
30    /// ECI value to include in the authorization request.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub eci: Option<Eci>,
33    /// CAVV / AAV — base64url encoded; present only on Y or A.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub authentication_value: Option<String>,
36
37    // ── Challenge parameters (only when transStatus = C) ─────────────────
38    /// URL of the ACS challenge page (browser channel).
39    #[serde(rename = "acsURL", skip_serializing_if = "Option::is_none")]
40    pub acs_url: Option<String>,
41    /// JWS-signed content for app-based challenge.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub acs_signed_content: Option<String>,
44    /// Decoupled authentication maximum timeout (minutes).
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub acs_dec_con_ind: Option<String>,
47
48    // ── DS reference info ─────────────────────────────────────────────────
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub acs_reference_number: Option<String>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub ds_reference_number: Option<String>,
53
54    // ── Cardholder info (may be returned for informational NPA) ──────────
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub cardholder_info: Option<String>,
57
58    // ── Whitelisting (v2.2+) ──────────────────────────────────────────────
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub whitelist_status: Option<WhitelistStatus>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub whitelist_status_source: Option<WhitelistStatusSource>,
63}
64
65impl AuthenticationResponse {
66    /// True if authentication completed without a challenge.
67    pub fn is_frictionless(&self) -> bool {
68        self.trans_status.is_authenticated()
69    }
70
71    /// True if the requestor must present a challenge UI to the cardholder.
72    pub fn requires_challenge(&self) -> bool {
73        self.trans_status.requires_challenge()
74    }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78pub enum MessageType {
79    ARes,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83pub enum AcsMandated {
84    #[serde(rename = "Y")]
85    Yes,
86    #[serde(rename = "N")]
87    No,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub enum WhitelistStatus {
92    #[serde(rename = "Y")]
93    Whitelisted,
94    #[serde(rename = "N")]
95    NotWhitelisted,
96    #[serde(rename = "E")]
97    NotEligible,
98    #[serde(rename = "P")]
99    Pending,
100    #[serde(rename = "R")]
101    RequestedByRequestor,
102    #[serde(rename = "U")]
103    Unknown,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107pub enum WhitelistStatusSource {
108    #[serde(rename = "01")]
109    ThreeDsServerCached,
110    #[serde(rename = "02")]
111    Ds,
112    #[serde(rename = "03")]
113    Acs,
114}