ya_client/payment/
api.rs

1//!  part of the Payment API
2use chrono::{DateTime, TimeZone, Utc};
3use std::borrow::Borrow;
4use std::fmt::Display;
5use std::marker::PhantomData;
6use std::sync::Arc;
7
8use crate::{
9    web::{default_on_timeout, url_format_obj, WebClient, WebInterface},
10    Result,
11};
12use serde::de::DeserializeOwned;
13use serde::{Deserialize, Serialize};
14use std::time::Duration;
15use ya_client_model::payment::payment::Signed;
16use ya_client_model::payment::*;
17
18#[derive(Default, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
19#[serde(default)]
20pub struct ApiConfig {
21    // All timeouts are given in seconds.
22    // None is interpreted by server as default timeout (60 seconds).
23
24    // Requestor
25    pub accept_debit_note_timeout: Option<f64>,
26    pub reject_debit_note_timeout: Option<f64>,
27    pub accept_invoice_timeout: Option<f64>,
28    pub reject_invoice_timeout: Option<f64>,
29
30    // Provider
31    pub send_debit_note_timeout: Option<f64>,
32    pub cancel_debit_note_timeout: Option<f64>,
33    pub send_invoice_timeout: Option<f64>,
34    pub cancel_invoice_timeout: Option<f64>,
35}
36
37impl ApiConfig {
38    pub fn from_env() -> envy::Result<Self> {
39        envy::from_env()
40    }
41}
42
43#[derive(Clone)]
44pub struct PaymentApi {
45    client: WebClient,
46    config: Arc<ApiConfig>,
47}
48
49impl WebInterface for PaymentApi {
50    const API_URL_ENV_VAR: &'static str = crate::payment::PAYMENT_URL_ENV_VAR;
51    const API_SUFFIX: &'static str = ya_client_model::payment::PAYMENT_API_PATH;
52
53    fn from_client(client: WebClient) -> Self {
54        let config = ApiConfig::default();
55        let config = Arc::new(config);
56        Self { client, config }
57    }
58}
59
60impl PaymentApi {
61    pub fn new(client: &WebClient, config: ApiConfig) -> Self {
62        let config = config.into();
63        let client = client.clone();
64        Self { client, config }
65    }
66
67    // accounts
68
69    pub async fn get_requestor_accounts(&self) -> Result<Vec<Account>> {
70        self.client.get("requestorAccounts").send().json().await
71    }
72
73    pub async fn get_provider_accounts(&self) -> Result<Vec<Account>> {
74        self.client.get("providerAccounts").send().json().await
75    }
76
77    // allocations
78
79    pub async fn create_allocation(&self, allocation: &NewAllocation) -> Result<Allocation> {
80        self.client
81            .post("allocations")
82            .send_json(allocation)
83            .json()
84            .await
85    }
86
87    pub async fn get_allocations<Tz>(
88        &self,
89        after_timestamp: Option<DateTime<Tz>>,
90        max_items: Option<u32>,
91    ) -> Result<Vec<Allocation>>
92    where
93        Tz: TimeZone,
94        Tz::Offset: Display,
95    {
96        let input = params::FilterParams {
97            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
98            max_items,
99        };
100        let url = url_format_obj("allocations", &input);
101        self.client.get(&url).send().json().await
102    }
103
104    pub async fn get_allocation(&self, allocation_id: &str) -> Result<Allocation> {
105        let url = url_format!("allocations/{allocation_id}");
106        self.client.get(&url).send().json().await
107    }
108
109    pub async fn amend_allocation(
110        &self,
111        allocation_id: &str,
112        allocation: &AllocationUpdate,
113    ) -> Result<Allocation> {
114        let url = url_format!("allocations/{allocation_id}");
115        self.client.put(&url).send_json(allocation).json().await
116    }
117
118    pub async fn release_allocation(&self, allocation_id: &str) -> Result<()> {
119        let url = url_format!("allocations/{allocation_id}");
120        self.client.delete(&url).send().json().await
121    }
122
123    #[rustfmt::skip]
124    pub async fn get_demand_decorations(
125        &self,
126        allocation_ids: Vec<String>,
127    ) -> Result<MarketDecoration> {
128        // *Not* using url_format_obj because serde_qs doesn't support comma-separated list serialization
129        let allocation_ids = Some(allocation_ids.join(","));
130        let url = url_format!(
131            "demandDecorations",
132            #[query] allocation_ids
133        );
134        self.client.get(&url).send().json().await
135    }
136
137    // debit_notes
138    // Shared
139
140    pub async fn get_debit_notes<Tz>(
141        &self,
142        after_timestamp: Option<DateTime<Tz>>,
143        max_items: Option<u32>,
144    ) -> Result<Vec<DebitNote>>
145    where
146        Tz: TimeZone,
147        Tz::Offset: Display,
148    {
149        let input = params::FilterParams {
150            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
151            max_items,
152        };
153        let url = url_format_obj("debitNotes", &input);
154        self.client.get(&url).send().json().await
155    }
156
157    pub async fn get_debit_note(&self, debit_note_id: &str) -> Result<DebitNote> {
158        let url = url_format!("debitNotes/{debit_note_id}");
159        self.client.get(&url).send().json().await
160    }
161
162    pub async fn get_payments_for_debit_note<Tz>(
163        &self,
164        debit_note_id: &str,
165        after_timestamp: Option<DateTime<Tz>>,
166        max_items: Option<u32>,
167    ) -> Result<Vec<Payment>>
168    where
169        Tz: TimeZone,
170        Tz::Offset: Display,
171    {
172        // NOT IMPLEMENTED ON SERVER
173        let input = params::FilterParams {
174            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
175            max_items,
176        };
177        let base_url = format!("debitNotes/{}/payments", debit_note_id);
178        let url = url_format_obj(&base_url, &input);
179        self.client.get(&url).send().json().await
180    }
181
182    ///
183    /// ## Example
184    ///
185    /// ```rust
186    /// use std::time::Duration;
187    /// use chrono::{DateTime, Utc};
188    /// use ya_client::payment::PaymentApi;
189    /// use ya_client_model::payment::{DebitNoteEvent, DebitNoteEventType, InvoiceEvent};
190    ///
191    /// async fn my_get_events(payment_api : PaymentApi) -> anyhow::Result<Vec<DebitNoteEvent>> {
192    ///     let ts = Utc::now() - chrono::Duration::days(1);
193    ///
194    ///     let events = payment_api.events()
195    ///         .after_timestamp(&ts)
196    ///         .timeout(Duration::from_secs(60))
197    ///         .max_events(100)
198    ///         .provider_events(&[
199    ///             DebitNoteEventType::DebitNoteReceivedEvent,
200    ///             DebitNoteEventType::DebitNoteAcceptedEvent,
201    ///             DebitNoteEventType::DebitNoteSettledEvent])
202    ///         .requestor_events(vec![
203    ///              DebitNoteEventType::DebitNoteReceivedEvent,
204    ///             DebitNoteEventType::DebitNoteAcceptedEvent,
205    ///             DebitNoteEventType::DebitNoteSettledEvent])
206    ///         .get().await?;
207    ///     Ok(events)
208    /// }
209    ///
210    /// async fn my_last_invoice(payment_api : PaymentApi) {
211    ///     if let Some(invoiceEvent)  = payment_api.events::<InvoiceEvent>()
212    ///         .max_events(1)
213    ///         .get().await
214    ///         .unwrap().into_iter().next() {
215    ///         eprintln!("first invoice id: {}", invoiceEvent.invoice_id);
216    ///     }
217    /// }
218    /// ```
219    pub fn events<Evtype: PaymentEvent>(&self) -> EventsBuilder<Evtype> {
220        EventsBuilder::with_client(&self.client)
221    }
222
223    pub async fn get_debit_note_events<Tz>(
224        &self,
225        after_timestamp: Option<&DateTime<Tz>>,
226        timeout: Option<Duration>,
227        max_events: Option<u32>,
228        app_session_id: Option<String>,
229    ) -> Result<Vec<DebitNoteEvent>>
230    where
231        Tz: TimeZone,
232        Tz::Offset: Display,
233    {
234        let input = params::EventParams {
235            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
236            timeout: timeout.map(|d| d.as_secs_f64()),
237            max_events,
238            app_session_id,
239        };
240        let url = url_format_obj("debitNoteEvents", &input);
241        self.client
242            .get(&url)
243            .send()
244            .json()
245            .await
246            .or_else(default_on_timeout)
247    }
248
249    // debit_notes
250    // Provider
251
252    pub async fn issue_debit_note(&self, debit_note: &NewDebitNote) -> Result<DebitNote> {
253        self.client
254            .post("debitNotes")
255            .send_json(debit_note)
256            .json()
257            .await
258    }
259
260    pub async fn send_debit_note(&self, debit_note_id: &str) -> Result<()> {
261        let input = params::Timeout {
262            timeout: self.config.send_debit_note_timeout,
263        };
264        let base_url = format!("debitNotes/{}/send", debit_note_id);
265        let url = url_format_obj(&base_url, &input);
266        self.client.post(&url).send().json().await
267    }
268
269    pub async fn cancel_debit_note(&self, debit_note_id: &str) -> Result<()> {
270        let input = params::Timeout {
271            timeout: self.config.cancel_debit_note_timeout,
272        };
273        let base_url = format!("debitNotes/{}/cancel", debit_note_id);
274        let url = url_format_obj(&base_url, &input);
275        self.client.post(&url).send().json().await
276    }
277
278    // debit_notes
279    // Requestor
280
281    pub async fn accept_debit_note(
282        &self,
283        debit_note_id: &str,
284        acceptance: &Acceptance,
285    ) -> Result<()> {
286        let input = params::Timeout {
287            timeout: self.config.accept_debit_note_timeout,
288        };
289        let base_url = format!("debitNotes/{}/accept", debit_note_id);
290        let url = url_format_obj(&base_url, &input);
291        self.client.post(&url).send_json(acceptance).json().await
292    }
293
294    pub async fn reject_debit_note(
295        &self,
296        debit_note_id: &str,
297        rejection: &Rejection,
298    ) -> Result<()> {
299        let input = params::Timeout {
300            timeout: self.config.reject_debit_note_timeout,
301        };
302        let base_url = format!("debitNotes/{}/reject", debit_note_id);
303        let url = url_format_obj(&base_url, &input);
304        self.client.post(&url).send_json(rejection).json().await
305    }
306
307    // invoices
308    // Shared
309
310    pub async fn get_invoices<Tz>(
311        &self,
312        after_timestamp: Option<DateTime<Tz>>,
313        max_items: Option<u32>,
314    ) -> Result<Vec<Invoice>>
315    where
316        Tz: TimeZone,
317        Tz::Offset: Display,
318    {
319        let input = params::FilterParams {
320            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
321            max_items,
322        };
323        let url = url_format_obj("invoices", &input);
324        self.client.get(&url).send().json().await
325    }
326
327    pub async fn get_invoice(&self, invoice_id: &str) -> Result<Invoice> {
328        let url = url_format!("invoices/{invoice_id}");
329        self.client.get(&url).send().json().await
330    }
331
332    pub async fn get_payments_for_invoice<Tz>(
333        &self,
334        invoice_id: &str,
335        after_timestamp: Option<DateTime<Tz>>,
336        max_items: Option<u32>,
337    ) -> Result<Vec<Payment>>
338    where
339        Tz: TimeZone,
340        Tz::Offset: Display,
341    {
342        // NOT IMPLEMENTED ON SERVER
343        let input = params::FilterParams {
344            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
345            max_items,
346        };
347        let base_url = format!("invoices/{}/payments", invoice_id);
348        let url = url_format_obj(&base_url, &input);
349        self.client.get(&url).send().json().await
350    }
351
352    pub async fn get_invoice_events<Tz>(
353        &self,
354        after_timestamp: Option<&DateTime<Tz>>,
355        timeout: Option<Duration>,
356        max_events: Option<u32>,
357        app_session_id: Option<String>,
358    ) -> Result<Vec<InvoiceEvent>>
359    where
360        Tz: TimeZone,
361        Tz::Offset: Display,
362    {
363        let input = params::EventParams {
364            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
365            timeout: timeout.map(|d| d.as_secs_f64()),
366            max_events,
367            app_session_id,
368        };
369
370        let url = url_format_obj("invoiceEvents", &input);
371        self.client
372            .get(&url)
373            .send()
374            .json()
375            .await
376            .or_else(default_on_timeout)
377    }
378
379    // invoices
380    // Provider
381
382    pub async fn issue_invoice(&self, invoice: &NewInvoice) -> Result<Invoice> {
383        self.client.post("invoices").send_json(invoice).json().await
384    }
385
386    pub async fn send_invoice(&self, invoice_id: &str) -> Result<()> {
387        let input = params::Timeout {
388            timeout: self.config.send_invoice_timeout,
389        };
390        let base_url = format!("invoices/{}/send", invoice_id);
391        let url = url_format_obj(&base_url, &input);
392        self.client.post(&url).send().json().await
393    }
394
395    pub async fn cancel_invoice(&self, invoice_id: &str) -> Result<()> {
396        let input = params::Timeout {
397            timeout: self.config.cancel_invoice_timeout,
398        };
399        let base_url = format!("invoices/{}/cancel", invoice_id);
400        let url = url_format_obj(&base_url, &input);
401        self.client.post(&url).send().json().await
402    }
403
404    // invoices
405    // Requestor
406
407    pub async fn accept_invoice(&self, invoice_id: &str, acceptance: &Acceptance) -> Result<()> {
408        let input = params::Timeout {
409            timeout: self.config.accept_invoice_timeout,
410        };
411        let base_url = format!("invoices/{}/accept", invoice_id);
412        let url = url_format_obj(&base_url, &input);
413        self.client.post(&url).send_json(acceptance).json().await
414    }
415
416    pub async fn reject_invoice(&self, invoice_id: &str, rejection: &Rejection) -> Result<()> {
417        let input = params::Timeout {
418            timeout: self.config.reject_invoice_timeout,
419        };
420        let base_url = format!("invoices/{}/reject", invoice_id);
421        let url = url_format_obj(&base_url, &input);
422        self.client.post(&url).send_json(rejection).json().await
423    }
424
425    // payments
426
427    pub async fn get_payments<Tz>(
428        &self,
429        after_timestamp: Option<&DateTime<Tz>>,
430        timeout: Option<Duration>,
431        max_events: Option<u32>,
432        app_session_id: Option<String>,
433    ) -> Result<Vec<Payment>>
434    where
435        Tz: TimeZone,
436        Tz::Offset: Display,
437    {
438        let input = params::EventParams {
439            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
440            timeout: timeout.map(|d| d.as_secs_f64()),
441            max_events,
442            app_session_id,
443        };
444        let url = url_format_obj("payments", &input);
445        self.client
446            .get(&url)
447            .send()
448            .json()
449            .await
450            .or_else(default_on_timeout)
451    }
452
453    pub async fn get_signed_payments<Tz>(
454        &self,
455        after_timestamp: Option<&DateTime<Tz>>,
456        timeout: Option<Duration>,
457        max_events: Option<u32>,
458        app_session_id: Option<String>,
459    ) -> Result<Vec<Signed<Payment>>>
460    where
461        Tz: TimeZone,
462        Tz::Offset: Display,
463    {
464        let input = params::EventParams {
465            after_timestamp: after_timestamp.map(|dt| dt.with_timezone(&Utc)),
466            timeout: timeout.map(|d| d.as_secs_f64()),
467            max_events,
468            app_session_id,
469        };
470        let url = url_format_obj("payments", &input);
471        self.client
472            .get(&url)
473            .send()
474            .json()
475            .await
476            .or_else(default_on_timeout)
477    }
478
479    pub async fn get_payment(&self, payment_id: &str) -> Result<Payment> {
480        let url = url_format!("payments/{payment_id}");
481        self.client.get(&url).send().json().await
482    }
483
484    pub async fn driver_status(
485        &self,
486        driver: Option<String>,
487        network: Option<String>,
488    ) -> Result<Vec<DriverStatusProperty>> {
489        let params = params::DriverStatusParams { driver, network };
490        let url = url_format_obj("payments/status", &params);
491
492        self.client.get(&url).send().json().await
493    }
494}
495
496pub trait PaymentEvent: DeserializeOwned {
497    const PATH: &'static str;
498    type EventType: ToString;
499}
500
501impl PaymentEvent for DebitNoteEvent {
502    const PATH: &'static str = "debitNoteEvents";
503    type EventType = DebitNoteEventType;
504}
505
506impl PaymentEvent for InvoiceEvent {
507    const PATH: &'static str = "invoiceEvents";
508    type EventType = InvoiceEventType;
509}
510
511pub struct EventsBuilder<'a, Event: PaymentEvent> {
512    event_type: PhantomData<Event>,
513    client: &'a WebClient,
514    after_timestamp: Option<DateTime<Utc>>,
515    timeout: Option<Duration>,
516    max_events: Option<u32>,
517    app_session_id: Option<String>,
518    requestor_events: Option<String>,
519    provider_events: Option<String>,
520}
521
522impl<'a, EvType: PaymentEvent> EventsBuilder<'a, EvType> {
523    fn with_client(client: &'a WebClient) -> Self {
524        let event_type = Default::default();
525        EventsBuilder {
526            event_type,
527            client,
528            after_timestamp: None,
529            timeout: None,
530            max_events: None,
531            app_session_id: None,
532            requestor_events: None,
533            provider_events: None,
534        }
535    }
536
537    pub fn after_timestamp<Tz: TimeZone>(mut self, ts: &DateTime<Tz>) -> Self {
538        self.after_timestamp = Some(ts.with_timezone(&Utc));
539        self
540    }
541
542    pub fn timeout(mut self, timeout: Duration) -> Self {
543        self.timeout = Some(timeout);
544        self
545    }
546
547    pub fn max_events(mut self, max_events: u32) -> Self {
548        self.max_events = Some(max_events);
549        self
550    }
551
552    fn join_events(
553        events: impl IntoIterator<Item = impl Borrow<EvType::EventType>>,
554    ) -> Option<String> {
555        let mut buf = String::new();
556        for ev in events {
557            let ename = ev.borrow().to_string();
558            if !buf.is_empty() {
559                buf.push(',');
560            }
561            buf.push_str(&ename)
562        }
563        if buf.is_empty() {
564            None
565        } else {
566            Some(buf)
567        }
568    }
569
570    pub fn provider_events(
571        mut self,
572        events: impl IntoIterator<Item = impl Borrow<EvType::EventType>>,
573    ) -> Self {
574        self.provider_events = Self::join_events(events);
575        self
576    }
577
578    pub fn requestor_events(
579        mut self,
580        events: impl IntoIterator<Item = impl Borrow<EvType::EventType>>,
581    ) -> Self {
582        self.requestor_events = Self::join_events(events);
583        self
584    }
585
586    pub async fn get(self) -> Result<Vec<EvType>> {
587        let input = params::EventParams {
588            after_timestamp: self.after_timestamp,
589            timeout: self.timeout.map(|d| d.as_secs_f64()),
590            max_events: self.max_events,
591            app_session_id: self.app_session_id,
592        };
593        let url = url_format_obj(EvType::PATH, &input);
594        let mut req = self.client.get(&url);
595        if let Some(requestor_events) = self.requestor_events {
596            req = req.add_header("X-Requestor-Events", requestor_events.as_str())
597        }
598        if let Some(provider_events) = self.provider_events {
599            req = req.add_header("X-Provider-Events", provider_events.as_str())
600        }
601
602        req.send().json().await.or_else(default_on_timeout)
603    }
604}