1use 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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", ¶ms);
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}