diff --git a/src/client.rs b/src/client.rs index d342dd8..c34d53f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -136,8 +136,9 @@ impl Pay<'_> { amount: &crate::pay::Amount, comment: Option<&str>, convert: Option<&str>, + payer: Option, ) -> Result { - let callback = self.core.invoice(amount, comment, convert); + let callback = self.core.invoice(amount, comment, convert, payer); let response = self .client diff --git a/src/core/pay.rs b/src/core/pay.rs index 68ddf1b..ec45549 100644 --- a/src/core/pay.rs +++ b/src/core/pay.rs @@ -19,7 +19,7 @@ pub struct Currency { } #[derive(Clone, Debug)] -pub struct Payer { +pub struct PayerRequirements { pub name: Option, pub pubkey: Option, pub identifier: Option, @@ -39,6 +39,22 @@ pub struct PayerRequirementAuth { pub k1: [u8; 32], } +#[derive(Clone, Debug)] +pub struct PayerInformations { + pub name: Option, + pub pubkey: Option>, + pub identifier: Option, + pub email: Option, + pub auth: Option, +} + +#[derive(Clone, Debug)] +pub struct PayerInformationAuth { + pub key: Vec, + pub k1: [u8; 32], + pub sig: [u8; 64], +} + mod serde { use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -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, + } + + #[derive(Deserialize, Serialize)] + pub struct PayerInformationAuth { + #[serde(with = "hex::serde")] + pub key: Vec, + #[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}; diff --git a/src/core/pay/client.rs b/src/core/pay/client.rs index af1d01f..5802d14 100644 --- a/src/core/pay/client.rs +++ b/src/core/pay/client.rs @@ -12,7 +12,7 @@ pub struct Entrypoint { pub min: u64, pub max: u64, pub currencies: Option>, - pub payer: Option, + pub payer: Option, } #[allow(clippy::too_many_lines)] @@ -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, }), @@ -146,12 +146,14 @@ impl Entrypoint { amount: &'a super::Amount, comment: Option<&'a str>, convert: Option<&'a str>, + payer: Option, ) -> Callback<'a> { Callback { url: &self.callback, amount, comment, convert, + payer, } } } @@ -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, } 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)?; @@ -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>, } } @@ -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" ); @@ -494,6 +523,7 @@ mod tests { .invoice( &super::super::Amount::Millisatoshis(314), Some("comentario"), + None, None ) .to_string(), @@ -517,6 +547,7 @@ mod tests { .invoice( &super::super::Amount::Currency(String::from("BRL"), 314), None, + None, None ) .to_string(), @@ -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" }"#; diff --git a/src/core/pay/server.rs b/src/core/pay/server.rs index 76f7e57..dc4d976 100644 --- a/src/core/pay/server.rs +++ b/src/core/pay/server.rs @@ -11,7 +11,7 @@ pub struct Entrypoint { pub min: u64, pub max: u64, pub currencies: Option>, - pub payer: Option, + pub payer: Option, } impl TryFrom for Vec { @@ -104,6 +104,7 @@ pub struct Callback { pub amount: super::Amount, pub comment: Option, pub convert: Option, + pub payer: Option, } impl<'a> TryFrom<&'a str> for Callback { @@ -112,10 +113,36 @@ impl<'a> TryFrom<&'a str> for Callback { fn try_from(s: &'a str) -> Result { serde_urlencoded::from_str::(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::(&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()?, + }) }) } } @@ -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, } } @@ -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 }), @@ -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, @@ -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 { diff --git a/tests/lud06.rs b/tests/lud06.rs index 775af10..7b4c664 100644 --- a/tests/lud06.rs +++ b/tests/lud06.rs @@ -71,6 +71,7 @@ async fn test() { &lnurlkit::pay::Amount::Millisatoshis(314), Some("comment"), None, + None, ) .await .expect("callback"); diff --git a/tests/lud09.rs b/tests/lud09.rs index 20f111c..8b56508 100644 --- a/tests/lud09.rs +++ b/tests/lud09.rs @@ -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"); @@ -82,6 +82,7 @@ async fn test() { &lnurlkit::pay::Amount::Millisatoshis(1), Some("mensagem"), None, + None, ) .await .expect("callback"); @@ -97,6 +98,7 @@ async fn test() { &lnurlkit::pay::Amount::Millisatoshis(2), Some("descricao"), None, + None, ) .await .expect("callback"); diff --git a/tests/lud11.rs b/tests/lud11.rs index 34c5822..99e4ef8 100644 --- a/tests/lud11.rs +++ b/tests/lud11.rs @@ -59,14 +59,14 @@ async fn test() { }; let invoice = pr - .invoice(&lnurlkit::pay::Amount::Millisatoshis(314), None, None) + .invoice(&lnurlkit::pay::Amount::Millisatoshis(314), None, None, None) .await .expect("callback"); assert!(invoice.disposable); let invoice = pr - .invoice(&lnurlkit::pay::Amount::Millisatoshis(315), None, None) + .invoice(&lnurlkit::pay::Amount::Millisatoshis(315), None, None, None) .await .expect("callback"); assert!(!invoice.disposable); diff --git a/tests/lud12.rs b/tests/lud12.rs index e6fc54c..b64e780 100644 --- a/tests/lud12.rs +++ b/tests/lud12.rs @@ -61,7 +61,7 @@ async fn test() { assert_eq!(pr.core.comment_size.unwrap(), 140); let invoice = pr - .invoice(&lnurlkit::pay::Amount::Millisatoshis(314), None, None) + .invoice(&lnurlkit::pay::Amount::Millisatoshis(314), None, None, None) .await .expect("callback"); @@ -72,6 +72,7 @@ async fn test() { &lnurlkit::pay::Amount::Millisatoshis(314), Some("comentario"), None, + None, ) .await .expect("callback"); diff --git a/tests/lud21.rs b/tests/lud21.rs index af15e66..db8e6ad 100644 --- a/tests/lud21.rs +++ b/tests/lud21.rs @@ -96,6 +96,7 @@ async fn test() { &lnurlkit::pay::Amount::Currency(String::from("USD"), 314), None, Some("BRL"), + None, ) .await .expect("callback");