Skip to content

Commit 52d49f1

Browse files
committed
refactor: transfer funds with progress
1 parent 1ddb563 commit 52d49f1

6 files changed

Lines changed: 243 additions & 72 deletions

File tree

Cargo.lock

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ serde = { version = "1.0.189", features = ["derive"] }
1717
serde_json = { version = "1.0.107" }
1818
log = { version = "0.4.20" }
1919
log4rs = { version = "1.3.0" }
20+
futures-util = "0.3.31"
2021

2122
[lints.rust]
2223
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }

src/bourso_api/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ reqwest_cookie_store = "0.8.0"
1717
cookie_store = "0.21.1"
1818
chrono = { version = "0.4.39" }
1919
log = { version = "0.4.20" }
20+
futures-util = "0.3.31"
21+
async-stream = "0.3.6"
2022

2123
[lints.rust]
2224
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }

src/bourso_api/src/client/transfer/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ impl fmt::Display for TransferError {
3535
}
3636
}
3737
}
38+
39+
impl std::error::Error for TransferError {}

src/bourso_api/src/client/transfer/mod.rs

Lines changed: 164 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,60 @@
22
use crate::account::{Account, AccountKind};
33
use crate::{client::transfer::error::TransferError, client::BoursoWebClient, constants::BASE_URL};
44
use anyhow::{bail, Context, Result};
5+
use futures_util::stream::Stream;
56

67
mod error;
78

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+
859
impl BoursoWebClient {
960
/// Initialize the transfer and extract the transfer ID
1061
async fn init_transfer(&self, from_account: &str) -> Result<String> {
@@ -272,86 +323,130 @@ impl BoursoWebClient {
272323
}
273324

274325
#[cfg(not(tarpaulin_include))]
275-
pub async fn transfer_funds(
326+
pub fn transfer_funds_with_progress(
276327
&self,
277328
amount: f64,
278329
from_account: Account,
279330
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;
303340
}
304-
r.to_string()
305-
} else {
306-
"Virement depuis BoursoBank".to_string()
307-
};
308341

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+
}
311397

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+
}
319411

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+
}
323419

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+
}
337427

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+
}
341440

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+
}
354448

355-
Ok(())
449+
yield Ok(TransferProgress::Completed);
450+
}
356451
}
357452
}

0 commit comments

Comments
 (0)