Skip to content

Commit

Permalink
feat(pay): add support for basic serde of payer data callback
Browse files Browse the repository at this point in the history
  • Loading branch information
lsunsi committed Dec 18, 2023
1 parent 2598a98 commit df5fa64
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ impl Pay<'_> {
amount: &crate::pay::Amount,
comment: Option<&str>,
convert: Option<&str>,
payer: Option<crate::pay::PayerInformations>,
) -> Result<crate::pay::client::CallbackResponse, &'static str> {
let callback = self.core.invoice(amount, comment, convert);
let callback = self.core.invoice(amount, comment, convert, payer);

let response = self
.client
Expand Down
42 changes: 41 additions & 1 deletion src/core/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct Currency {
}

#[derive(Clone, Debug)]
pub struct Payer {
pub struct PayerRequirements {
pub name: Option<PayerRequirement>,
pub pubkey: Option<PayerRequirement>,
pub identifier: Option<PayerRequirement>,
Expand All @@ -39,6 +39,22 @@ pub struct PayerRequirementAuth {
pub k1: [u8; 32],
}

#[derive(Clone, Debug)]
pub struct PayerInformations {
pub name: Option<String>,
pub pubkey: Option<Vec<u8>>,
pub identifier: Option<String>,
pub email: Option<String>,
pub auth: Option<PayerInformationAuth>,
}

#[derive(Clone, Debug)]
pub struct PayerInformationAuth {
pub key: Vec<u8>,
pub k1: [u8; 32],
pub sig: [u8; 64],
}

mod serde {
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -82,6 +98,30 @@ mod serde {
pub k1: [u8; 32],
}

#[derive(Deserialize, Serialize)]
pub struct PayerInformations<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pubkey: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identifier: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<PayerInformationAuth>,
}

#[derive(Deserialize, Serialize)]
pub struct PayerInformationAuth {
#[serde(with = "hex::serde")]
pub key: Vec<u8>,
#[serde(with = "hex::serde")]
pub k1: [u8; 32],
#[serde(with = "hex::serde")]
pub sig: [u8; 64],
}

pub(super) mod amount {
use serde::{Deserialize, Deserializer, Serialize, Serializer};

Expand Down
97 changes: 93 additions & 4 deletions src/core/pay/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct Entrypoint {
pub min: u64,
pub max: u64,
pub currencies: Option<Vec<super::Currency>>,
pub payer: Option<super::Payer>,
pub payer: Option<super::PayerRequirements>,
}

#[allow(clippy::too_many_lines)]
Expand All @@ -38,7 +38,7 @@ impl TryFrom<&[u8]> for Entrypoint {
.collect()
});

let payer = p.payer_data.map(|p| super::Payer {
let payer = p.payer_data.map(|p| super::PayerRequirements {
name: p.name.map(|p| super::PayerRequirement {
mandatory: p.mandatory,
}),
Expand Down Expand Up @@ -146,12 +146,14 @@ impl Entrypoint {
amount: &'a super::Amount,
comment: Option<&'a str>,
convert: Option<&'a str>,
payer: Option<super::PayerInformations>,
) -> Callback<'a> {
Callback {
url: &self.callback,
amount,
comment,
convert,
payer,
}
}
}
Expand All @@ -161,14 +163,40 @@ pub struct Callback<'a> {
pub comment: Option<&'a str>,
pub amount: &'a super::Amount,
pub convert: Option<&'a str>,
pub payer: Option<super::PayerInformations>,
}

impl std::fmt::Display for Callback<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let pubkey = self
.payer
.as_ref()
.and_then(|p| p.pubkey.as_ref().map(hex::encode));

let payer = self
.payer
.as_ref()
.map(|p| {
serde_json::to_string(&super::serde::PayerInformations {
name: p.name.as_deref(),
pubkey: pubkey.as_deref(),
identifier: p.identifier.as_deref(),
email: p.email.as_deref(),
auth: p.auth.as_ref().map(|p| super::serde::PayerInformationAuth {
key: p.key.clone(),
k1: p.k1,
sig: p.sig,
}),
})
})
.transpose()
.map_err(|_| std::fmt::Error)?;

let query = ser::Callback {
comment: self.comment,
amount: self.amount,
convert: self.convert,
payerdata: payer.as_deref(),
};

let querystr = serde_urlencoded::to_string(query).map_err(|_| std::fmt::Error)?;
Expand Down Expand Up @@ -225,6 +253,7 @@ mod ser {
#[serde(with = "super::super::serde::amount")]
pub amount: &'a super::super::Amount,
pub convert: Option<&'a str>,
pub payerdata: Option<&'a str>,
}
}

Expand Down Expand Up @@ -472,7 +501,7 @@ mod tests {

assert_eq!(
parsed
.invoice(&super::super::Amount::Millisatoshis(314), None, None)
.invoice(&super::super::Amount::Millisatoshis(314), None, None, None)
.to_string(),
"https://yuri/?o=callback&amount=314"
);
Expand All @@ -494,6 +523,7 @@ mod tests {
.invoice(
&super::super::Amount::Millisatoshis(314),
Some("comentario"),
None,
None
)
.to_string(),
Expand All @@ -517,6 +547,7 @@ mod tests {
.invoice(
&super::super::Amount::Currency(String::from("BRL"), 314),
None,
None,
None
)
.to_string(),
Expand All @@ -537,12 +568,70 @@ mod tests {

assert_eq!(
parsed
.invoice(&super::super::Amount::Millisatoshis(314), None, Some("BRL"))
.invoice(
&super::super::Amount::Millisatoshis(314),
None,
Some("BRL"),
None
)
.to_string(),
"https://yuri/?o=callback&amount=314&convert=BRL"
);
}

#[test]
fn callback_render_payer() {
let input = r#"{
"metadata": "[[\"text/plain\", \"boneco do steve magal\"]]",
"callback": "https://yuri?o=callback",
"maxSendable": 315,
"minSendable": 314
}"#;

let parsed: super::Entrypoint = input.as_bytes().try_into().expect("parse");

assert_eq!(
parsed
.invoice(
&super::super::Amount::Millisatoshis(314),
None,
None,
Some(super::super::PayerInformations {
name: None,
pubkey: None,
identifier: None,
email: None,
auth: None,
})
)
.to_string(),
"https://yuri/?o=callback&amount=314&payerdata=%7B%7D"
);

assert_eq!(
parsed
.invoice(
&super::super::Amount::Millisatoshis(314),
None,
None,
Some(super::super::PayerInformations {
name: Some(String::from("robson")),
pubkey: Some(b"publica".to_vec()),
identifier: Some(String::from("rob")),
email: Some(String::from("rob@son")),
auth: Some(super::super::PayerInformationAuth {
key: b"chave".to_vec(),
k1: *b"12332112312313123213123123211322",
sig:
*b"6564565465464564565465464565465464565464565465465465465464654343"
})
})
)
.to_string(),
"https://yuri/?o=callback&amount=314&payerdata=%7B%22name%22%3A%22robson%22%2C%22pubkey%22%3A%227075626c696361%22%2C%22identifier%22%3A%22rob%22%2C%22email%22%3A%22rob%40son%22%2C%22auth%22%3A%7B%22key%22%3A%226368617665%22%2C%22k1%22%3A%223132333332313132333132333133313233323133313233313233323131333232%22%2C%22sig%22%3A%2236353634353635343635343634353634353635343635343634353635343635343634353635343634353635343635343635343635343635343634363534333433%22%7D%7D"
);
}

#[test]
fn callback_response_parse_base() {
let input = r#"{ "pr": "pierre" }"#;
Expand Down
62 changes: 55 additions & 7 deletions src/core/pay/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct Entrypoint {
pub min: u64,
pub max: u64,
pub currencies: Option<Vec<super::Currency>>,
pub payer: Option<super::Payer>,
pub payer: Option<super::PayerRequirements>,
}

impl TryFrom<Entrypoint> for Vec<u8> {
Expand Down Expand Up @@ -104,6 +104,7 @@ pub struct Callback {
pub amount: super::Amount,
pub comment: Option<String>,
pub convert: Option<String>,
pub payer: Option<super::PayerInformations>,
}

impl<'a> TryFrom<&'a str> for Callback {
Expand All @@ -112,10 +113,36 @@ impl<'a> TryFrom<&'a str> for Callback {
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
serde_urlencoded::from_str::<de::Callback>(s)
.map_err(|_| "deserialize failed")
.map(|cb| Callback {
amount: cb.amount,
comment: cb.comment.map(String::from),
convert: cb.convert.map(String::from),
.and_then(|cb| {
Ok(Callback {
amount: cb.amount,
comment: cb.comment.map(String::from),
convert: cb.convert.map(String::from),
payer: cb
.payerdata
.map(|pd| {
serde_json::from_str::<super::serde::PayerInformations>(&pd)
.map_err(|_| "deserialize payer failed")
.and_then(|pi| {
Ok(super::PayerInformations {
name: pi.name.map(String::from),
pubkey: pi
.pubkey
.map(hex::decode)
.transpose()
.map_err(|_| "deserialize pubkey failed")?,
identifier: pi.identifier.map(String::from),
email: pi.email.map(String::from),
auth: pi.auth.map(|pia| super::PayerInformationAuth {
key: pia.key,
k1: pia.k1,
sig: pia.sig,
}),
})
})
})
.transpose()?,
})
})
}
}
Expand Down Expand Up @@ -204,6 +231,7 @@ mod de {
#[serde(with = "super::super::serde::amount")]
pub amount: super::super::Amount,
pub convert: Option<&'a str>,
pub payerdata: Option<String>,
}
}

Expand Down Expand Up @@ -401,7 +429,7 @@ mod tests {
identifier: None,
email: None,
currencies: None,
payer: Some(super::super::Payer {
payer: Some(super::super::PayerRequirements {
name: Some(super::super::PayerRequirement { mandatory: false }),
pubkey: Some(super::super::PayerRequirement { mandatory: true }),
identifier: Some(super::super::PayerRequirement { mandatory: false }),
Expand Down Expand Up @@ -436,7 +464,7 @@ mod tests {
identifier: None,
email: None,
currencies: None,
payer: Some(super::super::Payer {
payer: Some(super::super::PayerRequirements {
name: None,
pubkey: None,
identifier: None,
Expand Down Expand Up @@ -495,6 +523,26 @@ mod tests {
assert_eq!(parsed.convert.unwrap(), "BRL");
}

#[test]
fn callback_parse_payer() {
let input = "amount=314&payerdata=%7B%22name%22%3A%22robson%22%2C%22pubkey%22%3A%227075626c696361%22%2C%22identifier%22%3A%22rob%22%2C%22email%22%3A%22rob%40son%22%2C%22auth%22%3A%7B%22key%22%3A%226368617665%22%2C%22k1%22%3A%223132333332313132333132333133313233323133313233313233323131333232%22%2C%22sig%22%3A%2236353634353635343635343634353634353635343635343634353635343635343634353635343634353635343635343635343635343635343634363534333433%22%7D%7D";
let parsed: super::Callback = input.try_into().expect("parse");
let payer = parsed.payer.unwrap();

assert_eq!(payer.name.unwrap(), "robson");
assert_eq!(payer.pubkey.unwrap(), b"publica");
assert_eq!(payer.identifier.unwrap(), "rob");
assert_eq!(payer.email.unwrap(), "rob@son");

let auth = payer.auth.unwrap();
assert_eq!(auth.key, b"chave");
assert_eq!(auth.k1, *b"12332112312313123213123123211322");
assert_eq!(
auth.sig,
*b"6564565465464564565465464565465464565464565465465465465464654343"
);
}

#[test]
fn callback_response_render_base() {
let input = super::CallbackResponse {
Expand Down
1 change: 1 addition & 0 deletions tests/lud06.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ async fn test() {
&lnurlkit::pay::Amount::Millisatoshis(314),
Some("comment"),
None,
None,
)
.await
.expect("callback");
Expand Down
4 changes: 3 additions & 1 deletion tests/lud09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async fn test() {
};

let invoice = pr
.invoice(&lnurlkit::pay::Amount::Millisatoshis(0), None, None)
.invoice(&lnurlkit::pay::Amount::Millisatoshis(0), None, None, None)
.await
.expect("callback");

Expand All @@ -82,6 +82,7 @@ async fn test() {
&lnurlkit::pay::Amount::Millisatoshis(1),
Some("mensagem"),
None,
None,
)
.await
.expect("callback");
Expand All @@ -97,6 +98,7 @@ async fn test() {
&lnurlkit::pay::Amount::Millisatoshis(2),
Some("descricao"),
None,
None,
)
.await
.expect("callback");
Expand Down
Loading

0 comments on commit df5fa64

Please sign in to comment.