Skip to main content

cdk_payment_processor/proto/
mod.rs

1use std::str::FromStr;
2
3use cdk_common::payment::{
4    CreateIncomingPaymentResponse, MakePaymentResponse as CdkMakePaymentResponse,
5    PaymentIdentifier as CdkPaymentIdentifier, PaymentQuoteResponse as CdkPaymentQuoteResponse,
6    WaitPaymentResponse,
7};
8use cdk_common::{CurrencyUnit, MeltOptions as CdkMeltOptions};
9
10mod client;
11mod server;
12
13pub use client::PaymentProcessorClient;
14pub use server::PaymentProcessorServer;
15
16tonic::include_proto!("cdk_payment_processor");
17
18impl From<CdkPaymentIdentifier> for PaymentIdentifier {
19    fn from(value: CdkPaymentIdentifier) -> Self {
20        match value {
21            CdkPaymentIdentifier::Label(id) => Self {
22                r#type: PaymentIdentifierType::Label.into(),
23                value: Some(payment_identifier::Value::Id(id)),
24            },
25            CdkPaymentIdentifier::OfferId(id) => Self {
26                r#type: PaymentIdentifierType::OfferId.into(),
27                value: Some(payment_identifier::Value::Id(id)),
28            },
29            CdkPaymentIdentifier::PaymentHash(hash) => Self {
30                r#type: PaymentIdentifierType::PaymentHash.into(),
31                value: Some(payment_identifier::Value::Hash(hex::encode(hash))),
32            },
33            CdkPaymentIdentifier::Bolt12PaymentHash(hash) => Self {
34                r#type: PaymentIdentifierType::Bolt12PaymentHash.into(),
35                value: Some(payment_identifier::Value::Hash(hex::encode(hash))),
36            },
37            CdkPaymentIdentifier::CustomId(id) => Self {
38                r#type: PaymentIdentifierType::CustomId.into(),
39                value: Some(payment_identifier::Value::Id(id)),
40            },
41            CdkPaymentIdentifier::PaymentId(hash) => Self {
42                r#type: PaymentIdentifierType::PaymentId.into(),
43                value: Some(payment_identifier::Value::Hash(hex::encode(hash))),
44            },
45        }
46    }
47}
48
49impl TryFrom<PaymentIdentifier> for CdkPaymentIdentifier {
50    type Error = crate::error::Error;
51
52    fn try_from(value: PaymentIdentifier) -> Result<Self, Self::Error> {
53        match (value.r#type(), value.value) {
54            (PaymentIdentifierType::Label, Some(payment_identifier::Value::Id(id))) => {
55                Ok(CdkPaymentIdentifier::Label(id))
56            }
57            (PaymentIdentifierType::OfferId, Some(payment_identifier::Value::Id(id))) => {
58                Ok(CdkPaymentIdentifier::OfferId(id))
59            }
60            (PaymentIdentifierType::PaymentHash, Some(payment_identifier::Value::Hash(hash))) => {
61                let decoded = hex::decode(hash)?;
62                let hash_array: [u8; 32] = decoded
63                    .try_into()
64                    .map_err(|_| crate::error::Error::InvalidHash)?;
65                Ok(CdkPaymentIdentifier::PaymentHash(hash_array))
66            }
67            (
68                PaymentIdentifierType::Bolt12PaymentHash,
69                Some(payment_identifier::Value::Hash(hash)),
70            ) => {
71                let decoded = hex::decode(hash)?;
72                let hash_array: [u8; 32] = decoded
73                    .try_into()
74                    .map_err(|_| crate::error::Error::InvalidHash)?;
75                Ok(CdkPaymentIdentifier::Bolt12PaymentHash(hash_array))
76            }
77            (PaymentIdentifierType::CustomId, Some(payment_identifier::Value::Id(id))) => {
78                Ok(CdkPaymentIdentifier::CustomId(id))
79            }
80            (PaymentIdentifierType::PaymentId, Some(payment_identifier::Value::Hash(hash))) => {
81                let decoded = hex::decode(hash)?;
82                let hash_array: [u8; 32] = decoded
83                    .try_into()
84                    .map_err(|_| crate::error::Error::InvalidHash)?;
85                Ok(CdkPaymentIdentifier::PaymentId(hash_array))
86            }
87            _ => Err(crate::error::Error::InvalidPaymentIdentifier),
88        }
89    }
90}
91
92// Amount<CurrencyUnit> <-> proto AmountMessage conversions
93
94impl From<cdk_common::Amount<CurrencyUnit>> for AmountMessage {
95    fn from(value: cdk_common::Amount<CurrencyUnit>) -> Self {
96        Self {
97            value: value.value(),
98            unit: value.unit().to_string(),
99        }
100    }
101}
102
103impl TryFrom<AmountMessage> for cdk_common::Amount<CurrencyUnit> {
104    type Error = crate::error::Error;
105    fn try_from(value: AmountMessage) -> Result<Self, Self::Error> {
106        let unit = CurrencyUnit::from_str(&value.unit)?;
107        Ok(cdk_common::Amount::new(value.value, unit))
108    }
109}
110
111// Helper trait for converting Option<Amount<CurrencyUnit>> <-> Option<proto::AmountMessage>
112pub(crate) trait IntoProtoAmount {
113    fn into_proto(self) -> Option<AmountMessage>;
114}
115
116impl IntoProtoAmount for Option<cdk_common::Amount<CurrencyUnit>> {
117    fn into_proto(self) -> Option<AmountMessage> {
118        self.map(Into::into)
119    }
120}
121
122pub(crate) trait TryFromProtoAmount {
123    fn try_from_proto(
124        self,
125    ) -> Result<Option<cdk_common::Amount<CurrencyUnit>>, crate::error::Error>;
126}
127
128impl TryFromProtoAmount for Option<AmountMessage> {
129    fn try_from_proto(
130        self,
131    ) -> Result<Option<cdk_common::Amount<CurrencyUnit>>, crate::error::Error> {
132        match self {
133            Some(amount) => Ok(Some(amount.try_into()?)),
134            None => Ok(None),
135        }
136    }
137}
138
139impl TryFrom<MakePaymentResponse> for CdkMakePaymentResponse {
140    type Error = crate::error::Error;
141    fn try_from(value: MakePaymentResponse) -> Result<Self, Self::Error> {
142        // Use direct enum conversion instead of parsing string from as_str_name()
143        // as_str_name() returns "QUOTE_STATE_PAID" but MeltQuoteState::from_str expects "PAID"
144        let status: cdk_common::nuts::MeltQuoteState = value.status().into();
145        let payment_proof = value.payment_proof;
146        let total_spent = value
147            .total_spent
148            .ok_or(crate::error::Error::MissingAmount)?
149            .try_into()?;
150        let payment_identifier = value
151            .payment_identifier
152            .ok_or(crate::error::Error::InvalidPaymentIdentifier)?;
153        Ok(Self {
154            payment_lookup_id: payment_identifier.try_into()?,
155            payment_proof,
156            status,
157            total_spent,
158        })
159    }
160}
161
162impl From<CdkMakePaymentResponse> for MakePaymentResponse {
163    fn from(value: CdkMakePaymentResponse) -> Self {
164        Self {
165            payment_identifier: Some(value.payment_lookup_id.into()),
166            payment_proof: value.payment_proof,
167            status: QuoteState::from(value.status).into(),
168            total_spent: Some(value.total_spent.into()),
169            extra_json: None,
170        }
171    }
172}
173
174impl From<CreateIncomingPaymentResponse> for CreatePaymentResponse {
175    fn from(value: CreateIncomingPaymentResponse) -> Self {
176        Self {
177            request_identifier: Some(value.request_lookup_id.into()),
178            request: value.request,
179            expiry: value.expiry,
180            extra_json: None,
181        }
182    }
183}
184
185impl TryFrom<CreatePaymentResponse> for CreateIncomingPaymentResponse {
186    type Error = crate::error::Error;
187
188    fn try_from(value: CreatePaymentResponse) -> Result<Self, Self::Error> {
189        let request_identifier = value
190            .request_identifier
191            .ok_or(crate::error::Error::InvalidPaymentIdentifier)?;
192        Ok(Self {
193            request_lookup_id: request_identifier.try_into()?,
194            request: value.request,
195            expiry: value.expiry,
196            extra_json: Some(
197                serde_json::from_str(value.extra_json.unwrap_or_default().as_str())
198                    .unwrap_or_default(),
199            ),
200        })
201    }
202}
203impl From<CdkPaymentQuoteResponse> for PaymentQuoteResponse {
204    fn from(value: CdkPaymentQuoteResponse) -> Self {
205        Self {
206            request_identifier: value.request_lookup_id.map(|i| i.into()),
207            amount: Some(value.amount.into()),
208            fee: Some(value.fee.into()),
209            state: QuoteState::from(value.state).into(),
210            extra_json: None,
211        }
212    }
213}
214
215impl TryFrom<PaymentQuoteResponse> for CdkPaymentQuoteResponse {
216    type Error = crate::error::Error;
217    fn try_from(value: PaymentQuoteResponse) -> Result<Self, Self::Error> {
218        let state_val = value.state();
219        let request_identifier = value.request_identifier;
220
221        Ok(Self {
222            request_lookup_id: request_identifier
223                .map(|i| i.try_into().expect("valid request identifier")),
224            amount: value
225                .amount
226                .ok_or(crate::error::Error::MissingAmount)?
227                .try_into()?,
228            fee: value
229                .fee
230                .ok_or(crate::error::Error::MissingAmount)?
231                .try_into()?,
232            state: state_val.into(),
233        })
234    }
235}
236
237impl From<MeltOptions> for CdkMeltOptions {
238    fn from(value: MeltOptions) -> Self {
239        match value.options.expect("option defined") {
240            melt_options::Options::Mpp(mpp) => Self::Mpp {
241                mpp: cashu::nuts::nut15::Mpp {
242                    amount: mpp.amount.into(),
243                },
244            },
245            melt_options::Options::Amountless(amountless) => Self::Amountless {
246                amountless: cashu::nuts::nut23::Amountless {
247                    amount_msat: amountless.amount_msat.into(),
248                },
249            },
250        }
251    }
252}
253
254impl From<CdkMeltOptions> for MeltOptions {
255    fn from(value: CdkMeltOptions) -> Self {
256        match value {
257            CdkMeltOptions::Mpp { mpp } => Self {
258                options: Some(melt_options::Options::Mpp(Mpp {
259                    amount: mpp.amount.into(),
260                })),
261            },
262            CdkMeltOptions::Amountless { amountless } => Self {
263                options: Some(melt_options::Options::Amountless(Amountless {
264                    amount_msat: amountless.amount_msat.into(),
265                })),
266            },
267        }
268    }
269}
270
271impl From<QuoteState> for cdk_common::nuts::MeltQuoteState {
272    fn from(value: QuoteState) -> Self {
273        match value {
274            QuoteState::Unpaid => Self::Unpaid,
275            QuoteState::Paid => Self::Paid,
276            QuoteState::Pending => Self::Pending,
277            QuoteState::Unknown => Self::Unknown,
278            QuoteState::Failed => Self::Failed,
279            QuoteState::Issued => Self::Unknown,
280            QuoteState::Unspecified => Self::Unknown,
281        }
282    }
283}
284
285impl From<cdk_common::nuts::MeltQuoteState> for QuoteState {
286    fn from(value: cdk_common::nuts::MeltQuoteState) -> Self {
287        match value {
288            cdk_common::nuts::MeltQuoteState::Unpaid => Self::Unpaid,
289            cdk_common::nuts::MeltQuoteState::Paid => Self::Paid,
290            cdk_common::nuts::MeltQuoteState::Pending => Self::Pending,
291            cdk_common::nuts::MeltQuoteState::Unknown => Self::Unknown,
292            cdk_common::nuts::MeltQuoteState::Failed => Self::Failed,
293        }
294    }
295}
296
297impl From<cdk_common::nuts::MintQuoteState> for QuoteState {
298    fn from(value: cdk_common::nuts::MintQuoteState) -> Self {
299        match value {
300            cdk_common::nuts::MintQuoteState::Unpaid => Self::Unpaid,
301            cdk_common::nuts::MintQuoteState::Paid => Self::Paid,
302            cdk_common::nuts::MintQuoteState::Issued => Self::Issued,
303        }
304    }
305}
306
307impl From<WaitPaymentResponse> for WaitIncomingPaymentResponse {
308    fn from(value: WaitPaymentResponse) -> Self {
309        Self {
310            payment_identifier: Some(value.payment_identifier.into()),
311            payment_amount: Some(value.payment_amount.into()),
312            payment_id: value.payment_id,
313        }
314    }
315}
316
317impl TryFrom<WaitIncomingPaymentResponse> for WaitPaymentResponse {
318    type Error = crate::error::Error;
319
320    fn try_from(value: WaitIncomingPaymentResponse) -> Result<Self, Self::Error> {
321        let payment_identifier = value
322            .payment_identifier
323            .ok_or(crate::error::Error::InvalidPaymentIdentifier)?
324            .try_into()?;
325
326        Ok(Self {
327            payment_identifier,
328            payment_amount: value
329                .payment_amount
330                .ok_or(crate::error::Error::MissingAmount)?
331                .try_into()?,
332            payment_id: value.payment_id,
333        })
334    }
335}