1use anyhow::bail;
4use reqwest::StatusCode;
5use serde::{Deserialize, Serialize};
6
7use crate::{Error, Phoenixd};
8
9#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
11#[serde(rename_all = "camelCase")]
12pub struct PayInvoiceRequest {
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub amount_sat: Option<u64>,
16 pub invoice: String,
18}
19
20#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct PayBolt12Request {
24 pub amount_sat: u64,
26 pub offer: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub message: Option<String>,
31}
32
33#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
35#[serde(rename_all = "camelCase")]
36pub struct PayInvoiceResponse {
37 pub recipient_amount_sat: u64,
39 pub routing_fee_sat: u64,
41 pub payment_id: String,
43 pub payment_hash: String,
45 pub payment_preimage: String,
47}
48
49#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct GetOutgoingInvoiceResponse {
53 pub payment_hash: String,
55 pub preimage: String,
57 pub is_paid: bool,
59 pub sent: u64,
61 pub fees: u64,
63 pub invoice: Option<String>,
65 pub completed_at: Option<u64>,
67 pub created_at: u64,
69}
70
71impl Phoenixd {
72 pub async fn pay_bolt11_invoice(
74 &self,
75 invoice: &str,
76 amount_sat: Option<u64>,
77 ) -> anyhow::Result<PayInvoiceResponse> {
78 let url = self.api_url.join("/payinvoice")?;
79
80 let request = PayInvoiceRequest {
81 amount_sat,
82 invoice: invoice.to_string(),
83 };
84
85 let res = self.make_post(url, Some(request)).await?;
86
87 match serde_json::from_value(res.clone()) {
88 Ok(res) => Ok(res),
89 Err(_) => {
90 log::error!("Api error response on payment quote execution");
91 log::error!("{}", res);
92 bail!("Could not execute payment quote")
93 }
94 }
95 }
96
97 pub async fn pay_bolt12_offer(
99 &self,
100 offer: String,
101 amount_sat: u64,
102 message: Option<String>,
103 ) -> anyhow::Result<PayInvoiceResponse> {
104 let url = self.api_url.join("/payoffer")?;
105
106 let request = PayBolt12Request {
107 amount_sat,
108 offer,
109 message,
110 };
111
112 let res = self.make_post(url, Some(request)).await?;
113
114 match serde_json::from_value(res.clone()) {
115 Ok(res) => Ok(res),
116 Err(_) => {
117 log::error!("Api error response on payment quote execution");
118 log::error!("{}", res);
119 bail!("Could not execute payment quote")
120 }
121 }
122 }
123
124 pub async fn get_outgoing_invoice(
126 &self,
127 payment_hash: &str,
128 ) -> Result<GetOutgoingInvoiceResponse, Error> {
129 let url = self
130 .api_url
131 .join(&format!("payments/outgoing/{}", payment_hash))
132 .map_err(|_| Error::InvalidUrl)?;
133
134 let res = match self.make_get(url).await {
135 Ok(res) => res,
136 Err(err) => {
137 if let Error::ReqwestError(err) = &err {
138 if err.status().unwrap_or_default() == StatusCode::NOT_FOUND {
139 return Err(Error::NotFound);
140 }
141 }
142 return Err(err);
143 }
144 };
145
146 match serde_json::from_value(res.clone()) {
147 Ok(res) => Ok(res),
148 Err(err) => {
149 log::error!("Api error response getting payment quote");
150 log::error!("{}", res);
151
152 Err(err.into())
153 }
154 }
155 }
156}