remove boxed errors, some updates

This commit is contained in:
Edgar 2021-01-08 16:03:19 +01:00
parent 5ba9bfabfc
commit 5f77a90e1b
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
6 changed files with 185 additions and 101 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "paypal-rs"
version = "0.2.0-alpha.2"
version = "0.2.0-alpha.3"
authors = ["Edgar <git@edgarluque.com>"]
description = "A library that wraps the paypal api asynchronously."
repository = "https://github.com/edg-l/paypal-rs/"
@ -23,6 +23,5 @@ log = "0.4.11"
bytes = "1.0.0"
[dev-dependencies]
# Can't update this until reqwest updates.
tokio = { version = "1.0.1", features = ["macros", "rt"] }
dotenv = "0.15.0"

View file

@ -1,7 +1,7 @@
# paypal-rs
[![Crates.io](https://meritbadge.herokuapp.com/paypal-rs)](https://crates.io/crates/paypal-rs)
![Rust](https://github.com/edg-l/paypal-rs/workflows/Rust/badge.svg)
![Docs](https://docs.rs/paypal-rs/badge.svg)
[![Docs](https://docs.rs/paypal-rs/badge.svg)](https://docs.rs/paypal-rs)
A rust library that wraps the [paypal api](https://developer.paypal.com/docs/api) asynchronously in a stringly typed manner.
@ -35,9 +35,7 @@ async fn main() {
let order_payload = OrderPayload::new(
Intent::Authorize,
vec![PurchaseUnit::new(Amount::new(
Currency::EUR, "10.0",
))],
vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))],
);
let order = client

View file

@ -5,9 +5,10 @@ use std::collections::HashMap;
use std::error::Error;
use std::fmt;
/// A paypal api response error.
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiResponseError {
pub struct PaypalError {
/// The error name.
pub name: String,
/// The error message.
@ -24,13 +25,22 @@ pub struct ApiResponseError {
pub links: Vec<LinkDescription>,
}
impl fmt::Display for ApiResponseError {
impl fmt::Display for PaypalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#?}", self)
}
}
impl Error for ApiResponseError {}
impl Error for PaypalError {}
/// A response error, it may be paypal related or an error related to the http request itself.
#[derive(Debug)]
pub enum ResponseError {
/// A paypal api error.
ApiError(PaypalError),
/// A http error.
HttpError(reqwest::Error)
}
/// When a currency is invalid.
#[derive(Debug)]

View file

@ -7,7 +7,8 @@
//! Reference: https://developer.paypal.com/docs/api/invoicing/v2/
use crate::common::*;
use crate::errors;
use crate::HeaderParams;
use crate::errors::{ResponseError, PaypalError};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@ -727,7 +728,7 @@ impl super::Client {
pub async fn generate_invoice_number(
&mut self,
header_params: crate::HeaderParams,
) -> Result<String, Box<dyn std::error::Error>> {
) -> Result<String, ResponseError> {
let build = self
.setup_headers(
self.client
@ -736,13 +737,13 @@ impl super::Client {
)
.await;
let res = build.send().await?;
let res = build.send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let x = res.json::<HashMap<String, String>>().await?;
let x = res.json::<HashMap<String, String>>().await.map_err(ResponseError::HttpError)?;
Ok(x.get("invoice_number").expect("to have a invoice number").clone())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
@ -751,8 +752,8 @@ impl super::Client {
pub async fn create_draft_invoice(
&mut self,
invoice: InvoicePayload,
header_params: crate::HeaderParams,
) -> Result<Invoice, Box<dyn std::error::Error>> {
header_params: HeaderParams,
) -> Result<Invoice, ResponseError> {
let build = self
.setup_headers(
self.client
@ -761,22 +762,22 @@ impl super::Client {
)
.await;
let res = build.json(&invoice).send().await?;
let res = build.json(&invoice).send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let x = res.json::<Invoice>().await?;
let x = res.json::<Invoice>().await.map_err(ResponseError::HttpError)?;
Ok(x)
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// Get an invoice by ID.
pub async fn get_invoice<S: std::fmt::Display>(
pub async fn get_invoice(
&mut self,
invoice_id: S,
header_params: crate::HeaderParams,
) -> Result<Invoice, Box<dyn std::error::Error>> {
invoice_id: &str,
header_params: HeaderParams,
) -> Result<Invoice, ResponseError> {
let build = self
.setup_headers(
self.client
@ -785,13 +786,13 @@ impl super::Client {
)
.await;
let res = build.send().await?;
let res = build.send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let x = res.json::<Invoice>().await?;
let x = res.json::<Invoice>().await.map_err(ResponseError::HttpError)?;
Ok(x)
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
@ -801,8 +802,8 @@ impl super::Client {
&mut self,
page: i32,
page_size: i32,
header_params: crate::HeaderParams,
) -> Result<InvoiceList, Box<dyn std::error::Error>> {
header_params: HeaderParams,
) -> Result<InvoiceList, ResponseError> {
let build = self
.setup_headers(
self.client.get(
@ -818,22 +819,22 @@ impl super::Client {
)
.await;
let res = build.send().await?;
let res = build.send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let x = res.json::<InvoiceList>().await?;
let x = res.json::<InvoiceList>().await.map_err(ResponseError::HttpError)?;
Ok(x)
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// Delete a invoice
pub async fn delete_invoice<S: std::fmt::Display>(
pub async fn delete_invoice(
&mut self,
invoice_id: S,
header_params: crate::HeaderParams,
) -> Result<(), Box<dyn std::error::Error>> {
invoice_id: &str,
header_params: HeaderParams,
) -> Result<(), ResponseError> {
let build = self
.setup_headers(
self.client
@ -842,12 +843,12 @@ impl super::Client {
)
.await;
let res = build.send().await?;
let res = build.send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
Ok(())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
@ -857,8 +858,8 @@ impl super::Client {
invoice: Invoice,
send_to_recipient: bool,
send_to_invoicer: bool,
header_params: crate::HeaderParams,
) -> Result<(), Box<dyn std::error::Error>> {
header_params: HeaderParams,
) -> Result<(), ResponseError> {
let build = self
.setup_headers(
self.client.put(
@ -875,22 +876,22 @@ impl super::Client {
)
.await;
let res = build.send().await?;
let res = build.send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
Ok(())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// Cancel a invoice
pub async fn cancel_invoice<S: std::fmt::Display>(
pub async fn cancel_invoice(
&mut self,
invoice_id: S,
invoice_id: &str,
reason: CancelReason,
header_params: crate::HeaderParams,
) -> Result<(), Box<dyn std::error::Error>> {
header_params: HeaderParams,
) -> Result<(), ResponseError> {
let build = self
.setup_headers(
self.client
@ -899,22 +900,22 @@ impl super::Client {
)
.await;
let res = build.json(&reason).send().await?;
let res = build.json(&reason).send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
Ok(())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// Generate a QR code
pub async fn generate_qr_code<S: std::fmt::Display>(
pub async fn generate_qr_code(
&mut self,
invoice_id: S,
invoice_id: &str,
params: QRCodeParams,
header_params: crate::HeaderParams,
) -> Result<Bytes, Box<dyn std::error::Error>> {
header_params: HeaderParams,
) -> Result<Bytes, ResponseError> {
let build = self
.setup_headers(
self.client.post(
@ -929,23 +930,23 @@ impl super::Client {
)
.await;
let res = build.json(&params).send().await?;
let res = build.json(&params).send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let b = res.bytes().await?;
let b = res.bytes().await.map_err(ResponseError::HttpError)?;
Ok(b)
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// 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<S: std::fmt::Display>(
pub async fn record_invoice_payment(
&mut self,
invoice_id: S,
invoice_id: &str,
payload: RecordPaymentPayload,
header_params: crate::HeaderParams,
) -> Result<String, Box<dyn std::error::Error>> {
) -> Result<String, ResponseError> {
let build = self
.setup_headers(
self.client
@ -954,13 +955,13 @@ impl super::Client {
)
.await;
let res = build.json(&payload).send().await?;
let res = build.json(&payload).send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let x = res.json::<HashMap<String, String>>().await?;
let x = res.json::<HashMap<String, String>>().await.map_err(ResponseError::HttpError)?;
Ok(x.get("payment_id").unwrap().to_owned())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}

View file

@ -1,14 +1,82 @@
//! # paypal-rs
//! [![Crates.io](https://meritbadge.herokuapp.com/paypal-rs)](https://crates.io/crates/paypal-rs)
//! ![Rust](https://github.com/edg-l/paypal-rs/workflows/Rust/badge.svg)
//! ![Docs](https://docs.rs/paypal-rs/badge.svg)
//! [![Docs](https://docs.rs/paypal-rs/badge.svg)](https://docs.rs/paypal-rs)
//!
//! A rust library that wraps the [paypal api](https://developer.paypal.com/docs/api) asynchronously in a strongly typed manner.
//! A rust library that wraps the [paypal api](https://developer.paypal.com/docs/api) asynchronously in a stringly typed manner.
//!
//! Crate: https://crates.io/crates/paypal-rs
//!
//! Documentation: https://docs.rs/paypal-rs
//!
//! Currently in early development.
//!
//! ## Example
//!
//! ```rust
//! use paypal_rs::{
//! Client,
//! HeaderParams,
//! Prefer,
//! orders::{OrderPayload, Intent, PurchaseUnit, Amount},
//! common::Currency,
//! };
//!
//! #[tokio::main]
//! async fn main() {
//! dotenv::dotenv::ok();
//! let clientid = std::env::var("PAYPAL_CLIENTID").unwrap();
//! let secret = std::env::var("PAYPAL_SECRET").unwrap();
//!
//! let mut client = Client::new(clientid, secret, true);
//!
//! client.get_access_token().await.unwrap();
//!
//! let order_payload = OrderPayload::new(
//! Intent::Authorize,
//! vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))],
//! );
//!
//! let order = client
//! .create_order(
//! order_payload,
//! HeaderParams {
//! prefer: Some(Prefer::Representation),
//! ..Default::default()
//! },
//! )
//! .await
//! .unwrap();
//! }
//! ```
//!
//! ## Testing
//! You need the enviroment variables PAYPAL_CLIENTID and PAYPAL_SECRET to be set.
//!
//! `cargo test`
//!
//! ## Roadmap
//!
//! - [x] Orders API - 0.1.0
//! - - [x] Create order
//! - - [x] Update order
//! - - [x] Show order details
//! - - [x] Authorize payment for order
//! - - [x] Capture payment for order
//! - [ ] Invoicing API - 0.2.0
//! - [ ] Payments API - 0.3.0
//! - [ ] Tracking API - 0.4.0
//! - [ ] Subscriptions API - 0.5.0
//! - [ ] Identity API - 0.6.0
//! - [ ] Disputes API - 0.7.0
//! - [ ] Catalog Products API - 0.8.0
//! - [ ] Partner Referrals API - 0.9.0
//! - [ ] Payouts API - 0.10.0
//! - [ ] Transaction Search API - 0.11.0
//! - [ ] Referenced Payouts API - 0.12.0
//! - [ ] Vault API - 0.13.0
//! - [ ] Webhooks Management API - 0.14.0
//! - [ ] Payment Experience Web Profiles API - 1.0.0
#![deny(missing_docs)]
@ -17,6 +85,7 @@ pub mod errors;
pub mod invoice;
pub mod orders;
use errors::{PaypalError, ResponseError};
use reqwest::header;
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
@ -271,7 +340,7 @@ impl Client {
}
/// Gets a access token used in all the api calls.
pub async fn get_access_token(&mut self) -> Result<(), Box<dyn std::error::Error>> {
pub async fn get_access_token(&mut self) -> Result<(), ResponseError> {
if !self.access_token_expired() {
return Ok(());
}
@ -283,15 +352,18 @@ impl Client {
.header("Accept", "application/json")
.body("grant_type=client_credentials")
.send()
.await?;
.await
.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let token = res.json::<AccessToken>().await?;
let token = res.json::<AccessToken>().await.map_err(ResponseError::HttpError)?;
self.auth.expires = Some((Instant::now(), Duration::new(token.expires_in, 0)));
self.auth.access_token = Some(token);
Ok(())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(
res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?,
))
}
}
@ -307,10 +379,10 @@ impl Client {
#[cfg(test)]
mod tests {
use crate::{orders::*, Client, HeaderParams, Prefer};
use crate::common::Currency;
use std::str::FromStr;
use crate::{orders::*, Client, HeaderParams, Prefer};
use std::env;
use std::str::FromStr;
async fn create_client() -> Client {
dotenv::dotenv().ok();
@ -326,7 +398,10 @@ mod tests {
async fn test_order() {
let mut client = create_client().await;
let order = OrderPayload::new(Intent::Authorize, vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))]);
let order = OrderPayload::new(
Intent::Authorize,
vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))],
);
let ref_id = format!(
"TEST-{:?}",
@ -354,7 +429,7 @@ mod tests {
client
.update_order(
order_created.id,
&order_created.id,
Some(Intent::Capture),
Some(order_created.purchase_units.expect("to exist")),
)

View file

@ -4,8 +4,9 @@
//!
//! Reference: https://developer.paypal.com/docs/api/orders/v2/
use crate::HeaderParams;
use crate::common::*;
use crate::errors;
use crate::errors::{ResponseError, PaypalError};
use serde::{Deserialize, Serialize};
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
@ -742,8 +743,8 @@ impl super::Client {
pub async fn create_order(
&mut self,
order: OrderPayload,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
header_params: HeaderParams,
) -> Result<Order, ResponseError> {
let builder = {
self.setup_headers(
self.client.post(&format!("{}/v2/checkout/orders", self.endpoint())),
@ -751,24 +752,24 @@ impl super::Client {
)
.await
};
let res = builder.json(&order).send().await?;
let res = builder.json(&order).send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let order = res.json::<Order>().await?;
let order = res.json::<Order>().await.map_err(ResponseError::HttpError)?;
Ok(order)
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// Used internally for order requests that have no body.
async fn build_endpoint_order<S: std::fmt::Display, A: std::fmt::Display>(
async fn build_endpoint_order(
&mut self,
order_id: S,
endpoint: A,
order_id: &str,
endpoint: &str,
post: bool,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
) -> Result<Order, ResponseError> {
let format = format!("{}/v2/checkout/orders/{}/{}", self.endpoint(), order_id, endpoint);
let builder = self
@ -781,13 +782,13 @@ impl super::Client {
)
.await;
let res = builder.send().await?;
let res = builder.send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
let order = res.json::<Order>().await?;
let order = res.json::<Order>().await.expect("error serializing json response");
Ok(order)
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
@ -799,12 +800,12 @@ impl super::Client {
/// Note: You can only update the intent from Authorize to Capture
///
/// More info on what you can change: https://developer.paypal.com/docs/api/orders/v2/#orders_patch
pub async fn update_order<S: std::fmt::Display>(
pub async fn update_order(
&mut self,
id: S,
id: &str,
intent: Option<Intent>,
purchase_units: Option<Vec<PurchaseUnit>>,
) -> Result<(), Box<dyn std::error::Error>> {
) -> Result<(), ResponseError> {
let mut intent_json = String::new();
let units_json = String::new();
@ -812,7 +813,7 @@ impl super::Client {
let mut units_json = String::new();
for (i, unit) in p_units.iter().enumerate() {
let unit_str = serde_json::to_string(&unit)?;
let unit_str = serde_json::to_string(&unit).expect("error deserializing PurchaseUnit json");
let mut unit_json = format!(
r#"
{{
@ -871,32 +872,32 @@ impl super::Client {
.await
};
let res = builder.body(final_json.clone()).send().await?;
let res = builder.body(final_json.clone()).send().await.map_err(ResponseError::HttpError)?;
if res.status().is_success() {
Ok(())
} else {
Err(Box::new(res.json::<errors::ApiResponseError>().await?))
Err(ResponseError::ApiError(res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?))
}
}
/// Shows details for an order, by ID.
pub async fn show_order_details<S: std::fmt::Display>(
pub async fn show_order_details(
&mut self,
order_id: S,
) -> Result<Order, Box<dyn std::error::Error>> {
self.build_endpoint_order(order_id, "", false, crate::HeaderParams::default())
order_id: &str,
) -> Result<Order, ResponseError> {
self.build_endpoint_order(order_id, "", false, HeaderParams::default())
.await
}
/// Captures payment for an order. To successfully capture payment for an order,
/// 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<S: std::fmt::Display>(
pub async fn capture_order(
&mut self,
order_id: S,
order_id: &str,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
) -> Result<Order, ResponseError> {
self.build_endpoint_order(order_id, "capture", true, header_params)
.await
}
@ -904,11 +905,11 @@ impl super::Client {
/// Authorizes payment for an order. To successfully authorize payment for an order,
/// 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<S: std::fmt::Display>(
pub async fn authorize_order(
&mut self,
order_id: S,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
order_id: &str,
header_params: HeaderParams,
) -> Result<Order, ResponseError> {
self.build_endpoint_order(order_id, "authorize", true, header_params)
.await
}