1use reqwest::header::HeaderValue;
7use serde_json::Value;
8
9use crate::client::DhanClient;
10use crate::constants::AUTH_BASE_URL;
11use crate::error::{ApiErrorBody, DhanError, Result};
12use crate::types::auth::{AppConsentResponse, PartnerConsentResponse, TokenResponse};
13
14impl DhanClient {
15 pub async fn generate_access_token(
44 client_id: &str,
45 pin: &str,
46 totp: &str,
47 ) -> Result<TokenResponse> {
48 let url = format!(
49 "{}/app/generateAccessToken?dhanClientId={}&pin={}&totp={}",
50 AUTH_BASE_URL, client_id, pin, totp
51 );
52
53 tracing::debug!(%url, "POST generate_access_token");
54
55 let http = reqwest::Client::new();
56 let resp = http.post(&url).send().await?;
57
58 let status = resp.status();
59 let body = resp.text().await.unwrap_or_default();
60
61 if status.is_success() {
62 serde_json::from_str(&body).map_err(DhanError::Json)
63 } else {
64 if let Ok(api_err) = serde_json::from_str::<ApiErrorBody>(&body) {
65 if api_err.error_code.is_some() || api_err.error_message.is_some() {
66 return Err(DhanError::Api(api_err));
67 }
68 }
69 Err(DhanError::HttpStatus { status, body })
70 }
71 }
72
73 pub async fn renew_token(&mut self) -> Result<TokenResponse> {
90 let url = format!("{}/v2/RenewToken", self.base_url());
91
92 tracing::debug!(%url, "GET renew_token");
93
94 let resp = self
95 .http()
96 .get(&url)
97 .header("access-token", self.access_token())
98 .header("dhanClientId", self.client_id())
99 .header("Content-Type", "application/json")
100 .header("Accept", "application/json")
101 .send()
102 .await?;
103
104 let status = resp.status();
105 let bytes = resp.bytes().await.unwrap_or_default();
106
107 if status.is_success() {
108 let token: TokenResponse = serde_json::from_slice(&bytes).map_err(DhanError::Json)?;
109 self.set_access_token(&token.access_token);
111 Ok(token)
112 } else {
113 let body = String::from_utf8_lossy(&bytes);
114 Err(self.parse_error_body(status, &body))
115 }
116 }
117
118 pub async fn generate_consent(
131 client_id: &str,
132 app_id: &str,
133 app_secret: &str,
134 ) -> Result<AppConsentResponse> {
135 let url = format!(
136 "{}/app/generate-consent?client_id={}",
137 AUTH_BASE_URL, client_id
138 );
139
140 tracing::debug!(%url, "POST generate_consent");
141
142 let http = reqwest::Client::new();
143 let resp = http
144 .post(&url)
145 .header(
146 "app_id",
147 HeaderValue::from_str(app_id).map_err(|_| {
148 DhanError::InvalidArgument("app_id contains invalid characters".into())
149 })?,
150 )
151 .header(
152 "app_secret",
153 HeaderValue::from_str(app_secret).map_err(|_| {
154 DhanError::InvalidArgument("app_secret contains invalid characters".into())
155 })?,
156 )
157 .send()
158 .await?;
159
160 let status = resp.status();
161 let body = resp.text().await.unwrap_or_default();
162
163 if status.is_success() {
164 serde_json::from_str(&body).map_err(DhanError::Json)
165 } else {
166 Self::parse_auth_error(status, &body)
167 }
168 }
169
170 pub fn consent_login_url(consent_app_id: &str) -> String {
182 format!(
183 "{}/login/consentApp-login?consentAppId={}",
184 AUTH_BASE_URL, consent_app_id
185 )
186 }
187
188 pub async fn consume_consent(
195 token_id: &str,
196 app_id: &str,
197 app_secret: &str,
198 ) -> Result<TokenResponse> {
199 let url = format!(
200 "{}/app/consumeApp-consent?tokenId={}",
201 AUTH_BASE_URL, token_id
202 );
203
204 tracing::debug!(%url, "POST consume_consent");
205
206 let http = reqwest::Client::new();
207 let resp = http
208 .post(&url)
209 .header(
210 "app_id",
211 HeaderValue::from_str(app_id).map_err(|_| {
212 DhanError::InvalidArgument("app_id contains invalid characters".into())
213 })?,
214 )
215 .header(
216 "app_secret",
217 HeaderValue::from_str(app_secret).map_err(|_| {
218 DhanError::InvalidArgument("app_secret contains invalid characters".into())
219 })?,
220 )
221 .send()
222 .await?;
223
224 let status = resp.status();
225 let body = resp.text().await.unwrap_or_default();
226
227 if status.is_success() {
228 serde_json::from_str(&body).map_err(DhanError::Json)
229 } else {
230 Self::parse_auth_error(status, &body)
231 }
232 }
233
234 pub async fn partner_generate_consent(
242 partner_id: &str,
243 partner_secret: &str,
244 ) -> Result<PartnerConsentResponse> {
245 let url = format!("{}/partner/generate-consent", AUTH_BASE_URL);
246
247 tracing::debug!(%url, "POST partner_generate_consent");
248
249 let http = reqwest::Client::new();
250 let resp = http
251 .post(&url)
252 .header(
253 "partner_id",
254 HeaderValue::from_str(partner_id).map_err(|_| {
255 DhanError::InvalidArgument("partner_id contains invalid characters".into())
256 })?,
257 )
258 .header(
259 "partner_secret",
260 HeaderValue::from_str(partner_secret).map_err(|_| {
261 DhanError::InvalidArgument("partner_secret contains invalid characters".into())
262 })?,
263 )
264 .send()
265 .await?;
266
267 let status = resp.status();
268 let body = resp.text().await.unwrap_or_default();
269
270 if status.is_success() {
271 serde_json::from_str(&body).map_err(DhanError::Json)
272 } else {
273 Self::parse_auth_error(status, &body)
274 }
275 }
276
277 pub fn partner_consent_login_url(consent_id: &str) -> String {
282 format!("{}/consent-login?consentId={}", AUTH_BASE_URL, consent_id)
283 }
284
285 pub async fn partner_consume_consent(
289 token_id: &str,
290 partner_id: &str,
291 partner_secret: &str,
292 ) -> Result<TokenResponse> {
293 let url = format!(
294 "{}/partner/consume-consent?tokenId={}",
295 AUTH_BASE_URL, token_id
296 );
297
298 tracing::debug!(%url, "POST partner_consume_consent");
299
300 let http = reqwest::Client::new();
301 let resp = http
302 .post(&url)
303 .header(
304 "partner_id",
305 HeaderValue::from_str(partner_id).map_err(|_| {
306 DhanError::InvalidArgument("partner_id contains invalid characters".into())
307 })?,
308 )
309 .header(
310 "partner_secret",
311 HeaderValue::from_str(partner_secret).map_err(|_| {
312 DhanError::InvalidArgument("partner_secret contains invalid characters".into())
313 })?,
314 )
315 .send()
316 .await?;
317
318 let status = resp.status();
319 let body = resp.text().await.unwrap_or_default();
320
321 if status.is_success() {
322 serde_json::from_str(&body).map_err(DhanError::Json)
323 } else {
324 Self::parse_auth_error(status, &body)
325 }
326 }
327
328 fn parse_auth_error<T>(status: reqwest::StatusCode, body: &str) -> Result<T> {
334 if let Ok(api_err) = serde_json::from_str::<ApiErrorBody>(body) {
335 if api_err.error_code.is_some() || api_err.error_message.is_some() {
336 return Err(DhanError::Api(api_err));
337 }
338 }
339 if let Ok(val) = serde_json::from_str::<Value>(body) {
341 if let Some(status_str) = val.get("status").and_then(|v| v.as_str()) {
342 return Err(DhanError::HttpStatus {
343 status,
344 body: format!("auth error: {status_str}"),
345 });
346 }
347 }
348 Err(DhanError::HttpStatus {
349 status,
350 body: body.to_owned(),
351 })
352 }
353}