Skip to content

Commit 1db2b08

Browse files
committed
receive payjoin payments
1 parent bbefa73 commit 1db2b08

File tree

7 files changed

+657
-0
lines changed

7 files changed

+657
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ log = { version = "0.4.22", default-features = false, features = ["std"]}
7878
vss-client = { package = "vss-client-ng", version = "0.4" }
7979
prost = { version = "0.11.6", default-features = false}
8080

81+
payjoin = { version = "0.24.0", default-features = false, features = ["v2", "io"] }
82+
8183
[target.'cfg(windows)'.dependencies]
8284
winapi = { version = "0.3", features = ["winbase"] }
8385

src/io/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,7 @@ pub(crate) const BDK_WALLET_INDEXER_KEY: &str = "indexer";
7878
///
7979
/// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice
8080
pub(crate) const STATIC_INVOICE_STORE_PRIMARY_NAMESPACE: &str = "static_invoices";
81+
82+
/// The payjoin sessions will be persisted under this key.
83+
pub(crate) const PAYJOIN_SESSION_STORE_PRIMARY_NAMESPACE: &str = "payjoin_sessions";
84+
pub(crate) const PAYJOIN_SESSION_STORE_SECONDARY_NAMESPACE: &str = "";

src/payment/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod onchain;
1414
mod spontaneous;
1515
pub(crate) mod store;
1616
mod unified_qr;
17+
pub(crate) mod payjoin_payment;
1718

1819
pub use bolt11::Bolt11Payment;
1920
pub use bolt12::Bolt12Payment;

src/payment/payjoin_payment/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
9+
pub(crate) mod payjoin_session;
10+
pub(crate) mod persist;
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
9+
10+
use lightning::ln::channelmanager::PaymentId;
11+
use lightning::ln::msgs::DecodeError;
12+
use lightning::util::ser::{Readable, Writeable};
13+
use lightning::{
14+
_init_and_read_len_prefixed_tlv_fields, impl_writeable_tlv_based,
15+
impl_writeable_tlv_based_enum, write_tlv_fields,
16+
};
17+
18+
use crate::data_store::{StorableObject, StorableObjectUpdate};
19+
20+
/// Represents a payjoin session with persisted events
21+
#[derive(Clone, Debug, PartialEq, Eq)]
22+
pub struct PayjoinSession {
23+
/// Session identifier (uses PaymentId from PaymentDetails)
24+
pub session_id: PaymentId,
25+
26+
/// Direction of the payjoin (Send or Receive)
27+
pub direction: PayjoinDirection,
28+
29+
/// HPKE public key of receiver (only for sender sessions)
30+
pub receiver_pubkey: Option<Vec<u8>>,
31+
32+
/// Serialized session events as JSON strings
33+
pub events: Vec<SerializedSessionEvent>,
34+
35+
/// Current status of the session
36+
pub status: PayjoinStatus,
37+
38+
/// Unix timestamp of session completion (if completed)
39+
pub completed_at: Option<u64>,
40+
41+
/// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated.
42+
pub latest_update_timestamp: u64,
43+
}
44+
45+
impl PayjoinSession {
46+
pub fn new(
47+
session_id: PaymentId, direction: PayjoinDirection, receiver_pubkey: Option<Vec<u8>>,
48+
) -> Self {
49+
let latest_update_timestamp = SystemTime::now()
50+
.duration_since(UNIX_EPOCH)
51+
.unwrap_or(Duration::from_secs(0))
52+
.as_secs();
53+
Self {
54+
session_id,
55+
direction,
56+
receiver_pubkey,
57+
events: Vec::new(),
58+
status: PayjoinStatus::Active,
59+
completed_at: None,
60+
latest_update_timestamp,
61+
}
62+
}
63+
}
64+
65+
impl Writeable for PayjoinSession {
66+
fn write<W: lightning::util::ser::Writer>(
67+
&self, writer: &mut W,
68+
) -> Result<(), lightning::io::Error> {
69+
write_tlv_fields!(writer, {
70+
(0, self.session_id, required),
71+
(2, self.direction, required),
72+
(4, self.receiver_pubkey, option),
73+
(6, self.events, required_vec),
74+
(8, self.status, required),
75+
(10, self.completed_at, required),
76+
(12, self.latest_update_timestamp, required),
77+
});
78+
Ok(())
79+
}
80+
}
81+
82+
impl Readable for PayjoinSession {
83+
fn read<R: lightning::io::Read>(reader: &mut R) -> Result<PayjoinSession, DecodeError> {
84+
let unix_time_secs = SystemTime::now()
85+
.duration_since(UNIX_EPOCH)
86+
.unwrap_or(Duration::from_secs(0))
87+
.as_secs();
88+
_init_and_read_len_prefixed_tlv_fields!(reader, {
89+
(0, session_id, required),
90+
(2, direction, required),
91+
(4, receiver_pubkey, option),
92+
(6, events, required_vec),
93+
(8, status, required),
94+
(10, completed_at, option),
95+
(12, latest_update_timestamp, (default_value, unix_time_secs))
96+
});
97+
98+
let session_id: PaymentId = session_id.0.ok_or(DecodeError::InvalidValue)?;
99+
let direction: PayjoinDirection = direction.0.ok_or(DecodeError::InvalidValue)?;
100+
let status: PayjoinStatus = status.0.ok_or(DecodeError::InvalidValue)?;
101+
let latest_update_timestamp: u64 =
102+
latest_update_timestamp.0.ok_or(DecodeError::InvalidValue)?;
103+
104+
Ok(PayjoinSession {
105+
session_id,
106+
direction,
107+
receiver_pubkey,
108+
events,
109+
status,
110+
completed_at,
111+
latest_update_timestamp,
112+
})
113+
}
114+
}
115+
116+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
117+
pub enum PayjoinDirection {
118+
/// The session is for sending a payment
119+
Send,
120+
/// The session is for receiving a payment
121+
Receive,
122+
}
123+
124+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
125+
pub enum PayjoinStatus {
126+
/// The session is active
127+
Active,
128+
/// The session has completed successfully
129+
Completed,
130+
/// The session has failed
131+
Failed,
132+
}
133+
134+
#[derive(Clone, Debug, PartialEq, Eq)]
135+
pub struct SerializedSessionEvent {
136+
/// JSON representation of the event
137+
pub event_json: String,
138+
/// Unix timestamp of when the event occurred
139+
pub created_at: u64,
140+
}
141+
142+
impl_writeable_tlv_based!(SerializedSessionEvent, {
143+
(0, event_json, required),
144+
(2, created_at, required),
145+
});
146+
147+
impl_writeable_tlv_based_enum!(PayjoinDirection,
148+
(0, Send) => {},
149+
(2, Receive) => {}
150+
);
151+
152+
impl_writeable_tlv_based_enum!(PayjoinStatus,
153+
(0, Active) => {},
154+
(2, Completed) => {},
155+
(4, Failed) => {}
156+
);
157+
158+
/// Represents a payjoin session with persisted events
159+
#[derive(Clone, Debug, PartialEq, Eq)]
160+
pub(crate) struct PayjoinSessionUpdate {
161+
pub session_id: PaymentId,
162+
pub receiver_pubkey: Option<Option<Vec<u8>>>,
163+
pub events: Option<Vec<SerializedSessionEvent>>,
164+
pub status: Option<PayjoinStatus>,
165+
pub completed_at: Option<Option<u64>>,
166+
}
167+
168+
impl PayjoinSessionUpdate {
169+
pub fn new(id: PaymentId) -> Self {
170+
Self {
171+
session_id: id,
172+
receiver_pubkey: None,
173+
events: None,
174+
status: None,
175+
completed_at: None,
176+
}
177+
}
178+
}
179+
180+
impl From<&PayjoinSession> for PayjoinSessionUpdate {
181+
fn from(value: &PayjoinSession) -> Self {
182+
Self {
183+
session_id: value.session_id,
184+
receiver_pubkey: Some(value.receiver_pubkey.clone()),
185+
events: Some(value.events.clone()),
186+
status: Some(value.status),
187+
completed_at: Some(value.completed_at),
188+
}
189+
}
190+
}
191+
192+
impl StorableObject for PayjoinSession {
193+
type Id = PaymentId;
194+
type Update = PayjoinSessionUpdate;
195+
196+
fn id(&self) -> Self::Id {
197+
self.session_id
198+
}
199+
200+
fn update(&mut self, update: &Self::Update) -> bool {
201+
debug_assert_eq!(
202+
self.session_id, update.session_id,
203+
"We should only ever override data for the same id"
204+
);
205+
206+
let mut updated = false;
207+
208+
macro_rules! update_if_necessary {
209+
($val:expr, $update:expr) => {
210+
if $val != $update {
211+
$val = $update;
212+
updated = true;
213+
}
214+
};
215+
}
216+
217+
if let Some(receiver_pubkey_opt) = &update.receiver_pubkey {
218+
update_if_necessary!(self.receiver_pubkey, receiver_pubkey_opt.clone());
219+
}
220+
if let Some(events_opt) = &update.events {
221+
update_if_necessary!(self.events, events_opt.clone());
222+
}
223+
if let Some(status_opt) = update.status {
224+
update_if_necessary!(self.status, status_opt);
225+
}
226+
if let Some(completed_at_opt) = update.completed_at {
227+
update_if_necessary!(self.completed_at, completed_at_opt);
228+
}
229+
230+
if updated {
231+
self.latest_update_timestamp = SystemTime::now()
232+
.duration_since(UNIX_EPOCH)
233+
.unwrap_or(Duration::from_secs(0))
234+
.as_secs();
235+
}
236+
237+
updated
238+
}
239+
240+
fn to_update(&self) -> Self::Update {
241+
self.into()
242+
}
243+
}
244+
245+
impl StorableObjectUpdate<PayjoinSession> for PayjoinSessionUpdate {
246+
fn id(&self) -> <PayjoinSession as StorableObject>::Id {
247+
self.session_id
248+
}
249+
}

0 commit comments

Comments
 (0)