Skip to content

Commit

Permalink
Add fiat topup info persistency (#742)
Browse files Browse the repository at this point in the history
* Add fiat topup info persistency

* Apply code review suggestions
  • Loading branch information
dleutenegger authored Nov 10, 2023
1 parent fededde commit 3ba3cd9
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 4 deletions.
14 changes: 14 additions & 0 deletions examples/node/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ pub(crate) fn poll_for_user_input(node: &LightningNode, log_file_path: &str) {
println!("{}", format!("{message:#}").red());
}
}
"getregisteredtopup" => {
if let Err(message) = get_registered_topup(node) {
println!("{}", format!("{message:#}").red());
}
}
"listoffers" => {
if let Err(message) = list_offers(node) {
println!("{}", format!("{message:#}").red());
Expand Down Expand Up @@ -246,6 +251,7 @@ fn setup_editor(history_path: &Path) -> Editor<CommandHinter, DefaultHistory> {
"registertopup <IBAN> <currency> [email]",
"registertopup ",
));
hints.insert(CommandHint::new("getregisteredtopup", "getregisteredtopup"));
hints.insert(CommandHint::new("listoffers", "listoffers"));

hints.insert(CommandHint::new(
Expand Down Expand Up @@ -293,6 +299,7 @@ fn help() {
println!(" refundfailedswap <swap address> <to address>");
println!();
println!(" registertopup <IBAN> <currency> [email]");
println!(" getregisteredtopup");
println!(" listoffers");
println!();
println!(" listpayments");
Expand Down Expand Up @@ -827,3 +834,10 @@ fn amount_to_string(amount: Amount) -> String {
};
format!("{} SAT ({fiat})", amount.sats)
}

fn get_registered_topup(node: &LightningNode) -> Result<()> {
let topup_info = node.retrieve_latest_fiat_topup_info()?;
println!("{topup_info:?}");

Ok(())
}
101 changes: 100 additions & 1 deletion src/data_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use crate::fund_migration::MigrationStatus;
use crate::migrations::migrate;
use crate::{ExchangeRate, OfferKind, PocketOfferError, TzConfig, UserPreferences};

use crate::fiat_topup::FiatTopupInfo;
use chrono::{DateTime, Utc};
use crow::{PermanentFailureCode, TemporaryFailureCode};
use perro::MapToError;
use rusqlite::Connection;
use rusqlite::Row;
use rusqlite::{params, Connection};
use std::time::SystemTime;

pub(crate) struct LocalPaymentData {
Expand Down Expand Up @@ -252,6 +253,36 @@ impl DataStore {
)
.map_to_permanent_failure("Failed to query funds migration status")
}

pub fn store_fiat_topup_info(&self, fiat_topup_info: FiatTopupInfo) -> Result<()> {
let dt: DateTime<Utc> = SystemTime::now().into();
self.conn
.execute(
"INSERT INTO fiat_topup_info (order_id, created_at, debitor_iban, creditor_reference, creditor_iban, creditor_bank_name,
creditor_bank_street, creditor_bank_postal_code, creditor_bank_town, creditor_bank_country,
creditor_bank_bic, creditor_name, creditor_street, creditor_postal_code, creditor_town,
creditor_country, currency)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17);",
params![fiat_topup_info.order_id, dt, fiat_topup_info.debitor_iban, fiat_topup_info.creditor_reference, fiat_topup_info.creditor_iban, fiat_topup_info.creditor_bank_name, fiat_topup_info.creditor_bank_street, fiat_topup_info.creditor_bank_postal_code, fiat_topup_info.creditor_bank_town, fiat_topup_info.creditor_bank_country, fiat_topup_info.creditor_bank_bic, fiat_topup_info.creditor_name, fiat_topup_info.creditor_street, fiat_topup_info.creditor_postal_code, fiat_topup_info.creditor_town, fiat_topup_info.creditor_country, fiat_topup_info.currency],
)
.map_to_permanent_failure("Failed to store fiat topup info in db")?;
Ok(())
}

pub fn retrieve_latest_fiat_topup_info(&self) -> Result<Option<FiatTopupInfo>> {
let mut statement = self.conn.prepare(
"SELECT order_id, debitor_iban, creditor_reference, creditor_iban, creditor_bank_name, creditor_bank_street, creditor_bank_postal_code, creditor_bank_town, creditor_bank_country, creditor_bank_bic, creditor_name, creditor_street, creditor_postal_code, creditor_town, creditor_country, currency FROM fiat_topup_info ORDER BY created_at DESC LIMIT 1",
).map_to_permanent_failure("Failed to prepare query latest fiat topup info statement")?;

let mut fiat_topup_info_iter = statement
.query_map([], fiat_topup_info_from_row)
.map_to_permanent_failure("Failed to bind parameter to prepared SQL query")?;

match fiat_topup_info_iter.next() {
None => Ok(None),
Some(f) => Ok(f.map_to_permanent_failure("Corrupted db")?),
}
}
}

// Store all provided exchange rates.
Expand Down Expand Up @@ -416,13 +447,35 @@ pub fn to_offer_error(code: Option<String>) -> Option<PocketOfferError> {
})
}

fn fiat_topup_info_from_row(row: &Row) -> rusqlite::Result<Option<FiatTopupInfo>> {
Ok(Some(FiatTopupInfo {
order_id: row.get(0)?,
debitor_iban: row.get(1)?,
creditor_reference: row.get(2)?,
creditor_iban: row.get(3)?,
creditor_bank_name: row.get(4)?,
creditor_bank_street: row.get(5)?,
creditor_bank_postal_code: row.get(6)?,
creditor_bank_town: row.get(7)?,
creditor_bank_country: row.get(8)?,
creditor_bank_bic: row.get(9)?,
creditor_name: row.get(10)?,
creditor_street: row.get(11)?,
creditor_postal_code: row.get(12)?,
creditor_town: row.get(13)?,
creditor_country: row.get(14)?,
currency: row.get(15)?,
}))
}

#[cfg(test)]
mod tests {
use crate::config::TzConfig;
use crate::data_store::DataStore;
use crate::fund_migration::MigrationStatus;
use crate::{ExchangeRate, OfferKind, PocketOfferError, UserPreferences};

use crate::fiat_topup::FiatTopupInfo;
use crow::TopupError::TemporaryFailure;
use crow::{PermanentFailureCode, TemporaryFailureCode};
use std::fs;
Expand Down Expand Up @@ -884,6 +937,52 @@ mod tests {
);
}

#[test]
fn test_fiat_topup_info_persistence() {
let db_name = String::from("fiat_topup_info_persistence");
reset_db(&db_name);
let data_store = DataStore::new(&format!("{TEST_DB_PATH}/{db_name}")).unwrap();

assert_eq!(data_store.retrieve_latest_fiat_topup_info().unwrap(), None);

let mut fiat_topup_info = FiatTopupInfo {
order_id: "961b8ee9-74cc-4844-9fe8-b02ce0702663".to_string(),
debitor_iban: "CH4889144919566329178".to_string(),
creditor_reference: "8584-9931-ABCD".to_string(),
creditor_iban: "DE2089144126222826294".to_string(),
creditor_bank_name: "Example Bank".to_string(),
creditor_bank_street: "Bankingstreet 21".to_string(),
creditor_bank_postal_code: "2121".to_string(),
creditor_bank_town: "Example Town".to_string(),
creditor_bank_country: "CH".to_string(),
creditor_bank_bic: "VA7373".to_string(),
creditor_name: "John Doe".to_string(),
creditor_street: "Doestreet 21".to_string(),
creditor_postal_code: "2112".to_string(),
creditor_town: "Creditor Town".to_string(),
creditor_country: "DE".to_string(),
currency: "EUR".to_string(),
};

data_store
.store_fiat_topup_info(fiat_topup_info.clone())
.unwrap();
assert_eq!(
data_store.retrieve_latest_fiat_topup_info().unwrap(),
Some(fiat_topup_info.clone())
);

fiat_topup_info.order_id = "361dd7f8-c7b7-4871-b906-b67fa3ed9b55".to_string();

data_store
.store_fiat_topup_info(fiat_topup_info.clone())
.unwrap();
assert_eq!(
data_store.retrieve_latest_fiat_topup_info().unwrap(),
Some(fiat_topup_info)
);
}

fn reset_db(db_name: &str) {
let _ = fs::create_dir(TEST_DB_PATH);
let _ = fs::remove_file(format!("{TEST_DB_PATH}/{db_name}"));
Expand Down
4 changes: 3 additions & 1 deletion src/fiat_topup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::sync::Arc;
use std::time::Duration;

/// Information about a fiat top-up registration
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq)]
pub struct FiatTopupInfo {
pub order_id: String,
/// The user should transfer fiat from this IBAN
Expand All @@ -33,6 +33,7 @@ pub struct FiatTopupInfo {
pub creditor_postal_code: String,
pub creditor_town: String,
pub creditor_country: String,
pub currency: String,
}

impl FiatTopupInfo {
Expand All @@ -55,6 +56,7 @@ impl FiatTopupInfo {
creditor_postal_code: create_order_response.payment_method.creditor_postal_code,
creditor_town: create_order_response.payment_method.creditor_town,
creditor_country: create_order_response.payment_method.creditor_country,
currency: create_order_response.payment_method.currency,
}
}
}
Expand Down
16 changes: 14 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,6 @@ impl LightningNode {
Arc::clone(&auth),
));

let fiat_topup_client =
PocketClient::new(environment.pocket_url, Arc::clone(&sdk), rt.handle())?;
let offer_manager = OfferManager::new(environment.backend_url.clone(), Arc::clone(&auth));

let db_path = format!("{}/db2.db3", config.local_persistence_path);
Expand All @@ -480,6 +478,9 @@ impl LightningNode {

let data_store = Arc::new(Mutex::new(DataStore::new(&db_path)?));

let fiat_topup_client =
PocketClient::new(environment.pocket_url, Arc::clone(&sdk), rt.handle())?;

let task_manager = Arc::new(Mutex::new(TaskManager::new(
rt.handle(),
exchange_rate_provider,
Expand Down Expand Up @@ -1177,6 +1178,10 @@ impl LightningNode {
.fiat_topup_client
.register_pocket_fiat_topup(&user_iban, user_currency)?;

self.data_store
.lock_unwrap()
.store_fiat_topup_info(topup_info.clone())?;

self.offer_manager
.register_topup(topup_info.order_id.clone(), email)
.map_runtime_error_to(RuntimeErrorCode::OfferServiceUnavailable)?;
Expand Down Expand Up @@ -1456,6 +1461,13 @@ impl LightningNode {

Ok(())
}

/// Returns the latest [`FiatTopupInfo`] if the user has registered for the fiat topup.
pub fn retrieve_latest_fiat_topup_info(&self) -> Result<Option<FiatTopupInfo>> {
self.data_store
.lock_unwrap()
.retrieve_latest_fiat_topup_info()
}
}

fn to_offer(topup_info: TopupInfo, current_rate: &Option<ExchangeRate>) -> OfferInfo {
Expand Down
4 changes: 4 additions & 0 deletions src/lipalightninglib.udl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ interface LightningNode {

[Throws=LnError]
void log_debug_info();

[Throws=LnError]
FiatTopupInfo? retrieve_latest_fiat_topup_info();
};

dictionary Config {
Expand Down Expand Up @@ -282,6 +285,7 @@ dictionary FiatTopupInfo {
string creditor_postal_code;
string creditor_town;
string creditor_country;
string currency;
};

dictionary OfferInfo {
Expand Down
23 changes: 23 additions & 0 deletions src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ const MIGRATION_04_CREATED_INVOICES: &str = "
);
";

const MIGRATION_05_FIAT_TOPUP_INFO: &str = "
CREATE TABLE fiat_topup_info (
order_id TEXT NOT NULL PRIMARY KEY,
created_at INTEGER NOT NULL,
debitor_iban TEXT NOT NULL,
creditor_reference TEXT NOT NULL,
creditor_iban TEXT NOT NULL,
creditor_bank_name TEXT NOT NULL,
creditor_bank_street TEXT NOT NULL,
creditor_bank_postal_code TEXT NOT NULL,
creditor_bank_town TEXT NOT NULL,
creditor_bank_country TEXT NOT NULL,
creditor_bank_bic TEXT NOT NULL,
creditor_name TEXT NOT NULL,
creditor_street TEXT NOT NULL,
creditor_postal_code TEXT NOT NULL,
creditor_town TEXT NOT NULL,
creditor_country TEXT NOT NULL,
currency TEXT NOT NULL
);
";

pub(crate) fn migrate(conn: &mut Connection) -> Result<()> {
migrations()
.to_latest(conn)
Expand All @@ -74,6 +96,7 @@ fn migrations() -> Migrations<'static> {
M::up(MIGRATION_02_FUNDS_MIGRATION_STATUS),
M::up(MIGRATION_03_OFFER_ERROR_MESSAGE),
M::up(MIGRATION_04_CREATED_INVOICES),
M::up(MIGRATION_05_FIAT_TOPUP_INFO),
])
}

Expand Down

0 comments on commit 3ba3cd9

Please sign in to comment.