Skip to main content

gl_client/lnurl/withdraw/
mod.rs

1use super::models::WithdrawRequestResponse;
2use anyhow::Result;
3use log::debug;
4use reqwest::Url;
5use serde_json::{to_value, Map, Value};
6
7impl WithdrawRequestResponse {
8    /// Build the callback URL for submitting an invoice to the service.
9    ///
10    /// Appends `k1` and `pr` (the BOLT11 invoice) as query parameters.
11    pub fn build_callback_url(&self, invoice: &str) -> Result<String> {
12        build_withdraw_callback_url(&self.callback, &self.k1, invoice)
13    }
14}
15
16/// Build a withdraw callback URL from its individual components.
17///
18/// Appends `k1` and `pr` (the BOLT11 invoice) as query parameters
19/// to the callback base URL.
20pub fn build_withdraw_callback_url(callback: &str, k1: &str, invoice: &str) -> Result<String> {
21    let mut url = Url::parse(callback)?;
22    url.query_pairs_mut()
23        .append_pair("k1", k1)
24        .append_pair("pr", invoice);
25    Ok(url.to_string())
26}
27
28fn convert_value_field_from_str_to_u64(
29    value: &mut Map<String, Value>,
30    field_name: &str,
31) -> Result<()> {
32    match value.get(field_name) {
33        Some(field_value) => match field_value.as_str() {
34            Some(field_value_str) => {
35                let converted_field_value = field_value_str.parse::<u64>()?;
36                value.insert(
37                    String::from(field_name),
38                    to_value(converted_field_value).unwrap(),
39                );
40                Ok(())
41            }
42            None => Err(anyhow::anyhow!(
43                "Failed to convert {} into a str",
44                field_name
45            )),
46        },
47        None => Err(anyhow::anyhow!("Failed to find {} in map", field_name)),
48    }
49}
50
51pub fn parse_withdraw_request_response_from_url(url: &str) -> Option<WithdrawRequestResponse> {
52    let url = Url::parse(url).unwrap();
53    let query_params: Value = url.query_pairs().clone().collect();
54
55    if let Some(mut query_params) = query_params.as_object().cloned() {
56        if convert_value_field_from_str_to_u64(&mut query_params, "minWithdrawable").is_err() {
57            debug!("minWithdrawable could not be parsed into a number");
58            return None;
59        };
60
61        if convert_value_field_from_str_to_u64(&mut query_params, "maxWithdrawable").is_err() {
62            debug!("maxWithdrawable could not be parsed into a number");
63            return None;
64        };
65
66        match serde_json::from_value(Value::Object(query_params)) {
67            Ok(w) => {
68                return w;
69            }
70            Err(e) => {
71                debug!("{:?}", e);
72                return None;
73            }
74        }
75    }
76
77    None
78}
79
80#[cfg(test)]
81mod test {
82    use super::*;
83
84    #[test]
85    fn test_build_withdraw_request_callback_url() -> Result<()> {
86        let resp = WithdrawRequestResponse {
87            tag: String::from("withdraw"),
88            callback: String::from("https://cipherpunk.com/"),
89            k1: String::from("unique"),
90            default_description: String::from(""),
91            min_withdrawable: 2,
92            max_withdrawable: 300,
93        };
94
95        let url_str = resp.build_callback_url("invoice")?;
96        let url = Url::parse(&url_str)?;
97        let query_pairs = url.query_pairs().collect::<Value>();
98        let query_params: &Map<String, Value> = query_pairs.as_object().unwrap();
99
100        assert_eq!(query_params.get("k1").unwrap().as_str().unwrap(), "unique");
101        assert_eq!(
102            query_params.get("pr").unwrap().as_str().unwrap(),
103            "invoice"
104        );
105
106        Ok(())
107    }
108
109    #[test]
110    fn test_parse_withdraw_request_response_from_url() {
111        let withdraw_request = parse_withdraw_request_response_from_url("https://cipherpunk.com?tag=withdraw&callback=cipherpunk.com&k1=42&minWithdrawable=1&maxWithdrawable=100&defaultDescription=");
112        assert!(withdraw_request.is_some());
113    }
114
115    #[test]
116    fn test_parse_withdraw_request_response_from_url_fails_when_field_is_missing() {
117        let withdraw_request = parse_withdraw_request_response_from_url("https://cipherpunk.com?tag=withdraw&callback=cipherpunk.com&k1=42&minWithdrawable=1&maxWithdrawable=100");
118        assert!(withdraw_request.is_none());
119    }
120
121    #[test]
122    fn test_parse_withdraw_request_response_from_url_fails_when_min_withdrawable_is_wrong_type() {
123        let withdraw_request = parse_withdraw_request_response_from_url("https://cipherpunk.com?tag=withdraw&callback=cipherpunk.com&k1=42&minWithdrawable=one&maxWithdrawable=100&defaultDescription=");
124        assert!(withdraw_request.is_none());
125    }
126}