move/update deps, don't require explicit call to get auth token
This commit is contained in:
parent
31a4a78a42
commit
a2032510e3
13
Cargo.toml
13
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "paypal-rs"
|
||||
version = "0.2.0-alpha.0"
|
||||
version = "0.2.0-alpha.1"
|
||||
authors = ["Edgar <git@edgarluque.com>"]
|
||||
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"
|
||||
|
|
|
@ -105,4 +105,3 @@ pub struct LinkDescription {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub method: Option<LinkMethod>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
166
src/invoice.rs
166
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,7 +688,7 @@ 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)]
|
||||
|
@ -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<String, Box<dyn std::error::Error>> {
|
||||
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<Invoice, Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
invoice_id: S,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<Invoice, Box<dyn std::error::Error>> {
|
||||
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<InvoiceList, Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
invoice_id: S,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
invoice_id: S,
|
||||
reason: CancelReason,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
invoice_id: S,
|
||||
params: QRCodeParams,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<Bytes, Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
invoice_id: S,
|
||||
payload: RecordPaymentPayload,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
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?;
|
||||
|
||||
|
|
23
src/lib.rs
23
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<dyn std::error::Error>> {
|
||||
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"))]);
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
//!
|
||||
//! 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<Order, Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display, A: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
order_id: S,
|
||||
endpoint: A,
|
||||
post: bool,
|
||||
|
@ -771,13 +771,15 @@ impl super::Client {
|
|||
) -> Result<Order, Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
id: S,
|
||||
intent: Option<Intent>,
|
||||
purchase_units: Option<Vec<PurchaseUnit>>,
|
||||
|
@ -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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
order_id: S,
|
||||
) -> Result<Order, Box<dyn std::error::Error>> {
|
||||
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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
order_id: S,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<Order, Box<dyn std::error::Error>> {
|
||||
|
@ -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<S: std::fmt::Display>(
|
||||
&self,
|
||||
&mut self,
|
||||
order_id: S,
|
||||
header_params: crate::HeaderParams,
|
||||
) -> Result<Order, Box<dyn std::error::Error>> {
|
||||
|
|
Loading…
Reference in a new issue