1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
use crate::client::paypal::USER_AGENT;
use http_types::url::ParseError;
use http_types::Url;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::ops::AddAssign;

/// For most REST GET calls, you can include one or more query parameters on the request URI to filter, limit the size of,
/// and sort the data in an API response. For filter parameters, see the individual GET calls.
/// To limit, or page, and sort the data that is returned in some API responses, use these, or similar, query parameters:
#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct QueryParams {
    /// The number of items to list in the response.
    pub count: Option<i32>,

    /// The end date and time for the range to show in the response,
    /// in [Internet date and time format](https://tools.ietf.org/html/rfc3339#section-5.6).
    /// For example, end_time=2016-03-06T11:00:00Z.
    pub end_time: Option<i32>,

    /// The page number indicating which set of items will be returned in the response.
    /// So, the combination of page=1 and page_size=20 returns the first 20 items.
    /// The combination of page=2 and page_size=20 returns items 21 through 40.
    pub page: Option<i32>,

    /// The number of items to return in the response.
    pub page_size: Option<i32>,

    /// Indicates whether to show the total count in the response.
    pub total_count_required: Option<bool>,

    /// Sorts the payments in the response by a specified value, such as the create time or update time.
    pub sort_by: Option<String>,

    /// Sorts the items in the response in ascending or descending order.
    pub sort_order: Option<String>,

    /// The ID of the starting resource in the response. When results are paged, you can use the
    /// next_id value as the start_id to continue with the next set of results.
    pub start_id: Option<String>,

    /// The start index of the payments to list. Typically, you use the start_index to jump to a
    /// specific position in the resource history based on its cart. For example, to start at the
    /// second item in a list of results, specify ?start_index=2.
    pub start_index: Option<i32>,

    /// The start date and time for the range to show in the response, in Internet date and time format.
    /// For example, start_time=2016-03-06T11:00:00Z.
    pub start_time: Option<String>,
}

impl QueryParams {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn count(mut self, count: i32) -> Self {
        self.count = Some(count);
        self
    }

    pub fn end_time(mut self, end_time: i32) -> Self {
        self.end_time = Some(end_time);
        self
    }

    pub fn page(mut self, page: i32) -> Self {
        self.page = Some(page);
        self
    }

    pub fn page_size(mut self, page_size: i32) -> Self {
        self.page_size = Some(page_size);
        self
    }

    pub fn total_count_required(mut self, total_count_required: bool) -> Self {
        self.total_count_required = Some(total_count_required);
        self
    }

    pub fn sort_by(mut self, sort_by: String) -> Self {
        self.sort_by = Some(sort_by);
        self
    }

    pub fn sort_order(mut self, sort_order: String) -> Self {
        self.sort_order = Some(sort_order);
        self
    }

    pub fn start_id(mut self, start_id: String) -> Self {
        self.start_id = Some(start_id);
        self
    }

    pub fn start_index(mut self, start_index: i32) -> Self {
        self.start_index = Some(start_index);
        self
    }

    pub fn start_time(mut self, start_time: String) -> Self {
        self.start_time = Some(start_time);
        self
    }
}

/// The commonly used HTTP request headers
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct HttpRequestHeaders {
    /// The response format, which is required for operations with a response body.
    #[serde(rename = "Accept")]
    pub accept: String,

    /// Required to get an access token or make API calls:
    /// * Get an access token `partial:partials/docs/oauth-credentials.en-XC`
    ///  `partial:partials/docs/access-tokens.en-XC` `partial:partials/docs/rest/bearer-token.en-XC`
    /// * Make REST API calls
    ///
    /// Include the access token in the Authorization header with the `Bearer` authentication scheme.
    #[serde(rename = "Authorization")]
    pub authorization: String,

    /// The request format, which is required for operations with a request body.
    #[serde(rename = "Content-Type")]
    pub content_type: String,

    /// An API client-provided JSON Web Token (JWT) assertion that identifies the merchant.
    ///
    /// To use this header, you must get consent to act on behalf of a merchant.
    /// You might want to use a JWT if you act on behalf of multiple merchants at the same time,
    /// because it is difficult and expensive to generate and manage multiple access tokens.
    /// Instead of managing multiple access tokens, you can use this header to provide a JWT
    /// assertion that identifies the merchant when you call the API.
    ///
    /// ### Constructing the JWT
    /// While you can use either a signed or unsigned (unsecured) JWT, PayPal recommends using the
    /// unsigned JWT for simplicity because the information you pass with the JWT is not sensitive
    /// data.
    ///
    /// A JWT consists of three parts:
    /// * Header: Identifies the algorithm that generated the signature. Because PayPal recommends
    ///           an unsigned JWT, pass an alg of none for the header.
    /// * Payload: Defines a set of claims, or statements, about an entity. In the case of the
    ///            PayPal-Auth-Assertion header, the entity is typically the merchant.
    ///            The payload can contain iss, which identifies the third-party calling the API,
    ///            and one of the following to identify the merchant for whom the call is made:
    ///            email or payer_id.
    ///            **Note**: Because a merchant's email address can change, PayPal recommends
    ///                      using payer_id to specify a merchant.
    /// * Signature: Validates the token and consists of the encoded header, the encoded payload,
    ///              a secret, and the algorithm. Because PayPal recommends an unsigned JWT, pass an
    ///              empty string for the signature.
    ///             **Note**: If you prefer a signed JWT, you can sign it using your secret from
    ///                       your API credentials.
    ///
    /// # Examples
    /// JOSE header:
    /// "alg": "none"
    /// Payload:
    /// ```json
    /// "iss": "client_id",
    /// "payer_id": "merchant_payer_id"
    /// ```
    /// Signature. Use "" for the unsigned case.
    ///
    /// To pass the JWT easily in HTTP environments, use Base64 to encode all three parts of the
    /// JWT separately, then concatenate them with a period (.). The following code shows the
    /// previous example after Base64 encoding and compact serialization (or concatenation):
    /// `ew0KICAiYWxnIjogIm5vbmUiDQp9.ew0KICAiaXNzIjogImNsaWVudF9pZCIsDQogICJwYXllcl9pZCI6ICJtZXJjaGFudF9wYXllcl9pZCINCn0=.`
    ///
    /// Refer to [Issue a Refund](https://developer.paypal.com/docs/multiparty/issue-refund/)
    /// in the PayPal Commerce Platform documentation for an example of using the
    /// `PayPal-Auth-Assertion` header in an API call.
    #[serde(
        rename = "PayPal-Auth-Assertion",
        skip_serializing_if = "Option::is_none"
    )]
    pub paypal_auth_assertion: Option<String>,

    /// Verifies that the payment originates from a valid, user-consented device and application.
    /// Reduces fraud and decreases declines. Transactions that do not include a client metadata ID
    /// are not eligible for PayPal Seller Protection.
    #[serde(
        rename = "PayPal-Client-Metadata-Id",
        skip_serializing_if = "Option::is_none"
    )]
    pub client_client_metadata_id: Option<String>,

    /// Optional. Identifies the caller as a PayPal partner. To receive revenue attribution,
    /// specify a unique build notation (BN) code. BN codes provide tracking on all transactions
    /// that originate or are associated with a particular partner. To find your BN code,
    /// see [Code andCredential Reference](https://developer.paypal.com/docs/multiparty/get-started#code-and-credential-reference).
    #[serde(
        rename = "PayPal-Partner-Attribution-Id",
        skip_serializing_if = "Option::is_none"
    )]
    pub paypal_partner_attribution_id: Option<String>,

    /// For example, a user calls refund captured payment with the PayPal-Request-Id header that
    /// contains a unique user-provided ID. The user can make the call again with the same ID in the
    /// `PayPal-Request-Id` header for up to 45 days because the server stores this ID for this long
    /// for this call.
    ///
    /// If the initial call fails with the HTTP `500` status code but the server has already
    /// refunded the payment, the caller does not need to worry that the server will refund the
    /// payment again.
    ///
    /// **Note**: Not all APIs support this header. To determine whether your API supports it and
    /// for information about how long the server stores the ID, see the reference for your API.
    #[serde(rename = "PayPal-Request-Id", skip_serializing_if = "Option::is_none")]
    pub paypal_request_id: Option<String>,

    #[serde(rename = "User-Agent")]
    pub user_agent: String,
}

impl Default for HttpRequestHeaders {
    fn default() -> Self {
        Self {
            accept: "application/json".to_string(),
            authorization: "Bearer ".to_string(),
            content_type: "application/json".to_string(),
            user_agent: USER_AGENT.to_string(),
            paypal_partner_attribution_id: None,
            client_client_metadata_id: None,
            paypal_auth_assertion: None,
            paypal_request_id: None,
        }
    }
}

impl HttpRequestHeaders {
    pub fn new(authentication: impl Into<String>) -> Self {
        Self {
            authorization: authentication.into(),
            ..Default::default()
        }
    }

    pub fn to_vec(&self) -> Vec<(&str, &str)> {
        let mut headers = Vec::new();
        headers.push(("Accept", self.accept.as_str()));
        headers.push(("Content-Type", self.content_type.as_str()));
        headers.push(("User-Agent", self.user_agent.as_str()));
        if let Some(paypal_partner_attribution_id) = &self.paypal_partner_attribution_id {
            headers.push((
                "PayPal-Partner-Attribution-Id",
                paypal_partner_attribution_id.as_str(),
            ));
        }
        if let Some(client_client_metadata_id) = &self.client_client_metadata_id {
            headers.push((
                "PayPal-Client-Metadata-Id",
                client_client_metadata_id.as_str(),
            ));
        }
        if let Some(paypal_auth_assertion) = &self.paypal_auth_assertion {
            headers.push(("PayPal-Auth-Assertion", paypal_auth_assertion.as_str()));
        }
        if let Some(paypal_request_id) = &self.paypal_request_id {
            headers.push(("PayPal-Request-Id", paypal_request_id.as_str()));
        }
        headers
    }
}

#[derive(Clone, Debug)]
pub struct Request {
    pub url: Url,
    pub headers: HttpRequestHeaders,
    pub query: Option<QueryParams>,
    pub body: Option<serde_json::Value>,
    pub method: reqwest::Method,
}

impl Request {
    pub fn new(
        url: Url,
        headers: HttpRequestHeaders,
        query: Option<QueryParams>,
        body: Option<serde_json::Value>,
    ) -> Self {
        Self {
            url,
            headers,
            query,
            body,
            method: reqwest::Method::GET,
        }
    }
}

impl Default for Request {
    fn default() -> Self {
        Self {
            url: RequestUrl::Sandbox.as_url().expect("Invalid URL"),
            headers: Default::default(),
            query: None,
            body: None,
            method: reqwest::Method::GET,
        }
    }
}

#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum RequestUrl {
    Sandbox,
    Live,
}

impl RequestUrl {
    pub fn as_url(&self) -> Result<Url, ParseError> {
        let sandbox_url = Url::parse("https://api-m.sandbox.paypal.com")?;
        let live_url = Url::parse("https://api-m.paypal.com")?;

        Ok(match self {
            RequestUrl::Sandbox => sandbox_url,
            RequestUrl::Live => live_url,
        })
    }
}

impl std::fmt::Display for RequestUrl {
    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.as_url().unwrap().as_str().fmt(formatter)
    }
}

impl Default for RequestUrl {
    fn default() -> Self {
        RequestUrl::Sandbox
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct RetryCount(u32);

impl RetryCount {
    pub fn increment(&mut self) {
        self.0 += 1;
    }

    pub fn get(&self) -> u32 {
        self.0
    }
}

impl From<i32> for RetryCount {
    fn from(value: i32) -> Self {
        if value < 0 {
            Self(0)
        } else {
            Self(value as u32)
        }
    }
}

impl AddAssign for RetryCount {
    fn add_assign(&mut self, other: Self) {
        self.0 += other.0;
    }
}

/// The strategy to use for executing a request. Defaults to `RetryStrategy::Once`.
#[derive(Clone, Debug)]
pub enum RequestStrategy {
    /// Fire the request once and return the response.
    Once,
    /// Fire the request once and return the response. If the response is an error, retry the request
    Retry(RetryCount),
}

impl RequestStrategy {
    pub fn is_retry(&self) -> bool {
        match self {
            RequestStrategy::Once => false,
            RequestStrategy::Retry(_) => true,
        }
    }

    pub fn get_retry_count(&self) -> Option<&RetryCount> {
        match self {
            RequestStrategy::Once => None,
            RequestStrategy::Retry(count) => Some(count),
        }
    }
}

impl Default for RequestStrategy {
    fn default() -> Self {
        RequestStrategy::Once
    }
}