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]
name = "paypal-rs"
version = "0.0.3"
version = "0.0.4"
authors = ["Edgar <git@edgarluque.com>"]
description = "A library that wraps the paypal api asynchronously."
repository = "https://github.com/edg-l/paypal-rs/"
@ -20,4 +20,5 @@ jsonwebtoken = "7"
base64 = "0.12"
log = "0.4"
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
![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.

View file

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

View file

@ -14,32 +14,46 @@ use reqwest::header;
pub const LIVE_ENDPOINT: &str = "https://api.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/
#[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 client: reqwest::Client,
/// Whether you are or not in a sandbox enviroment.
pub sandbox: bool,
/// Api Auth information
pub auth: Auth,
}
@ -51,26 +65,60 @@ pub struct Client {
/// ```
/// let query = Query { count: Some(40), ..Default::default() };
/// ```
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize)]
pub struct Query {
/// The number of items to list in the response.
#[serde(skip_serializing_if = "Option::is_none")]
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>>,
/// 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>,
/// The number of items to return in the response.
#[serde(skip_serializing_if = "Option::is_none")]
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>,
/// 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>,
/// Sorts the items in the response in ascending or descending order.
#[serde(skip_serializing_if = "Option::is_none")]
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>,
/// 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>,
/// 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>>,
// TODO: Use https://github.com/samscott89/serde_qs
}
#[derive(Debug)]
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,
/// The server returns a complete resource representation, including the current state of the resource.
Representation,
}
impl Default for Prefer {
fn default() -> Self {
Prefer::Minimal
}
}
/// Represents the optional header values used on paypal requests.
///
/// https://developer.paypal.com/docs/api/reference/api-requests/#paypal-auth-assertion
@ -96,21 +144,15 @@ impl Client {
/// Example:
///
/// ```
/// #[tokio::main]
/// async fn main() {
/// dotenv().ok();
/// let clientid = env::var("PAYPAL_CLIENTID").unwrap();
/// let secret = env::var("PAYPAL_SECRET").unwrap();
/// let clientid = env::var("PAYPAL_CLIENTID").unwrap();
/// let secret = env::var("PAYPAL_SECRET").unwrap();
///
/// let mut client = Client::new(
/// clientid.as_str(),
/// secret.as_str(),
/// true,
/// );
///
/// client.get_access_token().await.unwrap();
/// println!("{:#?}", client);
/// }
/// let mut client = Client::new(
/// clientid.as_str(),
/// secret.as_str(),
/// true,
/// );
/// client.get_access_token().await.unwrap();
/// ```
pub fn new<S: Into<String>>(client_id: S, secret: S, sandbox: bool) -> Client {
Client {
@ -199,11 +241,8 @@ impl Client {
let token = res.json::<AccessToken>().await?;
self.auth.expires = Some((Instant::now(), Duration::new(token.expires_in, 0)));
self.auth.access_token = Some(token);
println!("{:#?}", self.auth);
} else {
println!("status = {:#?}", res.status());
println!("res = {:#?}", res);
return Err(Box::new(errors::GetAccessTokenError));
return Err(Box::new(errors::Errors::GetAccessTokenFailure));
}
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.
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Intent {
/// The merchant intends to capture payment immediately after the customer makes a payment.
Capture,
@ -20,7 +24,7 @@ impl Default for Intent {
/// Represents a payer name.
///
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer.name
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct PayerName {
/// When the party is a person, the party's given, or first, name.
pub given_name: String,
@ -32,7 +36,8 @@ pub struct PayerName {
/// The phone 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 {
Fax,
Home,
@ -41,7 +46,7 @@ pub enum PhoneType {
Pager,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct PhoneNumber {
/// 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.
@ -51,13 +56,15 @@ pub struct PhoneNumber {
/// 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.
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Phone {
#[serde(skip_serializing_if = "Option::is_none")]
pub phone_type: Option<PhoneType>,
pub phone_number: PhoneNumber,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(non_camel_case_types)]
pub enum TaxIdType {
/// The individual tax ID type.
@ -66,7 +73,7 @@ pub enum TaxIdType {
BR_CNPJ,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct TaxInfo {
/// 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.
@ -75,19 +82,24 @@ pub struct TaxInfo {
pub tax_id_type: TaxIdType,
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Address {
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub address_line_1: Option<String>,
/// 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>,
/// A city, town, or village. Smaller than admin_area_level_1.
#[serde(skip_serializing_if = "Option::is_none")]
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.
/// Format for postal delivery. For example, CA and not California.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[serde(skip_serializing_if = "Option::is_none")]
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.
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.
///
/// https://developer.paypal.com/docs/api/orders/v2/#definition-payer
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Payer {
/// The name of the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<PayerName>,
/// The email address of the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub email_address: Option<String>,
/// The PayPal-assigned ID for the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_id: Option<String>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<Phone>,
/// The birth date of the payer in YYYY-MM-DD format.
#[serde(skip_serializing_if = "Option::is_none")]
pub birth_date: Option<String>,
/// The tax information of the payer. Required only for Brazilian payer's.
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_info: Option<TaxInfo>,
/// The address of the payer.
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<Address>,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
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: 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.
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Breakdown {
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub item_total: Option<Money>,
/// The shipping fee for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping: Option<Money>,
/// The handling fee for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_total: Option<Money>,
/// The insurance fee for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub insurance: Option<Money>,
/// The shipping discount for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_discount: Option<Money>,
/// The discount for all items within a given purchase_unit.
#[serde(skip_serializing_if = "Option::is_none")]
pub discount: Option<Money>,
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
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: 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/).
pub value: String,
/// The breakdown of the amount.
#[serde(skip_serializing_if = "Option::is_none")]
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 {
/// The email address of merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub email_address: Option<String>,
/// The encrypted PayPal account ID of the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub merchant_id: Option<String>,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct PlatformFee {
pub amount: Money,
#[serde(skip_serializing_if = "Option::is_none")]
pub payee: Option<Payee>,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub enum DisbursementMode {
/// The funds are released to the merchant immediately.
Instant,
@ -191,15 +232,18 @@ impl Default for DisbursementMode {
}
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaymentInstruction {
/// An array of various fees, commissions, tips, or donations.
#[serde(skip_serializing_if = "Option::is_none")]
pub platform_fees: Option<Vec<PlatformFee>>,
/// The funds that are held on behalf of the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub disbursement_mode: Option<DisbursementMode>
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ItemCategoryType {
/// Goods that are stored, delivered, and used in their electronic format.
/// 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 {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<Address>,
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Item {
/// The item name or title.
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.
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.
#[serde(skip_serializing_if = "Option::is_none")]
pub tax: Option<Money>,
/// The item quantity. Must be a whole number.
pub quantity: String,
/// The detailed item description.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// The stock keeping unit (SKU) for the item.
#[serde(skip_serializing_if = "Option::is_none")]
pub sku: Option<String>,
/// The item category type
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<ItemCategoryType>,
}
#[derive(Debug, Default)]
pub struct PurchaseUnitRequest {
#[derive(Debug, Serialize, Deserialize)]
#[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.
/// 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>,
/// 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.
@ -254,31 +432,57 @@ pub struct PurchaseUnitRequest {
/// see the PayPal REST APIs [Currency Codes](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/).
pub amount: Amount,
/// The merchant who receives payment for this transaction.
#[serde(skip_serializing_if = "Option::is_none")]
pub payee: Option<Payee>,
/// 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.
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_id: Option<String>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
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.
///
/// 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>,
/// An array of items that the customer purchases from the merchant.
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Vec<Item>>,
/// The name and address of the person to whom to ship the items.
#[serde(skip_serializing_if = "Option::is_none")]
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.
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
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.
Login,
@ -298,7 +502,8 @@ impl Default for LandingPage {
}
/// The shipping preference
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ShippingPreference {
/// Use the customer-provided shipping address on the PayPal site.
GetFromFile,
@ -314,7 +519,8 @@ impl Default for ShippingPreference {
}
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum UserAction {
/// 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
@ -332,7 +538,8 @@ impl Default for UserAction {
}
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayeePreferred {
/// Accepts any type of payment from the customer.
Unrestricted,
@ -348,45 +555,233 @@ impl Default for PayeePreferred {
}
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaymentMethod {
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_selected: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_prefered: Option<PayeePreferred>,
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ApplicationContext {
/// 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>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
/// 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>,
/// The shipping preference
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_preference: Option<ShippingPreference>,
/// Configures a Continue or Pay Now checkout flow.
#[serde(skip_serializing_if = "Option::is_none")]
pub user_action: Option<UserAction>,
/// The customer and merchant payment preferences.
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_method: Option<PaymentMethod>,
/// 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>,
/// 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>,
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct OrderPayload {
/// The intent to either capture payment immediately or authorize a payment for an order after order creation.
pub intent: 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 payer and 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.
#[serde(skip_serializing_if = "Option::is_none")]
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/

View file

@ -1,19 +1,29 @@
use crate::*;
use dotenv::dotenv;
use std::env;
#[tokio::test]
async fn it_works() {
dotenv().ok();
dotenv::dotenv().ok();
let clientid = env::var("PAYPAL_CLIENTID").unwrap();
let secret = env::var("PAYPAL_SECRET").unwrap();
let mut client = Client::new(
clientid.as_str(),
secret.as_str(),
true,
let mut client = Client::new(clientid.as_str(), secret.as_str(), true);
assert_eq!(
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");
println!("{:#?}", client);
let order_created = client.create_order(order, HeaderParams::default()).await.unwrap();
println!("{:#?}", order_created);
}