Add full refund and order details

This commit is contained in:
eraden 2022-04-27 07:33:16 +02:00
parent 4886a76a76
commit 1f2ba82519
7 changed files with 324 additions and 62 deletions

View File

@ -42,10 +42,11 @@ impl PaymentManager {
merchant_pos_id: MerchantPosId, merchant_pos_id: MerchantPosId,
) -> Result<Self> ) -> Result<Self>
where where
ClientId: Into<String>, ClientId: Into<pay_u::ClientId>,
ClientSecret: Into<String>, ClientSecret: Into<pay_u::ClientSecret>,
{ {
let mut client = pay_u::Client::new(client_id, client_secret, merchant_pos_id); let mut client =
pay_u::Client::new(client_id.into(), client_secret.into(), merchant_pos_id);
client.authorize().await?; client.authorize().await?;
Ok(Self { Ok(Self {
client: Arc::new(Mutex::new(client)), client: Arc::new(Mutex::new(client)),

View File

@ -11,6 +11,7 @@ use actix_web::{App, HttpServer};
use gumdrop::Options; use gumdrop::Options;
use jemallocator::Jemalloc; use jemallocator::Jemalloc;
use password_hash::SaltString; use password_hash::SaltString;
use pay_u::MerchantPosId;
use validator::{validate_email, validate_length}; use validator::{validate_email, validate_length};
use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager}; use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager};
@ -189,7 +190,8 @@ async fn server(opts: ServerOpts) -> Result<()> {
std::env::var("PAYU_CLIENT_SECRET").expect("Missing PAYU_CLIENT_SECRET env"); std::env::var("PAYU_CLIENT_SECRET").expect("Missing PAYU_CLIENT_SECRET env");
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID") let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID")
.expect("Missing PAYU_CLIENT_MERCHANT_ID env") .expect("Missing PAYU_CLIENT_MERCHANT_ID env")
.parse() .parse::<i32>()
.map(MerchantPosId::from)
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number"); .expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id) payment_manager::PaymentManager::build(client_id, client_secret, merchant_id)
.await .await

View File

@ -1,7 +1,7 @@
[package] [package]
name = "pay_u" name = "pay_u"
description = "PayU Rest API wrapper" description = "PayU Rest API wrapper"
version = "0.1.4" version = "0.1.5"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -12,7 +12,7 @@ cargo add pay_u
async fn usage() { async fn usage() {
let client_id = std::env::var("PAYU_CLIENT_ID").unwrap(); let client_id = std::env::var("PAYU_CLIENT_ID").unwrap();
let client_secret = std::env::var("PAYU_CLIENT_SECRET").unwrap(); let client_secret = std::env::var("PAYU_CLIENT_SECRET").unwrap();
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse().unwrap(); let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse::<i32>().map(MerchantPosId::from).unwrap();
let mut client = Client::new(client_id, client_secret, merchant_id); let mut client = Client::new(client_id, client_secret, merchant_id);
client.authorize().await.expect("Invalid credentials"); client.authorize().await.expect("Invalid credentials");

View File

@ -9,6 +9,15 @@ where
d.deserialize_string(I32Visitor) d.deserialize_string(I32Visitor)
} }
pub(crate) fn deserialize_i32_newtype<'de, N: From<i32>, D>(
d: D,
) -> std::result::Result<N, D::Error>
where
D: serde::Deserializer<'de>,
{
d.deserialize_string(I32Visitor).map(N::from)
}
pub(crate) fn deserialize_u32<'de, D>(d: D) -> std::result::Result<u32, D::Error> pub(crate) fn deserialize_u32<'de, D>(d: D) -> std::result::Result<u32, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,

View File

@ -6,6 +6,8 @@ use std::sync::Arc;
use reqwest::redirect; use reqwest::redirect;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::res::OrdersInfo;
macro_rules! get_client { macro_rules! get_client {
($self:expr) => {{ ($self:expr) => {{
#[cfg(feature = "single-client")] #[cfg(feature = "single-client")]
@ -19,6 +21,8 @@ macro_rules! get_client {
}}; }};
} }
pub static SUCCESS: &str = "SUCCESS";
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Client is not authorized. No bearer token available")] #[error("Client is not authorized. No bearer token available")]
@ -41,7 +45,9 @@ pub enum Error {
CreateOrder, CreateOrder,
#[error("Failed to fetch order transactions")] #[error("Failed to fetch order transactions")]
OrderTransactions, OrderTransactions,
#[error("PayU rejected to create order. {status_code:?}")] #[error("Failed to fetch order details")]
OrderDetails,
#[error("PayU rejected to create order with status {status_code:?}")]
CreateFailed { CreateFailed {
status_code: String, status_code: String,
status_desc: Option<String>, status_desc: Option<String>,
@ -49,7 +55,7 @@ pub enum Error {
severity: Option<String>, severity: Option<String>,
code_literal: Option<CodeLiteral>, code_literal: Option<CodeLiteral>,
}, },
#[error("PayU rejected to perform refund. {status_code:?}")] #[error("PayU rejected to perform refund with status {status_code:?}")]
RefundFailed { RefundFailed {
status_code: String, status_code: String,
status_desc: Option<String>, status_desc: Option<String>,
@ -57,6 +63,24 @@ pub enum Error {
severity: Option<String>, severity: Option<String>,
code_literal: Option<CodeLiteral>, code_literal: Option<CodeLiteral>,
}, },
#[error("PayU rejected order details request with status {status_code:?}")]
OrderDetailsFailed {
status_code: String,
status_desc: Option<String>,
code: Option<String>,
severity: Option<String>,
code_literal: Option<CodeLiteral>,
},
#[error("PayU rejected order transactions details request with status {status_code:?}")]
OrderTransactionsFailed {
status_code: String,
status_desc: Option<String>,
code: Option<String>,
severity: Option<String>,
code_literal: Option<CodeLiteral>,
},
#[error("PayU returned order details but without any order")]
NoOrderInDetails,
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -80,6 +104,57 @@ impl OrderId {
} }
} }
/// PayU internal order id
#[derive(
Debug,
serde::Deserialize,
serde::Serialize,
Copy,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
derive_more::Constructor,
)]
#[serde(transparent)]
pub struct MerchantPosId(pub i32);
#[derive(
Debug,
Clone,
serde::Deserialize,
serde::Serialize,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
#[serde(transparent)]
pub struct ClientId(pub String);
impl ClientId {
pub fn new<S: Into<String>>(id: S) -> Self {
Self(id.into())
}
}
#[derive(
Debug,
Clone,
serde::Deserialize,
serde::Serialize,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
#[serde(transparent)]
pub struct ClientSecret(pub String);
impl ClientSecret {
pub fn new<S: Into<String>>(id: S) -> Self {
Self(id.into())
}
}
/// PayU payment status. /// PayU payment status.
/// ///
/// Each payment is initially Pending and can change according to following /// Each payment is initially Pending and can change according to following
@ -212,7 +287,6 @@ impl Buyer {
pub type Price = i32; pub type Price = i32;
pub type Quantity = u32; pub type Quantity = u32;
pub type MerchantPosId = i32;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -280,8 +354,8 @@ pub struct OrderCreateRequest {
customer_ip: String, customer_ip: String,
/// Secret pos ip. This is connected to PayU account /// Secret pos ip. This is connected to PayU account
#[serde( #[serde(
serialize_with = "serialize::serialize_i32", serialize_with = "serialize::serialize_newtype",
deserialize_with = "deserialize::deserialize_i32" deserialize_with = "deserialize::deserialize_i32_newtype"
)] )]
merchant_pos_id: MerchantPosId, merchant_pos_id: MerchantPosId,
/// Transaction description /// Transaction description
@ -316,7 +390,7 @@ impl OrderCreateRequest {
Self { Self {
notify_url: None, notify_url: None,
customer_ip: customer_ip.into(), customer_ip: customer_ip.into(),
merchant_pos_id: 0, merchant_pos_id: 0.into(),
description: String::from(""), description: String::from(""),
currency_code: currency.into(), currency_code: currency.into(),
total_amount: 0, total_amount: 0,
@ -544,7 +618,7 @@ impl Status {
/// assert_eq!(status.is_success(), true); /// assert_eq!(status.is_success(), true);
/// ``` /// ```
pub fn is_success(&self) -> bool { pub fn is_success(&self) -> bool {
self.status_code.as_str() == "SUCCESS" self.status_code.as_str() == SUCCESS
} }
/// Returns http status /// Returns http status
@ -565,6 +639,13 @@ impl Status {
} }
} }
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Prop {
pub name: String,
pub value: String,
}
pub mod res { pub mod res {
use crate::{OrderId, Refund, Status}; use crate::{OrderId, Refund, Status};
@ -588,27 +669,27 @@ pub mod res {
pub refund: Option<Refund>, pub refund: Option<Refund>,
pub status: Status, pub status: Status,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionPayMethod { pub struct TransactionPayMethod {
pub value: String, pub value: String,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardProfile { pub enum CardProfile {
Consumer, Consumer,
Business, Business,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardClassification { pub enum CardClassification {
Debit, Debit,
Credit, Credit,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionCartData { pub struct TransactionCartData {
/// // "543402******4014", /// // "543402******4014",
@ -632,40 +713,89 @@ pub mod res {
pub first_transaction_id: String, pub first_transaction_id: String,
} }
#[derive(serde::Deserialize)] /// > Installment proposal on the Sandbox environment is not related to the
/// > order amount and always returns data for 480 PLN.
#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionCardInstallmentProposal {} pub struct TransactionCardInstallmentProposal {
/// Example: "5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed"
pub proposal_id: String,
}
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionCart { pub struct TransactionCart {
pub cart_data: TransactionCartData, pub cart_data: TransactionCartData,
pub card_installment_proposal: TransactionCardInstallmentProposal, pub card_installment_proposal: TransactionCardInstallmentProposal,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Transaction { pub struct Transaction {
pub pay_method: TransactionPayMethod, pub pay_method: TransactionPayMethod,
pub payment_flow: String, pub payment_flow: String,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Transactions { pub struct Transactions {
pub transactions: Vec<Transaction>, pub transactions: Vec<Transaction>,
} }
#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Order {
/// Example: "{orderId}",
pub order_id: super::OrderId,
/// Example: "358766",
pub ext_order_id: Option<String>,
/// Example: "2014-10-27T14:58:17.443+01:00",
pub order_create_date: String,
/// Example: "http://localhost/OrderNotify/",
pub notify_url: Option<String>,
/// Example: "127.0.0.1",
pub customer_ip: String,
/// Example: "145227",
pub merchant_pos_id: String,
/// Example: "New order",
pub description: String,
/// Example: "PLN",
pub currency_code: String,
/// Example: "3200",
pub total_amount: String,
/// Example: "NEW",
pub status: String,
/// Example: `[{"name":"Product1","unitPrice":"1000","quantity":"1"}]`
pub products: Vec<super::Product>,
}
#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct OrdersInfo {
pub orders: Vec<Order>,
pub status: super::Status,
pub properties: Option<Vec<crate::Prop>>,
}
#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct OrderInfo {
pub order: Order,
pub status: super::Status,
pub properties: Option<Vec<crate::Prop>>,
}
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RefundRequest { pub struct RefundRequest {
description: String, description: String,
amount: Price, #[serde(skip_serializing_if = "Option::is_none")]
amount: Option<Price>,
} }
impl RefundRequest { impl RefundRequest {
pub fn new<Description>(description: Description, amount: Price) -> Self pub fn new<Description>(description: Description, amount: Option<Price>) -> Self
where where
Description: Into<String>, Description: Into<String>,
{ {
@ -679,7 +809,7 @@ impl RefundRequest {
&self.description &self.description
} }
pub fn amount(&self) -> Price { pub fn amount(&self) -> Option<Price> {
self.amount self.amount
} }
} }
@ -709,7 +839,7 @@ pub mod notify {
pub struct StatusUpdate { pub struct StatusUpdate {
pub order: Order, pub order: Order,
pub local_receipt_date_time: Option<String>, pub local_receipt_date_time: Option<String>,
pub properties: Option<Vec<Prop>>, pub properties: Option<Vec<super::Prop>>,
pub status: Option<super::Status>, pub status: Option<super::Status>,
} }
@ -748,7 +878,7 @@ pub mod notify {
/// Customer client IP address /// Customer client IP address
pub customer_ip: String, pub customer_ip: String,
/// Secret pos ip. This is connected to PayU account /// Secret pos ip. This is connected to PayU account
#[serde(deserialize_with = "deserialize::deserialize_i32")] #[serde(deserialize_with = "deserialize::deserialize_i32_newtype")]
pub merchant_pos_id: super::MerchantPosId, pub merchant_pos_id: super::MerchantPosId,
/// Transaction description /// Transaction description
pub description: String, pub description: String,
@ -768,20 +898,13 @@ pub mod notify {
pub pay_method: Option<super::PayMethod>, pub pay_method: Option<super::PayMethod>,
pub status: super::PaymentStatus, pub status: super::PaymentStatus,
} }
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Prop {
pub name: String,
pub value: String,
}
} }
pub struct Client { pub struct Client {
sandbox: bool, sandbox: bool,
merchant_pos_id: MerchantPosId, merchant_pos_id: MerchantPosId,
client_id: String, client_id: ClientId,
client_secret: String, client_secret: ClientSecret,
bearer: Option<String>, bearer: Option<String>,
bearer_expires_at: chrono::DateTime<chrono::Utc>, bearer_expires_at: chrono::DateTime<chrono::Utc>,
#[cfg(feature = "single-client")] #[cfg(feature = "single-client")]
@ -790,23 +913,19 @@ pub struct Client {
impl Client { impl Client {
/// Create new PayU client /// Create new PayU client
pub fn new<ClientId, ClientSecret>( pub fn new(
client_id: ClientId, client_id: ClientId,
client_secret: ClientSecret, client_secret: ClientSecret,
merchant_pos_id: MerchantPosId, merchant_pos_id: MerchantPosId,
) -> Self ) -> Self {
where
ClientId: Into<String>,
ClientSecret: Into<String>,
{
#[cfg(feature = "single-client")] #[cfg(feature = "single-client")]
{ {
Self { Self {
bearer: None, bearer: None,
sandbox: false, sandbox: false,
merchant_pos_id, merchant_pos_id,
client_id: client_id.into(), client_id,
client_secret: client_secret.into(), client_secret,
bearer_expires_at: chrono::Utc::now(), bearer_expires_at: chrono::Utc::now(),
client: Arc::new(Self::build_client()), client: Arc::new(Self::build_client()),
} }
@ -817,8 +936,8 @@ impl Client {
bearer: None, bearer: None,
sandbox: false, sandbox: false,
merchant_pos_id, merchant_pos_id,
client_id: client_id.into(), client_id,
client_secret: client_secret.into(), client_secret,
bearer_expires_at: chrono::Utc::now(), bearer_expires_at: chrono::Utc::now(),
} }
} }
@ -849,9 +968,9 @@ impl Client {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// # use pay_u::{Client, OrderCreateRequest, Product, Buyer}; /// # use pay_u::*;
/// async fn pay() { /// async fn pay() {
/// let mut client = Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746) /// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
/// .sandbox() /// .sandbox()
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000); /// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000);
/// let res = client /// let res = client
@ -906,7 +1025,7 @@ impl Client {
log::error!("{e:?}"); log::error!("{e:?}");
Error::CreateOrder Error::CreateOrder
})?; })?;
if res.status.status_code != "Success" { if !res.status.is_success() {
let Status { let Status {
status_code, status_code,
status_desc, status_desc,
@ -950,18 +1069,29 @@ impl Client {
/// ``` /// ```
/// # use pay_u::*; /// # use pay_u::*;
/// async fn perform_refund() { /// async fn perform_refund() {
/// let mut client = Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746) /// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000) /// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
/// .sandbox(); /// .sandbox();
/// let res = client /// let res = client
/// .partial_refund( /// .refund(
/// OrderId::new("H9LL64F37H160126GUEST000P01"), /// OrderId::new("H9LL64F37H160126GUEST000P01"),
/// RefundRequest::new("Refund", 1000), /// RefundRequest::new("Refund", Some(1000)),
/// )
/// .await;
/// }
/// async fn full_refund() {
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
/// .sandbox();
/// let res = client
/// .refund(
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
/// RefundRequest::new("Refund", None),
/// ) /// )
/// .await; /// .await;
/// } /// }
/// ``` /// ```
pub async fn partial_refund( pub async fn refund(
&mut self, &mut self,
order_id: OrderId, order_id: OrderId,
refund: RefundRequest, refund: RefundRequest,
@ -987,7 +1117,7 @@ impl Client {
log::error!("Invalid PayU response {e:?}"); log::error!("Invalid PayU response {e:?}");
Error::Refund Error::Refund
})?; })?;
if res.status.status_code.as_str() != "Success" { if !res.status.is_success() {
let Status { let Status {
status_code, status_code,
status_desc, status_desc,
@ -1006,6 +1136,66 @@ impl Client {
Ok(res) Ok(res)
} }
/// Order details request. You may use it to completely remove `Order`
/// persistence and use extOrderId to connect your data with PayU data.
///
/// # Examples
///
/// ```
/// # use pay_u::*;
/// async fn order_details() {
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
/// .sandbox();
/// let res = client
/// .order_details(OrderId::new("H9LL64F37H160126GUEST000P01"))
/// .await;
/// }
/// ```
pub async fn order_details(&mut self, order_id: OrderId) -> Result<res::OrderInfo> {
self.authorize().await?;
let bearer = self.bearer.as_ref().cloned().unwrap_or_default();
let path = format!("{}/orders/{}", self.base_url(), order_id);
let client = get_client!(self);
let text = client
.post(path)
.bearer_auth(bearer)
.send()
.await?
.text()
.await?;
log::trace!("Response: {}", text);
let mut res: OrdersInfo = serde_json::from_str(&text).map_err(|e| {
log::error!("{e:?}");
Error::OrderDetails
})?;
if !res.status.is_success() {
let Status {
status_code,
status_desc,
code,
severity,
code_literal,
} = res.status;
return Err(Error::OrderDetailsFailed {
status_code,
status_desc,
code,
severity,
code_literal,
});
}
Ok(res::OrderInfo {
order: if res.orders.is_empty() {
return Err(Error::NoOrderInDetails);
} else {
res.orders.remove(0)
},
status: res.status,
properties: res.properties,
})
}
/// The transaction retrieve request message enables you to retrieve the /// The transaction retrieve request message enables you to retrieve the
/// details of transactions created for an order. /// details of transactions created for an order.
/// ///
@ -1016,12 +1206,13 @@ impl Client {
/// > transaction has been processed, the bank details may be available /// > transaction has been processed, the bank details may be available
/// > either after few minutes or on the next business day, depending on the /// > either after few minutes or on the next business day, depending on the
/// > bank. /// > bank.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # use pay_u::*; /// # use pay_u::*;
/// async fn order_transactions() { /// async fn order_transactions() {
/// let mut client = Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746) /// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000) /// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
/// .sandbox(); /// .sandbox();
/// let res = client /// let res = client
@ -1104,9 +1295,13 @@ mod tests {
fn build_client() -> Client { fn build_client() -> Client {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746) Client::new(
.sandbox() ClientId::new("145227"),
.with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999) ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"),
MerchantPosId::new(300746),
)
.sandbox()
.with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999)
} }
#[tokio::test] #[tokio::test]
@ -1129,22 +1324,65 @@ mod tests {
), ),
) )
.await; .await;
if res.is_err() {
eprintln!("create_order res is {res:?}");
}
assert!(res.is_ok()); assert!(res.is_ok());
} }
#[tokio::test] #[tokio::test]
async fn partial_refund() { async fn partial_refund() {
let res = build_client() let res = build_client()
.partial_refund( .refund(
OrderId::new("H9LL64F37H160126GUEST000P01"), OrderId::new("H9LL64F37H160126GUEST000P01"),
RefundRequest::new("Refund", 1000), RefundRequest::new("Refund", Some(1000)),
) )
.await; .await;
assert!(matches!(res, Ok(_)));
if res.is_err() {
eprintln!("partial_refund res is {res:?}");
}
assert!(matches!(res, Err(Error::RefundFailed { .. })));
} }
#[tokio::test] #[tokio::test]
async fn check_refund() {} async fn full_refund() {
let res = build_client()
.refund(
OrderId::new("H9LL64F37H160126GUEST000P01"),
RefundRequest::new("Refund", None),
)
.await;
if res.is_err() {
eprintln!("full_refund res is {res:?}");
}
assert!(matches!(res, Err(Error::RefundFailed { .. })));
}
#[tokio::test]
async fn order_details() {
let res = build_client()
.order_details(OrderId::new("H9LL64F37H160126GUEST000P01"))
.await;
if res.is_err() {
eprintln!("order_details res is {res:?}");
}
assert!(matches!(res, Err(Error::OrderDetails)));
}
#[tokio::test]
async fn order_transactions() {
let res = build_client()
.order_transactions(OrderId::new("H9LL64F37H160126GUEST000P01"))
.await;
if res.is_err() {
eprintln!("order_transactions res is {res:?}");
}
assert!(matches!(res, Err(Error::OrderTransactions)));
}
#[test] #[test]
fn check_accepted_refund_json() { fn check_accepted_refund_json() {

View File

@ -1,3 +1,5 @@
use std::fmt::Display;
pub(crate) fn serialize_i32<S>(v: &i32, ser: S) -> std::result::Result<S::Ok, S::Error> pub(crate) fn serialize_i32<S>(v: &i32, ser: S) -> std::result::Result<S::Ok, S::Error>
where where
S: serde::Serializer, S: serde::Serializer,
@ -5,6 +7,16 @@ where
ser.serialize_str(&format!("{v}")) ser.serialize_str(&format!("{v}"))
} }
pub(crate) fn serialize_newtype<N: Display, S>(
v: &N,
ser: S,
) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.serialize_str(&format!("{v}"))
}
pub(crate) fn serialize_u32<S>(v: &u32, ser: S) -> std::result::Result<S::Ok, S::Error> pub(crate) fn serialize_u32<S>(v: &u32, ser: S) -> std::result::Result<S::Ok, S::Error>
where where
S: serde::Serializer, S: serde::Serializer,