diff --git a/Cargo.toml b/Cargo.toml index 3b37d1c..3783785 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ serde_qs = "0.10.1" [dev-dependencies] tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] } dotenv = "0.15.0" -anyhow = "1.0.65" color-eyre = "0.6.2" wiremock = "0.5.14" diff --git a/src/api/invoice.rs b/src/api/invoice.rs index 4d1360f..b24960b 100644 --- a/src/api/invoice.rs +++ b/src/api/invoice.rs @@ -407,7 +407,7 @@ mod tests { } #[tokio::test] - async fn test_invoice_create_cancel() -> anyhow::Result<()> { + async fn test_invoice_create_cancel() -> color_eyre::Result<()> { let client = create_client().await; let payload = InvoicePayloadBuilder::default() diff --git a/src/api/orders.rs b/src/api/orders.rs index 86b188a..bf1f59b 100644 --- a/src/api/orders.rs +++ b/src/api/orders.rs @@ -193,7 +193,7 @@ mod tests { use crate::{api::orders::*, data::orders::*, tests::create_client}; #[tokio::test] - async fn test_order() -> anyhow::Result<()> { + async fn test_order() -> color_eyre::Result<()> { let mut client = create_client().await; client.get_access_token().await.expect("get access token error"); diff --git a/src/client.rs b/src/client.rs index 6fe3b0e..ebe19bd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -171,7 +171,7 @@ impl Client { Ok(builder.headers(headers)) } - /// Gets a access token used in all the api calls. + /// Gets a access token used in all the api calls and saves it. pub async fn get_access_token(&mut self) -> Result<(), ResponseError> { if !self.access_token_expired() { return Ok(()); diff --git a/src/data/common.rs b/src/data/common.rs index 6dfe2c2..81b8b2f 100644 --- a/src/data/common.rs +++ b/src/data/common.rs @@ -41,7 +41,8 @@ pub struct AddressDetails { /// The address of the payer. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] 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. @@ -63,6 +64,7 @@ pub struct Address { /// Represents money #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct Money { /// The [three-character ISO-4217 currency code](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/) that identifies the currency. pub currency_code: Currency, diff --git a/src/data/orders.rs b/src/data/orders.rs index 1a6ce8c..e8bacbe 100644 --- a/src/data/orders.rs +++ b/src/data/orders.rs @@ -1,6 +1,6 @@ //! Paypal object definitions used by the orders api. -use super::common::*; +use super::{common::*, invoice::BillingInfo}; use derive_builder::Builder; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -28,7 +28,7 @@ impl Default for Intent { /// Represents a payer name. /// /// -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Builder)] pub struct PayerName { /// When the party is a person, the party's given, or first, name. pub given_name: String, @@ -38,7 +38,8 @@ pub struct PayerName { } /// The phone number, in its canonical international E.164 numbering plan format. -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Builder)] +#[builder(setter(strip_option))] 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. @@ -49,7 +50,8 @@ 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. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct Phone { /// The phone type. pub phone_type: Option, @@ -69,7 +71,8 @@ pub enum TaxIdType { } /// The tax information of the payer. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] 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. @@ -83,6 +86,7 @@ pub struct TaxInfo { /// #[skip_serializing_none] #[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option), default)] pub struct Payer { /// The name of the payer. pub name: Option, @@ -103,7 +107,8 @@ pub struct Payer { /// Breakdown provides details such as total item amount, total tax amount, shipping, handling, insurance, and discounts, if any. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] 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. @@ -124,7 +129,8 @@ pub struct Breakdown { /// Represents an amount of money. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct Amount { /// The [three-character ISO-4217 currency code](https://developer.paypal.com/docs/integration/direct/rest/currency-codes/) that identifies the currency. pub currency_code: Currency, @@ -147,11 +153,30 @@ impl Amount { breakdown: None, } } + + /// Creates a new amount with the EUR currency. + pub fn eur(value: &str) -> Self { + Amount { + currency_code: Currency::EUR, + value: value.to_owned(), + breakdown: None, + } + } + + /// Creates a new amount with the USD currency. + pub fn usd(value: &str) -> Self { + Amount { + currency_code: Currency::USD, + value: value.to_owned(), + breakdown: None, + } + } } /// The merchant who receives payment for this transaction. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct Payee { /// The email address of merchant. pub email_address: Option, @@ -161,7 +186,8 @@ pub struct Payee { /// Fees, commissions, tips, or donations #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct PlatformFee { /// The fee for this transaction. pub amount: Money, @@ -189,7 +215,8 @@ impl Default for DisbursementMode { /// Any additional payment instructions for PayPal Commerce Platform customers. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct PaymentInstruction { /// An array of various fees, commissions, tips, or donations. pub platform_fees: Option>, @@ -225,7 +252,8 @@ pub struct ShippingDetailName { /// The name and address of the person to whom to ship the items. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct ShippingDetail { /// The name of the person to whom to ship the items. Supports only the full_name property. pub name: Option, @@ -235,7 +263,8 @@ pub struct ShippingDetail { /// Represents an item. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into))] pub struct Item { /// The item name or title. pub name: String, @@ -342,7 +371,8 @@ pub struct CaptureStatusDetails { /// A captured payment. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone, Builder)] +#[builder(setter(strip_option))] pub struct Capture { /// The status of the captured payment. pub status: CaptureStatus, @@ -378,7 +408,7 @@ pub struct RefundStatusDetails { } /// Exchange rate. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] pub struct ExchangeRate { /// The source currency from which to convert an amount. pub source_currency: Currency, @@ -389,7 +419,7 @@ pub struct ExchangeRate { } /// The net breakdown of the refund. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] pub struct NetAmountBreakdown { /// The converted payable amount. pub converted_amount: Money, @@ -400,7 +430,8 @@ pub struct NetAmountBreakdown { } /// The breakdown of the refund. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct SellerPayableBreakdown { /// The amount that the payee refunded to the payer. pub gross_amount: Money, @@ -421,7 +452,8 @@ pub struct SellerPayableBreakdown { } /// A refund -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct Refund { /// The status of the refund. pub status: RefundStatus, @@ -442,7 +474,8 @@ pub struct Refund { } /// The comprehensive history of payments for the purchase unit. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct PaymentCollection { /// An array of authorized payments for a purchase unit. A purchase unit can have zero or more authorized payments. #[serde(default)] @@ -457,7 +490,8 @@ pub struct PaymentCollection { /// Represents either a full or partial order that the payer intends to purchase from the payee. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] 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. @@ -600,7 +634,8 @@ pub struct PaymentMethod { /// Customize the payer experience during the approval process for the payment with PayPal. #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option, into), default)] pub struct ApplicationContext { /// The label that overrides the business name in the PayPal account on the PayPal site. pub brand_name: Option, @@ -622,31 +657,81 @@ pub struct ApplicationContext { pub cancel_url: Option, } -/// A order payload to be used when creating an order. +/// A card used in payment sources. #[skip_serializing_none] #[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] -#[builder(setter(strip_option), default)] +#[builder(setter(into))] +pub struct PaymentCard { + /// The card number. + pub number: String, + /// The expiry date. + pub expiry: String, + /// The card owner name. + pub name: String, + /// The billing address. + pub billing_address: Address +} + +/// A transaction reference. +#[skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(into))] +pub struct TransactionReference { + /// The transaction id. + pub id: String, + /// The transaction network, e.g "VISA" + pub network: String, +} + +/// A stored credential. +#[skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(into))] +pub struct StoredCredential { + /// The payment initiator, e.g "MERCHANT" + pub payment_initiator: String, + /// The payment type, e.g "RECURRING" + pub payment_type: String, + /// The stored credential usage, e.g: SUBSEQUENT + pub usage: String, + /// The billing address. + pub previous_network_transaction_reference: TransactionReference +} + +/// A order payload to be used when creating an order. +// TODO: this only appears in the example body, not documented. +// https://developer.paypal.com/docs/api/orders/v2/#orders_create +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] +pub struct OrderPaymentSource { + /// The card used in the payment. + pub card: PaymentCard, + /// A stored credential. + // TODO: figure out what is this. + #[builder(default)] + pub stored_credential: Option, +} + +/// A order payload to be used when creating an order. +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] 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. + /// DEPRECATED. The customer who approves and pays for the order. The customer is also known as the payer. + #[builder(default)] pub payer: Option, /// 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, /// Customize the payer experience during the approval process for the payment with PayPal. + #[builder(default)] pub application_context: Option, -} - -impl OrderPayload { - /// Creates a new order payload with the required properties. - pub fn new>>(intent: Intent, purchase_units: S) -> Self { - Self { - intent, - purchase_units: purchase_units.into(), - ..Default::default() - } - } + /// The payment source. + #[builder(default)] + pub payment_source: Option, } /// The card brand or network. @@ -717,7 +802,7 @@ pub struct WalletResponse { } /// The paypal account used to fund the transaction. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] pub struct PaypalPaymentSourceResponse { /// The name of the payer. pub name: PayerName, @@ -728,7 +813,8 @@ pub struct PaypalPaymentSourceResponse { } /// The payment source used to fund the payment. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Builder, Default, Clone)] +#[builder(setter(strip_option), default)] pub struct PaymentSourceResponse { /// The payment card to use to fund a payment. Card can be a credit or debit card pub card: Option, @@ -740,7 +826,7 @@ pub struct PaymentSourceResponse { } /// The status of an order. -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OrderStatus { /// The order was created with the specified context. @@ -758,7 +844,8 @@ pub enum OrderStatus { /// An order represents a payment between two or more parties. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Builder)] +#[builder(setter(strip_option))] pub struct Order { /// The date and time when the transaction occurred. pub create_time: Option>, diff --git a/tests/auth_tests.rs b/tests/auth_tests.rs new file mode 100644 index 0000000..5e2cab3 --- /dev/null +++ b/tests/auth_tests.rs @@ -0,0 +1,36 @@ +use paypal_rs::{api::orders::*, data::orders::*}; +use paypal_rs::{data::common::Currency, Client, PaypalEnv}; +use paypal_rs::{AccessToken, HeaderParams}; +use wiremock::matchers::{basic_auth, body_string, header, method, path, BodyExactMatcher, HeaderExactMatcher}; +use wiremock::{ + matchers::{BasicAuthMatcher, BearerTokenMatcher}, + Mock, MockServer, ResponseTemplate, +}; + +fn create_client(url: &str) -> Client { + Client::new("clientid".to_string(), "secret".to_string(), PaypalEnv::Mock(url.to_string())) +} + +#[tokio::test] +async fn test_auth() -> color_eyre::Result<()> { + color_eyre::install()?; + + let mock_server = MockServer::start().await; + + let access_token: serde_json::Value = serde_json::from_str(include_str!("resources/oauth_token.json")).unwrap(); + + Mock::given(method("POST")) + .and(path("/v1/oauth2/token")) + .and(basic_auth("clientid", "secret")) + .and(header("Content-Type", "x-www-form-urlencoded")) + .and(body_string("grant_type=client_credentials")) + .respond_with(ResponseTemplate::new(200).set_body_json(&access_token)) + .mount(&mock_server) + .await; + + let mut client = create_client(&mock_server.uri()); + + client.get_access_token().await?; + + Ok(()) +} diff --git a/tests/orders_tests.rs b/tests/orders_tests.rs new file mode 100644 index 0000000..b6e6549 --- /dev/null +++ b/tests/orders_tests.rs @@ -0,0 +1,165 @@ +use paypal_rs::{ + api::orders::*, + data::{common::AddressBuilder, orders::*}, +}; +use paypal_rs::{Client, PaypalEnv}; +use wiremock::matchers::{basic_auth, bearer_token, body_string, header, method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +fn create_client(url: &str) -> Client { + Client::new( + "clientid".to_string(), + "secret".to_string(), + PaypalEnv::Mock(url.to_string()), + ) +} + +#[tokio::test] +async fn test_create_order() -> color_eyre::Result<()> { + color_eyre::install()?; + + let mock_server = MockServer::start().await; + + let access_token: serde_json::Value = serde_json::from_str(include_str!("resources/oauth_token.json")).unwrap(); + + Mock::given(method("POST")) + .and(path("/v1/oauth2/token")) + .and(basic_auth("clientid", "secret")) + .and(header("Content-Type", "x-www-form-urlencoded")) + .and(body_string("grant_type=client_credentials")) + .respond_with(ResponseTemplate::new(200).set_body_json(&access_token)) + .mount(&mock_server) + .await; + + let response_body: serde_json::Value = + serde_json::from_str(include_str!("resources/create_order_response.json")).unwrap(); + + Mock::given(method("POST")) + .and(path("/v2/checkout/orders")) + .and(bearer_token("TESTBEARERTOKEN")) + .and(header("Content-Type", "application/json")) + .respond_with(ResponseTemplate::new(200).set_body_json(&response_body)) + .mount(&mock_server) + .await; + + let mut client = create_client(&mock_server.uri()); + + client.get_access_token().await?; + + let order = OrderPayloadBuilder::default() + .intent(Intent::Authorize) + .purchase_units(vec![PurchaseUnitBuilder::default() + .reference_id("d9f80740-38f0-11e8-b467-0ed5f89f718b") + .amount(Amount::usd("100.00")) + .build()?]) + .payment_source( + OrderPaymentSourceBuilder::default() + .card( + PaymentCardBuilder::default() + .number("4111111111111111") + .expiry("2020-02") + .name("John Doe") + .billing_address( + AddressBuilder::default() + .address_line_1("2211 N First Street") + .address_line_2("17.3.160") + .admin_area_1("CA") + .admin_area_2("San Jose") + .postal_code("95131") + .country_code("US") + .build()?, + ) + .build()?, + ) + .stored_credential( + StoredCredentialBuilder::default() + .payment_initiator("MERCHANT") + .payment_type("RECURRING") + .usage("SUBSEQUENT") + .previous_network_transaction_reference( + TransactionReferenceBuilder::default() + .id("156GHJ654SFH543") + .network("VISA") + .build()?, + ) + .build()?, + ) + .build()?, + ) + .build()?; + + let create_order = CreateOrder::new(order); + + Ok(()) +} + +/* + +#[tokio::test] +async fn test_order2() -> anyhow::Result<()> { + let mock_server = MockServer::start().await; + + + let mut client = create_client(&mock_server.uri()); + client.get_access_token().await?; + + + let order = OrderPayloadBuilder::default() + .intent(Intent::Authorize) + .purchase_units(vec![PurchaseUnit::new(Amount::new(Currency::EUR, "10.0"))]) + .build()?; + + let ref_id = format!( + "TEST-{:?}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + ); + + let create_order = CreateOrder::new(order); + + let order_created = client + .execute_ext( + &create_order, + HeaderParams { + request_id: Some(ref_id.clone()), + ..Default::default() + }, + ) + .await; + + assert!(order_created.is_ok()); + + let order_created = order_created?; + + assert_ne!(order_created.id, ""); + assert_eq!(order_created.status, OrderStatus::Created); + assert_eq!(order_created.links.len(), 4); + + let show_order = ShowOrderDetails::new(&order_created.id); + + let show_order_result = client + .execute_ext( + &show_order, + HeaderParams { + request_id: Some(ref_id.clone()), + ..Default::default() + }, + ) + .await; + + assert!(show_order_result.is_ok()); + + let show_order_result = show_order_result?; + + assert_eq!(order_created.id, show_order_result.id); + assert_eq!(order_created.status, show_order_result.status); + + let authorize_order = AuthorizeOrder::new(&show_order_result.id); + + let res = client.execute(&authorize_order).await; + assert!(res.is_err()); // Fails with ORDER_NOT_APPROVED + + Ok(()) +} */ diff --git a/tests/resources/create_order_response.json b/tests/resources/create_order_response.json new file mode 100644 index 0000000..eac3ab3 --- /dev/null +++ b/tests/resources/create_order_response.json @@ -0,0 +1,67 @@ +{ + "id": "5O190127TN364715T", + "status": "COMPLETED", + "payment_source": { + "card": { + "last_digits": "1111", + "brand": "VISA", + "type": "CREDIT" + } + }, + "purchase_units": [ + { + "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b", + "payments": { + "authorizations": [ + { + "id": "0AW2184448108334S", + "status": "CREATED", + "amount": { + "currency_code": "USD", + "value": "100.00" + }, + "seller_protection": { + "status": "NOT_ELIGIBLE" + }, + "processor_response": { + "avs_code": "Z", + "response_code": "0000" + }, + "expiration_time": "2022-04-01T21:20:49Z", + "create_time": "2022-03-01T21:20:49Z", + "update_time": "2022-03-01T21:20:49Z", + "links": [ + { + "href": "https://api-m.paypal.com/v2/payments/authorizations/0AW2184448108334S", + "rel": "self", + "method": "GET" + }, + { + "href": "https://api-m.paypal.com/v2/payments/authorizations/0AW2184448108334S/capture", + "rel": "capture", + "method": "POST" + }, + { + "href": "https://api-m.paypal.com/v2/payments/authorizations/0AW2184448108334S/void", + "rel": "void", + "method": "POST" + }, + { + "href": "https://api-m.paypal.com/v2/payments/authorizations/0AW2184448108334S/reauthorize", + "rel": "reauthorize", + "method": "POST" + } + ] + } + ] + } + } + ], + "links": [ + { + "href": "https://api-m.paypal.com/v2/checkout/orders/5O190127TN364715T", + "rel": "self", + "method": "GET" + } + ] +} diff --git a/tests/resources/oauth_token.json b/tests/resources/oauth_token.json new file mode 100644 index 0000000..644d409 --- /dev/null +++ b/tests/resources/oauth_token.json @@ -0,0 +1,8 @@ +{ + "scope": "https://uri.paypal.com/services/invoicing https://uri.paypal.com/services/disputes/read-buyer https://uri.paypal.com/services/payments/realtimepayment https://uri.paypal.com/services/disputes/update-seller https://uri.paypal.com/services/payments/payment/authcapture openid https://uri.paypal.com/services/disputes/read-seller https://uri.paypal.com/services/payments/refund https://api-m.paypal.com/v1/vault/credit-card https://api-m.paypal.com/v1/payments/.* https://uri.paypal.com/payments/payouts https://api-m.paypal.com/v1/vault/credit-card/.* https://uri.paypal.com/services/subscriptions https://uri.paypal.com/services/applications/webhooks", + "access_token": "TESTBEARERTOKEN", + "token_type": "Bearer", + "app_id": "APP-80W284485P519543T", + "expires_in": 9999999, + "nonce": "2022-08-03T15:35:36ZaYZlGvEkV4yVSz8g6bAKFoGSEzuy3CQcz3ljhibkOHg" +} \ No newline at end of file