Fix docs, fix get info

This commit is contained in:
Adrian Woźniak 2022-04-27 09:13:42 +02:00
parent 1f2ba82519
commit f55f2c4a0d
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
4 changed files with 129 additions and 40 deletions

2
Cargo.lock generated
View File

@ -2135,7 +2135,7 @@ checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
[[package]] [[package]]
name = "pay_u" name = "pay_u"
version = "0.1.4" version = "0.1.6"
dependencies = [ dependencies = [
"chrono", "chrono",
"derive_more", "derive_more",

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.5" version = "0.1.6"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -10,8 +10,8 @@ cargo add pay_u
```rust ```rust
async fn usage() { async fn usage() {
let client_id = std::env::var("PAYU_CLIENT_ID").unwrap(); let client_id = ClientId::new(std::env::var("PAYU_CLIENT_ID").unwrap());
let client_secret = std::env::var("PAYU_CLIENT_SECRET").unwrap(); let client_secret = ClientSecret::new(std::env::var("PAYU_CLIENT_SECRET").unwrap());
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse::<i32>().map(MerchantPosId::from).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");
@ -38,13 +38,27 @@ async fn usage() {
.with_product(Product::new("HDMI cable", 6000, 1)), .with_product(Product::new("HDMI cable", 6000, 1)),
) )
.await; .await;
// partial refund
let _res = client let _res = client
.partial_refund( .refund(
"H9LL64F37H160126GUEST000P01", OrderId::new("H9LL64F37H160126GUEST000P01"),
RefundRequest::new("Refund", 1000), RefundRequest::new("Refund", Some(1000)),
) )
.await; .await;
// Full refund
let _res = client
.refund(
OrderId::new("H9LL64F37H160126GUEST000P01"),
RefundRequest::new("Refund", None),
)
.await;
// Order details
let _res = client.order_details(OrderId::new("H9LL64F37H160126GUEST000P01")).await;
// Transactions
let _res = client.order_transactions(OrderId::new("H9LL64F37H160126GUEST000P01")).await;
} }
``` ```
@ -60,7 +74,7 @@ async fn checkout(session: Data<Session>, db: Data<Database>, payu: Data<Arc<Mut
let shopping_cart = db.send(LoadShoppingCart { user_id }).await??; let shopping_cart = db.send(LoadShoppingCart { user_id }).await??;
let shopping_cart_id = shopping_cart.id; let shopping_cart_id = shopping_cart.id;
let create_order_req: pay_u::OrderCreateRequest = shopping_cart.into(); let create_order_req: pay_u::OrderCreateRequest = shopping_cart.into();
let pay_u::CreateOrderResult { redirect_uri, order_id, .. } = payu.create_order(create_order_req).await?; let pay_u::res::CreateOrder { redirect_uri, order_id, .. } = payu.create_order(create_order_req).await?;
db.send(database::CreateOrder { shopping_cart_id, order_id }).await??; db.send(database::CreateOrder { shopping_cart_id, order_id }).await??;
HttpResponse::SeeOther().append_header((actix_web::http::header::LOCATION, redirect_uri)).body("") HttpResponse::SeeOther().append_header((actix_web::http::header::LOCATION, redirect_uri)).body("")
} }

View File

@ -47,6 +47,8 @@ pub enum Error {
OrderTransactions, OrderTransactions,
#[error("Failed to fetch order details")] #[error("Failed to fetch order details")]
OrderDetails, OrderDetails,
#[error("Failed to fetch order refunds")]
OrderRefunds,
#[error("PayU rejected to create order with status {status_code:?}")] #[error("PayU rejected to create order with status {status_code:?}")]
CreateFailed { CreateFailed {
status_code: String, status_code: String,
@ -664,11 +666,18 @@ pub mod res {
#[derive(serde::Deserialize, Debug)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PartialRefund { pub struct RefundDetails {
pub order_id: Option<String>, pub order_id: Option<String>,
pub refund: Option<Refund>, pub refund: Option<Refund>,
pub status: Status, pub status: Status,
} }
#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Refunds {
pub refunds: Vec<Refund>,
}
#[derive(serde::Deserialize, Debug)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionPayMethod { pub struct TransactionPayMethod {
@ -1068,7 +1077,7 @@ impl Client {
/// ///
/// ``` /// ```
/// # use pay_u::*; /// # use pay_u::*;
/// async fn perform_refund() { /// async fn partial_refund() {
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(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();
@ -1095,7 +1104,13 @@ impl Client {
&mut self, &mut self,
order_id: OrderId, order_id: OrderId,
refund: RefundRequest, refund: RefundRequest,
) -> Result<res::PartialRefund> { ) -> Result<res::RefundDetails> {
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct RefundWrapper {
refund: RefundRequest,
}
self.authorize().await?; self.authorize().await?;
if refund.description().trim().is_empty() { if refund.description().trim().is_empty() {
return Err(Error::NoDescription); return Err(Error::NoDescription);
@ -1107,13 +1122,13 @@ impl Client {
let text = client let text = client
.post(path) .post(path)
.bearer_auth(bearer) .bearer_auth(bearer)
.json(&refund) .json(&RefundWrapper { refund })
.send() .send()
.await? .await?
.text() .text()
.await?; .await?;
log::trace!("Response: {}", text); log::trace!("Response: {}", text);
let res: res::PartialRefund = serde_json::from_str(&text).map_err(|e| { let res: res::RefundDetails = serde_json::from_str(&text).map_err(|e| {
log::error!("Invalid PayU response {e:?}"); log::error!("Invalid PayU response {e:?}");
Error::Refund Error::Refund
})?; })?;
@ -1158,15 +1173,17 @@ impl Client {
let path = format!("{}/orders/{}", self.base_url(), order_id); let path = format!("{}/orders/{}", self.base_url(), order_id);
let client = get_client!(self); let client = get_client!(self);
let text = client let text = client
.post(path) .get(path)
.bearer_auth(bearer) .bearer_auth(bearer)
.send() .send()
.await? .await?
.text() .text()
.await?; .await?;
log::trace!("Response: {}", text); log::trace!("Response: {}", text);
dbg!(&text);
let mut res: OrdersInfo = serde_json::from_str(&text).map_err(|e| { let mut res: OrdersInfo = serde_json::from_str(&text).map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
dbg!(e);
Error::OrderDetails Error::OrderDetails
})?; })?;
if !res.status.is_success() { if !res.status.is_success() {
@ -1226,19 +1243,65 @@ impl Client {
let path = format!("{}/orders/{}/transactions", self.base_url(), order_id); let path = format!("{}/orders/{}/transactions", self.base_url(), order_id);
let client = get_client!(self); let client = get_client!(self);
let text = client let text = client
.post(path) .get(path)
.bearer_auth(bearer) .bearer_auth(bearer)
.send() .send()
.await? .await?
.text() .text()
.await?; .await?;
log::trace!("Response: {}", text); log::trace!("Response: {}", text);
dbg!(&text);
serde_json::from_str(&text).map_err(|e| { serde_json::from_str(&text).map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
dbg!(e);
Error::OrderTransactions Error::OrderTransactions
}) })
} }
/// The transaction retrieve request message enables you to retrieve the
/// details of transactions created for an order.
///
/// Using this endpoint is extremely useful if you would like to get bank
/// account details or card details.
///
/// > Please note that although card details are available right after
/// > transaction has been processed, the bank details may be available
/// > either after few minutes or on the next business day, depending on the
/// > bank.
///
/// # Examples
///
/// ```
/// # use pay_u::*;
/// async fn order_transactions() {
/// 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_transactions(OrderId::new("H9LL64F37H160126GUEST000P01"))
/// .await;
/// }
/// ```
pub async fn order_refunds(&mut self, order_id: OrderId) -> Result<Vec<Refund>> {
self.authorize().await?;
let bearer = self.bearer.as_ref().cloned().unwrap_or_default();
let path = format!("{}/orders/{}/refunds", self.base_url(), order_id);
let client = get_client!(self);
let text = client
.get(path)
.bearer_auth(bearer)
.send()
.await?
.text()
.await?;
log::trace!("Response: {}", text);
let res::Refunds { refunds, .. } = serde_json::from_str(&text).map_err(|e| {
log::error!("{e:?}");
Error::OrderRefunds
})?;
Ok(refunds)
}
/// Get or refresh token /// Get or refresh token
pub async fn authorize(&mut self) -> Result<bool> { pub async fn authorize(&mut self) -> Result<bool> {
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
@ -1292,6 +1355,7 @@ impl Client {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::res::CreateOrder;
fn build_client() -> Client { fn build_client() -> Client {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
@ -1304,9 +1368,8 @@ mod tests {
.with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999) .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999)
} }
#[tokio::test] async fn perform_create_order(client: &mut Client) -> Result<CreateOrder> {
async fn create_order() { client
let res = build_client()
.create_order( .create_order(
OrderCreateRequest::new( OrderCreateRequest::new(
Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"), Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
@ -1323,7 +1386,13 @@ mod tests {
.into_iter(), .into_iter(),
), ),
) )
.await; .await
}
#[tokio::test]
async fn create_order() {
let mut client = build_client();
let res = perform_create_order(&mut client).await;
if res.is_err() { if res.is_err() {
eprintln!("create_order res is {res:?}"); eprintln!("create_order res is {res:?}");
@ -1333,11 +1402,12 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn partial_refund() { async fn partial_refund() {
let res = build_client() let mut client = build_client();
.refund( let CreateOrder { order_id, .. } = perform_create_order(&mut client)
OrderId::new("H9LL64F37H160126GUEST000P01"), .await
RefundRequest::new("Refund", Some(1000)), .expect("Failed to create");
) let res = client
.refund(order_id, RefundRequest::new("Refund", Some(10)))
.await; .await;
if res.is_err() { if res.is_err() {
@ -1348,11 +1418,12 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn full_refund() { async fn full_refund() {
let res = build_client() let mut client = build_client();
.refund( let CreateOrder { order_id, .. } = perform_create_order(&mut client)
OrderId::new("H9LL64F37H160126GUEST000P01"), .await
RefundRequest::new("Refund", None), .expect("Failed to create");
) let res = client
.refund(order_id, RefundRequest::new("Refund", None))
.await; .await;
if res.is_err() { if res.is_err() {
@ -1363,21 +1434,25 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn order_details() { async fn order_details() {
let res = build_client() let mut client = build_client();
.order_details(OrderId::new("H9LL64F37H160126GUEST000P01")) let CreateOrder { order_id, .. } = perform_create_order(&mut client)
.await; .await
.expect("Failed to create");
let res = client.order_details(order_id).await;
if res.is_err() { if res.is_err() {
eprintln!("order_details res is {res:?}"); eprintln!("order_details res is {res:?}");
} }
assert!(matches!(res, Err(Error::OrderDetails))); assert!(matches!(res, Ok(res::OrderInfo { .. })));
} }
#[tokio::test] #[tokio::test]
async fn order_transactions() { async fn order_transactions() {
let res = build_client() let mut client = build_client();
.order_transactions(OrderId::new("H9LL64F37H160126GUEST000P01")) let CreateOrder { order_id, .. } = perform_create_order(&mut client)
.await; .await
.expect("Failed to create");
let res = client.order_transactions(order_id).await;
if res.is_err() { if res.is_err() {
eprintln!("order_transactions res is {res:?}"); eprintln!("order_transactions res is {res:?}");
} }
@ -1386,7 +1461,7 @@ mod tests {
#[test] #[test]
fn check_accepted_refund_json() { fn check_accepted_refund_json() {
let res = serde_json::from_str::<res::PartialRefund>(include_str!( let res = serde_json::from_str::<res::RefundDetails>(include_str!(
"../tests/responses/accepted_refund.json" "../tests/responses/accepted_refund.json"
)); ));
assert!(res.is_ok()); assert!(res.is_ok());
@ -1428,14 +1503,14 @@ mod tests {
} }
#[test] #[test]
fn check_rejection_json() { fn check_rejection_json() {
let res = serde_json::from_str::<res::PartialRefund>(include_str!( let res = serde_json::from_str::<res::RefundDetails>(include_str!(
"../tests/responses/rejection.json" "../tests/responses/rejection.json"
)); ));
assert!(res.is_ok()); assert!(res.is_ok());
} }
#[test] #[test]
fn check_custom_literal_json() { fn check_custom_literal_json() {
let res = serde_json::from_str::<res::PartialRefund>(include_str!( let res = serde_json::from_str::<res::RefundDetails>(include_str!(
"../tests/responses/custom_code_literal.json" "../tests/responses/custom_code_literal.json"
)); ));
assert!(res.is_ok()); assert!(res.is_ok());