create_order

This commit is contained in:
Edgar 2020-06-09 18:23:46 +02:00
parent 5d5c7d0cfa
commit 42e5396ad0
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
6 changed files with 514 additions and 72 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "paypal-rs" name = "paypal-rs"
version = "0.0.3" version = "0.0.4"
authors = ["Edgar <git@edgarluque.com>"] authors = ["Edgar <git@edgarluque.com>"]
description = "A library that wraps the paypal api asynchronously." description = "A library that wraps the paypal api asynchronously."
repository = "https://github.com/edg-l/paypal-rs/" repository = "https://github.com/edg-l/paypal-rs/"
@ -20,4 +20,5 @@ jsonwebtoken = "7"
base64 = "0.12" base64 = "0.12"
log = "0.4" log = "0.4"
dotenv = "0.15.0" dotenv = "0.15.0"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
thiserror = "1.0"

View file

@ -1,5 +1,6 @@
# paypal-rs # paypal-rs
![Rust](https://github.com/edg-l/paypal-rs/workflows/Rust/badge.svg) ![Rust](https://github.com/edg-l/paypal-rs/workflows/Rust/badge.svg)
![Docs](https://docs.rs/paypal-rs/badge.svg)
A rust library that wraps the [paypal api](https://developer.paypal.com/docs/api) asynchronously. A rust library that wraps the [paypal api](https://developer.paypal.com/docs/api) asynchronously.

View file

@ -1,13 +1,9 @@
use std::fmt; use thiserror::Error;
use std::error::Error;
#[derive(Debug, Clone)] #[derive(Debug, Error)]
pub struct GetAccessTokenError; pub enum Errors {
#[error("failed to get access token")]
impl fmt::Display for GetAccessTokenError { GetAccessTokenFailure,
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[error("failure when calling the paypal api")]
write!(f, "error getting access token") ApiCallFailure,
} }
}
impl Error for GetAccessTokenError {}

View file

@ -14,32 +14,46 @@ use reqwest::header;
pub const LIVE_ENDPOINT: &str = "https://api.paypal.com"; pub const LIVE_ENDPOINT: &str = "https://api.paypal.com";
pub const SANDBOX_ENDPOINT: &str = "https://api.sandbox.paypal.com"; pub const SANDBOX_ENDPOINT: &str = "https://api.sandbox.paypal.com";
/// Represents the access token returned by the oauth2 authentication. /// Represents the access token returned by the OAuth2 authentication.
/// ///
/// https://developer.paypal.com/docs/api/get-an-access-token-postman/ /// https://developer.paypal.com/docs/api/get-an-access-token-postman/
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct AccessToken { pub struct AccessToken {
/// The OAuth2 scopes.
pub scope: String, pub scope: String,
/// The access token.
pub access_token: String, pub access_token: String,
/// The token type.
pub token_type: String, pub token_type: String,
/// The app id.
pub app_id: String, pub app_id: String,
/// Seconds until it expires.
pub expires_in: u64, pub expires_in: u64,
/// The nonce.
pub nonce: String, pub nonce: String,
} }
/// Stores OAuth2 information.
#[derive(Debug)] #[derive(Debug)]
pub struct Auth { pub struct Auth {
/// Your client id.
pub client_id: String, pub client_id: String,
/// The secret.
pub secret: String, pub secret: String,
/// The access token returned by oauth2 authentication.
pub access_token: Option<AccessToken>, pub access_token: Option<AccessToken>,
/// Used to check when the token expires.
pub expires: Option<(Instant, Duration)>, pub expires: Option<(Instant, Duration)>,
} }
/// Represents a client used to interact with the paypal api. /// Represents a client used to interact with the paypal api.
#[derive(Debug)] #[derive(Debug)]
pub struct Client { pub struct Client {
/// Internal http client
pub client: reqwest::Client, pub client: reqwest::Client,
/// Whether you are or not in a sandbox enviroment.
pub sandbox: bool, pub sandbox: bool,
/// Api Auth information
pub auth: Auth, pub auth: Auth,
} }
@ -51,26 +65,60 @@ pub struct Client {
/// ``` /// ```
/// let query = Query { count: Some(40), ..Default::default() }; /// let query = Query { count: Some(40), ..Default::default() };
/// ``` /// ```
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize)]
pub struct Query { pub struct Query {
/// The number of items to list in the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i32>, pub count: Option<i32>,
/// The end date and time for the range to show in the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<chrono::DateTime<chrono::Utc>>, pub end_time: Option<chrono::DateTime<chrono::Utc>>,
/// The page number indicating which set of items will be returned in the response.
/// So, the combination of page=1 and page_size=20 returns the first 20 items.
/// The combination of page=2 and page_size=20 returns items 21 through 40.
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<i32>, pub page: Option<i32>,
/// The number of items to return in the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<i32>, pub page_size: Option<i32>,
/// Indicates whether to show the total count in the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub total_count_required: Option<bool>, pub total_count_required: Option<bool>,
/// Sorts the payments in the response by a specified value, such as the create time or update time.
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_by: Option<String>, pub sort_by: Option<String>,
/// Sorts the items in the response in ascending or descending order.
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_order: Option<String>, pub sort_order: Option<String>,
/// The ID of the starting resource in the response.
/// When results are paged, you can use the next_id value as the start_id to continue with the next set of results.
#[serde(skip_serializing_if = "Option::is_none")]
pub start_id: Option<String>, pub start_id: Option<String>,
/// The start index of the payments to list. Typically, you use the start_index to jump to a specific position in the resource history based on its cart.
/// For example, to start at the second item in a list of results, specify start_index=2.
#[serde(skip_serializing_if = "Option::is_none")]
pub start_index: Option<i32>, pub start_index: Option<i32>,
/// The start date and time for the range to show in the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<chrono::DateTime<chrono::Utc>>, pub start_time: Option<chrono::DateTime<chrono::Utc>>,
// TODO: Use https://github.com/samscott89/serde_qs
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Prefer { pub enum Prefer {
/// 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.
Minimal, Minimal,
/// The server returns a complete resource representation, including the current state of the resource.
Representation, Representation,
} }
impl Default for Prefer {
fn default() -> Self {
Prefer::Minimal
}
}
/// 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
@ -96,21 +144,15 @@ impl Client {
/// Example: /// Example:
/// ///
/// ``` /// ```
/// #[tokio::main] /// let clientid = env::var("PAYPAL_CLIENTID").unwrap();
/// async fn main() { /// let secret = env::var("PAYPAL_SECRET").unwrap();
/// dotenv().ok();
/// let clientid = env::var("PAYPAL_CLIENTID").unwrap();
/// let secret = env::var("PAYPAL_SECRET").unwrap();
/// ///
/// let mut client = Client::new( /// let mut client = Client::new(
/// clientid.as_str(), /// clientid.as_str(),
/// secret.as_str(), /// secret.as_str(),
/// true, /// true,
/// ); /// );
/// /// client.get_access_token().await.unwrap();
/// client.get_access_token().await.unwrap();
/// println!("{:#?}", client);
/// }
/// ``` /// ```
pub fn new<S: Into<String>>(client_id: S, secret: S, sandbox: bool) -> Client { pub fn new<S: Into<String>>(client_id: S, secret: S, sandbox: bool) -> Client {
Client { Client {
@ -199,11 +241,8 @@ impl Client {
let token = res.json::<AccessToken>().await?; let token = res.json::<AccessToken>().await?;
self.auth.expires = Some((Instant::now(), Duration::new(token.expires_in, 0))); self.auth.expires = Some((Instant::now(), Duration::new(token.expires_in, 0)));
self.auth.access_token = Some(token); self.auth.access_token = Some(token);
println!("{:#?}", self.auth);
} else { } else {
println!("status = {:#?}", res.status()); return Err(Box::new(errors::Errors::GetAccessTokenFailure));
println!("res = {:#?}", res);
return Err(Box::new(errors::GetAccessTokenError));
} }
Ok(()) Ok(())

View file

@ -1,5 +1,9 @@
use serde::{Serialize, Deserialize};
use crate::errors;
/// 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)] #[derive(Debug, Serialize, Deserialize)]
#[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.
Capture, Capture,
@ -20,7 +24,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)] #[derive(Debug, Serialize, Deserialize)]
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,
@ -32,7 +36,8 @@ pub struct PayerName {
/// 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)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PhoneType { pub enum PhoneType {
Fax, Fax,
Home, Home,
@ -41,7 +46,7 @@ pub enum PhoneType {
Pager, Pager,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
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.
@ -51,13 +56,15 @@ 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.
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
pub struct Phone { pub struct Phone {
#[serde(skip_serializing_if = "Option::is_none")]
pub phone_type: Option<PhoneType>, pub phone_type: Option<PhoneType>,
pub phone_number: PhoneNumber, pub phone_number: PhoneNumber,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum TaxIdType { pub enum TaxIdType {
/// The individual tax ID type. /// The individual tax ID type.
@ -66,7 +73,7 @@ pub enum TaxIdType {
BR_CNPJ, BR_CNPJ,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
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.
@ -75,19 +82,24 @@ pub struct TaxInfo {
pub tax_id_type: TaxIdType, pub tax_id_type: TaxIdType,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub address_line_1: Option<String>, pub address_line_1: Option<String>,
/// The second line of the address. For example, suite or apartment number. /// The second line of the address. For example, suite or apartment number.
#[serde(skip_serializing_if = "Option::is_none")]
pub address_line_2: Option<String>, pub address_line_2: Option<String>,
/// A city, town, or village. Smaller than admin_area_level_1. /// A city, town, or village. Smaller than admin_area_level_1.
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_area_2: Option<String>, pub admin_area_2: Option<String>,
/// The highest level sub-division in a country, which is usually a province, state, or ISO-3166-2 subdivision. /// The highest level sub-division in a country, which is usually a province, state, or ISO-3166-2 subdivision.
/// Format for postal delivery. For example, CA and not California. /// Format for postal delivery. For example, CA and not California.
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_area_1: Option<String>, pub admin_area_1: Option<String>,
/// The postal code, which is the zip code or equivalent. Typically required for countries with a postal code or an equivalent. /// The postal code, which is the zip code or equivalent. Typically required for countries with a postal code or an equivalent.
#[serde(skip_serializing_if = "Option::is_none")]
pub postal_code: Option<String>, pub postal_code: Option<String>,
/// The two-character [ISO 3166-1](https://developer.paypal.com/docs/api/reference/country-codes/) code that identifies the country or region. /// The two-character [ISO 3166-1](https://developer.paypal.com/docs/api/reference/country-codes/) code that identifies the country or region.
pub country_code: String, pub country_code: String,
@ -96,26 +108,33 @@ pub struct Address {
/// The customer who approves and pays for the order. The customer is also known as the payer. /// The customer who approves and pays for the order. The customer is also known as the payer.
/// ///
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer /// https://developer.paypal.com/docs/api/orders/v2/#definition-payer
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct Payer { pub struct Payer {
/// The name of the payer. /// The name of the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<PayerName>, pub name: Option<PayerName>,
/// The email address of the payer. /// The email address of the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub email_address: Option<String>, pub email_address: Option<String>,
/// The PayPal-assigned ID for the payer. /// The PayPal-assigned ID for the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_id: Option<String>, pub payer_id: Option<String>,
/// The phone number of the customer. Available only when you enable the Contact /// 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. /// Telephone Number option in the Profile & Settings for the merchant's PayPal account.
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<Phone>, pub phone: Option<Phone>,
/// The birth date of the payer in YYYY-MM-DD format. /// The birth date of the payer in YYYY-MM-DD format.
#[serde(skip_serializing_if = "Option::is_none")]
pub birth_date: Option<String>, pub birth_date: Option<String>,
/// The tax information of the payer. Required only for Brazilian payer's. /// The tax information of the payer. Required only for Brazilian payer's.
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_info: Option<TaxInfo>, pub tax_info: Option<TaxInfo>,
/// The address of the payer. /// The address of the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<Address>, pub address: Option<Address>,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
pub struct Money { pub struct Money {
/// 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: String, pub currency_code: String,
@ -128,26 +147,33 @@ pub struct Money {
} }
/// 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.
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub item_total: Option<Money>, pub item_total: Option<Money>,
/// The shipping fee for all items within a given purchase_unit. /// The shipping fee for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping: Option<Money>, pub shipping: Option<Money>,
/// The handling fee for all items within a given purchase_unit. /// The handling fee for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub handling: Option<Money>, pub handling: Option<Money>,
/// The total tax for all items. Required if the request includes purchase_units.items.tax. Must equal the sum of (items[].tax * items[].quantity) for all items. /// The total tax for all items. Required if the request includes purchase_units.items.tax. Must equal the sum of (items[].tax * items[].quantity) for all items.
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_total: Option<Money>, pub tax_total: Option<Money>,
/// The insurance fee for all items within a given purchase_unit. /// The insurance fee for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub insurance: Option<Money>, pub insurance: Option<Money>,
/// The shipping discount for all items within a given purchase_unit. /// The shipping discount for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_discount: Option<Money>, pub shipping_discount: Option<Money>,
/// The discount for all items within a given purchase_unit. /// The discount for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub discount: Option<Money>, pub discount: Option<Money>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
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: String, pub currency_code: String,
@ -158,24 +184,39 @@ 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/). /// 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, pub value: String,
/// The breakdown of the amount. /// The breakdown of the amount.
#[serde(skip_serializing_if = "Option::is_none")]
pub breakdown: Option<Breakdown>, pub breakdown: Option<Breakdown>,
} }
#[derive(Debug, Default)] impl Amount {
/// Creates a new amount with the required values.
pub fn new<S: Into<String>>(currency_code: S, value: S) -> Self {
Amount {
currency_code: currency_code.into(),
value: value.into(),
breakdown: None,
}
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Payee { pub struct Payee {
/// The email address of merchant. /// The email address of merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub email_address: Option<String>, pub email_address: Option<String>,
/// The encrypted PayPal account ID of the merchant. /// The encrypted PayPal account ID of the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub merchant_id: Option<String>, pub merchant_id: Option<String>,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
pub struct PlatformFee { pub struct PlatformFee {
pub amount: Money, pub amount: Money,
#[serde(skip_serializing_if = "Option::is_none")]
pub payee: Option<Payee>, pub payee: Option<Payee>,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
pub enum DisbursementMode { pub enum DisbursementMode {
/// The funds are released to the merchant immediately. /// The funds are released to the merchant immediately.
Instant, Instant,
@ -191,15 +232,18 @@ impl Default for DisbursementMode {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaymentInstruction { pub struct PaymentInstruction {
/// An array of various fees, commissions, tips, or donations. /// An array of various fees, commissions, tips, or donations.
#[serde(skip_serializing_if = "Option::is_none")]
pub platform_fees: Option<Vec<PlatformFee>>, pub platform_fees: Option<Vec<PlatformFee>>,
/// The funds that are held on behalf of the merchant. /// The funds that are held on behalf of the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub disbursement_mode: Option<DisbursementMode> pub disbursement_mode: Option<DisbursementMode>
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
#[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.
/// This value is not currently supported for API callers that leverage /// This value is not currently supported for API callers that leverage
@ -215,13 +259,15 @@ impl Default for ItemCategoryType {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct ShippingDetail { pub struct ShippingDetail {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<Address>, pub address: Option<Address>,
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
pub struct Item { pub struct Item {
/// The item name or title. /// The item name or title.
pub name: String, pub name: String,
@ -229,21 +275,153 @@ pub struct Item {
/// If you specify unit_amount, purchase_units[].amount.breakdown.item_total is required. Must equal unit_amount * quantity for all items. /// If you specify unit_amount, purchase_units[].amount.breakdown.item_total is required. Must equal unit_amount * quantity for all items.
pub unit_amount: Money, pub unit_amount: Money,
/// The item tax for each unit. If tax is specified, purchase_units[].amount.breakdown.tax_total is required. Must equal tax * quantity for all items. /// The item tax for each unit. If tax is specified, purchase_units[].amount.breakdown.tax_total is required. Must equal tax * quantity for all items.
#[serde(skip_serializing_if = "Option::is_none")]
pub tax: Option<Money>, pub tax: Option<Money>,
/// The item quantity. Must be a whole number. /// The item quantity. Must be a whole number.
pub quantity: String, pub quantity: String,
/// The detailed item description. /// The detailed item description.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
/// The stock keeping unit (SKU) for the item. /// The stock keeping unit (SKU) for the item.
#[serde(skip_serializing_if = "Option::is_none")]
pub sku: Option<String>, pub sku: Option<String>,
/// The item category type /// The item category type
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<ItemCategoryType>, pub category: Option<ItemCategoryType>,
} }
#[derive(Debug, Default)] #[derive(Debug, Serialize, Deserialize)]
pub struct PurchaseUnitRequest { #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AuthorizationStatus {
/// The authorized payment is created. No captured payments have been made for this authorized payment.
Created,
/// The authorized payment has one or more captures against it. The sum of these captured payments is greater than the amount of the original authorized payment.
Captured,
/// PayPal cannot authorize funds for this authorized payment.
Denied,
/// The authorized payment has expired.
Expired,
/// A captured payment was made for the authorized payment for an amount that is less than the amount of the original authorized payment.
PartiallyExpired,
/// The payment which was authorized for an amount that is less than the originally requested amount.
PartiallyCaptured,
/// The authorized payment was voided. No more captured payments can be made against this authorized payment.
Voided,
/// The created authorization is in pending state. For more information, see status.details.
Pending,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AuthorizationStatusDetails {
/// Authorization is pending manual review.
PendingReview,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AuthorizationWithData {
/// The status for the authorized payment.
status: AuthorizationStatus,
/// The details of the authorized order pending status.
status_details: AuthorizationStatusDetails,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CaptureStatus {
/// The funds for this captured payment were credited to the payee's PayPal account.
Completed,
/// The funds could not be captured.
Declined,
/// An amount less than this captured payment's amount was partially refunded to the payer.
PartiallyRefunded,
/// The funds for this captured payment was not yet credited to the payee's PayPal account. For more information, see status.details.
Pending,
/// An amount greater than or equal to this captured payment's amount was refunded to the payer.
Refunded,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CaptureStatusDetails {
/// The payer initiated a dispute for this captured payment with PayPal.
BuyerComplaint,
/// The captured funds were reversed in response to the payer disputing this captured payment with
/// the issuer of the financial instrument used to pay for this captured payment.
Chargeback,
/// The payer paid by an eCheck that has not yet cleared.
Echeck,
/// Visit your online account. In your **Account Overview**, accept and deny this payment.
InternationalWithdrawal,
/// No additional specific reason can be provided. For more information about this captured payment, visit your account online or contact PayPal.
Other,
/// The captured payment is pending manual review.
PendingReview,
/// The payee has not yet set up appropriate receiving preferences for their account.
/// For more information about how to accept or deny this payment, visit your account online.
/// This reason is typically offered in scenarios such as when the currency of the captured
/// payment is different from the primary holding currency of the payee.
ReceivingPreferenceMandatesManualAction,
/// The captured funds were refunded.
Refunded,
/// The payer must send the funds for this captured payment. This code generally appears for manual EFTs.
TransactionApprovedAwaitingFunding,
/// The payee does not have a PayPal account.
Unilateral,
/// The payee's PayPal account is not verified.
VerificationRequired,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Capture {
/// The status of the captured payment.
status: CaptureStatus,
/// The details of the captured payment status.
status_details: CaptureStatusDetails,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
/// The refund was cancelled.
Cancelled,
/// The refund is pending. For more information, see status_details.reason.
Pending,
/// The funds for this transaction were debited to the customer's account.
Completed,
}
/// The reason why the refund has the PENDING or FAILED status.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatusDetails {
/// The customer's account is funded through an eCheck, which has not yet cleared.
Echeck,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Refund {
/// The status of the refund.
status: RefundStatus,
/// The details of the refund status.
status_details: RefundStatusDetails,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaymentCollection {
/// An array of authorized payments for a purchase unit. A purchase unit can have zero or more authorized payments.
authorizations: Vec<AuthorizationWithData>,
/// An array of captured payments for a purchase unit. A purchase unit can have zero or more captured payments.
captures: Vec<Capture>,
/// An array of refunds for a purchase unit. A purchase unit can have zero or more refunds.
refuns: Vec<Refund>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub reference_id: Option<String>, pub reference_id: Option<String>,
/// The total order amount with an optional breakdown that provides details, such as the total item amount, /// The total order amount with an optional breakdown that provides details, such as the total item amount,
/// total tax amount, shipping, handling, insurance, and discounts, if any. /// total tax amount, shipping, handling, insurance, and discounts, if any.
@ -254,31 +432,57 @@ pub struct PurchaseUnitRequest {
/// see the PayPal REST APIs [Currency Codes](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/). /// see the PayPal REST APIs [Currency Codes](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/).
pub amount: Amount, pub amount: Amount,
/// The merchant who receives payment for this transaction. /// The merchant who receives payment for this transaction.
#[serde(skip_serializing_if = "Option::is_none")]
pub payee: Option<Payee>, pub payee: Option<Payee>,
/// Any additional payment instructions for PayPal Commerce Platform customers. /// Any additional payment instructions for PayPal Commerce Platform customers.
/// Enables features for the PayPal Commerce Platform, such as delayed disbursement and collection of a platform fee. /// Enables features for the PayPal Commerce Platform, such as delayed disbursement and collection of a platform fee.
/// Applies during order creation for captured payments or during capture of authorized payments. /// Applies during order creation for captured payments or during capture of authorized payments.
pub payment_instruction: Option<PaymentInstruction>, #[serde(skip_serializing_if = "Option::is_none")]
pub payment_instruction: Option<PaymentInstruction>,
/// The purchase description. /// The purchase description.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
/// The API caller-provided external ID. Used to reconcile client transactions with PayPal transactions. /// The API caller-provided external ID. Used to reconcile client transactions with PayPal transactions.
/// Appears in transaction and settlement reports but is not visible to the payer. /// Appears in transaction and settlement reports but is not visible to the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_id: Option<String>, pub custom_id: Option<String>,
/// The API caller-provided external invoice number for this order. /// The API caller-provided external invoice number for this order.
/// Appears in both the payer's transaction history and the emails that the payer receives. /// Appears in both the payer's transaction history and the emails that the payer receives.
#[serde(skip_serializing_if = "Option::is_none")]
pub invoice_id: Option<String>, pub invoice_id: Option<String>,
/// The PayPal-generated ID for the purchase unit.
/// This ID appears in both the payer's transaction history and the emails that the payer receives.
/// In addition, this ID is available in transaction and settlement reports that merchants and API callers can use to reconcile transactions.
/// This ID is only available when an order is saved by calling v2/checkout/orders/id/save.
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
/// The soft descriptor is the dynamic text used to construct the statement descriptor that appears on a payer's card statement. /// The soft descriptor is the dynamic text used to construct the statement descriptor that appears on a payer's card statement.
/// ///
/// More info here: https://developer.paypal.com/docs/api/orders/v2/#definition-purchase_unit_request /// More info here: https://developer.paypal.com/docs/api/orders/v2/#definition-purchase_unit_request
#[serde(skip_serializing_if = "Option::is_none")]
pub soft_descriptor: Option<String>, pub soft_descriptor: Option<String>,
/// An array of items that the customer purchases from the merchant. /// An array of items that the customer purchases from the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Vec<Item>>, pub items: Option<Vec<Item>>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping: Option<ShippingDetail>, pub shipping: Option<ShippingDetail>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payments: Option<PaymentCollection>,
}
impl PurchaseUnit {
pub fn new(amount: Amount) -> Self {
Self {
amount,
..Default::default()
}
}
} }
/// 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)] #[derive(Debug, Serialize, Deserialize)]
#[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.
Login, Login,
@ -298,7 +502,8 @@ impl Default for LandingPage {
} }
/// The shipping preference /// The shipping preference
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
#[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.
GetFromFile, GetFromFile,
@ -314,7 +519,8 @@ impl Default for ShippingPreference {
} }
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
#[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
/// the final amount is not known when the checkout flow is initiated and you want to redirect the customer /// the final amount is not known when the checkout flow is initiated and you want to redirect the customer
@ -332,7 +538,8 @@ impl Default for UserAction {
} }
} }
#[derive(Debug)] #[derive(Debug, Serialize, Deserialize)]
#[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.
Unrestricted, Unrestricted,
@ -348,45 +555,233 @@ impl Default for PayeePreferred {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaymentMethod { pub struct PaymentMethod {
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_selected: Option<String>, pub payer_selected: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_prefered: Option<PayeePreferred>, pub payer_prefered: Option<PayeePreferred>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub brand_name: Option<String>, pub brand_name: Option<String>,
/// The BCP 47-formatted locale of pages that the PayPal payment experience shows. PayPal supports a five-character code. /// The BCP 47-formatted locale of pages that the PayPal payment experience shows. PayPal supports a five-character code.
/// ///
/// For example, da-DK, he-IL, id-ID, ja-JP, no-NO, pt-BR, ru-RU, sv-SE, th-TH, zh-CN, zh-HK, or zh-TW. /// For example, da-DK, he-IL, id-ID, ja-JP, no-NO, pt-BR, ru-RU, sv-SE, th-TH, zh-CN, zh-HK, or zh-TW.
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>, pub locale: Option<String>,
/// 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
#[serde(skip_serializing_if = "Option::is_none")]
pub landing_page: Option<LandingPage>, pub landing_page: Option<LandingPage>,
/// The shipping preference /// The shipping preference
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_preference: Option<ShippingPreference>, pub shipping_preference: Option<ShippingPreference>,
/// Configures a Continue or Pay Now checkout flow. /// Configures a Continue or Pay Now checkout flow.
#[serde(skip_serializing_if = "Option::is_none")]
pub user_action: Option<UserAction>, pub user_action: Option<UserAction>,
/// The customer and merchant payment preferences. /// The customer and merchant payment preferences.
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_method: Option<PaymentMethod>, pub payment_method: Option<PaymentMethod>,
/// The URL where the customer is redirected after the customer approves the payment. /// The URL where the customer is redirected after the customer approves the payment.
#[serde(skip_serializing_if = "Option::is_none")]
pub return_url: Option<String>, pub return_url: Option<String>,
/// The URL where the customer is redirected after the customer cancels the payment. /// The URL where the customer is redirected after the customer cancels the payment.
#[serde(skip_serializing_if = "Option::is_none")]
pub cancel_url: Option<String>, pub cancel_url: Option<String>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize, Deserialize)]
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,
/// The customer who approves and pays for the order. The customer is also known as the payer. /// The customer who approves and pays for the order. The customer is also known as the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub payer: Option<Payer>, pub payer: Option<Payer>,
/// An array of purchase units. Each purchase unit establishes a contract between a payer and the payee. /// An array of purchase units. Each purchase unit establishes a contract between a payer and the payee.
/// Each purchase unit represents either a full or partial order that the payer intends to purchase from the payee. /// Each purchase unit represents either a full or partial order that the payer intends to purchase from the payee.
pub purchase_units: Vec<PurchaseUnitRequest>, pub purchase_units: Vec<PurchaseUnit>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub application_context: Option<ApplicationContext>, pub application_context: Option<ApplicationContext>,
} }
impl OrderPayload {
pub fn new<S: Into<Vec<PurchaseUnit>>>(intent: Intent, purchase_units: S) -> Self {
Self {
intent,
purchase_units: purchase_units.into(),
..Default::default()
}
}
}
/// The card brand or network.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardBrand {
/// Visa card.
Visa,
/// Mastecard card.
Mastercard,
/// Discover card.
Discover,
/// American Express card.
Amex,
/// Solo debit card.
Solo,
/// Japan Credit Bureau card.
JCB,
/// Military Star card.
Star,
/// Delta Airlines card.
Delta,
/// Switch credit card.
Switch,
/// Maestro credit card.
Maestro,
/// Carte Bancaire (CB) credit card.
CbNationale,
/// Configoga credit card.
Configoga,
/// Confidis credit card.
Confidis,
/// Visa Electron credit card.
Electron,
/// Cetelem credit card.
Cetelem,
/// China union pay credit card.
ChinaUnionPay,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardType {
Credit,
Debit,
Prepaid,
Unknown
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CardResponse {
/// The last digits of the payment card.
pub last_digits: String,
/// The card brand or network.
pub brand: CardBrand,
#[serde(rename = "type")]
pub card_type: CardType,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WalletResponse {
pub apple_pay: CardResponse
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaymentSourceResponse {
pub card: CardResponse,
pub wallet: WalletResponse,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OrderStatus {
/// The order was created with the specified context.
Created,
/// The order was saved and persisted. The order status continues to be in progress until a capture
/// is made with final_capture = true for all purchase units within the order.
Saved,
/// The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on.
Approved,
/// All purchase units in the order are voided.
Voided,
/// The payment was authorized or the authorized payment was captured for the order.
Completed,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LinkMethod {
Get,
Post,
Put,
Delete,
Head,
Connect,
Options,
Patch
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct LinkDescription {
/// The complete target URL.
pub href: String,
/// The link relation type, which serves as an ID for a link that unambiguously describes the semantics of the link.
pub rel: String,
/// The HTTP method required to make the related call.
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<LinkMethod>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Order {
/// The date and time when the transaction occurred.
#[serde(skip_serializing_if = "Option::is_none")]
pub create_time: Option<chrono::DateTime<chrono::Utc>>,
/// The date and time when the transaction was last updated.
#[serde(skip_serializing_if = "Option::is_none")]
pub update_time: Option<chrono::DateTime<chrono::Utc>>,
/// The ID of the order.
pub id: String,
/// The payment source used to fund the payment.
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_source: Option<PaymentSourceResponse>,
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
#[serde(skip_serializing_if = "Option::is_none")]
pub intent: Option<Intent>,
/// The customer who approves and pays for the order. The customer is also known as the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub payer: Option<Payer>,
/// An array of purchase units. Each purchase unit establishes a contract between a customer and merchant.
/// Each purchase unit represents either a full or partial order that the customer intends to purchase from the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub purchase_units: Option<Vec<PurchaseUnit>>,
/// The order status.
pub status: OrderStatus,
/// An array of request-related HATEOAS links. To complete payer approval, use the approve link to redirect the payer.
pub links: Vec<LinkDescription>,
}
impl super::Client {
/// Creates an order. Supports orders with only one purchase unit.
pub async fn create_order(&self, order: OrderPayload, header_params: crate::HeaderParams) -> Result<Order, Box<dyn std::error::Error>> {
let builder = self.setup_headers(self.client.post(format!("{}/v2/checkout/orders", self.endpoint()).as_str()), header_params);
let res = builder.json(&order).send().await?;
if res.status().is_success() {
let order: Order = res.json::<Order>().await?;
Ok(order)
} else {
Err(Box::new(errors::Errors::ApiCallFailure))
}
}
/// Updates an order with the CREATED or APPROVED status.
///
/// You cannot update an order with the COMPLETED status.
///
/// TODO: Use a enum for status. https://developer.paypal.com/docs/api/orders/v2/#orders_patch
pub async fn update_order<S: AsRef<str>>(&self, id: S, status: S) {
todo!()
}
pub async fn get_order<S: AsRef<str>>(id: S) {
todo!()
}
}
// TODO: Finish order https://developer.paypal.com/docs/api/orders/v2/ // TODO: Finish order https://developer.paypal.com/docs/api/orders/v2/

View file

@ -1,19 +1,29 @@
use crate::*; use crate::*;
use dotenv::dotenv;
use std::env; use std::env;
#[tokio::test] #[tokio::test]
async fn it_works() { async fn it_works() {
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();
let mut client = Client::new( let mut client = Client::new(clientid.as_str(), secret.as_str(), true);
clientid.as_str(),
secret.as_str(), assert_eq!(
true, client.get_access_token().await.is_err(),
false,
"should not error"
);
println!("{:#?}", client);
let order = orders::OrderPayload::new(
orders::Intent::Capture,
vec![orders::PurchaseUnit::new(orders::Amount::new(
"EUR", "10.0",
))],
); );
assert_eq!(client.get_access_token().await.is_err(), false, "should not error"); let order_created = client.create_order(order, HeaderParams::default()).await.unwrap();
println!("{:#?}", client);
println!("{:#?}", order_created);
} }