paypal_rust/client/
request.rs

1use std::ops::AddAssign;
2
3use http_types::url::ParseError;
4use http_types::Url;
5use serde::{Deserialize, Serialize};
6use serde_with::skip_serializing_none;
7
8use crate::client::paypal::USER_AGENT;
9
10/// For most REST GET calls, you can include one or more query parameters on the request URI to filter, limit the size of,
11/// and sort the data in an API response. For filter parameters, see the individual GET calls.
12/// To limit, or page, and sort the data that is returned in some API responses, use these, or similar, query parameters:
13#[skip_serializing_none]
14#[derive(Clone, Debug, Default, Deserialize, Serialize)]
15pub struct QueryParams {
16    /// The number of items to list in the response.
17    pub count: Option<i32>,
18
19    /// The end date and time for the range to show in the response,
20    /// in [Internet date and time format](https://tools.ietf.org/html/rfc3339#section-5.6).
21    /// For example, end_time=2016-03-06T11:00:00Z.
22    pub end_time: Option<i32>,
23
24    /// The page number indicating which set of items will be returned in the response.
25    /// So, the combination of page=1 and page_size=20 returns the first 20 items.
26    /// The combination of page=2 and page_size=20 returns items 21 through 40.
27    pub page: Option<i32>,
28
29    /// The number of items to return in the response.
30    pub page_size: Option<i32>,
31
32    /// Indicates whether to show the total count in the response.
33    pub total_count_required: Option<bool>,
34
35    /// Sorts the payments in the response by a specified value, such as the create time or update time.
36    pub sort_by: Option<String>,
37
38    /// Sorts the items in the response in ascending or descending order.
39    pub sort_order: Option<String>,
40
41    /// The ID of the starting resource in the response. When results are paged, you can use the
42    /// next_id value as the start_id to continue with the next set of results.
43    pub start_id: Option<String>,
44
45    /// The start index of the payments to list. Typically, you use the start_index to jump to a
46    /// specific position in the resource history based on its cart. For example, to start at the
47    /// second item in a list of results, specify ?start_index=2.
48    pub start_index: Option<i32>,
49
50    /// The start date and time for the range to show in the response, in Internet date and time format.
51    /// For example, start_time=2016-03-06T11:00:00Z.
52    pub start_time: Option<String>,
53}
54
55impl QueryParams {
56    #[must_use]
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    #[must_use]
62    pub const fn count(mut self, count: i32) -> Self {
63        self.count = Some(count);
64        self
65    }
66
67    #[must_use]
68    pub const fn end_time(mut self, end_time: i32) -> Self {
69        self.end_time = Some(end_time);
70        self
71    }
72
73    #[must_use]
74    pub const fn page(mut self, page: i32) -> Self {
75        self.page = Some(page);
76        self
77    }
78
79    #[must_use]
80    pub const fn page_size(mut self, page_size: i32) -> Self {
81        self.page_size = Some(page_size);
82        self
83    }
84
85    #[must_use]
86    pub const fn total_count_required(mut self, total_count_required: bool) -> Self {
87        self.total_count_required = Some(total_count_required);
88        self
89    }
90
91    #[must_use]
92    pub fn sort_by(mut self, sort_by: String) -> Self {
93        self.sort_by = Some(sort_by);
94        self
95    }
96
97    #[must_use]
98    pub fn sort_order(mut self, sort_order: String) -> Self {
99        self.sort_order = Some(sort_order);
100        self
101    }
102
103    #[must_use]
104    pub fn start_id(mut self, start_id: String) -> Self {
105        self.start_id = Some(start_id);
106        self
107    }
108
109    #[must_use]
110    pub const fn start_index(mut self, start_index: i32) -> Self {
111        self.start_index = Some(start_index);
112        self
113    }
114
115    #[must_use]
116    pub fn start_time(mut self, start_time: String) -> Self {
117        self.start_time = Some(start_time);
118        self
119    }
120}
121
122/// The commonly used HTTP request headers
123#[derive(Clone, Debug, Deserialize, Serialize)]
124pub struct HttpRequestHeaders {
125    /// The response format, which is required for operations with a response body.
126    #[serde(rename = "Accept")]
127    pub accept: String,
128
129    /// Required to get an access token or make API calls:
130    /// * Get an access token `partial:partials/docs/oauth-credentials.en-XC`
131    ///  `partial:partials/docs/access-tokens.en-XC` `partial:partials/docs/rest/bearer-token.en-XC`
132    /// * Make REST API calls
133    ///
134    /// Include the access token in the Authorization header with the `Bearer` authentication scheme.
135    #[serde(rename = "Authorization")]
136    pub authorization: String,
137
138    /// The request format, which is required for operations with a request body.
139    #[serde(rename = "Content-Type")]
140    pub content_type: String,
141
142    /// An API client-provided JSON Web Token (JWT) assertion that identifies the merchant.
143    ///
144    /// To use this header, you must get consent to act on behalf of a merchant.
145    /// You might want to use a JWT if you act on behalf of multiple merchants at the same time,
146    /// because it is difficult and expensive to generate and manage multiple access tokens.
147    /// Instead of managing multiple access tokens, you can use this header to provide a JWT
148    /// assertion that identifies the merchant when you call the API.
149    ///
150    /// ### Constructing the JWT
151    /// While you can use either a signed or unsigned (unsecured) JWT, PayPal recommends using the
152    /// unsigned JWT for simplicity because the information you pass with the JWT is not sensitive
153    /// data.
154    ///
155    /// A JWT consists of three parts:
156    /// * Header: Identifies the algorithm that generated the signature. Because PayPal recommends
157    ///           an unsigned JWT, pass an alg of none for the header.
158    /// * Payload: Defines a set of claims, or statements, about an entity. In the case of the
159    ///            PayPal-Auth-Assertion header, the entity is typically the merchant.
160    ///            The payload can contain iss, which identifies the third-party calling the API,
161    ///            and one of the following to identify the merchant for whom the call is made:
162    ///            email or payer_id.
163    ///            **Note**: Because a merchant's email address can change, PayPal recommends
164    ///                      using payer_id to specify a merchant.
165    /// * Signature: Validates the token and consists of the encoded header, the encoded payload,
166    ///              a secret, and the algorithm. Because PayPal recommends an unsigned JWT, pass an
167    ///              empty string for the signature.
168    ///             **Note**: If you prefer a signed JWT, you can sign it using your secret from
169    ///                       your API credentials.
170    ///
171    /// # Examples
172    /// JOSE header:
173    /// "alg": "none"
174    /// Payload:
175    /// ```json
176    /// "iss": "client_id",
177    /// "payer_id": "merchant_payer_id"
178    /// ```
179    /// Signature. Use "" for the unsigned case.
180    ///
181    /// To pass the JWT easily in HTTP environments, use Base64 to encode all three parts of the
182    /// JWT separately, then concatenate them with a period (.). The following code shows the
183    /// previous example after Base64 encoding and compact serialization (or concatenation):
184    /// `ew0KICAiYWxnIjogIm5vbmUiDQp9.ew0KICAiaXNzIjogImNsaWVudF9pZCIsDQogICJwYXllcl9pZCI6ICJtZXJjaGFudF9wYXllcl9pZCINCn0=.`
185    ///
186    /// Refer to [Issue a Refund](https://developer.paypal.com/docs/multiparty/issue-refund/)
187    /// in the PayPal Commerce Platform documentation for an example of using the
188    /// `PayPal-Auth-Assertion` header in an API call.
189    #[serde(
190        rename = "PayPal-Auth-Assertion",
191        skip_serializing_if = "Option::is_none"
192    )]
193    pub paypal_auth_assertion: Option<String>,
194
195    /// Verifies that the payment originates from a valid, user-consented device and application.
196    /// Reduces fraud and decreases declines. Transactions that do not include a client metadata ID
197    /// are not eligible for PayPal Seller Protection.
198    #[serde(
199        rename = "PayPal-Client-Metadata-Id",
200        skip_serializing_if = "Option::is_none"
201    )]
202    pub client_client_metadata_id: Option<String>,
203
204    /// Optional. Identifies the caller as a PayPal partner. To receive revenue attribution,
205    /// specify a unique build notation (BN) code. BN codes provide tracking on all transactions
206    /// that originate or are associated with a particular partner. To find your BN code,
207    /// see [Code andCredential Reference](https://developer.paypal.com/docs/multiparty/get-started#code-and-credential-reference).
208    #[serde(
209        rename = "PayPal-Partner-Attribution-Id",
210        skip_serializing_if = "Option::is_none"
211    )]
212    pub paypal_partner_attribution_id: Option<String>,
213
214    /// For example, a user calls refund captured payment with the PayPal-Request-Id header that
215    /// contains a unique user-provided ID. The user can make the call again with the same ID in the
216    /// `PayPal-Request-Id` header for up to 45 days because the server stores this ID for this long
217    /// for this call.
218    ///
219    /// If the initial call fails with the HTTP `500` status code but the server has already
220    /// refunded the payment, the caller does not need to worry that the server will refund the
221    /// payment again.
222    ///
223    /// **Note**: Not all APIs support this header. To determine whether your API supports it and
224    /// for information about how long the server stores the ID, see the reference for your API.
225    #[serde(rename = "PayPal-Request-Id", skip_serializing_if = "Option::is_none")]
226    pub paypal_request_id: Option<String>,
227
228    #[serde(rename = "User-Agent")]
229    pub user_agent: String,
230}
231
232impl Default for HttpRequestHeaders {
233    fn default() -> Self {
234        Self {
235            accept: "application/json".to_string(),
236            authorization: "Bearer ".to_string(),
237            content_type: "application/json".to_string(),
238            user_agent: USER_AGENT.to_string(),
239            paypal_partner_attribution_id: None,
240            client_client_metadata_id: None,
241            paypal_auth_assertion: None,
242            paypal_request_id: None,
243        }
244    }
245}
246
247impl HttpRequestHeaders {
248    pub fn new(authentication: impl Into<String>) -> Self {
249        Self {
250            authorization: authentication.into(),
251            ..Default::default()
252        }
253    }
254
255    pub fn to_vec(&self) -> Vec<(&str, &str)> {
256        let mut headers = Vec::new();
257        headers.push(("Accept", self.accept.as_str()));
258        headers.push(("Content-Type", self.content_type.as_str()));
259        headers.push(("User-Agent", self.user_agent.as_str()));
260        if let Some(paypal_partner_attribution_id) = &self.paypal_partner_attribution_id {
261            headers.push((
262                "PayPal-Partner-Attribution-Id",
263                paypal_partner_attribution_id.as_str(),
264            ));
265        }
266        if let Some(client_client_metadata_id) = &self.client_client_metadata_id {
267            headers.push((
268                "PayPal-Client-Metadata-Id",
269                client_client_metadata_id.as_str(),
270            ));
271        }
272        if let Some(paypal_auth_assertion) = &self.paypal_auth_assertion {
273            headers.push(("PayPal-Auth-Assertion", paypal_auth_assertion.as_str()));
274        }
275        if let Some(paypal_request_id) = &self.paypal_request_id {
276            headers.push(("PayPal-Request-Id", paypal_request_id.as_str()));
277        }
278        headers
279    }
280}
281
282#[derive(Clone, Debug)]
283pub struct Request {
284    pub url: Url,
285    pub headers: HttpRequestHeaders,
286    pub query: Option<QueryParams>,
287    pub body: Option<serde_json::Value>,
288    pub method: reqwest::Method,
289}
290
291impl Request {
292    pub const fn new(
293        url: Url,
294        headers: HttpRequestHeaders,
295        query: Option<QueryParams>,
296        body: Option<serde_json::Value>,
297    ) -> Self {
298        Self {
299            url,
300            headers,
301            query,
302            body,
303            method: reqwest::Method::GET,
304        }
305    }
306}
307
308impl Default for Request {
309    fn default() -> Self {
310        Self {
311            url: RequestUrl::Sandbox.as_url().expect("Invalid URL"),
312            headers: Default::default(),
313            query: None,
314            body: None,
315            method: reqwest::Method::GET,
316        }
317    }
318}
319
320#[derive(Copy, Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
321pub enum RequestUrl {
322    #[default]
323    Sandbox,
324    Live,
325}
326
327impl RequestUrl {
328    pub fn as_url(&self) -> Result<Url, ParseError> {
329        let sandbox_url = Url::parse("https://api-m.sandbox.paypal.com")?;
330        let live_url = Url::parse("https://api-m.paypal.com")?;
331
332        Ok(match self {
333            Self::Sandbox => sandbox_url,
334            Self::Live => live_url,
335        })
336    }
337}
338
339impl std::fmt::Display for RequestUrl {
340    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
341        self.as_url().unwrap().as_str().fmt(formatter)
342    }
343}
344
345#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
346pub struct RetryCount(u32);
347
348impl RetryCount {
349    pub fn increment(&mut self) {
350        self.0 += 1;
351    }
352
353    pub fn get(&self) -> u32 {
354        self.0
355    }
356}
357
358impl From<i32> for RetryCount {
359    fn from(value: i32) -> Self {
360        if value < 0 {
361            Self(0)
362        } else {
363            Self(value as u32)
364        }
365    }
366}
367
368impl AddAssign for RetryCount {
369    fn add_assign(&mut self, other: Self) {
370        self.0 += other.0;
371    }
372}
373
374/// The strategy to use for executing a request. Defaults to `RetryStrategy::Once`.
375#[derive(Clone, Debug, Default)]
376pub enum RequestStrategy {
377    /// Fire the request once and return the response.
378    #[default]
379    Once,
380    /// Fire the request once and return the response. If the response is an error, retry the request
381    Retry(RetryCount),
382}
383
384impl RequestStrategy {
385    pub const fn is_retry(&self) -> bool {
386        matches!(self, Self::Retry(_))
387    }
388
389    pub const fn get_retry_count(&self) -> Option<&RetryCount> {
390        match self {
391            Self::Once => None,
392            Self::Retry(count) => Some(count),
393        }
394    }
395}