implement header params and more
This commit is contained in:
parent
a40c45a114
commit
db9c0b8df1
|
@ -5,7 +5,6 @@ authors = ["Edgar <git@edgarluque.com>"]
|
|||
description = "A library that wraps the paypal api asynchronously."
|
||||
repository = "https://github.com/edg-l/paypal-rs/"
|
||||
license = "AGPL-3.0"
|
||||
license-file = "LICENSE"
|
||||
keywords = ["paypal", "api", "async"]
|
||||
categories = ["api-bindings", "asynchronous"]
|
||||
readme = "README.md"
|
||||
|
@ -16,5 +15,8 @@ edition = "2018"
|
|||
reqwest = { version = "0.10", features = ["json"] }
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
jsonwebtoken = "7"
|
||||
base64 = "0.12"
|
||||
log = "0.4"
|
||||
dotenv = "0.15.0"
|
||||
dotenv = "0.15.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
143
src/lib.rs
143
src/lib.rs
|
@ -1,10 +1,14 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
extern crate chrono;
|
||||
|
||||
pub mod errors;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::time::{Duration, Instant};
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::header;
|
||||
|
||||
pub const LIVE_ENDPOINT: &str = "https://api.paypal.com";
|
||||
pub const SANDBOX_ENDPOINT: &str = "https://api.sandbox.paypal.com";
|
||||
|
@ -23,30 +27,97 @@ pub struct AccessToken {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Auth<'a> {
|
||||
pub client_id: &'a str,
|
||||
pub secret: &'a str,
|
||||
pub struct Auth {
|
||||
pub client_id: String,
|
||||
pub secret: String,
|
||||
pub access_token: Option<AccessToken>,
|
||||
pub expires: Option<(Instant, Duration)>,
|
||||
}
|
||||
|
||||
/// Represents a client used to interact with the paypal api.
|
||||
#[derive(Debug)]
|
||||
pub struct Client<'a> {
|
||||
pub struct Client {
|
||||
pub client: reqwest::Client,
|
||||
pub sandbox: bool,
|
||||
pub auth: Auth<'a>,
|
||||
pub auth: Auth,
|
||||
}
|
||||
|
||||
impl<'a> Client<'a> {
|
||||
/// Represents the query used in most GET api requests.
|
||||
///
|
||||
/// Reference: https://developer.paypal.com/docs/api/reference/api-requests/#query-parameters
|
||||
///
|
||||
/// Note: You can avoid most fields by the Default impl like so:
|
||||
/// ```
|
||||
/// let query = Query { count: Some(40), ..Default::default() };
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Query {
|
||||
pub count: Option<i32>,
|
||||
pub end_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub page: Option<i32>,
|
||||
pub page_size: Option<i32>,
|
||||
pub total_count_required: Option<bool>,
|
||||
pub sort_by: Option<String>,
|
||||
pub sort_order: Option<String>,
|
||||
pub start_id: Option<String>,
|
||||
pub start_index: Option<i32>,
|
||||
pub start_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Prefer {
|
||||
Minimal,
|
||||
Representation,
|
||||
}
|
||||
|
||||
/// Represents the optional header values used on paypal requests.
|
||||
///
|
||||
/// https://developer.paypal.com/docs/api/reference/api-requests/#paypal-auth-assertion
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HeaderParams {
|
||||
merchant_payer_id: Option<String>,
|
||||
client_metadata_id: Option<String>,
|
||||
partner_attribution_id: Option<String>,
|
||||
request_id: Option<String>,
|
||||
prefer: Option<Prefer>,
|
||||
content_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct AuthAssertionClaims {
|
||||
iss: String,
|
||||
payer_id: String,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Returns a new client, you must get_access_token afterwards to interact with the api.
|
||||
pub fn new(client_id: &'a str, secret: &'a str, sandbox: bool) -> Client<'a> {
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// dotenv().ok();
|
||||
/// let clientid = env::var("PAYPAL_CLIENTID").unwrap();
|
||||
/// let secret = env::var("PAYPAL_SECRET").unwrap();
|
||||
///
|
||||
/// let mut client = Client::new(
|
||||
/// clientid.as_str(),
|
||||
/// secret.as_str(),
|
||||
/// true,
|
||||
/// );
|
||||
///
|
||||
/// client.get_access_token().await.unwrap();
|
||||
/// println!("{:#?}", client);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new<S: Into<String>>(client_id: S, secret: S, sandbox: bool) -> Client {
|
||||
Client {
|
||||
client: reqwest::Client::new(),
|
||||
sandbox,
|
||||
auth: Auth {
|
||||
client_id,
|
||||
secret,
|
||||
client_id: client_id.into(),
|
||||
secret: secret.into(),
|
||||
access_token: None,
|
||||
expires: None,
|
||||
},
|
||||
|
@ -61,12 +132,62 @@ impl<'a> Client<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets up the request headers as required on https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
|
||||
fn setup_headers(&self, builder: reqwest::RequestBuilder, header_params: HeaderParams) -> reqwest::RequestBuilder {
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
headers.append(header::ACCEPT, "application/json".parse().unwrap());
|
||||
|
||||
if let Some(token) = &self.auth.access_token {
|
||||
headers.append(header::AUTHORIZATION, format!("Bearer {}", token.access_token).as_str().parse().unwrap());
|
||||
}
|
||||
|
||||
if let Some(merchant_payer_id) = header_params.merchant_payer_id {
|
||||
let claims = AuthAssertionClaims {
|
||||
iss: self.auth.client_id.clone(),
|
||||
payer_id: merchant_payer_id
|
||||
};
|
||||
let jwt_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256);
|
||||
let token = jsonwebtoken::encode(&jwt_header, &claims, &jsonwebtoken::EncodingKey::from_secret(self.auth.secret.as_ref())).unwrap();
|
||||
let encoded_token = base64::encode(token);
|
||||
headers.append("PayPal-Auth-Assertion", encoded_token.as_str().parse().unwrap());
|
||||
}
|
||||
|
||||
if let Some(client_metadata_id) = header_params.client_metadata_id {
|
||||
headers.append("PayPal-Client-Metadata-Id", client_metadata_id.as_str().parse().unwrap());
|
||||
}
|
||||
|
||||
if let Some(partner_attribution_id) = header_params.partner_attribution_id {
|
||||
headers.append("PayPal-Partner-Attribution-Id", partner_attribution_id.as_str().parse().unwrap());
|
||||
}
|
||||
|
||||
if let Some(request_id) = header_params.request_id {
|
||||
headers.append("PayPal-Request-Id", request_id.as_str().parse().unwrap());
|
||||
}
|
||||
|
||||
if let Some(prefer) = header_params.prefer {
|
||||
match prefer {
|
||||
Prefer::Minimal => headers.append("Prefer", "return=minimal".parse().unwrap()),
|
||||
Prefer::Representation => headers.append("Prefer", "return=representation".parse().unwrap()),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(content_type) = header_params.content_type {
|
||||
headers.append(header::CONTENT_TYPE, content_type.as_str().parse().unwrap());
|
||||
}
|
||||
|
||||
builder.headers(headers)
|
||||
}
|
||||
|
||||
/// Gets a access token used in all the api calls.
|
||||
pub async fn get_access_token(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let res = self
|
||||
.client
|
||||
.post(format!("{}/v1/oauth2/token", self.endpoint()).as_str())
|
||||
.basic_auth(self.auth.client_id, Some(self.auth.secret))
|
||||
.basic_auth(
|
||||
self.auth.client_id.as_str(),
|
||||
Some(self.auth.secret.as_str()),
|
||||
)
|
||||
.header("Content-Type", "x-www-form-urlencoded")
|
||||
.header("Accept", "application/json")
|
||||
.body("grant_type=client_credentials")
|
||||
|
|
Loading…
Reference in a new issue