From 997b129a38319dff2d3f50fbb01230f61b163842 Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Tue, 26 Apr 2022 09:25:39 +0200 Subject: [PATCH] various fixes and lot of builders --- Cargo.toml | 9 +++-- examples/invoice.rs | 93 ++++++++++++++------------------------------- src/api/invoice.rs | 35 ++++++++++++++--- src/api/orders.rs | 2 - src/client.rs | 6 +++ src/data/common.rs | 3 +- src/data/invoice.rs | 90 +++++++++++++++++++++++++++++++++---------- src/data/orders.rs | 2 +- 8 files changed, 141 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d2f2c68..458e231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,16 +16,17 @@ edition = "2021" reqwest = { version = "0.11.10", features = ["json"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" -serde_with = "1.12.1" +serde_with = "1.13.0" chrono = { version = "0.4.19", features = ["serde"] } -jsonwebtoken = "8.0.1" +jsonwebtoken = "8.1.0" base64 = "0.13.0" log = "0.4.16" bytes = "1.1.0" -derive_builder = "0.11.1" +derive_builder = "0.11.2" serde_qs = "0.9.1" [dev-dependencies] tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } dotenv = "0.15.0" -anyhow = "1.0.56" +anyhow = "1.0.57" +color-eyre = "0.6.1" diff --git a/examples/invoice.rs b/examples/invoice.rs index 47b1c73..7ae1bfd 100644 --- a/examples/invoice.rs +++ b/examples/invoice.rs @@ -1,76 +1,41 @@ -/* -use paypal_rs::{common::*, errors::*, invoice::*, Client, HeaderParams}; +use color_eyre::Result; +use paypal_rs::data::invoice::*; +use paypal_rs::{api::invoice::*, data::common::Money}; +use paypal_rs::{data::common::Currency, Client}; #[tokio::main] -async fn main() -> Result<(), ResponseError> { +async fn main() -> Result<()> { + color_eyre::install()?; dotenv::dotenv().ok(); - let clientid = std::env::var("PAYPAL_CLIENTID").unwrap(); - let secret = std::env::var("PAYPAL_SECRET").unwrap(); + let clientid = std::env::var("PAYPAL_CLIENTID")?; + let secret = std::env::var("PAYPAL_SECRET")?; let mut client = Client::new(clientid, secret, true); + client.get_access_token().await?; - let payload = InvoicePayload { - detail: InvoiceDetail { - currency_code: Currency::EUR, - //reference: Some("deal-ref".to_owned()), - ..Default::default() - }, - invoicer: Some(InvoicerInfo { - name: Some(Name { - given_name: Some("Lucas".to_owned()), - prefix: None, - suffix: None, - surname: None, - full_name: None, - middle_name: None, - alternate_full_name: None, - }), - phones: None, - tax_id: None, - website: None, - business_name: "Lucas Corp".to_owned(), - logo_url: None, - // needs to be a valid address... - email_address: Some("merchant@example.com".to_owned()), - additional_notes: None, - }), - items: vec![Item { - id: None, - name: "My item".to_owned(), - unit_amount: Money { + let payload = InvoicePayloadBuilder::default() + .detail(InvoiceDetailBuilder::default().currency_code(Currency::EUR).build()?) + .invoicer( + InvoicerInfoBuilder::default() + .name(NameBuilder::default().full_name("Test Person").build()?) + .build()?, + ) + .items(vec![ItemBuilder::default() + .name("Some name") + .unit_amount(Money { currency_code: Currency::EUR, - value: "10.0".to_owned(), - }, - quantity: "1".to_owned(), - discount: None, - item_date: None, - description: Some("A random item".to_owned()), - tax: Some(Tax { - name: "Sales tax".to_owned(), - percent: "7".to_owned(), - amount: None, - }), - unit_of_measure: Some(UnitOfMeasure::Quantity), - }], - ..Default::default() - }; - match client.create_draft_invoice(payload, HeaderParams::default()).await { - Ok(r) => { - println!("{:#?}", r); - } - Err(ResponseError::HttpError(e)) => { - println!("{}", e); - } - Err(e) => { - println!("{:#?}", e); - } - } + value: "10.0".to_string(), + }) + .quantity("1") + .build()?]) + .build()?; - // some stuff is not sent when representation is minimal. + let invoice = CreateDraftInvoice::new(payload); + + let res = client.execute(invoice).await?; + + println!("{:#?}", res); Ok(()) } -*/ - -fn main() {} diff --git a/src/api/invoice.rs b/src/api/invoice.rs index f15db9c..03ac35f 100644 --- a/src/api/invoice.rs +++ b/src/api/invoice.rs @@ -341,9 +341,13 @@ impl super::Client { // TODO: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_payments-delete } +*/ #[cfg(test)] mod tests { + use super::*; + use crate::data::common::*; + use crate::data::invoice::*; use crate::{Client, HeaderParams}; async fn create_client() -> Client { @@ -351,16 +355,35 @@ mod tests { let clientid = std::env::var("PAYPAL_CLIENTID").unwrap(); let secret = std::env::var("PAYPAL_SECRET").unwrap(); - Client::new(clientid, secret, true) + let mut client = Client::new(clientid, secret, true); + client.get_access_token().await.unwrap(); + client } #[tokio::test] - async fn test_invoice() -> anyhow::Result<()> { - let mut client = create_client().await; + async fn test_invoice_creates() -> anyhow::Result<()> { + let client = create_client().await; - let _list = client.list_invoices(1, 10, HeaderParams::default()).await?; + let payload = InvoicePayloadBuilder::default() + .detail(InvoiceDetailBuilder::default().currency_code(Currency::EUR).build()?) + .invoicer( + InvoicerInfoBuilder::default() + .name(NameBuilder::default().full_name("Test Person").build()?) + .build()?, + ) + .items(vec![ItemBuilder::default() + .name("Some name") + .unit_amount(Money { + currency_code: Currency::EUR, + value: "10.0".to_string(), + }) + .quantity("1") + .build()?]) + .build()?; + + let invoice = CreateDraftInvoice::new(payload); + + client.execute(invoice).await?; Ok(()) } } - -*/ diff --git a/src/api/orders.rs b/src/api/orders.rs index 1eb80cb..7253adf 100644 --- a/src/api/orders.rs +++ b/src/api/orders.rs @@ -46,8 +46,6 @@ impl Endpoint for CreateOrder { } } -// TODO: Update order. - /// Query an order by id. #[derive(Debug)] pub struct ShowOrderDetails { diff --git a/src/client.rs b/src/client.rs index 8fcf82c..4fb4836 100644 --- a/src/client.rs +++ b/src/client.rs @@ -210,6 +210,12 @@ impl Client { let res = request.send().await?; if res.status().is_success() { + // code to debug responses when parse fails. + //let resp_text = res.text().await?; + //dbg!(&resp_text); + //let mut f = std::fs::File::create("output.txt").unwrap(); + //f.write_all(resp_text.as_bytes()).ok(); + //let response_body: E::Response = serde_json::from_str(&resp_text).unwrap(); let response_body = res.json::().await?; Ok(response_body) } else { diff --git a/src/data/common.rs b/src/data/common.rs index 48ad819..cf99c5b 100644 --- a/src/data/common.rs +++ b/src/data/common.rs @@ -1,6 +1,7 @@ //! Common paypal object definitions used by 2 or more APIs use crate::errors::InvalidCurrencyError; +use derive_builder::Builder; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use std::str::FromStr; @@ -61,7 +62,7 @@ pub struct Address { } /// Represents money -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Builder)] pub struct Money { /// 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, diff --git a/src/data/invoice.rs b/src/data/invoice.rs index 26624f8..eefeb1f 100644 --- a/src/data/invoice.rs +++ b/src/data/invoice.rs @@ -98,7 +98,8 @@ pub struct Metadata { /// The details of the invoice. Includes the invoice number, date, payment terms, and audit metadata. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder)] +#[builder(setter(strip_option), default)] pub struct InvoiceDetail { /// The reference data. Includes a post office (PO) number. pub reference: Option, @@ -124,7 +125,8 @@ pub struct InvoiceDetail { /// A name to be used as recipient, etc. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct Name { /// The prefix, or title, to the party's name. pub prefix: Option, @@ -161,10 +163,11 @@ pub struct PhoneDetail { /// The invoicer information. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder)] +#[builder(setter(strip_option), default)] pub struct InvoicerInfo { /// Required. The business name of the party. - pub business_name: String, + pub business_name: Option, /// The first and Last name of the recipient. pub name: Option, /// The invoicer email address, which must be listed in the user's PayPal profile. @@ -237,12 +240,13 @@ pub struct Tax { } /// Discount information -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder, Default)] +#[builder(setter(strip_option, into), default)] pub struct Discount { /// The discount as a percentage value. Value is from 0 to 100. Supports up to five decimal places. pub percent: Option, /// The invoice level discount amount. Value is from 0 to 1000000. Supports up to two decimal places. - pub amount: Option, + pub amount: Option>, } /// The unit of measure for the invoiced item. @@ -259,32 +263,40 @@ pub enum UnitOfMeasure { /// Item information #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct Item { /// The ID of the invoice line item. /// Read only. + #[builder(default)] pub id: Option, /// The item name for the invoice line item. pub name: String, /// The item description for the invoice line item. + #[builder(default)] pub description: Option, /// The quantity of the item that the invoicer provides to the payer. Value is from -1000000 to 1000000. Supports up to five decimal places. pub quantity: String, /// The unit price of the item. This does not include tax and discount. Value is from -1000000 to 1000000. Supports up to two decimal places. pub unit_amount: Money, /// The tax associated with the item. The tax amount is added to the item total. Value is from 0 to 100. Supports up to five decimal places. + #[builder(default)] pub tax: Option, /// The date when the item or service was provided, in Internet date and time format. + #[builder(default)] pub item_date: Option>, /// Discount as a percent or amount at invoice level. The invoice discount amount is subtracted from the item total. + #[builder(default)] pub discount: Option, /// The unit of measure for the invoiced item. For AMOUNT the unit_amount and quantity are not shown on the invoice. + #[builder(default)] pub unit_of_measure: Option, } /// The partial payment details. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Default, Builder)] +#[builder(setter(strip_option, into), default)] pub struct PartialPayment { /// Indicates whether the invoice allows a partial payment. If false, the invoice must be paid in full. If true, the invoice allows partial payments. pub allow_partial_payment: Option, @@ -294,7 +306,8 @@ pub struct PartialPayment { /// The invoice configuration details. Includes partial payment, tip, and tax calculated after discount. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Default, Builder)] +#[builder(setter(strip_option, into), default)] pub struct Configuration { /// Indicates whether the tax is calculated before or after a discount. If false, the tax is calculated before a discount. If true, the tax is calculated after a discount. pub tax_calculated_after_discount: Option, @@ -311,7 +324,8 @@ pub struct Configuration { /// The discount #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct AggregatedDiscount { /// The discount as a percent or amount at invoice level. The invoice discount amount is subtracted from the item total. pub invoice_discount: Option, @@ -320,7 +334,8 @@ pub struct AggregatedDiscount { } /// The shipping fee -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct ShippingCost { /// The shipping amount. Value is from 0 to 1000000. Supports up to two decimal places. pub amount: Option, @@ -330,17 +345,20 @@ pub struct ShippingCost { /// The custom amount to apply to an invoice #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct CustomAmount { /// The label to the custom amount of the invoice. pub label: String, /// The custom amount value. Value is from -1000000 to 1000000. Supports up to two decimal places. + #[builder(default)] pub amount: Option, } /// The breakdown of the amount. Breakdown provides details such as total item amount, total tax amount, custom amount, shipping and discounts, if any. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct Breakdown { /// The subtotal for all items. Must equal the sum of (items[].unit_amount * items[].quantity) for all items. pub item_total: Option, @@ -356,7 +374,8 @@ pub struct Breakdown { /// Represents an amount of money. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct Amount { /// 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, @@ -367,6 +386,7 @@ pub struct Amount { /// For the required number of decimal places for a currency code, see [Currency Codes](https://developer.paypal.com/docs/api/reference/currency-codes/). pub value: String, /// The breakdown of the amount. Breakdown provides details such as total item amount, total tax amount, custom amount, shipping and discounts, if any. + #[builder(default)] pub breakdown: Option, } @@ -421,27 +441,35 @@ impl Default for PaymentMethod { /// Payment detail #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct PaymentDetail { /// The payment type in an invoicing flow which can be PayPal or an external cash or check payment. + #[builder(default)] pub r#type: Option, /// The ID for a PayPal payment transaction. Required for the PAYPAL payment type. + #[builder(default)] pub payment_id: Option, /// The date when the invoice was paid, in Internet date and time format. + #[builder(default)] pub payment_date: Option>, /// The payment mode or method through which the invoicer can accept the payment. pub method: PaymentMethod, /// A note associated with an external cash or check payment. + #[builder(default)] pub note: Option, /// The payment amount to record against the invoice. If you omit this parameter, the total invoice amount is marked as paid. This amount cannot exceed the amount due. + #[builder(default)] pub amount: Option, /// The recipient's shipping information. Includes the user's contact information, which includes name and address. + #[builder(default)] pub shipping_info: Option, } /// Payments registered against the invoice #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct Payments { /// The aggregated payment amounts against this invoice. /// Read only. @@ -453,15 +481,20 @@ pub struct Payments { /// Refund details #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct RefundDetail { /// The PayPal refund type. Indicates whether the refund was paid through PayPal or externally in the invoicing flow. + #[builder(default)] pub r#type: Option, /// The ID for a PayPal payment transaction. Required for the PAYPAL payment type. + #[builder(default)] pub refund_id: Option, /// The date when the invoice was refunded, in Internet date format. + #[builder(default)] pub refund_date: Option>, /// The amount to record as refunded. If you omit the amount, the total invoice paid amount is recorded as refunded. + #[builder(default)] pub amount: Option, /// The payment mode or method through which the invoicer can accept the payments. pub method: PaymentMethod, @@ -469,7 +502,8 @@ pub struct RefundDetail { /// List of refunds #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct Refunds { /// The aggregated refund amounts. /// Read only. @@ -511,7 +545,8 @@ pub enum Status { /// An invoice payload #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder)] +#[builder(setter(strip_option), default)] pub struct InvoicePayload { /// The details of the invoice. Includes the invoice number, date, payment terms, and audit metadata. pub detail: InvoiceDetail, @@ -536,43 +571,56 @@ pub struct InvoicePayload { /// Definition: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_get #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct Invoice { /// The ID of the invoice. pub id: String, /// The parent ID to an invoice that defines the group invoice to which the invoice is related. + #[builder(default)] pub parent_id: Option, /// The status of the invoice. pub status: Status, /// The details of the invoice. Includes the invoice number, date, payment terms, and audit metadata. pub detail: InvoiceDetail, /// The invoicer information. Includes the business name, email, address, phone, fax, tax ID, additional notes, and logo URL. + #[builder(default)] pub invoicer: Option, /// The billing and shipping information. Includes name, email, address, phone and language. + #[builder(default)] pub primary_recipients: Option>, /// An array of one or more CC: emails to which notifications are sent. /// If you omit this parameter, a notification is sent to all CC: email addresses that are part of the invoice. + #[builder(default)] pub additional_recipients: Option>, /// An array of invoice line item information. + #[builder(default)] pub items: Option>, /// The invoice configuration details. Includes partial payment, tip, and tax calculated after discount. + #[builder(default)] pub configuration: Option, /// The invoice amount summary of item total, discount, tax total and shipping.. pub amount: Amount, /// The due amount, which is the balance amount outstanding after payments. + #[builder(default)] pub due_amount: Option, /// The amount paid by the payer as gratuity to the invoicer. + #[builder(default)] pub gratuity: Option, /// List of payments registered against the invoice.. + #[builder(default)] pub payments: Option, /// List of refunds against this invoice. The invoicing refund details includes refund type, date, amount, and method. + #[builder(default)] pub refunds: Option, /// An array of request-related HATEOAS links. + #[builder(default)] pub links: Option>, } /// A invoice list -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(into))] pub struct InvoiceList { /// Total items pub total_items: i32, diff --git a/src/data/orders.rs b/src/data/orders.rs index 151205e..8c88a4d 100644 --- a/src/data/orders.rs +++ b/src/data/orders.rs @@ -82,7 +82,7 @@ pub struct TaxInfo { /// /// https://developer.paypal.com/docs/api/orders/v2/#definition-payer #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] pub struct Payer { /// The name of the payer. pub name: Option,