From a2032510e373c547d1097b253e95a0551490cb6b Mon Sep 17 00:00:00 2001 From: Edgar Date: Tue, 5 Jan 2021 12:22:34 +0100 Subject: [PATCH] move/update deps, don't require explicit call to get auth token --- Cargo.toml | 13 ++-- src/common.rs | 1 - src/errors.rs | 10 +-- src/invoice.rs | 172 ++++++++++++++++++++++++++++--------------------- src/lib.rs | 23 +++++-- src/orders.rs | 49 +++++++------- 6 files changed, 155 insertions(+), 113 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7e7fb4a..8f9043c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "paypal-rs" -version = "0.2.0-alpha.0" +version = "0.2.0-alpha.1" authors = ["Edgar "] description = "A library that wraps the paypal api asynchronously." repository = "https://github.com/edg-l/paypal-rs/" @@ -14,12 +14,15 @@ edition = "2018" [dependencies] reqwest = { version = "0.10.10", features = ["json"] } -tokio = { version = "0.3.6", features = ["full"] } serde = { version = "1.0.118", features = ["derive"] } -serde_json = "1.0.60" +serde_json = "1.0.61" +chrono = { version = "0.4.19", features = ["serde"] } jsonwebtoken = "7.2.0" base64 = "0.13.0" log = "0.4.11" +bytes = "~0.5.6" + +[dev-dependencies] +# Can't update this until reqwest updates. +tokio = { version = "0.2.24", features = ["macros", "rt-core"] } dotenv = "0.15.0" -chrono = { version = "0.4.19", features = ["serde"] } -bytes = "0.5" diff --git a/src/common.rs b/src/common.rs index cfd74a8..864638a 100644 --- a/src/common.rs +++ b/src/common.rs @@ -105,4 +105,3 @@ pub struct LinkDescription { #[serde(skip_serializing_if = "Option::is_none")] pub method: Option, } - diff --git a/src/errors.rs b/src/errors.rs index e312508..ee7436d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,9 @@ //! Errors created by this crate. -use std::collections::HashMap; -use std::fmt; -use std::error::Error; -use serde::{Deserialize, Serialize}; use crate::common::LinkDescription; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::error::Error; +use std::fmt; /// A paypal api response error. #[derive(Debug, Serialize, Deserialize)] @@ -30,4 +30,4 @@ impl fmt::Display for ApiResponseError { } } -impl Error for ApiResponseError {} \ No newline at end of file +impl Error for ApiResponseError {} diff --git a/src/invoice.rs b/src/invoice.rs index c4248a1..224e5a0 100644 --- a/src/invoice.rs +++ b/src/invoice.rs @@ -8,9 +8,9 @@ use crate::common::*; use crate::errors; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use bytes::Bytes; /// Paypal File reference #[derive(Debug, Serialize, Deserialize)] @@ -688,16 +688,16 @@ pub struct CancelReason { /// QR pay action pub const QR_ACTION_PAY: &str = "pay"; /// QR details action -pub const QR_ACTION_DETAILS: &str ="details"; +pub const QR_ACTION_DETAILS: &str = "details"; /// QR creation parameters #[derive(Debug, Serialize, Deserialize, Default)] pub struct QRCodeParams { - /// The width, in pixels, of the QR code image. Value is from 150 to 500. + /// The width, in pixels, of the QR code image. Value is from 150 to 500. pub width: i32, - /// The height, in pixels, of the QR code image. Value is from 150 to 500. + /// The height, in pixels, of the QR code image. Value is from 150 to 500. pub height: i32, - /// The type of URL for which to generate a QR code. Valid values are pay and details. + /// The type of URL for which to generate a QR code. Valid values are pay and details. /// /// Check QR_ACTION_PAY and QR_ACTION_DETAILS constants pub action: Option, @@ -725,14 +725,16 @@ impl super::Client { /// /// For example, the next invoice number after `INVOICE-1234` is `INVOICE-1235`. pub async fn generate_invoice_number( - &self, + &mut self, header_params: crate::HeaderParams, ) -> Result> { - let build = self.setup_headers( - self.client - .post(format!("{}/v2/invoicing/generate-next-invoice-number", self.endpoint()).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client + .post(format!("{}/v2/invoicing/generate-next-invoice-number", self.endpoint()).as_str()), + header_params, + ) + .await; let res = build.send().await?; @@ -747,15 +749,17 @@ impl super::Client { /// Creates a draft invoice. To move the invoice from a draft to payable state, you must send the invoice. /// Include invoice details including merchant information. The invoice object must include an items array. pub async fn create_draft_invoice( - &self, + &mut self, invoice: InvoicePayload, header_params: crate::HeaderParams, ) -> Result> { - let build = self.setup_headers( - self.client - .post(format!("{}/v2/invoicing/invoices", self.endpoint()).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client + .post(format!("{}/v2/invoicing/invoices", self.endpoint()).as_str()), + header_params, + ) + .await; let res = build.json(&invoice).send().await?; @@ -769,15 +773,17 @@ impl super::Client { /// Get an invoice by ID. pub async fn get_invoice( - &self, + &mut self, invoice_id: S, header_params: crate::HeaderParams, ) -> Result> { - let build = self.setup_headers( - self.client - .post(format!("{}/v2/invoicing/invoices/{}", self.endpoint(), invoice_id).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client + .post(format!("{}/v2/invoicing/invoices/{}", self.endpoint(), invoice_id).as_str()), + header_params, + ) + .await; let res = build.send().await?; @@ -792,23 +798,25 @@ impl super::Client { /// List invoices /// Page size has the following limits: [1, 100]. pub async fn list_invoices( - &self, + &mut self, page: i32, page_size: i32, header_params: crate::HeaderParams, ) -> Result> { - let build = self.setup_headers( - self.client.get( - format!( - "{}/v2/invoicing/invoices?page={}&page_size={}&total_required=true", - self.endpoint(), - page, - page_size - ) - .as_str(), - ), - header_params, - ); + let build = self + .setup_headers( + self.client.get( + format!( + "{}/v2/invoicing/invoices?page={}&page_size={}&total_required=true", + self.endpoint(), + page, + page_size + ) + .as_str(), + ), + header_params, + ) + .await; let res = build.send().await?; @@ -822,15 +830,17 @@ impl super::Client { /// Delete a invoice pub async fn delete_invoice( - &self, + &mut self, invoice_id: S, header_params: crate::HeaderParams, ) -> Result<(), Box> { - let build = self.setup_headers( - self.client - .delete(format!("{}/v2/invoicing/invoices/{}", self.endpoint(), invoice_id).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client + .delete(format!("{}/v2/invoicing/invoices/{}", self.endpoint(), invoice_id).as_str()), + header_params, + ) + .await; let res = build.send().await?; @@ -843,25 +853,27 @@ impl super::Client { /// Update a invoice pub async fn update_invoice( - &self, + &mut self, invoice: Invoice, send_to_recipient: bool, send_to_invoicer: bool, header_params: crate::HeaderParams, ) -> Result<(), Box> { - let build = self.setup_headers( - self.client.put( - format!( - "{}/v2/invoicing/invoices/{}?send_to_recipient={}&send_to_invoicer={}", - self.endpoint(), - invoice.id, - send_to_recipient, - send_to_invoicer - ) - .as_str(), - ), - header_params, - ); + let build = self + .setup_headers( + self.client.put( + format!( + "{}/v2/invoicing/invoices/{}?send_to_recipient={}&send_to_invoicer={}", + self.endpoint(), + invoice.id, + send_to_recipient, + send_to_invoicer + ) + .as_str(), + ), + header_params, + ) + .await; let res = build.send().await?; @@ -874,16 +886,18 @@ impl super::Client { /// Cancel a invoice pub async fn cancel_invoice( - &self, + &mut self, invoice_id: S, reason: CancelReason, header_params: crate::HeaderParams, ) -> Result<(), Box> { - let build = self.setup_headers( - self.client - .post(format!("{}/v2/invoicing/invoices/{}/cancel", self.endpoint(), invoice_id,).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client + .post(format!("{}/v2/invoicing/invoices/{}/cancel", self.endpoint(), invoice_id,).as_str()), + header_params, + ) + .await; let res = build.json(&reason).send().await?; @@ -896,16 +910,24 @@ impl super::Client { /// Generate a QR code pub async fn generate_qr_code( - &self, + &mut self, invoice_id: S, params: QRCodeParams, header_params: crate::HeaderParams, ) -> Result> { - let build = self.setup_headers( - self.client - .post(format!("{}/v2/invoicing/invoices/{}/generate-qr-code", self.endpoint(), invoice_id).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client.post( + format!( + "{}/v2/invoicing/invoices/{}/generate-qr-code", + self.endpoint(), + invoice_id + ) + .as_str(), + ), + header_params, + ) + .await; let res = build.json(¶ms).send().await?; @@ -919,16 +941,18 @@ impl super::Client { /// Records a payment for the invoice. If no payment is due, the invoice is marked as PAID. Otherwise, the invoice is marked as PARTIALLY PAID. pub async fn record_invoice_payment( - &self, + &mut self, invoice_id: S, payload: RecordPaymentPayload, header_params: crate::HeaderParams, ) -> Result> { - let build = self.setup_headers( - self.client - .post(format!("{}/v2/invoicing/invoices/{}/payments", self.endpoint(), invoice_id).as_str()), - header_params, - ); + let build = self + .setup_headers( + self.client + .post(format!("{}/v2/invoicing/invoices/{}/payments", self.endpoint(), invoice_id).as_str()), + header_params, + ) + .await; let res = build.json(&payload).send().await?; diff --git a/src/lib.rs b/src/lib.rs index 716a1db..eebe253 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,9 +23,9 @@ use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; /// The paypal api endpoint used on a live application. -pub const LIVE_ENDPOINT: &str = "https://api.paypal.com"; +pub const LIVE_ENDPOINT: &str = "https://api-m.paypal.com"; /// The paypal api endpoint used on when testing. -pub const SANDBOX_ENDPOINT: &str = "https://api.sandbox.paypal.com"; +pub const SANDBOX_ENDPOINT: &str = "https://api-m.sandbox.paypal.com"; /// Represents the access token returned by the OAuth2 authentication. /// @@ -207,7 +207,16 @@ impl Client { } /// 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 { + async fn setup_headers( + &mut self, + builder: reqwest::RequestBuilder, + header_params: HeaderParams, + ) -> reqwest::RequestBuilder { + // Check if the token hasn't expired here, since it's called before any other call. + if let Err(e) = self.get_access_token().await { + log::warn!(target: "paypal-rs", "error getting access token: {:?}", e); + } + let mut headers = HeaderMap::new(); headers.append(header::ACCEPT, "application/json".parse().unwrap()); @@ -263,6 +272,9 @@ impl Client { /// Gets a access token used in all the api calls. pub async fn get_access_token(&mut self) -> Result<(), Box> { + if !self.access_token_expired() { + return Ok(()); + } let res = self .client .post(format!("{}/v1/oauth2/token", self.endpoint()).as_str()) @@ -303,15 +315,14 @@ mod tests { let clientid = env::var("PAYPAL_CLIENTID").unwrap(); let secret = env::var("PAYPAL_SECRET").unwrap(); - let mut client = Client::new(clientid, secret, true); + let client = Client::new(clientid, secret, true); - assert_eq!(client.get_access_token().await.is_err(), false, "should not error"); client } #[tokio::test] async fn test_order() { - let client = create_client().await; + let mut client = create_client().await; let order = OrderPayload::new(Intent::Authorize, vec![PurchaseUnit::new(Amount::new("EUR", "10.0"))]); diff --git a/src/orders.rs b/src/orders.rs index 61a44a2..6176188 100644 --- a/src/orders.rs +++ b/src/orders.rs @@ -1,12 +1,12 @@ //! An order represents a payment between two or more parties. //! -//! Use the Orders API to create, update, retrieve, authorize, and capture orders. +//! Use the Orders API to create, update, retrieve, authorize, and capture orders. //! //! Reference: https://developer.paypal.com/docs/api/orders/v2/ +use crate::common::*; use crate::errors; use serde::{Deserialize, Serialize}; -use crate::common::*; /// The intent to either capture payment immediately or authorize a payment for an order after order creation. #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] @@ -740,16 +740,16 @@ pub struct Order { impl super::Client { /// Creates an order. Supports orders with only one purchase unit. pub async fn create_order( - &self, + &mut self, order: OrderPayload, header_params: crate::HeaderParams, ) -> Result> { let builder = { self.setup_headers( - self.client - .post(&format!("{}/v2/checkout/orders", self.endpoint())), + self.client.post(&format!("{}/v2/checkout/orders", self.endpoint())), header_params, ) + .await }; let res = builder.json(&order).send().await?; @@ -763,7 +763,7 @@ impl super::Client { /// Used internally for order requests that have no body. async fn build_endpoint_order( - &self, + &mut self, order_id: S, endpoint: A, post: bool, @@ -771,13 +771,15 @@ impl super::Client { ) -> Result> { let format = format!("{}/v2/checkout/orders/{}/{}", self.endpoint(), order_id, endpoint); - let builder = self.setup_headers( - match post { - true => self.client.post(&format), - false => self.client.get(&format), - }, - header_params, - ); + let builder = self + .setup_headers( + match post { + true => self.client.post(&format), + false => self.client.get(&format), + }, + header_params, + ) + .await; let res = builder.send().await?; @@ -798,7 +800,7 @@ impl super::Client { /// /// More info on what you can change: https://developer.paypal.com/docs/api/orders/v2/#orders_patch pub async fn update_order( - &self, + &mut self, id: S, intent: Option, purchase_units: Option>, @@ -811,15 +813,17 @@ impl super::Client { for (i, unit) in p_units.iter().enumerate() { let unit_str = serde_json::to_string(&unit)?; - let mut unit_json = format!(r#" + let mut unit_json = format!( + r#" {{ "op": "replace", "path": "/purchase_units/@reference_id='{reference_id}'", "value": {unit} }} "#, - reference_id = unit.reference_id.clone().unwrap_or_else(|| String::from("default")), - unit = unit_str); + reference_id = unit.reference_id.clone().unwrap_or_else(|| String::from("default")), + unit = unit_str + ); if i < p_units.len() - 1 { unit_json += ","; @@ -832,7 +836,7 @@ impl super::Client { if let Some(x) = intent { let intent_str = match x { Intent::Authorize => String::from("AUTHORIZE"), - Intent::Capture => String::from("CAPTURE") + Intent::Capture => String::from("CAPTURE"), }; intent_json = format!( @@ -848,7 +852,7 @@ impl super::Client { } let final_json = { - if intent_json != "" && units_json != "" { + if !intent_json.is_empty() && !units_json.is_empty() { format!("[{},{}]", intent_json, units_json) } else { format!("[{}{}]", intent_json, units_json) @@ -864,6 +868,7 @@ impl super::Client { ..Default::default() }, ) + .await }; let res = builder.body(final_json.clone()).send().await?; @@ -877,7 +882,7 @@ impl super::Client { /// Shows details for an order, by ID. pub async fn show_order_details( - &self, + &mut self, order_id: S, ) -> Result> { self.build_endpoint_order(order_id, "", false, crate::HeaderParams::default()) @@ -888,7 +893,7 @@ impl super::Client { /// the buyer must first approve the order or a valid payment_source must be provided in the request. /// A buyer can approve the order upon being redirected to the rel:approve URL that was returned in the HATEOAS links in the create order response. pub async fn capture_order( - &self, + &mut self, order_id: S, header_params: crate::HeaderParams, ) -> Result> { @@ -900,7 +905,7 @@ impl super::Client { /// the buyer must first approve the order or a valid payment_source must be provided in the request. /// A buyer can approve the order upon being redirected to the rel:approve URL that was returned in the HATEOAS links in the create order response. pub async fn authorize_order( - &self, + &mut self, order_id: S, header_params: crate::HeaderParams, ) -> Result> {