progress with orders

This commit is contained in:
Edgar 2020-06-10 12:29:13 +02:00
parent 32f5493c95
commit b9845d284f
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
7 changed files with 288 additions and 113 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "paypal-rs" name = "paypal-rs"
version = "0.0.4" version = "0.0.5"
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/"
@ -16,6 +16,7 @@ edition = "2018"
reqwest = { version = "0.10", features = ["json"] } reqwest = { version = "0.10", features = ["json"] }
tokio = { version = "0.2", features = ["full"] } tokio = { version = "0.2", features = ["full"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
jsonwebtoken = "7" jsonwebtoken = "7"
base64 = "0.12" base64 = "0.12"
log = "0.4" log = "0.4"

View file

@ -2,7 +2,7 @@
![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) ![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 in a strongly typed manner.
Crate: https://crates.io/crates/paypal-rs Crate: https://crates.io/crates/paypal-rs

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
max_width = 120

View file

@ -5,5 +5,5 @@ pub enum Errors {
#[error("failed to get access token")] #[error("failed to get access token")]
GetAccessTokenFailure, GetAccessTokenFailure,
#[error("failure when calling the paypal api")] #[error("failure when calling the paypal api")]
ApiCallFailure, ApiCallFailure(String),
} }

View file

@ -6,10 +6,10 @@ extern crate chrono;
pub mod errors; pub mod errors;
pub mod orders; pub mod orders;
use serde::{Serialize, Deserialize};
use std::time::{Duration, Instant};
use reqwest::header::HeaderMap;
use reqwest::header; use reqwest::header;
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
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";
@ -104,7 +104,7 @@ pub struct Query {
// TODO: Use https://github.com/samscott89/serde_qs // TODO: Use https://github.com/samscott89/serde_qs
} }
#[derive(Debug)] #[derive(Debug, Eq, PartialEq)]
pub enum Prefer { pub enum Prefer {
/// The server returns a minimal response to optimize communication between the API caller and the server. /// The server returns a minimal response to optimize communication between the API caller and the server.
/// A minimal response includes the id, status and HATEOAS links. /// A minimal response includes the id, status and HATEOAS links.
@ -124,11 +124,21 @@ impl Default for Prefer {
/// https://developer.paypal.com/docs/api/reference/api-requests/#paypal-auth-assertion /// https://developer.paypal.com/docs/api/reference/api-requests/#paypal-auth-assertion
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct HeaderParams { pub struct HeaderParams {
pub merchant_payer_id: Option<String>, /// The merchant payer id used on PayPal-Auth-Assertion
pub merchant_payer_id: Option<String>,
/// Verifies that the payment originates from a valid, user-consented device and application.
/// Reduces fraud and decreases declines. Transactions that do not include a client metadata ID are not eligible for PayPal Seller Protection.
pub client_metadata_id: Option<String>, pub client_metadata_id: Option<String>,
/// Identifies the caller as a PayPal partner. To receive revenue attribution, specify a unique build notation (BN) code.
/// BN codes provide tracking on all transactions that originate or are associated with a particular partner.
pub partner_attribution_id: Option<String>, pub partner_attribution_id: Option<String>,
/// Contains a unique user-generated ID that the server stores for a period of time. Use this header to enforce idempotency on REST API POST calls.
/// You can make these calls any number of times without concern that the server creates or completes an action on a resource more than once.
/// You can retry calls that fail with network timeouts or the HTTP 500 status code. You can retry calls for as long as the server stores the ID.
pub request_id: Option<String>, pub request_id: Option<String>,
/// The preferred server response upon successful completion of the request.
pub prefer: Option<Prefer>, pub prefer: Option<Prefer>,
/// The media type. Required for operations with a request body.
pub content_type: Option<String>, pub content_type: Option<String>,
} }
@ -182,26 +192,40 @@ impl Client {
headers.append(header::ACCEPT, "application/json".parse().unwrap()); headers.append(header::ACCEPT, "application/json".parse().unwrap());
if let Some(token) = &self.auth.access_token { if let Some(token) = &self.auth.access_token {
headers.append(header::AUTHORIZATION, format!("Bearer {}", token.access_token).as_str().parse().unwrap()); headers.append(
header::AUTHORIZATION,
format!("Bearer {}", token.access_token).as_str().parse().unwrap(),
);
} }
if let Some(merchant_payer_id) = header_params.merchant_payer_id { if let Some(merchant_payer_id) = header_params.merchant_payer_id {
let claims = AuthAssertionClaims { let claims = AuthAssertionClaims {
iss: self.auth.client_id.clone(), iss: self.auth.client_id.clone(),
payer_id: merchant_payer_id payer_id: merchant_payer_id,
}; };
let jwt_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256); let jwt_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256);
let token = jsonwebtoken::encode(&jwt_header, &claims, &jsonwebtoken::EncodingKey::from_secret(self.auth.secret.as_ref())).unwrap(); let token = jsonwebtoken::encode(
&jwt_header,
&claims,
&jsonwebtoken::EncodingKey::from_secret(self.auth.secret.as_ref()),
)
.unwrap();
let encoded_token = base64::encode(token); let encoded_token = base64::encode(token);
headers.append("PayPal-Auth-Assertion", encoded_token.as_str().parse().unwrap()); headers.append("PayPal-Auth-Assertion", encoded_token.as_str().parse().unwrap());
} }
if let Some(client_metadata_id) = header_params.client_metadata_id { if let Some(client_metadata_id) = header_params.client_metadata_id {
headers.append("PayPal-Client-Metadata-Id", client_metadata_id.as_str().parse().unwrap()); headers.append(
"PayPal-Client-Metadata-Id",
client_metadata_id.as_str().parse().unwrap(),
);
} }
if let Some(partner_attribution_id) = header_params.partner_attribution_id { if let Some(partner_attribution_id) = header_params.partner_attribution_id {
headers.append("PayPal-Partner-Attribution-Id", partner_attribution_id.as_str().parse().unwrap()); headers.append(
"PayPal-Partner-Attribution-Id",
partner_attribution_id.as_str().parse().unwrap(),
);
} }
if let Some(request_id) = header_params.request_id { if let Some(request_id) = header_params.request_id {
@ -227,10 +251,7 @@ impl Client {
let res = self let res = self
.client .client
.post(format!("{}/v1/oauth2/token", self.endpoint()).as_str()) .post(format!("{}/v1/oauth2/token", self.endpoint()).as_str())
.basic_auth( .basic_auth(self.auth.client_id.as_str(), Some(self.auth.secret.as_str()))
self.auth.client_id.as_str(),
Some(self.auth.secret.as_str()),
)
.header("Content-Type", "x-www-form-urlencoded") .header("Content-Type", "x-www-form-urlencoded")
.header("Accept", "application/json") .header("Accept", "application/json")
.body("grant_type=client_credentials") .body("grant_type=client_credentials")
@ -242,7 +263,7 @@ impl Client {
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);
} else { } else {
return Err(Box::new(errors::Errors::GetAccessTokenFailure)); return Err(Box::new(errors::Errors::ApiCallFailure(res.text().await?)));
} }
Ok(()) Ok(())

View file

@ -1,8 +1,8 @@
use serde::{Serialize, Deserialize};
use crate::errors; use crate::errors;
use serde::{Deserialize, Serialize};
/// The intent to either capture payment immediately or authorize a payment for an order after order creation. /// The intent to either capture payment immediately or authorize a payment for an order after order creation.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Intent { pub enum Intent {
/// The merchant intends to capture payment immediately after the customer makes a payment. /// The merchant intends to capture payment immediately after the customer makes a payment.
@ -24,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, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
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,
@ -36,7 +36,7 @@ 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, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PhoneType { pub enum PhoneType {
Fax, Fax,
@ -46,7 +46,7 @@ pub enum PhoneType {
Pager, Pager,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
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.
@ -63,7 +63,7 @@ pub struct Phone {
pub phone_number: PhoneNumber, pub phone_number: PhoneNumber,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum TaxIdType { pub enum TaxIdType {
@ -134,7 +134,7 @@ pub struct Payer {
pub address: Option<Address>, pub address: Option<Address>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
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,
@ -216,7 +216,7 @@ pub struct PlatformFee {
pub payee: Option<Payee>, pub payee: Option<Payee>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub enum DisbursementMode { pub enum DisbursementMode {
/// The funds are released to the merchant immediately. /// The funds are released to the merchant immediately.
Instant, Instant,
@ -239,10 +239,10 @@ pub struct PaymentInstruction {
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")] #[serde(skip_serializing_if = "Option::is_none")]
pub disbursement_mode: Option<DisbursementMode> pub disbursement_mode: Option<DisbursementMode>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ItemCategoryType { pub enum ItemCategoryType {
/// Goods that are stored, delivered, and used in their electronic format. /// Goods that are stored, delivered, and used in their electronic format.
@ -290,7 +290,7 @@ pub struct Item {
pub category: Option<ItemCategoryType>, pub category: Option<ItemCategoryType>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AuthorizationStatus { pub enum AuthorizationStatus {
/// The authorized payment is created. No captured payments have been made for this authorized payment. /// The authorized payment is created. No captured payments have been made for this authorized payment.
@ -311,14 +311,14 @@ pub enum AuthorizationStatus {
Pending, Pending,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AuthorizationStatusDetails { pub enum AuthorizationStatusDetails {
/// Authorization is pending manual review. /// Authorization is pending manual review.
PendingReview, PendingReview,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct AuthorizationWithData { pub struct AuthorizationWithData {
/// The status for the authorized payment. /// The status for the authorized payment.
pub status: AuthorizationStatus, pub status: AuthorizationStatus,
@ -326,7 +326,7 @@ pub struct AuthorizationWithData {
pub status_details: AuthorizationStatusDetails, pub status_details: AuthorizationStatusDetails,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CaptureStatus { pub enum CaptureStatus {
/// The funds for this captured payment were credited to the payee's PayPal account. /// The funds for this captured payment were credited to the payee's PayPal account.
@ -341,7 +341,7 @@ pub enum CaptureStatus {
Refunded, Refunded,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CaptureStatusDetails { pub enum CaptureStatusDetails {
/// The payer initiated a dispute for this captured payment with PayPal. /// The payer initiated a dispute for this captured payment with PayPal.
@ -372,7 +372,7 @@ pub enum CaptureStatusDetails {
VerificationRequired, VerificationRequired,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Capture { pub struct Capture {
/// The status of the captured payment. /// The status of the captured payment.
status: CaptureStatus, status: CaptureStatus,
@ -380,7 +380,7 @@ pub struct Capture {
status_details: CaptureStatusDetails, status_details: CaptureStatusDetails,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus { pub enum RefundStatus {
/// The refund was cancelled. /// The refund was cancelled.
@ -392,7 +392,7 @@ pub enum RefundStatus {
} }
/// The reason why the refund has the PENDING or FAILED status. /// The reason why the refund has the PENDING or FAILED status.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatusDetails { pub enum RefundStatusDetails {
/// The customer's account is funded through an eCheck, which has not yet cleared. /// The customer's account is funded through an eCheck, which has not yet cleared.
@ -481,7 +481,7 @@ impl PurchaseUnit {
} }
/// The type of landing page to show on the PayPal site for customer checkout. /// The type of landing page to show on the PayPal site for customer checkout.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LandingPage { pub enum LandingPage {
/// When the customer clicks PayPal Checkout, the customer is redirected to a page to log in to PayPal and approve the payment. /// When the customer clicks PayPal Checkout, the customer is redirected to a page to log in to PayPal and approve the payment.
@ -502,7 +502,7 @@ impl Default for LandingPage {
} }
/// The shipping preference /// The shipping preference
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ShippingPreference { pub enum ShippingPreference {
/// Use the customer-provided shipping address on the PayPal site. /// Use the customer-provided shipping address on the PayPal site.
@ -519,7 +519,7 @@ impl Default for ShippingPreference {
} }
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum UserAction { pub enum UserAction {
/// After you redirect the customer to the PayPal payment page, a Continue button appears. Use this option when /// After you redirect the customer to the PayPal payment page, a Continue button appears. Use this option when
@ -538,7 +538,7 @@ impl Default for UserAction {
} }
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayeePreferred { pub enum PayeePreferred {
/// Accepts any type of payment from the customer. /// Accepts any type of payment from the customer.
@ -619,7 +619,7 @@ impl OrderPayload {
} }
/// The card brand or network. /// The card brand or network.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardBrand { pub enum CardBrand {
/// Visa card. /// Visa card.
@ -656,13 +656,13 @@ pub enum CardBrand {
ChinaUnionPay, ChinaUnionPay,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardType { pub enum CardType {
Credit, Credit,
Debit, Debit,
Prepaid, Prepaid,
Unknown Unknown,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -677,7 +677,7 @@ pub struct CardResponse {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct WalletResponse { pub struct WalletResponse {
pub apple_pay: CardResponse pub apple_pay: CardResponse,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -686,7 +686,7 @@ pub struct PaymentSourceResponse {
pub wallet: WalletResponse, pub wallet: WalletResponse,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OrderStatus { pub enum OrderStatus {
/// The order was created with the specified context. /// The order was created with the specified context.
@ -702,7 +702,7 @@ pub enum OrderStatus {
Completed, Completed,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LinkMethod { pub enum LinkMethod {
Get, Get,
@ -712,10 +712,9 @@ pub enum LinkMethod {
Head, Head,
Connect, Connect,
Options, Options,
Patch Patch,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct LinkDescription { pub struct LinkDescription {
/// The complete target URL. /// The complete target URL.
@ -758,30 +757,174 @@ pub struct Order {
impl super::Client { impl super::Client {
/// Creates an order. Supports orders with only one purchase unit. /// 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>> { pub async fn create_order(
let builder = self.setup_headers(self.client.post(format!("{}/v2/checkout/orders", self.endpoint()).as_str()), header_params); &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?; let res = builder.json(&order).send().await?;
if res.status().is_success() { if res.status().is_success() {
let order: Order = res.json::<Order>().await?; let order = res.json::<Order>().await?;
Ok(order) Ok(order)
} else { } else {
Err(Box::new(errors::Errors::ApiCallFailure)) Err(Box::new(errors::Errors::ApiCallFailure(res.text().await?)))
}
}
/// Used internally for order requests that have no body.
async fn build_endpoint_order<S: std::fmt::Display, A: std::fmt::Display>(
&self,
order_id: S,
endpoint: A,
post: bool,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
let format = format!("{}/v2/checkout/orders/{}/{}", self.endpoint(), order_id, endpoint);
let builder = self.setup_headers(
match post {
true => self.client.post(format.as_str()),
false => self.client.get(format.as_str()),
},
header_params,
);
let res = builder.send().await?;
if res.status().is_success() {
let order = res.json::<Order>().await?;
Ok(order)
} else {
Err(Box::new(errors::Errors::ApiCallFailure(res.text().await?)))
} }
} }
/// Updates an order with the CREATED or APPROVED status. /// Updates an order with the CREATED or APPROVED status.
///
/// You cannot update an order with the COMPLETED 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 /// Only replacing the existing purchase units and intent is supported right now.
pub async fn update_order<S: AsRef<str>>(&self, id: S, status: S) { ///
todo!() /// Note: You can only update the intent from Authorize to Capture
///
/// More info on what you can change: https://developer.paypal.com/docs/api/orders/v2/#orders_patch
pub async fn update_order<S: std::fmt::Display>(
&self,
id: S,
intent: Option<Intent>,
purchase_units: Option<Vec<PurchaseUnit>>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut intent_json = String::new();
let units_json = String::new();
if let Some(p_units) = purchase_units {
let mut units_json = String::new();
for (i, unit) in p_units.iter().enumerate() {
let unit_str = serde_json::to_string(&unit)?;
let mut unit_json = format!(r#"
{{
"op": "replace",
"path": "/purchase_units/@reference_id='{reference_id}'",
"value": {unit}
}}
"#,
reference_id = unit.reference_id.clone().unwrap_or(String::from("default")),
unit = unit_str);
if i < p_units.len() - 1 {
unit_json += ",";
}
units_json += unit_json.as_str();
}
}
if let Some(x) = intent {
let intent_str = match x {
Intent::Authorize => String::from("AUTHORIZE"),
Intent::Capture => String::from("CAPTURE")
};
intent_json = format!(
r#"
{{
"op": "replace",
"path": "/intent",
"value": "{intent}"
}}
"#,
intent = intent_str
);
}
let final_json = {
if intent_json != "" && units_json != "" {
format!("[{},{}]", intent_json, units_json)
} else {
format!("[{}{}]", intent_json, units_json)
}
};
let builder = {
self.setup_headers(
self.client
.patch(format!("{}/v2/checkout/orders/{}", self.endpoint(), id).as_str()),
crate::HeaderParams {
content_type: Some(String::from("application/json")),
..Default::default()
},
)
};
let res = builder.body(final_json.clone()).send().await?;
if res.status().is_success() {
Ok(())
} else {
Err(Box::new(errors::Errors::ApiCallFailure(res.text().await?)))
}
} }
pub async fn get_order<S: AsRef<str>>(id: S) { /// Shows details for an order, by ID.
todo!() pub async fn show_order_details<S: std::fmt::Display>(
&self,
order_id: S,
) -> Result<Order, Box<dyn std::error::Error>> {
self.build_endpoint_order(order_id, "", false, crate::HeaderParams::default())
.await
}
/// Captures payment for an order. To successfully capture payment for an order,
/// the buyer must first approve the order or a valid payment_source must be provided in the request.
/// A buyer can approve the order upon being redirected to the rel:approve URL that was returned in the HATEOAS links in the create order response.
pub async fn capture_order<S: std::fmt::Display>(
&self,
order_id: S,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
self.build_endpoint_order(order_id, "capture", true, header_params)
.await
}
/// Authorizes payment for an order. To successfully authorize payment for an order,
/// the buyer must first approve the order or a valid payment_source must be provided in the request.
/// A buyer can approve the order upon being redirected to the rel:approve URL that was returned in the HATEOAS links in the create order response.
pub async fn authorize_order<S: std::fmt::Display>(
&self,
order_id: S,
header_params: crate::HeaderParams,
) -> Result<Order, Box<dyn std::error::Error>> {
self.build_endpoint_order(order_id, "authorize", true, header_params)
.await
} }
} }
// TODO: Finish order https://developer.paypal.com/docs/api/orders/v2/ // TODO: Add strong typed support for order errors in body: https://developer.paypal.com/docs/api/orders/v2/#errors

View file

@ -14,16 +14,25 @@ async fn it_works() {
false, false,
"should not error" "should not error"
); );
println!("{:#?}", client);
let order = orders::OrderPayload::new( let order = orders::OrderPayload::new(
orders::Intent::Capture, orders::Intent::Authorize,
vec![orders::PurchaseUnit::new(orders::Amount::new( vec![orders::PurchaseUnit::new(orders::Amount::new(
"EUR", "10.0", "EUR", "10.0",
))], ))],
); );
let order_created = client.create_order(order, HeaderParams::default()).await.unwrap(); let ref_id = format!("TEST-{:?}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs());
println!("{:#?}", order_created); let order_created = client.create_order(order, HeaderParams {
prefer: Some(Prefer::Representation),
request_id: Some(ref_id.clone()),
..Default::default()
}).await.unwrap();
assert!(order_created.id != "", "order id is not empty");
assert_eq!(order_created.status, orders::OrderStatus::Created, "order status is created");
assert_eq!(order_created.links.len(), 4, "order links exist");
client.update_order(order_created.id, Some(orders::Intent::Capture), Some(order_created.purchase_units.expect("to exist"))).await.unwrap();
} }