use std::net::SocketAddr;
use reqwest::Client;
#[cfg(not(target_arch = "wasm32"))]
use reqwest::Proxy;
use serde::{Deserialize, Serialize};
use crate::error::Error;
use crate::lud06::LnUrl;
use crate::lud16::LightningAddress;
#[derive(Serialize, Deserialize)]
enum TagRequest {
#[serde(rename = "payRequest")]
Pay,
#[serde(rename = "withdrawRequest")]
Withdraw,
#[serde(rename = "channelRequest")]
Channel,
}
#[derive(Serialize, Deserialize)]
struct PayResponse {
pub callback: String,
#[serde(rename = "maxSendable")]
pub max_sendable: u64,
#[serde(rename = "minSendable")]
pub min_sendable: u64,
pub tag: TagRequest,
pub metadata: String,
#[serde(rename = "allowsNostr")]
pub allows_nostr: Option<bool>,
}
#[derive(Serialize, Deserialize)]
struct LnURLPayInvoice {
pr: Option<String>,
status: Option<String>,
reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Lud06OrLud16 {
Lud06(LnUrl),
Lud16(LightningAddress),
}
impl Lud06OrLud16 {
pub fn endpoint(&self) -> String {
match self {
Self::Lud06(v) => v.endpoint(),
Self::Lud16(v) => v.endpoint(),
}
}
}
impl From<LnUrl> for Lud06OrLud16 {
fn from(value: LnUrl) -> Self {
Self::Lud06(value)
}
}
impl From<LightningAddress> for Lud06OrLud16 {
fn from(value: LightningAddress) -> Self {
Self::Lud16(value)
}
}
pub async fn get_invoice<S>(
lud: S,
msats: u64,
comment: Option<String>,
zap_request: Option<String>,
_proxy: Option<SocketAddr>,
) -> Result<String, Error>
where
S: Into<Lud06OrLud16>,
{
if msats == 0 {
return Err(Error::AmountTooLow { msats, min: 1 });
}
#[cfg(not(target_arch = "wasm32"))]
let client: Client = {
let mut builder = Client::builder();
if let Some(proxy) = _proxy {
let proxy = format!("socks5h://{proxy}");
builder = builder.proxy(Proxy::all(proxy)?);
}
builder.build()?
};
#[cfg(target_arch = "wasm32")]
let client: Client = Client::new();
let lud: Lud06OrLud16 = lud.into();
let endpoint: String = lud.endpoint();
let resp = client.get(endpoint).send().await?;
let pay_response: PayResponse = resp.error_for_status()?.json().await?;
if msats < pay_response.min_sendable {
return Err(Error::AmountTooLow {
msats,
min: pay_response.min_sendable,
});
}
if msats > pay_response.max_sendable {
return Err(Error::AmountTooHigh {
msats,
max: pay_response.max_sendable,
});
}
let symbol: &str = if pay_response.callback.contains('?') {
"&"
} else {
"?"
};
let url = match zap_request {
Some(zap_request) => format!(
"{}{}amount={}&nostr={}",
pay_response.callback, symbol, msats, zap_request
),
None => format!("{}{}amount={}", pay_response.callback, symbol, msats),
};
let url = match comment {
Some(comment) => {
format!("{url}&comment={comment}")
}
None => url,
};
let resp = client.get(&url).send().await?;
let invoice: LnURLPayInvoice = resp.error_for_status()?.json().await?;
match invoice.pr {
Some(pr) => Ok(pr),
None => Err(Error::CantGetInvoice(invoice.reason)),
}
}