start working on new api design
This commit is contained in:
parent
d5d29fb016
commit
b1f97b13da
|
@ -22,6 +22,8 @@ jsonwebtoken = "8.0.1"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
log = "0.4.16"
|
log = "0.4.16"
|
||||||
bytes = "1.1.0"
|
bytes = "1.1.0"
|
||||||
|
derive_builder = "0.11.1"
|
||||||
|
serde_qs = "0.9.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/*
|
||||||
use paypal_rs::{common::*, errors::*, invoice::*, Client, HeaderParams};
|
use paypal_rs::{common::*, errors::*, invoice::*, Client, HeaderParams};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -70,3 +71,6 @@ async fn main() -> Result<(), ResponseError> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn main() {}
|
279
src/api/invoice.rs
Normal file
279
src/api/invoice.rs
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
//! Use the Invoicing API to create, send, and manage invoices.
|
||||||
|
//! You can also use the API or webhooks to track invoice payments. When you send an invoice to a customer,
|
||||||
|
//! the invoice moves from draft to payable state. PayPal then emails the customer a link to the invoice on the PayPal website.
|
||||||
|
//! Customers with a PayPal account can log in and pay the invoice with PayPal. Alternatively,
|
||||||
|
//! customers can pay as a guest with a debit card or credit card. For more information, see the Invoicing Overview and the Invoicing Integration Guide.
|
||||||
|
//!
|
||||||
|
//! Reference: https://developer.paypal.com/docs/api/invoicing/v2/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
impl super::Client {
|
||||||
|
/// Generates the next invoice number that is available to the merchant.
|
||||||
|
///
|
||||||
|
/// The next invoice number uses the prefix and suffix from the last invoice number and increments the number by one.
|
||||||
|
///
|
||||||
|
/// For example, the next invoice number after `INVOICE-1234` is `INVOICE-1235`.
|
||||||
|
pub async fn generate_invoice_number(
|
||||||
|
&mut self,
|
||||||
|
header_params: crate::HeaderParams,
|
||||||
|
) -> Result<String, ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let x = res.json::<HashMap<String, String>>().await?;
|
||||||
|
Ok(x.get("invoice_number").expect("to have a invoice number").clone())
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
&mut self,
|
||||||
|
invoice: InvoicePayload,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<Invoice, ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
//println!("{:#?}", res.text().await?);
|
||||||
|
let inv = res.json::<Invoice>().await?;
|
||||||
|
Ok(inv)
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an invoice by ID.
|
||||||
|
pub async fn get_invoice(
|
||||||
|
&mut self,
|
||||||
|
invoice_id: &str,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<Invoice, ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let x = res.json::<Invoice>().await?;
|
||||||
|
Ok(x)
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List invoices
|
||||||
|
/// Page size has the following limits: [1, 100].
|
||||||
|
pub async fn list_invoices(
|
||||||
|
&mut self,
|
||||||
|
page: i32,
|
||||||
|
page_size: i32,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<InvoiceList, ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let x = res.json::<InvoiceList>().await?;
|
||||||
|
Ok(x)
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a invoice
|
||||||
|
pub async fn delete_invoice(&mut self, invoice_id: &str, header_params: HeaderParams) -> Result<(), ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a invoice
|
||||||
|
pub async fn update_invoice(
|
||||||
|
&mut self,
|
||||||
|
invoice: Invoice,
|
||||||
|
send_to_recipient: bool,
|
||||||
|
send_to_invoicer: bool,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<(), ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel a invoice
|
||||||
|
pub async fn cancel_invoice(
|
||||||
|
&mut self,
|
||||||
|
invoice_id: &str,
|
||||||
|
reason: CancelReason,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<(), ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a QR code
|
||||||
|
pub async fn generate_qr_code(
|
||||||
|
&mut self,
|
||||||
|
invoice_id: &str,
|
||||||
|
params: QRCodeParams,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<Bytes, ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let b = res.bytes().await?;
|
||||||
|
Ok(b)
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
&mut self,
|
||||||
|
invoice_id: &str,
|
||||||
|
payload: RecordPaymentPayload,
|
||||||
|
header_params: crate::HeaderParams,
|
||||||
|
) -> Result<String, ResponseError> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let x = res.json::<HashMap<String, String>>().await?;
|
||||||
|
Ok(x.get("payment_id").unwrap().to_owned())
|
||||||
|
} else {
|
||||||
|
Err(res.json::<PaypalError>().await?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_payments-delete
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{Client, HeaderParams};
|
||||||
|
|
||||||
|
async fn create_client() -> Client {
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
let clientid = std::env::var("PAYPAL_CLIENTID").unwrap();
|
||||||
|
let secret = std::env::var("PAYPAL_SECRET").unwrap();
|
||||||
|
|
||||||
|
Client::new(clientid, secret, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_invoice() -> anyhow::Result<()> {
|
||||||
|
let mut client = create_client().await;
|
||||||
|
|
||||||
|
let _list = client.list_invoices(1, 10, HeaderParams::default()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
2
src/api/mod.rs
Normal file
2
src/api/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod orders;
|
||||||
|
pub mod invoice;
|
191
src/api/orders.rs
Normal file
191
src/api/orders.rs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::orders::{Order, OrderPayload, PaymentSourceResponse},
|
||||||
|
endpoint::Endpoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CreateOrder {
|
||||||
|
order: OrderPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateOrder {
|
||||||
|
pub fn new(order: OrderPayload) -> Self {
|
||||||
|
Self { order }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint for CreateOrder {
|
||||||
|
type Query = ();
|
||||||
|
|
||||||
|
type Body = OrderPayload;
|
||||||
|
|
||||||
|
type Response = Order;
|
||||||
|
|
||||||
|
fn relative_path(&self) -> Cow<str> {
|
||||||
|
Cow::Borrowed("/v2/checkout/orders")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(&self) -> reqwest::Method {
|
||||||
|
reqwest::Method::POST
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body(&self) -> Option<&Self::Body> {
|
||||||
|
Some(&self.order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Update order.
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShowOrderDetails {
|
||||||
|
order_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShowOrderDetails {
|
||||||
|
pub fn new(order_id: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
order_id: order_id.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint for ShowOrderDetails {
|
||||||
|
type Query = ();
|
||||||
|
|
||||||
|
type Body = ();
|
||||||
|
|
||||||
|
type Response = Order;
|
||||||
|
|
||||||
|
fn relative_path(&self) -> Cow<str> {
|
||||||
|
Cow::Owned(format!("/v2/checkout/orders/{}", self.order_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(&self) -> reqwest::Method {
|
||||||
|
reqwest::Method::GET
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CaptureOrder {
|
||||||
|
order_id: String,
|
||||||
|
// TODO: payment source? https://developer.paypal.com/docs/api/orders/v2/#orders_capture
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CaptureOrder {
|
||||||
|
pub fn new(order_id: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
order_id: order_id.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint for CaptureOrder {
|
||||||
|
type Query = ();
|
||||||
|
|
||||||
|
type Body = ();
|
||||||
|
|
||||||
|
type Response = Order;
|
||||||
|
|
||||||
|
fn relative_path(&self) -> Cow<str> {
|
||||||
|
Cow::Owned(format!("/v2/checkout/orders/{}/capture", self.order_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(&self) -> reqwest::Method {
|
||||||
|
reqwest::Method::POST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AuthorizeOrder {
|
||||||
|
order_id: String,
|
||||||
|
// TODO: payment source? https://developer.paypal.com/docs/api/orders/v2/#orders_authorize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthorizeOrder {
|
||||||
|
pub fn new(order_id: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
order_id: order_id.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint for AuthorizeOrder {
|
||||||
|
type Query = ();
|
||||||
|
|
||||||
|
type Body = ();
|
||||||
|
|
||||||
|
type Response = Order;
|
||||||
|
|
||||||
|
fn relative_path(&self) -> Cow<str> {
|
||||||
|
Cow::Owned(format!("/v2/checkout/orders/{}/authorize", self.order_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(&self) -> reqwest::Method {
|
||||||
|
reqwest::Method::POST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::data::common::Currency;
|
||||||
|
use crate::HeaderParams;
|
||||||
|
use crate::{
|
||||||
|
api::orders::{CreateOrder, ShowOrderDetails},
|
||||||
|
data::orders::*,
|
||||||
|
tests::create_client,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_order() -> anyhow::Result<()> {
|
||||||
|
let mut client = create_client().await;
|
||||||
|
client.get_access_token().await?;
|
||||||
|
|
||||||
|
let order = OrderPayloadBuilder::default()
|
||||||
|
.intent(Intent::Authorize)
|
||||||
|
.purchase_units(vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))])
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let ref_id = format!(
|
||||||
|
"TEST-{:?}",
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
);
|
||||||
|
|
||||||
|
let create_order = CreateOrder::new(order);
|
||||||
|
|
||||||
|
let order_created = client
|
||||||
|
.execute_ext(
|
||||||
|
create_order,
|
||||||
|
HeaderParams {
|
||||||
|
request_id: Some(ref_id.clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_ne!(order_created.id, "");
|
||||||
|
assert_eq!(order_created.status, OrderStatus::Created);
|
||||||
|
assert_eq!(order_created.links.len(), 4);
|
||||||
|
|
||||||
|
let show_order = ShowOrderDetails::new(&order_created.id);
|
||||||
|
|
||||||
|
let show_order_result = client
|
||||||
|
.execute_ext(
|
||||||
|
show_order,
|
||||||
|
HeaderParams {
|
||||||
|
request_id: Some(ref_id.clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(order_created.id, show_order_result.id);
|
||||||
|
assert_eq!(order_created.status, show_order_result.status);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
226
src/client.rs
226
src/client.rs
|
@ -0,0 +1,226 @@
|
||||||
|
use reqwest::header::{self, HeaderMap};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
endpoint::Endpoint,
|
||||||
|
errors::{PaypalError, ResponseError},
|
||||||
|
AuthAssertionClaims, HeaderParams, Prefer, LIVE_ENDPOINT, SANDBOX_ENDPOINT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents the access token returned by the OAuth2 authentication.
|
||||||
|
///
|
||||||
|
/// https://developer.paypal.com/docs/api/get-an-access-token-postman/
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct AccessToken {
|
||||||
|
/// The OAuth2 scopes.
|
||||||
|
pub scope: String,
|
||||||
|
/// The access token.
|
||||||
|
pub access_token: String,
|
||||||
|
/// The token type.
|
||||||
|
pub token_type: String,
|
||||||
|
/// The app id.
|
||||||
|
pub app_id: String,
|
||||||
|
/// Seconds until it expires.
|
||||||
|
pub expires_in: u64,
|
||||||
|
/// The nonce.
|
||||||
|
pub nonce: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores OAuth2 information.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Auth {
|
||||||
|
/// Your client id.
|
||||||
|
pub client_id: String,
|
||||||
|
/// The secret.
|
||||||
|
pub secret: String,
|
||||||
|
/// The access token returned by oauth2 authentication.
|
||||||
|
pub access_token: Option<AccessToken>,
|
||||||
|
/// Used to check when the token expires.
|
||||||
|
pub expires: Option<(Instant, Duration)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a client used to interact with the paypal api.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Client {
|
||||||
|
/// Internal http client
|
||||||
|
pub(crate) client: reqwest::Client,
|
||||||
|
/// Whether you are or not in a sandbox enviroment.
|
||||||
|
pub sandbox: bool,
|
||||||
|
/// Api Auth information
|
||||||
|
pub auth: Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Returns a new client, you must get_access_token afterwards to interact with the api.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use paypal_rs::Client;
|
||||||
|
///
|
||||||
|
/// #[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();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn new(client_id: String, secret: String, sandbox: bool) -> Client {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up the request headers as required on https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
|
||||||
|
async fn setup_headers(
|
||||||
|
&self,
|
||||||
|
builder: reqwest::RequestBuilder,
|
||||||
|
header_params: HeaderParams,
|
||||||
|
) -> Result<reqwest::RequestBuilder, ResponseError> {
|
||||||
|
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).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.parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(client_metadata_id) = header_params.client_metadata_id {
|
||||||
|
headers.append("PayPal-Client-Metadata-Id", client_metadata_id.parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(partner_attribution_id) = header_params.partner_attribution_id {
|
||||||
|
headers.append("PayPal-Partner-Attribution-Id", partner_attribution_id.parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(request_id) = header_params.request_id {
|
||||||
|
headers.append("PayPal-Request-Id", request_id.parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
match header_params.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.parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(builder.headers(headers))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a access token used in all the api calls.
|
||||||
|
pub async fn get_access_token(&mut self) -> Result<(), ResponseError> {
|
||||||
|
if !self.access_token_expired() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
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
|
||||||
|
.map_err(ResponseError::HttpError)?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
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(ResponseError::ApiError(
|
||||||
|
res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the access token expired.
|
||||||
|
pub fn access_token_expired(&self) -> bool {
|
||||||
|
if let Some(expires) = self.auth.expires {
|
||||||
|
expires.0.elapsed() >= expires.1
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_ext<E>(&self, endpoint: E, headers: HeaderParams) -> Result<E::Response, ResponseError>
|
||||||
|
where
|
||||||
|
E: Endpoint,
|
||||||
|
{
|
||||||
|
let mut url = endpoint.full_path(self.sandbox);
|
||||||
|
|
||||||
|
if let Some(query) = endpoint.query() {
|
||||||
|
let query_string = serde_qs::to_string(query).expect("serialize the query correctly");
|
||||||
|
url.push_str(&query_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut request = self.client.request(endpoint.method(), url);
|
||||||
|
request = self.setup_headers(request, headers).await?;
|
||||||
|
|
||||||
|
if let Some(body) = endpoint.body() {
|
||||||
|
request = request.json(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = request.send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let response_body = res.json::<E::Response>().await?;
|
||||||
|
Ok(response_body)
|
||||||
|
} else {
|
||||||
|
Err(ResponseError::ApiError(res.json::<PaypalError>().await?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute<E>(&self, endpoint: E) -> Result<E::Response, ResponseError>
|
||||||
|
where
|
||||||
|
E: Endpoint,
|
||||||
|
{
|
||||||
|
self.execute_ext(endpoint, HeaderParams::default()).await
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ use std::str::FromStr;
|
||||||
/// The phone type.
|
/// The phone type.
|
||||||
///
|
///
|
||||||
/// https://developer.paypal.com/docs/api/orders/v2/#definition-phone_with_type
|
/// https://developer.paypal.com/docs/api/orders/v2/#definition-phone_with_type
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum PhoneType {
|
pub enum PhoneType {
|
||||||
|
@ -21,7 +21,7 @@ pub enum PhoneType {
|
||||||
|
|
||||||
/// The non-portable additional address details
|
/// The non-portable additional address details
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct AddressDetails {
|
pub struct AddressDetails {
|
||||||
/// The street number.
|
/// The street number.
|
||||||
pub street_number: Option<String>,
|
pub street_number: Option<String>,
|
||||||
|
@ -40,7 +40,7 @@ pub struct AddressDetails {
|
||||||
|
|
||||||
/// The address of the payer.
|
/// The address of the payer.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Address {
|
pub struct Address {
|
||||||
/// The first line of the address. For example, number or street. For example, 173 Drury Lane.
|
/// The first line of the address. For example, number or street. For example, 173 Drury Lane.
|
||||||
/// Required for data entry and compliance and risk checks. Must contain the full address.
|
/// Required for data entry and compliance and risk checks. Must contain the full address.
|
|
@ -1,18 +1,6 @@
|
||||||
//! Use the Invoicing API to create, send, and manage invoices.
|
use crate::{data::common::*, data::common::LinkDescription};
|
||||||
//! You can also use the API or webhooks to track invoice payments. When you send an invoice to a customer,
|
|
||||||
//! the invoice moves from draft to payable state. PayPal then emails the customer a link to the invoice on the PayPal website.
|
|
||||||
//! Customers with a PayPal account can log in and pay the invoice with PayPal. Alternatively,
|
|
||||||
//! customers can pay as a guest with a debit card or credit card. For more information, see the Invoicing Overview and the Invoicing Integration Guide.
|
|
||||||
//!
|
|
||||||
//! Reference: https://developer.paypal.com/docs/api/invoicing/v2/
|
|
||||||
|
|
||||||
use crate::common::*;
|
|
||||||
use crate::errors::{PaypalError, ResponseError};
|
|
||||||
use crate::HeaderParams;
|
|
||||||
use bytes::Bytes;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Paypal File reference
|
/// Paypal File reference
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -641,271 +629,4 @@ pub struct RecordPaymentPayload {
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
|
|
||||||
shipping_info: Option<ContactInformation>,
|
shipping_info: Option<ContactInformation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Client {
|
|
||||||
/// Generates the next invoice number that is available to the merchant.
|
|
||||||
///
|
|
||||||
/// The next invoice number uses the prefix and suffix from the last invoice number and increments the number by one.
|
|
||||||
///
|
|
||||||
/// For example, the next invoice number after `INVOICE-1234` is `INVOICE-1235`.
|
|
||||||
pub async fn generate_invoice_number(
|
|
||||||
&mut self,
|
|
||||||
header_params: crate::HeaderParams,
|
|
||||||
) -> Result<String, ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let x = res.json::<HashMap<String, String>>().await?;
|
|
||||||
Ok(x.get("invoice_number").expect("to have a invoice number").clone())
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(
|
|
||||||
&mut self,
|
|
||||||
invoice: InvoicePayload,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<Invoice, ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
//println!("{:#?}", res.text().await?);
|
|
||||||
let inv = res.json::<Invoice>().await?;
|
|
||||||
Ok(inv)
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an invoice by ID.
|
|
||||||
pub async fn get_invoice(
|
|
||||||
&mut self,
|
|
||||||
invoice_id: &str,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<Invoice, ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let x = res.json::<Invoice>().await?;
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List invoices
|
|
||||||
/// Page size has the following limits: [1, 100].
|
|
||||||
pub async fn list_invoices(
|
|
||||||
&mut self,
|
|
||||||
page: i32,
|
|
||||||
page_size: i32,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<InvoiceList, ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let x = res.json::<InvoiceList>().await?;
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete a invoice
|
|
||||||
pub async fn delete_invoice(&mut self, invoice_id: &str, header_params: HeaderParams) -> Result<(), ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a invoice
|
|
||||||
pub async fn update_invoice(
|
|
||||||
&mut self,
|
|
||||||
invoice: Invoice,
|
|
||||||
send_to_recipient: bool,
|
|
||||||
send_to_invoicer: bool,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<(), ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cancel a invoice
|
|
||||||
pub async fn cancel_invoice(
|
|
||||||
&mut self,
|
|
||||||
invoice_id: &str,
|
|
||||||
reason: CancelReason,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<(), ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a QR code
|
|
||||||
pub async fn generate_qr_code(
|
|
||||||
&mut self,
|
|
||||||
invoice_id: &str,
|
|
||||||
params: QRCodeParams,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<Bytes, ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let b = res.bytes().await?;
|
|
||||||
Ok(b)
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(
|
|
||||||
&mut self,
|
|
||||||
invoice_id: &str,
|
|
||||||
payload: RecordPaymentPayload,
|
|
||||||
header_params: crate::HeaderParams,
|
|
||||||
) -> Result<String, ResponseError> {
|
|
||||||
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?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let x = res.json::<HashMap<String, String>>().await?;
|
|
||||||
Ok(x.get("payment_id").unwrap().to_owned())
|
|
||||||
} else {
|
|
||||||
Err(res.json::<PaypalError>().await?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_payments-delete
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{Client, HeaderParams};
|
|
||||||
|
|
||||||
async fn create_client() -> Client {
|
|
||||||
dotenv::dotenv().ok();
|
|
||||||
let clientid = std::env::var("PAYPAL_CLIENTID").unwrap();
|
|
||||||
let secret = std::env::var("PAYPAL_SECRET").unwrap();
|
|
||||||
|
|
||||||
Client::new(clientid, secret, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_invoice() -> anyhow::Result<()> {
|
|
||||||
let mut client = create_client().await;
|
|
||||||
|
|
||||||
let _list = client.list_invoices(1, 10, HeaderParams::default()).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
3
src/data/mod.rs
Normal file
3
src/data/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod common;
|
||||||
|
pub mod orders;
|
||||||
|
pub mod invoice;
|
|
@ -1,17 +1,12 @@
|
||||||
//! An order represents a payment between two or more parties.
|
use super::common::*;
|
||||||
//!
|
|
||||||
//! 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::{PaypalError, ResponseError};
|
use crate::errors::{PaypalError, ResponseError};
|
||||||
use crate::HeaderParams;
|
use crate::HeaderParams;
|
||||||
|
use derive_builder::Builder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
|
|
||||||
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
|
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum Intent {
|
pub enum Intent {
|
||||||
/// The merchant intends to capture payment immediately after the customer makes a payment.
|
/// The merchant intends to capture payment immediately after the customer makes a payment.
|
||||||
|
@ -33,7 +28,7 @@ impl Default for Intent {
|
||||||
/// Represents a payer name.
|
/// Represents a payer name.
|
||||||
///
|
///
|
||||||
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer.name
|
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer.name
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||||
pub struct PayerName {
|
pub struct PayerName {
|
||||||
/// When the party is a person, the party's given, or first, name.
|
/// When the party is a person, the party's given, or first, name.
|
||||||
pub given_name: String,
|
pub given_name: String,
|
||||||
|
@ -43,7 +38,7 @@ pub struct PayerName {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The phone number, in its canonical international E.164 numbering plan format.
|
/// The phone number, in its canonical international E.164 numbering plan format.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||||
pub struct PhoneNumber {
|
pub struct PhoneNumber {
|
||||||
/// The national number, in its canonical international E.164 numbering plan format.
|
/// The national number, in its canonical international E.164 numbering plan format.
|
||||||
/// The combined length of the country calling code (CC) and the national number must not be greater than 15 digits.
|
/// The combined length of the country calling code (CC) and the national number must not be greater than 15 digits.
|
||||||
|
@ -54,7 +49,7 @@ pub struct PhoneNumber {
|
||||||
/// The phone number of the customer. Available only when you enable the
|
/// The phone number of the customer. Available only when you enable the
|
||||||
/// Contact Telephone Number option in the Profile & Settings for the merchant's PayPal account.
|
/// Contact Telephone Number option in the Profile & Settings for the merchant's PayPal account.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Phone {
|
pub struct Phone {
|
||||||
/// The phone type.
|
/// The phone type.
|
||||||
pub phone_type: Option<PhoneType>,
|
pub phone_type: Option<PhoneType>,
|
||||||
|
@ -63,7 +58,7 @@ pub struct Phone {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The customer's tax ID type. Supported for the PayPal payment method only.
|
/// The customer's tax ID type. Supported for the PayPal payment method only.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum TaxIdType {
|
pub enum TaxIdType {
|
||||||
|
@ -74,7 +69,7 @@ pub enum TaxIdType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tax information of the payer.
|
/// The tax information of the payer.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct TaxInfo {
|
pub struct TaxInfo {
|
||||||
/// The customer's tax ID. Supported for the PayPal payment method only.
|
/// The customer's tax ID. Supported for the PayPal payment method only.
|
||||||
/// Typically, the tax ID is 11 characters long for individuals and 14 characters long for businesses.
|
/// Typically, the tax ID is 11 characters long for individuals and 14 characters long for businesses.
|
||||||
|
@ -87,7 +82,7 @@ pub struct TaxInfo {
|
||||||
///
|
///
|
||||||
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer
|
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Payer {
|
pub struct Payer {
|
||||||
/// The name of the payer.
|
/// The name of the payer.
|
||||||
pub name: Option<PayerName>,
|
pub name: Option<PayerName>,
|
||||||
|
@ -108,7 +103,7 @@ pub struct Payer {
|
||||||
|
|
||||||
/// Breakdown provides details such as total item amount, total tax amount, shipping, handling, insurance, and discounts, if any.
|
/// Breakdown provides details such as total item amount, total tax amount, shipping, handling, insurance, and discounts, if any.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Breakdown {
|
pub struct Breakdown {
|
||||||
/// The subtotal for all items. Required if the request includes purchase_units[].items[].unit_amount.
|
/// The subtotal for all items. Required if the request includes purchase_units[].items[].unit_amount.
|
||||||
/// Must equal the sum of (items[].unit_amount * items[].quantity) for all items.
|
/// Must equal the sum of (items[].unit_amount * items[].quantity) for all items.
|
||||||
|
@ -129,7 +124,7 @@ pub struct Breakdown {
|
||||||
|
|
||||||
/// Represents an amount of money.
|
/// Represents an amount of money.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Amount {
|
pub struct Amount {
|
||||||
/// The [three-character ISO-4217 currency code](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/) that identifies the currency.
|
/// The [three-character ISO-4217 currency code](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/) that identifies the currency.
|
||||||
pub currency_code: Currency,
|
pub currency_code: Currency,
|
||||||
|
@ -156,7 +151,7 @@ impl Amount {
|
||||||
|
|
||||||
/// The merchant who receives payment for this transaction.
|
/// The merchant who receives payment for this transaction.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Payee {
|
pub struct Payee {
|
||||||
/// The email address of merchant.
|
/// The email address of merchant.
|
||||||
pub email_address: Option<String>,
|
pub email_address: Option<String>,
|
||||||
|
@ -166,7 +161,7 @@ pub struct Payee {
|
||||||
|
|
||||||
/// Fees, commissions, tips, or donations
|
/// Fees, commissions, tips, or donations
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PlatformFee {
|
pub struct PlatformFee {
|
||||||
/// The fee for this transaction.
|
/// The fee for this transaction.
|
||||||
pub amount: Money,
|
pub amount: Money,
|
||||||
|
@ -176,7 +171,7 @@ pub struct PlatformFee {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The funds that are held on behalf of the merchant
|
/// The funds that are held on behalf of the merchant
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||||
pub enum DisbursementMode {
|
pub enum DisbursementMode {
|
||||||
/// The funds are released to the merchant immediately.
|
/// The funds are released to the merchant immediately.
|
||||||
Instant,
|
Instant,
|
||||||
|
@ -194,7 +189,7 @@ impl Default for DisbursementMode {
|
||||||
|
|
||||||
/// Any additional payment instructions for PayPal Commerce Platform customers.
|
/// Any additional payment instructions for PayPal Commerce Platform customers.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct PaymentInstruction {
|
pub struct PaymentInstruction {
|
||||||
/// An array of various fees, commissions, tips, or donations.
|
/// An array of various fees, commissions, tips, or donations.
|
||||||
pub platform_fees: Option<Vec<PlatformFee>>,
|
pub platform_fees: Option<Vec<PlatformFee>>,
|
||||||
|
@ -203,7 +198,7 @@ pub struct PaymentInstruction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The item category type.
|
/// The item category type.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum ItemCategoryType {
|
pub enum ItemCategoryType {
|
||||||
/// Goods that are stored, delivered, and used in their electronic format.
|
/// Goods that are stored, delivered, and used in their electronic format.
|
||||||
|
@ -222,7 +217,7 @@ impl Default for ItemCategoryType {
|
||||||
|
|
||||||
/// The name and address of the person to whom to ship the items.
|
/// The name and address of the person to whom to ship the items.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct ShippingDetail {
|
pub struct ShippingDetail {
|
||||||
/// The name of the person to whom to ship the items. Supports only the full_name property.
|
/// The name of the person to whom to ship the items. Supports only the full_name property.
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
@ -232,7 +227,7 @@ pub struct ShippingDetail {
|
||||||
|
|
||||||
/// Represents an item.
|
/// Represents an item.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
/// The item name or title.
|
/// The item name or title.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -252,7 +247,7 @@ pub struct Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The status of the payment authorization.
|
/// The status of the payment authorization.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum AuthorizationStatus {
|
pub enum AuthorizationStatus {
|
||||||
/// The authorized payment is created. No captured payments have been made for this authorized payment.
|
/// The authorized payment is created. No captured payments have been made for this authorized payment.
|
||||||
|
@ -274,7 +269,7 @@ pub enum AuthorizationStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authorization status reason.
|
/// Authorization status reason.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum AuthorizationStatusDetailsReason {
|
pub enum AuthorizationStatusDetailsReason {
|
||||||
/// Authorization is pending manual review.
|
/// Authorization is pending manual review.
|
||||||
|
@ -282,14 +277,14 @@ pub enum AuthorizationStatusDetailsReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details about the status of the authorization.
|
/// Details about the status of the authorization.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
pub struct AuthorizationStatusDetails {
|
pub struct AuthorizationStatusDetails {
|
||||||
/// The reason why the authorized status is PENDING.
|
/// The reason why the authorized status is PENDING.
|
||||||
pub reason: AuthorizationStatusDetailsReason,
|
pub reason: AuthorizationStatusDetailsReason,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A payment authorization.
|
/// A payment authorization.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
pub struct AuthorizationWithData {
|
pub struct AuthorizationWithData {
|
||||||
/// The status for the authorized payment.
|
/// The status for the authorized payment.
|
||||||
pub status: AuthorizationStatus,
|
pub status: AuthorizationStatus,
|
||||||
|
@ -298,7 +293,7 @@ pub struct AuthorizationWithData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The capture status.
|
/// The capture status.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum CaptureStatus {
|
pub enum CaptureStatus {
|
||||||
/// The funds for this captured payment were credited to the payee's PayPal account.
|
/// The funds for this captured payment were credited to the payee's PayPal account.
|
||||||
|
@ -314,7 +309,7 @@ pub enum CaptureStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture status reason.
|
/// Capture status reason.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum CaptureStatusDetailsReason {
|
pub enum CaptureStatusDetailsReason {
|
||||||
/// The payer initiated a dispute for this captured payment with PayPal.
|
/// The payer initiated a dispute for this captured payment with PayPal.
|
||||||
|
@ -346,7 +341,7 @@ pub enum CaptureStatusDetailsReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details about the captured payment status.
|
/// Details about the captured payment status.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
pub struct CaptureStatusDetails {
|
pub struct CaptureStatusDetails {
|
||||||
/// The reason why the captured payment status is PENDING or DENIED.
|
/// The reason why the captured payment status is PENDING or DENIED.
|
||||||
pub reason: CaptureStatusDetailsReason,
|
pub reason: CaptureStatusDetailsReason,
|
||||||
|
@ -354,7 +349,7 @@ pub struct CaptureStatusDetails {
|
||||||
|
|
||||||
/// A captured payment.
|
/// A captured payment.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
pub struct Capture {
|
pub struct Capture {
|
||||||
/// The status of the captured payment.
|
/// The status of the captured payment.
|
||||||
pub status: CaptureStatus,
|
pub status: CaptureStatus,
|
||||||
|
@ -363,7 +358,7 @@ pub struct Capture {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The status of the refund
|
/// The status of the refund
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum RefundStatus {
|
pub enum RefundStatus {
|
||||||
/// The refund was cancelled.
|
/// The refund was cancelled.
|
||||||
|
@ -375,7 +370,7 @@ pub enum RefundStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refund status reason.
|
/// Refund status reason.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum RefundStatusDetailsReason {
|
pub enum RefundStatusDetailsReason {
|
||||||
/// The customer's account is funded through an eCheck, which has not yet cleared.
|
/// The customer's account is funded through an eCheck, which has not yet cleared.
|
||||||
|
@ -383,14 +378,14 @@ pub enum RefundStatusDetailsReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details about the status of the refund.
|
/// Details about the status of the refund.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||||
pub struct RefundStatusDetails {
|
pub struct RefundStatusDetails {
|
||||||
/// The reason why the refund has the PENDING or FAILED status.
|
/// The reason why the refund has the PENDING or FAILED status.
|
||||||
pub reason: RefundStatusDetailsReason,
|
pub reason: RefundStatusDetailsReason,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A refund
|
/// A refund
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||||
pub struct Refund {
|
pub struct Refund {
|
||||||
/// The status of the refund.
|
/// The status of the refund.
|
||||||
pub status: RefundStatus,
|
pub status: RefundStatus,
|
||||||
|
@ -399,7 +394,7 @@ pub struct Refund {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The comprehensive history of payments for the purchase unit.
|
/// The comprehensive history of payments for the purchase unit.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PaymentCollection {
|
pub struct PaymentCollection {
|
||||||
/// An array of authorized payments for a purchase unit. A purchase unit can have zero or more authorized payments.
|
/// An array of authorized payments for a purchase unit. A purchase unit can have zero or more authorized payments.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -414,7 +409,7 @@ pub struct PaymentCollection {
|
||||||
|
|
||||||
/// Represents either a full or partial order that the payer intends to purchase from the payee.
|
/// Represents either a full or partial order that the payer intends to purchase from the payee.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct PurchaseUnit {
|
pub struct PurchaseUnit {
|
||||||
/// The API caller-provided external ID for the purchase unit. Required for multiple purchase units when you must update the order through PATCH.
|
/// The API caller-provided external ID for the purchase unit. Required for multiple purchase units when you must update the order through PATCH.
|
||||||
/// If you omit this value and the order contains only one purchase unit, PayPal sets this value to default.
|
/// If you omit this value and the order contains only one purchase unit, PayPal sets this value to default.
|
||||||
|
@ -469,7 +464,7 @@ impl PurchaseUnit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of landing page to show on the PayPal site for customer checkout.
|
/// The type of landing page to show on the PayPal site for customer checkout.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum LandingPage {
|
pub enum LandingPage {
|
||||||
/// When the customer clicks PayPal Checkout, the customer is redirected to a page to log in to PayPal and approve the payment.
|
/// When the customer clicks PayPal Checkout, the customer is redirected to a page to log in to PayPal and approve the payment.
|
||||||
|
@ -490,7 +485,7 @@ impl Default for LandingPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shipping preference
|
/// The shipping preference
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum ShippingPreference {
|
pub enum ShippingPreference {
|
||||||
/// Use the customer-provided shipping address on the PayPal site.
|
/// Use the customer-provided shipping address on the PayPal site.
|
||||||
|
@ -508,7 +503,7 @@ impl Default for ShippingPreference {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures a Continue or Pay Now checkout flow.
|
/// Configures a Continue or Pay Now checkout flow.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum UserAction {
|
pub enum UserAction {
|
||||||
/// After you redirect the customer to the PayPal payment page, a Continue button appears. Use this option when
|
/// After you redirect the customer to the PayPal payment page, a Continue button appears. Use this option when
|
||||||
|
@ -528,7 +523,7 @@ impl Default for UserAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The merchant-preferred payment sources.
|
/// The merchant-preferred payment sources.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum PayeePreferred {
|
pub enum PayeePreferred {
|
||||||
/// Accepts any type of payment from the customer.
|
/// Accepts any type of payment from the customer.
|
||||||
|
@ -547,7 +542,7 @@ impl Default for PayeePreferred {
|
||||||
|
|
||||||
/// A payment method.
|
/// A payment method.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct PaymentMethod {
|
pub struct PaymentMethod {
|
||||||
/// The customer-selected payment method on the merchant site.
|
/// The customer-selected payment method on the merchant site.
|
||||||
pub payer_selected: Option<String>,
|
pub payer_selected: Option<String>,
|
||||||
|
@ -557,7 +552,7 @@ pub struct PaymentMethod {
|
||||||
|
|
||||||
/// Customize the payer experience during the approval process for the payment with PayPal.
|
/// Customize the payer experience during the approval process for the payment with PayPal.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct ApplicationContext {
|
pub struct ApplicationContext {
|
||||||
/// The label that overrides the business name in the PayPal account on the PayPal site.
|
/// The label that overrides the business name in the PayPal account on the PayPal site.
|
||||||
pub brand_name: Option<String>,
|
pub brand_name: Option<String>,
|
||||||
|
@ -581,7 +576,8 @@ pub struct ApplicationContext {
|
||||||
|
|
||||||
/// A order payload to be used when creating an order.
|
/// A order payload to be used when creating an order.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)]
|
||||||
|
#[builder(setter(strip_option), default)]
|
||||||
pub struct OrderPayload {
|
pub struct OrderPayload {
|
||||||
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
|
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
|
||||||
pub intent: Intent,
|
pub intent: Intent,
|
||||||
|
@ -721,180 +717,4 @@ pub struct Order {
|
||||||
pub status: OrderStatus,
|
pub status: OrderStatus,
|
||||||
/// An array of request-related HATEOAS links. To complete payer approval, use the approve link to redirect the payer.
|
/// An array of request-related HATEOAS links. To complete payer approval, use the approve link to redirect the payer.
|
||||||
pub links: Vec<LinkDescription>,
|
pub links: Vec<LinkDescription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Client {
|
|
||||||
/// Creates an order. Supports orders with only one purchase unit.
|
|
||||||
pub async fn create_order(
|
|
||||||
&mut self,
|
|
||||||
order: OrderPayload,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<Order, ResponseError> {
|
|
||||||
let builder = {
|
|
||||||
self.setup_headers(
|
|
||||||
self.client.post(&format!("{}/v2/checkout/orders", self.endpoint())),
|
|
||||||
header_params,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
};
|
|
||||||
let res = builder.json(&order).send().await?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let order = res.json::<Order>().await?;
|
|
||||||
Ok(order)
|
|
||||||
} else {
|
|
||||||
Err(ResponseError::ApiError(res.json::<PaypalError>().await?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used internally for order requests that have no body.
|
|
||||||
async fn build_endpoint_order(
|
|
||||||
&mut self,
|
|
||||||
order_id: &str,
|
|
||||||
endpoint: &str,
|
|
||||||
post: bool,
|
|
||||||
header_params: crate::HeaderParams,
|
|
||||||
) -> Result<Order, ResponseError> {
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let res = builder.send().await?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
let order = res.json::<Order>().await?;
|
|
||||||
Ok(order)
|
|
||||||
} else {
|
|
||||||
Err(ResponseError::ApiError(res.json::<PaypalError>().await?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates an order with the CREATED or APPROVED status.
|
|
||||||
/// You cannot update an order with the COMPLETED status.
|
|
||||||
///
|
|
||||||
/// Only replacing the existing purchase units and intent is supported right now.
|
|
||||||
///
|
|
||||||
/// 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(
|
|
||||||
&mut self,
|
|
||||||
id: &str,
|
|
||||||
intent: Option<Intent>,
|
|
||||||
purchase_units: Option<Vec<PurchaseUnit>>,
|
|
||||||
) -> Result<(), ResponseError> {
|
|
||||||
let mut intent_json = String::new();
|
|
||||||
let units_json = String::new();
|
|
||||||
|
|
||||||
if let Some(p_units) = purchase_units {
|
|
||||||
let mut units_json = String::new();
|
|
||||||
|
|
||||||
for (i, unit) in p_units.iter().enumerate() {
|
|
||||||
let unit_str = serde_json::to_string(&unit).expect("error serializing purchase unit");
|
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
if i < p_units.len() - 1 {
|
|
||||||
unit_json += ",";
|
|
||||||
}
|
|
||||||
|
|
||||||
units_json.push_str(&unit_json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = intent {
|
|
||||||
let intent_str = match x {
|
|
||||||
Intent::Authorize => String::from("AUTHORIZE"),
|
|
||||||
Intent::Capture => String::from("CAPTURE"),
|
|
||||||
};
|
|
||||||
|
|
||||||
intent_json = format!(
|
|
||||||
r#"
|
|
||||||
{{
|
|
||||||
"op": "replace",
|
|
||||||
"path": "/intent",
|
|
||||||
"value": "{intent}"
|
|
||||||
}}
|
|
||||||
"#,
|
|
||||||
intent = intent_str
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let final_json = {
|
|
||||||
if !intent_json.is_empty() && !units_json.is_empty() {
|
|
||||||
format!("[{},{}]", intent_json, units_json)
|
|
||||||
} else {
|
|
||||||
format!("[{}{}]", intent_json, units_json)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let builder = {
|
|
||||||
self.setup_headers(
|
|
||||||
self.client
|
|
||||||
.patch(&format!("{}/v2/checkout/orders/{}", self.endpoint(), id)),
|
|
||||||
crate::HeaderParams {
|
|
||||||
content_type: Some(String::from("application/json")),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = builder.body(final_json.clone()).send().await?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ResponseError::ApiError(res.json::<PaypalError>().await?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shows details for an order, by ID.
|
|
||||||
pub async fn show_order_details(&mut self, 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(
|
|
||||||
&mut self,
|
|
||||||
order_id: &str,
|
|
||||||
header_params: crate::HeaderParams,
|
|
||||||
) -> Result<Order, ResponseError> {
|
|
||||||
self.build_endpoint_order(order_id, "capture", true, header_params)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(
|
|
||||||
&mut self,
|
|
||||||
order_id: &str,
|
|
||||||
header_params: HeaderParams,
|
|
||||||
) -> Result<Order, ResponseError> {
|
|
||||||
self.build_endpoint_order(order_id, "authorize", true, header_params)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add strong typed support for order errors in body: https://developer.paypal.com/docs/api/orders/v2/#errors
|
|
31
src/endpoint.rs
Normal file
31
src/endpoint.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
|
use crate::{SANDBOX_ENDPOINT, LIVE_ENDPOINT};
|
||||||
|
|
||||||
|
pub trait Endpoint {
|
||||||
|
type Query: Serialize;
|
||||||
|
type Body: Serialize;
|
||||||
|
type Response: DeserializeOwned;
|
||||||
|
|
||||||
|
// The endpoint relative path. Must start with a `/`
|
||||||
|
fn relative_path(&self) -> Cow<str>;
|
||||||
|
|
||||||
|
// The request method.
|
||||||
|
fn method(&self) -> reqwest::Method;
|
||||||
|
|
||||||
|
fn query(&self) -> Option<&Self::Query> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body(&self) -> Option<&Self::Body> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_path(&self, is_sandbox: bool) -> String {
|
||||||
|
if is_sandbox {
|
||||||
|
format!("{}{}", SANDBOX_ENDPOINT, self.relative_path())
|
||||||
|
} else {
|
||||||
|
format!("{}{}", LIVE_ENDPOINT, self.relative_path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//! Errors created by this crate.
|
//! Errors created by this crate.
|
||||||
use crate::common::LinkDescription;
|
use crate::data::common::LinkDescription;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
312
src/lib.rs
312
src/lib.rs
|
@ -19,10 +19,9 @@
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use paypal_rs::{
|
//! use paypal_rs::{
|
||||||
//! Client,
|
//! Client,
|
||||||
//! HeaderParams,
|
//! api::orders::*,
|
||||||
//! Prefer,
|
//! data::orders::*,
|
||||||
//! orders::{OrderPayload, Intent, PurchaseUnit, Amount},
|
//! data::common::Currency,
|
||||||
//! common::Currency,
|
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! #[tokio::main]
|
//! #[tokio::main]
|
||||||
|
@ -35,18 +34,15 @@
|
||||||
//!
|
//!
|
||||||
//! client.get_access_token().await.unwrap();
|
//! client.get_access_token().await.unwrap();
|
||||||
//!
|
//!
|
||||||
//! let order_payload = OrderPayload::new(
|
//! let order = OrderPayloadBuilder::default()
|
||||||
//! Intent::Authorize,
|
//! .intent(Intent::Authorize)
|
||||||
//! vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))],
|
//! .purchase_units(vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))])
|
||||||
//! );
|
//! .build().unwrap();
|
||||||
//!
|
//!
|
||||||
//! let order = client
|
//! let create_order = CreateOrder::new(order);
|
||||||
//! .create_order(
|
//!
|
||||||
//! order_payload,
|
//! let _order_created = client
|
||||||
//! HeaderParams::default(),
|
//! .execute(create_order).await.unwrap();
|
||||||
//! )
|
|
||||||
//! .await
|
|
||||||
//! .unwrap();
|
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -78,69 +74,24 @@
|
||||||
//! - [ ] Webhooks Management API - 0.14.0
|
//! - [ ] Webhooks Management API - 0.14.0
|
||||||
//! - [ ] Payment Experience Web Profiles API - 1.0.0
|
//! - [ ] Payment Experience Web Profiles API - 1.0.0
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
//#![deny(missing_docs)]
|
||||||
|
|
||||||
pub mod common;
|
pub mod api;
|
||||||
pub mod countries;
|
pub mod countries;
|
||||||
|
pub mod data;
|
||||||
|
pub mod endpoint;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod invoice;
|
pub mod client;
|
||||||
pub mod orders;
|
pub use client::*;
|
||||||
|
|
||||||
use errors::{PaypalError, ResponseError};
|
use derive_builder::Builder;
|
||||||
use reqwest::header;
|
use serde::Serialize;
|
||||||
use reqwest::header::HeaderMap;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::{borrow::Cow, time::{Duration, Instant}};
|
|
||||||
|
|
||||||
/// The paypal api endpoint used on a live application.
|
/// The paypal api endpoint used on a live application.
|
||||||
pub const LIVE_ENDPOINT: &str = "https://api-m.paypal.com";
|
pub const LIVE_ENDPOINT: &str = "https://api-m.paypal.com";
|
||||||
/// The paypal api endpoint used on when testing.
|
/// The paypal api endpoint used on when testing.
|
||||||
pub const SANDBOX_ENDPOINT: &str = "https://api-m.sandbox.paypal.com";
|
pub const SANDBOX_ENDPOINT: &str = "https://api-m.sandbox.paypal.com";
|
||||||
|
|
||||||
/// Represents the access token returned by the OAuth2 authentication.
|
|
||||||
///
|
|
||||||
/// https://developer.paypal.com/docs/api/get-an-access-token-postman/
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct AccessToken {
|
|
||||||
/// The OAuth2 scopes.
|
|
||||||
pub scope: String,
|
|
||||||
/// The access token.
|
|
||||||
pub access_token: String,
|
|
||||||
/// The token type.
|
|
||||||
pub token_type: String,
|
|
||||||
/// The app id.
|
|
||||||
pub app_id: String,
|
|
||||||
/// Seconds until it expires.
|
|
||||||
pub expires_in: u64,
|
|
||||||
/// The nonce.
|
|
||||||
pub nonce: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores OAuth2 information.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Auth {
|
|
||||||
/// Your client id.
|
|
||||||
pub client_id: String,
|
|
||||||
/// The secret.
|
|
||||||
pub secret: String,
|
|
||||||
/// The access token returned by oauth2 authentication.
|
|
||||||
pub access_token: Option<AccessToken>,
|
|
||||||
/// Used to check when the token expires.
|
|
||||||
pub expires: Option<(Instant, Duration)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a client used to interact with the paypal api.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client {
|
|
||||||
/// Internal http client
|
|
||||||
pub(crate) client: reqwest::Client,
|
|
||||||
/// Whether you are or not in a sandbox enviroment.
|
|
||||||
pub sandbox: bool,
|
|
||||||
/// Api Auth information
|
|
||||||
pub auth: Auth,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the query used in most GET api requests.
|
/// Represents the query used in most GET api requests.
|
||||||
///
|
///
|
||||||
/// Reference: https://developer.paypal.com/docs/api/reference/api-requests/#query-parameters
|
/// Reference: https://developer.paypal.com/docs/api/reference/api-requests/#query-parameters
|
||||||
|
@ -151,7 +102,7 @@ pub struct Client {
|
||||||
/// let query = Query { count: Some(40), ..Default::default() };
|
/// let query = Query { count: Some(40), ..Default::default() };
|
||||||
/// ```
|
/// ```
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Debug, Default, Serialize, Builder)]
|
||||||
pub struct Query {
|
pub struct Query {
|
||||||
/// The number of items to list in the response.
|
/// The number of items to list in the response.
|
||||||
pub count: Option<i32>,
|
pub count: Option<i32>,
|
||||||
|
@ -181,7 +132,7 @@ pub struct Query {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The preferred server response upon successful completion of the request.
|
/// The preferred server response upon successful completion of the request.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
pub enum Prefer {
|
pub enum Prefer {
|
||||||
/// The server returns a minimal response to optimize communication between the API caller and the server.
|
/// The server returns a minimal response to optimize communication between the API caller and the server.
|
||||||
/// A minimal response includes the id, status and HATEOAS links.
|
/// A minimal response includes the id, status and HATEOAS links.
|
||||||
|
@ -199,7 +150,7 @@ impl Default for Prefer {
|
||||||
/// Represents the optional header values used on paypal requests.
|
/// Represents the optional header values used on paypal requests.
|
||||||
///
|
///
|
||||||
/// https://developer.paypal.com/docs/api/reference/api-requests/#paypal-auth-assertion
|
/// https://developer.paypal.com/docs/api/reference/api-requests/#paypal-auth-assertion
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Builder, Clone)]
|
||||||
pub struct HeaderParams {
|
pub struct HeaderParams {
|
||||||
/// The merchant payer id used on PayPal-Auth-Assertion
|
/// The merchant payer id used on PayPal-Auth-Assertion
|
||||||
pub merchant_payer_id: Option<String>,
|
pub merchant_payer_id: Option<String>,
|
||||||
|
@ -225,184 +176,15 @@ struct AuthAssertionClaims {
|
||||||
pub payer_id: String,
|
pub payer_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
|
||||||
/// Returns a new client, you must get_access_token afterwards to interact with the api.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use paypal_rs::Client;
|
|
||||||
///
|
|
||||||
/// #[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();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn new(client_id: String, secret: String, sandbox: bool) -> Client {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets up the request headers as required on https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
|
|
||||||
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());
|
|
||||||
|
|
||||||
if let Some(token) = &self.auth.access_token {
|
|
||||||
headers.append(
|
|
||||||
header::AUTHORIZATION,
|
|
||||||
format!("Bearer {}", token.access_token).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.parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(client_metadata_id) = header_params.client_metadata_id {
|
|
||||||
headers.append("PayPal-Client-Metadata-Id", client_metadata_id.parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(partner_attribution_id) = header_params.partner_attribution_id {
|
|
||||||
headers.append("PayPal-Partner-Attribution-Id", partner_attribution_id.parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(request_id) = header_params.request_id {
|
|
||||||
headers.append("PayPal-Request-Id", request_id.parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
match header_params.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.parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.headers(headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a access token used in all the api calls.
|
|
||||||
pub async fn get_access_token(&mut self) -> Result<(), ResponseError> {
|
|
||||||
if !self.access_token_expired() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
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
|
|
||||||
.map_err(ResponseError::HttpError)?;
|
|
||||||
|
|
||||||
if res.status().is_success() {
|
|
||||||
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(ResponseError::ApiError(
|
|
||||||
res.json::<PaypalError>().await.map_err(ResponseError::HttpError)?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the access token expired.
|
|
||||||
pub fn access_token_expired(&self) -> bool {
|
|
||||||
if let Some(expires) = self.auth.expires {
|
|
||||||
expires.0.elapsed() >= expires.1
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait FromResponse: Sized {
|
|
||||||
type Response;
|
|
||||||
|
|
||||||
fn from_response(res: Self::Response) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait Endpoint {
|
|
||||||
type Query: Serialize;
|
|
||||||
type Body: Serialize;
|
|
||||||
type Response: FromResponse;
|
|
||||||
|
|
||||||
fn path(&self) -> Cow<str>;
|
|
||||||
|
|
||||||
fn method(&self) -> reqwest::Method {
|
|
||||||
reqwest::Method::GET
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query(&self) -> Option<&Self::Query> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn body(&self) -> Option<&Self::Body> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::common::Currency;
|
use crate::{countries::Country};
|
||||||
use crate::countries::Country;
|
use crate::data::common::Currency;
|
||||||
use crate::{orders::*, Client, HeaderParams};
|
use crate::{Client};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
async fn create_client() -> Client {
|
pub async fn create_client() -> Client {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
let clientid = env::var("PAYPAL_CLIENTID").unwrap();
|
let clientid = env::var("PAYPAL_CLIENTID").unwrap();
|
||||||
let secret = env::var("PAYPAL_SECRET").unwrap();
|
let secret = env::var("PAYPAL_SECRET").unwrap();
|
||||||
|
@ -410,48 +192,6 @@ mod tests {
|
||||||
Client::new(clientid, secret, true)
|
Client::new(clientid, secret, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
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 ref_id = format!(
|
|
||||||
"TEST-{:?}",
|
|
||||||
std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs()
|
|
||||||
);
|
|
||||||
|
|
||||||
let order_created = client
|
|
||||||
.create_order(
|
|
||||||
order,
|
|
||||||
HeaderParams {
|
|
||||||
request_id: Some(ref_id.clone()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_ne!(order_created.id, "");
|
|
||||||
assert_eq!(order_created.status, OrderStatus::Created);
|
|
||||||
assert_eq!(order_created.links.len(), 4);
|
|
||||||
|
|
||||||
client
|
|
||||||
.update_order(
|
|
||||||
&order_created.id,
|
|
||||||
Some(Intent::Capture),
|
|
||||||
Some(order_created.purchase_units.expect("to exist")),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_currency() {
|
fn test_currency() {
|
||||||
assert_eq!(Currency::EUR.to_string(), "EUR");
|
assert_eq!(Currency::EUR.to_string(), "EUR");
|
||||||
|
|
Loading…
Reference in a new issue