Skip to main content

concur_api/api/
receipts.rs

1//! SAP Concur Receipts API v4.
2//!
3//! Endpoints: `receipts/v4/...`
4
5use crate::client::ConcurClient;
6use crate::error::Result;
7use reqwest::Method;
8use serde::Deserialize;
9
10/// Receipts API.
11pub struct ReceiptsApi<'a> {
12    client: &'a ConcurClient,
13}
14
15impl<'a> ReceiptsApi<'a> {
16    pub fn new(client: &'a ConcurClient) -> Self {
17        Self { client }
18    }
19
20    /// Get service index.
21    pub async fn index(&self) -> Result<ReceiptIndex> {
22        self.client
23            .request(Method::GET, "/receipts/")
24            .await?
25            .send()
26            .await?
27            .json()
28            .await
29            .map_err(Into::into)
30    }
31
32    /// Get supported receipt schemas.
33    pub async fn schemas(&self, schema_id: Option<&str>) -> Result<serde_json::Value> {
34        let path = match schema_id {
35            Some(id) => format!("/receipts/schemas/{}", id),
36            None => "/receipts/schemas".to_string(),
37        };
38        self.client
39            .request(Method::GET, &path)
40            .await?
41            .send()
42            .await?
43            .json()
44            .await
45            .map_err(Into::into)
46    }
47
48    /// Get receipt processing status.
49    pub async fn status(&self, receipt_id: &str) -> Result<ReceiptStatus> {
50        self.client
51            .request(Method::GET, &format!("/receipts/v4/status/{}", receipt_id))
52            .await?
53            .send()
54            .await?
55            .json()
56            .await
57            .map_err(Into::into)
58    }
59
60    /// Post a receipt (JSON only).
61    pub async fn post_receipt(
62        &self,
63        user_id: &str,
64        receipt: &serde_json::Value,
65        schema_url: &str,
66    ) -> Result<PostReceiptResponse> {
67        let req = self
68            .client
69            .request(Method::POST, &format!("/receipts/v4/users/{}", user_id))
70            .await?;
71        let req = req
72            .header("Content-Type", "application/json")
73            .header("link", format!("<{}>;rel=describedBy", schema_url))
74            .json(receipt);
75        let response = self.client.execute_with_raw(req).await?;
76        let location = response
77            .headers()
78            .get("location")
79            .and_then(|v| v.to_str().ok())
80            .map(String::from);
81        let link = response
82            .headers()
83            .get("link")
84            .and_then(|v| v.to_str().ok())
85            .map(String::from);
86        Ok(PostReceiptResponse { location, link })
87    }
88
89    /// Get a user's receipts.
90    pub async fn get_user_receipts(&self, user_id: &str) -> Result<Vec<serde_json::Value>> {
91        self.client
92            .request(Method::GET, &format!("/receipts/v4/users/{}", user_id))
93            .await?
94            .send()
95            .await?
96            .json()
97            .await
98            .map_err(Into::into)
99    }
100
101    /// Get a receipt by ID.
102    pub async fn get_receipt(&self, receipt_id: &str) -> Result<serde_json::Value> {
103        self.client
104            .request(Method::GET, &format!("/receipts/v4/{}", receipt_id))
105            .await?
106            .send()
107            .await?
108            .json()
109            .await
110            .map_err(Into::into)
111    }
112
113    /// Get a receipt image.
114    pub async fn get_receipt_image(&self, receipt_id: &str) -> Result<Vec<u8>> {
115        self.client
116            .request(Method::GET, &format!("/receipts/v4/{}/image", receipt_id))
117            .await?
118            .send()
119            .await?
120            .bytes()
121            .await
122            .map(|b| b.to_vec())
123            .map_err(Into::into)
124    }
125}
126
127#[derive(Debug, Clone, Deserialize)]
128pub struct ReceiptIndex {
129    pub links: Vec<ReceiptLink>,
130}
131
132#[derive(Debug, Clone, Deserialize)]
133pub struct ReceiptLink {
134    pub rel: String,
135    pub href: String,
136    pub method: Option<String>,
137}
138
139#[derive(Debug, Clone, Deserialize)]
140pub struct ReceiptStatus {
141    pub status: String,
142    pub logs: Vec<ReceiptLog>,
143}
144
145#[derive(Debug, Clone, Deserialize)]
146pub struct ReceiptLog {
147    #[serde(rename = "logLevel")]
148    pub log_level: String,
149    pub message: String,
150    pub timestamp: String,
151}
152
153#[derive(Debug, Clone, Default)]
154pub struct PostReceiptResponse {
155    pub location: Option<String>,
156    pub link: Option<String>,
157}