fns_api_client/client/
mod.rs

1use std::sync::Arc;
2
3use chrono::{NaiveDateTime, ParseError};
4use reqwest::Client;
5use tokio::time::{Duration, sleep};
6use yaserde::ser::to_string;
7use log::debug;
8use serde_json::from_str;
9
10use crate::client::error::{FnsApiError, HttpClientError, OpenApiClientError};
11use crate::dto::ticket_request;
12use crate::dto::ticket_response::Envelope;
13use crate::models::auth_response::{AuthResponse, AuthResponseResult, AuthResponseToken};
14use crate::models::message_response::{MessageResponse, MessageStatus};
15use crate::models::ticket::TicketResponse;
16use crate::traits::check_query::CheckQueryTrait;
17use crate::traits::ticket::{Ticket, TicketResponseResult, TicketResponseTrait, TicketTrait};
18
19pub mod error;
20
21pub struct OpenApiClient {
22    http_client: Client,
23    master_token: String,
24    user_token: String,
25    temp_token: Option<AuthResponseToken>,
26}
27
28impl OpenApiClient {
29    pub fn new(master_token: &String, user_token: &String) -> Self {
30        OpenApiClient {
31            http_client: Client::new(),
32            master_token: master_token.to_string(),
33            user_token: user_token.to_string(),
34            temp_token: None,
35        }
36    }
37    pub async fn authorize(&mut self) -> Result<AuthResponse, OpenApiClientError> {
38        debug!("Авторизация");
39        let model = crate::models::auth_request::AuthRequest::new(&self.master_token);
40        let query = self.http_client
41            .post("https://openapi.nalog.ru:8090/open-api/AuthService/0.1")
42            .body(model.to_string());
43        let auth_request = query
44            .send()
45            .await;
46        match auth_request {
47            Ok(response) => {
48                match response.text().await {
49                    Ok(xml_string) => {
50                        let auth_response = AuthResponse::new(&xml_string);
51                        match auth_response.clone() {
52                            Ok(res) => {
53                                match res.result {
54                                    AuthResponseResult::Ok(token) => {
55                                        self.temp_token = Some(token);
56                                        debug!("Токен получен");
57                                    }
58                                    _ => {}
59                                }
60                            }
61                            _ => {}
62                        };
63                        auth_response
64                    }
65                    Err(error) => {
66                        Err(OpenApiClientError::FnsApiError(FnsApiError { message: error.to_string() }))
67                    }
68                }
69            }
70            Err(error) => {
71                Err(OpenApiClientError::HttpClientError(HttpClientError { message: error.to_string() }))
72            }
73        }
74    }
75    pub async fn get_ticket_with_retry(&self, check_query: impl CheckQueryTrait + Clone) -> Result<Arc<crate::dto::ticket::Ticket>, OpenApiClientError> {
76        let mut attempt = 0;
77        let max_attempts = 5;
78        let base_delay = Duration::from_secs(1);
79
80        while attempt < max_attempts {
81            match self.get_ticket(check_query.clone()).await {
82                Ok(ticket) => {
83                    debug!("Ответ {:?}", ticket);
84                    let status = match ticket.status() {
85                        Some(status) => status,
86                        None => return Err(OpenApiClientError::Error(String::from("Ошибка получения статуса"))),
87                    };
88                    debug!("Статус {:?}", status);
89                    match status {
90                        MessageStatus::Complete => {
91                            let json_string = ticket.ticket_json();
92                            debug!("{}", json_string.clone());
93                            let ticket_json = from_str::<crate::dto::ticket::Ticket>(json_string.as_str()).unwrap();
94                            return Ok(Arc::new(ticket_json))
95                        }
96                        MessageStatus::Processing => {
97                            let delay = base_delay * 2u32.pow(attempt);
98                            println!("Retry attempt {}: waiting for {:?} before next retry", attempt + 1, delay);
99                            sleep(delay).await;
100                            attempt += 1;
101                        }
102                        MessageStatus::Unknown => {
103                            return Err(OpenApiClientError::Error("Неизвестный статус сообщения".to_string()))
104                        }
105                    }
106                },
107                Err(e) => {
108                    return Err(e)
109                }
110            }
111        }
112        Err(OpenApiClientError::Error(String::from("Failed after max retry attempts")))
113    }
114    pub async fn get_ticket(&self, check_query: impl CheckQueryTrait) -> Result<crate::dto::messages_response::Envelope, OpenApiClientError> {
115        debug!("Получение чека");
116        let ticket_response = self.get_ticket_response(check_query).await;
117        let temp_token = match &self.temp_token {
118            Some(token) => token,
119            None => return Err(OpenApiClientError::Error(String::from("Отсутствует FNS-OpenApi-UserToken"))),
120        };
121        match ticket_response {
122            Ok(ticket_response) => {
123                match ticket_response.result() {
124                    TicketResponseResult::Ok(message) => {
125                        debug!("Сообщение {:?}", message);
126                        let response = MessageResponse::new(Arc::new(self.http_client.clone()), Arc::new(self.user_token.clone()), Arc::new(temp_token.clone()))
127                            .send(message).await;
128                        response
129                    }
130                    TicketResponseResult::Err(err) => {
131                        Err(OpenApiClientError::FnsApiError(FnsApiError { message: err.message() }))
132                    }
133                }
134            }
135            Err(error) => {
136                Err(error)
137            }
138        }
139    }
140    async fn get_ticket_response(&self, check_query: impl CheckQueryTrait) -> Result<Arc<dyn TicketResponseTrait>, OpenApiClientError> {
141        match &self.temp_token {
142            None => {
143                Err(OpenApiClientError::Error(String::from("Отсутствует FNS-OpenApi-UserToken")))
144            }
145            Some(temp_token) => {
146                fn convert_datetime(datetime: String) -> Result<String, ParseError> {
147                    if let Ok(parsed_date) = NaiveDateTime::parse_from_str(&datetime, "%Y%m%dT%H%M") {
148                        return Ok(parsed_date.format("%Y-%m-%dT%H:%M:%S").to_string());
149                    }
150                    let parsed_date = NaiveDateTime::parse_from_str(&datetime, "%Y%m%dT%H%M%S")?;
151                    Ok(parsed_date.format("%Y-%m-%dT%H:%M:%S").to_string())
152                }
153
154                let ticket_request = ticket_request::Envelope {
155                    body: ticket_request::Body {
156                        send_message_request: ticket_request::SendMessageRequest {
157                            message: ticket_request::Message {
158                                get_ticket_request: ticket_request::GetTicketRequest {
159                                    get_ticket_info: ticket_request::GetTicketInfo {
160                                        sum: check_query.s(),
161                                        date: convert_datetime(check_query.t()).unwrap(),
162                                        r#fn: check_query.r#fn(),
163                                        type_operation: check_query.n(),
164                                        fiscal_document_id: check_query.i(),
165                                        fiscal_sign: check_query.fp(),
166                                    }
167                                }
168                            }
169                        }
170                    }
171                };
172                let body = to_string(&ticket_request).unwrap();
173                debug!("Отправка запроса на получение чека");
174                println!("{}", body.clone());
175                let ticket_info_query = self.http_client.post("https://openapi.nalog.ru:8090/open-api/ais3/KktService/0.1")
176                    .body(body)
177                    .header("FNS-OpenApi-Token", &temp_token.value)
178                    .header("FNS-OpenApi-UserToken", &self.user_token);
179                let ticket_info_response = ticket_info_query.send()
180                    .await;
181                match ticket_info_response {
182                    Ok(response) => {
183                        match response.text().await {
184                            Ok(text) => {
185                                let deserialize = crate::models::serde::Serde::from_xml::<Envelope>(&text);
186                                match deserialize {
187                                    Ok(dto) => {
188                                        debug!("Ответ получен");
189                                        debug!("{:?}", dto);
190                                        debug!("{}", text);
191                                        TicketResponse::new(dto)
192                                    }
193                                    Err(xml_deserialization_error) => {
194                                        Err(OpenApiClientError::DeserializationError(xml_deserialization_error))
195                                    }
196                                }
197                            }
198                            Err(error) => {
199                                Err(OpenApiClientError::FnsApiError(FnsApiError { message: error.to_string() }))
200                            }
201                        }
202                    }
203                    Err(error) => {
204                        Err(OpenApiClientError::HttpClientError(HttpClientError::new(error)))
205                    }
206                }
207            }
208        }
209    }
210}