add res module

This commit is contained in:
Adrian Woźniak 2022-04-26 19:53:28 +02:00
parent cbc2a30e0e
commit 02c4473bc8
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
3 changed files with 89 additions and 52 deletions

View File

@ -3,8 +3,7 @@ use std::sync::Arc;
use parking_lot::Mutex; use parking_lot::Mutex;
use pay_u::{MerchantPosId, OrderCreateRequest}; use pay_u::{MerchantPosId, OrderCreateRequest};
use crate::model; use crate::model::{Price, Quantity};
use crate::model::Product;
#[macro_export] #[macro_export]
macro_rules! pay_async_handler { macro_rules! pay_async_handler {
@ -63,6 +62,7 @@ pub struct PaymentResult {
pub payu_order_id: String, pub payu_order_id: String,
} }
#[derive(Debug)]
pub struct Buyer { pub struct Buyer {
/// Required customer e-mail /// Required customer e-mail
pub email: String, pub email: String,
@ -76,22 +76,29 @@ pub struct Buyer {
pub language: String, pub language: String,
} }
impl From<model::Buyer> for pay_u::Buyer { impl From<Buyer> for pay_u::Buyer {
fn from(b: Buyer) -> Self { fn from(b: Buyer) -> Self {
pay_u::Buyer::new(b.email, b.phone, b.first_name, b.last_name, b.language) pay_u::Buyer::new(b.email, b.phone, b.first_name, b.last_name, b.language)
} }
} }
impl From<model::Product> for pay_u::Product { #[derive(Debug)]
pub struct Product {
pub name: String,
pub unit_price: Price,
pub quantity: Quantity,
}
impl From<Product> for pay_u::Product {
fn from(p: Product) -> Self { fn from(p: Product) -> Self {
todo!() pay_u::Product::new(p.name, **p.unit_price, **p.quantity as u32)
} }
} }
#[derive(Debug, actix::Message)] #[derive(Debug, actix::Message)]
#[rtype(result = "Result<PaymentResult>")] #[rtype(result = "Result<PaymentResult>")]
pub struct RequestPayment { pub struct RequestPayment {
pub products: Vec<model::Product>, pub products: Vec<Product>,
pub redirect_uri: String, pub redirect_uri: String,
pub currency: String, pub currency: String,
pub description: String, pub description: String,
@ -104,10 +111,12 @@ pub(crate) async fn request_payment(
client: PayUClient, client: PayUClient,
) -> Result<PaymentResult> { ) -> Result<PaymentResult> {
let mut client = &mut *client.lock(); let mut client = &mut *client.lock();
client.create_order( Ok(client
.create_order(
OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency) OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency)
.with_description(msg.description) .with_description(msg.description)
.with_notify_url(msg.redirect_uri) .with_notify_url(msg.redirect_uri)
.with_products(msg.products.into_iter().map(Into::into)), .with_products(msg.products.into_iter().map(Into::into)),
) )
.await?)
} }

View File

@ -131,12 +131,12 @@ impl Default for Audience {
} }
} }
#[derive(sqlx::Type, Serialize, Deserialize, Deref, From)] #[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, From)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct Price(NonNegative); pub struct Price(NonNegative);
#[derive(sqlx::Type, Serialize, Deserialize, Default, Deref, From)] #[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Deref, From)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct Quantity(NonNegative); pub struct Quantity(NonNegative);
@ -200,7 +200,7 @@ impl<'de> serde::Deserialize<'de> for Email {
} }
} }
#[derive(sqlx::Type, Serialize, Default, Deref, Display)] #[derive(sqlx::Type, Serialize, Default, Debug, Deref, Display)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct NonNegative(i32); pub struct NonNegative(i32);

View File

@ -39,6 +39,8 @@ pub enum Error {
Refund, Refund,
#[error("Create order returned invalid response")] #[error("Create order returned invalid response")]
CreateOrder, CreateOrder,
#[error("Operation failed with {0:?}")]
OpFailed(Status),
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -528,9 +530,12 @@ impl Status {
} }
} }
#[derive(Deserialize, Debug)] pub mod result {
use crate::{Refund, Status};
#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateOrderResult { pub struct CreateOrder {
/// Http status as a text /// Http status as a text
pub status: Status, pub status: Status,
/// Client should be redirected to this URI /// Client should be redirected to this URI
@ -541,13 +546,14 @@ pub struct CreateOrderResult {
pub ext_order_id: Option<String>, pub ext_order_id: Option<String>,
} }
#[derive(Deserialize, Debug)] #[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PartialRefundResult { pub struct PartialRefund {
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(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -570,6 +576,7 @@ impl RefundRequest {
pub fn description(&self) -> &str { pub fn description(&self) -> &str {
&self.description &self.description
} }
pub fn amount(&self) -> Price { pub fn amount(&self) -> Price {
self.amount self.amount
} }
@ -591,6 +598,8 @@ pub struct Refund {
pub mod notify { pub mod notify {
use serde::Deserialize; use serde::Deserialize;
use super::deserialize;
/// Payment notification object received on [super::Order].[notify_url] /// Payment notification object received on [super::Order].[notify_url]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -632,20 +641,31 @@ pub mod notify {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Order { pub struct Order {
#[serde(flatten)] pub notify_url: Option<String>,
pub order: super::OrderCreateRequest, /// Customer client IP address
pub customer_ip: String,
/// Secret pos ip. This is connected to PayU account
#[serde(deserialize_with = "deserialize::deserialize_i32")]
pub merchant_pos_id: super::MerchantPosId,
/// Transaction description
pub description: String,
/// 3 characters currency identifier, ex. PLN
pub currency_code: String,
/// Total price of the order in pennies (e.g. 1000 is 10.00 EUR).
/// Applies also to currencies without subunits (e.g. 1000 is 10
/// HUF).
#[serde(deserialize_with = "deserialize::deserialize_i32")]
pub total_amount: super::Price,
/// @see [Buyer]
pub buyer: Option<super::Buyer>,
/// List of products
pub products: Vec<super::Product>,
#[serde(skip_serializing)]
pub order_create_date: Option<String>,
pub pay_method: Option<super::PayMethod>, pub pay_method: Option<super::PayMethod>,
pub status: super::PaymentStatus, pub status: super::PaymentStatus,
} }
impl std::ops::Deref for Order {
type Target = super::OrderCreateRequest;
fn deref(&self) -> &Self::Target {
&self.order
}
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Prop { pub struct Prop {
@ -748,7 +768,7 @@ impl Client {
/// .await; /// .await;
/// } /// }
/// ``` /// ```
pub async fn create_order(&mut self, order: OrderCreateRequest) -> Result<CreateOrderResult> { pub async fn create_order(&mut self, order: OrderCreateRequest) -> Result<result::CreateOrder> {
self.authorize().await?; self.authorize().await?;
if order.total_amount if order.total_amount
!= order != order
@ -779,10 +799,14 @@ impl Client {
.text() .text()
.await?; .await?;
log::trace!("Response: {}", text); log::trace!("Response: {}", text);
serde_json::from_str(&text).map_err(|e| { let res: result::CreateOrder = serde_json::from_str(&text).map_err(|e| {
log::error!("{e:?}"); log::error!("{e:?}");
Error::CreateOrder Error::CreateOrder
}) })?;
if res.status.status_code != "Success" {
return Err(Error::OpFailed(res.status));
}
Ok(res)
} }
/// The PayU system fully supports refunds for processed payments, the /// The PayU system fully supports refunds for processed payments, the
@ -825,7 +849,7 @@ impl Client {
&mut self, &mut self,
order_id: OrderId, order_id: OrderId,
refund: RefundRequest, refund: RefundRequest,
) -> Result<PartialRefundResult> ) -> Result<result::PartialRefund>
where where
OrderId: std::fmt::Display, OrderId: std::fmt::Display,
{ {
@ -846,10 +870,14 @@ impl Client {
.text() .text()
.await?; .await?;
log::trace!("Response: {}", text); log::trace!("Response: {}", text);
serde_json::from_str::<'_, PartialRefundResult>(&text).map_err(|e| { let res: result::PartialRefund = serde_json::from_str(&text).map_err(|e| {
log::error!("Invalid PayU response {e:?}"); log::error!("Invalid PayU response {e:?}");
Error::Refund Error::Refund
}) })?;
if res.status.status_code.as_str() != "Success" {
return Err(Error::OpFailed(res.status));
}
Ok(res)
} }
/// Get or refresh token /// Get or refresh token
@ -953,7 +981,7 @@ mod tests {
#[test] #[test]
fn check_accepted_refund_json() { fn check_accepted_refund_json() {
let res = serde_json::from_str::<PartialRefundResult>(include_str!( let res = serde_json::from_str::<result::PartialRefund>(include_str!(
"../tests/responses/accepted_refund.json" "../tests/responses/accepted_refund.json"
)); ));
assert!(res.is_ok()); assert!(res.is_ok());
@ -995,14 +1023,14 @@ mod tests {
} }
#[test] #[test]
fn check_rejection_json() { fn check_rejection_json() {
let res = serde_json::from_str::<PartialRefundResult>(include_str!( let res = serde_json::from_str::<result::PartialRefund>(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::<PartialRefundResult>(include_str!( let res = serde_json::from_str::<result::PartialRefund>(include_str!(
"../tests/responses/custom_code_literal.json" "../tests/responses/custom_code_literal.json"
)); ));
assert!(res.is_ok()); assert!(res.is_ok());