|
2 | 2 | use crate::account::{Account, AccountKind}; |
3 | 3 | use crate::{client::transfer::error::TransferError, client::BoursoWebClient, constants::BASE_URL}; |
4 | 4 | use anyhow::{bail, Context, Result}; |
| 5 | +use futures_util::stream::Stream; |
5 | 6 |
|
6 | 7 | mod error; |
7 | 8 |
|
| 9 | +#[derive(Debug, Clone)] |
| 10 | +pub enum TransferProgress { |
| 11 | + Validating, |
| 12 | + InitializingTransfer, |
| 13 | + ExtractingFlowInstance, |
| 14 | + SettingDebitAccount, |
| 15 | + SettingCreditAccount, |
| 16 | + SettingAmount, |
| 17 | + SubmittingStep7, |
| 18 | + SettingReason, |
| 19 | + ConfirmingTransfer, |
| 20 | + Completed, |
| 21 | +} |
| 22 | + |
| 23 | +impl TransferProgress { |
| 24 | + pub fn step_number(&self) -> u8 { |
| 25 | + match self { |
| 26 | + TransferProgress::Validating => 1, |
| 27 | + TransferProgress::InitializingTransfer => 2, |
| 28 | + TransferProgress::ExtractingFlowInstance => 3, |
| 29 | + TransferProgress::SettingDebitAccount => 4, |
| 30 | + TransferProgress::SettingCreditAccount => 5, |
| 31 | + TransferProgress::SettingAmount => 6, |
| 32 | + TransferProgress::SubmittingStep7 => 7, |
| 33 | + TransferProgress::SettingReason => 8, |
| 34 | + TransferProgress::ConfirmingTransfer => 9, |
| 35 | + TransferProgress::Completed => 10, |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + pub fn total_steps() -> u8 { |
| 40 | + 10 |
| 41 | + } |
| 42 | + |
| 43 | + pub fn description(&self) -> &str { |
| 44 | + match self { |
| 45 | + TransferProgress::Validating => "Validating transfer parameters", |
| 46 | + TransferProgress::InitializingTransfer => "Initializing transfer", |
| 47 | + TransferProgress::ExtractingFlowInstance => "Extracting flow instance", |
| 48 | + TransferProgress::SettingDebitAccount => "Setting debit account", |
| 49 | + TransferProgress::SettingCreditAccount => "Setting credit account", |
| 50 | + TransferProgress::SettingAmount => "Setting transfer amount", |
| 51 | + TransferProgress::SubmittingStep7 => "Submitting intermediate step", |
| 52 | + TransferProgress::SettingReason => "Setting transfer reason", |
| 53 | + TransferProgress::ConfirmingTransfer => "Confirming transfer", |
| 54 | + TransferProgress::Completed => "Transfer completed", |
| 55 | + } |
| 56 | + } |
| 57 | +} |
| 58 | + |
8 | 59 | impl BoursoWebClient { |
9 | 60 | /// Initialize the transfer and extract the transfer ID |
10 | 61 | async fn init_transfer(&self, from_account: &str) -> Result<String> { |
@@ -272,86 +323,130 @@ impl BoursoWebClient { |
272 | 323 | } |
273 | 324 |
|
274 | 325 | #[cfg(not(tarpaulin_include))] |
275 | | - pub async fn transfer_funds( |
| 326 | + pub fn transfer_funds_with_progress( |
276 | 327 | &self, |
277 | 328 | amount: f64, |
278 | 329 | from_account: Account, |
279 | 330 | to_account: Account, |
280 | | - reason: Option<&str>, |
281 | | - ) -> Result<()> { |
282 | | - // Minimum amount is 10 EUR |
283 | | - if amount < 10.0 { |
284 | | - bail!(TransferError::AmountTooLow); |
285 | | - } |
286 | | - |
287 | | - log::debug!( |
288 | | - "Initiating transfer of {:.2} EUR from account {} to account {}", |
289 | | - amount, |
290 | | - from_account.id, |
291 | | - to_account.id |
292 | | - ); |
293 | | - |
294 | | - let transfer_from_banking = from_account.kind == AccountKind::Banking; |
295 | | - let from_account_id = &from_account.id; |
296 | | - let to_account_id = &to_account.id; |
297 | | - |
298 | | - // Default reason if none provided, else use provided reason and |
299 | | - // warn if the reason is too long (> 50 characters) |
300 | | - let transfer_reason = if let Some(r) = reason { |
301 | | - if r.len() > 50 { |
302 | | - bail!(TransferError::ReasonIsTooLong); |
| 331 | + reason: Option<String>, |
| 332 | + ) -> impl Stream<Item = Result<TransferProgress>> + '_ { |
| 333 | + async_stream::stream! { |
| 334 | + // Validation |
| 335 | + yield Ok(TransferProgress::Validating); |
| 336 | + |
| 337 | + if amount < 10.0 { |
| 338 | + yield Err(TransferError::AmountTooLow.into()); |
| 339 | + return; |
303 | 340 | } |
304 | | - r.to_string() |
305 | | - } else { |
306 | | - "Virement depuis BoursoBank".to_string() |
307 | | - }; |
308 | 341 |
|
309 | | - // Step 1: Initialize transfer and get transfer ID |
310 | | - let transfer_id = self.init_transfer(from_account_id).await?; |
| 342 | + log::debug!( |
| 343 | + "Initiating transfer of {:.2} EUR from account {} to account {}", |
| 344 | + amount, |
| 345 | + from_account.id, |
| 346 | + to_account.id |
| 347 | + ); |
| 348 | + |
| 349 | + let transfer_from_banking = from_account.kind == AccountKind::Banking; |
| 350 | + let from_account_id = from_account.id.clone(); |
| 351 | + let to_account_id = to_account.id.clone(); |
| 352 | + |
| 353 | + // Default reason if none provided, else use provided reason and |
| 354 | + // warn if the reason is too long (> 50 characters) |
| 355 | + let transfer_reason = if let Some(r) = reason { |
| 356 | + if r.len() > 50 { |
| 357 | + yield Err(TransferError::ReasonIsTooLong.into()); |
| 358 | + return; |
| 359 | + } |
| 360 | + r |
| 361 | + } else { |
| 362 | + "Virement depuis BoursoBank".to_string() |
| 363 | + }; |
| 364 | + |
| 365 | + // Step 1: Initialize transfer and get transfer ID |
| 366 | + yield Ok(TransferProgress::InitializingTransfer); |
| 367 | + let transfer_id = match self.init_transfer(&from_account_id).await { |
| 368 | + Ok(id) => id, |
| 369 | + Err(e) => { |
| 370 | + yield Err(e); |
| 371 | + return; |
| 372 | + } |
| 373 | + }; |
| 374 | + |
| 375 | + // Extract flow instance |
| 376 | + yield Ok(TransferProgress::ExtractingFlowInstance); |
| 377 | + let flow_instance = match self |
| 378 | + .extract_flow_instance(&format!( |
| 379 | + "{}/compte/cav/{}/virements/immediat/nouveau/{}/1", |
| 380 | + BASE_URL, &from_account_id, transfer_id |
| 381 | + )) |
| 382 | + .await { |
| 383 | + Ok(flow) => flow, |
| 384 | + Err(e) => { |
| 385 | + yield Err(e); |
| 386 | + return; |
| 387 | + } |
| 388 | + }; |
| 389 | + |
| 390 | + // Step 2: Set debit account |
| 391 | + yield Ok(TransferProgress::SettingDebitAccount); |
| 392 | + if let Err(e) = self.set_debit_account(&from_account_id, &transfer_id, &flow_instance) |
| 393 | + .await { |
| 394 | + yield Err(e); |
| 395 | + return; |
| 396 | + } |
311 | 397 |
|
312 | | - // Extract flow instance |
313 | | - let flow_instance = self |
314 | | - .extract_flow_instance(&format!( |
315 | | - "{}/compte/cav/{}/virements/immediat/nouveau/{}/1", |
316 | | - BASE_URL, from_account_id, transfer_id |
317 | | - )) |
318 | | - .await?; |
| 398 | + // Step 3: Set credit account |
| 399 | + yield Ok(TransferProgress::SettingCreditAccount); |
| 400 | + if let Err(e) = self.set_credit_account( |
| 401 | + &from_account_id, |
| 402 | + &to_account_id, |
| 403 | + &transfer_id, |
| 404 | + &flow_instance, |
| 405 | + transfer_from_banking, |
| 406 | + ) |
| 407 | + .await { |
| 408 | + yield Err(e); |
| 409 | + return; |
| 410 | + } |
319 | 411 |
|
320 | | - // Step 2: Set debit account |
321 | | - self.set_debit_account(from_account_id, &transfer_id, &flow_instance) |
322 | | - .await?; |
| 412 | + // Step 6: Set amount |
| 413 | + yield Ok(TransferProgress::SettingAmount); |
| 414 | + if let Err(e) = self.set_transfer_amount(&from_account_id, &transfer_id, &flow_instance, amount) |
| 415 | + .await { |
| 416 | + yield Err(e); |
| 417 | + return; |
| 418 | + } |
323 | 419 |
|
324 | | - // Step 3: Set credit account |
325 | | - self.set_credit_account( |
326 | | - from_account_id, |
327 | | - to_account_id, |
328 | | - &transfer_id, |
329 | | - &flow_instance, |
330 | | - transfer_from_banking, |
331 | | - ) |
332 | | - .await?; |
333 | | - |
334 | | - // Step 6: Set amount |
335 | | - self.set_transfer_amount(from_account_id, &transfer_id, &flow_instance, amount) |
336 | | - .await?; |
| 420 | + // Step 7: Submit |
| 421 | + yield Ok(TransferProgress::SubmittingStep7); |
| 422 | + if let Err(e) = self.submit_step_7(&from_account_id, &transfer_id, &flow_instance) |
| 423 | + .await { |
| 424 | + yield Err(e); |
| 425 | + return; |
| 426 | + } |
337 | 427 |
|
338 | | - // Step 7: Submit |
339 | | - self.submit_step_7(from_account_id, &transfer_id, &flow_instance) |
340 | | - .await?; |
| 428 | + // Step 10: Set reason |
| 429 | + yield Ok(TransferProgress::SettingReason); |
| 430 | + if let Err(e) = self.set_transfer_reason( |
| 431 | + &from_account_id, |
| 432 | + &transfer_id, |
| 433 | + &flow_instance, |
| 434 | + &transfer_reason, |
| 435 | + ) |
| 436 | + .await { |
| 437 | + yield Err(e); |
| 438 | + return; |
| 439 | + } |
341 | 440 |
|
342 | | - // Step 10: Set reason |
343 | | - self.set_transfer_reason( |
344 | | - from_account_id, |
345 | | - &transfer_id, |
346 | | - &flow_instance, |
347 | | - &transfer_reason, |
348 | | - ) |
349 | | - .await?; |
350 | | - |
351 | | - // Step 12: Confirm transfer |
352 | | - self.confirm_transfer(from_account_id, &transfer_id, &flow_instance) |
353 | | - .await?; |
| 441 | + // Step 12: Confirm transfer |
| 442 | + yield Ok(TransferProgress::ConfirmingTransfer); |
| 443 | + if let Err(e) = self.confirm_transfer(&from_account_id, &transfer_id, &flow_instance) |
| 444 | + .await { |
| 445 | + yield Err(e); |
| 446 | + return; |
| 447 | + } |
354 | 448 |
|
355 | | - Ok(()) |
| 449 | + yield Ok(TransferProgress::Completed); |
| 450 | + } |
356 | 451 | } |
357 | 452 | } |
0 commit comments