diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e551aa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.env diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..22cd558 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "paypal-rs" +version = "0.0.1" +authors = ["Edgar "] +description = "A library that wraps the paypal api asynchronously." +keywords = ["paypal", "api", "async"] +categories = ["api-bindings", "asynchronous"] +readme = "README.md" +edition = "2018" + + +[dependencies] +reqwest = { version = "0.10", features = ["json"] } +tokio = { version = "0.2", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +log = "0.4" +dotenv = "0.15.0" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bc947f --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# paypal-rs + +A rust library that wraps the [paypal api](https://developer.paypal.com/docs/api) asynchronously. + +Currently in early development. \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..9bd659e --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,13 @@ +use std::fmt; +use std::error::Error; + +#[derive(Debug, Clone)] +pub struct GetAccessTokenError; + +impl fmt::Display for GetAccessTokenError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "error getting access token") + } +} + +impl Error for GetAccessTokenError {} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0bb2b09 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,83 @@ +#[cfg(test)] +mod tests; + +pub mod errors; + +use serde::Deserialize; +use std::time::{Duration, Instant}; + +pub const LIVE_ENDPOINT: &str = "https://api.paypal.com"; +pub const SANDBOX_ENDPOINT: &str = "https://api.sandbox.paypal.com"; + +#[derive(Debug, Deserialize)] +pub struct AccessToken { + pub scope: String, + pub access_token: String, + pub token_type: String, + pub app_id: String, + pub expires_in: u64, + pub nonce: String, +} + +#[derive(Debug)] +pub struct Auth<'a> { + pub client_id: &'a str, + pub secret: &'a str, + pub access_token: Option, + pub expires: Option, +} + +#[derive(Debug)] +pub struct Client<'a> { + pub client: reqwest::Client, + pub sandbox: bool, + pub auth: Auth<'a>, +} + +impl<'a> Client<'a> { + pub fn new(client_id: &'a str, secret: &'a str, sandbox: bool) -> Client<'a> { + Client { + client: reqwest::Client::new(), + sandbox, + auth: Auth { + client_id, + secret, + access_token: None, + expires: None, + }, + } + } + + fn endpoint(&self) -> &str { + if self.sandbox { + SANDBOX_ENDPOINT + } else { + LIVE_ENDPOINT + } + } + + pub async fn get_access_token(&mut self) -> Result<(), Box> { + let res = self + .client + .post(format!("{}/v1/oauth2/token", self.endpoint()).as_str()) + .basic_auth(self.auth.client_id, Some(self.auth.secret)) + .header("Content-Type", "x-www-form-urlencoded") + .header("Accept", "application/json") + .body("grant_type=client_credentials") + .send() + .await?; + + if res.status().is_success() { + let token = res.json::().await?; + self.auth.expires = Some(Instant::now() + Duration::new(token.expires_in, 0)); + self.auth.access_token = Some(token); + println!("{:#?}", self.auth); + } else { + println!("status = {:#?}", res.status()); + println!("res = {:#?}", res); + return Err(Box::new(errors::GetAccessTokenError)); + } + + Ok(()) + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..f21b75e --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,19 @@ +use crate::*; +use dotenv::dotenv; +use std::env; + +#[tokio::test] +async fn it_works() { + 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, + ); + + assert_eq!(client.get_access_token().await.is_err(), false, "should not error"); + println!("{:#?}", client); +}