Skip to content

Commit

Permalink
Allow personal note for non local payments
Browse files Browse the repository at this point in the history
  • Loading branch information
dleutenegger committed Jan 14, 2025
1 parent cf829e5 commit fb842ab
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 43 deletions.
35 changes: 27 additions & 8 deletions src/activities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,15 @@ impl Activities {
let (exchange_rate, tz_config, personal_note, offer, received_on, received_lnurl_comment) =
match local_payment_data {
Some(data) => (
Some(data.exchange_rate),
data.user_preferences.timezone_config,
data.exchange_rate
.or_else(|| self.support.get_exchange_rate()),
data.user_preferences.map(|u| u.timezone_config).unwrap_or(
self.support
.user_preferences
.lock_unwrap()
.timezone_config
.clone(),
),
data.personal_note,
data.offer,
data.received_on,
Expand Down Expand Up @@ -512,27 +519,39 @@ impl Activities {
.lock_unwrap()
.retrieve_payment_info(&invoice_details.payment_hash)?
.ok_or_permanent_failure("Locally created invoice doesn't have local payment data")?;
let exchange_rate = Some(local_payment_data.exchange_rate);
let invoice_details = InvoiceDetails::from_ln_invoice(invoice, &exchange_rate);
let invoice_details =
InvoiceDetails::from_ln_invoice(invoice, &local_payment_data.exchange_rate);
// For receiving payments, we use the invoice timestamp.
let timezone_config = local_payment_data
.user_preferences
.map(|u| u.timezone_config)
.unwrap_or(
self.support
.user_preferences
.lock_unwrap()
.timezone_config
.clone(),
);
let time = invoice_details
.creation_timestamp
.with_timezone(local_payment_data.user_preferences.timezone_config);
.with_timezone(timezone_config);
let lsp_fees = created_invoice
.channel_opening_fees
.unwrap_or_default()
.as_msats()
.to_amount_up(&exchange_rate);
.to_amount_up(&local_payment_data.exchange_rate);
let requested_amount = invoice_details
.amount
.clone()
.ok_or_permanent_failure("Locally created invoice doesn't include an amount")?
.sats
.as_sats()
.to_amount_down(&exchange_rate);
.to_amount_down(&local_payment_data.exchange_rate);

let amount = requested_amount.clone().sats - lsp_fees.sats;
let amount = amount.as_sats().to_amount_down(&exchange_rate);
let amount = amount
.as_sats()
.to_amount_down(&local_payment_data.exchange_rate);

let personal_note = local_payment_data.personal_note;

Expand Down
136 changes: 101 additions & 35 deletions src/data_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ use crate::{EnableStatus, ExchangeRate, Offer, PocketOfferError, TzConfig, UserP
use chrono::{DateTime, Utc};
use crow::FiatTopupSetupInfo;
use crow::{PermanentFailureCode, TemporaryFailureCode};
use perro::{ensure, invalid_input, MapToError};
use perro::MapToError;
use rusqlite::{backup, params, Connection, OptionalExtension, Params, Row};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use log::debug;

pub(crate) const BACKUP_DB_FILENAME_SUFFIX: &str = ".backup";

#[derive(PartialEq, Debug, Clone)]
pub(crate) struct LocalPaymentData {
pub user_preferences: UserPreferences,
pub exchange_rate: ExchangeRate,
pub user_preferences: Option<UserPreferences>,
pub exchange_rate: Option<ExchangeRate>,
pub offer: Option<Offer>,
pub personal_note: Option<String>,
pub received_on: Option<String>,
Expand Down Expand Up @@ -74,7 +75,7 @@ impl DataStore {
tx.execute(
"\
INSERT INTO payments (hash, timezone_id, timezone_utc_offset_secs, fiat_currency, \
exchange_rates_history_snaphot_id, received_on, received_lnurl_comment)\
exchange_rates_history_snapshot_id, received_on, received_lnurl_comment)\
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)\
",
(
Expand Down Expand Up @@ -141,7 +142,7 @@ impl DataStore {
o.exchange_fee_minor_units, o.exchange_fee_rate_permyriad, o.error, o.topup_value_sats, \
payments.personal_note, payments.received_on, payments.received_lnurl_comment \
FROM payments \
LEFT JOIN exchange_rates_history h on payments.exchange_rates_history_snaphot_id=h.snapshot_id \
LEFT JOIN exchange_rates_history h on payments.exchange_rates_history_snapshot_id=h.snapshot_id \
AND payments.fiat_currency=h.fiat_currency \
LEFT JOIN offers o ON o.payment_hash=payments.hash \
WHERE hash=? \
Expand Down Expand Up @@ -260,10 +261,21 @@ impl DataStore {
params![personal_note, payment_hash],
)
.map_to_permanent_failure("Failed to store personal note in local db")?;
ensure!(
number_of_rows == 1,
invalid_input("Payment not found to set personal note")
);

if number_of_rows == 0 {
debug!("Payment with hash [{}] not found in local db, inserting new row.", payment_hash);
self
.conn
.execute(
"
INSERT INTO payments (personal_note, hash) \
VALUES (?1, ?2);",
params![personal_note, payment_hash],
)
.map_to_permanent_failure("Failed to insert new payment to store personal note in local db")?;

}

Ok(())
}

Expand Down Expand Up @@ -589,36 +601,57 @@ fn offer_from_row(row: &Row) -> rusqlite::Result<Option<Offer>> {
}

fn local_payment_data_from_row(row: &Row) -> rusqlite::Result<LocalPaymentData> {
let timezone_id: String = row.get(0)?;
let timezone_utc_offset_secs: i32 = row.get(1)?;
let fiat_currency: String = row.get(2)?;
let rate: u32 = row.get(3)?;
let updated_at: chrono::DateTime<chrono::Utc> = row.get(4)?;
let user_preferences = user_preferences_from_row(row)?;
let exchange_rate = payment_exchange_rate_from_row(row)?;
let offer = offer_from_row(row)?;
let personal_note = row.get(14)?;
let received_on = row.get(15)?;
let received_lnurl_comment = row.get(16)?;

Ok(LocalPaymentData {
user_preferences: UserPreferences {
fiat_currency: fiat_currency.clone(),
timezone_config: TzConfig {
timezone_id,
timezone_utc_offset_secs,
},
},
exchange_rate: ExchangeRate {
currency_code: fiat_currency,
rate,
updated_at: SystemTime::from(updated_at),
},
user_preferences,
exchange_rate,
offer,
personal_note,
received_on,
received_lnurl_comment,
})
}

fn user_preferences_from_row(row: &Row) -> rusqlite::Result<Option<UserPreferences>> {
let timezone_id: Option<String> = row.get(0)?;
let timezone_utc_offset_secs: Option<i32> = row.get(1)?;
let fiat_currency = row.get(2)?;

match (timezone_id, timezone_utc_offset_secs, fiat_currency) {
(Some(timezone_id), Some(timezone_utc_offset_secs), Some(fiat_currency)) => {
Ok(Some(UserPreferences {
fiat_currency,
timezone_config: TzConfig {
timezone_id,
timezone_utc_offset_secs,
},
}))
}
_ => Ok(None),
}
}

fn payment_exchange_rate_from_row(row: &Row) -> rusqlite::Result<Option<ExchangeRate>> {
let currency_code: Option<String> = row.get(2)?;
let rate: Option<u32> = row.get(3)?;
let updated_at: Option<DateTime<Utc>> = row.get(4)?;

match (currency_code, rate, updated_at) {
(Some(currency_code), Some(rate), Some(updated_at)) => Ok(Some(ExchangeRate {
currency_code,
rate,
updated_at: SystemTime::from(updated_at),
})),
_ => Ok(None),
}
}

pub fn from_offer_error(error: Option<PocketOfferError>) -> Option<String> {
error.map(|e| match e {
PocketOfferError::TemporaryFailure { code } => match code {
Expand Down Expand Up @@ -704,7 +737,7 @@ fn fiat_topup_info_from_row(row: &Row) -> rusqlite::Result<Option<FiatTopupSetup

#[cfg(test)]
mod tests {
use crate::data_store::{CreatedInvoice, DataStore};
use crate::data_store::{CreatedInvoice, DataStore, LocalPaymentData};
use crate::node_config::TzConfig;
use crate::{EnableStatus, ExchangeRate, Offer, PocketOfferError, UserPreferences};

Expand Down Expand Up @@ -831,29 +864,43 @@ mod tests {
let local_payment_data = data_store.retrieve_payment_info("hash").unwrap().unwrap();
assert_eq!(local_payment_data.offer.unwrap(), offer);
assert_eq!(
local_payment_data.user_preferences,
user_preferences.clone()
local_payment_data.user_preferences.as_ref().unwrap(),
&user_preferences.clone()
);
assert_eq!(
local_payment_data.exchange_rate.currency_code,
local_payment_data
.exchange_rate
.as_ref()
.unwrap()
.currency_code,
user_preferences.fiat_currency
);
assert_eq!(local_payment_data.exchange_rate.rate, 4123);
assert_eq!(
local_payment_data.exchange_rate.as_ref().unwrap().rate,
4123
);

let local_payment_data = data_store
.retrieve_payment_info("hash - no offer")
.unwrap()
.unwrap();
assert!(local_payment_data.offer.is_none());
assert_eq!(
local_payment_data.user_preferences,
user_preferences.clone()
local_payment_data.user_preferences.as_ref().unwrap(),
&user_preferences.clone()
);
assert_eq!(
local_payment_data.exchange_rate.currency_code,
local_payment_data
.exchange_rate
.as_ref()
.unwrap()
.currency_code,
user_preferences.fiat_currency
);
assert_eq!(local_payment_data.exchange_rate.rate, 4123);
assert_eq!(
local_payment_data.exchange_rate.as_ref().unwrap().rate,
4123
);

let local_payment_data = data_store
.retrieve_payment_info("hash - no error")
Expand Down Expand Up @@ -894,6 +941,25 @@ mod tests {
local_payment_data_without_note_from_store,
local_payment_data
);

data_store
.update_personal_note("hash - no local data", Some("a note"))
.unwrap();
let local_payment_data_with_note_from_store = data_store.
retrieve_payment_info("hash - no local data")
.unwrap()
.unwrap();
assert_eq!(
local_payment_data_with_note_from_store,
LocalPaymentData {
user_preferences: None,
exchange_rate: None,
offer: None,
personal_note: Some(String::from("a note")),
received_on: None,
received_lnurl_comment: None,
}
);
}
#[test]
fn test_offer_storage() {
Expand Down
23 changes: 23 additions & 0 deletions src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,28 @@ const MIGRATION_18_FIAT_CURRENCY: &str = "
);
";

const MIGRATION_19_PAYMENT_OPTIONAL_FIELDS: &str = "
CREATE TABLE payments_new (
hash TEXT NOT NULL PRIMARY KEY ON CONFLICT REPLACE,
timezone_id TEXT DEFAULT NULL,
timezone_utc_offset_secs INTEGER NULL,
fiat_currency TEXT DEFAULT NULL,
exchange_rates_history_snapshot_id INTEGER NULL,
personal_note TEXT DEFAULT NULL,
received_on TEXT DEFAULT NULL,
received_lnurl_comment TEXT DEFAULT NULL
);
INSERT INTO payments_new (hash, timezone_id, timezone_utc_offset_secs, fiat_currency,
exchange_rates_history_snapshot_id, personal_note, received_on, received_lnurl_comment)
SELECT hash, timezone_id, timezone_utc_offset_secs, fiat_currency, exchange_rates_history_snaphot_id, personal_note, received_on, received_lnurl_comment
FROM payments;
DROP TABLE payments;
ALTER TABLE payments_new RENAME TO payments;
";

pub(crate) fn migrate(conn: &mut Connection) -> Result<()> {
migrations()
.to_latest(conn)
Expand All @@ -188,6 +210,7 @@ fn migrations() -> Migrations<'static> {
M::up(MIGRATION_16_HIDDEN_CHANNEL_CLOSE_AMOUNT),
M::up(MIGRATION_17_HIDDEN_FAILED_SWAPS),
M::up(MIGRATION_18_FIAT_CURRENCY),
M::up(MIGRATION_19_PAYMENT_OPTIONAL_FIELDS),
])
}

Expand Down

0 comments on commit fb842ab

Please sign in to comment.