Skip to main content

concur_api/api/
cards.rs

1//! SAP Concur Cards API v4 and Card Transactions API v4.
2//!
3//! Endpoints: `cards/v4/...`
4
5use crate::client::ConcurClient;
6use crate::error::Result;
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9
10/// Cards API.
11pub struct CardsApi<'a> {
12    client: &'a ConcurClient,
13}
14
15impl<'a> CardsApi<'a> {
16    pub fn new(client: &'a ConcurClient) -> Self {
17        Self { client }
18    }
19
20    /// Create card accounts in bulk.
21    pub async fn create_accounts_bulk(
22        &self,
23        company_id: &str,
24        request: &CreateAccountsRequest,
25    ) -> Result<BulkResponse> {
26        let req = self
27            .client
28            .request(
29                Method::POST,
30                &format!("/cards/v4/companies/{}/accounts/bulk", company_id),
31            )
32            .await?;
33        let req = ConcurClient::with_json(req, request)?;
34        self.client.execute(req).await
35    }
36
37    /// Create card transactions in bulk.
38    pub async fn create_transactions_bulk(
39        &self,
40        company_id: &str,
41        request: &CreateTransactionsRequest,
42    ) -> Result<BulkResponse> {
43        let req = self
44            .client
45            .request(
46                Method::POST,
47                &format!("/cards/v4/companies/{}/transactions/bulk", company_id),
48            )
49            .await?;
50        let req = ConcurClient::with_json(req, request)?;
51        self.client.execute(req).await
52    }
53
54    /// Get bulk request details.
55    pub async fn get_bulk_request(
56        &self,
57        company_id: &str,
58        request_id: &str,
59    ) -> Result<BulkRequestStatus> {
60        self.client
61            .request(
62                Method::GET,
63                &format!(
64                    "/cards/v4/companies/{}/bulkrequests/{}",
65                    company_id, request_id
66                ),
67            )
68            .await?
69            .send()
70            .await?
71            .json()
72            .await
73            .map_err(Into::into)
74    }
75
76    /// Retrieve card transactions for a user.
77    pub async fn get_user_transactions(
78        &self,
79        user_id: &str,
80        query: &TransactionQuery,
81    ) -> Result<TransactionListResponse> {
82        let mut path = format!("/cards/v4/users/{}/transactions", user_id);
83        let mut params: Vec<(String, String)> = Vec::new();
84        if let Some(v) = &query.status {
85            params.push(("status".to_string(), v.clone()));
86        }
87        if let Some(v) = &query.transaction_date_from {
88            params.push(("transactionDateFrom".to_string(), v.clone()));
89        }
90        if let Some(v) = &query.transaction_date_to {
91            params.push(("transactionDateTo".to_string(), v.clone()));
92        }
93        if let Some(v) = query.page_size {
94            params.push(("pageSize".to_string(), v.to_string()));
95        }
96        if let Some(v) = &query.page_token {
97            params.push(("pageToken".to_string(), v.clone()));
98        }
99        if let Some(v) = &query.sort {
100            params.push(("sort".to_string(), v.clone()));
101        }
102        if let Some(v) = &query.order {
103            params.push(("order".to_string(), v.clone()));
104        }
105        if let Some(v) = &query.include_addendum_for {
106            params.push(("includeAddendumFor".to_string(), v.clone()));
107        }
108        if let Some(v) = &query.include_addendum_details_for {
109            params.push(("includeAddendumDetailsFor".to_string(), v.clone()));
110        }
111        if !params.is_empty() {
112            path.push('?');
113            path.push_str(
114                &params
115                    .iter()
116                    .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
117                    .collect::<Vec<_>>()
118                    .join("&"),
119            );
120        }
121        self.client
122            .request(Method::GET, &path)
123            .await?
124            .send()
125            .await?
126            .json()
127            .await
128            .map_err(Into::into)
129    }
130}
131
132#[derive(Debug, Clone, Default)]
133pub struct TransactionQuery {
134    pub status: Option<String>,
135    pub transaction_date_from: Option<String>,
136    pub transaction_date_to: Option<String>,
137    pub page_size: Option<u32>,
138    pub page_token: Option<String>,
139    pub sort: Option<String>,
140    pub order: Option<String>,
141    pub include_addendum_for: Option<String>,
142    pub include_addendum_details_for: Option<String>,
143}
144
145#[derive(Debug, Clone, Default, Serialize, Deserialize)]
146pub struct CreateAccountsRequest {
147    pub accounts: Vec<CardAccount>,
148}
149
150#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct CardAccount {
152    #[serde(rename = "accountType")]
153    pub account_type: String,
154    #[serde(rename = "billedCurrency")]
155    pub billed_currency: String,
156    #[serde(rename = "cardBrand")]
157    pub card_brand: String,
158    #[serde(rename = "cardProductDescription")]
159    pub card_product_description: Option<String>,
160    #[serde(rename = "cardProductType")]
161    pub card_product_type: String,
162    pub cardholder: Cardholder,
163    #[serde(rename = "externalId")]
164    pub external_id: String,
165    #[serde(rename = "lastSegment")]
166    pub last_segment: Option<String>,
167    #[serde(rename = "liabilityType")]
168    pub liability_type: String,
169    #[serde(rename = "nameOnCard")]
170    pub name_on_card: String,
171    pub provider: CardProvider,
172    pub status: String,
173    #[serde(rename = "billingAccount", skip_serializing_if = "Option::is_none")]
174    pub billing_account: Option<serde_json::Value>,
175}
176
177#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct Cardholder {
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub user_id: Option<String>,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub employee_id: Option<String>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub email: Option<String>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub first_name: Option<String>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub last_name: Option<String>,
189}
190
191#[derive(Debug, Clone, Default, Serialize, Deserialize)]
192pub struct CardProvider {
193    #[serde(rename = "countryCode")]
194    pub country_code: String,
195}
196
197#[derive(Debug, Clone, Default, Serialize, Deserialize)]
198pub struct CreateTransactionsRequest {
199    pub transactions: Vec<CardTransaction>,
200}
201
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct CardTransaction {
204    #[serde(rename = "externalId")]
205    pub external_id: String,
206    #[serde(rename = "transactionDate")]
207    pub transaction_date: String,
208    #[serde(rename = "transactionDateTime")]
209    pub transaction_date_time: Option<String>,
210    #[serde(rename = "postedDate")]
211    pub posted_date: String,
212    #[serde(rename = "transactionAmount")]
213    pub transaction_amount: Money,
214    #[serde(rename = "postedAmount")]
215    pub posted_amount: Money,
216    #[serde(rename = "billedAmount")]
217    pub billed_amount: Option<Money>,
218    #[serde(rename = "referenceNumber")]
219    pub reference_number: Option<String>,
220    #[serde(
221        rename = "authorizationExternalId",
222        skip_serializing_if = "Option::is_none"
223    )]
224    pub authorization_external_id: Option<String>,
225    #[serde(rename = "type")]
226    pub type_: String,
227    pub description: Option<String>,
228    pub account: TransactionAccountRef,
229    pub merchant: Option<serde_json::Value>,
230    pub lodging: Option<serde_json::Value>,
231}
232
233#[derive(Debug, Clone, Default, Serialize, Deserialize)]
234pub struct TransactionAccountRef {
235    #[serde(rename = "externalId")]
236    pub external_id: String,
237}
238
239#[derive(Debug, Clone, Default, Serialize, Deserialize)]
240pub struct Money {
241    #[serde(rename = "currencyCode")]
242    pub currency_code: String,
243    pub value: f64,
244}
245
246#[derive(Debug, Clone, Deserialize)]
247pub struct BulkResponse {
248    #[serde(rename = "requestId")]
249    pub request_id: String,
250    pub links: Vec<serde_json::Value>,
251}
252
253#[derive(Debug, Clone, Deserialize)]
254pub struct BulkRequestStatus {
255    #[serde(rename = "requestId")]
256    pub request_id: String,
257    pub status: String,
258    #[serde(rename = "processedCount")]
259    pub processed_count: Option<i32>,
260    #[serde(rename = "totalCount")]
261    pub total_count: Option<i32>,
262    pub errors: Option<Vec<serde_json::Value>>,
263}
264
265#[derive(Debug, Clone, Deserialize)]
266pub struct TransactionListResponse {
267    pub transactions: Vec<serde_json::Value>,
268    pub paging: Option<serde_json::Value>,
269}