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}